Value Domain
Value Domain protocol schemas
Standard value domains: one closed vocabulary and one membership predicate for settings and fields.
Specifier.valueDomain (settings) and Field.valueDomain (objects) both
reference the schema below and both are judged by the predicate below.
Maintainer ruling 2026-09-02 (A on the field-level card, verbatim 「同意」):
FieldSchema gains a valueDomain slot whose vocabulary is exactly the
settings specifier's three members — one closed vocabulary and one
membership predicate shared by settings specifiers and object fields; the
vocabulary does not widen. Before that ruling the vocabulary lived in
system/settings-manifest.zod.ts and the predicate lived in
service-settings only, so a second declaring surface would have meant a
second copy of both. This module is the one home; SpecifierValueDomainSchema
is an alias of ValueDomainSchema (same name, same three members, same
shape — nothing consuming it moved).
Why the PREDICATE lives in packages/spec at all
Prime Directive #2 keeps business logic out of the spec, and the earlier
TSDoc of this vocabulary read that as "the list does not live here". The
ruling above settles it the other way for this one predicate, on the same
footing as the package's existing shared verdicts: currencyPrecisionContradiction
(a checked-in CLDR table and the rule read over it), filterVerdict, the
comparand-shape door. Each is a pure, dependency-free function two or more
doors must answer IDENTICALLY — and "the same answer on both doors" is
exactly what a shared contract is for. The predicate takes no I/O, holds no
state, and reads no runtime service; the write path (engine) and the
settings door call it, they do not re-derive it.
The definitions of membership, per domain
"The IANA time zone database" and "what this Node happens to enumerate" are measurably different sets, and picking the wrong one rejects legal values. Every definition below was measured on the repo's Node 22 baseline (v22.22.2, full-icu); the shared test re-measures each trap so drift goes red instead of rotting.
iana_time_zone— an IANA/tzdb zone identifier (UTC,Asia/Kolkata,Europe/Kyiv). Membership is theIntl.DateTimeFormatprobe (construct with{ timeZone: value }, catch theRangeError) — the definitionisValidTimeZoneinpackages/core/src/security/resolve-authz-context.tsandlocalization.manifest.test.tsalready use. NOTIntl.supportedValuesOf('timeZone'): measured, it returns 418 CLDR canonical names and omitsUTC(this platform's own declared default),Asia/Kolkata(a curated option in the shipped localization manifest),Europe/Kyiv,Asia/Ho_Chi_Minh,US/EasternandGMT— ICU keeps the old spellings (Asia/Calcutta,Europe/Kiev) as its canonical names, so testing membership against that list rejects values every runtime accepts. The probe is case-insensitive (europe/zurichconstructs fine) — that IS the pinned definition: the accepted domain equals what everyIntl-based consumer downstream accepts.Europe/MunichandMars/Olympusare shape-valid zones that do not exist, and the probe refuses both — the thing apatterncannot do.iso_4217_currency— an ISO 4217 alphabetic currency code (USD,CHF), exact uppercase as the standard spells it. Membership is the key set of the checked-in CLDR snapshotCURRENCY_FRACTION_DIGITS(data/currency-fraction-digits.ts, 162 codes): that table was generated FROMIntl.supportedValuesOf('currency')on the baseline, and the shared test pins the two equal, so this is the same set the settings door has enforced since it shipped — read from a snapshot rather than probed at run time for the reason that table's own header gives (the verdict cannot vary with the host's ICU build, and the check takes noIntldependency). Known, deliberate gaps of that definition: the recently assignedVEDand the metal/fund codes (XAU,XAG, …). Widen by regenerating the snapshot, never by falling back to a regex.iso_3166_alpha2— an ISO 3166-1 alpha-2 country code (US,GB,CN), exact uppercase. There is no standard-library oracle for this one: measured,Intl.DisplayNames(…, { type: 'region' }).of()returns a distinct name forZZ("Unknown Region" — the exact value this domain exists to reject) and forUK(a CLDR alias that is not an ISO 3166-1 code), so "the name differs from the input" is not a membership test. Membership is the explicit list of the 249 officially assigned codes (ISO_3166_ALPHA2_CODES); user-assigned and reserved elements (ZZ,XX,UK,AA,QM–QZ, …) are deliberately absent. One strict spelling is the shape AI-authored metadata cannot get subtly wrong.
The vocabulary is closed and deliberately small: a member earns its place by
a metadata key that actually needs it, not by being a standard that exists.
bcp47_locale was proposed and dropped (no membership registry to enforce
against — Intl.getCanonicalLocales('xx-YY') succeeds, so a "domain" would
only re-check syntax, the weakness pattern already has).
Source: packages/spec/src/shared/value-domain.zod.ts
TypeScript Usage
import { ValueDomainSchema } from '@objectstack/spec/shared';
import type { ValueDomain } from '@objectstack/spec/shared';
// Validate data
const result = ValueDomainSchema.parse(data);ValueDomain
Allowed Values
iana_time_zoneiso_4217_currencyiso_3166_alpha2