Symptom
SeedSchema can gate a dataset on environment but not on locale. An app that wants to ship
a Chinese demo dataset and an English one — the same records, different display strings — has no
declarative way to say which applies, so the selection has to happen in application code before the
platform ever sees the seed.
The schema is strictObject, so an app cannot add the key itself:
packages/spec/src/data/seed.zod.ts:33 — SeedSchema = lazySchema(() => strictObject({…}));
fields are object / externalId / mode / env / records plus the package-provenance keys.
The alias table maps environment and environments onto env; there is no locale spelling.
packages/spec/src/data/seed-loader.zod.ts — SeedLoaderConfigSchema carries env,
organizationId and identity, but no locale.
packages/metadata-protocol/src/seed-loader.ts:2486 — filterByEnv(datasets, env) is the only
dataset-level filter; :490 is its single call site.
Verified against the source tree at c383352, and against the published @objectstack/spec@17.2.0
d.ts as installed in a consuming app. A repo-wide grep for locale across the seed sources returns
one unrelated hit (a pinyin-search comment in examples/app-showcase/src/data/seed/index.ts:111).
Why this lands on the platform
Per references/app-platform-boundary.md, third row of the deciding table: the subject is
platform behaviour and the cost lands on the app — the missing filter axis is the loader's, and
the workaround is paid for in every app that needs it.
The second question — if a second app needed this, would it copy the implementation? — is
answered structurally rather than by a second consumer already in hand: any marketplace app
installed into an org whose locale differs from the author's has the same problem, and the person
installing it cannot be expected to know an app-private environment variable exists. One real
consumer today (see below); the generalisation argument is the marketplace install path, not a
second app already written.
What the app-side workaround costs
objectstack-ai/kpi currently selects between two seed profiles with an app-invented environment
variable read at module-evaluation time:
// src/data/index.ts
export const SEED_PROFILE = (process.env.OS_SEED_PROFILE ?? 'default').trim().toLowerCase();
export const KpiSeedData = SEED_PROFILE === 'software' ? SoftwareSeedData : DefaultSeedData;
Because the choice is made while the config is assembled rather than while seeds are loaded, two
defects follow that the app cannot fix from where it sits:
- The selection is cached in the build output. Switching profiles requires deleting
dist,
otherwise the previous profile is still in effect. The app's README has to document this.
- The other profile's rows stay in the database. Every profile is
upsert and the loader only
writes, so switching on a non-empty database leaves both datasets resident — two org trees and
two plans in one database. The app's README has to tell operators to use a fresh database file.
Both are consequences of filtering in the wrong layer. A loader-side filter is evaluated at load
time, so the dist caching problem does not arise; and the loader already holds externalId and
the _packageId / _provenance keys, so it is the only layer that could reconcile rows belonging
to a locale that is no longer active.
Expected capability
Add locale to SeedSchema as a filter axis exactly parallel to env, honoured by the loader:
SeedSchema.locale?: string[] — omitted means "applies to every locale", matching how env
defaults to all environments.
SeedLoaderConfig.locale?: string — resolved from the runtime's configured locale, overridable
the way env is.
filterByEnv becomes the composition of both axes; a dataset applies when it passes env and
locale.
Deliberately out of scope: the platform does not translate anything. The app still authors both
record sets. The platform gains only the axis to select between them.
Scope note
The spec field and the loader filter have to land together. A locale key accepted by the schema
but ignored by the loader is a declared-not-enforced surface — the shape this repo routes to the
enforce-or-remove channel — and it would be worse than the current state, because an app would
write the key and silently get both datasets.
Environment
@objectstack/spec / @objectstack/runtime / @objectstack/cli 17.2.0
- Source tree
c383352
- Consuming app:
objectstack-ai/kpi (Apache-2.0, public)
Duplicate check
Searched seed data locale language variant demo data per locale i18n seed selection and
seed loader env filter records upsert in this repo. The two locale hits are unrelated — #9135
(auth email locale) and #5419 (console UI language). The control search surfaces real seed-loader
cards (#16488, #14403, #3434), so the negative result on locale is a real absence rather than a
search that matched nothing.
Symptom
SeedSchemacan gate a dataset on environment but not on locale. An app that wants to shipa Chinese demo dataset and an English one — the same records, different display strings — has no
declarative way to say which applies, so the selection has to happen in application code before the
platform ever sees the seed.
The schema is
strictObject, so an app cannot add the key itself:packages/spec/src/data/seed.zod.ts:33—SeedSchema = lazySchema(() => strictObject({…}));fields are
object/externalId/mode/env/recordsplus the package-provenance keys.The alias table maps
environmentandenvironmentsontoenv; there is no locale spelling.packages/spec/src/data/seed-loader.zod.ts—SeedLoaderConfigSchemacarriesenv,organizationIdandidentity, but no locale.packages/metadata-protocol/src/seed-loader.ts:2486—filterByEnv(datasets, env)is the onlydataset-level filter;
:490is its single call site.Verified against the source tree at
c383352, and against the published@objectstack/spec@17.2.0d.ts as installed in a consuming app. A repo-wide grep for
localeacross the seed sources returnsone unrelated hit (a pinyin-search comment in
examples/app-showcase/src/data/seed/index.ts:111).Why this lands on the platform
Per
references/app-platform-boundary.md, third row of the deciding table: the subject isplatform behaviour and the cost lands on the app — the missing filter axis is the loader's, and
the workaround is paid for in every app that needs it.
The second question — if a second app needed this, would it copy the implementation? — is
answered structurally rather than by a second consumer already in hand: any marketplace app
installed into an org whose locale differs from the author's has the same problem, and the person
installing it cannot be expected to know an app-private environment variable exists. One real
consumer today (see below); the generalisation argument is the marketplace install path, not a
second app already written.
What the app-side workaround costs
objectstack-ai/kpicurrently selects between two seed profiles with an app-invented environmentvariable read at module-evaluation time:
Because the choice is made while the config is assembled rather than while seeds are loaded, two
defects follow that the app cannot fix from where it sits:
dist,otherwise the previous profile is still in effect. The app's README has to document this.
upsertand the loader onlywrites, so switching on a non-empty database leaves both datasets resident — two org trees and
two plans in one database. The app's README has to tell operators to use a fresh database file.
Both are consequences of filtering in the wrong layer. A loader-side filter is evaluated at load
time, so the
distcaching problem does not arise; and the loader already holdsexternalIdandthe
_packageId/_provenancekeys, so it is the only layer that could reconcile rows belongingto a locale that is no longer active.
Expected capability
Add
localetoSeedSchemaas a filter axis exactly parallel toenv, honoured by the loader:SeedSchema.locale?: string[]— omitted means "applies to every locale", matching howenvdefaults to all environments.
SeedLoaderConfig.locale?: string— resolved from the runtime's configured locale, overridablethe way
envis.filterByEnvbecomes the composition of both axes; a dataset applies when it passesenvandlocale.Deliberately out of scope: the platform does not translate anything. The app still authors both
record sets. The platform gains only the axis to select between them.
Scope note
The spec field and the loader filter have to land together. A
localekey accepted by the schemabut ignored by the loader is a declared-not-enforced surface — the shape this repo routes to the
enforce-or-remove channel — and it would be worse than the current state, because an app would
write the key and silently get both datasets.
Environment
@objectstack/spec/@objectstack/runtime/@objectstack/cli17.2.0c383352objectstack-ai/kpi(Apache-2.0, public)Duplicate check
Searched
seed data locale language variant demo data per locale i18n seed selectionandseed loader env filter records upsertin this repo. The two locale hits are unrelated — #9135(auth email locale) and #5419 (console UI language). The control search surfaces real seed-loader
cards (#16488, #14403, #3434), so the negative result on locale is a real absence rather than a
search that matched nothing.