Skip to content

saas-EPIC-CAT — Products Catalog gap backlog

Epic ID: saas-EPIC-CAT
Repository: ConnectSoft.Saas.ProductsCatalogTemplate
Aggregate root: Product
Status: Not Started
Evidence: saas-gap-deep-analysis.md §4.2
Last verified: 2026-05-27
Master backlog: saas-gap-implementation-backlog.md (when authored)

Epic summary

Closes wave-1 gaps in the Products Catalog bounded context: Orleans write-path wiring, documentation drift, optional structural write APIs (or formal deferral), EntitlementsChanged fan-out, NHibernate transactional outbox (future work), query surface extensions, and grain test coverage.

Order Feature ID Title
[020] saas-CAT-F01 Orleans write-path wiring through IProductEditorGrain
[021] saas-CAT-F02 Doc drift fixes (aggregate-root.md lifecycle states + ADR-0001 Feature placement)
[022] saas-CAT-F03 Optional structural write APIs (SLA / EditionPricing / EditionBusinessModel) OR formal defer ADR
[023] saas-CAT-F04 EntitlementsChangedEvent fan-out on pricing / business-model / SLA bindings
[024] saas-CAT-F05 NHibernate transactional outbox (ADR-0003 future work)
[025] saas-CAT-F06 Additional query operations (ListEditions; get-by-id for pricing/business)
[026] saas-CAT-F07 Grain test coverage beyond CreateProduct

[020] saas-CAT-F01 — Orleans write-path wiring through IProductEditorGrain

Type: Feature
Parent: saas-EPIC-CAT
Implementation order: 020
Status: Not Started
Evidence: ConnectSoft.Saas.ProductsCatalogTemplate/src/ConnectSoft.Saas.ProductsCatalog.ServiceModel.RestApi/ProductsController.csIProductsProcessor; no GetGrain in ServiceModel layer
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, P1, orleans
Priority: P1
Effort: L
Dependencies: saas-INTEG-F06 (Orleans write-path ADR outcome)
Blocks: saas-CAT-F07
Source gap analysis: §3.6 Orleans write-path, §4.2 Products Catalog — Actor / Orleans

Description (full):

The Products Catalog template registers Orleans, defines IProductEditorActor / IProductEditorGrain (ProductEditorGrain.cs), and implements all write operations on the actor interface (CreateProductAsync, UpdateProductAsync, RetireProductAsync, AddEditionAsync, UpdateEditionAsync, ActivateFeatureAsync, DeactivateFeatureAsync). However, the public API path bypasses the grain entirely: ProductsController and GrpcProductManagementService inject IProductsProcessor and call DefaultProductsProcessor directly. No GrainFactory.GetGrain<IProductEditorGrain> usage exists in the ServiceModel or ApplicationModel write adapters.

This violates the documented actor model intent (single-threaded per-product write serialization keyed {tenantId}/{productId}) and is flagged as finding C-001 / cross-cutting X-007. The outcome of saas-INTEG-F06 (wire grains vs processor-only + ADR) governs whether this Feature implements routing or documents an accepted bypass. Assuming ADR-INTEG-001 option (A) — wire grains — this Feature introduces a thin write adapter that resolves the composite grain key from tenant + product id and delegates all management commands through IProductEditorGrain, keeping DefaultProductsProcessor as the grain's internal delegate (no duplicate domain logic).

Acceptance criteria (testable):

  • AC-1: Given a REST or gRPC management command (CreateProduct, UpdateProduct, RetireProduct, edition/feature mutations), when the request is handled, then the code path invokes IProductEditorGrain (not IProductsProcessor directly) from ProductsController / GrpcProductManagementService.
  • AC-2: Given concurrent write requests for the same {tenantId}/{productId}, when routed through the grain, then Orleans activation guarantees single-threaded execution (verified by grain silo test or integration test with ordering assertion).
  • AC-3: Given saas-INTEG-F06 selects processor-only bypass, when ADR-INTEG-001 option (B) is accepted, then this Feature is marked Deferred with ADR citation and no grain routing is added.
  • AC-4: Query operations (IProductQueryService) continue to call read-side services/processors directly (no grain on read path).

Implementation notes (full):

  • Files to touch: ProductsController.cs, GrpcProductManagementService.cs, new IProductEditorGrainFactory or inline key builder using ProductEditorGrain.TryParseKey / key separator constant.
  • Code symbols: IProductEditorGrain, ProductEditorGrain, IProductsProcessor, ProductEditorGrain.KeySeparator.
  • ADRs: follow saas-INTEG-F06 / ADR-INTEG-001 outcome before implementation.
  • Tests: extend ProductEditorGrainSiloTests or add ServiceModel integration test proving REST → grain → processor chain.
  • Migration / data impact: none (routing change only).

Out of scope:

  • Grain persistent state (grain remains stateless coordinator per existing design).
  • Read-path query routing.
  • Cross-repo topic or envelope changes (saas-INTEG-F03).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated (docs/messaging.md, actor section if present)
  • Status updated in master + mirrors

[020] saas-CAT-S01.1 — Route all management writes through ProductEditorGrain

Type: User Story
Parent: saas-CAT-F01
Implementation order: 020
Status: Not Started
Evidence: ProductsController.cs, GrpcProductManagementService.cs — direct IProductsProcessor injection
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, orleans
Priority: P1
Effort: M
Dependencies: saas-INTEG-F06
Blocks: saas-CAT-T01.1.1, saas-CAT-T01.1.2
Source gap analysis: §4.2 Products Catalog — Actor / Orleans

Description (full):

As a platform engineer maintaining the Products Catalog template, I need every product management command exposed via REST and gRPC to route through IProductEditorGrain so that concurrent mutations on the same product aggregate are serialized by Orleans and the runtime matches the documented actor model.

Today ProductsController and GrpcProductManagementService resolve IProductsProcessor from DI and call processor methods synchronously within the HTTP/gRPC request scope. The grain at ProductEditorGrain.cs already delegates to the same processor but is unreachable from the API surface. The story delivers a write adapter (inline or dedicated service) that builds the composite grain key {tenantId}/{productId} (for create, product id may be pre-generated or returned post-create per existing pattern), obtains the grain via IGrainFactory, and forwards the domain input DTO unchanged.

Acceptance criteria (testable):

  • AC-1: All seven IProductEditorActor operations are reachable from REST and gRPC management endpoints via grain dispatch.
  • AC-2: Tenant id in the request body/query must match the tenant encoded in the grain key; mismatch throws InvalidOperationException (consistent with existing ProductEditorGrainSiloTests boundary test).
  • AC-3: No regression in existing acceptance tests under tests/ConnectSoft.Saas.ProductsCatalog.AcceptanceTests/ProductsManagement/.

Implementation notes (full):

  • Introduce IProductWriteCoordinator or extend controllers with IGrainFactory injection registered in ProductsCatalogMicroserviceRegistration.cs.
  • Reuse ProductEditorGrain.TryParseKey for key validation helpers.
  • Preserve scoped IProductsProcessor lifetime inside grain activation (existing grain constructor injection).

Out of scope:

  • Changing processor business rules or NHibernate mappings.
  • Orleans cluster configuration changes.

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[020] saas-CAT-T01.1.1 — Add grain write coordinator and wire REST controller

Type: Task
Parent: saas-CAT-S01.1
Implementation order: 020
Status: Not Started
Evidence: ProductsController.csIProductsProcessor productsProcessor field
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, orleans, rest
Priority: P1
Effort: M
Dependencies: saas-INTEG-F06
Blocks:
Source gap analysis: §3.6 Orleans write-path

Description (full):

Implement a write coordinator service that wraps IGrainFactory.GetGrain<IProductEditorGrain>(key) and replace direct IProductsProcessor calls in ProductsController with coordinator calls for all management operations. Register the coordinator in DI. Ensure OpenTelemetry scopes and existing validation/mapping pipelines remain unchanged.

Acceptance criteria (testable):

  • AC-1: ProductsController no longer injects IProductsProcessor for write operations (may retain for reads if shared controller implements both interfaces).
  • AC-2: Unit or integration test asserts GetGrain<IProductEditorGrain> is invoked for at least CreateProduct and UpdateProduct.
  • AC-3: Build succeeds; existing REST acceptance scenarios pass.

Implementation notes (full):

  • Files: ProductsController.cs, ProductsCatalogMicroserviceRegistration.cs, new ProductEditorGrainCoordinator.cs under ApplicationModel or ServiceModel.
  • Symbol: IProductEditorGrain, ProductEditorGrain.KeySeparator.

Out of scope:

  • gRPC adapter (separate task).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[020] saas-CAT-T01.1.2 — Wire gRPC management service through grain coordinator

Type: Task
Parent: saas-CAT-S01.1
Implementation order: 020
Status: Not Started
Evidence: GrpcProductManagementService.cs — direct IProductsProcessor calls
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, orleans, grpc
Priority: P1
Effort: S
Dependencies: saas-CAT-T01.1.1
Blocks:
Source gap analysis: §4.2 Products Catalog — ServiceModel

Description (full):

Refactor GrpcProductManagementService to use the same grain write coordinator introduced for REST, ensuring gRPC and REST share identical write semantics and grain key construction.

Acceptance criteria (testable):

  • AC-1: GrpcProductManagementService delegates all write ops to the shared coordinator (no direct processor calls).
  • AC-2: gRPC acceptance tests in ProductsManagementGrpcStepDefinitions.cs pass without modification to scenario data.
  • AC-3: Code search confirms zero IProductsProcessor write invocations from ServiceModel layer (reads excepted).

Implementation notes (full):

  • Files: GrpcProductManagementService.cs, shared coordinator from T01.1.1.
  • Reuse DI registration from REST task.

Out of scope:

  • gRPC query service (GrpcProductQueryService).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[021] saas-CAT-F02 — Doc drift fixes (aggregate-root.md lifecycle states + ADR-0001 Feature placement)

Type: Feature
Parent: saas-EPIC-CAT
Implementation order: 021
Status: Not Started
Evidence: docs/aggregate-root.md state diagram vs processor/entity lifecycle; docs/adr/0001-one-aggregate-root-per-repo.md Feature entity placement
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, docs, P2
Priority: P2
Effort: S
Dependencies:
Blocks:
Source gap analysis: §4.2 Products Catalog, finding C-002

Description (full):

Documentation in the Products Catalog repo has drifted from runtime truth. docs/aggregate-root.md depicts lifecycle states (Draft, Active, Suspended, Decommissioned) that may not align with ProductEntity / processor transitions or published event vocabulary. ADR-0001 discusses one aggregate root per repo but may incorrectly describe Feature as a separate aggregate root rather than a peer entity inside the Product aggregate (per ADR-0002 edition-inside-product). Finding C-002 tracks this drift.

This Feature reconciles human-authored docs with code and canonical DDD references (saas-platform-ddd-entities.md), ensuring onboarding developers and gap-analysis readers see accurate lifecycle and aggregate boundaries.

Acceptance criteria (testable):

  • AC-1: docs/aggregate-root.md state machine matches DefaultProductsProcessor allowed transitions and ProductStatusEnumeration (or documents intentional differences with ADR citation).
  • AC-2: ADR-0001 and docs/domain/product-aggregate.md consistently describe Feature, PricingModel, and BusinessModel as entities/VOs within the Product aggregate, not separate aggregate roots.
  • AC-3: docs/reference/events.md lifecycle event names align with ProductsCatalogConstants.EventTopics and MessagingModel event types.
  • AC-4: mkdocs build passes for the Products Catalog docs site.

Implementation notes (full):

  • Files: docs/aggregate-root.md, docs/adr/0001-one-aggregate-root-per-repo.md, docs/domain/product-aggregate.md, docs/reference/events.md.
  • Cross-check: ProductEntity.cs, DefaultProductsProcessor.cs, ProductsCatalogConstants.cs.
  • No code changes unless doc review reveals a one-line comment fix in processor.

Out of scope:

  • CompanyDocumentation canonical DDD edits (separate program).
  • Structural API implementation (saas-CAT-F03).

Definition of done:

  • All AC pass
  • Tests added/updated and green (doc build)
  • Docs updated
  • Status updated in master + mirrors

[021] saas-CAT-S02.1 — Reconcile aggregate-root lifecycle documentation with code

Type: User Story
Parent: saas-CAT-F02
Implementation order: 021
Status: Not Started
Evidence: docs/aggregate-root.md mermaid state diagram
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, docs
Priority: P2
Effort: S
Dependencies:
Blocks: saas-CAT-T02.1.1, saas-CAT-T02.1.2
Source gap analysis: Finding C-002

Description (full):

As a developer onboarding to Products Catalog, I need the aggregate-root documentation to accurately describe product lifecycle states and transitions so I do not implement invalid state changes or misinterpret retirement semantics.

Review DefaultProductsProcessor transition guards, entity status fields, and outbound integration events. Update the mermaid diagram and narrative in aggregate-root.md to match runtime behavior, including whether Suspended is implemented or deferred.

Acceptance criteria (testable):

  • AC-1: Every transition arrow in aggregate-root.md corresponds to a processor method or is marked "not implemented" with link to deferral ADR.
  • AC-2: Terminal state Decommissioned (or Retired if code uses alternate term) is named consistently across docs and code.
  • AC-3: Peer reviewer confirms doc matches grep of ProductStatusEnumeration and retire/suspend methods.

Implementation notes (full):

  • Compare with ConnectSoft.Saas.ProductsCatalog.json descriptor status vocabulary if present.

Out of scope:

  • Implementing missing lifecycle commands (unless doc reveals gap requiring new Feature).

Definition of done:

  • All AC pass
  • Docs updated
  • Status updated in master + mirrors

[021] saas-CAT-T02.1.1 — Audit processor transitions and update aggregate-root.md

Type: Task
Parent: saas-CAT-S02.1
Implementation order: 021
Status: Not Started
Evidence: DefaultProductsProcessor.cs
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, docs
Priority: P2
Effort: S
Dependencies:
Blocks:
Source gap analysis: Finding C-002

Description (full):

Produce a transition matrix (spreadsheet or markdown table in PR description) mapping from-status → to-status → processor method → published event. Edit docs/aggregate-root.md mermaid diagram and prose to reflect audited truth.

Acceptance criteria (testable):

  • AC-1: Transition matrix attached to PR or embedded in docs/aggregate-root.md appendix.
  • AC-2: No undocumented transitions remain in the diagram.
  • AC-3: mkdocs build clean.

Implementation notes (full):

  • Symbols: RetireProductAsync, status enumeration types in EntityModel.

Out of scope:

  • ADR-0001 edits (separate task).

Definition of done:

  • All AC pass
  • Docs updated
  • Status updated in master + mirrors

[021] saas-CAT-T02.1.2 — Fix ADR-0001 Feature aggregate placement narrative

Type: Task
Parent: saas-CAT-S02.1
Implementation order: 021
Status: Not Started
Evidence: docs/adr/0001-one-aggregate-root-per-repo.md, docs/adr/0002-edition-inside-product-aggregate.md
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, docs, adr
Priority: P2
Effort: S
Dependencies:
Blocks:
Source gap analysis: Finding C-002

Description (full):

Update ADR-0001 (and cross-links in product-aggregate.md) so Feature is documented as an entity within the Product aggregate boundary, consistent with ADR-0002 and architecture tests (OneAggregateRootPerRepoTests — already implemented in Catalog).

Acceptance criteria (testable):

  • AC-1: ADR-0001 explicitly lists only Product as aggregate root; Features/Editions/SLA called peer entities or child collections.
  • AC-2: ADR-0002 cross-reference added where edition nesting is explained.
  • AC-3: No contradiction with ConnectSoft.Saas.ProductsCatalog.ArchitectureTests.

Implementation notes (full):

  • Files: docs/adr/0001-one-aggregate-root-per-repo.md, docs/domain/product-aggregate.md.

Out of scope:

  • Changing NetArch tests (already green).

Definition of done:

  • All AC pass
  • Docs updated
  • Status updated in master + mirrors

[022] saas-CAT-F03 — Optional structural write APIs (SLA / EditionPricing / EditionBusinessModel) OR formal defer ADR

Type: Feature
Parent: saas-EPIC-CAT
Implementation order: 022
Status: Not Started
Evidence: Peer entities exist in EntityModel/processor but no dedicated write ServiceModel ops for SLA/pricing/business bindings; strategic decision table in gap analysis
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, P1, adr
Priority: P1
Effort: XL
Dependencies:
Blocks: saas-CAT-F04
Source gap analysis: §5 Decision log — ADR-CAT-001, strategic decisions table

Description (full):

The canonical DDD blueprint and JSON descriptor model SLA declarations, edition pricing models, and business model bindings as first-class structural catalog data. Wave-1 implements rich read/query paths and edition/feature mutations, but dedicated write APIs for EditionPricing, EditionBusinessModel, and SLA binding changes may be incomplete or only reachable through bulk product update paths.

Strategic decision ADR-CAT-001 requires either (A) implement explicit ServiceModel write operations (BindEditionPricing, UpdateSlaBinding, etc.) with processor methods, validation, and outbound events, or (B) accept deferral with a formal ADR documenting which structural edits remain internal/seed-only until a later wave. This Feature owns that decision and its implementation.

Acceptance criteria (testable):

  • AC-1: ADR-CAT-001 authored under docs/adr/ with status Accepted, recording option (A) or (B).
  • AC-2: If option (A): new REST/gRPC operations exist on IProductManagementService, covered by processor unit tests, and listed in ConnectSoft.Saas.ProductsCatalog.json descriptor.
  • AC-3: If option (B): deferral ADR lists explicit non-goals; Feature marked Deferred; saas-CAT-F04 blocked until structural APIs ship.
  • AC-4: No silent partial APIs — every structural concept in the descriptor is either writable via ServiceModel or listed as deferred in ADR.

Implementation notes (full):

  • Files: IProductManagementService.cs, DefaultProductsProcessor.cs, REST/gRPC adapters, ConnectSoft.Saas.ProductsCatalog.json, new ADR stub docs/adr/0005-structural-write-apis.md (number TBD).
  • Symbols: pricing/business/SLA entity types in EntityModel.
  • Tests: processor tests per new command; optional acceptance scenario in sample catalog seed.

Out of scope:

  • Billing price enforcement (Billing bounded context).
  • EntitlementsChanged fan-out (saas-CAT-F04) unless option (A) completes.

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[022] saas-CAT-S03.1 — Decide and document structural write API scope (ADR-CAT-001)

Type: User Story
Parent: saas-CAT-F03
Implementation order: 022
Status: Not Started
Evidence: Gap analysis strategic decisions table — Catalog structural APIs
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, adr
Priority: P1
Effort: S
Dependencies:
Blocks: saas-CAT-T03.1.1, saas-CAT-T03.1.2
Source gap analysis: §5 Decision log

Description (full):

As an architect, I need a recorded decision on whether Products Catalog exposes first-class write APIs for SLA, edition pricing, and business model bindings so downstream Features (EntitlementsChanged fan-out, E2E catalog seeding) have a clear dependency target.

Facilitate ADR-CAT-001 with options (A) implement vs (B) defer, citing E2E demo needs, descriptor completeness, and maintenance cost. Stakeholder sign-off captured in ADR status.

Acceptance criteria (testable):

  • AC-1: ADR-CAT-001 merged with Accepted status and chosen option documented.
  • AC-2: Master backlog and this epic file reference ADR path in Feature status when Deferred.
  • AC-3: If defer: explicit list of structural edits still allowed via UpdateProduct / migrations / seed scripts.

Implementation notes (full):

  • Author ADR using log4brains/front-matter pattern from existing ADRs in repo.

Out of scope:

  • Implementation work if option (B) chosen (implementation task cancelled).

Definition of done:

  • All AC pass
  • Docs updated
  • Status updated in master + mirrors

[022] saas-CAT-T03.1.1 — Author ADR-CAT-001 with options analysis

Type: Task
Parent: saas-CAT-S03.1
Implementation order: 022
Status: Not Started
Evidence: Pending ADR in decision log
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, adr
Priority: P1
Effort: S
Dependencies:
Blocks: saas-CAT-T03.1.2
Source gap analysis: ADR-CAT-001 row

Description (full):

Draft ADR documenting current partial implementation evidence (EntityModel types, processor methods if any, JSON descriptor fields), option (A) API surface proposal, option (B) deferral rationale, and consequences for Entitlements/Billing consumers.

Acceptance criteria (testable):

  • AC-1: ADR file exists at docs/adr/ with standard sections (Status, Context, Decision, Consequences).
  • AC-2: Links to saas-platform-ddd-entities.md and JSON descriptor paths.
  • AC-3: Reviewed by platform architect (noted in ADR or PR).

Implementation notes (full):

  • Follow 0002-edition-inside-product-aggregate.md format.

Out of scope:

  • Code changes.

Definition of done:

  • All AC pass
  • Docs updated
  • Status updated in master + mirrors

[022] saas-CAT-T03.1.2 — Implement structural write APIs (if ADR option A)

Type: Task
Parent: saas-CAT-S03.1
Implementation order: 022
Status: Not Started
Evidence: N/A until ADR decision
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, servicemodel
Priority: P1
Effort: XL
Dependencies: saas-CAT-T03.1.1 (option A only)
Blocks: saas-CAT-F04
Source gap analysis: §4.2 Products Catalog — Entity model

Description (full):

If ADR-CAT-001 selects option (A), implement ServiceModel contracts, processor methods, validation, REST/gRPC adapters, and JSON descriptor entries for SLA binding and edition pricing/business model structural writes. Wire through grain if saas-CAT-F01 is complete.

Acceptance criteria (testable):

  • AC-1: Each new operation has processor unit test covering happy path and validation failure.
  • AC-2: REST OpenAPI and gRPC ServiceModel packages updated; NuGet pack succeeds.
  • AC-3: Sample catalog seed or acceptance test exercises at least one structural write end-to-end.

Implementation notes (full):

  • Extend IProductEditorActor if grain path active.
  • Publish domain events per existing CatalogMassTransitTopology patterns.

Out of scope:

  • Executing if ADR option (B) — mark task Cancelled/Deferred.

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[023] saas-CAT-F04 — EntitlementsChangedEvent fan-out on pricing / business-model / SLA bindings

Type: Feature
Parent: saas-EPIC-CAT
Implementation order: 023
Status: Not Started
Evidence: Gap analysis §4.2 Messaging — EntitlementsChanged fan-out Missing; Entitlements/Billing/Metering consume entitlements.v1.entitlements-changed
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, messaging, P1
Priority: P1
Effort: L
Dependencies: saas-CAT-F03 (option A), saas-INTEG-F03 (envelope)
Blocks:
Source gap analysis: §4.2 Products Catalog — Messaging

Description (full):

Downstream bounded contexts (Entitlements, Billing, Metering) subscribe to catalog-driven entitlement changes. Today EntitlementsChangedEvent (topic per ProductsCatalogConstants.EventTopics) is published on edition/feature mutations, but not when structural bindings change — edition pricing, business model, or SLA attachments — because those write paths are missing or incomplete (saas-CAT-F03).

When structural APIs ship, the processor must fan out EntitlementsChangedEvent (or granular companion events if ADR specifies) whenever binding changes affect effective entitlement resolution. Payload must include tenant id, product/edition ids, aggregate version, and eventually full envelope fields from saas-INTEG-F03.

Acceptance criteria (testable):

  • AC-1: Given a successful structural binding write (pricing, business model, or SLA), when the transaction commits, then EntitlementsChangedEvent is published to the canonical topic with correct routing key.
  • AC-2: Given no effective entitlement impact (no-op update), when processed, then no duplicate event is published (idempotent compare on version/hash).
  • AC-3: Integration test or acceptance test asserts MassTransit test harness receives event after structural mutation.
  • AC-4: Event fields documented in docs/reference/events.md and match MessagingModel DTO.

Implementation notes (full):

  • Files: DefaultProductsProcessor.cs, MessagingModel event types, CatalogMassTransitTopology.cs.
  • Coordinate with Entitlements EntitlementsChanged consumers and Billing/Metering sagas.
  • Depends on structural writes from F03; if F03 deferred, this Feature stays Blocked.

Out of scope:

  • Entitlements-side reaction logic (saas-ENT-F03).
  • Envelope rollout (tracked under INTEG-F03 but should not block topic publish).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[023] saas-CAT-S04.1 — Publish EntitlementsChanged on structural binding mutations

Type: User Story
Parent: saas-CAT-F04
Implementation order: 023
Status: Not Started
Evidence: Messaging table — fan-out Missing
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, messaging
Priority: P1
Effort: M
Dependencies: saas-CAT-F03
Blocks: saas-CAT-T04.1.1, saas-CAT-T04.1.2
Source gap analysis: §4.2 Products Catalog — Messaging

Description (full):

As an Entitlements service operator, I need catalog structural binding changes to emit EntitlementsChangedEvent so assignment effective dates and feature matrices refresh without manual entitlement overrides.

Implement publish hooks in processor methods introduced by F03 (or existing update paths if partial). Ensure aggregate version increments and event carries edition/product identifiers consumed by Entitlements sagas.

Acceptance criteria (testable):

  • AC-1: At least three mutation types (pricing, business model, SLA) each trigger event in tests.
  • AC-2: Topic name matches saas-cross-repo-published-language.md canonical entry.
  • AC-3: No regression to existing edition/feature event publishes.

Implementation notes (full):

  • Reuse IEventBus.PublishEvent pattern from existing processor methods.
  • Align payload with Entitlements inbound DTO expectations.

Out of scope:

  • Consumer-side handling.

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[023] saas-CAT-T04.1.1 — Add processor publish hooks for structural mutations

Type: Task
Parent: saas-CAT-S04.1
Implementation order: 023
Status: Not Started
Evidence: DefaultProductsProcessor.cs — no publish on structural paths
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, messaging
Priority: P1
Effort: M
Dependencies: saas-CAT-F03
Blocks:
Source gap analysis: §4.2 Messaging

Description (full):

After structural write methods exist, add PublishEntitlementsChanged helper (or inline) invoking IEventBus with populated EntitlementsChangedIntegrationEvent (exact type name per MessagingModel).

Acceptance criteria (testable):

  • AC-1: Unit tests with mocked IEventBus verify publish called once per successful structural mutation.
  • AC-2: Event includes TenantId, product/edition ids, AggregateVersion, OccurredOn.
  • AC-3: Failed validation does not publish.

Implementation notes (full):

  • Mirror existing publish pattern from ActivateFeatureAsync or similar.

Out of scope:

  • Envelope fields from INTEG-F03 (add when available without breaking contract).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[023] saas-CAT-T04.1.2 — Document and verify EntitlementsChanged fan-out in events reference

Type: Task
Parent: saas-CAT-S04.1
Implementation order: 023
Status: Not Started
Evidence: docs/reference/events.md
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, docs, messaging
Priority: P1
Effort: S
Dependencies: saas-CAT-T04.1.1
Blocks:
Source gap analysis: §4.2 Messaging

Description (full):

Update docs/reference/events.md and JSON descriptor publishedEvents to list structural mutations as triggers for EntitlementsChangedEvent. Add cross-repo link to Entitlements consumer saga (ProductCatalogReactionStateMachine is ProductRetired — document distinction).

Acceptance criteria (testable):

  • AC-1: Events doc table includes structural binding triggers with example payload snippet.
  • AC-2: ConnectSoft.Saas.ProductsCatalog.json publishedEvents consistent with code.
  • AC-3: mkdocs build passes.

Implementation notes (full):

  • Cross-link Entitlements docs/reference/events.md in PR description.

Out of scope:

  • Entitlements doc edits (Entitlements repo).

Definition of done:

  • All AC pass
  • Docs updated
  • Status updated in master + mirrors

[024] saas-CAT-F05 — NHibernate transactional outbox (ADR-0003 future work)

Type: Feature
Parent: saas-EPIC-CAT
Implementation order: 024
Status: Not Started
Evidence: docs/adr/0003-publish-only-mass-transit-products-catalog.md Future work section; commit-then-publish in processor
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, messaging, outbox, P2
Priority: P2
Effort: XL
Dependencies: saas-INTEG-F05
Blocks:
Source gap analysis: §3.5 MassTransit outbox, ADR-CAT-003

Description (full):

Products Catalog currently follows commit-then-publish: NHibernate transaction commits in ExecuteTransactional, then IEventBus.PublishEvent runs outside the database transaction. ADR-0003 accepts publish-only MassTransit with explicit Future work to adopt MassTransit.NHibernate built-in outbox when durable at-least-once publishing is required. ADR-CAT-003 in the gap program tracks this as deferred future work (saas-INTEG-F05 covers cross-cutting outbox policy).

This Feature implements official MassTransit NHibernate outbox integration shared with the domain unit-of-work, updates MassTransit:PersistenceType, and eliminates custom FluentMigrator outbox tables (MassTransit-owned storage only).

Acceptance criteria (testable):

  • AC-1: Domain write + event publish occur in a single transactional outbox scope; integration test simulates broker failure and verifies eventual dispatch after recovery.
  • AC-2: No custom outbox tables added to DatabaseModel.Migrations; MassTransit package owns schema.
  • AC-3: MassTransit:PersistenceType and host startup documented in docs/messaging.md and ADR-0003 amended with Implemented status for outbox section.
  • AC-4: CI includes outbox dispatch test or documented manual verification checklist.

Implementation notes (full):

  • Packages: MassTransit.NHibernate (version aligned with repo MassTransit major).
  • Files: MassTransitExtensions.cs, ProductsCatalogMicroserviceRegistration.cs, NHibernate session factory wiring.
  • Coordinate with saas-INTEG-F05 for RabbitMQ/ASB durability settings.

Out of scope:

  • Inbound consumers (still publish-only per ADR-0003).
  • Tenants saga in-memory repository (Tenants repo).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[024] saas-CAT-S05.1 — Adopt MassTransit NHibernate outbox for catalog publishes

Type: User Story
Parent: saas-CAT-F05
Implementation order: 024
Status: Not Started
Evidence: ADR-0003 Future work — transactional outbox
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, outbox
Priority: P2
Effort: L
Dependencies: saas-INTEG-F05
Blocks: saas-CAT-T05.1.1, saas-CAT-T05.1.2
Source gap analysis: §3.5

Description (full):

As a platform operator, I need catalog integration events to be durably tied to database commits so a crash after SQL commit but before broker publish cannot silently drop catalog facts consumed by Entitlements and Billing.

Wire MassTransit outbox to the same NHibernate ISession used by DefaultProductsProcessor.ExecuteTransactional.

Acceptance criteria (testable):

  • AC-1: Publish calls from processor route through outbox API (no naked post-commit publish).
  • AC-2: Failure injection test demonstrates redelivery after restart.
  • AC-3: Performance baseline documented (no >20% regression on sample create-product scenario).

Implementation notes (full):

  • Follow BaseTemplate MassTransit outbox examples where applicable.
  • Keep ADR-0003 publish-only consumer stance.

Out of scope:

  • Cross-repo inbox dedupe (saas-INTEG-F04).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[024] saas-CAT-T05.1.1 — Configure MassTransit.NHibernate outbox in host startup

Type: Task
Parent: saas-CAT-S05.1
Implementation order: 024
Status: Not Started
Evidence: MassTransit:PersistenceType = None in template appsettings
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, outbox
Priority: P2
Effort: L
Dependencies: saas-INTEG-F05
Blocks:
Source gap analysis: ADR-0003

Description (full):

Add package references, configure outbox on bus registration, bind to NHibernate session factory, update appsettings and Docker compose env vars for persistence type.

Acceptance criteria (testable):

  • AC-1: Application starts with outbox enabled against SQLite/SQL Server test database.
  • AC-2: Outbox tables created by MassTransit (verified in migration log or schema dump).
  • AC-3: Health check includes bus/outbox readiness if BaseTemplate pattern exists.

Implementation notes (full):

  • Files: ApplicationModel MassTransit extensions, appsettings templates.

Out of scope:

  • Production Helm value tuning.

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[024] saas-CAT-T05.1.2 — Refactor processor publish path to outbox dispatch

Type: Task
Parent: saas-CAT-S05.1
Implementation order: 024
Status: Not Started
Evidence: DefaultProductsProcessor post-transaction publish
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, outbox
Priority: P2
Effort: M
Dependencies: saas-CAT-T05.1.1
Blocks:
Source gap analysis: ADR-0003 Future work

Description (full):

Replace direct IEventBus.PublishEvent after commit with outbox-scheduled publish inside the NHibernate transaction boundary. Update unit tests to use test harness with outbox.

Acceptance criteria (testable):

  • AC-1: All existing processor tests pass with outbox test fixture.
  • AC-2: No PublishEvent calls remain outside transactional outbox scope in processor.
  • AC-3: ADR-0003 updated to reflect implemented outbox.

Implementation notes (full):

  • May require IPublishEndpoint or MassTransit outbox scoped bus per docs.

Out of scope:

  • Changing event DTO shapes.

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[025] saas-CAT-F06 — Additional query operations (ListEditions; get-by-id for pricing/business)

Type: Feature
Parent: saas-EPIC-CAT
Implementation order: 025
Status: Not Started
Evidence: IProductQueryService.cs — no ListEditions; no get-by-id for pricing/business models
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, query, P2
Priority: P2
Effort: M
Dependencies:
Blocks:
Source gap analysis: §4.2 ServiceModel — ListEditions partial

Description (full):

The read-side ServiceModel exposes GetProductById, ListProducts, and GetEditionById, but lacks ListEditions for a product and dedicated get-by-id operations for edition pricing and business model declarations. Entitlements assign flow and admin UIs often need edition lists and pricing metadata without loading the full product graph. Gap analysis marks query gaps under ServiceModel partial implementation.

This Feature extends IProductQueryService, NHibernate/query repository methods, REST/gRPC adapters, and OpenAPI metadata with backward-compatible additions.

Acceptance criteria (testable):

  • AC-1: ListEditionsAsync returns paginated/filtered editions for a tenant-scoped product id; 404 when product missing.
  • AC-2: GetEditionPricingByIdAsync and GetEditionBusinessModelByIdAsync (or consolidated DTO) return structural data when present; 404 when not found.
  • AC-3: gRPC and REST both expose new operations; ServiceModel NuGet version bumped per SemVer minor.
  • AC-4: Acceptance or unit tests cover happy path and not-found for each new operation.

Implementation notes (full):

  • Files: IProductQueryService.cs, GrpcProductQueryService.cs, ProductsController.cs (query section), query repository in PersistenceModel.
  • No write-side changes.

Out of scope:

  • GraphQL or OData exposure.
  • Cross-tenant queries.

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated (rest-api.md, grpc-api.md)
  • Status updated in master + mirrors

[025] saas-CAT-S06.1 — Expose ListEditions and structural get-by-id on query service

Type: User Story
Parent: saas-CAT-F06
Implementation order: 025
Status: Not Started
Evidence: IProductQueryService interface surface
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, query
Priority: P2
Effort: M
Dependencies:
Blocks: saas-CAT-T06.1.1, saas-CAT-T06.1.2
Source gap analysis: §4.2 ServiceModel

Description (full):

As a ServiceModel consumer (Entitlements ACL, Blazor MFE, gateway), I need list-editions and get-by-id pricing/business queries so I can validate assignments and display catalog metadata without over-fetching full products.

Acceptance criteria (testable):

  • AC-1: Sample gRPC client in acceptance tests calls ListEditions successfully against seeded catalog.
  • AC-2: Response DTOs include tenant id and edition id fields required by cross-repo published language.
  • AC-3: Authorization/tenant middleware enforced on all new endpoints.

Implementation notes (full):

  • Follow existing GetEditionByIdAsync mapping patterns.
  • Update JSON descriptor query operations section if present.

Out of scope:

  • Caching layer (Redis) for query results.

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[025] saas-CAT-T06.1.1 — Extend IProductQueryService and persistence queries

Type: Task
Parent: saas-CAT-S06.1
Implementation order: 025
Status: Not Started
Evidence: Missing methods on IProductQueryService
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, query
Priority: P2
Effort: M
Dependencies:
Blocks:
Source gap analysis: §4.2 ServiceModel

Description (full):

Add request/response DTOs, interface methods, NHibernate LINQ or criteria queries, and AutoMapper profiles for ListEditions and pricing/business get-by-id.

Acceptance criteria (testable):

  • AC-1: Unit tests on query repository return correct counts and shapes for seed data.
  • AC-2: Interface XML docs complete for NuGet consumers.
  • AC-3: No N+1 query regression (single round-trip or documented fetch join).

Implementation notes (full):

  • PersistenceModel repository interfaces and NHibernate implementations.

Out of scope:

  • REST/gRPC adapters (next task).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[025] saas-CAT-T06.1.2 — Wire REST and gRPC query adapters + API docs

Type: Task
Parent: saas-CAT-S06.1
Implementation order: 025
Status: Not Started
Evidence: GrpcProductQueryService.cs, ProductsController.cs
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, query, rest, grpc
Priority: P2
Effort: S
Dependencies: saas-CAT-T06.1.1
Blocks:
Source gap analysis: §4.2 ServiceModel

Description (full):

Implement adapter methods, Swagger annotations, and gRPC ServiceContract operations. Update rest-api.md and grpc-api.md with examples.

Acceptance criteria (testable):

  • AC-1: OpenAPI spec includes new paths; Swashbuckle generates without errors.
  • AC-2: gRPC acceptance tests invoke new operations.
  • AC-3: API docs examples match actual route names and DTO fields.

Implementation notes (full):

  • Mirror naming from existing GetEditionByIdAsync.

Out of scope:

  • Shell/MFE UI consumption.

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[026] saas-CAT-F07 — Grain test coverage beyond CreateProduct

Type: Feature
Parent: saas-EPIC-CAT
Implementation order: 026
Status: Not Started
Evidence: ProductEditorGrainSiloTests.cs — only CreateProductAsync scenarios; IProductEditorActor defines 7 operations
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, orleans, tests, P1
Priority: P1
Effort: M
Dependencies: saas-CAT-F01
Blocks:
Source gap analysis: §4.2 Actor / Orleans — Grain tests Partial

Description (full):

Orleans grain tests exist for composite key parsing (ProductEditorGrainKeyTests) and silo tests for CreateProductAsync including tenant mismatch guard. However UpdateProductAsync, RetireProductAsync, AddEditionAsync, UpdateEditionAsync, ActivateFeatureAsync, and DeactivateFeatureAsync lack silo coverage. With F01 wiring grains onto the API path, grain tests become critical CI gates for write correctness.

Acceptance criteria (testable):

  • AC-1: Silo test exists for each of the seven IProductEditorActor methods (happy path + at least one failure path where applicable).
  • AC-2: Tests use hand-written processor stub pattern (no mocking framework) consistent with existing ProductEditorGrainSiloTests.
  • AC-3: CI runs grain tests on every PR; no [Ignore] attributes without linked issue.
  • AC-4: Tenant mismatch test pattern replicated for mutating operations that accept tenant in input.

Implementation notes (full):

  • Files: ProductEditorGrainSiloTests.cs, extend stub IProductsProcessor in test project.
  • Optional: parameterized test over operation names.

Out of scope:

  • Full TestCluster deployment tests in AcceptanceTests (unit silo sufficient).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated (docs/Testing.md grain section)
  • Status updated in master + mirrors

[026] saas-CAT-S07.1 — Expand ProductEditorGrain silo tests for all write operations

Type: User Story
Parent: saas-CAT-F07
Implementation order: 026
Status: Not Started
Evidence: ProductEditorGrainSiloTests.cs — 2 CreateProduct tests only
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, orleans, tests
Priority: P1
Effort: M
Dependencies: saas-CAT-F01
Blocks: saas-CAT-T07.1.1, saas-CAT-T07.1.2
Source gap analysis: §4.2 Actor / Orleans

Description (full):

As a maintainer, I need comprehensive grain silo tests so Orleans serialization, tenant guards, and processor delegation remain correct when API routing changes in F01.

Acceptance criteria (testable):

  • AC-1: Code coverage on ProductEditorGrain.cs methods ≥ 80% line coverage (or all public methods hit).
  • AC-2: Stub processor records invocations for assertion.
  • AC-3: Tests run in < 30 seconds on local dev hardware.

Implementation notes (full):

  • Extend existing TestCluster fixture setup in test class.

Out of scope:

  • Load/stress testing grains.

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[026] saas-CAT-T07.1.1 — Add silo tests for product and edition mutations

Type: Task
Parent: saas-CAT-S07.1
Implementation order: 026
Status: Not Started
Evidence: Missing tests for Update/Retire/AddEdition/UpdateEdition
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, orleans, tests
Priority: P1
Effort: M
Dependencies: saas-CAT-F01
Blocks:
Source gap analysis: §4.2 Actor / Orleans

Description (full):

Implement silo tests for UpdateProductAsync, RetireProductAsync, AddEditionAsync, UpdateEditionAsync with stub processor returning deterministic ids and verifying grain forwards inputs unchanged.

Acceptance criteria (testable):

  • AC-1: Four new test methods pass locally and in CI.
  • AC-2: Tenant mismatch throws for UpdateProductAsync (representative mutating op).
  • AC-3: Grain key parsing uses same composite key as production coordinator.

Implementation notes (full):

  • Reuse ProductEditorGrainSiloTests cluster initialization.

Out of scope:

  • Feature activate/deactivate tests (next task).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

[026] saas-CAT-T07.1.2 — Add silo tests for feature activate/deactivate

Type: Task
Parent: saas-CAT-S07.1
Implementation order: 026
Status: Not Started
Evidence: Missing tests for ActivateFeature/DeactivateFeature
Last verified: 2026-05-27
Area path: ConnectSoft\SaaS\ProductsCatalog
Iteration: TBD
Tags: saas-platform, gap, catalog, orleans, tests
Priority: P1
Effort: S
Dependencies: saas-CAT-T07.1.1
Blocks:
Source gap analysis: §4.2 Actor / Orleans

Description (full):

Add silo tests for ActivateFeatureAsync and DeactivateFeatureAsync, including surrogate serialization round-trip if surrogates are registered for feature inputs.

Acceptance criteria (testable):

  • AC-1: Two new test methods pass; stub verifies processor called with edition and feature keys.
  • AC-2: Orleans surrogate converters (if any) do not throw on activation path.
  • AC-3: docs/Testing.md lists grain test matrix updated.

Implementation notes (full):

  • Check ActorModel.Orleans Surrogates folder for feature input types.

Out of scope:

  • Processor business rule tests (DomainModel unit tests).

Definition of done:

  • All AC pass
  • Tests added/updated and green
  • Docs updated
  • Status updated in master + mirrors

Implementation ordering (this epic)

Preferred sequence within saas-EPIC-CAT (global order in brackets):

  1. [022] saas-CAT-F03 — Decision first (unblocks or explicitly defers F04).
  2. [020] saas-CAT-F01 — Orleans write-path (after saas-INTEG-F06 ADR).
  3. [026] saas-CAT-F07 — Grain tests (after F01).
  4. [023] saas-CAT-F04 — EntitlementsChanged fan-out (after F03 option A).
  5. [025] saas-CAT-F06 — Query extensions (parallel-friendly).
  6. [021] saas-CAT-F02 — Documentation drift (parallel-friendly).
  7. [024] saas-CAT-F05 — NHibernate outbox (after saas-INTEG-F05, lower priority).

Cross-epic dependencies: saas-INTEG-F06, saas-INTEG-F03, saas-INTEG-F05.