Skip to content

Commit 0c9aa82

Browse files
fix(spec): the Seed.locale liveness note names the six seed-load builders and the locale each supplies (#30666)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuV5cRafodM2YQHxeDkdiWJM)_ domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P Fixes #30607 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么**:`packages/spec/liveness/seed.json` 里 `Seed.locale` 这一行的 `note` 重写为六个种子加载请求构建点各自是否带语言、语言从哪来;`verifiedAt` 改为 2026-11-05;另加一个 `@objectstack/spec` 的 patch changeset。 - **为什么改**:PR #30346 之后,市场安装、样例数据重灌、重启自愈和按组织回放都按组织语言挑选种子数据,台账却还写着"市场安装不传语言、安装是语言中立的",与代码和 `SeedSchema.locale` 的 describe(PR #30604)相反。 - **风险与回滚**:只改台账文字与日期,`status` 仍是 `live`,无代码、schema 或导出变化;回滚即 revert 本 PR。 - **先看**:① `packages/spec/liveness/seed.json` 第 53 行 note 中 "THE SCOPE OF THIS `live`" 一段(六个构建点清单)与紧随 #17011 裁决原文之后新增的 "NARROWED 2026-11-05" 一句;② `.changeset/30607-seed-locale-liveness-note.md`。 - **两分钟验证**:`pnpm --filter @objectstack/spec check:liveness`,预期退出码 0,末尾两行均为 ✓。 - **需要你做的**:无,席位落地。 ## What changes `packages/spec/liveness/seed.json`, the `props.locale` row: `note` and `verifiedAt` only (2 lines). `status` (`live`), `evidence`, `producer`, `evidenceScope`, every other row and the file's `_note` parse identical before and after. Plus one `@objectstack/spec` patch changeset. **The builders, counted from the code at `b8929f48`.** Production `SeedLoaderRequestSchema` parses (tests excluded) give **six** request builders, the same number the old note gave. A control scan of every `new SeedLoaderService(` finds three more constructions (`marketplace-install-local-plugin.ts#sampleDataInScope`, `#handlePurge`, `#handleSampleDataCount`). All three call only `buildDependencyGraph`, never `load()`, so none of them is a request builder. | # | Builder (`path#function`) | Path(s) it serves | Locale it supplies | |:--|:--|:--|:--| | 1 | `packages/runtime/src/app-plugin.ts#start` (inline boot seed) | single-tenant boot seed | the app's `i18n.defaultLocale` (`resolveSeedLocale`) | | 2 | `packages/runtime/src/app-plugin.ts#registerHotReloadSeeder` | dev hot-reload | the same, off the boot bundle | | 3 | `packages/runtime/src/app-plugin.ts#start` (the `seed-replayer` it registers) | per-organization replay | `resolveSeedLocaleForLoad`: the org's `localization.locale` when set (not `source: 'default'`), then the founder's `sys_user.locale`, then the app's `i18n.defaultLocale` | | 4 | `packages/cloud-connection/src/marketplace-install-local-plugin.ts#runInlineSeed` | marketplace install and sample-data reseed (`applySideEffects`), restart heal (`maybeHealSampleData`), the plugin's own per-org replayer (`buildSeedReplayer`, used only where no `AppPlugin` replayer is registered) | `resolveSeedLocaleForLoad`: the setting, then the installing / reseeding user or the founder, then the package's `i18n.defaultLocale` (the heal has no user; the plugin's replayer has no app-default rung) | | 5 | `packages/runtime/src/domains/packages.ts#applyPublishedSeeds` | package apply | none | | 6 | `packages/metadata-protocol/src/protocol-publish-batch.ts#applySeedBodies` (the body `protocol.ts#applySeedBodies` delegates to) | draft publish | none | **The note, FROM → TO, by sentence** (every sentence not listed is unchanged byte for byte): In the quotes below, PR #30346's closing line is spelled in words so that this body points a closing keyword only at this card. The note itself writes it in code. 1. FROM "⛔ It does NOT assert that every path which can reach `SeedLoaderService.load()` supplies a locale — **three** do not, they are named below, …" TO "… — **two** do not, they are named below, …" 2. FROM "#16595 wired the THREE in `packages/runtime/src/app-plugin.ts` — the default boot path, which is what makes authoring `locale` change runtime behaviour and therefore what makes this row `live`." TO "FOUR supply a locale, each named by the function that builds its request:" followed by builders (1)–(4) above, each with its `path#function` and its locale source, then "#16595 wired (1)–(3) in `packages/runtime/src/app-plugin.ts` — the default boot path, which is what makes authoring `locale` change runtime behaviour and therefore what makes this row `live` — and PR #30346 (the PR that closed card 30281 [spelled in code in the note], merged 2026-11-05) moved (3) onto `resolveSeedLocaleForLoad`, keeping `resolveSeedLocale` as its last rung, and wired (4). A builder whose source answers nothing sends no `locale` key, never an `'en'` guess." 3. FROM "The other three are publish/install-time paths and still pass no locale: `…/packages.ts#applyPublishedSeeds` (package apply), `…/protocol.ts#applySeedBodies` (draft publish) and `…/marketplace-install-local-plugin.ts#runInlineSeed` (marketplace install)." TO "TWO are publish-time paths and still pass no locale: (5) `packages/runtime/src/domains/packages.ts#applyPublishedSeeds` (package apply) and (6) `packages/metadata-protocol/src/protocol-publish-batch.ts#applySeedBodies` (draft publish — the body `packages/metadata-protocol/src/protocol.ts#applySeedBodies` delegates to; the request moved out of `protocol.ts` with it)." 4. FROM "None of the three is handed the stack config, and none passes `env` either, … and a locale has no ambient source, so it cannot be closed the same way." TO "Neither is handed the stack config, and neither passes `env` either, … and a locale has no ambient source inside `load()`, so it cannot be closed the same way: the four builders that supply one resolve it at the call site." 5. The paragraph "RULED 2026-09-10 on #17011 (director seat, one-class self-adjudication, option A 「不扩散」): … So this line is a DECISION, not a to-do." is **unchanged**: 653 characters, the same bytes, found once in the new note. ADDED right after it: "⚠️ NARROWED 2026-11-05 — the ruling above is kept as it was recorded, and its "three paths" and "three sites" are the 2026-09-10 set, marketplace install included. A maintainer ruling on #30281 (2026-11-05, option A, reversing #17011's install-path bound) ordered the install paths to pick datasets by locale, on HotCRM's request for zh-CN demo data rather than through the measurement the premise below names; PR #30346 (the PR that closed card 30281 [spelled in code in the note], merged 2026-11-05) carried it out and moved the marketplace-install path, builder (4) above, across that bound. The ruling's bound now covers package apply and draft publish only — builders (5) and (6) — and `SeedSchema.locale`'s describe (PR #30604) and that docs page state it narrowed the same way." 6. FROM "It is the live diagnostic for the three call sites above, for any embedding host that builds its own request, and for a stack that declares no `i18n` block — all of which …" TO "It is the live diagnostic for the two call sites above that pass none (package apply, draft publish), for any embedding host that builds its own request, for a stack that declares no `i18n` block (the boot seed and the dev hot-reload seeder have no other source), and, since 2026-11-05, for an install, reseed, heal or replay on which no rung of `resolveSeedLocaleForLoad` answers — all of which …". This reason was checked against the code: `SeedLoaderService#warnOnUnresolvedLocaleScope` returns early only when `config.locale` is set, and `loadDatasets` calls it on every load. `resolveSeedLocaleForLoad` returns `undefined` when no rung answers, and both of its callers then spread no `locale` key. 7. ADDED at the end, after the "REPOINTED while flipping" paragraph: "RE-COUNTED 2026-11-05 against b8929f4 by the same scan of production `SeedLoaderRequestSchema` parses: still SIX builders, the six named above, with the draft-publish request now built in `protocol-publish-batch.ts`. A control scan of every `new SeedLoaderService(` finds no seventh: its three other constructions, in the marketplace plugin's sample-data scope, purge and count reads (`sampleDataInScope`, `handlePurge`, `handleSampleDataCount`), call only `buildDependencyGraph` and never `load()`." 8. `verifiedAt`: `2026-09-09` → `2026-11-05`, the `YYYY-MM-DD` form the sibling rows use. ## Verification All local readings are on head `0cd1455d86` (branch point `b8929f48`). - **Liveness gate**, `pnpm --filter @objectstack/spec check:liveness`: **exit 0**. Its two closing lines are both ✓: every property is classified and every repo-local `evidence` / `producer` pointer, `path:NNN` line and `path#symbol` anchor resolves; `state-counts/` is current. Totals are unchanged: 1172 live · 1 experimental · 83 dead · 42 planned. - **Derived families**, `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, over 2 paths (+14 / −2 lines): **55 commands**, all run with exit codes captured before any pipe. **55 × exit 0.** - The first pass, before a build, had 51 × exit 0 and 4 × exit 3 (`PREREQUISITE NOT MET`, no `dist/`): `check:dts-closure`, `check:dual-build-cjs-loads`, `check:lean-entry-closure` and `check:sourcemap-no-sources-content`. - The full workspace build then ran under the verify lock: `pnpm build --concurrency=2`, 118/118 tasks, `VERDICT command-exit 0`. The working tree was still clean afterwards. - The whole sweep was then re-run on the built tree, and that second run is the table below. The build-reading verdicts it records: 295/295 declared `.d.ts` present across 115 packages; 116 `require` entry points load; 2 lean conditions are measured from a real load; 1276 source maps embed no source text. - **Reconciliation**, `dispatch-gates.mjs --ran`: "✓ 55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED (a DERIVED zero — all 55 recorded an exit code and none of them is 3)". 0 UNRUN. - **`main` moved during the run** (`47c457b23c`, 9 commits). None of them touches `seed.json` or any file the note cites. The builder recount at that tip is the same six `SeedLoaderRequestSchema` parses and the same nine `SeedLoaderService` constructions. - **CI-only families** (artifact rosters, wide populations, the path-scheduled jobs, the type-check lanes) are not local readings. They are this PR's check-runs. <details><summary>Derived families, second pass (55 × exit 0)</summary> | # | Family (command as derived) | Exit | |--:|:--|--:| | 1 | `node scripts/check-adr-0087-registration.mjs --base origin/main` | 0 | | 2 | `node scripts/check-adr-0087-registration.mjs --self-test` | 0 | | 3 | `node scripts/check-changeset-no-major.mjs --base origin/main` | 0 | | 4 | `node scripts/check-closing-keyword-parity.mjs` | 0 | | 5 | `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 | | 6 | `node scripts/check-comment-mask-adoption.mjs` | 0 | | 7 | `node scripts/check-comment-mask-adoption.mjs --self-test` | 0 | | 8 | `node scripts/check-comment-mask-corpus.mjs` | 0 | | 9 | `node scripts/check-dev-prereqs.mjs --self-test` | 0 | | 10 | `node scripts/check-dts-emitted.mjs --self-test` | 0 | | 11 | `node scripts/check-empty-changeset.mjs --base origin/main` | 0 | | 12 | `node scripts/check-empty-changeset.mjs --self-test` | 0 | | 13 | `node scripts/check-issue-citations.mjs` | 0 | | 14 | `node scripts/check-plugin-teardown-shape.mjs` | 0 | | 15 | `node scripts/check-plugin-teardown-shape.mjs --self-test` | 0 | | 16 | `node scripts/ci/assert-turbo-remote-cache.mjs --self-test` | 0 | | 17 | `node scripts/ci/plan-test-core-shards.mjs --self-test` | 0 | | 18 | `node scripts/ci/prune-turbo-cache.mjs --self-test` | 0 | | 19 | `node scripts/docs-audit/check-affected-docs.mjs` | 0 | | 20 | `node scripts/docs-audit/check-drift-comment.mjs` | 0 | | 21 | `node scripts/pm/release-rehearsal-clone.mjs --self-test` | 0 | | 22 | `node scripts/release-pending-publish.mjs --self-test` | 0 | | 23 | `pnpm --filter @objectstack/spec run check:empty-state` | 0 | | 24 | `pnpm --filter @objectstack/spec run check:liveness` | 0 | | 25 | `pnpm --filter @objectstack/spec run check:strictness-ledger` | 0 | | 26 | `pnpm --filter @objectstack/spec run check:variant-docs` | 0 | | 27 | `pnpm check:changeset-gate-self-tests` | 0 | | 28 | `pnpm check:ci-filter-parity` | 0 | | 29 | `pnpm check:closed-ceremony-text` | 0 | | 30 | `pnpm check:cross-package-test-inputs` | 0 | | 31 | `pnpm check:doc-authoring` | 0 | | 32 | `pnpm check:driver-memory-census` | 0 | | 33 | `pnpm check:dts-closure` | 0 | | 34 | `pnpm check:dual-build-cjs-loads` | 0 | | 35 | `pnpm check:gitlink-declared` | 0 | | 36 | `pnpm check:issue-citations` | 0 | | 37 | `pnpm check:keyed-text-bounds` | 0 | | 38 | `pnpm check:lean-entry-closure` | 0 | | 39 | `pnpm check:merge-driver` | 0 | | 40 | `pnpm check:nul-bytes` | 0 | | 41 | `pnpm check:org-identifier` | 0 | | 42 | `pnpm check:page-declaration-shape` | 0 | | 43 | `pnpm check:platform-checklist` | 0 | | 44 | `pnpm check:platform-object-tenancy-census` | 0 | | 45 | `pnpm check:published-files` | 0 | | 46 | `pnpm check:refd-timer-probe` | 0 | | 47 | `pnpm check:registry-log-declared` | 0 | | 48 | `pnpm check:rest-log-spy-declared` | 0 | | 49 | `pnpm check:slot-lookup` | 0 | | 50 | `pnpm check:sourcemap-no-sources-content` | 0 | | 51 | `pnpm check:system-context-census` | 0 | | 52 | `pnpm check:test-source-alias` | 0 | | 53 | `pnpm check:tier-file-adoption` | 0 | | 54 | `pnpm check:undeclared-dep-imports` | 0 | | 55 | `pnpm check:watch-hint-literal` | 0 | </details> ## Acceptance notes - **No pin owed.** The liveness gate does not read `note`. It checks statuses, `evidence` / `producer` pointers (`path:NNN` lines, `path#symbol` anchors, key mentions) and the `verifiedAt` format and age. No test pins the note's text: `git grep` for its phrases outside `seed.json` finds only #30347's changeset and the docs page, and they use their own wording. Every `path#symbol` the new note names was checked to exist in its file at `b8929f48`. - **Changeset, not `skip-changeset`.** `liveness` is in `@objectstack/spec`'s `package.json` `files`, so `seed.json` ships in the tarball, and Check Changeset requires a changeset when the PR has no `skip-changeset` label. A `patch` changeset with both declaration lines, as #30368 and #30611 did for liveness-only edits. - **Where the card's paraphrase and the code differ** (the note follows the code): the draft-publish request is built in `protocol-publish-batch.ts#applySeedBodies`, and `protocol.ts#applySeedBodies` delegates to it. The sample-data reseed and the restart heal live in `marketplace-install-local-plugin.ts` (both go through `runInlineSeed`), not in `app-plugin.ts`. There are two per-org replayers: AppPlugin's, and cloud-connection's `buildSeedReplayer`, which is registered only where no replayer exists yet. - **False sentences found outside this PR's file surface, not fixed here:** - The same row's `producer` field says `resolveSeedLocale` is threaded "into all THREE `SeedLoaderRequest`s this plugin builds: the inline boot seed, the per-org replayer registered for tenant provisioning, and the dev hot-reload seeder". Since PR #30346 the per-org replayer passes `resolveSeedLocaleForLoad`'s answer and uses `resolveSeedLocale` only as its last rung (`packages/runtime/src/app-plugin.ts`, the `replayer` closure in `start`). - The file-level `_note` cites `packages/runtime/src/app-plugin.ts:832, :971` and `packages/metadata-protocol/src/protocol.ts:6764` for the seed-load calls. At `b8929f48` those lines are a closing brace, a datasource-routing docblock line and a search-403 docblock line. The calls are now at `app-plugin.ts` L1856 / L1951 / L2376, and the publish apply has moved to `protocol-publish-batch.ts`. The gate bounds `path:NNN` only in `evidence` / `producer`, so this has never been checked. - **Kept as recorded, not re-measured:** the note's "EVIDENCE FOR THE FLIP" paragraph (an ablation of `resolveSeedLocale` turned 5 of 7 cases red, covering all three AppPlugin call sites) is the 2026-09-09 reading. The per-org replay now resolves through `resolveSeedLocaleForLoad`, so a fresh ablation could read differently. No ablation was run here, because this PR changes no tested behaviour. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L6bhgVugtCCwBs98FeR27P Co-authored-by: Claude <noreply@anthropic.com>
1 parent a2437b5 commit 0c9aa82

2 files changed

Lines changed: 14 additions & 2 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): the liveness ledger's `Seed.locale` row names the six seed-load request builders and where each one's locale comes from, so it no longer says the marketplace install passes none
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- `liveness/seed.json`'s `locale` row said three publish/install-time paths pass no locale, the marketplace install among them. The install paths now pick seed datasets by locale: the marketplace install, the sample-data reseed, the restart heal and the per-organization replay each resolve one (the organization's Localization setting, then the user's language, then the app's `i18n.defaultLocale`). The boot seed and the dev hot-reload seeder still use the app's `i18n.defaultLocale`. Package apply and draft publish still pass none.
11+
- The earlier ruling that publish and install are locale-neutral is kept as recorded, followed by a dated line saying its bound now covers package apply and draft publish only. The note also says why the loader's no-locale warning stays.
12+
- Nothing to do on upgrade: only the shipped `liveness/seed.json` note text and its `verifiedAt` date change. No key, schema shape or export changes.

‎packages/spec/liveness/seed.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@
4646
},
4747
"locale": {
4848
"status": "live",
49-
"verifiedAt": "2026-09-09",
49+
"verifiedAt": "2026-11-05",
5050
"evidenceScope": "in-repo",
5151
"evidence": "packages/metadata-protocol/src/seed-loader.ts#datasetAllowsLocale (`const declared = dataset.locale` — the one read of the key; absence is the unrestricted spelling, because locales have no enumerable universe to default to the way `env` does); packages/metadata-protocol/src/seed-loader.ts#filterByLocale (drops the datasets it excludes, and always NAMES what it dropped)",
5252
"producer": "packages/runtime/src/app-plugin.ts#resolveSeedLocale (AppPlugin resolves the load-time locale ITSELF off the app's declared `i18n.defaultLocale` — the same ENVELOPE key, read the same way `loadTranslations` reads it for `setDefaultLocale` — and threads it into all THREE `SeedLoaderRequest`s this plugin builds: the inline boot seed, the per-org replayer registered for tenant provisioning, and the dev hot-reload seeder. A blank or undeclared `defaultLocale` sends NO `locale` key rather than an `'en'` default, because absence is the loader's unrestricted spelling and a default would have turned a wiring change into a data change)",
53-
"note": "FLIPPED experimental → live 2026-09-09 by #16595, which supplied the missing PRODUCER. The consumer half (#16510 / PR #16592) was always closed and tested; what was missing was the second input the effect depends on, `SeedLoaderConfig.locale`, which no first-party call site passed — so `filterByLocale` returned its input on its first line and `dataset.locale` was never read at all on the default boot path. That is the same shape `Seed.env` was in before framework#4704, which is why this row was recorded as `experimental` instead of published `live` on a correct-but-insufficient consumer pointer (#4837). EVIDENCE FOR THE FLIP, and it is a NEGATIVE leg rather than a green suite: `packages/runtime/src/app-plugin.seed-locale-producer.test.ts` asserts on the rows that reach the engine, not on the request object, and an ablation that neutralises `resolveSeedLocale` (returning `undefined`) turns 5 of its 7 cases red in the inert direction — a `locale: ['zh-CN']` dataset loads on an `en` stack. All three of this plugin's call sites go red, so the pin covers each one. ⚠️ WHAT THIS `live` ASSERTS, AND WHAT IT DOES NOT — read this before citing the row. It ASSERTS the ledger's own criterion and nothing wider: authoring `locale` changes runtime behaviour, on the boot path a `defineStack()` app actually boots through, and the `producer` field names the code that makes that true. ⛔ It does NOT assert that every path which can reach `SeedLoaderService.load()` supplies a locale — three do not, they are named below, and a reader who takes `live` as \"the axis is honoured everywhere\" is reading something this row does not say. That is the distinction between this row and the #4837 specimen one entry up: `Seed.env` was `live` while ZERO of six call sites supplied it, so authoring the key changed nothing anywhere and the verdict was false in the only sense the criterion has. Here it is true, and bounded, and the bound is written down. ⚠️ THE SCOPE OF THIS `live`, stated rather than left to be discovered: this repo has SIX request builders, the same six the `env` row one entry up enumerates. #16595 wired the THREE in `packages/runtime/src/app-plugin.ts` — the default boot path, which is what makes authoring `locale` change runtime behaviour and therefore what makes this row `live`. The other three are publish/install-time paths and still pass no locale: packages/runtime/src/domains/packages.ts#applyPublishedSeeds (package apply), packages/metadata-protocol/src/protocol.ts#applySeedBodies (draft publish) and packages/cloud-connection/src/marketplace-install-local-plugin.ts#runInlineSeed (marketplace install). None of the three is handed the stack config, and none passes `env` either, so this is the SAME frontier the `live` `env` row already sits behind rather than a new one — but `env` crosses it because `resolveEnvConfig` can read an ambient `NODE_ENV` inside `load()`, and a locale has no ambient source, so it cannot be closed the same way. RULED 2026-09-10 on #17011 (director seat, one-class self-adjudication, option A 「不扩散」): publish and install are locale-neutral acts, so this bound is the CONTRACT rather than an open defect — 「无违约、非缺陷」, because `content/docs/data-modeling/seed-data.mdx` already declared that these three paths load every dataset and warn. What the ruling ordered was wording, not wiring: `SeedSchema.locale`'s describe and that docs page now state the bound, and ⛔ no behaviour changed on the three sites — making them pass a locale (option B) and turning the warning into a refusal (option C) were both explicitly ⛔ not ruled. So this line is a DECISION, not a to-do. ⚠️ THE RULING'S LOAD-BEARING PREMISE, and the one thing that reopens it: real first-party seed data using `locale`, measured repo-wide, is ZERO — re-derived 2026-09-22 against 1f53b0b685 by a structural scan of all 49 `defineSeed` call sites (24 of them real app data in `examples/`, none declaring `locale`), self-lit because the same scan does find the two `locale`-declaring call sites on the docs page. That reach is THIS repo only; cloud / hotcrm / external customer apps stay unmeasured. A measured use of `locale`-scoped seed data in any of them reopens this as option B. ⚠️ AND THEREFORE `SeedLoaderService#warnOnUnresolvedLocaleScope` STAYS (the judgement #16595 was asked to make, decided with its reason recorded here rather than deleted silently): it is not a signpost for an unwired state that has now gone away. It is the live diagnostic for the three call sites above, for any embedding host that builds its own request, and for a stack that declares no `i18n` block — all of which still reach `load()` with locale-scoped datasets and no `config.locale`. Deleting it would make exactly those paths silently inert, which is the property this row spent a release being. REPOINTED while flipping: the pre-flip text of this note named four call sites (the three in app-plugin.ts plus draft publish) and called the `protocol.ts` one unconfirmed. Re-derived against `main` at bb7d91f19f: the `protocol.ts` site is REAL — the earlier negative came from grepping `seedLoader.load`, and that call site names its local `loader` — and the enumeration was short by two (package apply, marketplace install). The full set is the six named above, found by scanning `SeedLoaderRequestSchema` rather than a variable name."
53+
"note": "FLIPPED experimental → live 2026-09-09 by #16595, which supplied the missing PRODUCER. The consumer half (#16510 / PR #16592) was always closed and tested; what was missing was the second input the effect depends on, `SeedLoaderConfig.locale`, which no first-party call site passed — so `filterByLocale` returned its input on its first line and `dataset.locale` was never read at all on the default boot path. That is the same shape `Seed.env` was in before framework#4704, which is why this row was recorded as `experimental` instead of published `live` on a correct-but-insufficient consumer pointer (#4837). EVIDENCE FOR THE FLIP, and it is a NEGATIVE leg rather than a green suite: `packages/runtime/src/app-plugin.seed-locale-producer.test.ts` asserts on the rows that reach the engine, not on the request object, and an ablation that neutralises `resolveSeedLocale` (returning `undefined`) turns 5 of its 7 cases red in the inert direction — a `locale: ['zh-CN']` dataset loads on an `en` stack. All three of this plugin's call sites go red, so the pin covers each one. ⚠️ WHAT THIS `live` ASSERTS, AND WHAT IT DOES NOT — read this before citing the row. It ASSERTS the ledger's own criterion and nothing wider: authoring `locale` changes runtime behaviour, on the boot path a `defineStack()` app actually boots through, and the `producer` field names the code that makes that true. ⛔ It does NOT assert that every path which can reach `SeedLoaderService.load()` supplies a locale — two do not, they are named below, and a reader who takes `live` as \"the axis is honoured everywhere\" is reading something this row does not say. That is the distinction between this row and the #4837 specimen one entry up: `Seed.env` was `live` while ZERO of six call sites supplied it, so authoring the key changed nothing anywhere and the verdict was false in the only sense the criterion has. Here it is true, and bounded, and the bound is written down. ⚠️ THE SCOPE OF THIS `live`, stated rather than left to be discovered: this repo has SIX request builders, the same six the `env` row one entry up enumerates. FOUR supply a locale, each named by the function that builds its request: (1) packages/runtime/src/app-plugin.ts#start, the inline boot seed (single-tenant boot) — the app's declared `i18n.defaultLocale`, read by `resolveSeedLocale`; (2) packages/runtime/src/app-plugin.ts#registerHotReloadSeeder, the dev hot-reload seeder — the same `resolveSeedLocale`, off the boot bundle; (3) packages/runtime/src/app-plugin.ts#start, the per-organization replayer it registers as `seed-replayer` for tenant provisioning — `resolveSeedLocaleForLoad` (packages/runtime/src/seed-locale.ts), resolved per organization at replay time: the organization's `localization.locale` setting when somebody set it (a `source: 'default'` answer does not count), then the founder's `sys_user.locale`, then the app's `i18n.defaultLocale`; (4) packages/cloud-connection/src/marketplace-install-local-plugin.ts#runInlineSeed, one builder behind four paths, all through `resolveSeedLocaleForLoad` — the marketplace install and the sample-data reseed (`applySideEffects`: the setting, then the installing or reseeding user's `sys_user.locale`, then the package's declared `i18n.defaultLocale`), the restart heal (`maybeHealSampleData`: no organization and no user, so the setting as read for a load naming no organization, then the package's default) and this plugin's own per-organization replayer (`buildSeedReplayer`, registered only where no `AppPlugin` replayer already is: the setting, then the founder, and no app-default rung, because its shared list is every installed package's datasets). #16595 wired (1)–(3) in `packages/runtime/src/app-plugin.ts` — the default boot path, which is what makes authoring `locale` change runtime behaviour and therefore what makes this row `live` — and PR #30346 (`Fixes #30281`, merged 2026-11-05) moved (3) onto `resolveSeedLocaleForLoad`, keeping `resolveSeedLocale` as its last rung, and wired (4). A builder whose source answers nothing sends no `locale` key, never an `'en'` guess. TWO are publish-time paths and still pass no locale: (5) packages/runtime/src/domains/packages.ts#applyPublishedSeeds (package apply) and (6) packages/metadata-protocol/src/protocol-publish-batch.ts#applySeedBodies (draft publish — the body `packages/metadata-protocol/src/protocol.ts#applySeedBodies` delegates to; the request moved out of `protocol.ts` with it). Neither is handed the stack config, and neither passes `env` either, so this is the SAME frontier the `live` `env` row already sits behind rather than a new one — but `env` crosses it because `resolveEnvConfig` can read an ambient `NODE_ENV` inside `load()`, and a locale has no ambient source inside `load()`, so it cannot be closed the same way: the four builders that supply one resolve it at the call site. RULED 2026-09-10 on #17011 (director seat, one-class self-adjudication, option A 「不扩散」): publish and install are locale-neutral acts, so this bound is the CONTRACT rather than an open defect — 「无违约、非缺陷」, because `content/docs/data-modeling/seed-data.mdx` already declared that these three paths load every dataset and warn. What the ruling ordered was wording, not wiring: `SeedSchema.locale`'s describe and that docs page now state the bound, and ⛔ no behaviour changed on the three sites — making them pass a locale (option B) and turning the warning into a refusal (option C) were both explicitly ⛔ not ruled. So this line is a DECISION, not a to-do. ⚠️ NARROWED 2026-11-05 — the ruling above is kept as it was recorded, and its \"three paths\" and \"three sites\" are the 2026-09-10 set, marketplace install included. A maintainer ruling on #30281 (2026-11-05, option A, reversing #17011's install-path bound) ordered the install paths to pick datasets by locale, on HotCRM's request for zh-CN demo data rather than through the measurement the premise below names; PR #30346 (`Fixes #30281`, merged 2026-11-05) carried it out and moved the marketplace-install path, builder (4) above, across that bound. The ruling's bound now covers package apply and draft publish only — builders (5) and (6) — and `SeedSchema.locale`'s describe (PR #30604) and that docs page state it narrowed the same way. ⚠️ THE RULING'S LOAD-BEARING PREMISE, and the one thing that reopens it: real first-party seed data using `locale`, measured repo-wide, is ZERO — re-derived 2026-09-22 against 1f53b0b685 by a structural scan of all 49 `defineSeed` call sites (24 of them real app data in `examples/`, none declaring `locale`), self-lit because the same scan does find the two `locale`-declaring call sites on the docs page. That reach is THIS repo only; cloud / hotcrm / external customer apps stay unmeasured. A measured use of `locale`-scoped seed data in any of them reopens this as option B. ⚠️ AND THEREFORE `SeedLoaderService#warnOnUnresolvedLocaleScope` STAYS (the judgement #16595 was asked to make, decided with its reason recorded here rather than deleted silently): it is not a signpost for an unwired state that has now gone away. It is the live diagnostic for the two call sites above that pass none (package apply, draft publish), for any embedding host that builds its own request, for a stack that declares no `i18n` block (the boot seed and the dev hot-reload seeder have no other source), and, since 2026-11-05, for an install, reseed, heal or replay on which no rung of `resolveSeedLocaleForLoad` answers — all of which still reach `load()` with locale-scoped datasets and no `config.locale`. Deleting it would make exactly those paths silently inert, which is the property this row spent a release being. REPOINTED while flipping: the pre-flip text of this note named four call sites (the three in app-plugin.ts plus draft publish) and called the `protocol.ts` one unconfirmed. Re-derived against `main` at bb7d91f19f: the `protocol.ts` site is REAL — the earlier negative came from grepping `seedLoader.load`, and that call site names its local `loader` — and the enumeration was short by two (package apply, marketplace install). The full set is the six named above, found by scanning `SeedLoaderRequestSchema` rather than a variable name. RE-COUNTED 2026-11-05 against b8929f48 by the same scan of production `SeedLoaderRequestSchema` parses: still SIX builders, the six named above, with the draft-publish request now built in `protocol-publish-batch.ts`. A control scan of every `new SeedLoaderService(` finds no seventh: its three other constructions, in the marketplace plugin's sample-data scope, purge and count reads (`sampleDataInScope`, `handlePurge`, `handleSampleDataCount`), call only `buildDependencyGraph` and never `load()`."
5454
},
5555
"records": {
5656
"status": "live",

0 commit comments

Comments
 (0)