Uh oh!
There was an error while loading. Please reload this page.
refactor(spec): 三个热点生成物按 category / entrypoint 分片,拆掉合并队列的串行税 (#5837) - #6069
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
|
20:01Z 平台冻结窗口里挂的 rerun attempt(补 Dogfood Verify CLI + 聚合门)卡在 GitHub 预排队 limbo:409 不可取消,近 4 小时零 job 实例化。调度层已恢复但只对 新触发生效,所以换一个 head sha 重新调度整轮门禁。 空提交:零文件变化,不 merge main,不 rebase。aa121e7 上已 success 的门禁 (ESLint / TypeScript Type Check / Test Core 1-3 / Dogfood 1-3 / Temporal / Check Changeset)在新 head 上原样重跑。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
📓 Docs Drift CheckThis PR changes 1 package(s): 112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
qq9340100
commented
Aug 7, 2026
落地前门禁终读(head
按维护者优先级指示,本 PR 现在转 ready 并挂 auto-merge,第一个入队;MERGED 后立即向补圈集(#6068/#6076/#6078/#6072/#6077,#6078 先于 #6072)发同步指令。 Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
#5837(PR #6069)把三个单体生成物按 category / entrypoint 拆成分片目录。 本分支原先是对着单体布局生成的,merge origin/main 时 authorable-surface.json 取 main 侧(接受删除,不手工恢复),再整体重生成让本单的 schema 变更落到 分片布局里:packages/spec/authorable-surface/cloud.json 新增一行 `cloud/ProvisionEnvironmentResponse:hostnameAssignment`。 分片的 locality 在这里得到实测:14 个 authorable 分片只动了 cloud.json 一个, api-surface/ 与 json-schema.manifest/ 零改动(本单不新增导出符号)。 check:generated 10 门全绿。 Refs #5185, #5837 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
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