Skip to content

feat(spec, metadata-protocol): add a locale filter axis to seed datasets, composed with env - #16592

Open
hotlong wants to merge 5 commits into
mainfrom
claude/issue-16510-seed-locale-axis
Open

feat(spec, metadata-protocol): add a locale filter axis to seed datasets, composed with env#16592
hotlong wants to merge 5 commits into
mainfrom
claude/issue-16510-seed-locale-axis

Conversation

@hotlong

@hotlong hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #16510

Seed datasets gain a locale filter axis, composed with env by the loader. Both halves land here: the key SeedSchema accepts and the filter that enforces it.

What changed

File Change
packages/spec/src/data/seed.zod.ts SeedSchema.locale?: string[] (BCP-47 tags, LocaleSchema), .min(1), optional. Aliases locales / language / languages onto it, matching the existing environment / environments pair.
packages/spec/src/data/seed-loader.zod.ts SeedLoaderConfigSchema.locale?: string — the tag a load filters on.
packages/metadata-protocol/src/seed-loader.ts datasetAllowsLocale / isLocaleScopedDataset / normalizeLocaleTag mirror the env helpers; filterByLocale mirrors filterByEnv; filterDatasets composes the two by conjunction and replaces the single filterByEnv call site. warnOnUnresolvedLocaleScope signposts an inert axis.
packages/spec/liveness/seed.json new locale row, status experimental — see The producer gap below.
packages/metadata-protocol/src/seed-loader-locale-scope.test.ts 16 cases, shaped on seed-loader-env-scope.test.ts.
packages/spec/src/data/seed.test.ts 6 contract cases: accept, absence, empty-array rejection, bare-string rejection, alias routing.
content/docs/data-modeling/seed-data.mdx a Locale Scoping section, a best-practice entry, and the defineSeed() API block.
.changeset/seed-locale-axis.md @objectstack/spec minor, @objectstack/metadata-protocol minor.

Design decisions, all recorded in the code that carries them:

  • Omitted means every locale, and there is no default array. env can 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 reasoning externalId's .min(1) already carries.
  • Tags compare case-insensitively, and otherwise exactly. RFC 5646 casing is a convention, not part of a tag's identity, so zh-cn and zh-CN are one tag. ['zh'] does not match zh-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.
  • The axes compose by conjunction, in separate functions with separate log lines. The two answer different operator questions ("why are my demo rows missing in production" vs "why did the Chinese dataset load"), and one merged message would have to name a reason it did not measure.
  • Permissive when indeterminate, but never silent. Fail-closed on a missing locale would drop rows on every host that does not pass one — a silent data-loss regression strictly worse than the over-seeding it prevents. Same posture resolveEnvConfig already 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 in packages/runtime/src/app-plugin.ts (three seedLoader.load(request) sites) plus the draft-publish path in protocol.ts — outside this card's declared file surface, so it is not in this PR. An embedding host that passes config.locale itself gets the full behaviour today; on the default boot path the axis is inert.

That is the shape Seed.env was in before framework#4704, so two things follow and both are in this diff:

  1. warnOnUnresolvedLocaleScope — a load carrying locale-scoped datasets and no config.locale warns naming every dataset it let through and the config key that would make the scope take effect. This is exactly what Seed.env is authorable but never enforced: the app seeding path never sets SeedLoaderConfig.env, so env: ['dev'] seeds into production too #4704 did not have: the axis can be unwired, it cannot be silently unwired.
  2. The liveness ledger records seed.locale as experimental, not live — consumer side cited, producer gap spelled out in the row. Publishing live on a correct-but-insufficient consumer pointer is the liveness 台账把「消费端存在读取代码」当作 live 的证据,会漏掉「没有任何生产者传值」的死键(Seed.env 即如此) #4837 falsehood the producer field exists to prevent, and seed.env is the specimen it was built from. Re-classify to live with a producer pointer when the wiring lands.

Follow-up the PM should file: wire config.locale from the runtime's configured locale at the seed-load call sites in packages/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 through scripts/pm/os-verify-lock.sh.

Dependency closure and generated artifacts

pnpm --filter '@objectstack/metadata-protocol^...' build --concurrency=2
  os-verify-lock: VERDICT command-exit 0 · held the lock 195s

pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated --fix
  ✗ 3 of 15 artifact(s) stale:  skill references · content/docs/references/** · liveness/state-counts.md
  --fix: regenerating 3 of the 3 stale artifact(s).
    ✓ gen:skill-refs   ✓ gen:docs   ✓ gen:liveness-counts
  os-verify-lock: VERDICT command-exit 0

Tests

pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 \
    src/seed-loader-locale-scope.test.ts src/seed-loader-env-scope.test.ts
  Test Files  2 passed (2)
       Tests  29 passed (29)

pnpm --filter @objectstack/spec test && pnpm --filter @objectstack/spec typecheck
  Test Files  460 passed (460)
       Tests  12778 passed (12778)
  os-verify-lock: VERDICT command-exit 0 · held the lock 354s

pnpm --filter @objectstack/metadata-protocol test
  Test Files  170 passed | 2 skipped (172)
       Tests  2448 passed | 10 skipped (2458)

pnpm --filter @objectstack/metadata-protocol typecheck
  os-verify-lock: VERDICT command-exit 0

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 early return true inserted above the normalizeLocaleTag(locale) read in datasetAllowsLocale, i.e. the locale predicate neutered while everything else stands.

HEAD blob: 7772349ec06deaa63b19a54a0b7f42011fcbfe5d
--- after: marker landed on disk ---
marker occurrences: 1
mutated blob: 5ea1048d3883b16f98129a8bdb00442c7053aa34
--- MUTANT LEG: expect RED ---
mutant-exit=1
 Test Files  1 failed (1)
      Tests  8 failed | 8 passed (16)
--- RESTORE LEG: prove the tree is back ---
restored blob: 7772349ec06deaa63b19a54a0b7f42011fcbfe5d (HEAD: 7772349ec06deaa63b19a54a0b7f42011fcbfe5d)
marker absent: OK
--- RESTORED LEG: expect GREEN ---
restored-exit=0
 Test Files  1 passed (1)
      Tests  16 passed (16)

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/objectstack at d621dec over 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 in seed-loader.ts moved three anchors cited from content/docs/permissions/system-context.mdx. Repaired mechanically with the gate's own --fix (3 anchors rewritten); gate now OK — 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 returned PREREQUISITE 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=272 successful, 72 total) and re-ran: all three green. check:type-check-debt OOMed 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-test refuses on a shallow clone — cannot read the positive control at 621a487607881c66b2899b7e3477115229a156b4. git fetch --deepen 500 did 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-bytes green, 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 by gen:skill-refs. It moved because data/seed.zod.ts now imports LocaleSchema from system/translation.zod.ts, which enlarges the data skill's reference closure by that one module. Two readings, as the published-skills rule requires:

  • changed file: 66 → 67 lines (+1, generated)
  • whole published skills/ package, sum of every SKILL.md: 6858 → 6858 lines (unchanged — no SKILL.md is 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 侧 filterByLocalefilterByEnv 合取——两条都通过才装载。

为什么改 — 一个要同时发中英文演示数据的应用,今天只能在装配 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

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>
…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>
@github-actions github-actions Bot added the size/l label Sep 7, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/spec, touching 11 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/spec/authorable-surface/data.json, packages/spec/liveness/seed.json, packages/spec/liveness/state-counts.md), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/seed-data.mdx (via SeedSchema (symbol, a top-level const))
  • content/docs/data-modeling/validation.mdx (via SeedLoaderService (symbol, a top-level class))
  • content/docs/protocol/objectql/state-machine.mdx (via SeedLoaderService (symbol, a top-level class))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via SeedLoaderService (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/authorable-surface/data.json, packages/spec/liveness/seed.json, packages/spec/liveness/state-counts.md) — pages documenting those are invisible to this run
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json bc0ac1d1f5775659e72ad35452c463443a71f1bepackageMentionDocs.

Which tree this was computed on

This run read content/docs from cd8f5b3049d1679d538e2d115c459e4afa900265 — the merge of head ba564fccb79e9bd4c8aee6cf8f13a8bda338aea2 into base bc0ac1d1f5775659e72ad35452c463443a71f1be, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs bc0ac1d1f5775659e72ad35452c463443a71f1be → pass the list as
args.docs, on the commit named under Which tree this was computed on.

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Contract review PASS at CONTRACT_REVIEW_TIER#16510 comment 5571519820 (director seat, summon #17, session_01XesLUWmuhjuRwmU618AZ1M). needs:contract-review stripped from this PR in the same stroke (the card never carried it), read back. Route: GOVERNED (skills/objectstack-data/references/_index.md, 1 of 16; the generated-surface exception did not lift it) ⇒ stays draft, needs-user-decision on the PR, human merge. The author identity is one of the two authorised accounts, so this PR is assigned to hotlong in place of a review request to it; review requested from os-zhuang.

维护者速读(终稿)

改了什么:种子数据集 Seed 新增可选的 locale 过滤轴(BCP-47 标签数组),与既有的 env 轴并列;装载器按「env 且 locale 都通过」才装载。不写 locale 的数据集行为字节不变。SeedLoaderConfig.locale 是装载时传入的目标语言。同 PR 更新 spec 台账(该键记为 experimental)、文档与 changeset(@objectstack/spec / @objectstack/metadata-protocol 各 minor)。

为什么改:您在 #16510 立的卡:一个要同时发中英文演示数据的应用,今天只能在装配 config 时用自造的环境变量二选一,选择被烤进 dist、另一语言的行留在库里。把过滤放到装载层,两件事都自然消失。

风险与代价(含回滚):纯新增可选键。唯一未闭合的是 runtime 还没把配置里的语言传给装载器(#16595 已立,Blocked-by: #16510),所以默认启动路径上这条轴暂时空转——但不是静默:带 locale 的数据集在没收到 config.locale 时装载器会点名告警,并写明该传哪个键。回滚 = revert 本 PR,无数据迁移。

席位意见:契约复审达档 PASS(5571519820)。接受集扩大方向正确;locales / language / languages 是拒收带指引而非双拼写;标签匹配只做大小写折叠、不做 zhzh-CN 这类宽容回退;版本级别正确。受管面只有 1 行由 gen:skill-refs 生成的技能索引行,生成物例外因生成器未申报输出集而未生效,按 Prime Directive #14 归您人工合并。

你要做的:人工合并 PR #16592(受管面,不走队列)。是或否。


Generated by Claude Code

@hotlong
hotlong requested a review from os-zhuang September 7, 2026 13:44
@os-zhuang
os-zhuang marked this pull request as ready for review September 7, 2026 13:47
@os-zhuang
os-zhuang enabled auto-merge September 7, 2026 13:47
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 7, 2026

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

出队处置(PM 审计)

队列在 14:11:43ZMERGE_CONFLICT 把本 PR 踢出。不是 CI 问题——踢出前 46 个 check 全部 success/skipped,含 Governed Surface Queue Guard(13:48:00Z success)。按 platform-readings.md 的两则成因,MERGE_CONFLICT = 兄弟卡抢先落地。

冲突签名

零配额本地试合并,git merge-tree --write-tree origin/main refs/pull/16592/head:

merge-base   f2f6684cd5e93032ed5d82a1848784b6026a8a03
origin/main  8341ed21cededfc1d4f953c54b71e76c26628ee9  (merge-base 之后 27 个提交)
PR head      35929cd379c6f35eaebe2a414b24737712d5cf4c  (未动过)

CONFLICT (content): content/docs/permissions/system-context.mdx
Auto-merging scripts/engine-double-contract.pinned.json      ← 自动合并成功,非冲突

冲突文件只有一个,两侧的体量差是这次处置的全部关键:

改动 内容
本 PR +1/−1 行号锚 metadata-protocol/src/seed-loader.ts:2032(及 :19421944)顺延为 :2083(及 :19931995)——check:system-context-census --fix 机械改出来的
origin/main +141/−113 #16614 docs(devx): anchor the isSystem census page by symbol, not by line number,把整页从行号锚迁移到符号锚

即:抢先落地的那张卡,拆掉的正是本 PR 那一行所属的整套锚点机制。本 PR 的 +51 行 seed-loader.ts 之所以会碰这个文件,只因为旧机制按行号记账;新机制按符号记账,而这 +51 行没有移动任何符号。

⚠️ 该路径带 merge=os-regen(.gitattributes:154),本 PR 触及的 os-regen 路径共三条——另两条是 skills/objectstack-data/references/_index.md(:155)与 packages/spec/liveness/state-counts.md(:141)。git merge-tree 只答默认文本合并、不套 merge driver,所以上面这份读数对 os-regen 路径系统性低报,不得读作「只有这一处要处理」。

处置路由

landing-operations.md A + B:dirty ⇒ 先同步 origin/main、整体重生成、再重挂;走机械化的 bash scripts/pm/os-regen-merge.sh,禁 rebase 与 force-push。已回原实现席位返工(本卡第 1 轮返工,上限 2 轮)。

预期解法是取 main 侧整文件后按新符号锚重跑 check:system-context-census,极可能对该文件净零改动——但这是预期不是裁决,以席位在树上跑出来的读数为准。

落地路径提醒

本 PR 属受管面(skills/objectstack-data/references/_index.md 一行生成物),契约复审已 PASS(评论 5571551413)。返工推新提交后 head 变化,原批准可能被分支保护自动 dismiss,届时需要重新批准并重新入队。这一步不在我权限内:受管面 PR 我不批准、不入队、不合并。


Generated by Claude Code

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

派发记录

返工已派出 · 2026-09-07T14:20:06Z

轮次 返工第 1 轮(上限 2 轮)
会话 session_01U4d9i4ZmjdYG2Wiq66Rx35 · mode:cloud
环境 env_01XuYuAYgtcEcgtZ2nrHPi7v
检出 / 推送 claude/issue-16510-seed-locale-axis(在既有 4 个提交上追加,非新建分支)
标签 domain:spec issue-16510 pm-dispatch rework
派发词要点 os-regen-merge.sh 四步序;禁 rebase 与 force-push;禁在 MERGE 状态跑 gen:schema;不动本卡实现;不转 draft、不入队、不挂 auto-merge、不合并

更正上一条评论的一处措辞。 我写的是「已回实现席位返工」,不准确:原实现会话 session_013r78utTbiWqxghcuRJxfZf 虽仍存活(IDLE、未归档),但本会话没有到它的直接投递通道,所以派的是新席位、新容器、从该分支重新检出。差别只在现场:新席位没有原 worktree 里已构建的产物,首轮构建耗时会更长;派发词是自足的,交付判据不变。

派发词里对冲突解法给的是建议路线(取 main 侧整文件后按符号锚重跑门禁),明确标注可换、以席位在树上跑出的读数为准——不是裁决。

我继续订阅本 PR 盯到 MERGED 或关闭。


Generated by Claude Code

…ed-locale-axis

# Conflicts:
#	content/docs/permissions/system-context.mdx

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

@os-zhuang 提醒一句,免得白等或白入队一次:14:40:03Z 这次批准落在未变的 head 上,清不掉冲突。

两次批准 commit_id 相同   35929cd  (13:47:06Z · 14:40:03Z)
head                     35929cd  仍是 4 个提交,返工席位尚未推送
base                     ffca0df  未动
mergeable_state          dirty    ← 未变

dirty 是队列入口的否决项,与批准数无关——现在重新入队会再被踢一次,签名还是 MERGE_CONFLICT

真正的闸门是那次同步提交。返工席位 14:20:06Z 派出(见上一条派发记录),现在约 20 分钟,还在克隆与首轮构建的窗口内——这张卡的门禁要跑全工作区构建,首轮偏慢属正常。我 15:10Z 复检。

推送落地后可能还要请你再批一次:若本仓分支保护开了 dismiss stale reviews,新 head 会作废现有批准。这一步我做不了——受管面 PR 我不批准、不入队、不合并。


Generated by Claude Code

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

返工验收(PM 复核)· 第 1 轮 ACCEPT

席位推了同步提交后被用量墙掐断(session_01U4d9i4ZmjdYG2Wiq66Rx35,rate_limit_info.status: rejected,重置 15:50Z),所以它欠的那条返工说明评论没写出来。代码已经落地且验证通过——下面这份读数是我在 GitHub 侧亲核的,不是转述席位自述。

状态

head              ba564fc  (5 个提交,新增 1 个同步 merge 提交)
base              8341ed2
mergeable_state   clean    ← 从 dirty 恢复
draft             false
CI                34 个 check,全部 success/skipped

关键门禁逐条(新 head 上复跑,非沿用旧读数):

Check 结论 完成时刻
Governed Surface Queue Guard success 15:02:24Z
Lint & Repo Gates success 15:26:42Z(最后一个完成)
Spec property liveness success 15:02:31Z
Check Changeset success 15:02:34Z
Type Check · workspace / source gates / consumer gates / debt ledger 4/4 success 15:04–15:18Z
Test Core (1–6 of 6) 6/6 success 15:11–15:23Z
Dogfood Regression Gate (1–3 of 3) + Verify CLI 4/4 success 15:09–15:12Z
Build Core · Build Docs · Temporal Conformance (live PG + MySQL) 3/3 success 15:07–15:10Z
Vercel(commit status) success 15:14:26Z

冲突是怎么解的 —— 用文件面证实,不是靠席位声明

我在派发词里给的建议路线是「对 system-context.mdx 取 main 侧整文件,再按新符号锚重跑门禁」,并注明可换、以树上读数为准。实际落点可以从文件面直接读出来:

返工前   16 个文件  +660 / −6
返工后   15 个文件  +659 / −5

少掉的正是 content/docs/permissions/system-context.mdx —— 它已完全不在变更文件列表里。也就是说该文件净零改动:#16614 把整页迁到符号锚之后,本 PR 那 +51 行 seed-loader.ts 不移动任何符号,新机制下无锚可改。预期与结果一致,且这里的判据是 GitHub 的文件列表,不是我的预测。

本卡实现未被动过

剩下 15 个文件与契约复审时逐字相同:seed.zod.ts +33、seed-loader.zod.ts +18、seed-loader.ts +124/−2、seed-loader-locale-scope.test.ts +323、seed.test.ts +57,加文档、changeset、liveness 台账、authorable-surface、engine-double 台账钉,以及 skills/objectstack-data/references/_index.md 那 1 行生成的登记行。契约复审(评论 5571551413)的审查对象未变,PASS 依然成立;受管面读数也未变,仍是 1 行生成物。

更正我先前的一处预判

我此前说「新 head 可能作废现有批准」。实测没有作废:两次批准现在都仍是 APPROVED(未转 DISMISSED),挂在旧 commit 35929cd 上——本仓分支保护没有开 dismiss stale reviews。所以不需要你再批一次。

现在卡在哪

绿、干净、已批准,needs-user-decision 仍在,PR 自 15:26:42Z 起全绿约一小时未落地。按契约复审给的路由,本卡是受管面、人工合并(评论 5571551413:「人工合并 PR #16592(受管面,不走队列)。是或否。」)。

@os-zhuang 就差这一步。我做不了:受管面 PR 我不批准、不入队、不挂 auto-merge、不合并。

合并后 #16595(runtime 三个 seedLoader.load 调用点接 config.locale、台账翻 live)与 #16596(跨语言残行对账)解除 Blocked-by,我会去收口。我继续盯到 MERGED 或关闭。


Generated by Claude Code

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

返工第 1 轮:同步 origin/main + 重生成 — 冲突已解,dirty 已脱离

新 head ba564fcc(原 35929cd3),分支 claude/issue-16510-seed-locale-axis,merge 提交追加在原 4 个提交之上(现 5 个)。⛔ 无 rebase、无 force-push:推送是 35929cd3..ba564fcc 的快进,reviewer 的 checkout 与已有评审线程原样保留。

同步到哪个 main sha

同步目标 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(及 :19421944:19931995),是原会话跑 check:system-context-census --fix 机械改出来的行号锚
  • origin/main55bbd92(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.mjsscripts/isystem-census.mjscontent/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 +3seed.mdx +1manifest.mdx +1authorable-surface/data.json +2liveness/state-counts.md +2/−2skills/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

hotlong commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

终态补记(上一条评论写于 CI 在跑时,状态一节已被此条取代)

新 head ba564fcc 的 CI 已全部结束:34 个 check —— 32 success / 2 skipped(Console Pin GatePacked-tarball smoke (opt-in))/ 0 failure。含 Governed Surface Queue Guard success(15:02:24Z)、Lint & Repo Gates success、四个 Type Check · 分片全绿、Test Core 6 分片全绿、Dogfood Regression Gate 3 分片全绿。commit status:state: success

mergeable_state = clean 派发令的完成判据已满足:dirtyclean,冲突消除,原本跑绿的门禁在新 head 上复跑全绿。

一处更正:上一条评论按分支保护的一般行为预判 os-zhuang 的批准会被新 head dismiss —— 实际没有被 dismiss,clean 即表明评审要求仍然满足,无需重新批准。

我这边到此为止:未入队、未挂 auto-merge、未合并、未转 draft。受管面(skills/objectstack-data/references/_index.md 一行生成物)按 Prime Directive #14 归人工合并,交回车道 PM 的落地窗口。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants