Skip to content

feat(spec): 可作者化 key 的默认值进入门禁 —— authorable-defaults 指纹比对 (#4666) - #6360

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-4666-default-value-fingerprint
Aug 7, 2026
Merged

feat(spec): 可作者化 key 的默认值进入门禁 —— authorable-defaults 指纹比对 (#4666)#6360
os-zhuang merged 3 commits into
mainfrom
claude/issue-4666-default-value-fingerprint

Conversation

@os-zhuang

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

Copy link
Copy Markdown
Contributor

Fixes #4666

按维护者 2026-08-07 裁决执行方向 B —— 只对默认值做指纹,约束不纳入

前提复核(当前 origin/main

盲区原样存在。build-schemas.ts 今天被移动过三次,正文行号已过期,但结论未变:可作者化面门禁只比较 key 的集合(currentKeys 只记 key 到 isRetired 的映射),.default() 对它完全不可见。#4661 的 sabotage 在本分支上原样复现(见下表 P1)。

一处正文已过期的细节:shared/retry-policy.zod.ts 的 shape 并不发出 shared/RetryPolicy def,它被 ./automation./system 两个 barrel 复用,所以改一个字符实际移动的是两个 def key。门禁按 def key 逐个报,这是对的 —— 声明也必须逐个写。

指纹捕获什么,怎么规范化

新增 packages/spec/authorable-defaults/{category}.json(按 category 分片,与 authorable-surface/ 同构,所以两个 PR 改不同 category 不共享文件)。每行形如 "{def}:{key} = {规范化 JSON}",例如 shared/CorsConfig:enabled = true,只收有默认值的 key。

指纹读的是发出的 JSON Schema 的 default 字段,不是 Zod 内部节点。理由:那是已发布契约真正对作者和 IDE 承诺的东西,也是 key 集合本身的来源;读 _zod.def 会把门禁绑死在「值由 .default() 还是 .prefault() 还是 .meta() 产生」上,而作者体验到的只有结果。

规范化四条,每条都对着一类会误报的 churn:

规则 排除掉的噪音
对象键递归排序 对象字面量重排键不是语义变化
数组顺序保留 数组默认值的顺序作者看得见,重排是真变化
无空白(compact) 重新格式化不能动指纹
只读 default 一个字段 约束、.describe()title 结构性地进不来

最后一条就是方向 B 的边界,它是结构性的而不是靠自觉:门禁根本不读 minimum / maximum,所以收紧一个 bound 连意外移动指纹都做不到。这条边界另有单测钉住(两个只差 bound 和文案的 shape 必须指纹相同),免得后人「顺手改进」时把它侵蚀掉。

只记有默认值的 key,不记 (none):7943 个可作者化 key 里 1326 个有默认值,只记这些让产物只有六分之一大;存在性并没丢 —— 新增默认值让一行出现,删除让一行消失,两者都被判为变更。

基线取哪儿:这才是能不能防住的关键

另一条纪律:判定跑在写产物之前。所以 gen:schema 不会把未声明的变更吸收进它自己充当证据的那份记录里 —— 实测见下表。

「已确认的变更」出口怎么保持诚实

出口是 DEFAULT_CHANGES_BY_MAJORscripts/lib/default-changes.ts),四条性质每次运行都从声明者控制不了的来源重新推导:

  1. 两端都钉死 —— from 对基线(git 里的 merge base),to本次构建发出的值。都不是填表人能设的字段。
  2. 链尾必须仍然为真 —— 当前 major 下某 key 的最后一跳 to 必须等于活值。默认值再动一次、或者被回滚,声明立刻红。这就是「不再为真就死掉」的那条,也是 check (b2) 的镜像。
  3. 链必须连续 —— 前一跳的 to 必须等于后一跳的 from,跳不过中间真的发过的值。
  4. 只有当前 major 授权 —— 旧 major 的行是历史记录,授权不能活过它被写下的那个 major。

外加:被消费的声明每次都整条打印 reason。已确认的默认值翻转在构建日志里自报家门,而不是静默通过。

本 PR 落地时表是空的 —— 这份空本身就是门禁的自证:表里什么都没有而 check:authorable-surfaceorigin/main 上是绿的,等于说树里每个默认值都对得上记录。

反向验证(预测先写后跑)

预测写在实现之前。P1 里 sabotage = retryPolicyShape().default(0) 改成 .default(3),一个字符。

# 场景 预测 实测
P0 未改动的树 绿 绿,1326 default(s) unchanged
P1 sabotage 红,点名两个 key,0 → 3 红,automation/RetryPolicy:maxRetries: 0 → 3 + system/RetryPolicy:maxRetries: 0 → 3
P2 sabotage + 先跑 gen:schema 红 —— 但机理和预测的不一样,见下 ⚠️
P3 删掉指纹比对 + 重生成记录 绿 绿,且记录静默吸收 = 3
P6 只声明两个 key 中的一个 仍红 红:一个 📌 accepted,另一个仍被拦
P7 两个都声明 绿,且打印 reason 绿,两条 reason 整条打印
P8 两个都声明后把默认值改回去 红(声明过期) 红:declared to end at 3, but this build emits 0

P2 预测落空,而且这是本 PR 最有价值的发现。 预测的理由是「基线来自本 commit 拥有不了的锚点,所以重生成洗不掉」。在本分支上这个理由是错的 —— 本分支的 merge base 还没有这份产物,用的是 in-tree 锚点,本 commit 恰恰拥有它。方向对了,机理是另一个:gen:schema 直接拒绝写(判定在写之前),所以记录压根没被重生成。

顺着这条继续挖,得到了真正该知道的那件事:把 schema 和记录同时手改成一致(把记录里的 = 0 字节手术成 = 3),in-tree 锚点下门禁全绿。这就是 #4650 的攻击形状,原样长在新产物上。

于是补了一次隔离 probe(独立 bare clone + linked worktree,origin/main 指向本 PR 的 commit,refs 与共享 checkout 完全隔离),把同一次「协同手改」重放一遍:

❌ 2 authorable key(s) changed the DEFAULT they apply when the author omits them:
     - automation/RetryPolicy:maxRetries: 0 → 3  (changed)
     - system/RetryPolicy:maxRetries: 0 → 3  (changed)

   Baseline: upstream 3cc2bf0e5841

结论:in-tree 锚点挡得住忘记,挡不住手改;upstream 锚点两个都挡得住。 这个窗口在本 PR 合并的那一刻自动关闭(此后每条分支的 merge base 都带着这份产物),离线构建则是已合并的不可变树 —— #5235 对同一处境给的理由在这里逐字适用。这一点写进了代码注释,不留给下一个读者自己踩。

另一条没预测到但很说明问题的:rebase 到新 main 后记录少了一行,ui/ElementRecordPickerProps:multiple。查下来是 #6281(SDUI props enforce-or-remove)把它 retiredKey() 墓碑化了,默认值随墓碑消失。门禁按设计保持沉默 —— 墓碑归 check (b) 的 RETIRED_KEYS_BY_MAJOR 管,在这里再要一次声明就是同一次退役记两遍。写下来的排除规则在落地几小时内就被一次真实的上游事件验证了一遍。

防空转的钉子

scripts/authorable-defaults.test.ts,23 条,全部关于区分度(这个输入出结论、那个不出),而不是「函数能跑」。把 diffAuthorableDefaults 改成无条件返回空数组之后,catches the #4661 sabotage: one character, one finding, named 立刻红 —— 空转钉不住的正是 #4642 的那类病。

产物成本(A-vs-B 决策的代价侧,要数字不要印象)

行数 字节
authorable-defaults/(新增,13 个 shard) 1405 86,715
authorable-surface/(既有) 0(逐字节未变)

方向 B 的代价全部落在一个新文件上,authorable-surface/ 一个字节没动 —— #4535 §1 抱怨的那个文件不因本 PR 变大变吵。方向 A 会把 7943 个 key 全部加上约束摘要,落点正是那个文件。

登记点

.gitattributesmerge=os-regen)、scripts/regen-artifacts.mjspackages/spec/scripts/check-generated.ts 的 artifact 描述。没有新增 gen: / check: 脚本 —— 门禁跑在 build-schemas.ts 内,check:authorable-surface 已经覆盖它,check:generated --reconcile-only 双向对账通过。

build-schemas-check-mode.test.ts 的四个沙箱补种了这份产物:默认值门禁每次调用都跑,缺记录时六条 fixture 会在它们真正要测的删除 / 改名判定之前就红。

changeset

不需要 —— 走 skip-changeset(已打标)。 packages/specfiles 白名单是 [dist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json]authorable-defaultsscripts/ 都不在其中。本 PR 不含任何 packages/spec/src/** 改动(sabotage 是本地临时改动,已复原并逐条核过),所以 dist/*.d.ts 不变、api-surface 不变,发布物零变化。

验证

  • pnpm --filter @objectstack/spec test —— 333 files / 8523 tests 全绿
  • pnpm --filter @objectstack/spec typecheck —— 绿
  • pnpm --filter @objectstack/spec check:authorable-surface —— 绿(rebase 到当前 main 之后)
  • pnpm --filter @objectstack/spec check:generated --reconcile-only —— 18 check: + 13 gen: 全部归类
  • node scripts/check-nul-bytes.mjs —— 绿;新文件另做了越过门禁扫描面的自查,无命中

Generated by Claude Code

claude added 3 commits August 7, 2026 15:08
`authorable-surface/` 只比对 key 的集合,同一个 key 的 `.default()` 变了对它
完全不可见 —— 而默认值决定「作者没写这个 key 时会发生什么」,翻转它会**静默**
改变所有已部署元数据的行为。#4661 实测:把 `maxRetries` 默认值改一个字符,
`check:authorable-surface` 全绿。

新增 `packages/spec/authorable-defaults/`(按 category 分片,与 authorable-surface
同构)记录每个有默认值的可作者化 key 的规范化指纹;比对基线优先取 origin/main
merge base(本 commit 改不动的锚点),其次取树内已提交的记录。已存在 key 的默认值
发生 变更 / 新增 / 删除 一律红,除非在 `DEFAULT_CHANGES_BY_MAJOR` 显式声明。

⛔ 约束(`.min()` / `.max()`)**不**纳入指纹 —— 维护者对 #4666 的裁决(方向 B):
约束收紧是**硬拒**,响亮可诊断;只有默认值翻转是静默的。该排除是结构性的:指纹
只读发出的 JSON Schema 的 `default` 一个字段。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
四个沙箱(顶层 + 三个 box)此前只种 authorable-surface/。默认值门禁每次调用
都会跑,缺少记录时 `--check` 报「产物缺失」,六条 fixture 因此在它们真正要测的
删除 / 改名判定之前就红了。补种后沙箱重新是真实树的缩影。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
`ui/ElementRecordPickerProps:multiple` 在 #6281(SDUI props enforce-or-remove)
中被 `retiredKey()` 墓碑化,默认值随之消失。门禁按设计对此保持沉默 —— 墓碑属于
check (b) 的 RETIRED_KEYS_BY_MAJOR 通道,在这里再要一次声明就是同一次退役被记两遍。
记录相应少一行。

Co-Authored-By: Claude Opus 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 3:11pm

Request Review

@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 added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 7, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 7, 2026 15:36
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

PM 验收:ACCEPT — 已 ready + auto-merge。

先答你标出的 HONEST GAP:两条尾巴都已收敛为 success,我逐个核过 —— TypeScript Type Check 单独取 check run 读到 conclusion: success;Test Core (1/3) 15:20:59、(2/3) 15:22:09、聚合 Test Core 15:23:21 全 success。29 个 check 零 failure。你当时不把它们叫绿是对的:那时它们确实还没结论,而「看起来像 runner 饱和」是推测。把推测标成推测、把结论留给能看到结论的人,这正是我要的移交方式。

维护者裁决(方向 B)的执行:结构性排除,而不是纪律性排除

指纹只读发出的 JSON Schema 的一个字段 default,并有单测钉住这条边界。这意味着约束收紧或 .describe() 改写在物理上动不了它 —— 而不是「我们约定不去看」。后来者想把方向 A 悄悄加回来,得先改指纹读什么,那是一次显式的、会被看见的改动。这是把裁决写进结构,不是写进注释。

我要的那个数字,拿到了,而且它正好证成了裁决

authorable-defaults/ 新增 1405 行 / 86,715 字节 / 13 个分片
authorable-surface/ 变化 0 字节,逐字节不变

方向 A 会把约束摘要压在全部 7943 个 key 上,而且压在 #4535 §1 已经在抱怨过度收集的那个文件里。B 把新数据放进新文件、让吵的那个文件一动不动。成本侧的账现在是可复核的数字,不是印象。

那条落空的预测,产出了本单最重要的东西

你预测 sabotage 会红,理由是「基线取自本 commit 改不动的锚点」。方向对,机制错:在这条分支上 merge base 早于该产物,所以锚点回落到 in-tree —— 而那个 commit 拥有它的。

顺着这条错追下去才是价值所在:同时手改 schema 与记录(逐字节 = 0= 3)在 in-tree 锚点下完全绿 —— 这就是 #4650 的洗白形状在新产物上重演。你没有停在推理,而是造了一个隔离探针(独立 bare clone + linked worktree,自己的 origin/main 指向本 PR 的 commit,refs 与共享检出隔离)复现同一次洗白:红,Baseline: upstream 3cc2bf0e5841

结论干净利落:in-tree 锚点挡得住「忘记」,挡不住「手改」;upstream 锚点两者都挡 —— 而这个窗口在本 PR 合并的瞬间自行关闭(届时 merge base 里就有该产物了)。写进了代码注释,而不是留给下一个人重新发现。 这是今天第四次「落空的预测比命中的预测更值钱」。

三处判断我要点名认可

  1. DEFAULT_CHANGES_BY_MAJOR 是一个诚实的逃生口,不是空白支票。 每一行每次运行都从作者控制不了的来源重新推导(from 对基线、to 对本次构建实际发出的值),随 major 过期,必须构成连续链,且被消费时完整打印 reason。这满足了我要求的「非静默」,而且比我要求的更强 —— 它让一条写错的声明也活不下去。
  2. 墓碑交互按设计保持沉默。 rebase 后 ui/ElementRecordPickerProps:multiplespec: SDUI props — enforce or remove (#5775) #6281 被墓碑化、默认值随之消失,门禁不报 —— 因为墓碑属于 check (b) 的 RETIRED_KEYS_BY_MAJOR 通道,在这里再要一次声明就是同一次退役被记两遍。这是今天第二个 dev 独立做出的「两道门不得对同一事实重复报告」判断(另一个是 [finding] ADR-0087 台账没有「完备性」门禁:已发生的退役漏登记时全仓全绿,只有人工能发现(#6011 即如此) #6148 的「投影 ⊆ 源」方向)。
  3. 沙箱补种而不是给门开后门。 四个沙箱缺 authorable-defaults/ 记录导致六条 fixture 在测到它们真正要测的东西之前就红;你补种记录让沙箱重新成为真实树的缩影,而不是给门加一个「沙箱里跳过」的分支。后者会让沙箱从此不再代表真实。

对 issue 正文的更正,已采纳

shared/retry-policy.zod.ts 不产出 shared/RetryPolicy def —— 它经 ./automation./system 两个 barrel 复用,所以那次「改一个字符」的 sabotage 动的是两个 def key,门禁分别报告、分别要求声明。issue 正文按一个 key 写的,实测是两个。


Generated by Claude Code

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec 门禁盲区:可作者化 key 的「默认值 / 约束」变更不被任何 gate、tombstone 或 conversion 记录(#4650 / #4659 同族)

2 participants