Skip to content

fix(spec): 基线编码统一为裸字符,写盘端与文件共用一个序列化器 (#5990) - #6096

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5990-import-baseline-encoding
Aug 7, 2026
Merged

fix(spec): 基线编码统一为裸字符,写盘端与文件共用一个序列化器 (#5990)#6096
os-zhuang merged 1 commit into
mainfrom
claude/issue-5990-import-baseline-encoding

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #5990

选路:方案 2 —— 把基线统一为裸字符,并留一次性转换提交

分诊要求二选一并在 PR 里写明选择与判据,这里明写。

判据 = 该 ratchet 文件的设计意图:只减不增、变化应当显眼。 两条路都能消除震荡,但只有方案 2 让这个文件与仓内其余部分服从同一条规则 —— 而「显眼」恰恰是靠复核者能一眼认出反常来实现的,让唯一一个文件用只有它自己用的编码,等于每次都要求复核者先记住这条例外。

选路的实测依据(不是偏好,是数据)

在本 PR 的 base(e3ef52b5a)上逐文件量过:

度量 数值
仓内 tracked .json 总数 305
其中带非 ASCII 字符的 151
其中用 \uXXXX 转义的 1(就是本单这个文件)

也就是说,issue 标题里的「仓内 ASCII 转义编码惯例」这个前提本身是不成立的 —— 不存在这样一条惯例,这个文件是全仓孤例。同类的手工 shrink-only 棘轮(dual-source-exports.baseline.jsonreact-declaration-parity.baseline.jsontest-typecheck-debt.jsonvariant-docs.json)和全部生成物(api-surface/authorable-surface/json-schema.manifest/spec-changes.json)一律落裸字符

更直接的一条:packages/spec/scripts/lib/sharded-artifacts.ts 已经有一个名为 serializeShard() 的函数,注释写着 "Canonical bytes of every shard: 2-space JSON, trailing newline",实现就是裸 JSON.stringify(doc, null, 2) + '\n'规范字节的写法在仓内已经有名有姓地确立过了,而且是裸字符。 走方案 1 等于让 build-docs.ts 去生产一种全仓没有第二处在生产的编码。

一次性代价如实记:本 PR 的基线 diff 是 137 增 / 137 删。这是一次,换永久干净。

两侧一起改,没有只改一侧

分诊的 ⛔ 说得很明确,所以修法是把「写盘端的编码」和「文件的编码」从两个可以各自漂移的事实,变成同一个函数:

  1. IMPORT_BASELINE_COMMENT 与新增的 serializeImportBaseline() 移入 packages/spec/scripts/lib/docs-import-surface.ts。移出 build-docs.ts 是必要的而不是顺手 —— 那个脚本 import 即执行,常量留在里面就没有任何测试能读到它。
  2. build-docs.ts--update-import-baseline 写盘改为调用 serializeImportBaseline(gaps),不再内联 JSON.stringify
  3. 基线文件由生成器重写,内容 100% 来自生成器(见下一节的 parse 层比对)。

基线 diff 的性质:确实只有那一处有意变化

该文件是 NOT_DRIVER_MANAGED 的手工棘轮,所以按 #6069 的口径做了 parse 层比对:

$ git diff --stat packages/spec/docs-import-surface.baseline.json
 packages/spec/docs-import-surface.baseline.json | 274 ++++++++++++------------
 1 file changed, 137 insertions(+), 137 deletions(-)

entries unchanged: True   count 136
comment changed:   True
top-level keys:    ['_comment', 'entries'] -> ['_comment', 'entries']
  _comment replace: '.json' -> '/'

136 条 entries 在 parse 层与 origin/main 逐条相同,137 行里 136 行是编码翻转,唯一的语义变化是 _commentapi-surface.json -> api-surface/ —— 也就是 #6069 有意留给本单的那处过期路径(该文件在 #5837 分片后已不存在)。按分诊要求,编码归一化和路径修正落在同一个有意的 diff 里。

字节层复核:转换后 138 个裸非 ASCII 字符、0 个 \uXXXX、0 个控制字节、单个结尾换行。

修复的复现证明:命令现在是幂等的

这是本单真正的验收 —— 修复前每跑一次就产生一次满额 churn,修复后第二次跑是空操作:

$ sha256sum ... ; tsx scripts/build-docs.ts --update-import-baseline ; sha256sum ...
before: 0c89499f1cfb11424e9fcbeecca60197ada70d727c51694f5d75589368a47034
after:  0c89499f1cfb11424e9fcbeecca60197ada70d727c51694f5d75589368a47034
IDEMPOTENT: YES

防震荡的 pin(新增 4 个用例)

packages/spec/scripts/docs-import-surface.test.ts 新增 docs-import-surface.baseline.json bytes 一组。核心是字节级往返:读 committed 文件 -> JSON.parse -> 用同一个 serializeImportBaseline() 重新序列化 -> 要求字节相同。它不需要 build、不需要 json-schema/ 树(用文件自己的 entries 重序列化,比的是编码而不是 gap 分析),一次钉住编码、缩进、键序、结尾换行,外加 _comment 的新鲜度。

_comment 新鲜度这一条值得单独说:该字段不被任何门禁比对(entries 才是判据),这正是 #6069 能改了源常量却留下过期文件的原因。以前不值得为一行散文去红,因为修它要付 137 行噪声;编码统一之后那次重跑就是 1 行 diff,所以现在值得让它红。 这是本单顺带买到的东西。

另有一条把 #6069 那类漂移钉在源头:IMPORT_BASELINE_COMMENT 必须含 API_SURFACE_DIR_NAME + '/'、且不得含 LEGACY_MONOLITH_NAMES[API_SURFACE_DIR_NAME](即 api-surface.json)—— 两个拼法都从 sharded-artifacts.ts 这个同时拥有它们的模块里取,所以将来再分片一次也不会悄悄留下描述旧布局的散文。

反向验证(方向是事前预测的:三条回归路径都应转红)

预测:红。三种可能的复发方式各试一次,并确认红的是对应的那几条而不是全体:

反向操作 预测 实测
(a) 把文件重新编码回 \uXXXX(还原被删的那条肢体) 字节往返 + 裸字符两条红;_comment 相关两条绿 ✅ 完全一致(2 failed / 15 passed)
(b) 把 _comment 改回 api-surface.json _comment 新鲜度 + 字节往返红;裸字符与常量那条绿 ✅ 完全一致(2 failed / 15 passed)
(c) 在 serializeImportBaseline() 里加回 ASCII 转义包装(写盘端回归) 字节往返 + 裸字符红 ✅ 完全一致(2 failed / 15 passed)

(c) 是关键的一条:它证明这个 pin 管的是两侧,而不是只把文件钉死、任由写盘端漂走。三次反向验证后已还原,grep REVERSE-VERIFICATION 无残留。

Changeset:skip-changeset 标签,不写具名 patch

#6049 / #6057 的口径实测了发布面,而不是凭印象:

packages/specfiles 白名单是 dist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json。本 PR 改动的 4 个文件逐个比对:

改动路径 是否随包发布
packages/spec/scripts/build-docs.ts 否(scripts/ 不在白名单)
packages/spec/scripts/lib/docs-import-surface.ts
packages/spec/scripts/docs-import-surface.test.ts 否(check:published-files 本身就禁止测试文件进产物)
packages/spec/docs-import-surface.baseline.json

dist/json-schema/api-surface/authorable-surface/json-schema.manifest/content/docs/references/ 全部零 diff(gen:schema 与写盘模式的 build-docs 都跑过,232 个生成文件原样重写)。发布面 delta 为零,即本 PR 不发布任何东西,按 pr-automation.yml 的路线 2(preferred)取标签而非空 changeset。

验证

pnpm --filter @objectstack/spec gen:schema                        # 0 tracked diff
pnpm --filter @objectstack/spec check:docs                        # OK — 136 accepted gap(s);232 generated files in sync
pnpm --filter @objectstack/spec typecheck                         # OK — tsc --noEmit + check:test-typecheck
pnpm --filter @objectstack/spec test                              # OK — 326 files / 8336 tests passed
pnpm lint                                                         # OK — exit 0
pnpm check:published-files / check:merge-driver /
     check:doc-authoring / check:docs-audit-scope / check:nul-bytes  # OK — 全绿

控制字节自查(本 PR 的散文与注释多处提到转义):node scripts/check-nul-bytes.mjs 绿,另按门禁盲区做了 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' 逐文件自查,四个文件均无命中 —— 文中的 \uXXXX 一律是转义文本,不是裸字节。

对同文件后续单的影响

#5853 / #5553 / #5059 都排在 build-docs.ts 本单之后。三者都不碰基线写盘路径(分别是 getCategoryTitle 大小写、JSDoc 段落切断、参考页正文被内部注释顶替,全在页面渲染侧),语义上互不相干;本 PR 从 build-docs.ts 净删 12 行、只在 import 与写盘一行处留下改动,合并冲突面极小。要说影响,是轻微变容易:那三单一旦顺带动到 IMPORT_BASELINE_COMMENT,或它们的改动让某个 schema 的 gap 增减而需要重跑 --update-import-baseline,拿到的都会是 1 行 diff 而不是 137 行。


🤖 Generated with Claude Code

https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5

`--update-import-baseline` 用 JSON.stringify 写裸字符,而仓内
`docs-import-surface.baseline.json` 落盘是 \uXXXX 转义。两者 JSON.parse
后逐条等价,门禁只读 parse 后的 entries,所以一直是绿的 —— 代价全落在复核:
每跑一次该命令,136 条 entry 行被原样重写一遍,真实的 1 行决定被埋掉。

选路 2(基线统一为裸字符 + 一次性转换提交),判据是该 ratchet 的设计意图:
只减不增、变化应当显眼。实测支撑 —— 本仓 305 个 tracked .json 中 151 个带
非 ASCII,只有这一个用转义;同类手工棘轮与全部生成物都落裸字符,且
lib/sharded-artifacts.ts 的 serializeShard 已把「2 空格 + 结尾换行」的裸字符
写法确立为分片规范字节。选路 1 会让这个文件成为全仓唯一的转义孤例。

两侧一起改,不只改一侧:
- IMPORT_BASELINE_COMMENT 与新的 serializeImportBaseline() 移入
  lib/docs-import-surface.ts,写盘端改为调用它;
- 基线文件由生成器重写(内容 100% 来自生成器),entries 在 parse 层与
  origin/main 逐条相同(136 条不变),唯一语义变化是 #6069 有意留下的
  过期路径 api-surface.json -> api-surface/;
- 新增字节级往返 pin:读committed 文件 -> JSON.parse -> 用同一个序列化器
  重新序列化 -> 要求字节相同。它同时钉住编码、缩进、键序、结尾换行,以及
  _comment 的新鲜度,任一侧再分叉即红。

复现修复:连续两次跑 --update-import-baseline,文件 sha256 不变(修复前每次
都产生 137 增 137 删)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 1:11am

Request Review

@os-zhuang os-zhuang added skip-changeset PR has no user-facing published change; bypasses the changeset gate and removed tests tooling labels Aug 7, 2026 — with Claude
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review August 7, 2026 01:38
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 830f3f1 Aug 7, 2026
49 of 52 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5990-import-baseline-encoding branch August 7, 2026 01:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants