Skip to main content

Context

Three closed sets are genuinely cross-cutting: currencies, national ID types and notification channels. Everything else belongs to its own schema (DEC-B3). This is the first delivery in the entire suite for a mechanical reason. The is_system rows of a parametric catalog are the single source that generates the Zod and TypeScript unions in packages/core; nothing that references a currency_code or a channel_code can typecheck until they exist. DEC-A2 makes national_id_types the largest of the three and the one carrying real logic: every South American country, each with the formats, normalizers and check-digit algorithms that apply to persons and to companies.

Scope (normative)

  • core.countries: ISO 3166-1 alpha-2, the anchor for national ID types.
  • core.currencies: ISO 4217 with minor_unit — the exponent that makes minor-unit arithmetic correct.
  • core.national_id_types: per country and per contact kind, with format regex, normalizer id and check-digit algorithm id.
  • core.notification_channels: email, in_app, webhook, push, sms, whatsapp, live_activity — the row exists from day 1 even where the adapter arrives later (DEC-E1).
  • The seed-to-union generation pipeline: is_system rows produce the Zod schemas and TS types.
  • The catalog cache: process memory plus Redis, so hot paths never join.

Non-scope (normative)

  • Validating a national ID. This delivers the catalog; applying its regex and check digit to a value is FS-CORE-0002.
  • Sending on a channel. The row is a declaration that the channel exists.
  • FX rates. F1 is single-currency per tenant (DEC-B6); rates arrive with multi-currency in F2.

Behaviour (normative)

  1. Every catalog follows the parametric standard exactly: code TEXT primary key, label, is_system, is_active, sort_order, metadata JSONB. No exceptions, no extra shape.
  2. These three are not tenant-extensible. A tenant cannot invent a currency or a channel: both govern system logic (DEC-B4).
  3. is_system rows are seeded in migrations and are immutable through the API. A seed changes by migration, never by an endpoint.
  4. Seeds generate the TS/Zod unions at build time. A drift between the database codes and the generated union fails CI (TS-005) — that check is what keeps the catalog honest.
  5. currencies.minor_unit is mandatory. CLP is 0, USD is 2, and money arithmetic is wrong without it — a fact worth stating because “amounts in cents” quietly assumes 2.
  6. A code is never deleted and never reused. Retiring one means is_active = false, because historic rows still reference it.
  7. Hot paths read from cache. FORBIDDEN: joining a catalog inside a Runtime-class request.
  8. national_id_types rows declare which contact_kind they apply to — a RUT applies to both a person and a company in Chile, a CPF only to a person in Brazil.

Data (normative)

Seeded national_id_types cover every South American country per DEC-A2: CL (RUT) · AR (DNI, CUIT, CUIL) · BR (CPF, CNPJ) · UY (CI, RUT) · PY (CI, RUC) · BO (CI, NIT) · PE (DNI, RUC, CE) · EC (CI, RUC) · CO (CC, CE, NIT) · VE (CI, RIF) · GY and SR generic — plus PASSPORT and FOREIGN_ID. No table here is partitioned; all are small, regular and cached.

API (normative)

Read-only. There is no write endpoint for a system catalog, and that absence is the feature.

Events (normative)

None. A catalog is reference data; a seed change is a migration, covered by the deployment record.

Acceptance criteria (normative)

  1. Migrations seed every catalog idempotently: running them twice produces no duplicates and no errors.
  2. The generated TS union for notification_channels contains exactly the seeded is_system codes, and packages/core compiles against it.
  3. Seed drift is detected: removing a code from the seed while leaving it in the database fails the CI check with a message naming the code.
  4. minor_unit is correct for a currency with 0 (CLP) and one with 2 (USD), verified by a formatting test.
  5. Every seeded national_id_type with a check-digit algorithm has that algorithm implemented — a catalog row pointing at a missing implementation fails CI.
  6. Catalog reads are served from cache: a Runtime-class request performs zero catalog joins, verified by a query-count assertion.
  7. Negative: no API path modifies an is_system row.

Execution

Single slice, synchronous command. Ships as the first part of TS-001. Nothing else in the module can be written before it merges.

Open questions

Changelog

Delivery record

Not implemented yet.