@cfxlabsinc/b2b-services
    Preparing search index...

    Admin-tier projection of a product row. id is the canonical ProductName (externalId in the DB). The set of products is authored at compile time via CONCRETE_PRODUCT_NAMES and seeded — runtime creation is intentionally out of scope (see rules/cascade-product.md). Only name, description, and apiStage are runtime-editable.

    type Product = {
        apiStage: string | null;
        category: SelectProduct["productCategory"];
        createdAt: Temporal.Instant;
        description: string | null;
        id: ProductName;
        name: string;
        updatedAt: Temporal.Instant;
    }
    Index
    apiStage: string | null

    Stable AWS API Gateway stage this product is served by (operator-linked; null until linked). The stage name resolves to a usage plan at runtime; no usage-plan id is stored here.

    category: SelectProduct["productCategory"]

    Structural aggregate grouping — DEPOSIT, WITHDRAWAL, or null for products in no aggregate family (organization, identity, card, swap, redemption transfer, virtual account).

    Read-only, deliberately. Unlike name / description / apiStage, this is not an operator-editable label: it selects which wildcard product's aggregate limit rules a quote is enforced against, so editing it at runtime would silently re-point live limit enforcement. It is written by migration and read from the column — update() does not accept it.

    createdAt: Temporal.Instant
    description: string | null
    id: ProductName
    name: string
    updatedAt: Temporal.Instant