@cfxlabsinc/b2b-services
    Preparing search index...
    Index
    db: B2bDatabase
    • Why each of this product's routes did or did not survive candidacy.

      search filters rejected routes out in SQL, so when it returns nothing there is no row left to describe and the caller can only say "no eligible route". This answers the question search structurally cannot: it selects EVERY live route for the product without applying either matcher, then evaluates both in TypeScript via ruleMatchExplain — which is the same evaluator ruleMatch projects its boolean from, and is property-tested against the plpgsql b2b.rule_match it mirrors.

      The criteria come back FROM SQL rather than being rebuilt here. Entity fields and the per-route clock fields (dayOfWeek / timeOfDay in each route's data.cutoffTimezone) are derived per route by the same expressions search uses; re-deriving them in TypeScript would make time-windowed rules explain wrongly. SQL stays the source of truth for the payload; TypeScript only interprets it.

      Diagnostic only — nothing on a money path calls this, and it deliberately omits the activation, fee and limit CTEs. That also keeps the statement far below the 16,384-byte RDS Proxy pinning line (see search's note at the vendor-matcher call site); it is a separate, smaller statement rather than a widening of the hot one.

      Parameters

      • __namedParameters: {
            customerId: string;
            entityId: string;
            entityType: NonNullable<"IDENTITY" | "ORGANIZATION" | undefined>;
            now?: Instant;
            productName:
                | "organization.v1"
                | "identity.v1"
                | "card.physical_card.v1"
                | "card.virtual_card.v1"
                | "deposit.us_cash.v1"
                | "deposit.rtp.v1"
                | "deposit.us_bank_ach.v1"
                | "deposit.ach_credit.v1"
                | "deposit.us_wire.v1"
                | "deposit.swift_wire.v1"
                | "transfer.redemption.v1"
                | "swap.v1"
                | "withdraw.blockchain.v1"
                | "withdraw.ke_bank.v1"
                | "withdraw.ke_momo.v1"
                | "withdraw.mx_bank_spei.v1"
                | "withdraw.swift_wire.v1"
                | "withdraw.tg_momo.v1"
                | "withdraw.us_bank_ach.v1"
                | "withdraw.us_instant.v1"
                | "withdraw.us_wire.v1"
                | "withdraw.ach_pull.v1"
                | "withdraw.us_wire_drawdown.v1"
                | "account.virtual-account.v1"
                | "deposit.*"
                | "withdraw.*";
        } & CallerSuppliedCriteria

      Returns Promise<RouteExplanation[]>

    • For each requested product, return the rail-level set of supported source currencies and country codes.

      sourceCurrencies are extracted from product_route_version.matcher (the route's own gate). countryCodes are extracted from each route's ADMIN-tier APPROVE activation rule matchers — route matchers don't carry country constraints in practice. Both use b2b.matcher_extract_literals (recursive plpgsql walker that pulls is / is_one_of literals; none / is_not / ranges are unbounded) and the b2b.literal_union aggregate to fold per-route extractions into one capability summary per product.

      A field is undefined when at least one contributing matcher placed no enumerable constraint on it (ALWAYS, the field didn't appear, or any unsupported operator). Callers surface that as "no rail-level restriction"; an empty array would mean "the rail supports nothing".

      Customer-tier rules are intentionally excluded — this is a product- level capability summary, not a per-customer authorization check.

      Parameters

      • productNames: readonly (
            | "organization.v1"
            | "identity.v1"
            | "card.physical_card.v1"
            | "card.virtual_card.v1"
            | "deposit.us_cash.v1"
            | "deposit.rtp.v1"
            | "deposit.us_bank_ach.v1"
            | "deposit.ach_credit.v1"
            | "deposit.us_wire.v1"
            | "deposit.swift_wire.v1"
            | "transfer.redemption.v1"
            | "swap.v1"
            | "withdraw.blockchain.v1"
            | "withdraw.ke_bank.v1"
            | "withdraw.ke_momo.v1"
            | "withdraw.mx_bank_spei.v1"
            | "withdraw.swift_wire.v1"
            | "withdraw.tg_momo.v1"
            | "withdraw.us_bank_ach.v1"
            | "withdraw.us_instant.v1"
            | "withdraw.us_wire.v1"
            | "withdraw.ach_pull.v1"
            | "withdraw.us_wire_drawdown.v1"
            | "account.virtual-account.v1"
            | "deposit.*"
            | "withdraw.*"
        )[]

      Returns Promise<
          Record<
              string,
              {
                  countryCodes: string[]
                  | undefined;
                  sourceCurrencies: string[] | undefined;
              },
          >,
      >

    • Parameters

      • __namedParameters: {
            customerId: string;
            entityId: string;
            entityType: NonNullable<"IDENTITY" | "ORGANIZATION" | undefined>;
            now?: Instant;
            orderBy?: "createdAt" | "priority";
            page?: number;
            pageSize?: number;
            productName:
                | "organization.v1"
                | "identity.v1"
                | "card.physical_card.v1"
                | "card.virtual_card.v1"
                | "deposit.us_cash.v1"
                | "deposit.rtp.v1"
                | "deposit.us_bank_ach.v1"
                | "deposit.ach_credit.v1"
                | "deposit.us_wire.v1"
                | "deposit.swift_wire.v1"
                | "transfer.redemption.v1"
                | "swap.v1"
                | "withdraw.blockchain.v1"
                | "withdraw.ke_bank.v1"
                | "withdraw.ke_momo.v1"
                | "withdraw.mx_bank_spei.v1"
                | "withdraw.swift_wire.v1"
                | "withdraw.tg_momo.v1"
                | "withdraw.us_bank_ach.v1"
                | "withdraw.us_instant.v1"
                | "withdraw.us_wire.v1"
                | "withdraw.ach_pull.v1"
                | "withdraw.us_wire_drawdown.v1"
                | "account.virtual-account.v1"
                | "deposit.*"
                | "withdraw.*";
        } & CallerSuppliedCriteria
        • customerId: string
        • entityId: string

          The entity this search resolves routes for, together with the entityType below. The four EntityDerivedCriteriaFields are derived from this row in SQL, per candidate route — callers pass CallerSuppliedCriteria and never those four fields.

          The entity must already be known to exist: the subquery yields no row for an unknown id, and jsonb || NULL is NULL, which no matcher accepts. ProductQuoteService.resolve() satisfies that by fetching the entity first for its error paths and the audit-row pin.

          Quoting a resource rather than an amount — creating a new organization, say — has no entity for the quote's target yet. Pass the customer's own entity id there: the four fields then describe the identity the resource is being created under, which is what those matchers gate on in that flow anyway. There is deliberately no second parameter for the resource case.

          RFC §4.5 Deploy 1.

        • entityType: NonNullable<"IDENTITY" | "ORGANIZATION" | undefined>

          Which kind of entity entityId names, and therefore which table the other three EntityDerivedCriteriaFields are read from. Required alongside entityId: nothing derives an entity's kind from its id string, so the two always travel together. Every caller already holds the fetched row that proves entityId exists, which is where this comes from (entity.type).

          Mislabelling an entity is not silently corrected — the subquery reads the named table only, finds no row, and the search returns no candidates. See entityCriteriaSql.

        • Optionalnow?: Instant

          Wall-clock instant the resolver evaluates against. Each candidate route's matcher sees dayOfWeek / minuteOfDay computed from this instant converted to that route's data.cutoffTimezone — so a "before 4:15 PM" cutoff fires at different UTC instants for routes in different timezones. Defaults to Temporal.Now.instant().

        • OptionalorderBy?: "createdAt" | "priority"
        • Optionalpage?: number
        • OptionalpageSize?: number
        • productName:
              | "organization.v1"
              | "identity.v1"
              | "card.physical_card.v1"
              | "card.virtual_card.v1"
              | "deposit.us_cash.v1"
              | "deposit.rtp.v1"
              | "deposit.us_bank_ach.v1"
              | "deposit.ach_credit.v1"
              | "deposit.us_wire.v1"
              | "deposit.swift_wire.v1"
              | "transfer.redemption.v1"
              | "swap.v1"
              | "withdraw.blockchain.v1"
              | "withdraw.ke_bank.v1"
              | "withdraw.ke_momo.v1"
              | "withdraw.mx_bank_spei.v1"
              | "withdraw.swift_wire.v1"
              | "withdraw.tg_momo.v1"
              | "withdraw.us_bank_ach.v1"
              | "withdraw.us_instant.v1"
              | "withdraw.us_wire.v1"
              | "withdraw.ach_pull.v1"
              | "withdraw.us_wire_drawdown.v1"
              | "account.virtual-account.v1"
              | "deposit.*"
              | "withdraw.*"

      Returns Promise<ResolvedRoute[]>