From 6d3aab4ff4fabc3a184519da3816d6c859e6bc8f Mon Sep 17 00:00:00 2001 From: Mikhail Fedorov Date: Fri, 3 Jul 2026 18:46:57 +0200 Subject: [PATCH] Add Product & SKU Management PRD (cf-bss format) Signed-off-by: Mikhail Fedorov --- gears/bss/product-sku/docs/PRD.md | 1636 +++++++++++++++++++++++++++++ 1 file changed, 1636 insertions(+) create mode 100644 gears/bss/product-sku/docs/PRD.md diff --git a/gears/bss/product-sku/docs/PRD.md b/gears/bss/product-sku/docs/PRD.md new file mode 100644 index 000000000..fd2fcdf3a --- /dev/null +++ b/gears/bss/product-sku/docs/PRD.md @@ -0,0 +1,1636 @@ +--- +refs: + - bss/manifest/vz-arch-manifest-bss-only.md + - bss/prd/PRD-billing-ledger-balances-202604041200 + - bss/prd/PRD-billing-module-202601120119 + - bss/prd/PRD-billing-system-202601120119 + - bss/prd/PRD-contracts-agreements-202601120119 + - bss/prd/PRD-metering-pricing-module-202601120119 + - bss/prd/PRD-plan-price-modeling-202605281200 + - bss/prd/PRD-product-catalog-marketplace-202601120119 + - bss/prd/PRD-rating-engine-202604031200 + - bss/prd/PRD-subscriptions-entitlements-202601120119 + - bss/prd/PRD-subscriptions-lifecycle-202604021200 + - bss/prd/PRD-tariffs-pricing-logic-202604011200 +--- + +# PRD — Product & SKU Management + + + +- [1. Overview](#1-overview) + - [1.1 Purpose](#11-purpose) + - [1.2 Background / Problem Statement](#12-background--problem-statement) + - [1.3 Goals (Business Outcomes)](#13-goals-business-outcomes) + - [1.4 Glossary](#14-glossary) +- [2. Architecture Alignment](#2-architecture-alignment) + - [2.1 Catalog Decomposition and Registry Boundary](#21-catalog-decomposition-and-registry-boundary) + - [2.2 Predecessor PRDs and Scope Migration](#22-predecessor-prds-and-scope-migration) +- [3. Actors](#3-actors) + - [3.1 Human Actors](#31-human-actors) + - [3.2 System Actors](#32-system-actors) +- [4. Operational Concept & Environment](#4-operational-concept--environment) + - [4.1 Module-Specific Environment Constraints](#41-module-specific-environment-constraints) +- [5. Scope](#5-scope) + - [5.1 In Scope](#51-in-scope) + - [5.2 Out of Scope](#52-out-of-scope) +- [6. Functional Requirements](#6-functional-requirements) + - [6.1 Identifiers & Integrity](#61-identifiers--integrity) + - [6.2 Product & Taxonomy Definition](#62-product--taxonomy-definition) + - [6.3 SKU Definition & Classification](#63-sku-definition--classification) + - [6.4 Attributes & Localization](#64-attributes--localization) + - [6.5 Versioning, Lifecycle & Deprecation](#65-versioning-lifecycle--deprecation) + - [6.6 Catalog Versioning & Snapshots](#66-catalog-versioning--snapshots) + - [6.7 Approval, Publishing & Eventing](#67-approval-publishing--eventing) + - [6.8 Multi-Tenancy & Read Models](#68-multi-tenancy--read-models) + - [6.9 Bulk Operations](#69-bulk-operations) + - [6.10 Cloning](#610-cloning) + - [6.11 Data Retention & Erasure](#611-data-retention--erasure) + - [6.12 Cross-PRD Consistency](#612-cross-prd-consistency) + - [6.13 Operational Resilience & Concurrency](#613-operational-resilience--concurrency) +- [7. Non-Functional Requirements](#7-non-functional-requirements) + - [7.1 NFR Inclusions](#71-nfr-inclusions) + - [7.2 NFR Exclusions](#72-nfr-exclusions) +- [8. Five Quality Vectors Analysis](#8-five-quality-vectors-analysis) +- [9. Public Library Interfaces](#9-public-library-interfaces) + - [9.1 Public API Surface](#91-public-api-surface) + - [9.2 External Integration Contracts](#92-external-integration-contracts) +- [10. Use Cases](#10-use-cases) +- [11. User Interaction and Design](#11-user-interaction-and-design) +- [12. Acceptance Criteria](#12-acceptance-criteria) + - [Identifiers & Integrity](#identifiers--integrity) + - [Product & Taxonomy Definition](#product--taxonomy-definition) + - [SKU Definition & Classification](#sku-definition--classification) + - [Attributes & Localization](#attributes--localization) + - [Versioning, Lifecycle & Deprecation](#versioning-lifecycle--deprecation) + - [Catalog Versioning & Snapshots](#catalog-versioning--snapshots) + - [Approval, Publishing & Eventing](#approval-publishing--eventing) + - [Multi-Tenancy & Read Models](#multi-tenancy--read-models) + - [Bulk Operations](#bulk-operations) + - [Cloning](#cloning) + - [Data Retention & Erasure](#data-retention--erasure) + - [Cross-PRD Consistency](#cross-prd-consistency) + - [Error & Negative Paths](#error--negative-paths) + - [Operational Resilience & Concurrency](#operational-resilience--concurrency) + - [Non-Functional Requirements (Show-Stoppers)](#non-functional-requirements-show-stoppers) +- [13. Dependencies](#13-dependencies) +- [14. Assumptions](#14-assumptions) +- [15. Open Questions](#15-open-questions) +- [16. Risks](#16-risks) +- [17. Reference Materials](#17-reference-materials) + - [17.1 Configurable-Policy Interim Defaults](#171-configurable-policy-interim-defaults) + - [17.2 Monetization-Model Traceability](#172-monetization-model-traceability) + + + +## 1. Overview + +### 1.1 Purpose + +**Product & SKU Management** is the authoritative, multi-tenant **catalog registry** for VHP BSS: the System of Record for *what can be sold, how it is described, classified, versioned, and published*. It owns Products, SKUs, categories/taxonomy, attributes/localization, and immutable catalog versions, with **financial-grade governance** (approval-gated publishing, immutable audit, deterministic snapshots) so that Plan & Price Modeling, Subscriptions, Contracts, Tariffs/Rating, Billing, Marketplace, and Presentation build on **stable, versioned, reproducible** catalog references. + +It owns the **registry** half of BSS manifest §4.1 and stops at the SKU (including the `bundle` type flag and the metering-unit declaration). All commercial-pricing concerns are delegated by reference to the sibling decomposition PRDs (§2.1). + +### 1.2 Background / Problem Statement + +BSS must monetize diverse offerings (IaaS, PaaS, SaaS, marketplace services) across a multi-tenant, brand/region-scoped hierarchy. Without a single authoritative catalog registry, plan/price authoring, subscriptions, contracts, rating, and billing bind to mutable, non-reproducible product state — breaking posted invoices, active contracts, and in-flight subscriptions when the catalog changes, and leaving governance (who approved what, when) unauditable. + +This PRD carves the **registry** scope out of the combined predecessor (`PRD-product-catalog-marketplace-202601120119`), completing the §4.1 decomposition already begun by Tariffs and Plan & Price Modeling. It fixes lifecycle/versioning semantics (draft → published [↔ deprecated] → retired, immutable history), a catalog publish contract (approval-gated, idempotent, event-fanned-out), a catalog-wide immutable `CatalogVersion` snapshot, and a stable SKU contract (identity, type, `PlanTier`, metering-unit declaration) that downstream modules can assume without re-validation. + +### 1.3 Goals (Business Outcomes) + +- **Flexibility / time-to-market**: Product Managers self-serve Product, SKU, category, and attribute changes across offering types without engineering involvement. +- **Stable monetization foundation**: every published SKU exposes a stable identifier, type, `PlanTier` classification, and metering-unit declaration, so plan/price authoring and rating bind to a fixed reference. +- **Auditable governance**: two-person approval for material catalog changes, immutable version history, and a complete CloudEvents audit trail satisfy financial and regulatory controls. +- **Safe evolution**: backward-compatible schema evolution and immutable `CatalogVersion` snapshots let the catalog change without breaking posted invoices, active contracts, or in-flight subscriptions. +- **Single source of truth**: one authoritative registry feeds partner/brand/region-scoped offerings, marketplace listings, and contract quotes. + +> **Note**: The registry-vs-commercial boundary is stated canonically in §2.1. Where requirements or acceptance criteria touch commercial concerns, they define only the **registry-side contract** and reference the owning PRD. + +### 1.4 Glossary + +| **Term** | **Definition** | +|----------|----------------| +| **Catalog (registry)** | The authoritative registry of products/services/bundles/SKUs, categories, and localized attributes, and the catalog-wide version/publish mechanism (manifest §4.1). SoR: BSS. Defines *what can be sold and how it is described, classified, and published* — not how it is priced. | +| **Product** | A sellable or describable offering record with a name, **one required primary category plus optional secondary categories**, lifecycle state, brand/region scope, and version. The top of the catalog hierarchy. Identified by a system-generated `productId`. | +| **SKU (Stock Keeping Unit)** | A uniquely identifiable variant of a Product, typed as `product`, `service`, or `bundle`, optionally carrying a **metering-unit declaration** (for usage products) and stable accounting codes (`taxCategory`, `glCode`). A SKU has two identifiers: a system-generated immutable `skuId` and an operator-supplied human-readable `skuCode`. A SKU carries its own brand/region scope, **contained within its parent Product's scope**; the SKU→Product link is immutable after first publish. | +| **Usage SKU** | Definition, not detection: a SKU that **carries a metering-unit declaration**. There is no separate "is-usage" flag — declaring a metering unit **is** what makes a SKU a usage SKU. "A usage SKU missing its declaration" is not a detectable registry state; usage-completeness is enforced at the plan-price seam, never at registry publish. | +| **Identifier** | The registry distinguishes **system identity** from **human/business code**. `productId`/`skuId` are server-generated immutable UUIDs. `skuCode` is operator-supplied, fixed-format, tenant-unique, immutable after first publish. Products MAY carry an optional `productCode` under the same reservation rules. Downstream consumers bind to `skuId`; humans/external catalogs reference `skuCode`/`productCode`. | +| **Bundle (SKU type)** | A SKU whose `type = bundle`. This PRD owns only the **type flag and identity**; the bundle's commercial composition (included SKUs, constraints, revenue share, invoice itemization) is authored in plan-price. A published bundle is commercially incomplete until composed. | +| **Category** | A node in the catalog taxonomy for browse, search, curation, and marketplace listing classification; supports hierarchy. | +| **Attribute** | A **governed** (defined, typed, optionally localized) key/value descriptor attached to a Product or SKU with brand/region visibility, managed via attribute **definitions**. Contrast the ungoverned **Metadata map**. | +| **Metadata map** | An **ungoverned**, per-entity free-form key/value channel for machine metadata (external ids, sync markers, migration tags): tenant-scoped, size-bounded, non-localized, excluded from read-model search, still PII-prohibited, captured in `CatalogVersion` snapshots. | +| **Brand** | A commercial/presentation identity within a tenant under which Products/SKUs/attributes are scoped for visibility, isolation, and localized display. A **visibility/legal scope, not a pricing dimension**. | +| **Region** | A geography/jurisdiction scope on Product/SKU/Attribute governing **visibility, legal availability, and localization fallback** — **never** pricing (currency/price-region/FX are plan-price/Tariffs). Drives name-uniqueness and read-model scoping. The region-set algebra is a pre-approval gate (§15). | +| **PlanTier** | Mandatory classification carried on SKUs/Plans (manifest §4.1) consumed by Subscriptions, `SlaPolicy`, and quota/entitlement policies. This PRD owns the **PlanTier taxonomy and the SKU-level value**; plan-price enforces presence at **plan** publish. Distinct from **OrgTier** (a partner commercial standing that never changes tenant topology). | +| **Metering-unit declaration** | The unit identity (e.g. vCPU-hours, GB-storage) declared on a usage SKU. This PRD owns the **declaration and its validation**; usage collection is OSS metering, plan-level meter binding is plan-price, and rating is Rating. | +| **Lifecycle state** | The Product/SKU state machine: `draft → published [↔ deprecated] → retired`, plus `draft → discarded` for never-published entities. `deprecated` is a governed sub-state of `published` (referenceable by existing consumers, closed to new adoption). `retired` is terminal (revival only via clone). `discarded` is terminal for an abandoned never-published draft (releases the `skuCode` reservation, audited, emits a discard event). | +| **Deprecation** | A governed marking of a `published` SKU that blocks **new adoption** while existing references continue, ahead of eventual retirement; modeled as the `deprecated` sub-state. | +| **Retirement / EOL** | Retirement runs as a **scheduled transition**: at initiation the entity is forced into `deprecated` (new adoption blocked immediately, still browsable) for the lead-time window (≥ 30 days interim), then flips to `retired` at `effectiveAt`. **EOL** is the optional stronger variant that additionally sets a `mustMigrateBy` date; the registry guarantees only the event + lead-time contract, while live-subscription migration is owned by subscriptions-lifecycle. | +| **Revision vs published version** | Two counters. The **internal revision** increments on every save (incl. draft edits) for optimistic concurrency and audit. The **published version** increments only on publish and is what downstream consumers and `CatalogVersion` reference. Draft churn MUST NOT inflate the published version. | +| **CatalogVersion** | An immutable, checksummed, **full** published snapshot of the catalog at publish time (monotonic `catalogVersionId`), enumerating the published Product/SKU/Category/Attribute set and their published versions. Re-resolving a `catalogVersionId` MUST always yield a byte-identical checksum. plan-price/Contracts/Billing freeze their own content keyed to `catalogVersionId`. One **component** of a downstream `pricingSnapshotRef` (defined in Tariffs); not equal to it. | +| **Material change / Materiality threshold** | A change is material when it touches the enumerated material-field set (canonically defined in the Materiality-gated-publish FR/AC) or exceeds a configured count of affected entities. The threshold is a typed, configurable policy with a default; material changes trigger the two-person rule. | +| **Recognized-unit set** | The configured set of metering units a usage SKU may declare. Owner and add-unit approval path are an owned dependency (§15); the registry validates declarations against the configured set. | +| **Two-person rule** | A multi-approver control requiring at least two distinct approvers (each distinct from the author) for material/above-threshold catalog changes before publication. | +| **Read model** | Cache-first, query-optimized projection of published catalog content for high-throughput browse/search; converges within a bounded window of the write model. | +| **Active-reference count** | Whether a SKU has live downstream references (plans, subscriptions, contracts). Derived as a **3-state predicate** from the `SkuReferenceCount` signal: **fresh-zero ⇒ unreferenced** (enables correction), **fresh > 0 ⇒ referenced**, **stale/never-received ⇒ conservatively referenced**. | +| **`SkuReferenceCount` signal** | A named, owned integration contract by which downstream modules (Subscriptions, Contracts, plan-price) publish reference liveness via a **per-producer watermark** ("as of `T`, my complete live-reference set is {…}"). Absence of a `skuId` under a fresh watermark ⇒ zero for that producer. `referenced` is a boolean OR across producers; the registry never sums across producers. A stale watermark is conservatively referenced + alerted; never-received is conservative but flagged distinctly. | +| **freezeComplete** | A per-`CatalogVersion` flag indicating that all registered freeze-participants (plan-price, Contracts, Billing) have acknowledged freezing their referenced content for that `catalogVersionId`. Resolution for posted/contractual use is rejected until `freezeComplete`, with a bounded timeout that fails closed. | +| **Freeze-participant set** | The configured list of modules that MUST acknowledge a freeze for a `CatalogVersion`. Membership is governed (two-person) and snapshotted into each `catalogVersionId`. | +| **Grandfathering** | Continuation of an existing reference on its frozen snapshot after the underlying SKU is deprecated/retired. The registry guarantees the snapshot is never mutated; eligibility policy is owned by plan-price / subscriptions-lifecycle. | +| **`compositionPending`** | A registry flag on a `bundle` SKU published-with-override while still uncomposed. While `true` the SKU is not-yet-adoptable for new references; cleared by a plan-price composition signal and emitted as `BundleCompositionCompleted`. | +| **OrgTier** | A partner's commercial standing (a partner commercial projection that MUST NOT change tenant topology). Distinct from `PlanTier`. | + +## 2. Architecture Alignment + +| **Field** | **Value** | +|-----------|----------| +| **Applicable Manifest(s)** | BSS | +| **Manifest Chapters** | §4.1 Product and Service Catalog (primary — catalog **registry**: Product, SKU, Category, Attribute, CatalogVersion, lifecycle, approvals, publish); §4.4 Billing and Invoicing (posting/snapshot immutability); §4.3 Subscriptions, §4.2 Rating, §4.6 Contracts, §4.8 Marketplace (consumers of published SKUs); §2.1.3 Multi-tenant semantics; §7.2 Event governance (CloudEvents 1.0) | + +> **Normative alignment**: This PRD owns the **catalog registry** half of BSS §4.1 — the SoR for **Product, SKU, Category, Attribute, and CatalogVersion**, plus their authoring, versioning, lifecycle, taxonomy, localization, governance, publishing, and catalog-wide snapshotting. It MUST NOT contradict the sibling decomposition PRDs and MUST delegate, by reference, every commercial-pricing concern. + +### 2.1 Catalog Decomposition and Registry Boundary + +The combined Catalog (§4.1) capability is split across complementary PRDs (registry **plus** plan-price together realize "Catalog"). This PRD implements the **registry** half and is authoritative ONLY for the registry concerns above. The following are owned elsewhere and MUST NOT be re-specified here: + +- **Plan, Price, PriceWindow, PriceList, Bundle composition, add-on rules, billing descriptors (invoice line template / tax category / GL code), plan lifecycle/migration/grandfathering, plan publish validation & approval, price history** → `PRD-plan-price-modeling-202605281200`. +- **Price resolution / override precedence / tier-volume-hybrid-commitment math / FX / `pricingSnapshotRef` composition** → `PRD-tariffs-pricing-logic-202604011200`. +- **Usage → RatedCharge → BillableItem** → `PRD-rating-engine-202604031200`. **Subscription lifecycle** → `PRD-subscriptions-lifecycle-202604021200`. **Marketplace vendor ops (§4.8)** → `PRD-product-catalog-marketplace-202601120119`. + +> **Registry vs commercial boundary (normative):** A **SKU** is the unit of *what exists and how it is described/classified/published*; a **Plan/Price** is the unit of *how it is sold and charged*. This PRD stops at the SKU (the `bundle` type flag and the metering-unit declaration). Two corollaries: **(a) No monetization-model marker** — the registry carries no monetization-model field; only **usage** leaves a footprint (the metering-unit declaration); absence of a model marker is intentional, not a gap. **(b) `region` is visibility/legal scope only** — never a pricing dimension here. + +### 2.2 Predecessor PRDs and Scope Migration + +- **PRD-product-catalog-marketplace-202601120119** (combined Catalog §4.1 + Marketplace §4.8) — the **registry** scope items ("Product & SKU Management", "Catalog Versioning", catalog-level approval workflows, "Localization & Branding Infrastructure", taxonomy/attributes) are **superseded by this PRD**; its `UC-product-sku-management-202601121200` use-case doc is superseded here. Marketplace (§4.8) scope remains authoritative there pending a dedicated Marketplace PRD. +- **PRD-plan-price-modeling-202605281200** — authoritative for Plan, Price, PriceWindow, PriceList, Bundle composition, add-ons, billing descriptors, plan lifecycle/migration, plan publish validation & approval. This PRD provides the published SKU/Category/Attribute foundation and `CatalogVersion` that plan-price builds on. The two MUST stay consistent on: SKU identity & `bundle` type, metering-unit declaration, `PlanTier` taxonomy, and `CatalogVersion`. +- **PRD-tariffs-pricing-logic-202604011200** / **PRD-rating-engine-202604031200** — downstream consumers of published SKUs and `CatalogVersion`; own evaluation and charging respectively. + +> **Recommendation on the combined PRD (§15):** Do **not** delete `PRD-product-catalog-marketplace-202601120119`. After this PRD + plan-price + Tariffs are approved, refactor it into a Marketplace-only PRD (§4.8). Until then, this PRD is authoritative for catalog-registry requirements only. + +## 3. Actors + +### 3.1 Human Actors + +#### Product Manager + +**ID**: `cpt-cf-bss-product-sku-actor-product-manager` + +**Role**: Self-serves Product, SKU, category, and attribute authoring across offering types. +**Needs**: Product/SKU editor, taxonomy manager, attribute/localization editor, metering-unit and `PlanTier` selection, clone/templating. + +#### Catalog Admin + +**ID**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +**Role**: Governs the catalog: publishes `CatalogVersion`, runs bulk import/export, manages break-glass, force-completes stuck freezes, requests immutable-field corrections. +**Needs**: Approval/publish console, bulk-operations console, freeze monitoring & recovery, break-glass elevation. + +#### Finance Reviewer + +**ID**: `cpt-cf-bss-product-sku-actor-finance-reviewer` + +**Role**: Reviews and approves finance-material catalog changes (`taxCategory`, `glCode`, `PlanTier`); second approver under the two-person rule for finance-bearing changes. +**Needs**: Pending-approval queue with diffs, pre-publish lint report, separation-of-duties enforcement. + +#### Auditor + +**ID**: `cpt-cf-bss-product-sku-actor-auditor` + +**Role**: Inspects immutable version history, audit trail, and lineage; exports for compliance. +**Needs**: Version timeline with diffs, tenant-scoped audit retrieval, break-glass audit-export. + +#### Platform Owner + +**ID**: `cpt-cf-bss-product-sku-actor-platform-owner` + +**Role**: Privileged cross-tenant operator; accesses foreign-tenant catalog only under time-boxed break-glass. +**Needs**: Break-glass read/audit-export (writes separately gated or disallowed in v1). + +### 3.2 System Actors + +#### Plan & Price Modeling + +**ID**: `cpt-cf-bss-product-sku-actor-plan-price` + +**Role**: Consumes published SKU identity/type, metering-unit declaration, `PlanTier`, `CatalogVersion`; produces `SkuReferenceCount`, `freezeComplete` ack, and the bundle composition-completed signal that clears `compositionPending`. + +#### Tariffs / Pricing Logic + +**ID**: `cpt-cf-bss-product-sku-actor-tariffs` + +**Role**: Consumes published SKU refs + `CatalogVersion` for price resolution. No authoring here. + +#### Rating & Charging + +**ID**: `cpt-cf-bss-product-sku-actor-rating` + +**Role**: Consumes metering-unit declaration and published SKU refs to map usage. + +#### OSS Metering + +**ID**: `cpt-cf-bss-product-sku-actor-oss-metering` + +**Role**: Emits usage values (external); consumes the metering-unit **declaration** on usage SKUs. + +#### Subscriptions + +**ID**: `cpt-cf-bss-product-sku-actor-subscriptions` + +**Role**: Consumes published SKU refs + `PlanTier` + `replacedBy` for eligibility/composition/migration; produces `SkuReferenceCount`; consumes `mustMigrateBy` (post-v1 EOL). Owns live-subscription migration. + +#### Contracts & Agreements + +**ID**: `cpt-cf-bss-product-sku-actor-contracts` + +**Role**: Consumes `CatalogVersion` snapshots for quotes; produces `SkuReferenceCount` (incl. draft/quote refs per producer contract) and `freezeComplete` ack. + +#### Billing & Invoicing + +**ID**: `cpt-cf-bss-product-sku-actor-billing` + +**Role**: Consumes published SKU refs + `CatalogVersion`; produces `freezeComplete` ack (descriptor freeze). Billing descriptors are authored in plan-price and frozen into `CatalogVersion`. + +#### Marketplace & Vendor Portal + +**ID**: `cpt-cf-bss-product-sku-actor-marketplace` + +**Role**: References published SKUs in vendor listings. Vendor ops remain in the Marketplace PRD (§4.8). + +#### Presentation / Portals + +**ID**: `cpt-cf-bss-product-sku-actor-presentation` + +**Role**: Consumes catalog read models for browse/search cache warming. + +#### Events & Audit (Common Core) + +**ID**: `cpt-cf-bss-product-sku-actor-events-audit` + +**Role**: Provides the shared event system: durable acceptance, per-consumer delivery/dead-letter state, bounded-backoff retry. Transport mechanics owned there. + +#### Tenant Identity (OSS/AMS + IdP) + +**ID**: `cpt-cf-bss-product-sku-actor-oss-ams-idp` + +**Role**: Supplies `tenantId`, brand/region claims, OrgTier projection targets, and role claims. The registry MUST NOT mutate tenant topology. + +## 4. Operational Concept & Environment + +### 4.1 Module-Specific Environment Constraints + +- **Registry is upstream of all commercial modeling**: a SKU MUST be published before a Plan/Price can reference it. The registry MUST NOT require any downstream consumer to re-interpret mutable catalog state for **posted** periods; the `CatalogVersion` snapshot contract is authoritative (manifest §4.4). +- **Multi-tenant isolation**: tenant/brand/region scoping via IdP claims; deny-by-default at the gateway; cross-tenant access audited; time-boxed break-glass for platform-owner access. +- **`region` is visibility/legal scope, never pricing**; currency/price-region/FX live in plan-price/Tariffs. +- **Time**: scheduled publish (`publishAt`) and scheduled retirement (`effectiveAt`) are UTC; retirement lead-time ≥ 30 days (interim). +- **Eventing**: every state-changing mutation emits CloudEvents 1.0 onto the shared event system (Common Core) with `dataschema`+semver, correlation/causation, and per-aggregate ordering keys `(tenant, aggregate)`; **pseudonymous actor references only** (no direct PII). Delivery/ordering/dead-letter mechanics are owned by the common event system, not re-specified here. +- **Snapshots are financial records**: `CatalogVersion` snapshots + version history require a durability class (interim ≥ 11 nines / replicated storage), backup/restore with periodic checksum verification, and a cross-region/DR posture. + +## 5. Scope + +### 5.1 In Scope + +| **Feature** | **Priority** | **Notes** | +|-------------|--------------|-----------| +| Product definition | `p1` | Create/update Products: name, one required primary category + optional secondary, description, brand/region scope, lifecycle, version (§4.1 Product). | +| Category & taxonomy | `p1` | Hierarchical Category tree; cycle-free; uniqueness within parent. | +| SKU definition & typing | `p1` | Define SKUs typed `product`/`service`/`bundle`; stable accounting codes; `bundle` type flag only (composition is plan-price). | +| Metering-unit declaration | `p1` | Declare/validate the usage metering unit (unit identity only); governed de-listing. Consumed by plan-price, metering, Rating. | +| PlanTier taxonomy & SKU classification | `p1` | Own the `PlanTier` taxonomy and the SKU-level value; plan-price enforces presence at plan publish. Distinct from OrgTier. | +| Attribute management & localization (i18n) | `p1` | Extensible attribute schema; i18n with brand/region visibility and fallback `(locale,region,brand) → (locale,brand) → (default-locale,brand) → global`. | +| Identifiers & integrity | `p1` | Server-generated immutable `productId`/`skuId`; operator `skuCode` fixed-format, tenant-unique, immutable after first publish; field-mutability rules. | +| Product/SKU versioning & immutable history | `p1` | Internal revision per save; published version on publish; immutable history with diff; optimistic concurrency. | +| Product/SKU lifecycle, deprecation & retirement | `p1` | `draft → published [↔ deprecated] → retired` state machine; scheduled publish (`publishAt`) + scheduled retirement (`effectiveAt`); parent-child publish-ordering + cascade; retirement/EOL blocks new adoption, preserves references/snapshots, emits consumer handoff + lead-time. | +| Catalog versioning & snapshots (CatalogVersion) | `p1` | Stage/publish immutable full `CatalogVersion` with checksum + monotonic id; byte-identical re-resolution; emit `CatalogVersionPublished`; expose `freezeComplete`. | +| Catalog approval & publishing workflow | `p1` | Approval-gated publish with two-person rule above a typed materiality policy; approvals pinned to the approved revision; idempotent mutation boundary. Categories & attribute definitions are governed live entities. | +| Multi-tenant isolation & brand/region scoping | `p1` | Tenant/brand/region scoping via IdP claims; deny-by-default; audited cross-tenant; time-boxed break-glass; RBAC. | +| Eventing, audit & integration surface | `p1` | Publish CloudEvents 1.0 for every state-changing mutation with `dataschema`+semver + correlation/causation + ordering keys; pseudonymous actors; replay/bootstrap; immutable audit. | +| Data retention & right-to-erasure | `p1` | Defined retention for retired entities/versions/audit; reconcile immutable audit + version history with GDPR/CCPA (pseudonymize operator PII, retain financial records). | +| Catalog read models (core browse/search) | `p1` | Cache-first read models scoped by tenant/brand/region, bounded convergence; premise of the show-stopper NFRs (§7). | +| Bulk import (catalog onboarding at scale) | `p1` | Bulk create/update/import with idempotency + per-row partial-failure; imports land in draft and pass the gated publish. Required at ≥ 10K-SKU scale. | +| Advanced search, filter & faceting | `p2` | Rich faceted search/filter over the core read model. | +| Catalog lint / validation & snapshot export | `p2` | `validate(lint)` before publish (warn at SKU publish, blocking-with-override at `CatalogVersion` publish for uncomposed bundles); `export snapshot`. | +| Bulk export & bulk lifecycle tooling | `p2` | Deterministic snapshot export; bulk lifecycle (mass deprecate/retire) beyond parent→child cascade. | +| Product/SKU cloning / templating | `p3` | Clone to a new draft with new identifiers; explicit copy/reset field disposition; pricing not copied. | + +### 5.2 Out of Scope + +- **API schemas, storage DDL/data models, error-code taxonomies** — Design document(s). +- **Plan, Price, PriceWindow, PriceList, Bundle composition, add-on rules, billing descriptors, plan lifecycle/migration/grandfathering, plan publish validation & approval, price history, bulk price import, effective-price preview** — plan-price. This PRD owns only the SKU `bundle` **type flag** and stable SKU accounting codes, not their commercial use. +- **Price resolution, override precedence, tier/volume/hybrid/commitment math, FX, `pricingSnapshotRef` composition** — Tariffs. +- **Usage collection/normalization and charge rating** — OSS metering + metering-pricing-module + Rating. +- **Subscription lifecycle, entitlement enforcement, proration, recurring charge generation** — subscriptions-lifecycle + subscriptions-entitlements. +- **Contract negotiation, customer-specific overrides, committed-usage true-up, SLA penalties** — contracts-agreements. +- **Tax determination/statutory invoicing and revenue recognition** — Tax Engine / Billing / Finance. Catalog supplies only the tax-category/GL **code** on the SKU. +- **Marketplace vendor onboarding/certification, listings, fee schedules, payouts, fraud holds** — Marketplace PRD (§4.8). +- **Customer-facing storefront UI** — Presentation layer; Catalog provides read-model APIs. +- **Promotional/coupon pricing, eligibility, lifecycle** — Promotions (TBD) + plan-price. A *sellable* promo/$0/"Free" offering is a **normal registry SKU** here (identity, type, `PlanTier`, visibility); only its $0/promotional **price** is out of scope. Registry rules apply identically; no separate promo entity. +- **Tenant merge/split and brand transfer between tenants** — explicit non-goal for v1. +- **Binary / media assets** (images, icons, datasheets) — not stored here in v1; the registry MAY carry asset reference URIs as attribute values. +- **Recurring availability windows and scheduled `CatalogVersion` publish** — out of scope for v1 (entity-level scheduled publish IS in scope; `CatalogVersion` publish stays manual so the freeze protocol is untouched). +- **Configurable products / CPQ** — not modeled; the registry uses a concrete SKU per variant. +- **Product-to-product relationships beyond `bundle`, parent-child, and `replacedBy`/supersedes** — not modeled in v1; a governed catalog-relationship block is a post-v1 consideration (§15). + +## 6. Functional Requirements + +> **Content boundary**: FRs define WHAT the registry must do, not schemas. Any concrete field/flag/event/idempotency-key name or format is an illustrative handle; canonical field schemas, formats/regexes, error codes, event catalog, and payloads are owned by `DESIGN-product-sku-management-*`. Full Given/When/Then acceptance detail is preserved in §12; interim configurable-policy defaults are in §17.1. + +### 6.1 Identifiers & Integrity + +#### Identifier contract + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-identifier-contract` + +`productId` and `skuId` **MUST** be server-generated immutable identifiers (never operator-supplied). `skuCode` **MUST** be operator-supplied, fixed-format, tenant-unique, reserved **atomically at create time**, and immutable after first publish; once first published a `skuCode` **MUST** be permanently reserved within the tenant and **MUST NOT** be reissued. Downstream consumers **MUST** bind to `skuId`. Products **MAY** carry an optional `productCode` under the same rules. + +**Rationale**: Stable system identity plus a protected human/external code is the foundation every downstream reference depends on. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +#### Product/SKU field-mutability matrix + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-field-mutability-matrix` + +Mutability **MUST** be classified by lifecycle state: in `draft` all fields editable (incl. SKU→parent link and `skuCode`/`productCode`); after publish four buckets apply — **(i) structural identity** immutable and never correctable in place (remedied only by retire + clone); **(ii)** `type` and metering-unit declaration immutable but correctable via the governed fresh-zero path; **(iii) material-but-mutable** (`PlanTier`, `taxCategory`, `glCode`) change via a new published version under governance; **(iv)** other descriptive fields via a new published version. Illegal changes **MUST** be rejected fail-closed with an audited reason. The active-reference count **MUST** be sourced from `SkuReferenceCount` as the 3-state predicate; the registry **MUST NEVER** treat an entity as unreferenced absent a fresh watermark. + +**Rationale**: Protecting identity/external caches while allowing governed evolution requires a per-state, per-field classification. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +#### Reference-signal sourcing, freshness & counting + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-reference-signal` + +The registry **MUST** consume the named `SkuReferenceCount` signal (per-producer watermark). Absence of a `skuId` under a **fresh** watermark ⇒ zero for that producer; `referenced` **MUST** be a boolean OR (any registered producer > 0); the registry **MUST NOT** sum across producers, and each producer dedups within itself. A **fresh-zero** across all registered producers ⇒ unreferenced; a **stale** or **never-received** watermark ⇒ conservatively referenced (stale alert distinct from never-received). Only **registered** producers count; Contracts **MUST** declare whether draft/quote references count, with identical semantics across mutability/correction/retirement. + +**Rationale**: A watermark-based OR predicate scales to 10K+ SKUs × N producers without dense zero publishing and never falsely frees a referenced SKU. + +**Actors**: `cpt-cf-bss-product-sku-actor-plan-price` + +#### Immutable-field correction (zero-reference & break-glass) + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-immutable-field-correction` + +For a correctable immutable field (`type` or metering-unit declaration; **never** structural identity), if `SkuReferenceCount` is **fresh and zero** across all producers the system **MUST** allow a governed re-publish under the two-person rule, increment the published version, and emit `SkuImmutableFieldCorrected`; absent a fresh-zero signal it **MUST** reject fail-closed. While the signal is entirely unavailable, a single-SKU correction **MAY** proceed only via **break-glass** (two-person + mandatory reason + `SkuCorrectionOverride` recording signal-unavailability), behind a feature flag OFF by default. + +**Rationale**: Corrections must be provably safe (fresh-zero) or explicitly break-glass-audited, never silent. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +### 6.2 Product & Taxonomy Definition + +#### Create a product + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-create-product` + +Creating a Product **MUST** generate a `productId`, persist as `draft` (published version 0) with full multi-tenant isolation and an audit entry (primary category optional at draft, required at publish; optional secondary categories allowed). Name uniqueness **MUST** be enforced on `(tenantId, brandId, normalized(name))` within any overlapping region scope; two same-named Products are allowed only when region scopes are **disjoint**. Indeterminate region overlap **MUST** fail closed with an operator-facing reason. The uniqueness key is the **canonical internal name**; localized display name/description are well-known attributes. + +**Rationale**: Multi-region catalogs need same-name coexistence without mangling, while same-region collisions must be rejected deterministically. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +#### Manage taxonomy (create, rename, re-parent, retire, delete) + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-manage-taxonomy` + +Category operations **MUST** validate name uniqueness within parent (re-checked on rename/re-parent), reject cycles, and reject exceeding configured max depth/children. A Product carries **exactly one primary category + zero or more secondary**; the read model **MUST** make it filterable under every assigned category. Categories are **governed live entities** (in-place, two-person-gated on material ops, audited) — not draft/publish-versioned. Retire/delete **MUST** be blocked while any active Product references the category (as primary or secondary) or active child categories exist. Each operation emits the corresponding `Category*` event. + +**Rationale**: Taxonomy reshaping affects browse for every published product, so it must be governed and cycle-free. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +### 6.3 SKU Definition & Classification + +#### Define a SKU + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-define-sku` + +Defining a SKU **MUST** link it to the Product, assign `skuId`/`skuCode`, type it `product`/`service`/`bundle`, and validate the per-type required-field set. A `bundle`-typed SKU **MUST** persist only the type flag and identity (composition authored in plan-price; blocking completeness enforced at `CatalogVersion` publish). Promotional/$0/"Free" SKUs **MUST** follow identical registry rules; there is no separate promo entity. + +**Rationale**: A uniform, type-aware SKU contract lets downstream bind without re-validation. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +#### Declare metering unit + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-metering-unit-declaration` + +Declaring a metering unit **MUST** validate it against the configured recognized-unit set and reject an unrecognized unit unless elevated approval marks a new validated unit. A usage SKU declares **exactly one** unit (single-dimension); multi-dimension usage **MUST** be expressed via separate SKUs composed at plan/bundle level. A draft whose unit was `deprecated` before first publish **MUST** be treated as a new declaration and rejected. The declared unit **MUST** be carried on publish; this PRD **MUST NOT** compute charges. + +**Rationale**: Declaring the unit is what defines a usage SKU; validation prevents downstream rate corruption. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +#### Metering-unit de-listing + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-metering-unit-delisting` + +De-listing a metering unit **MUST** be rejected while ≥ 1 `published` SKU references it; the system **MUST** instead support marking it **deprecated** (no new declarations) with full removal only once unreferenced. A unit's identity/semantics are **immutable** (e.g. `GB-storage` MUST NOT be silently redefined to GiB); a correction is a new unit + deprecation of the old. De-listing/deprecation **MUST** be audited and **MUST NOT** mutate any frozen snapshot. + +**Rationale**: Redefining a live unit corrupts every downstream rate; deprecate-then-remove protects existing references. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +#### PlanTier classification + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-plantier-classification` + +Assigning `PlanTier` **MUST** validate against the taxonomy owned here and carry it on the published SKU. A `PlanTier` value has a **stable tier code** as identity; **rename affects the display label only**. Managing the taxonomy (add/rename/retire) is a governed (two-person), audited operation emitting `PlanTierUpdated`; a value **MUST NOT** be retired while any `published` SKU carries it (deprecate-then-retire). The taxonomy **MUST** be seeded with a neutral value. `PlanTier` **MUST NOT** be conflated with OrgTier; plan-publish presence enforcement is delegated to plan-price. + +**Rationale**: Stable tier codes keep SLA/quota policies from rippling on rename; universal presence is a manifest mandate. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +#### Stable accounting codes on SKU + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-accounting-codes` + +Setting tax-category and GL codes **MUST** persist them as stable codes and **validate each against a configured recognized set** (owner = Finance, deprecate-then-remove governance). The codes are **required at publish for `product`/`service`-type SKUs**; a SKU published without a required code is unpostable and **MUST** be rejected. The system **MUST NOT** compute tax or post to GL (codes only). + +**Rationale**: Validating codes at authoring prevents unpostable SKUs surfacing weeks later at ERP export. + +**Actors**: `cpt-cf-bss-product-sku-actor-finance-reviewer` + +### 6.4 Attributes & Localization + +#### Localized attributes, well-known display fields, and definition lifecycle + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-localized-attributes` + +Adding i18n attribute **values** **MUST** validate against the attribute **definition**, require the default-locale value (rejected at publish if absent), and resolve locale via `(locale, region, brand) → (locale, brand) → (default-locale, brand) → global` (default-locale resolved per brand, falling back to tenant default). The registry **MUST** seed **well-known display attribute definitions** (localized display name/description for Product/SKU/Category). Managing attribute **definitions** is a governed live-entity operation emitting `AttributeDefinitionUpdated`; changes **MUST** be backward-compatible and follow a deprecate-then-remove lifecycle. The registry **MUST** provide an ungoverned, size-bounded, non-localized, search-excluded, PII-prohibited **metadata map** for machine metadata. + +**Rationale**: Localized display without a second identity key, plus a governed/ungoverned split, keeps portals correct and integrations from flooding the definition registry. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +### 6.5 Versioning, Lifecycle & Deprecation + +#### Revision vs published version + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-revision-vs-version` + +The system **MUST** increment the **internal revision** on every save (rejecting stale-revision writes via optimistic concurrency) while the **published version** increments only on publish. Consumers and `CatalogVersion` **MUST** reference the published version; historical versions **MUST** be retained with a diff and **MUST NOT** be modifiable. **Version binding** **MUST** be explicit: a new reference binds to the latest published version at bind time; a frozen reference keeps its snapshot; a bound-but-not-yet-frozen reference re-resolves at freeze and **MUST** surface a version-change diff to the freezing module rather than silently swapping. + +**Rationale**: Separating draft churn from published versions keeps downstream snapshots and quotes stable and auditable. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +#### Lifecycle transitions & reversibility + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-lifecycle-transitions` + +The state machine `draft → published [↔ deprecated] → retired` (+ `draft → discarded`) **MUST** allow only its defined transitions, treat `retired` and `discarded` as terminal (revival only via clone), and allow downstream referencing only for `published`/`deprecated`. Entity publish **MAY** be **scheduled** (`publishAt`, UTC) with approval pinned at scheduling time and re-validated fail-closed at activation. Publication of incomplete entities **MUST** be rejected. There is **no `unpublish`** and **no in-place rollback** — retraction/reversion is `deprecate`/`retire` + a new version (forward-only). + +**Rationale**: A constrained, forward-only state machine keeps caches/snapshots/posted content consistent. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +#### Parent-child (Product↔SKU) lifecycle integrity + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-parent-child-integrity` + +A SKU **MUST NOT** reach `published` while its parent Product is not `published`, and the system **MUST NOT** orphan a `published` SKU under a `retired` Product. A SKU's brand/region scope **MUST** be contained within its parent's; a scope-narrowing Product publish **MUST** fail closed while any non-`retired` child would fall outside. Retiring a Product with non-`retired` SKUs **MUST** require confirmed **cascade-retire** (partial by design, recording `direct` vs `cascaded` provenance; EOL-requiring children left un-retired and listed; never-published children auto-`discarded`). When a partial cascade leaves children, the parent **MUST** remain non-`retired` and its deferred-retire intent tracked/queryable. + +**Rationale**: Hierarchy and scope-containment invariants prevent orphaned or out-of-scope published content. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +#### Deprecation (governed sub-state) + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-deprecation` + +Marking a `published` SKU `deprecated` **MUST** move it to the `deprecated` sub-state, mark it so consumers **block new adoption** while existing references continue, and emit `SkuDeprecated`. `deprecated` **MUST** be a tracked, queryable state (not a flag), recording provenance `direct` (vs `cascaded`). The registry marks and exposes; the consumer enforces the new-adoption block (CI-verified via the seam suite). + +**Rationale**: A tracked sub-state makes the new-adoption guard testable and consistent with the composition-pending pattern. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +#### Un-deprecation + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-undeprecation` + +`deprecated → published` **MUST** be allowed under the two-person rule, re-open new adoption, and emit `SkuUndeprecated`. Un-deprecating a **Product** reverses **only `cascaded`** child deprecations and **MUST NOT** revive a child's `direct` deprecation. The transition **MUST** be audited; a `retired` entity **MUST NOT** be reversible. + +**Rationale**: Provenance-aware reversal prevents accidentally reviving individually-deprecated children. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +#### Retirement / EOL consumer handoff + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-retirement-eol` + +Retiring a referenced SKU **MUST** require explicit confirmation with the active-reference count shown, then run as a **scheduled transition**: force `deprecated` at initiation (new adoption blocked immediately, still browsable), preserve snapshots, emit `SkuRetired`/`ProductRetired` with `{ skuId, fromVersion, reason, replacedBy?, mustMigrateBy?, effectiveAt }` honoring the ≥ 30-day lead-time, then flip to `retired` at `effectiveAt`. The registry is SoR for `replacedBy` (a successor `published` SKU). **v1 = plain retirement + grandfathering only; EOL-with-`mustMigrateBy` is a defined-but-deferred post-v1 follow-on** that MUST stay disabled until the consuming subscriptions-lifecycle AC exists and is referenced by number, and requires a consumer acknowledgment contract (lapsed ack ⇒ suspend fail-closed + `SkuEolSuspended`). + +**Rationale**: A defined lead-time state and successor pointer let consumers migrate safely without undefined limbo. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +### 6.6 Catalog Versioning & Snapshots + +> **Two publish layers.** A Product/SKU has its own **published version** that increments each time that entity is published. A **`CatalogVersion`** is a catalog-wide immutable snapshot; a SKU can be published yet not appear in any `CatalogVersion` until the next catalog publish. New references bind to the latest published entity version at bind time; posted/contractual references freeze to a `catalogVersionId`. + +#### Publish an immutable catalog version + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-catalog-version-publish` + +Publishing a `CatalogVersion` **MUST** persist a **full** snapshot (published Product/SKU set + their versions + current categories/attributes captured together), assign a monotonic `catalogVersionId`, generate a checksum, record `stagedAt`/`publishedAt`, capture the freeze-participant set, and make it immutable (storing references to plan-price/Contracts/Billing content, not that content). An uncomposed `bundle` SKU **MUST** require explicit two-person override (blocking-with-override) and be flagged `compositionPending = true`. It **MUST** emit `CatalogVersionPublished` and expose per-version `freezeComplete`. A published `CatalogVersion` **cannot be withdrawn or rolled back** (roll-forward N+1 only); snapshot boundary is the whole tenant (serialized). + +**Rationale**: A full, immutable, checksummed snapshot is the reproducibility anchor for posted invoices and contracts. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +#### Snapshot reproducibility + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-snapshot-reproducibility` + +Re-resolving a `catalogVersionId` at any future time **MUST** yield a byte-identical checksum and unchanged **registry** content (manifest §4.4). `CatalogVersion` **MUST** be exposable as **one component** of a downstream `pricingSnapshotRef` without asserting it equals the full snapshot; referenced-module content reproducibility is governed by the freeze protocol. + +**Rationale**: Byte-identical re-resolution is required for posting immutability and dispute defensibility. + +**Actors**: `cpt-cf-bss-product-sku-actor-billing` + +#### Cross-module snapshot freeze atomicity + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-freeze-atomicity` + +The system **MUST** expose a `freezeComplete` flag per `catalogVersionId` and **reject resolution for posted/contractual use** until all registered freeze-participants acknowledge, with a bounded timeout that **fails closed**. Read-only browse **MAY** proceed during the freeze window. The resolution API **MUST** require the consumer to **declare intent** (`browse` vs `posted/contractual`) so a consumer cannot post against a not-yet-`freezeComplete` version by mislabeling its call (consumer-side obligation, CI-verified in the seam suite). + +**Rationale**: Cross-module atomicity prevents posting against a partially-frozen snapshot. + +**Actors**: `cpt-cf-bss-product-sku-actor-plan-price` + +#### Freeze recovery & force-completion + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-freeze-recovery` + +For a `CatalogVersion` past the freeze timeout the system **MUST** identify each non-acknowledging participant, support an **idempotent re-trigger** of the fan-out, and support **force-completion** under the two-person rule that records each missing participant as explicitly **not-frozen** and emits `FreezeForceCompleted`. Force-completion **MUST NOT** mark missing content as frozen; the default is **pinned fail-closed** for that participant's content (auto-fallback is an off-by-default later enhancement). + +**Rationale**: A stuck freeze must be recoverable without silently marking content frozen. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +#### Freeze-participant set governance + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-freeze-participant-governance` + +Freeze-participant membership changes **MUST** be governed (two-person), audited, and each `CatalogVersion` **MUST** snapshot the participant set at publish time so a historical version re-resolves `freezeComplete` against its original participants. A participant removed after publish **MUST NOT** retroactively flip that version's `freezeComplete`. + +**Rationale**: Historical versions must re-resolve against the participants that existed at publish. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +#### Grandfathering invariant + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-grandfathering-invariant` + +The registry **MUST** guarantee a grandfathered frozen snapshot is **never mutated**; retirement/deprecation affects only new adoption, never existing frozen references. Grandfathering **eligibility policy** is owned by plan-price / subscriptions-lifecycle; this requirement makes the delegation auditable from the registry side. + +**Rationale**: Existing terms must persist byte-identically after the underlying SKU is deprecated/retired. + +**Actors**: `cpt-cf-bss-product-sku-actor-subscriptions` + +#### Uncomposed-bundle adoption guard + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-bundle-adoption-guard` + +A `bundle` SKU published with the uncomposed override **MUST** carry `compositionPending = true` until plan-price composes it, and consumers **MUST** treat `compositionPending` SKUs as **not-yet-adoptable** for new references. Clearing it **MUST** be driven by a plan-price composition signal, audited, and emitted as `BundleCompositionCompleted` (producing a new published version, never mutating a prior frozen `CatalogVersion`). + +**Rationale**: An incomplete bundle must be reproducible-as-pending and blocked from new adoption until composed. + +**Actors**: `cpt-cf-bss-product-sku-actor-plan-price` + +### 6.7 Approval, Publishing & Eventing + +#### Materiality-gated publish + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-materiality-gated-publish` + +A **material** change (touching `PlanTier`/metering-unit/`taxCategory`/`glCode`, a lifecycle transition to `published`/`deprecated`/`retired`, a Category create/rename/re-parent/retire/delete, a material attribute-definition change, or exceeding the configured affected-entity count) **MUST** enforce a two-person rule: ≥ two distinct approvers, each distinct from the author and holding CatalogAdmin or FinanceReviewer; a **finance-material** field (`taxCategory`, `glCode`, `PlanTier`) **MUST** include ≥ 1 FinanceReviewer. An approval **MUST** be **pinned to the internal revision**; any subsequent edit invalidates it and re-queues with the diff re-presented. The materiality rule **MUST** be a typed, configurable policy with an enforceable interim default (§17.1); a rejection returns the entity to `draft` with reason recorded. + +**Rationale**: Two-person control with separation of duties and revision-pinning prevents unauthorized or bypassed publishes. + +**Actors**: `cpt-cf-bss-product-sku-actor-finance-reviewer` + +#### Idempotent authoring boundary + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-idempotent-authoring` + +For a retried create/update/publish with an idempotency key, the same key + identical payload **MUST NOT** create duplicate entities, versions, or events; the key **MUST** be scoped per tenant + endpoint + client key and retained ≥ 24h **and never less than the maximum freeze timeout**. Reuse with a **different** payload **MUST** be rejected as a conflict (no silent no-op). + +**Rationale**: Idempotency prevents duplicate publishes on retry, including after the freeze window. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +#### Registry eventing & audit + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-registry-eventing-audit` + +Every state-changing mutation **MUST** publish the corresponding CloudEvents 1.0 event onto the shared event system, stamping correlation/causation + idempotency key + per-aggregate ordering keys `(tenant, aggregate)`; delivery/ordering/durability are owned by the common event system. Every state-changing requirement **MUST** map to exactly one named event (or an explicit "no event" decision in Design). Event payloads **MUST** carry **pseudonymous actor references only** (never direct operator PII). The mutation **MUST** be recorded in an immutable, queryable audit trail. Plan/Price/Bundle-composition events **MUST NOT** be emitted here (owned by plan-price). + +**Rationale**: Complete, pseudonymous, ordered eventing + immutable audit is what makes erasure (AC #35) and downstream consumption work. + +**Actors**: `cpt-cf-bss-product-sku-actor-events-audit` + +#### Event schema versioning & replay + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-event-versioning-replay` + +Every event **MUST** carry a `dataschema` URI with a semantic version; a consumer pinned to `vN` **MUST** deserialize `vN+1` (new fields optional with defaults); out-of-order/duplicate delivery beyond the idempotency window **MUST** be detectable via `(tenant, aggregate, sequence)`. The system **MUST** provide a **bootstrap path** (latest `CatalogVersion` + event tail) for published-scope consumers, and **MUST** detect when a consumer checkpoint predates the available event tail and **fail loudly**. + +**Rationale**: Forward-compatible schemas + a bootstrap path let consumers evolve and recover without full historical replay. + +**Actors**: `cpt-cf-bss-product-sku-actor-events-audit` + +### 6.8 Multi-Tenancy & Read Models + +#### Tenant/brand/region isolation & break-glass + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-tenant-isolation-breakglass` + +Cross-scope query/mutation **MUST** be denied by default at the API gateway and the attempt audited. Privileged platform-owner cross-tenant access **MUST** use **break-glass** elevation that is time-boxed, reason-required, separately alertable, and itself two-person-approved or post-hoc-reviewed; standing cross-tenant access **MUST NOT** be granted. + +**Rationale**: Cross-tenant catalog leakage is a critical commercial/competitive incident class. + +**Actors**: `cpt-cf-bss-product-sku-actor-platform-owner` + +#### Break-glass action scope + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-breakglass-action-scope` + +Break-glass **MUST** permit **read and audit-export only**; any write/publish under break-glass **MUST** be separately gated (two-person + distinct alert) or disallowed in v1. Every break-glass action **MUST** be individually audited with the elevation reason and correlation ID. + +**Rationale**: Elevation must not silently grant write authority in a foreign tenant. + +**Actors**: `cpt-cf-bss-product-sku-actor-platform-owner` + +#### Cache-first browse/search with bounded convergence + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-cache-first-browse` + +Browse/search/filter **MUST** be served from cache-first read models scoped to the caller's tenant/brand/region, converging within its own budget (interim p99 < 2 s after write commit). Stale reads during the window **MUST** be safe (never expose unpublished or cross-scope content) and **MUST** carry the `asOfCatalogVersion` staleness signal. The per-state visibility contract **MUST** hold: `published` browsable; `deprecated` browsable with a machine-readable flag and excludable by filter; `retired` excluded from default browse and retrievable only via explicit history query. + +**Rationale**: The show-stopper read NFRs and Performance vector are premised on a cache-first read model with a safe staleness contract. + +**Actors**: `cpt-cf-bss-product-sku-actor-presentation` + +### 6.9 Bulk Operations + +#### Bulk import/export + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-bulk-import-export` + +Bulk import/export **MUST** apply **per-row idempotency**, report per-row success/failure (no hidden partial failure), and never leave a partially-inconsistent published state. Dependent rows **MUST** apply two-phase (stage-all-then-commit) or dependency-ordered, never committing an orphan. Idempotency operates at two levels (batch key + per-row keys). A bulk operation **MUST** emit a coalesced `CatalogBulkOperationCompleted` (no event storm). **Bulk import lands entities in `draft`**; publication remains gated, approved against an **aggregated change report** (counts, per-type summary, sample, lint findings). Export **MUST** be deterministic for a given `catalogVersionId`. + +**Rationale**: Onboarding/migration at ≥ 10K-SKU scale cannot be row-by-row and must stay consistent and governed. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +### 6.10 Cloning + +#### Clone a product/SKU + +- [ ] `p3` - **ID**: `cpt-cf-bss-product-sku-fr-clone` + +Cloning a source Product/SKU (draft, published, or **retired** — the sanctioned revival path) **MUST** create a new `draft` with new `productId`/`skuId` and a new `skuCode`/optional `productCode` (system-suggested, operator-overridable, atomically reserved), copying structure/attributes/scoping/category/`PlanTier`/metering-unit while resetting lifecycle and version counters and **never copying** pricing/plan content. The cloned metering unit, `PlanTier`, and category assignment **MUST** be re-validated against live registries; the clone **MUST** fail or force re-selection if any was de-listed/deprecated/retired. It **MUST** record a `clonedFrom` reference and **MUST NOT** affect the source. + +**Rationale**: Cloning accelerates catalog expansion and is the safe revival path for retired items, provided re-validation runs. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +### 6.11 Data Retention & Erasure + +#### Retention & right-to-erasure + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-retention-erasure` + +The system **MUST** retain financial/version/audit records for the configured retention duration and satisfy erasure of **actor PII** by **pseudonymizing** it across audit, entity version fields, and the actor identity-reference map — never deleting immutable financial/version records; because events carry only pseudonymous actor references, updating the reference map completes erasure without touching immutable event streams. Attribute/description free-text **MUST NOT** contain personal data — enforced by a **validation block at write** (hard prohibition, no erasure carve-out, fail-closed on uncertainty, curated allow-list for legitimate person-named products). Erasure **MUST NOT** break `CatalogVersion` reproducibility or audit completeness. + +**Rationale**: Content-erasure is logically incompatible with byte-identical reproducibility, so PII is kept out at write and actor PII is pseudonymized. + +**Actors**: `cpt-cf-bss-product-sku-actor-auditor` + +### 6.12 Cross-PRD Consistency + +#### Registry ↔ plan-price seam + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-plan-price-seam` + +There **MUST** be a shared schema-version pin and a CI contract test that fails when registry and plan-price diverge on any shared field (`skuId`, `bundle` type, metering-unit declaration, `PlanTier`, `CatalogVersion`); a runtime divergence **MUST** fail closed (reject the dependent plan publish). The same suite **MUST** assert consumer-side lifecycle obligations: reject adoption of `compositionPending`/`deprecated` SKUs; reject a usage binding when the target SKU has no declared unit (and reject/warn when its unit is `deprecated`) — **this is where usage-completeness is enforced**; consume `mustMigrateBy` (post-v1); resolve grandfathered refs against the frozen snapshot; re-validate on `SkuImmutableFieldCorrected`; and declare intent before `freezeComplete` on posted/contractual resolution. Assertions are authorable only once the referenced counterpart AC exists. + +**Rationale**: A CI-verified seam turns delegated boundaries into enforced contracts rather than assumptions. + +**Actors**: `cpt-cf-bss-product-sku-actor-plan-price` + +#### Monetization-model traceability + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-monetization-traceability` + +The PRD **MUST** expose a traceability map (§17.2) so the registry's deliberate lack of a monetization-model marker does not read as an unmet requirement: flat/per-seat/tiered/volume/hybrid/commitment → authored/evaluated in plan-price + Tariffs; usage → metering-unit declaration here + binding/rating downstream. Absence of a model marker on a SKU **MUST** be treated as intentional, not a missing field. + +**Rationale**: Explicit traceability prevents the boundary from being mistaken for a gap. + +**Actors**: `cpt-cf-bss-product-sku-actor-finance-reviewer` + +### 6.13 Operational Resilience & Concurrency + +#### Expected failure behavior + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-expected-failure-behavior` + +Invalid/conflicting authoring **MUST** fail closed with an audited reason and **MUST NOT** partially apply, for each of: stale-revision write, duplicate idempotency key with different body, taxonomy cycle, unrecognized metering unit without elevation, publish of an incomplete entity, immutable-field change without a valid correction path, reissue of a reserved `skuCode` and concurrent `skuCode` collision, EOL retirement without an acknowledged migration consumer (post-v1), publishing a SKU under a non-`published` parent, a SKU scope falling outside its parent, authoring/cloning against a de-listed/deprecated unit, a bulk row whose in-batch dependency failed, adopting a `compositionPending` bundle, an indeterminate region overlap, and a retention process that would orphan a live grandfathered reference. + +**Rationale**: A single enumerated fail-closed contract keeps negative paths deterministic and auditable. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +#### Event delivery resilience + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-event-delivery-resilience` + +The **shared event system** **MUST** provide bounded-backoff retry, per-consumer delivery state, and an **audited dead-letter** path with alerting (transport owned there). The **registry's own** obligations are limited to: not reporting emission success until the event is **durably accepted**, **surfacing** the per-consumer delivery/dead-letter state as a projection, and never mutating registry state on a delivery failure. During a bus outage, mutations **MAY** commit with events to a durable **outbox** for later emission; the propagation clock starts at durable bus acceptance, not at commit. + +**Rationale**: Resilience mechanics belong to the bus; the registry must not falsely report propagation or lose events. + +**Actors**: `cpt-cf-bss-product-sku-actor-events-audit` + +#### CatalogVersion publish concurrency + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-catalog-publish-concurrency` + +Concurrent publishes within a tenant **MUST** serialize; `catalogVersionId` **MUST** be allocated monotonically without gaps or collisions; a staged entity mutated or retired between stage and publish **MUST** cause that publish to **re-validate fail-closed** (rejected, naming the changed entity) rather than freezing stale or partial content. + +**Rationale**: Per-tenant serialization + re-validation guarantees no published version contains concurrently-invalidated content. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +#### Fail-safe duration tripwire + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-failsafe-tripwire` + +While operating in `SkuReferenceCount`-unavailable fail-safe mode, when break-glass immutable-field corrections exceed a configured rate (interim > 5 in 30 days) the system **MUST** raise an escalation alert and **reclassify `SkuReferenceCount` delivery as a release blocker**, so unbounded degraded operation is detected and escalated, not normalized. + +**Rationale**: The tripwire bounds the fail-safe operational debt in time, not merely acknowledging it. + +**Actors**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +#### `skuCode` reservation concurrency + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-skucode-reservation-concurrency` + +For two concurrent reserve requests for the same `skuCode` within a tenant, the system **MUST** atomically reserve at **create** time, admit exactly one, and reject the other fail-closed with an audited reason; a `draft` reservation **MUST** block a second draft until released or discarded. A `skuCode` changed while still `draft` **MUST** release the previous code; **discarding a never-published draft MUST also release** its `skuCode`/`productCode` reservation (permanent reservation applies only from first publish). + +**Rationale**: Atomic reservation prevents duplicate codes while letting abandoned drafts free the namespace. + +**Actors**: `cpt-cf-bss-product-sku-actor-product-manager` + +#### Reference-producer registration + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-reference-producer-registration` + +Only **registered** producers' signals or silence **MUST** factor into the `referenced` predicate; an unregistered producer's absence **MUST NOT** pin every SKU immutable. Producer-set membership **MUST** be a governed, audited change **snapshotted symmetrically with the freeze-participant set**, and onboarding a new producer **MUST NOT** retroactively flip historical mutability/retirement decisions. + +**Rationale**: Registration prevents a not-yet-onboarded producer from freezing the whole catalog and keeps history stable. + +**Actors**: `cpt-cf-bss-product-sku-actor-subscriptions` + +#### Grandfathered-snapshot retention coupling + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-fr-grandfathered-retention-coupling` + +For a `catalogVersionId` referenced by ≥ 1 **live** grandfathered reference, the snapshot **MUST** remain byte-identically resolvable for as long as a live reference exists, **regardless of the statutory-max retention clock**; retention expiry **MUST** be gated on no live references to that `catalogVersionId`. Because per-SKU `SkuReferenceCount` carries no version dimension, version-liveness **MUST** be sourced from per-version freeze-registration records (or a `(catalogVersionId, producer)` producer contract), never from the SKU-level count alone. A retention process that would orphan a live reference **MUST** fail closed with an alert. + +**Rationale**: Silently GC'ing a snapshot under a live contract breaks reproducibility for every reference frozen to it — a compliance event. + +**Actors**: `cpt-cf-bss-product-sku-actor-billing` + +#### Pre-publish lint report + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-fr-prepublish-lint` + +The `validate(lint)` operation before `CatalogVersion` publish **MUST** return a **structured, per-entity report** of every override-requiring or attention condition (uncomposed `bundle` SKUs, missing default-locale attribute values, declarations against a `deprecated` unit) so the two-person override is an **informed** decision and the audit records **what** was overridden. + +**Rationale**: An informed override beats a blind acknowledgment and produces a meaningful audit trail. + +**Actors**: `cpt-cf-bss-product-sku-actor-finance-reviewer` + +## 7. Non-Functional Requirements + +### 7.1 NFR Inclusions + +> Numeric targets are binding **design targets** until the program NFR workshop (scheduled within 2 weeks of PRD approval; named DRI = BSS Program Lead). Interim configurable-policy defaults are in §17.1. + +#### Read latency + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-nfr-read-latency` + +Browse/search reads within a tenant partition **MUST** meet **p95 < 100 ms** over a 5-minute window on a warm read model holding 10K SKUs/tenant with ≥ 100 concurrent readers, sustained via cache-first read models and tenant/brand/region partitioning. + +**Threshold**: p95 < 100 ms @ 10K SKUs/tenant, ≥ 100 concurrent readers. + +**Rationale**: Slow catalog reads degrade portal/sales UX. + +#### Read throughput + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-nfr-read-throughput` + +The cache-first read model **MUST** sustain **≥ 2,000 read QPS per tenant partition** at the read-latency target. + +**Threshold**: ≥ 2,000 read QPS/tenant partition at p95 < 100 ms. + +**Rationale**: Peak browse/search traffic must not breach latency. + +#### Publication propagation + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-nfr-publication-propagation` + +Downstream event availability (incl. fan-out) after an approved publish **MUST** occur within **< 3 s** — a component preceding freeze acks, distinct from read-model convergence (< 2 s) and end-to-end posting-safe (< 5 s). + +**Threshold**: event availability < 3 s after publish (p99). + +**Rationale**: Delayed publication yields stale offerings downstream; the three nested budgets must not collapse to one. + +#### End-to-end posting-safe budget + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-nfr-posting-safe-budget` + +From write commit to "posting-safe" (read-model converged **and** all participants' `freezeComplete` acknowledged) **MUST** be **p99 < 5 s**; if the freeze times out the version **MUST** remain non-posting-safe (fail closed). This composite is a **program-level SLO** decomposed into a registry-owned `commit → event-durably-published` budget and per-participant `event → ack` budgets. + +**Threshold**: p99 < 5 s commit → posting-safe (fail-closed on freeze timeout). + +**Rationale**: Downstream needs a single SLA to design against; freeze acks follow fan-out. + +#### Snapshot archival & cold-resolution SLA + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-nfr-snapshot-archival-dr` + +Cold `catalogVersionId` re-resolution **MUST** remain byte-identical and meet a looser-than-hot target (interim p95 < 2 s). `CatalogVersion` snapshots + version history are **financial records** with a durability class (interim **≥ 11 nines** / replicated storage), backup/restore with **periodic checksum restore verification**, and a cross-region/DR posture with RPO/RTO (set at the NFR workshop). Availability SLOs do **not** substitute for durability. + +**Threshold**: cold p95 < 2 s; durability ≥ 11 nines; periodic restore verification; RPO/RTO TBD (workshop). + +**Rationale**: Silently losing one snapshot breaks reproducibility for every contract frozen to it — a compliance event. + +#### Scale & extensibility limits + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-nfr-scale-extensibility` + +The system **MUST** support **≥ 10K SKUs per tenant** without breaching read latency, within configured limits (max attributes/entity, max taxonomy depth, max children/node). The scale model **MUST** also bound tenant count, total cardinality, and **`CatalogVersion` growth** (full-snapshot-per-publish is the dominant cost driver) with a publishes/day/tenant target set at the workshop. + +**Threshold**: ≥ 10K SKUs/tenant; extensibility limits + publish-frequency target per workshop. + +**Rationale**: Full-snapshot economics and extensibility limits bound the design. + +#### Graceful degradation & staleness exposure + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-nfr-graceful-degradation` + +Above the throughput ceiling or read-model lag, the system **MUST** shed or queue excess load **without ever serving cross-scope or unpublished content**, and **MUST** expose staleness via the **same `asOfCatalogVersion` mechanism** (one signal, machine-readable) — no silently-stale degraded response. + +**Threshold**: zero cross-scope/unpublished leakage under overload; machine-readable `asOfCatalogVersion` on every stale response. + +**Rationale**: Overload must never compromise isolation or hide staleness. + +#### Determinism & integrity + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-nfr-determinism-integrity` + +Version immutability, taxonomy acyclicity, SKU identity uniqueness, and metering-unit validity **MUST** be enforced fail-closed, and posted-period `CatalogVersion` snapshots **MUST** remain immutable. + +**Threshold**: 100% fail-closed enforcement of the registry invariants. + +**Rationale**: The registry is the integrity foundation all monetization binds to. + +#### Backward-compatible schema evolution + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-nfr-backward-compatible-evolution` + +A consumer pinned to schema `vN` **MUST** successfully deserialize a `vN+1` payload (new fields optional with defined defaults); a CI contract test **MUST** assert backward compatibility on every schema change. + +**Threshold**: 100% `vN`→`vN+1` deserialization; CI-guarded on every schema change. + +**Rationale**: New product categories/fields must not break published content or downstream contracts. + +#### Availability & audit completeness + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-nfr-availability-audit` + +The cache-first **read** path **MUST** meet **99.9%** availability and the **write/publish** path **99.5%** (reads must not block downstream when writes degrade); write paths **MUST** be fully audited even during partial failures. + +**Threshold**: read 99.9% / write 99.5% availability; 100% write-path audit. + +**Rationale**: Reads feeding portals/sales must stay up independently of write degradation. + +### 7.2 NFR Exclusions + +- **Pricing/rating performance** — owned by plan-price / Tariffs / Rating; the registry only serves published primitives. +- **Usage collection/normalization throughput** — OSS metering / Usage Collector. +- **Event-bus transport SLOs (delivery latency, DLQ retention)** — owned by the common event system (Common Core); the registry states only its own emission/projection obligations. +- **Storefront UX performance / accessibility (WCAG) / i18n rendering** — Presentation layer / frontend DESIGN. +- **Marketplace listing/search performance** — Marketplace PRD (§4.8). + +## 8. Five Quality Vectors Analysis + +| **Quality Vector** | **Show-Stopper Requirements** | **Rationale** | +|--------------------|-------------------------------|---------------| +| **Efficiency** | Product/SKU/category/attribute changes MUST be operator self-service with automated versioning and approvals — no engineering involvement for routine registry change. | Manual catalog management blocks catalog growth and time-to-market. | +| **Reliability** | Immutable Product/SKU versioning, byte-identical `CatalogVersion` snapshots, 100% audited write paths, fail-closed publish validation, and a CI contract test guarding the registry↔plan-price seam. | The registry is the foundation all monetization binds to; silent drift or lost history breaks downstream snapshots and compliance. | +| **Performance** | Browse/search p95 < 100 ms and ≥ 2,000 read QPS/tenant partition via cache-first read models; nested propagation budgets (convergence < 2 s, propagation < 3 s, posting-safe < 5 s, cold-version < 2 s); graceful degradation. | Slow catalog reads degrade portal/sales UX; delayed publication yields stale offerings. | +| **Security** | Complete tenant/brand/region isolation (deny-by-default), RBAC, two-person rule for material changes, time-boxed break-glass for cross-tenant access, retention/erasure reconciled with immutable audit, minimal PII in events. | Cross-tenant leakage is a commercial risk; unauthorized publish is a fraud risk; standing super-access and unbounded retention are compliance risks. | +| **Versatility** | Extensible attributes/taxonomy and a type-agnostic SKU model (product/service/bundle) with backward-compatible schema evolution (`vN` deserializes `vN+1`). | New product categories must be added without breaking published content or downstream contracts. | + +## 9. Public Library Interfaces + +> The registry is a backend service, not a client library. Interfaces below are high-level contracts; concrete API schemas, endpoints, event payloads, and DDL belong in DESIGN. + +### 9.1 Public API Surface + +#### Catalog authoring & publish + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-interface-authoring-publish` + +**Type**: command/authoring + approval-gated publish API (shape in Design) + +**Stability**: stable (contract intent), schema unstable (Design owns) + +**Description**: Create/update Products, SKUs, categories, attributes, `PlanTier`; declare metering units and accounting codes; lifecycle transitions; two-person-gated publish of entities and `CatalogVersion`; idempotent by key. Requires the resolution caller to **declare intent** (`browse` vs `posted/contractual`). + +**Breaking Change Policy**: Major version bump; idempotency-key and intent-declaration semantics are part of the contract. + +#### Catalog read model (browse/search) + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-interface-read-model` + +**Type**: cache-first query/read API (shape in Design) + +**Stability**: stable (contract intent) + +**Description**: Tenant/brand/region-scoped browse/search/filter of published Products/SKUs/Categories with the per-state visibility contract and an `asOfCatalogVersion` staleness signal; version-history retrieval. + +**Breaking Change Policy**: Major version bump for incompatible query/response changes. + +### 9.2 External Integration Contracts + +#### CatalogVersionPublished + registry events (outbound) + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-contract-registry-events` + +**Direction**: provided by the registry to the shared event system + +**Protocol/Format**: CloudEvents 1.0 with `dataschema`+semver, correlation/causation, per-aggregate ordering keys, pseudonymous actor refs; includes `CatalogVersionPublished` and the full Product/SKU/Category/Attribute/governance event set (Design owns names/schemas). + +**Compatibility**: `vN` consumer deserializes `vN+1`; bootstrap path (latest `CatalogVersion` + tail); no direct PII. + +#### `SkuReferenceCount` signal (inbound) + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-contract-sku-reference-count` + +**Direction**: required from Subscriptions, Contracts, plan-price + +**Protocol/Format**: per-producer watermark ("as of `T`, complete live-reference set is {…}"); freshness on the watermark; registered producers only (Design owns shape). + +**Compatibility**: absence under a fresh watermark ⇒ zero; boolean OR across producers; stale/never-received ⇒ conservatively referenced + alert. **Pre-approval gate**: owner + delivery date (§15). + +#### Freeze acknowledgment (inbound) + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-contract-freeze-ack` + +**Direction**: required from plan-price, Contracts, Billing + +**Protocol/Format**: per-`catalogVersionId` freeze acknowledgment feeding `freezeComplete` (Design owns shape). + +**Compatibility**: bounded timeout fails closed; participant set snapshotted per `catalogVersionId`; force-completion records missing participants as not-frozen. + +#### Bundle composition-completed signal (inbound) + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-contract-bundle-composition-signal` + +**Direction**: required from plan-price + +**Protocol/Format**: signal that a `bundle` SKU has been composed, clearing `compositionPending` (Design owns shape). + +**Compatibility**: clearing produces a new published version and emits `BundleCompositionCompleted`; MUST NOT mutate a prior frozen `CatalogVersion`. + +## 10. Use Cases + +#### Author products and SKUs + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-usecase-product-sku-editor` + +**Actor**: `cpt-cf-bss-product-sku-actor-product-manager` + +**Preconditions**: +- Established tenant/brand/region context; CatalogAdmin or ProductManager role. + +**Main Flow**: +1. Create/select a Product (name, category, description, brand/region scope); `productId`/`skuId` system-generated, `skuCode` operator-entered with inline format check. +2. Add a SKU; pick type product/service/bundle. +3. For a usage SKU, declare the metering unit (validated); set `PlanTier` from the taxonomy. +4. Set tax/GL codes; save as draft (each save bumps the internal revision). + +**Postconditions**: +- A draft Product/SKU exists with reserved codes and an audit entry, pending gated publish. + +**Alternative Flows**: +- **Incomplete at publish**: publish rejected fail-closed (missing required fields / `PlanTier` / accounting codes). + +#### Approve and publish registry changes + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-usecase-approval-publish` + +**Actor**: `cpt-cf-bss-product-sku-actor-finance-reviewer` + +**Preconditions**: +- Pending material change(s) in the approval queue. + +**Main Flow**: +1. Open the pending-approval queue; review the diff and (for `CatalogVersion` publish) the pre-publish lint report. +2. Approve/reject with reason; two-person rule enforced above threshold (≥ 1 FinanceReviewer for finance-material fields). +3. On full sign-off, publish triggers the entity/`CatalogVersion` publish + events. + +**Postconditions**: +- Content published (or returned to draft with reason); approval pinned to the approved revision. + +**Alternative Flows**: +- **Edit after approval**: approval invalidated, change re-queued with diff re-presented. + +#### Deprecate and retire safely + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-usecase-lifecycle-deprecation` + +**Actor**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +**Preconditions**: +- A `published` SKU with (possibly) active references. + +**Main Flow**: +1. Mark `deprecated` (blocks new adoption; existing continue) or un-deprecate (two-person). +2. Initiate retire/EOL; the system shows the active-reference count and requires confirmation. +3. Confirm; snapshots preserved; retirement event emitted with lead-time and optional `replacedBy`. + +**Postconditions**: +- Scheduled transition set; existing references grandfathered on frozen snapshots. + +**Alternative Flows**: +- **Cascade with EOL-requiring children**: those children listed and left un-retired; parent stays non-`retired` with deferred-retire intent tracked. + +#### Browse, search, and inspect history + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-usecase-catalog-browser-history` + +**Actor**: `cpt-cf-bss-product-sku-actor-auditor` + +**Preconditions**: +- Published catalog content and version history exist for the scope. + +**Main Flow**: +1. Filter/search (category, status, brand/region) on the cache-first read model. +2. Open an item; view attributes/classification. +3. Open the version timeline with diffs and audit entries (actor, time, correlation ID). + +**Postconditions**: +- Offerings found; change lineage traced (tenant-scoped). + +**Alternative Flows**: +- **Cross-tenant inspection**: requires time-boxed break-glass (read/audit-export only), individually audited. + +#### Bulk import/export at scale + +- [ ] `p1` - **ID**: `cpt-cf-bss-product-sku-usecase-bulk-operations` + +**Actor**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +**Preconditions**: +- A CSV/JSON batch of Products/SKUs/Categories. + +**Main Flow**: +1. Upload/import; rows land as **draft** with per-row validation and per-row idempotency. +2. Review the aggregated change report (counts, per-type summary, sample, lint findings). +3. Submit the batch for gated approval (two-person on the batch); track per-row success/failure. + +**Postconditions**: +- Rows applied consistently (no orphan/partial publish); a coalesced `CatalogBulkOperationCompleted` emitted. + +**Alternative Flows**: +- **Dependent-row failure**: dependent rows fail with a distinct per-row error; no orphan committed. + +#### Inspect and recover a stuck freeze + +- [ ] `p2` - **ID**: `cpt-cf-bss-product-sku-usecase-freeze-monitoring` + +**Actor**: `cpt-cf-bss-product-sku-actor-catalog-admin` + +**Preconditions**: +- A `CatalogVersion` whose `freezeComplete` has not been reached past the timeout. + +**Main Flow**: +1. View per-`catalogVersionId` `freezeComplete` status and each non-acknowledging participant. +2. Idempotently re-trigger the freeze fan-out. +3. Force-complete under the two-person rule (records missing participants as not-frozen; emits `FreezeForceCompleted`). + +**Postconditions**: +- Freeze resolved or force-completed with missing participants pinned fail-closed for posted use. + +## 11. User Interaction and Design + +| **Interface Name** | **Role** | **Steps** | **Mockup Screen** | +|--------------------|----------|-----------|-------------------| +| Product & SKU editor | As a Product Manager, I define products and SKUs so the catalog foundation is accurate | 1. Create/select Product (name, category, description, scope); `productId`/`skuId` system-generated, `skuCode` operator-entered with format check
2. Add SKU; pick type
3. For usage SKU declare metering unit; set PlanTier
4. Set tax/GL codes
5. Save as draft | — | +| Category & taxonomy manager | As a Product Manager, I organize products into a taxonomy so browse/search and listings are coherent | 1. Open taxonomy tree
2. Create/re-parent Category (uniqueness + cycle checks)
3. Assign products to categories | — | +| Attribute & localization editor | As a Product Manager, I add localized attributes so products read correctly per brand/region | 1. Open attribute editor
2. Add key/value; enable i18n; add locale values
3. Set brand/region visibility; configure fallback | — | +| Catalog approval & publish console | As a Finance Reviewer, I review and approve registry changes before publication | 1. Open pending-approval queue
2. Review diff (+ lint report)
3. Approve/reject with reason; two-person above threshold
4. On sign-off, publish triggers CatalogVersion + events | — | +| Lifecycle & deprecation manager | As a Product Manager, I deprecate and retire SKUs safely so existing references are unaffected | 1. Select published SKU
2. Deprecate / un-deprecate (two-person)
3. Initiate retire/EOL; system shows active-reference count and optional `mustMigrateBy`
4. Confirm; snapshots preserved; retirement event with lead-time | — | +| Catalog browser & version history | As a Partner/Auditor, I browse/search and inspect version history to find offerings and trace changes | 1. Filter/search (category, status, brand/region) on cache-first read model
2. Open item; view attributes/classification
3. Open version timeline with diffs and audit entries | — | +| Bulk operations console | As a CatalogAdmin, I import/export catalog entities in bulk so onboarding and mass edits scale | 1. Upload/import (CSV/JSON); rows land as draft with per-row validation
2. Review aggregated change report (counts, summary, sample, lint)
3. Submit for gated approval; track per-row status
4. Export a deterministic snapshot for a `catalogVersionId` | — | +| Freeze monitoring & recovery console | As an Operator, I inspect and recover stuck cross-module freezes so posting is never blocked silently | 1. View per-`catalogVersionId` `freezeComplete` and non-acknowledging participants
2. Idempotently re-trigger the fan-out
3. Force-complete under two-person (emits `FreezeForceCompleted`) | — | +| Operational console (clone, break-glass, deferred retire) | As an Operator/Platform owner, I want clone, break-glass, and deferred-cascade actions in one place | 1. Clone a Product/SKU (incl. a retired source) to a new draft
2. Break-glass cross-tenant read/audit-export under time-boxed elevation
3. Resume a deferred cascade-retire once blocked children clear | — | + +## 12. Acceptance Criteria + +**As a** Product Manager, Catalog Admin, or Finance Reviewer, **I want** an authoritative, governed, versioned catalog registry **so that** plan/price authoring, subscriptions, contracts, rating, and billing build on stable, reproducible product definitions. + +### Identifiers & Integrity + +**1. Identifier contract** +- **Given** a Product or SKU being created +- **When** the system assigns identifiers +- **Then** `productId`/`skuId` MUST be server-generated immutable identifiers (never operator-supplied); `skuCode` MUST be short, fixed-format, tenant-unique, reserved **atomically at create**, and immutable after first publish +- **And** downstream consumers MUST bind to `skuId`; a reused/malformed `skuCode` MUST be rejected with an audited reason; once first published a `skuCode` is permanently reserved within the tenant +- **And** a Product MAY carry an optional `productCode` under the same rules; when unset, product-level external mapping is `productId`-only + +**2. Product/SKU field-mutability matrix** +- **Given** a published Product or SKU +- **When** an operator edits it +- **Then** mutability MUST be classified by lifecycle state: structural identity immutable (remedied only by retire + clone); `type`/metering-unit immutable-but-correctable via the fresh-zero path; material-but-mutable (`PlanTier`/`taxCategory`/`glCode`) via a new published version under governance; other fields via a new version +- **And** an illegal change MUST be rejected fail-closed with an audited reason +- **And** the active-reference count MUST be sourced from `SkuReferenceCount` as the 3-state predicate; never treat an entity as unreferenced absent a fresh watermark + +**3. Reference-signal sourcing, freshness & counting** +- **Given** the `SkuReferenceCount` signal (per-producer watermark; owner/delivery date is a pre-approval gate) +- **When** the registry evaluates mutability/correction/retirement +- **Then** absence of a `skuId` under a fresh watermark ⇒ zero for that producer; `referenced` MUST be a boolean OR across registered producers; the registry MUST NOT sum across producers +- **And** a fresh-zero across all producers ⇒ unreferenced; stale ⇒ conservatively referenced + alert; never-received ⇒ conservative + distinct flag +- **And** only registered producers count; Contracts MUST declare whether draft/quote refs count, identically across AC #2/#4/#18; until the signal ships, #2/#4/#18 run fail-safe + +**4. Immutable-field correction (zero-reference & break-glass)** +- **Given** a published SKU whose correctable immutable field (`type` or metering-unit) was set wrong +- **When** a CatalogAdmin requests a correction +- **Then** if `SkuReferenceCount` is fresh-zero across all producers the system MUST allow a governed re-publish (two-person), bump the version, and emit `SkuImmutableFieldCorrected`; absent a fresh-zero signal it MUST reject fail-closed +- **And** while the signal is entirely unavailable, correction MAY proceed only via break-glass (two-person + reason + `SkuCorrectionOverride` recording signal-unavailability), feature-flag OFF by default + +### Product & Taxonomy Definition + +**5. Create a product** +- **Given** a CatalogAdmin/ProductManager in a tenant/brand/region context +- **When** they create a Product (primary category optional at draft, required at publish) +- **Then** the system MUST generate `productId`, persist as `draft` (version 0) with isolation + audit +- **And** name uniqueness MUST hold on `(tenantId, brandId, normalized(name))` within any overlapping region scope; two same-named Products allowed only when region scopes are disjoint +- **And** indeterminate region overlap MUST fail closed with an operator-facing reason; the region-set algebra is a pre-approval gate + +**6. Manage taxonomy (create, rename, re-parent, retire, delete)** +- **Given** a CatalogAdmin managing the taxonomy +- **When** they create/rename/re-parent/retire/delete a Category +- **Then** the system MUST validate uniqueness within parent (re-checked on rename/re-parent), reject cycles, and reject exceeding max depth/children +- **And** a Product carries exactly one primary + zero-or-more secondary categories; the read model MUST make it filterable under every assigned category +- **And** categories are governed live entities (in-place, two-person-gated, audited); retire/delete MUST be blocked while any active Product references it or active children exist; each op emits a `Category*` event + +### SKU Definition & Classification + +**7. Define a SKU** +- **Given** an existing Product +- **When** a ProductManager defines a SKU typed `product`/`service`/`bundle` +- **Then** the system MUST link it, assign `skuId`/`skuCode`, and validate the per-type required-field set +- **And** a `bundle` SKU persists only type flag + identity (composition in plan-price; blocking completeness at `CatalogVersion` publish) +- **And** promotional/$0/"Free" SKUs follow identical registry rules; no separate promo entity + +**8. Declare metering unit (defines a "usage SKU")** +- **Given** a SKU to be metered (declaring a unit is what defines a usage SKU) +- **When** a ProductManager declares its metering unit +- **Then** the system MUST validate against the configured recognized-unit set and reject an unrecognized unit unless elevated approval marks a new validated unit +- **And** a usage SKU declares exactly one unit (single-dimension); multi-dimension via separate SKUs composed at plan/bundle level +- **And** a draft whose unit was `deprecated` before first publish MUST be treated as a new declaration and rejected; the declared unit MUST be carried on publish + +**9. Metering-unit de-listing** +- **Given** a recognized unit referenced by ≥ 1 published SKU +- **When** an operator attempts to de-list it +- **Then** the system MUST reject removal while live references exist and instead support marking it `deprecated` (no new declarations), with full removal only once unreferenced +- **And** a unit's identity/semantics are immutable (no silent GB→GiB); a correction is a new unit + deprecation; de-listing MUST be audited and MUST NOT mutate any frozen snapshot + +**10. PlanTier classification** +- **Given** a SKU being authored +- **When** a ProductManager assigns its `PlanTier` +- **Then** the system MUST validate against the taxonomy and carry it on the published SKU +- **And** a `PlanTier` value has a stable tier code; rename affects the display label only; taxonomy management is governed (two-person), emits `PlanTierUpdated`, and a value MUST NOT be retired while any published SKU carries it; seeded with a neutral value +- **And** `PlanTier` MUST NOT be conflated with OrgTier; plan-publish presence enforcement is delegated to plan-price + +**11. Stable accounting codes on SKU** +- **Given** a SKU +- **When** a ProductManager sets tax-category and GL codes +- **Then** the system MUST persist them as stable codes and validate each against a configured recognized set (owner Finance) +- **And** the codes are required at publish for `product`/`service`-type SKUs; a SKU published without a required code MUST be rejected +- **And** the system MUST NOT compute tax or post to GL (codes only) + +### Attributes & Localization + +**12. Localized attributes, well-known display fields, and definition lifecycle** +- **Given** a Product/SKU with attributes +- **When** a ProductManager adds i18n values with brand/region visibility +- **Then** the system MUST validate against the attribute definition, require the default-locale value, and resolve locale via `(locale, region, brand) → (locale, brand) → (default-locale, brand) → global` +- **And** the registry MUST seed well-known display attribute definitions (localized display name/description for Product/SKU/Category) +- **And** managing definitions is a governed live-entity op emitting `AttributeDefinitionUpdated`; changes MUST be backward-compatible with a deprecate-then-remove lifecycle +- **And** the registry MUST provide an ungoverned, size-bounded, non-localized, search-excluded, PII-prohibited metadata map for machine metadata + +### Versioning, Lifecycle & Deprecation + +**13. Revision vs published version** +- **Given** a Product/SKU +- **When** an operator saves a draft edit +- **Then** the system MUST bump the internal revision (every save) and reject stale-revision writes, while the published version bumps only on publish +- **And** consumers and `CatalogVersion` MUST reference the published version; historical versions retained with a diff, non-modifiable +- **And** version binding MUST be explicit; a bound-but-not-yet-frozen reference re-resolving to a different version at freeze MUST surface a version-change diff, not silently swap + +**14. Lifecycle transitions & reversibility** +- **Given** the state machine `draft → published [↔ deprecated] → retired` (+ `draft → discarded`) +- **When** an operator requests a transition +- **Then** the system MUST allow only defined transitions, treat `retired`/`discarded` as terminal, and allow referencing only for `published`/`deprecated` +- **And** entity publish MAY be scheduled (`publishAt`, UTC) with approval pinned at scheduling and re-validated fail-closed at activation +- **And** there is no `unpublish` and no in-place rollback — retraction/reversion is `deprecate`/`retire` + a new version (forward-only) + +**15. Parent-child (Product↔SKU) lifecycle integrity** +- **Given** the Product↔SKU hierarchy +- **When** an operator publishes or retires across the hierarchy +- **Then** a SKU MUST NOT reach `published` under a non-`published` parent, and MUST NOT be orphaned under a `retired` Product; a SKU's scope MUST be contained within its parent's +- **And** retiring a Product with non-`retired` SKUs MUST require confirmed cascade-retire (partial by design, recording `direct` vs `cascaded`); EOL-requiring children are listed and left un-retired; never-published children auto-`discarded` +- **And** when a partial cascade leaves children, the parent MUST remain non-`retired` with deferred-retire intent tracked/queryable + +**16. Deprecation (governed sub-state)** +- **Given** a published SKU referenced by active plans/subscriptions/contracts +- **When** an operator marks it `deprecated` +- **Then** the system MUST move it to the `deprecated` sub-state, mark it so consumers block new adoption while existing references continue, and emit `SkuDeprecated` +- **And** `deprecated` MUST be a tracked, queryable state recording provenance `direct` (vs `cascaded`) + +**17. Un-deprecation** +- **Given** a `deprecated` SKU +- **When** an authorized operator un-deprecates it +- **Then** `deprecated → published` MUST be allowed under the two-person rule, re-open new adoption, and emit `SkuUndeprecated` +- **And** un-deprecating a Product reverses only `cascaded` child deprecations, never a `direct` one; a `retired` entity MUST NOT be reversible + +**18. Retirement / EOL consumer handoff** +- **Given** a `published`/`deprecated` SKU with active references +- **When** an operator retires it (optionally EOL with `mustMigrateBy`) +- **Then** the system MUST require confirmation with the active-reference count shown, then run a scheduled transition: force `deprecated` at initiation, preserve snapshots, emit `SkuRetired`/`ProductRetired` with `{ skuId, fromVersion, reason, replacedBy?, mustMigrateBy?, effectiveAt }` honoring the ≥ 30-day lead-time, then flip to `retired` at `effectiveAt` +- **And** the registry is SoR for `replacedBy` (a successor published SKU) +- **And** v1 = plain retirement + grandfathering only; EOL-with-`mustMigrateBy` is post-v1, disabled until the subscriptions-lifecycle AC exists and is referenced by number, and requires a consumer ack contract (lapsed ack ⇒ suspend fail-closed + `SkuEolSuspended`) + +### Catalog Versioning & Snapshots + +**19. Publish an immutable catalog version** +- **Given** approved catalog changes +- **When** a CatalogAdmin publishes a `CatalogVersion` +- **Then** the system MUST persist a full snapshot (published Product/SKU set + versions + current categories/attributes), assign a monotonic `catalogVersionId`, generate a checksum, record timestamps, capture the freeze-participant set, and make it immutable +- **And** an uncomposed `bundle` SKU MUST require explicit two-person override (blocking-with-override), recorded in audit, and be flagged `compositionPending = true` +- **And** it MUST emit `CatalogVersionPublished` and expose per-version `freezeComplete`; a published version cannot be withdrawn/rolled back (roll-forward N+1 only); publishes serialize per tenant + +**20. Snapshot reproducibility** +- **Given** a posted invoice or active contract that referenced a `catalogVersionId` +- **When** the catalog later changes +- **Then** re-resolving that `catalogVersionId` MUST yield a byte-identical checksum and unchanged registry content +- **And** `CatalogVersion` MUST be exposable as one component of a downstream `pricingSnapshotRef` without asserting it equals the full snapshot + +**21. Cross-module snapshot freeze atomicity** +- **Given** a `CatalogVersionPublished` consumed by the registered freeze-participants +- **When** a consumer resolves `catalogVersionId` before all have frozen +- **Then** the system MUST expose `freezeComplete` and reject resolution for posted/contractual use until all participants ack, with a bounded timeout that fails closed +- **And** read-only browse MAY proceed during the freeze window +- **And** the resolution API MUST require the consumer to declare intent (`browse` vs `posted/contractual`) so it cannot post against a not-yet-`freezeComplete` version by mislabeling + +**22. Freeze recovery & force-completion** +- **Given** a `CatalogVersion` past the freeze timeout +- **When** an operator inspects it +- **Then** the system MUST identify each non-acknowledging participant, support an idempotent re-trigger, and support force-completion (two-person) that records each missing participant as not-frozen and emits `FreezeForceCompleted` +- **And** force-completion MUST NOT mark missing content as frozen; the default is pinned fail-closed for that participant's content + +**23. Freeze-participant set governance** +- **Given** the set of freeze-participants +- **When** the set changes +- **Then** membership MUST be a governed (two-person), audited change, and each `CatalogVersion` MUST snapshot the participant set at publish time +- **And** a participant removed after publish MUST NOT retroactively flip that version's `freezeComplete` + +**24. Grandfathering invariant** +- **Given** a reference grandfathered onto a frozen snapshot after its SKU is deprecated/retired +- **When** the catalog subsequently changes +- **Then** the registry MUST guarantee the grandfathered snapshot is never mutated +- **And** grandfathering eligibility policy is owned by plan-price / subscriptions-lifecycle; this AC makes the delegation auditable + +**25. Uncomposed-bundle adoption guard** +- **Given** a `bundle` SKU published with the uncomposed override +- **When** the read model and events expose it +- **Then** it MUST carry `compositionPending = true` until composed, and consumers MUST treat it as not-yet-adoptable for new references +- **And** clearing it MUST be driven by a plan-price composition signal, audited, and emitted as `BundleCompositionCompleted` (new version, never mutating a prior frozen `CatalogVersion`) + +### Approval, Publishing & Eventing + +**26. Materiality-gated publish** +- **Given** a Product/SKU change or a material Category/attribute-definition op +- **When** the change is material (touches `PlanTier`/metering-unit/`taxCategory`/`glCode`, a lifecycle transition, a Category create/rename/re-parent/retire/delete, a material attribute-definition change, or exceeds the configured affected-entity count) +- **Then** the system MUST enforce ≥ two distinct approvers, each distinct from the author and holding CatalogAdmin or FinanceReviewer; a finance-material field MUST include ≥ 1 FinanceReviewer +- **And** an approval MUST be pinned to the internal revision; any subsequent edit invalidates it and re-queues with the diff re-presented +- **And** the rule MUST be a typed configurable policy with an enforceable interim default (§17.1); a rejection returns the entity to `draft` with reason; v1 uses a single two-person step + +**27. Idempotent authoring boundary** +- **Given** a retried create/update/publish carrying an idempotency key +- **When** the system processes the retry +- **Then** the same key + identical payload MUST NOT create duplicate entities/versions/events; the key MUST be scoped per tenant + endpoint + client key and retained ≥ 24h and ≥ the max freeze timeout +- **And** reuse with a different payload MUST be rejected as a conflict (no silent no-op) + +**28. Registry eventing & audit** +- **Given** any state-changing registry mutation that completes +- **When** the write commits +- **Then** the registry MUST publish the corresponding CloudEvents 1.0 event onto the shared event system with correlation/causation + idempotency key + ordering keys `(tenant, aggregate)`; every state-changing AC maps to exactly one named event (or an explicit "no event" in Design) +- **And** payloads MUST carry pseudonymous actor references only (never direct operator PII); the mutation MUST be recorded in an immutable, queryable audit trail +- **And** Plan/Price/Bundle-composition events MUST NOT be emitted here (owned by plan-price) + +**29. Event schema versioning & replay** +- **Given** the CloudEvents of AC #28 +- **When** the schema evolves or a consumer must rebuild state +- **Then** every event MUST carry a `dataschema` URI with a semantic version; a consumer pinned to `vN` MUST deserialize `vN+1`; out-of-order/duplicate delivery beyond the idempotency window MUST be detectable via `(tenant, aggregate, sequence)` +- **And** the system MUST provide a bootstrap path (latest `CatalogVersion` + event tail) for published-scope consumers and MUST fail loudly when a consumer checkpoint predates the available event tail + +### Multi-Tenancy & Read Models + +**30. Tenant/brand/region isolation & break-glass** +- **Given** a user scoped to one tenant/brand/region +- **When** they query/mutate outside their scope +- **Then** the system MUST deny by default at the gateway and audit the cross-scope attempt +- **And** privileged cross-tenant access MUST use time-boxed, reason-required, alertable break-glass, itself two-person-approved or post-hoc-reviewed; standing cross-tenant access MUST NOT be granted + +**31. Break-glass action scope** +- **Given** a platform owner under break-glass elevation +- **When** they access a foreign tenant's catalog +- **Then** break-glass MUST permit read and audit-export only; any write/publish MUST be separately gated (two-person + distinct alert) or disallowed in v1 +- **And** every break-glass action MUST be individually audited with the reason and correlation ID + +**32. Cache-first browse/search with bounded convergence** +- **Given** published Products/SKUs/Categories +- **When** a partner/customer browses/searches/filters +- **Then** the system MUST serve from cache-first read models scoped to the caller's tenant/brand/region, converging within its own budget (interim p99 < 2 s) +- **And** stale reads during the window MUST be safe (never expose unpublished/cross-scope content) and carry the `asOfCatalogVersion` staleness signal +- **And** the per-state visibility contract MUST hold: `published` browsable; `deprecated` browsable + flagged + excludable; `retired` excluded from default browse, retrievable via explicit history query + +### Bulk Operations + +**33. Bulk import/export** +- **Given** a CatalogAdmin importing/exporting in bulk +- **When** the batch is processed +- **Then** the system MUST apply per-row idempotency, report per-row success/failure (no hidden partial failure), and never leave a partially-inconsistent published state +- **And** dependent rows MUST apply two-phase or dependency-ordered, never committing an orphan; idempotency operates at batch + per-row levels; a coalesced `CatalogBulkOperationCompleted` is emitted (no event storm) +- **And** bulk import lands entities in `draft`; publication remains gated, approved against an aggregated change report; export MUST be deterministic for a given `catalogVersionId` + +### Cloning + +**34. Clone a product/SKU** +- **Given** a source Product/SKU (draft, published, or retired — the sanctioned revival path) +- **When** a ProductManager clones it +- **Then** the system MUST create a new `draft` with new `productId`/`skuId` and a new `skuCode`/optional `productCode`, copying structure/attributes/scoping/category/`PlanTier`/metering-unit, resetting lifecycle and version counters, and never copying pricing/plan content +- **And** the cloned metering unit, `PlanTier`, and category assignment MUST be re-validated against live registries; the clone MUST fail or force re-selection if any was de-listed/deprecated/retired; a `clonedFrom` reference is recorded and the source unaffected + +### Data Retention & Erasure + +**35. Retention & right-to-erasure** +- **Given** retired entities, historical versions, and audit records +- **When** a retention or erasure (GDPR/CCPA) request applies +- **Then** the system MUST retain financial/version/audit records for the configured duration and satisfy erasure of actor PII by pseudonymizing it across audit, entity version fields, and the actor identity-reference map — not deleting immutable records +- **And** attribute/description free-text MUST NOT contain personal data — enforced by a validation block at write (hard prohibition, no carve-out, fail-closed on uncertainty, curated allow-list); Legal sign-off recorded in the approval artifact +- **And** erasure MUST NOT break `CatalogVersion` reproducibility or audit completeness + +### Cross-PRD Consistency + +**36. Registry ↔ plan-price seam** +- **Given** the shared contract on `skuId`, `bundle` type, metering-unit declaration, `PlanTier`, and `CatalogVersion` +- **When** registry or plan-price schemas change +- **Then** there MUST be a shared schema-version pin and a CI contract test that fails on divergence; a runtime divergence MUST fail closed (reject the dependent plan publish) +- **And** the same suite MUST assert consumer-side obligations: reject adoption of `compositionPending`/`deprecated` SKUs; reject a usage binding with no declared unit (and reject/warn on a `deprecated` unit) — where usage-completeness is enforced; consume `mustMigrateBy` (post-v1); resolve grandfathered refs against the frozen snapshot; re-validate on `SkuImmutableFieldCorrected`; declare intent before `freezeComplete` +- **And** each consumer-side assertion is authorable only once the referenced counterpart AC exists; (d) `mustMigrateBy` is deferred with the post-v1 EOL capability + +**37. Monetization-model traceability** +- **Given** the deliberate decision that the registry carries no monetization-model marker (only `usage` leaves a footprint) +- **When** a reader asks which models are supported and where +- **Then** the PRD MUST expose a traceability map (§17.2): flat/per-seat/tiered/volume/hybrid/commitment → plan-price + Tariffs; usage → metering-unit declaration here + binding/rating downstream +- **And** absence of a model marker on a SKU MUST be treated as intentional, not a missing field + +### Error & Negative Paths + +**38. Expected failure behavior** +- **Given** an invalid or conflicting authoring request +- **When** the system processes it +- **Then** it MUST fail closed with an audited reason and MUST NOT partially apply, for each of the enumerated cases (see the `expected-failure-behavior` FR): stale-revision write, duplicate idempotency key with a different body, taxonomy cycle, unrecognized unit without elevation, publish of an incomplete entity, immutable-field change without a valid correction path, reissue/collision of a reserved `skuCode`, EOL without an acknowledged migration consumer (post-v1), SKU under a non-`published` parent, SKU scope outside its parent, authoring/cloning against a de-listed/deprecated unit, a bulk row whose in-batch dependency failed, adopting a `compositionPending` bundle, an indeterminate region overlap, and a retention process that would orphan a live grandfathered reference + +### Operational Resilience & Concurrency + +**39. Event delivery resilience** +- **Given** a registry event fanned out to ≥ 1 consumer +- **When** a delivery fails, a consumer never acks, or an event is poison +- **Then** the shared event system MUST provide bounded-backoff retry, per-consumer delivery state, and an audited dead-letter path with alerting (transport owned there) +- **And** the registry's own obligations are: not reporting emission success until durably accepted, surfacing per-consumer delivery/DLQ state as a projection, and never mutating registry state on delivery failure; during a bus outage mutations MAY commit with events to a durable outbox, with the propagation clock starting at durable bus acceptance + +**40. CatalogVersion publish concurrency** +- **Given** two staged sets of changes targeting publication within one tenant +- **When** publishes are submitted concurrently, or a publish races a `deprecate`/`retire` on an entity it enumerates +- **Then** publishes MUST serialize per tenant, `catalogVersionId` MUST be allocated monotonically without gaps/collisions, and a staged entity mutated/retired between stage and publish MUST cause that publish to re-validate fail-closed (rejected, naming the changed entity) + +**41. Fail-safe duration tripwire** +- **Given** the registry operating in `SkuReferenceCount`-unavailable fail-safe mode +- **When** break-glass immutable-field corrections exceed the configured rate (interim > 5 in 30 days) +- **Then** the system MUST raise an escalation alert and reclassify `SkuReferenceCount` delivery as a release blocker, so degraded operation is escalated, not normalized + +**42. `skuCode` reservation concurrency** +- **Given** two concurrent create/reserve requests for the same `skuCode` within one tenant +- **When** both are processed +- **Then** the system MUST atomically reserve at create, admit exactly one, and reject the other fail-closed with an audited reason; a `draft` reservation MUST block a second draft until released/discarded +- **And** a `skuCode` changed while still `draft` MUST release the previous code; discarding a never-published draft MUST also release its `skuCode`/`productCode` reservation + +**43. Reference-producer registration** +- **Given** the set of `SkuReferenceCount` producers (Subscriptions, Contracts, plan-price) +- **When** the registry evaluates `referenced` +- **Then** only registered producers' signals or silence MUST factor in; an unregistered producer's absence MUST NOT pin SKUs conservatively-referenced; membership MUST be a governed, audited change snapshotted symmetrically with the freeze-participant set +- **And** onboarding a new producer MUST NOT retroactively flip historical mutability/retirement decisions + +**44. Grandfathered-snapshot retention coupling** +- **Given** a `catalogVersionId` referenced by ≥ 1 live grandfathered reference +- **When** retention/erasure would expire, tier, or GC that snapshot +- **Then** the snapshot MUST remain byte-identically resolvable for as long as a live reference exists, regardless of the statutory-max clock; retention expiry MUST be gated on no live references to that `catalogVersionId` +- **And** version-liveness MUST be sourced from per-version freeze-registration records (or a `(catalogVersionId, producer)` contract), never the SKU-level count alone; a process that would orphan a live reference MUST fail closed with an alert + +**45. Pre-publish lint report** +- **Given** the `validate(lint)` operation before `CatalogVersion` publish +- **When** an admin runs it (or publish triggers it) +- **Then** the lint MUST return a structured, per-entity report of every override-requiring/attention condition (uncomposed bundles, missing default-locale attribute values, declarations against a `deprecated` unit) so the two-person override is informed and the audit records what was overridden + +### Non-Functional Requirements (Show-Stoppers) + +**1. Read latency** +- **Given** a warm read model holding 10K SKUs/tenant with ≥ 100 concurrent readers +- **When** browse/search reads execute within a tenant partition +- **Then** p95 latency MUST be < 100 ms over a 5-minute window, sustained via cache-first read models and partitioning + +**2. Read throughput** +- **Given** the cache-first read model under load +- **When** browse/search traffic peaks +- **Then** the system MUST sustain ≥ 2,000 read QPS per tenant partition at the AC-1 latency target + +**3. Publication propagation** +- **Given** an approved publish +- **When** the publish completes +- **Then** downstream event availability (incl. fan-out) MUST occur within < 3 s — distinct from read-model convergence (< 2 s) and the end-to-end posting-safe budget (< 5 s) + +**4. End-to-end posting-safe budget** +- **Given** a write commit that must become safe for Contracts/Billing to post against +- **When** measured from commit to "posting-safe" (read converged AND all `freezeComplete` acks) +- **Then** the composite MUST be p99 < 5 s; if the freeze times out the version MUST remain non-posting-safe (fail closed) + +**5. Snapshot archival & cold-resolution SLA** +- **Given** accumulating immutable snapshots under statutory-bounded retention at ≥ 10K SKUs/tenant +- **When** an archived ("cold") `catalogVersionId` is re-resolved +- **Then** re-resolution MUST remain byte-identical and meet a looser-than-hot target (interim p95 < 2 s) +- **And** snapshots + version history are financial records with a durability class (≥ 11 nines / replicated), periodic restore verification, and a cross-region/DR posture with RPO/RTO + +**6. Scale & extensibility limits** +- **Given** a large tenant +- **When** the catalog grows +- **Then** the system MUST support ≥ 10K SKUs/tenant without breaching read latency, within configured extensibility limits, and MUST bound tenant count, cardinality, and `CatalogVersion` growth + +**7. Graceful degradation & staleness exposure** +- **Given** read load above the throughput ceiling or read-model lag above budget +- **When** the system serves browse/search +- **Then** it MUST shed/queue excess load without ever serving cross-scope or unpublished content, and MUST expose staleness via the same machine-readable `asOfCatalogVersion` signal (no silently-stale response) + +**8. Determinism & integrity** +- **Given** the registry invariants +- **When** authoring/publish runs +- **Then** version immutability, taxonomy acyclicity, SKU identity uniqueness, and metering-unit validity MUST be enforced fail-closed, and posted-period snapshots MUST remain immutable + +**9. Backward-compatible schema evolution** +- **Given** a consumer pinned to schema `vN` +- **When** the registry publishes a `vN+1` payload +- **Then** the consumer MUST deserialize it (new fields optional with defaults); a CI contract test MUST assert backward compatibility on every schema change + +**10. Availability & audit completeness** +- **Given** the catalog service +- **When** measured over the SLO window +- **Then** the cache-first read path MUST meet 99.9% availability and the write/publish path 99.5%; write paths MUST be fully audited even during partial failures + +## 13. Dependencies + +| Dependency | Description | Criticality | +|------------|-------------|-------------| +| Tenant identity & hierarchy (OSS/AMS + IdP) | `tenantId`, brand/region claims, OrgTier projection targets, role claims (registry MUST NOT mutate tenant topology) | `p1` | +| Plan & Price Modeling | Consumes published SKU identity/type, metering-unit declaration, `PlanTier`, `CatalogVersion`; produces `SkuReferenceCount`, `freezeComplete` ack, bundle composition-completed signal | `p1` | +| Tariffs / Pricing Logic | Consumes published SKU refs + `CatalogVersion` (price resolution there) | `p1` | +| Rating & Charging | Consumes metering-unit declaration + published SKU refs | `p1` | +| OSS metering | Emits usage values (external); consumes the metering-unit declaration | `p1` | +| Subscriptions (lifecycle & entitlements) | Produces `SkuReferenceCount`; consumes SKU refs + `PlanTier` + `replacedBy` + `mustMigrateBy` (post-v1); owns live-subscription migration | `p1` | +| Contracts & Agreements | Produces `SkuReferenceCount` (incl. draft/quote refs per contract) + `freezeComplete` ack; consumes `CatalogVersion` snapshots for quotes | `p1` | +| Billing & Invoicing | Produces `freezeComplete` ack; consumes SKU refs + `CatalogVersion` (descriptors authored in plan-price, frozen into `CatalogVersion`) | `p1` | +| Marketplace & Vendor Portal | References published SKUs; vendor ops remain in the Marketplace PRD (§4.8) | `p2` | +| Presentation / Portals | Consumes catalog read models for browse/search cache warming | `p2` | +| Events & Audit (Common Core) | Shared event system: durable acceptance, per-consumer delivery/DLQ state, retry; transport owned there | `p1` | +| BSS Architecture Manifest | §4.1 (registry), §4.4 (posting immutability), §4.2/§4.3/§4.6/§4.8 (consumers), §2.1.3, §7.2 | `p1` | + +> **Registry is upstream of all commercial modeling.** A SKU MUST be published before a Plan/Price can reference it. The registry MUST NOT require any downstream consumer to re-interpret mutable catalog state for **posted** periods; the `CatalogVersion` snapshot contract is authoritative (manifest §4.4). + +## 14. Assumptions + +- The `SkuReferenceCount` signal will be delivered by Subscriptions, Contracts, and plan-price with a committed owner + date (pre-approval gate); until then AC #2/#4/#18 run fail-safe, bounded by the break-glass path and the fail-safe tripwire. +- Interim configurable-policy defaults (§17.1) are enforceable at launch; each final value is owned by another function and changes are governed/audited. +- Numeric NFR targets are binding **design targets** until the NFR workshop (within 2 weeks of approval; DRI = BSS Program Lead). +- The shared event system (Common Core) provides ordering/at-least-once/DLQ transport; the registry states only its own emission/projection obligations. +- Recognized-set owners (metering units → Product + Rating; tax/GL codes → Finance; `PlanTier` → Product) seed and govern their sets; the registry validates against them. +- `PRD-plan-price-modeling` and this PRD stay consistent on the shared fields via a CI seam test; the combined predecessor PRD is refactored to Marketplace-only after approval. + +## 15. Open Questions + +> **Pre-approval gates (must be closed at approval).** Two items MUST carry a committed owner/decision **before** sign-off because they govern launch behavior: (1) **`SkuReferenceCount` owner + delivery date** — until committed, AC #2/#18 run fail-safe; (2) **Region-set algebra** for AC #5 overlap semantics. Event delivery resilience and `CatalogVersion` publish concurrency are build conditions captured as FRs/ACs. + +| **Question** | **Answer** | **Date Answered** | +|--------------|------------|-------------------| +| **GATE — `SkuReferenceCount` signal owner + delivery date** | Contract + counting defined; owner sign-off + committed delivery date MUST be recorded at approval. Until it ships, AC #2/#18 run fail-safe (bounded by break-glass + tripwire). *(Owner: Architecture + Subscriptions/Contracts.)* | TBD (gate) | +| **GATE — Region-set algebra** (containment/wildcard/disjointness for AC #5) | Interim conservative rule live (non-disjoint ⇒ overlapping, fail-closed); exact algebra pinned in Design before implementation. *(Owner: Design + Product.)* | TBD (gate) | +| Contracts draft/quote references: count toward `referenced`? + re-resolve-at-freeze behavior | Producer contract must declare both, identical across AC #2/#4/#18; recorded at sign-off. *(Owner: Contracts + Architecture.)* | TBD | +| EOL `mustMigrateBy`: pull into v1 or confirm the post-v1 deferral? | Registry side deferred; needs a date to pull in or confirmation. Gates EOL child cascade. *(Owner: Subscriptions.)* | TBD | +| Finance materiality threshold production value + date | Dimension + interim default resolved; needs a committed production value + date. *(Owner: Finance.)* | TBD | +| Legal content-PII prohibition sign-off | Normative position = hard prohibition, no carve-out; Legal to confirm sufficiency + detector posture, recorded at approval. *(Owner: Legal.)* | TBD | +| Data retention durations per record class + PII pseudonymization age | Interim set (financial/version/audit → statutory max). Final durations per jurisdiction. *(Owner: Legal/Finance.)* | TBD | +| Recognized metering-unit set owner + add-unit / de-list workflow | Interim seed ships; de-list governed. Owner + workflow. *(Owner: Product + Rating.)* | TBD | +| Recognized tax-category / GL-code sets: owner + add / de-list workflow | Interim configured sets ship; validated + required at publish for product/service types. Owner + workflow. *(Owner: Finance.)* | TBD | +| PlanTier taxonomy governance ownership confirmation | Taxonomy + SKU value owned here; plan enforces presence at plan publish. Confirm with plan-price. *(Owner: Product + plan-price.)* | TBD | +| Catalog taxonomy/category scheme (IaaS/PaaS/SaaS/…) | To be defined. *(Owner: Product.)* | TBD | +| Media/binary asset ownership (does the registry hold asset URIs?) | Out of scope as binaries; registry may carry URIs. Confirm owner — Presentation / Marketplace / DAM. *(Owner: Product + Presentation/Marketplace.)* | TBD | +| Catalog-relationship block beyond `bundle`/parent-child/`replacedBy` | Registry owns `replacedBy`/supersedes; other types out of scope v1. Confirm need vs plan-price/Subscriptions. *(Owner: Product + Architecture.)* | TBD | +| Per-seat monetization: truly zero registry footprint? | Confirm no seat-as-unit artifact; metered seats would collide with the single-unit rule. *(Owner: Product.)* | TBD | +| Monetization-model coverage: plan-price pointer accepted in lieu of registry fields? | Traceability map provided (§17.2). *(Owner: Finance/Program.)* | TBD | +| Event-bus transport contract owner + home (ordering, at-least-once, DLQ retention) | Registry states its requirements; the contract is owned by Common Core / Events & Audit. *(Owner: Architecture/Eng — Common Core.)* | TBD | +| Event-log retention/TTL value | MUST be ≥ the bootstrap gap implied by AC #29. *(Owner: Eng/Common Core.)* | TBD | +| `CatalogVersion` archival economics: storage growth + publishes/day/tenant target | Tiering allowed while byte-identical; needs the publish-frequency target before storage design. *(Owner: Eng/Finance.)* | TBD | +| Snapshot durability / DR targets (RPO/RTO + restore-verification cadence) | Snapshots are financial records: interim ≥ 11 nines + periodic checksum restore verification; RPO/RTO ratified at the NFR workshop. *(Owner: Eng/Program.)* | TBD | +| Snapshot-GC version-liveness source | Per-SKU count has no version dimension; source from per-version freeze-registration or a `(catalogVersionId, producer)` contract. Confirm. *(Owner: Architecture + freeze participants.)* | TBD | +| Cross-PRD seam contract-suite owner + repo/pipeline | Proposed: BSS Catalog/Architecture in `api-contracts` CI. Final owner sign-off. *(Owner: needs assignment.)* | TBD | +| Disposition of `PRD-product-catalog-marketplace-202601120119` (refactor to Marketplace-only) | Keep now; refactor to §4.8-only after this + plan-price + Tariffs approved. *(Owner: Product/Program.)* | TBD | +| NFR workshop: named DRI, held within 2 weeks of approval, SLO table ratified | Targets are binding design targets until then. *(Owner: BSS Program Lead.)* | TBD | + +## 16. Risks + +| Risk | Impact | Mitigation | +|------|--------|------------| +| `SkuReferenceCount` signal slips | AC #2/#4/#18 stuck in fail-safe (no immutable-field correction on referenced SKUs) | Pre-approval gate on owner + date; break-glass bounds per-operation debt; the fail-safe tripwire (> 5/30d) bounds it in time | +| Region-set algebra undefined | False name collisions/allows on create (AC #5) | Interim conservative fail-closed rule; pin the algebra in Design before implementation (gate) | +| Snapshot lost/corrupted | Breaks byte-identical reproducibility for every contract frozen to it — a compliance event | Snapshots as financial records: ≥ 11 nines durability, periodic checksum restore verification, retention gated on live references (AC #44/#55) | +| Registry ↔ plan-price schema drift | Silent divergence breaks downstream binding/posting | Shared schema pin + CI seam contract test that fails closed (AC #36/#48) | +| Stuck cross-module freeze | Posting blocked or unsafe | `freezeComplete` fail-closed + idempotent re-trigger + governed force-completion (AC #21/#22) | +| Full-snapshot-per-publish cost growth | `CatalogVersion` storage/economics at 10K+ SKUs × frequent publishes | Batching-as-policy; publishes/day/tenant target + archival economics at the NFR workshop (AC #44/#45) | +| Combined predecessor PRD left authoritative for registry | Duplicate/divergent catalog requirements | Refactor `PRD-product-catalog-marketplace` to Marketplace-only after approval (§15) | + +## 17. Reference Materials + +| **Material** | **Link** | **Comments** | +|--------------|----------|--------------| +| BSS Architecture Manifest | `docs/bss/manifest/vz-arch-manifest-bss-only.md` | §4.1 (registry) incl. the Decomposition (BSS realization) note = normative home of the §4.1 split; §4.4 posting immutability; §4.2/§4.3/§4.6/§4.8 consumers; §2.1.3; §7.2 events | +| Plan & Price Modeling | `docs/bss/prd/PRD-plan-price-modeling-202605281200/PRD-plan-price-modeling-202605281200.md` | Owns Plan/Price/PriceWindow/PriceList/Bundle composition/add-ons/billing descriptors/plan lifecycle; builds on this registry | +| Tariffs — Commercial Pricing Logic | `docs/bss/prd/PRD-tariffs-pricing-logic-202604011200/PRD-tariffs-pricing-logic-202604011200.md` | Price evaluation over the primitives plan-price authors | +| Rating Engine | `docs/bss/prd/PRD-rating-engine-202604031200/PRD-rating-engine-202604031200.md` | Consumer of metering-unit declaration and published SKU refs | +| Subscriptions — Lifecycle | `docs/bss/prd/PRD-subscriptions-lifecycle-202604021200/PRD-subscriptions-lifecycle-202604021200.md` | Consumes SKU refs + PlanTier + CatalogVersion; owns live-subscription migration | +| Product Catalog & Marketplace (predecessor) | `docs/bss/prd/PRD-product-catalog-marketplace-202601120119/PRD-product-catalog-marketplace-202601120119.md` | Combined §4.1+§4.8 predecessor; registry scope superseded here, Marketplace retained there | +| Project glossary | `docs/project-glossary.md` | Canonical terms | +| Trace chain | `AGENTS.md` (repository root) | Manifest → PRD → ADR → Design → Stories | + +### 17.1 Configurable-Policy Interim Defaults + +| Policy | Interim default (fail-safe) | Final owner | +|--------|-----------------------------|-------------| +| Materiality threshold | Two-person rule on any material-field change (always); affected-entity-count trigger ≥ 10; single-entity non-material change passes with one approver | Finance | +| Recognized tax-category & GL-code sets | Configured enum + GL chart; unknown codes rejected at authoring; new codes require elevated approval; de-list blocked while referenced; required at publish for product/service types | Finance | +| PlanTier taxonomy seed | Seeded with a neutral value (`standard`/`none`) + operator-defined tiers; tier identity is a stable code, rename = display-only | Product | +| Idempotency-key retention | ≥ 24h and ≥ the maximum freeze timeout | Eng/Design | +| `SkuReferenceCount` freshness threshold | 15 min; staler → conservative + alert | Architecture | +| Fail-safe break-glass tripwire | > 5 break-glass corrections / 30 days → escalate + signal delivery becomes release blocker | Architecture | +| Retirement / EOL lead-time | ≥ 30 days between event and effective hide | Subscriptions + Product | +| Recognized metering-unit set | Seeded with platform base units (`vCPU-hours`, `GB-storage`, `GB-egress`, `request-count`); new units require elevated approval; de-list blocked while referenced | Product + Rating | +| Retention — financial/version/audit | Retain to statutory maximum (not "indefinite") | Legal/Finance | +| Retention — operator PII | Pseudonymize at erasure request or a defined max age, whichever first | Legal/Finance | +| Read-model convergence | p99 < 2 s after write commit | Eng | +| Event propagation / fan-out | p99 < 3 s after publish | Eng | +| End-to-end posting-safe budget | p99 < 5 s (read converged and `freezeComplete`) | Eng | +| Cold `catalogVersionId` resolution | p95 < 2 s (looser than hot reads) | Eng | +| Snapshot durability & DR / RPO-RTO | ≥ 11 nines / replicated storage; periodic restore verification; RPO/RTO at the NFR workshop | Eng/Program | +| Numeric NFRs | Binding design targets until the NFR workshop (approval + 2 weeks); DRI = BSS Program Lead | Program/Eng | + +### 17.2 Monetization-Model Traceability + +| Monetization model | Where authored / evaluated | +|--------------------|----------------------------| +| flat, per-seat, tiered, volume, hybrid, commitment | `PRD-plan-price-modeling-202605281200` (authoring) + `PRD-tariffs-pricing-logic-202604011200` (evaluation) | +| usage | Metering-unit **declaration** here (registry) + plan-level meter binding (plan-price) + rating (Rating) | + +Absence of a monetization-model marker on a SKU is **intentional**, not a missing field. + +--- + +*Child artifacts: ADR(s) for versioning/snapshot strategy and lifecycle/deprecation modeling; DESIGN (`DESIGN-product-sku-management-*`) for entity schemas, APIs, events, and read-model design; STORY documents per scope item. The §4.1 registry↔commercial decomposition is recorded in the manifest §4.1 Decomposition (BSS realization) note, not a separate ADR.* +