feat(spec, metadata-protocol): add a locale filter axis to seed datasets, composed with env - #16592
feat(spec, metadata-protocol): add a locale filter axis to seed datasets, composed with env#16592hotlong wants to merge 5 commits into
Conversation
WIP — schema + loader halves; tests, docs and changeset to follow. Claude-Session: https://claude.ai/code/session_013r78utTbiWqxghcuRJxfZf Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013r78utTbiWqxghcuRJxfZf Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013r78utTbiWqxghcuRJxfZf Co-authored-by: Claude <noreply@anthropic.com>
…oubles Both are mechanical, gate-driven repairs of this change's own side effects: the +51 lines in seed-loader.ts moved three cited anchors, and the new locale-scope test pins engine doubles the contract ledger had not recorded. Claude-Session: https://claude.ai/code/session_013r78utTbiWqxghcuRJxfZf Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin cd8f5b3049d1679d538e2d115c459e4afa900265 && git checkout cd8f5b3049d1679d538e2d115c459e4afa900265
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bc0ac1d1f5775659e72ad35452c463443a71f1be ba564fccb79e9bd4c8aee6cf8f13a8bda338aea2 && git checkout -B drift-repro bc0ac1d1f5775659e72ad35452c463443a71f1be && git merge --no-ff ba564fccb79e9bd4c8aee6cf8f13a8bda338aea2
node scripts/docs-audit/affected-docs.mjs --json bc0ac1d1f5775659e72ad35452c463443a71f1be
|
|
Contract review PASS at 维护者速读(终稿)改了什么:种子数据集 为什么改:您在 #16510 立的卡:一个要同时发中英文演示数据的应用,今天只能在装配 config 时用自造的环境变量二选一,选择被烤进 风险与代价(含回滚):纯新增可选键。唯一未闭合的是 runtime 还没把配置里的语言传给装载器(#16595 已立, 席位意见:契约复审达档 PASS(5571519820)。接受集扩大方向正确; 你要做的:人工合并 PR #16592(受管面,不走队列)。是或否。 Generated by Claude Code |
出队处置(PM 审计)队列在 冲突签名零配额本地试合并, 冲突文件只有一个,两侧的体量差是这次处置的全部关键:
即:抢先落地的那张卡,拆掉的正是本 PR 那一行所属的整套锚点机制。本 PR 的 +51 行
处置路由按 预期解法是取 main 侧整文件后按新符号锚重跑 落地路径提醒本 PR 属受管面( Generated by Claude Code |
派发记录返工已派出 ·
更正上一条评论的一处措辞。 我写的是「已回原实现席位返工」,不准确:原实现会话 派发词里对冲突解法给的是建议路线(取 main 侧整文件后按符号锚重跑门禁),明确标注可换、以席位在树上跑出的读数为准——不是裁决。 我继续订阅本 PR 盯到 MERGED 或关闭。 Generated by Claude Code |
…ed-locale-axis # Conflicts: # content/docs/permissions/system-context.mdx
|
@os-zhuang 提醒一句,免得白等或白入队一次:14:40:03Z 这次批准落在未变的 head 上,清不掉冲突。
真正的闸门是那次同步提交。返工席位 推送落地后可能还要请你再批一次:若本仓分支保护开了 dismiss stale reviews,新 head 会作废现有批准。这一步我做不了——受管面 PR 我不批准、不入队、不合并。 Generated by Claude Code |
返工验收(PM 复核)· 第 1 轮 ACCEPT席位推了同步提交后被用量墙掐断( 状态关键门禁逐条(新 head 上复跑,非沿用旧读数):
冲突是怎么解的 —— 用文件面证实,不是靠席位声明我在派发词里给的建议路线是「对 少掉的正是 本卡实现未被动过剩下 15 个文件与契约复审时逐字相同: 更正我先前的一处预判我此前说「新 head 可能作废现有批准」。实测没有作废:两次批准现在都仍是 现在卡在哪绿、干净、已批准, @os-zhuang 就差这一步。我做不了:受管面 PR 我不批准、不入队、不挂 auto-merge、不合并。 合并后 #16595(runtime 三个 Generated by Claude Code |
返工第 1 轮:同步
|
| 同步目标 | origin/main = 8341ed21cededfc1d4f953c54b71e76c26628ee9 |
| 同步前 merge-base | f2f6684cd5e93032ed5d82a1848784b6026a8a03 |
| merge 提交 | ba564fccb79e9bd4c8aee6cf8f13a8bda338aea2,双亲 35929cd3 + 8341ed21 |
| PR base sha | 已随之推进到 8341ed21 |
走的是 bash scripts/pm/os-regen-merge.sh。脚本按设计在 step 1 停下:os-regen driver 对 MIXED 文件拒绝 defer,改走文本合并并冲突,脚本据此分类并打印了「generated, MIXED」分支的指引后 exit 1。
冲突怎么解的
唯一冲突:content/docs/permissions/system-context.mdx。 scripts/engine-double-contract.pinned.json 自动合并成功,非冲突(与 PM 侧 merge-tree 读数一致)。
取 main 侧整文件。 理由是两侧改的根本不是同一套东西:
- 本 PR 侧 +1/−1:
seed-loader.ts:2032→:2083(及:1942–1944→:1993–1995),是原会话跑check:system-context-census --fix机械改出来的行号锚。 origin/main侧55bbd92(docs(devx): anchor the isSystem census page by symbol, not by line number #16614)把整页从行号锚迁移到符号锚(packages/metadata-protocol/src/seed-loader.ts#SEED_OPTIONS、#writeDeferredReference),行号在这一页上已经不再是记账单位。
本 PR 给 seed-loader.ts 加的 +51 行没有移动任何符号,而且该 delta 通篇不含 isSystem / skipTriggers(实测 git diff f2f6684 35929cd -- packages/metadata-protocol/src/seed-loader.ts | grep 'isSystem\|skipTriggers' 零命中),所以在新机制下这一页对本 PR 是净零改动。取 main 侧后该文件与 origin/main 逐字节相同,并整个退出了 PR 的 diff:changed_files 由 16 降到 15,+660/−6 变 +659/−5。
这同时就是脚本 step 2 该做的事:两侧都动过的 os-regen 路径只有这一条,取 main 侧正是 step 2 对该情形的规定动作;分支单侧动过的 os-regen 路径(另外 6 条)保留分支字节,未被回退。Step 3 照序在任何重生成之前提交了 merge。
重生成动了哪些产物
一件都没动。 merge 提交落地后,先建依赖闭包再整链重生成:
pnpm --filter '@objectstack/metadata-protocol^...' build --concurrency=2 && pnpm --filter @objectstack/spec build
os-verify-lock: VERDICT command-exit 0 · held the lock 529s
pnpm --filter @objectstack/spec check:generated --fix
✓ 15/15 migration-registry · spec-changes · upgrade-guide · meta-url-spelling · skill-docs
skill-refs · react-blocks · authorable-surface · api-surface · export-origins
declaration-map · docs · strictness-ledger · liveness · test-typecheck
✓ All 15 generated artifacts are up to date.
--fix 无一条可修,git status 全程干净 —— 所以本轮没有第二个重生成提交,merge 提交就是全部交付物。gen:schema 未在 MERGE 状态下跑过(merge 已先提交,重生成在其之后)。
A 节要求的断言
- 兄弟单条目全在。 更硬的一条:main 在本窗口推进过的每一条 os-regen 产物,在合并树里与
origin/main逐字节相同 ——git diff HEAD origin/main -- <main 侧动过的 14 条>输出为空。没有任何一侧被 driver 静默吞掉。 - 上一单实现体完好。 docs(devx): anchor the isSystem census page by symbol, not by line number #16614 的三个文件(
scripts/check-system-context-census.mjs、scripts/isystem-census.mjs、content/docs/permissions/system-context.mdx)对origin/main逐字节相同。命名旁证:stranded(feat(runtime, spec): the resume door's 400 FLOW_FAILED details carry the engine's stranded verdict #16587)仍在content/docs/references/api/automation-api.mdx(4 处)。 - 合并树里对 main 的剩余 os-regen 差异,恰好只有本 PR 自己的行:
seed-loader.mdx +3、seed.mdx +1、manifest.mdx +1、authorable-surface/data.json +2、liveness/state-counts.md +2/−2、skills/objectstack-data/references/_index.md +1。受管面读数不变:仍是那一行生成的登记行。 - 锚点断言:
pnpm --filter @objectstack/spec check:authorable-surface绿 ——🔒 authorable-defaults/ verified against upstream 8341ed21cede — 1211 default(s) unchanged,✅ 1586 schemas。baseRev 本轮甚至没有滞后;⛔ 未为凑相等手改任何锚点。 - 本卡实现未被触碰:
git diff 35929cd HEAD --对seed.zod.ts/seed-loader.zod.ts/seed-loader.ts/ 两个测试 /seed-data.mdx/liveness/seed.json/ changeset 输出为空。
新 head 上的复跑读数
在 ba564fcc 的专用 worktree 内跑,重活走 scripts/pm/os-verify-lock.sh。
check:system-context-census (含 --self-test)
--self-test: all cases passed
OK — 106 elevation read sites in 20 packages across 45 files, living in 89 symbol(s);
the page cites 103 symbol(s) against 103 required, over 127 anchors and
8 file-level citation(s); 22 declared non-read; 18 row reference(s) resolve
to their keyed row, 2 declared unheld.
check:engine-double-contract (含 --self-test)
OK — 826 pinned, 133 in the DEBT ledger, 3 exempt.
758 (file, verb) row(s) held by the RETAINED ledger.
check:generated
✓ All 15 generated artifacts are up to date.
check:authorable-surface
🔒 verified against upstream 8341ed21cede — 1211 default(s) unchanged
✅ 1586 schemas
pnpm --filter @objectstack/spec test && typecheck
Test Files 461 passed (461)
Tests 12809 passed (12809) # 交付时 460/12778,增量来自 main
tsc --noEmit: exit 0
pnpm --filter @objectstack/metadata-protocol test && typecheck
Test Files 170 passed | 2 skipped (172)
Tests 2448 passed | 10 skipped (2458)
tsc --noEmit: exit 0
os-verify-lock: VERDICT command-exit 0 · held the lock 737s (12m17s) · waited 0s
派发令点名的 5 项门禁在新 head 上全部复绿,读数与交付时一致或更好。
状态与边界
mergeable_state 已从 dirty 变为 blocked —— 冲突消失,当前挡在「CI 在跑 + 需要批准」上,新 head 的 16 个 check 已启动。os-zhuang 的两次批准都记在 35929cd3 上,按分支保护会随 head 变化被 dismiss,需要重新批准(派发时已预期,非异常)。
本轮未做、也不归我做:未转 draft(本仓转 draft 会同时掉 auto-merge 与队列成员资格且不自动恢复);未入队、未挂 auto-merge、未合并 —— 本 PR 因 skills/objectstack-data/references/_index.md 命中受管面,按 Prime Directive #14 走人工合并道。content/docs/releases/** 未触碰。本卡实现零改动。
推送完毕,交回车道 PM 的落地窗口。
Generated by Claude Code
终态补记(上一条评论写于 CI 在跑时,状态一节已被此条取代)新 head
一处更正:上一条评论按分支保护的一般行为预判 os-zhuang 的批准会被新 head dismiss —— 实际没有被 dismiss, 我这边到此为止:未入队、未挂 auto-merge、未合并、未转 draft。受管面( Generated by Claude Code |
Fixes #16510
Seed datasets gain a
localefilter axis, composed withenvby the loader. Both halves land here: the keySeedSchemaaccepts and the filter that enforces it.What changed
packages/spec/src/data/seed.zod.tsSeedSchema.locale?: string[](BCP-47 tags,LocaleSchema),.min(1), optional. Aliaseslocales/language/languagesonto it, matching the existingenvironment/environmentspair.packages/spec/src/data/seed-loader.zod.tsSeedLoaderConfigSchema.locale?: string— the tag a load filters on.packages/metadata-protocol/src/seed-loader.tsdatasetAllowsLocale/isLocaleScopedDataset/normalizeLocaleTagmirror theenvhelpers;filterByLocalemirrorsfilterByEnv;filterDatasetscomposes the two by conjunction and replaces the singlefilterByEnvcall site.warnOnUnresolvedLocaleScopesignposts an inert axis.packages/spec/liveness/seed.jsonlocalerow, statusexperimental— see The producer gap below.packages/metadata-protocol/src/seed-loader-locale-scope.test.tsseed-loader-env-scope.test.ts.packages/spec/src/data/seed.test.tscontent/docs/data-modeling/seed-data.mdxdefineSeed()API block..changeset/seed-locale-axis.md@objectstack/specminor,@objectstack/metadata-protocolminor.Design decisions, all recorded in the code that carries them:
envcan spell its default out because its three environments are a closed set; BCP-47 tags have no enumerable universe, so absence has to be the unrestricted spelling. An empty array is rejected — a dataset that applies nowhere is an authoring mistake, the same reasoningexternalId's.min(1)already carries.zh-cnandzh-CNare one tag.['zh']does not matchzh-CN: widening that would be the lenient consumer-side fallback Prime Directive Add comprehensive test suite for Zod schema validation #12 forbids, and the author's remedy is to list both tags.resolveEnvConfigalready takes.The producer gap, stated plainly
The axis is evaluated against
config.locale, and no first-party call site supplies one yet. The runtime wiring that would resolve it from the stack's configured locale lives inpackages/runtime/src/app-plugin.ts(threeseedLoader.load(request)sites) plus the draft-publish path inprotocol.ts— outside this card's declared file surface, so it is not in this PR. An embedding host that passesconfig.localeitself gets the full behaviour today; on the default boot path the axis is inert.That is the shape
Seed.envwas in before framework#4704, so two things follow and both are in this diff:warnOnUnresolvedLocaleScope— a load carrying locale-scoped datasets and noconfig.localewarns naming every dataset it let through and the config key that would make the scope take effect. This is exactly whatSeed.envis authorable but never enforced: the app seeding path never setsSeedLoaderConfig.env, soenv: ['dev']seeds into production too #4704 did not have: the axis can be unwired, it cannot be silently unwired.seed.localeasexperimental, notlive— consumer side cited, producer gap spelled out in the row. Publishingliveon a correct-but-insufficient consumer pointer is the liveness 台账把「消费端存在读取代码」当作 live 的证据,会漏掉「没有任何生产者传值」的死键(Seed.env 即如此) #4837 falsehood theproducerfield exists to prevent, andseed.envis the specimen it was built from. Re-classify tolivewith aproducerpointer when the wiring lands.Follow-up the PM should file: wire
config.localefrom the runtime's configured locale at the seed-load call sites inpackages/runtime, and flip the ledger row. A second, genuinely separate follow-up named in the card and deliberately not attempted: reconciling rows already written under a different locale.Verification record
Ran in a dedicated worktree at
35929cd3, everything foreground, heavy runs throughscripts/pm/os-verify-lock.sh.Dependency closure and generated artifacts
Tests
Reverse verification (ablation). Subject resolves through SOURCE — the test imports
./seed-loader.js, a relative specifier inside the same package — so no dist leg is involved. Mutation: an earlyreturn trueinserted above thenormalizeLocaleTag(locale)read indatasetAllowsLocale, i.e. the locale predicate neutered while everything else stands.8 of 16 red under the mutation, byte-identical restore proven by blob hash rather than by an exit code, 16/16 green again. The 8 that stayed green are the ones that should: the unscoped-dataset cases, the no-locale-supplied cases and the env-only composition legs do not depend on this predicate.
Gate families. Derived from the diff rather than guessed:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackatd621decover 14 changed paths, 103 commands. Ran all 103. Final state: 102 pass, 1 not measurable here.Three were red on the first pass and are fixed in this diff:
check:system-context-census— 6 problems. The +51 lines inseed-loader.tsmoved three anchors cited fromcontent/docs/permissions/system-context.mdx. Repaired mechanically with the gate's own--fix(3 anchors rewritten); gate nowOK — 106 elevation read sites in 20 packages across 45 files, all anchored.check:engine-double-contract— the new test pins 3 engine doubles the ledger had not recorded.node scripts/check-engine-double-contract.mjs --write:756 (file, verb) row(s), 3 added or grown, 0 lost. Gate green.check:skill-examples,check:dual-build-cjs-loads,check:type-check-debt— all three returnedPREREQUISITE NOT MET(exit 3 for two of them), reading built output this fresh worktree had not produced. Built the workspace (turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2—72 successful, 72 total) and re-ran: all three green.check:type-check-debtOOMed once at 4 GB and was re-run at 8 GB —--re-measure: OK — 5 ledger entr(ies) re-measured in 66.6s, 55 raw tsc error(s) total, none above its recorded number.Not measurable in this checkout, and not read as a pass:
node scripts/check-plugin-teardown-shape.mjs --self-testrefuses on a shallow clone —cannot read the positive control at 621a487607881c66b2899b7e3477115229a156b4.git fetch --deepen 500did not reach it. It is a checker-health self-test that judges the checker's own fixtures, not this diff, and my diff touches neither the script nor plugin teardown shapes.Lint. The repo-wide run, not a narrowed one:
pnpm lint(eslint . --no-inline-config) —VERDICT command-exit 0, 66s. No narrowing to declare.Byte discipline.
pnpm check:nul-bytesgreen, plus a direct scan over every file in the diff with the control-character class — no hits.Governed surface reading
The diff touches one governed path:
skills/objectstack-data/references/_index.md, +1 line, entirely generated bygen:skill-refs. It moved becausedata/seed.zod.tsnow importsLocaleSchemafromsystem/translation.zod.ts, which enlarges the data skill's reference closure by that one module. Two readings, as the published-skills rule requires:skills/package, sum of everySKILL.md: 6858 → 6858 lines (unchanged — noSKILL.mdis in this diff)No prose was written or expanded. Recorded here because a governed path is judged on the file list, not on the description.
维护者速读(草稿)
改了什么 — 给种子数据集加了
locale过滤轴,和现有的env轴完全平行:schema 侧新增Seed.locale(BCP-47 标签数组,不写 = 所有语言),loader 侧filterByLocale与filterByEnv合取——两条都通过才装载。为什么改 — 一个要同时发中英文演示数据的应用,今天只能在装配 config 的时候用应用自己发明的环境变量二选一。选择被固化进
dist(换语言要删构建产物),而且因为每份都是 upsert、loader 只写不清,换过去之后另一份的行还留在库里。这两件事都是"在错误的层做过滤"的后果;放到装载层,两件都自然消失。风险与代价(含回滚) — 纯新增可选键,现有种子的行为字节不变:不写
locale的数据集在任何语言下照常装载。风险集中在一处:runtime 还没有把配置里的语言传给 loader(那三个调用点在packages/runtime,不在本卡的文件面内),所以默认启动路径上这条轴目前是空转的。这不是静默空转——带locale的数据集遇不到config.locale时 loader 会点名告警;liveness 台账也按experimental而非live记账,理由写在行里。回滚成本低:整个改动是可选键 + 一个过滤函数,revert 即可,没有数据迁移。席位意见 — (留空,待席位定稿)
你要做的 — 决定 runtime 接线那半是否立即立卡(报告里已建议)。除此之外这张卡按 Clause-② 走契约评审,
skills/**有一行生成的登记行,按 Prime Directive #14 属受管面。Generated by Claude Code