refactor(spec): 三个热点生成物按 category / entrypoint 分片,拆掉合并队列的串行税 (#5837) - #6069
Draft
qq9340100 wants to merge 3 commits into
Draft
refactor(spec): 三个热点生成物按 category / entrypoint 分片,拆掉合并队列的串行税 (#5837)#6069qq9340100 wants to merge 3 commits into
qq9340100 wants to merge 3 commits into
Conversation
`merge=os-regen`(#4675)只在本地 git 生效;合并队列在服务端重建 PR 时不跑自定义 merge driver,所以两个都动过 `authorable-surface.json`(310KB / 7941 行排序数组)的 PR 在队列里是纯文本冲突,第二个必然被踢。内容本来就按 `<category>/<Def>[:<prop>]` 天然分组,按它拆开,动不同 category 的两个 PR 从此文件不相交。 S1 布局 + 生成器:新增 `scripts/lib/sharded-artifacts.ts`(布局、writer、完整性检查、 历史基线读取一处收口);`gen:schema` 写 `authorable-surface/` 与 `json-schema.manifest/`,`gen:api-surface` 写 `api-surface/`(`.` → `root.json`)。 S2 门禁 + 锚点:所有闸读**整个目录**当一个集合,ratchet 语义逐条不变; `authorable-surface.base.json` 保持聚合单文件(只有 `--update-base` 写它,不在 churn 路径上,且 `baseRev` 是整个 surface 的一个 commit),其比对改读基线 commit 的分片并 聚合,authenticity 判据两半未动。 S3 消费方:build-docs / build-spec-changes / root-index.test / release-spec-changes.sh / check-published-files / package.json `files` 全量改点。 S4 收尾:`.gitattributes` 与 `regen-artifacts.mjs` 路由到分片路径,单体文件退役, AGENTS.md / 两个内部 skill / lint.yml 注释 / ADR-0059 与 ADR-0087 路径修订、 `docs/spec-generated-artifact-sharding.md`。 新增 `scripts/sharded-artifacts.test.ts`(23 例):局部性与幂等各成常驻断言 —— 「只改 category X → 只有 X.json 变化,其余分片逐字节不变」,以及「同一 key 集合重写 零文件」。#5976 的 def key 撞名守卫按剥后缀 schema 名判定,与输出路径无关,其反向 验证测试同步改为断言分片目录名(否则该断言会变成空跑)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
含 #6000(#4723:check:docs 不再以 gen:schema 开头,并在 check-generated 的 GATED 表 里声明 `readsSchemaTree`),S2 的门禁构成改动叠在它之上而不是并排。 冲突一处,packages/spec/scripts/check-generated.ts 的 GATED 表:两侧都改了 `check:docs` 与 `check:api-surface` 两行。取并集 —— 保留 #6000 的 `readsSchemaTree: 'check:authorable-surface'`(它声明的是 check:docs 依赖 check:authorable-surface 先生成 json-schema/ 树,与分片无关),同时保留本分支把 artifact 标签从 `api-surface.json` 改成 `api-surface/`。 生成物按 os-regen 纪律处理:merge 提交先落地(MERGE 态跑 gen:schema 会被 #5851 守卫 拒绝),随后整体重生成并补跑 gen:openapi(#5371)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
merge 带进来的 #4723/#6000 新增了几处描述「check:docs 曾重写两个 tracked 投影」的散文, 它们指的是当前机制而不是历史叙述,所以路径随分片一起更新:build-docs.ts、AGENTS.md、 check-regen-pending.mjs、check-generated-ledger.test.ts、schema-tree-freshness.test.ts。 另外 build-schemas.ts 里把 api-surface 说成「另一个见证者」的两处、以及 migrations/spec-changes.ts 的 Release 工作流描述,同属活文档。 ⛔ 有意不动的:所有 `src/**` 与测试里追述「当年这个 key 怎么出账」的历史叙述 (http-server.zod.ts / theme.zod.ts / data-engine.zod.ts / retry-policy.* / …), 以及 ADR 正文 —— 后者按 AGENTS.md #13 用顶部 Amended 行声明路径迁移,不改写决策记录。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
This was referenced Aug 6, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5837
维护者 2026-08-06 已拍板「分片 + 缩盘子」。本 PR 一次性落 S1-S4,不拆多 PR —— 分片布局的中间态(分片产物 + 未改的门禁 / 未改的消费方)在任何提交点都是坏的。
前提复核(在
origin/main上实测,开工前)authorable-surface.json311KB / 7941 keys、json-schema.manifest.json49KB / 1610 defs、api-surface.json157KB / 16 entries / 4391 exports.gitattributes把三者逐个映到merge=os-regen,单一事实源是scripts/regen-artifacts.mjs,check:merge-driver双向对账schemaNameFromExportKey的 category 结构< category >/< Def >[:< prop >];category 就是build-schemas.ts迭代的 namespace 小写名,14 个非空单子诊断的因果也成立且是本 PR 的全部理由:
merge=os-regen(#4675)只在本地 git 生效,合并队列在服务端重建 PR 时不跑自定义 merge driver,所以两个都动过这些文件的 PR 在队列里是纯文本冲突。布局
packages/spec/authorable-surface.jsonpackages/spec/authorable-surface/< category >.jsonpackages/spec/json-schema.manifest.jsonpackages/spec/json-schema.manifest/< category >.jsonpackages/spec/api-surface.jsonpackages/spec/api-surface/< entry >.json.→root.json(16 片)有意维持单体,并各自写了理由:
spec-changes.json(按版本键控)、api-surface-signatures.json(1.3KB)、authorable-surface.base.json(见下)。布局、writer、完整性检查、历史基线读取一处收口:
packages/spec/scripts/lib/sharded-artifacts.ts。保持不变的那条不变量
这是分片保语义的全部依据:删掉一整个分片文件 = 删掉它的 key,checks (a)/(c) 看到的缺失 key 与从前删单体文件里的行时一模一样;没人重生成的分片报陈旧而不是被跳过;#4662 的逐字节规范形式比对现在是逐分片做的,手改因此还能被指名到文件(严格变紧,不是变松)。
分片让两类新违规成为可能,两条都做成硬报错:分片的
category/entry字段必须与文件名一致;分片里的每一条必须属于该 category。少了它们,一个分片可以替别的 category 背书,而按 category 路由的 writer 会在下一次运行时把它当作「没人做过的删除」丢掉。锚点(#5304 / #5235 / #5358 / #5370):判据一个字没弱化
authorable-surface.base.json保持聚合单文件,这是有意的不对称:--update-base才会写(check:authorable-surface在--check模式下也会重写authorable-surface.base.json—— 一次纯核验会改工作区,且任何无关 PR 都能因此静默推进删除门的锚点 #5358),从不在 churn 路径上,分片买不到任何冲突收益;baseRev是整个 surface 的一个 commit。逐分片复制会允许出现「不同分片镜像不同 revision」的树 —— 任何上游 commit 都没有过的状态。它喂的比对改为:读基线 commit 的分片并聚合。authenticity 判据两半原样保留 ——
baseRev是origin/main的祖先,且它记录的 keys 就是该 commit 的 surface。--update-base只许前移(#5370)、MERGE 态拒绝(#5851)的语义未动一行,其 8 例测试在新布局下全绿。本 PR 重新锚定了一次(
⚓ authorable-surface.base.json refreshed to 44106d9e8448 (7941 keys)),原因是它的 description 字面提到authorable-surface.json这个即将不存在的路径 —— 而该文件的规范形式检查把 description 的每个字节都算进去(脚本注释里写着「改这段文字不是改注释」)。前移方向已由assertAnchorMovesForward自证:fc5f536a13b3→44106d9e8448。历史布局的那一处读取
readShardedKeysAtRev在给定 revision 早于本次迁移时读单体。这不是消费方宽容:aggregateCategoryShards读,它只认分片布局。与
release-spec-changes.sh解包「上游 tarball 实际携带的那种形状」同类。该分支会自报家门(ℹ️ … predates the split),并在没有分叉早于迁移点的在飞分支后自行退役。验收判据 1 的可证明形式:文件级互斥
单 PR 内无法演示合并队列,其机械等价物是关于文件的陈述,那个可测。
实测(在本工作树):给
ui分类的一个 schema 加一个 key,重跑gen:schema—在
main上,同一次编辑重写的是一个 310KB、每个 spec PR 都会重写的文件。固化为常驻测试
packages/spec/scripts/sharded-artifacts.test.ts(23 例):X.json不同,其余逐字节相同;两个「PR」触碰的文件集合交集为空;两者的并集恰好等于按 key 并集重生成的结果(该「冲突解决」是 no-op);written为空数组。仅比字节相等不够 —— 重写相同内容仍然会动 mtime,而下游每一处「陈旧吗?」的比较都离一次 mtime 读只有一步;./root撞名拒绝)、历史基线读取四向。生成器的端到端幂等由
check:authorable-surface/check:api-surface每次 CI 自证(两者重新推导产物并逐字节比对工作树),不在这里重跑一次 7s 的全量构建去重复证明。ratchet 语义:反向验证,方向先预测后跑
预测的是标准方向(装回违规 → 门变红),三档实测都如预测:
①
src删 key、分片不动 → 红(check (a),silent-strip 类,始终致命)② 跨分片:单个分片手改藏不住。删
ui/View:formViews并手删authorable-surface/ui.json里那行之后,门在另一个分片上开火 —— 因为ViewSchema也经api/GetUiViewResponse发布:这条是分片布局独有的加固读数,值得单独记一笔:门读整个目录当一个集合,所以在一个分片里删行不会让另一个分片忘记它。
③ 两个分片的行都手删 → 仍红(check (c),按 merge base
44106d9e8448判定,该 commit 早于分片,走的正是上面那条历史读取路径):一个诚实的补充读数:第一次探针选的
ui/RecordPathProps事后被门自己判为「不可从 25 个元数据根到达」,于是删除合法地被豁免(证明 2)。那是我选的 key 的性质,不是门的松动 —— 换成可达的ui/View后才拿到上面 ② ③ 两条红。这一段没有按模板凑「应当变红」,而是照实记。#5976 的 def-key 撞名守卫
守卫本身一行未动:撞名按剥后缀的 schema 名(
shared/HttpMethod)判定,那才是选定 def key 的东西,与它之后落进哪个分片文件无关;它仍在两个 ratchet 之前运行。其反向验证测试(
def-key-collisions.test.ts,沙箱真跑)有一处必须改,否则会变成空跑:它断言expect(output).not.toContain('json-schema.manifest.json')(证明守卫在 ratchet 之前退出)。分片后没有任何输出会打印那个字符串,该断言会无条件通过 —— 一个假装成 pin 的 phantom check。改为断言分片目录名,并补了一条正向断言(输出里确实出现shared/HttpMethod),两条都实跑绿。S3 消费方普查:零遗漏声明
git grep -n "authorable-surface\.json\|json-schema\.manifest\.json\|api-surface\.json" origin/main全仓逐条处置。代码读点(全部已改):packages/spec/scripts/build-schemas.tsreadSurfaceKeysAtRevpackages/spec/scripts/build-api-surface.tsapi-surface/;--check读整个目录并新增「字节漂移但导出名没动」的红packages/spec/scripts/build-docs.tsreadApiSurfaceFrom(API_SURFACE_DIR)packages/spec/scripts/build-spec-changes.tsSURFACE指向目录;--previous-surface接受目录或单文件(见下)packages/spec/scripts/root-index.test.tspackages/spec/scripts/check-generated.tspackages/spec/scripts/def-key-collisions.test.tspackages/spec/scripts/build-schemas-check-mode.test.tspackages/spec/package.jsonfilesapi-surface.json→api-surfacescripts/regen-artifacts.mjs/**,并写下为什么另外三件不分片.gitattributescheck:merge-driver双向对账绿)scripts/check-published-files.mjsPURPOSE表键名scripts/release-spec-changes.shapi-surface/(本次起)、api-surface.json(protocol 15 起)、两者皆无(15 之前)MCP
spec_changes工具:实测无需改动 —— 它是spec-changes.json的投影,而该文件有意维持单体(单子正文的范围外声明)。仓内git grep spec_changes的全部命中是散文/CHANGELOG,零代码读点触及被分片的三件。docs build:无需改动 ——
apps/docs不读这三件;唯一相关读点是packages/spec/scripts/build-docs.ts(已改)。散文提及(按性质分三档处置):
AGENTS.md(§11 os-regen 表 + 新增一段解释为什么 driver 不够、以及哪三件有意不分片;Post-Task 第 4 条)、.github/workflows/lint.yml注释、.claude/skills/pm-dispatch/SKILL.md(os-regen 路径清单 + 锚点措辞)、.claude/skills/spec-property-retirement/SKILL.md(退休套件里三处路径)、packages/spec/scripts/lib/{renamed-defs,docs-import-surface}.ts、check-exported-any.ts、check-dual-source-exports.ts、schema-name.test.ts、src/migrations/spec-changes.ts,以及 merge 带进来的 fix(spec): check:docs 不再以 gen:schema 开头,生成交给调用方 (#4723) #6000 那几处(build-docs.ts/check-regen-pending.mjs/check-generated-ledger.test.ts/schema-tree-freshness.test.ts)。src/**与测试里追述「当年这个 key 怎么出账」的注释(http-server.zod.ts/theme.zod.ts/data-engine.zod.ts/retry-policy.*/notification.test.ts/approval.test.ts/explain.test.ts/interaction-config-retirement.test.ts/alias-table-registry.ts/packages/lint/src/validate-org-axis-red-lines.ts/docs-import-surface.test.ts)、全部CHANGELOG.md与.changeset/*.md、content/docs/releases/(⛔ 从不在代码 PR 里动)、docs/audits/**与docs/protocol-upgrade-guide.md/spec-changes.json里的 rationale 文本(生成物 + 台账,内容是当时的判断)。build-docs.ts --update-import-baseline用 JSON.stringify 写原始字符,与仓内 ASCII 转义编码惯例冲突 —— 每次运行产生 ~275 行纯编码 churn 淹没真实变化 #5990:packages/spec/docs-import-surface.baseline.json的_comment仍写着api-surface.json。源常量已改,但没有重跑--update-import-baseline:实测重跑除注释外还会把该文件 137 行的 em-dash 从—转义归一化成裸字符(entries数组逐条不变,已核验),那是build-docs.ts --update-import-baseline用 JSON.stringify 写原始字符,与仓内 ASCII 转义编码惯例冲突 —— 每次运行产生 ~275 行纯编码 churn 淹没真实变化 #5990 记的同一处编码 churn。它是手工 ratchet(NOT_DRIVER_MANAGED),在分片 PR 里顺手重写会把 274 行无关归一化混进来。变更集档位:实测后定
patch对照 #5967 的先例(
openapi.json因在exports里而判 major),实测本 PR 的发布面:authorable-surface/json-schema.manifest:不在发布面(既不在files也不在exports),对外零可见。api-surface:在files、不在exports。因此它进 tarball,但无法经包的公开接口访问 ——exports映射不含./api-surface.json,require('@objectstack/spec/api-surface.json')在改动前就已经被 exports 挡住;唯一能读到它的方式是直接按文件系统路径钻进node_modules,那不是受支持的访问面。对照 fix(spec): openapi 静态产物摘除 built-in 路由段,只保留契约半边 (#5744) #5967:openapi.json是 major,正因为它在exports里,是公开子路径。check:api-surface实测0 breaking / 0 added),运行时行为、类型、schema 一律未动。⇒
@objectstack/spec: patch。changeset 正文写了对外的迁移一句话(改读api-surface/目录、按entry合并各分片的exports,内容逐条相同)。基底与 os-regen 四步
git fetch origin main后 #6000(#4723)已合入(b5bdf48),所以 S2 的门禁构成改动是叠在它之上的:merge 唯一冲突就在check-generated.ts的 GATED 表,两侧都改了check:docs/check:api-surface两行,取并集 —— 保留 #6000 的readsSchemaTree: 'check:authorable-surface',同时保留本分支的artifact: 'api-surface/'。四步纪律照做:主线未动过这三个单体(
git log 44106d9..origin/main -- <三件>为空,#5983 是纯 TS 退休),因此无 modify/delete 冲突;先提交 merge(MERGE 态跑gen:schema会被 #5851 守卫拒绝),再pnpm install --frozen-lockfile+ 整体重建(pnpm --filter @objectstack/spec build=gen:schema && gen:openapi && tsup,已含 #5371 要求的gen:openapi),重建后git status为空 —— 合并后的源重新生成产物零 diff。scripts/下的 merge driver 实现按 #4868 复查过:DRIVER_SCRIPT_EXPR是"$(git rev-parse --show-toplevel)/scripts/git-merge-regen.mjs",merge 时相对当前 worktree 解析,无写死绝对路径;新加的readShardedKeysAtRev同样只用 cwd 相对的ls-tree+show < rev >:./< path >,不引入绝对路径。验证(全部前台阻塞执行,持
flock /tmp/os-heavy-verify.lock,NODE_OPTIONS=--max-old-space-size=4096)merge 后重跑:
merge 前完整跑过一轮(merge 只带入
check-generated.ts一行 + 散文,已在 merge 后重跑check:generated):pnpm --filter @objectstack/spec test→ 325 files / 8319 tests passed(含改写后的build-schemas-check-mode.test.ts45 passed 与新增sharded-artifacts.test.ts23 passed)pnpm --filter @objectstack/spec typecheck→ 通过(tsc --noEmit+check:test-typecheck,79 files / 691 errors 的 shrink-only 账本无变化)node scripts/git-merge-regen.mjs --self-test→ 绿(.gitattributes↔regen-artifacts.mjs9 条路径双向对账 + 真 git 端到端)pnpm check:published-files/check:adr-anchors/check:nul-bytes/check:doc-authoring/check:skill-frame-sync/check:docs-audit-scope→ 全 OKeslint改动文件 → exit 0identical: true控制字节:
check:nul-bytesOK(5825 个 tracked 文件);改动文件另跑了越过门禁盲区的自扫描grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]',零命中。对 #5551(C2 首期铺 XParsed,55 文件全量重生成)的影响:变简单
明确回答,因为它是下一轮首单:
qa(22 keys)、shared(39)、integration(138)、security(140)、studio(156)、identity(186)这些小 category 的单子从此完全不与它排队。git diff里逐字节不动,这一点现在有常驻测试兜底。merge=os-regen+ 四步重建兜。分片缩的是盘子,不是取消了盘子。未做 / 有意留下
packages/spec/src/**/*.zod.ts(两次临时探针都已git checkout还原,git status干净)与 strictness 台账。spec-changes.json/api-surface-signatures.json维持单体(单子正文的有意范围外),理由写进了regen-artifacts.mjs。content/docs/releases/。docs/spec-generated-artifact-sharding.md:S4 要求的docs/说明,记录布局、不变量、新增 category 怎么办、那一处历史读取何时退役。Generated by Claude Code