Proposed (STA-917, batch stackra-22). It closes the Commerce module-catalogue gaps for
rules
(two rows) and market-packs (one row), verified against wave 12. The loader of record stays OS
settings market_packs (owner decision D2); this ADR changes the libraries only.Context
Commerce found four gaps.RuleSetService.open and seedPack need a ScopeRef, so a module cannot decide with a
pack’s default rule set (returns policy, COD) before a scope has seeded its own. Rules.create freezes the fact
sources, so an edition loaded after the consumer cannot contribute one (shipping’s delivery promise, the OMS busy
rules). FloorBound is min or max, so a list a scope may add to but never drop from (refused goods) cannot be a
legal floor. resolvePack refuses a market without a base pack, and the OS base packs do not exist yet (STA-848), so
Commerce resolves its sections with its own pickCommercePack and market-code nesting. Wave 12 already has seedPack
on a scope; the other three gaps were open as filed.
Decision
- A pack’s default rule set opens without a scope.
packRuleSetOf(pack, key, rules)(the.entry, isomorphic) parses aRuleSetPack$document, finds templatekey, refuses one of another consumer, and checks its condition and outcomes against the consumer’sRules, returning aRuleDocumentthatevaluateanddecidetake.RuleSetService.openDefault(pack, key, rules)returns the same as aRuleSetRevisionnumbered0, with the pack version asseedVersionand never published. Nothing is stored and no platform scope row stands in for it;seedPack(scope, …)remains the way a default becomes a scope’s own record.decidestill applies the floors. - Fact sources are registered after
create.Rules.register(...sources)is the registration hook: each source is checked ascreatechecks one (camelCase keys, declared types, enum values), a source key already registered is refused, and a call is all or nothing.Rulesstays frozen except for its source list and field map; nothing is ever replaced or removed. Until a source is registered, a condition naming its fields isfield_unknownat save time, as before. An edition registers at module init, before it saves or evaluates a rule naming its fields, andConditionFieldService.syncruns after it. FloorBound.List, the list floor. Alistfloor’s value is a list (MarketPack$refuses another value). A value keeps every item of the floor and may add more.overlayFloorIssuesreports an overlay list that drops an item asfloor_loosened. In rules, alistoutcome parameter may name a floor;crossedFloormoves a list missing items to the list plus the missing items (past_floor), and throws for a list floor on a non-list parameter, amin/maxfloor on a list, or a list floor that is not strings (a pack bug).- Sections without a base pack.
ResolveOptions.requireBase(defaulttrue): whenfalse, a market with no fitting base pack answers with its requested sections alone, on its pin or else the highest version, and its parent is its market code without the last segment (eu-deundereu). The asked-for market still needs a base or a section, orPackNotFoundError; a base pack, when present, is used as always and itsextendsis followed. The default staystrue, so a missing platform base pack is still refused where it should exist. pickPackis public on the.entry, so a product picks one section version under the same pin and status rulesresolvePackuses.compareVersionsstays internal.
Consequences
@stackra/settings’crossesFloorcomparesminandmaxonly; alistfloor reaching it is not refused there until the settings owner adds the list case (a follow-up, outside this batch). Every bound comparison must handle all three bounds.Rules.sourcesis now a getter returning a frozen copy; reading it is unchanged.- Commerce can drop
pickCommercePackandcommerceChain(setup) forpickPackandrequireBase: false, read the returns and COD defaults withopenDefault, and register edition fact sources withregister. - A list floor may grow a list past the 100 strings a list parameter takes; a pack author keeps floor lists short.
Alternatives
- A platform scope row for pack defaults. Rejected: it puts a fake scope under row-level security and a seed to maintain per pack version, for a value that is pure pack data.
- Rebuilding
Ruleswith the extra sources (Rules.extendreturning a new value). Rejected: every holder of the consumer’sRulesconstant would need the new value; the registry is one per consumer. - Market-code nesting always. Rejected: base packs declare
extends, and an overlay whose parent is not its code prefix must follow the data; nesting by code is only the fallback for a market with no base pack.
Supersedes and sources
Supersedes nothing; amends ADR 0370 (decisions 3, 4 and 6) and ADR 0331 (decision 2). Sources: Commerceapps/docs/src/docs/build/module-catalogue.mdx (Stackra gaps: rules, market-packs), services/catalogue/src/modules/setup/SPEC.md,
services/fulfilment/src/modules/shipping/SPEC.md; STA-848; owner decision D2.