Proposed. It places the market-pack loader that
0363
(configuration over code) needs, and settles that ADR’s open question 1 (the pack home). It reads
with 0213 (settings
cascade), 0331 (rules),
0356
(provider per Market),
0359
(sandbox and live),
0366
(calendar data from packs) and 0127
(package categories).
Context
The owner ruled on 2026-10-25 that countries are market packs: versioned data holding tax, e-invoicing, till fiscal rules, legal floors, providers, sign-in methods, calendar, formats and price tier. OS pricing, invoicing, identity, verification, notifications, geography and tenancy, and every product, need to read that data the same way: verified, overlaid (eu then eu-de, ae then ae-difc), dated (a VAT change on 1 July), pinned per tenant, and with the
floors no setting may cross. Juristra also needs reviewer sign-off per rule and a launch gate per pack and product.
Nothing is on disk, and 0363 left the home open. Alternatives weighed: a part of @stackra/settings (rejected: settings
is per-scope state, while packs are signed per-market data that the rules engine and offline tills read too) and a
pack format inside @stackra/assets (rejected: assets resolves files; it does not know versions, overlays or dates).
Decision
- Package.
@stackra/market-packsinpackages/foundation/market-packs,tier:lib, entry.only, depending on@stackra/contracts,@stackra/errorsandzod. Pure mechanism, isomorphic and offline; it ships no pack content. Base packs are owned by the platform (OS), product sections by each product. - Signed and content-hashed. A pack ships as
{ pack, hash, signature }: the SHA-256 of the canonical JSON of the pack (keys sorted at every depth) and an Ed25519 signature of that hash by a named key.signPackis the build step;verifyPackchecks shape, hash (over the pack exactly as shipped) and signature against trusted public keys from deployment configuration, and refuses withPackRejectedError(shape_invalid,hash_mismatch,key_unknown,signature_invalid). A service whose packs do not verify does not start. Crypto is Web Crypto. - Overlays. A pack names the market it overlays in
extends.resolvePackwalks the chain to the root and returns the layers root first. A layer’s value wins over its parent’s only while its own entry is in force. A loop is refused (overlay_cycle); a market with no fitting version isPackNotFoundError. - Versions, status and pins. A pack version is
major.minor.patchwith a status:draft,beta,live,retired. Which markets are open is this status, never a list in code. A tenant pins per market to a major line ("2") or, for a rollback, one exact version ("2.1.0"); an unpinned market takes its highest accepted version. Onlyliveis accepted unless the caller says otherwise (a sandbox may acceptbeta, per 0359). A major release changes shape and is promoted like any structural change. - Dated values. An entry is a key, a JSON value and the days it is in force (
from, optionaluntil, both inclusive); the latest start in force wins.valueOf(resolved, key, on)answers “the value of X for market M on date D” with the market and version that set it;valuesOngives all of them. A consumer checks a value against its own schema (for exampleBusinessCalendar$of 0366). - Legal floors as data. A floor entry carries
bound(minormax) andlaw.floorsOnlists the floors in force with their pack, so the settings resolver (0213 cascade plus a floor) and the rules engine (0331, as a final guard) refuse a value past the floor and say which pack and law set it.factsOngives the values in force as a plain record, a fact source for rules. This package carries floors; it does not apply them. - Product sections. A pack with
sectionset is one product’s part of a market (courts, safeguarding, hallmarking). Its keys start with<section>., so it never overwrites a base value; it has noextendsof its own and follows its base pack’s major line. A tenant resolves the sections its products need. - Review and launch gate. A pack version records reviewer sign-offs per entry key and a launch checklist whose
items may name a product. Both are signed with the version, so completing an item is a new patch.
unreviewedlists keys without a sign-off;launchGatesays whether a product may sell under a resolved market: every layerlive, every checklist item for that product done, every key reviewed. - Not here. Applying a floor to a setting stays with
@stackra/settings; rule evaluation with@stackra/rules; provider choice per Connection with@stackra/integrations(0356); where pack bytes are stored and fetched with the caller; which pack wins for a tenant active in two markets stays 0363 open question 3.
Consequences
- A new market is a new pack (and drivers where needed); a dated legal change is a new minor version published ahead.
- Every refusal over a floor can name the pack, version and law.
- A tampered or unsigned pack stops a service at start-up rather than changing tax or legal behaviour silently.
- The calendar (0366), settings and rules can drop their “until the pack loader exists” caveats once they read through this package.
- Checklist and review changes need a re-signed version; counsel signs pack versions, not ADRs.
Enforcement
planned — the package’s tests (packages/foundation/market-packs/src/**/__tests__/) fix the behaviour with made-up
markets only; the lint rule of
0363 will flag market
codes and values in source. Nothing checks it in CI yet.
Supersedes and sources
Supersedes: none. Settles 0363 open question 1. Sources:notes/handover/stackra-package-needs.md (market-pack loader
row); notes/handover/config-model.md section 1; notes/handover/answers-as-config.md (CAP-market-pack,
CAP-legal-floor, CAP-pack-review, CAP-pack-launch-gate); notes/handover/adr-hardcoding-findings.md (draft 0363);
owner principle of 2026-10-25.