Why each live route did or did not survive, per request:
explainRoutes({ requests })[i] answers requests[i] alone, [] when
its product has no routes. Walks every live route and evaluates its
matcher in TypeScript via ruleMatchExplain — see
docs/proposals/EXPLAINABLE_ROUTE_ENGINE.md §4.4 for why. Criteria come
back FROM SQL rather than rebuilt here, so time-windowed rules still
explain correctly. Diagnostic only: a smaller, separate, constant-text
statement than search's — see test/ProductRouteService.statementSize.test.ts.
Each route also names the cross-route activation rules that remove it,
read by searchCrossRouteActivationRules alongside.
The customer's entity as the engine sees it: its kind and the criteria
routeCriteriaEntityView derives for it. null for an unknown or
deleted entity, which EntityService.get does not return either.
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. Both
read the live versions search reads, through the same views.
Single composite read-through for the rules engine. One CTE-shaped SQL traversal returns every route matching the lookup criteria, joined with the route's current version + vendor row, and the effective activation/ fee/limit rule-version per route.
The route's own matcher (on product_route_version) gates which routes
are candidates. Tier precedence (CUSTOMER > ADMIN-for-customer >
ADMIN-global) is resolved inside the rule CTEs via DISTINCT ON ... ORDER BY. b2b.rule_match is the only custom SQL surface.
search({ requests })[i] answers requests[i] alone, including [] for
a request with no eligible route. Two requests naming the same product
resolve independently, so their criteria can select different rules.
The requests travel as one jsonb array bind parameter, unnested WITH ORDINALITY to recover the request index, so the statement TEXT is
byte-identical for 1 request and for 25 — which is what keeps a wide
batch clear of the RDS Proxy pinning line. See
test/ProductRouteService.statementSize.test.ts.
It also derives the DOMAIN_DERIVED_CRITERIA_FIELDS; a caller value wins and a foreign or unknown id derives nothing. See docs/PRODUCT_SERVICES.md § Domain-derived criteria.
Cross-route rules, a vendor's own restriction among them, are not applied:
callers apply searchCrossRouteActivationRules' denials, as
ProductQuoteService.resolve() and the eligibility trace's deniedBy do.
No pagination: a caller wants every request's whole result set, and a page across a batch names no meaningful slice.
The entity this search resolves routes for, together with the
entityType below. The EntityDerivedCriteriaFields are
derived from this row in SQL — callers pass
CallerSuppliedCriteria and never those fields.
The entity must already be known to exist: the view 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.
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 view lookup is keyed on the kind too, finds no row, and the search returns no candidates. See EntityDerivedCriteriaField.
Optionalnow?: InstantWall-clock instant the resolver evaluates against. Each candidate
route's matcher sees dayOfWeek / timeOfDay 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().
The live routes a cross-route activation rule denies, per request: one entry per (route, matching rule). Callers intersect with their own candidates; see docs/PRODUCT_SERVICES.md § Cross-route activation rules.
OptionalawaitingVerificationAsActive?: booleanAWAITING_VERIFICATION_STATUSES read as APPROVED; onboarding sets it.
Optionalnow?: InstantEvery live product (narrowed by the filters) resolved through the route
engine for one subject: search's CTEs over requests sourced from the
products themselves, with criteria shared by all. Request-independent,
so no amount, instrument, account or usage; limits are not applied.
ENTITY_NOT_FOUND when entityId is not the customer's.
Two statements: the cross-route activation rules first, whose denials the
route statement then applies by product and vendor, as resolve() does.
OptionalaccessibleOnly?: booleanKeep only ACTIVE products.
Optionalcriteria?: CallerSuppliedCriteriaOptionalnow?: InstantOptionalpage?: numberOptionalpageSize?: number
The process default: built from each dependency's own
instance().