Skip to content

feat(spec): ADR-0122 第一期 —— 解析态统一命名 XParsed(纯增量)+ 防回流门 - #6072

Merged
qq9340100 merged 5 commits into
mainfrom
claude/issue-5551-xparsed-convention-adr-phase1
Aug 7, 2026
Merged

feat(spec): ADR-0122 第一期 —— 解析态统一命名 XParsed(纯增量)+ 防回流门#6072
qq9340100 merged 5 commits into
mainfrom
claude/issue-5551-xparsed-convention-adr-phase1

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Refs #5551

按 2026-08-06 维护者批复(裁 C,分期 C2)落地第一期。原单叙事(「翻 connector 单文件」)已被前任 dev 证伪,本 PR 不执行它。

关单口径:本 PR 只完成第一期(加性),裸名翻转留到 major 窗口,故用 Refs 而非 Fixes,由 PM 决定 #5551 是关单另开第二期单、还是保持 open。


一、先说结论:判据是「形状差」,不是「有没有 XInput」

裁决把第一期铺设判据交给 dev 定并论证。我重跑了全仓测量,结论与派发单沿用的 03:24Z 摘要不同,且差异是决定性的:

派发单里的「B 族 ~55 文件 / ~108 别名」是**「已经写了 XInput 的那批」**。它不是需要迁移目标的那批 —— 第二期翻裸名会改变含义的别名共 663 个,其中只有 91 个今天带 XInput,572 个裸名连作者态的名字都没有。按「有 XInput」铺,会漏掉 572 个 —— 恰恰是第一期要防的那种静默破坏。

所以 ADR-0122 D5 写下的判据是:

当且仅当 z.input< typeof XSchema >z.infer< typeof XSchema >不是同一个类型时,该裸名别名获得 XParsed

三轴论证(写进 ADR):

  • 真实业务需要:第二期只在两形状有差时改变含义;同构者翻不翻都一样,给它 XParsed 是永久同义词。全仓同构的有 721 个 —— 按「一律铺」会给公开面凭空加 721 个「选哪个都行、于是必然有人选错」的名字。创业期收紧口径,名字要靠用途挣位置。
  • 长期正确性:XParsed 只在「解析态确实是另一个类型」时存在,名字才不撒谎。
  • 让 AI 不易写错:同构者不给第二个名字,作者就没有可选错的机会;有形状差的一律给,作者/LLM 迁移时永远找得到目标。

⚠️ 由此,本 PR 的量级与派发单预估(+~100 导出)不同:实际新增 660 个别名。这是测量取代估算,不是扩范围 —— 判据本身就是裁决交给我定的那一项。

二、改了什么(纯增量)

新增 XParsed 别名660
涉及 *.zod.ts 文件151
修改/重命名/删除的现有声明0
钉住的同构 schema717
// 之前exporttypeConnector=z.infer<typeofConnectorSchema>;exporttypeConnectorInput=z.input<typeofConnectorSchema>;// 之后 —— 只加了中间这行exporttypeConnector=z.infer<typeofConnectorSchema>;exporttypeConnectorParsed=z.infer<typeofConnectorSchema>;exporttypeConnectorInput=z.input<typeofConnectorSchema>;

XInput 一个没动(qa downstream-contract 的 FROZEN fixtures 按文件头明文原样不迁移);A 族 8 文件未触碰。

三、同构豁免是钉住的,不是写在注释里的

D3 说同构者不给第二个名字。但「同构」不是谁声明的,是 schema 树的事实,而且会烂 —— 哪天某个嵌套字段加了 .default(),这个别名就悄悄进了形状差集合,第二期翻它时消费方没有迁移目标,正是第一期要防的那件事。

所以 717 个豁免全部落在 packages/spec/src/type-alias-convention.pin.test.ts,每条都是一句编译期断言。tsc 在给包做类型检查的同一趟里证明它们为真,某条不再成立的当天这个文件就红,并直接点名是哪个别名。修法只有一种:给裸名补 XParsed,然后删掉那行 pin。

四、防回流门:落在 scripts/,不在 packages/lint

派发单写的是「packages/lint/** 新增一条规则」。这条做不到,是架构事实不是偏好:@objectstack/lint 的模块头写着它校验的是内存里、已 schema-parse 的元数据图,"no I/O, no runtime, no filesystem",每条规则都是纯 (stack) => Finding[]。一条要读我们自己 .zod.ts 源码的规则放进去,会是该包里唯一打开文件的规则,直接破坏它的契约。

所以它落在本仓源码形态门那一族(check:error-code-casing / check:route-envelope / check:engine-double-contract 的邻居),挂进 lint.yml 的 lint job:

  • pnpm check:spec-parsed-alias —— 每个裸 z.infer 别名必须要么XParsed,要么在 pin 文件里被钉为同构;顺带报告「已经没人依赖」的过期 pin。
  • 豁免名单不写在门里,门去读 pin 文件 —— 一份产物两个用途:门拿到机器可读的豁免表,tsc 保证表上每条都为真。豁免名单要是搬进门里,它就变成 AGENTS.md 说的那种 phantom check 了。
  • --self-test(11 条正反断言),照本族惯例先自检再扫。

由此不涉及 packages/lint,与 devx 车道在飞的 #4251 零文件相交(注册表/barrel 都没碰)。

门与 pin 的实测信噪比

  • 铺完后全仓 0 误报:1384 bare z.infer aliases, 717 pinned isomorphic, 667 paired with an XParsed. OK(667 + 717 = 1384,门每次运行自己算这道账)。
  • 门在真语料上抓到了 5 个我的普查漏掉的真实边角,不是空转:ServiceObject(ObjectSchemaBase 未导出,探针够不到 —— 单独验证确有形状差,已补 ServiceObjectParsed)+ automation/execution.zod.ts 4 条本就配好 XParsed、因而多余的 pin。

五、反向验证(方向事先定,两处都预期变红)

1. 删掉一个新铺的 XParsed → 门必须红并点名该别名。ConnectorParsed:

ADR-0122: 1 type-alias convention violation(s) in packages/spec.
integration/connector.zod.ts — Connector
`Connector` names the PARSED state of `ConnectorSchema`, but ADR-0122 reserves
the bare name for the AUTHOR state. Declare `export type ConnectorParsed = ...`
GATE_EXIT=1

还原后回到 ... 667 paired with an XParsed. OK

2. 给一个被钉为同构的 schema 加 .default() → pin 必须红。SyncStrategySchema(pin 文件第 781 行 = Iso376)加 .default('full'):

packages/spec/src/type-alias-convention.pin.test.ts(781,29): error TS2344:
Type 'false' does not satisfy the constraint 'true'.

红的正是事先点名的那一行,不是别的行。还原后 check:test-typecheck 恢复绿。

六、SYNC_ARCHITECTURE.md 误导句

原文说 connector「还没搬到那条 house convention 上」,把 8 文件的少数派说成全仓惯例 —— 与实测相反。已改写为:两种拼法都无记录、ADR-0122 才是那条记录、第一期已给 Connector 备好 ConnectorParsed、第二期在 major 翻裸名。

七、生成物

XParsed 是纯类型别名,所以只有 api-surface.json 动:+637 导出名,0 删除。声明了 660 个,其中 23 个所在模块没有被任何公开入口再导出,故不上公开面 —— 这个差额是核对过的,不是漏生成。

json-schema/authorable-surface.json零变化:gen:schema + gen:openapi 跑完 git status 只有我自己手改的两个文件。这是验证结果,不是预期。

check:generated 十个门:9 绿,唯一 stale 的 api-surface.json 已按它给的命令重生成(未整套重跑)。

八、必答:本改动对 #5837(生成物分片)的影响

变简单一点点,主要是无影响。 本 PR 不碰 packages/spec/scripts/**(#5837 的源文件面),两边源文件零相交;唯一相交是 api-surface.json 这个生成物本身,而这正是分片要解决的问题 —— 本 PR 给它加 637 个导出名,是分片收益的一个具体例证(单文件更大),但既不改变它的生成方式,也不增加分片的实现难度,更没让分片变得不必要。落地次序按 PM 定的 #5983#5837 → 本单,我在末次同步圈按分片后的生成流程整体重生成。

九、验证

命令结果
pnpm --filter @objectstack/spec typecheckTYPECHECK_EXIT=0
pnpm --filter @objectstack/spec testTest Files 326 passed (326) / Tests 8311 passed (8311)
pnpm --filter @objectstack/lint testTest Files 61 passed (61) / Tests 1449 passed | 4 skipped
pnpm check:spec-parsed-alias(含 --self-test)11 断言过 + 全仓 OK
pnpm check:adr-anchorsOK(36 anchored files)
pnpm check:nul-bytesOK(5783 files)
npx eslint(改动文件)ESLINT_EXIT=0

@objectstack/lint 首跑 13 个测试文件红 / 3 条测试红。查明是 AGENTS.md §9 与工具链陷阱 #2依赖未构建假红(13 files 红但只有 3 tests 红 = collection error 特征),pnpm --filter '@objectstack/lint^...' build 后 61/61 全绿。与本改动无关。


Generated by Claude Code

`packages/spec` named a schema's two types (`z.input` = author state,
`z.infer` = parsed state) two different ways, with nothing recorded about
which was which. Measured on origin/main: 1384 bare aliases mean the parsed
state, 86 mean the author state, and three first-hand sources each called the
8-file minority "the house convention". No ADR recorded either.
ADR-0122 settles it — bare name = author state, `XParsed` = parsed state —
and lands additively. Phase 1 declares `XParsed` for the 660 aliases whose
schema genuinely has two shapes, so the major-window flip has a migration
target everywhere it changes meaning. Nothing renamed, nothing removed,
every `XInput` left in place.
- ADR-0122, with both measurement rounds as its appendix
- 660 `XParsed` aliases across 152 `*.zod.ts` files
- 717 isomorphic schemas pinned with compile-time assertions, so the D3
exemption cannot rot silently when one later gains a `.default()`
- `check:spec-parsed-alias` backflow gate (lint.yml), reading the pin file
as its exemption registry
- SYNC_ARCHITECTURE.md: correct the sentence that called the minority
spelling the house convention
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
…se 1
- api-surface.json: +637 export names, 0 removed. 660 aliases were declared;
23 live in modules no public entry point re-exports.
- json-schema/ and authorable-surface.json verified unchanged — `XParsed` is a
pure type alias, so `gen:schema` / `gen:openapi` produce no diff at all.
- the pin file's count case now reads the pin count out of the source instead
of asserting a literal against itself. tsc proves each pin is TRUE; nothing
proved they were still THERE, so a red pin could be made green by deleting
it. Counting them makes that edit fail `pnpm test` too.
- ADR appendix: 151 files edited (not 152 — `automation/execution.zod.ts`'s
three differing aliases already had their `XParsed`), plus the two findings
the gate produced on its first real run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
@vercel

vercelBot commented Aug 6, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 7, 2026 4:44am

Request Review

@github-actions

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.

@qq9340100Claude

Copy link
Copy Markdown
CollaboratorAuthor

CI 收敛记录(⚠️ ESLint job 至今未拿到 runner —— 合并前请自行复核该 job 颜色)

在合并前请以 CI 上 ESLint job 的真实 conclusion 为准。本条记录我离开时的状态与替代证据,不代表该 job 已通过。

两个门 job 的实际状态(merge commit aed99a8,run 31123460219)

jobconclusion备注
TypeScript Type Checksuccess本改动风险最高的 job —— spec 全部八个生成物门都在这里跑
ESLintqueued,未分配 runner首轮 17:33 排队 21 分钟后被 cancelled(零 step 执行、runner: (none assigned));rerun_failed_jobs 重试后 18:12 起再排队 70+ 分钟,仍未分配

这是全仓 runner 饥饿,不是本 PR

最近 30 个 workflow run 里有 19–22 个处于 queued/in_progress,横跨多个分支,连 merge queue 自己的 run(gh-readonly-queue/main/pr-6031pr-6034)也在排队。同一时段 Console Pin Freshness / Duplicate Fix Guard / Validate Dependencies 三个 workflow 的 run 结论是 failure,但逐个查 job 全部是 cancelled(零 step),同一个饥饿现象。

替代证据:把 ESLint job 的命令逐条在合并后的树上跑了一遍

不是「本地测试绿」,是把该 job YAML 里的 27 条命令原样抽出来逐条执行,含全仓 pnpm lint 本身:

green: pnpm install --frozen-lockfile green: pnpm check:route-envelope
green: pnpm lint green: pnpm check:error-code-casing
green: pnpm check:slot-lookup green: pnpm check:wildcard-fallthrough
green: pnpm check:query-options-erasure green: pnpm check:init-service-contract
green: pnpm check:nul-bytes green: pnpm check:durability-log-level
green: pnpm check:doc-authoring green: pnpm check:startup-registry-verdict
green: pnpm check:docs-audit-scope green: pnpm check:objectui-changeset
green: pnpm check:role-word green: pnpm check:release-notes
green: pnpm check:adr-anchors green: pnpm check:release-body
green: pnpm check:org-identifier green: pnpm check:node-version
green: pnpm check:authz-resolver green: pnpm check:workflow-status-functions
green: pnpm check:service-providers green: pnpm check:published-files
green: pnpm check:engine-double-contract green: pnpm check:resume-authority-declared
green: pnpm check:spec-parsed-alias
LINTJOB_ALL_EXIT=0

其中 pnpm check:spec-parsed-alias 是本 PR 新增的门,也在这个 job 里 —— 所以它是否误报,这一轮已经用真语料验过(全仓 0 误报)。

合并前还需确认

  1. ESLint job 拿到 runner 并出 conclusion(这是唯一没拿到的那块)。
  2. Check Changeset 同轮被 cancel,需重跑;本 PR 带 changeset(.changeset/spec-type-alias-parsed-convention.md,@objectstack/spec: minor),因此不需要skip-changeset 标签。
  3. 落地次序按 PM 定的 refactor(spec)!: 按 ADR-0049 摘除 IStorageService.list(prefix) —— 零消费方、双适配器语义分叉 (#5540) #5983spec 生成物按 category 分片:拆掉三个单体 ratchet 文件的合并队列串行税(维护者 2026-08-06 已拍板) #5837 → 本单;refactor(spec)!: 按 ADR-0049 摘除 IStorageService.list(prefix) —— 零消费方、双适配器语义分叉 (#5540) #5983 已在 main(本分支已 merge 并重新校验:check:generated 十个门全绿)。若 spec 生成物按 category 分片:拆掉三个单体 ratchet 文件的合并队列串行税(维护者 2026-08-06 已拍板) #5837 分片先落,本 PR 需按分片后的生成流程重跑一次 gen:api-surface

Generated by Claude Code


Generated by Claude Code

…rsed-convention-adr-phase1
# Conflicts:
#	.github/workflows/lint.yml
#	packages/spec/api-surface.json
#	packages/spec/src/shared/mapping.zod.ts
生成物:三个单体产物已被分片目录取代,api-surface 重生成进 `api-surface/`
13 个分片,+635 导出名 / 0 删除。json-schema.manifest/ 与 authorable-surface/
零变化(`XParsed` 是纯类型别名)。
别名面按 ADR-0122 D5 对合并后的树重测,#5552 让两条同时移动 —— 这正是 D6
两个方向各走了一次,都没有靠人发现:
- `FieldMappingTransform` 整个联合被退役,其裸名与本 PR 曾加的
`FieldMappingTransformParsed` 一并消失;
- `FieldMapping` 的两形状差**只**来自 `transform` 一个键。键被墓碑化后
`z.input` 与 `z.infer` 同构,按 D5 不再属于覆盖集、按 D3 不得保留第二个
名字 —— 故删掉 `FieldMappingParsed`、改为钉进 pin 文件。方向不是猜的:
探针带一条故意失败的 control 断言,确认不是空转后才判定同构。
读数 657 别名 / 149 文件;1383 裸名 = 665 配对 + 718 pin(门每次自算)。
ADR 附录 F 与 changeset 已按交付态订正,并记下这次双向 graduation。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
@qq9340100
qq9340100 marked this pull request as ready for review August 7, 2026 05:19
@qq9340100
qq9340100 added this pull request to the merge queueAug 7, 2026
Merged via the queue into main with commit 7f713b6Aug 7, 2026
26 checks passed
@qq9340100
qq9340100 deleted the claude/issue-5551-xparsed-convention-adr-phase1 branch August 7, 2026 05:34
qq9340100 pushed a commit that referenced this pull request Aug 7, 2026
#6072 的防回流门 check:spec-parsed-alias 已落 main,本分支建于其前,新增导出需补齐时序。
`ConnectorActionEffectSchema` 是一个裸 z.enum —— 无 default、无 transform,z.input 与
z.infer 同构 —— 按 D5 判据不配 `XParsed`(那会给作者一个只能选错的同义名),按 D6 改为在
type-alias-convention.pin.test.ts 立编译期断言。与其上方两行的 ConnectorType /
ConnectorStatus 是同一类,故并入 integration/connector.zod.ts 分块;编号接文件末尾续号
(IsoNNN 只是唯一名,门读的是 z.input 出现处),避免为补一个空位重排其后 300 余行。
同时把 pin 计数断言 718 → 719 —— 该用例原注释只预期「减一」,这里补记它同样会因新增
同构别名而「加一」。
实测 D6 确实咬得住:临时给该 schema 加 .default('read') 后,check:test-typecheck 报
`src/type-alias-convention.pin.test.ts: 1 type error(s) in a file the ledger does not
cover`(注:裸 tsc --noEmit 不覆盖 *.test.ts,证明落在 typecheck 脚本的第二步)。
api-surface/integration.json 一并重生成:regen 合并驱动在 merge 时把该产物留给合并后重生成,
本次补回 #6072 的 10 个 XParsed 条目,本 PR 自己的两个条目原样保留。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
qq9340100 pushed a commit that referenced this pull request Aug 7, 2026
…hase-2 flip (#5775)
`check:spec-parsed-alias` (the phase-1 gate from #6072) refuses a bare
`X = z.infer<typeof XSchema>` alias that is neither paired with an `XParsed`
nor pinned as isomorphic. `PageContainerProps` is a lone optional array with no
default, transform, catch or pipe anywhere in its tree, so `z.input` and
`z.infer` coincide and the phase-2 flip of the bare name changes nothing
observable — which is the pin route, not the `XParsed` route (a permanent
synonym is a name an author can only pick wrongly).
`component.zod.ts` had no bare alias until now, so this adds the file's first
module import to the registry. It takes the next free M-index rather than the
alphabetical slot: those indices are positional identifiers the 720 pin lines
reference by number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwTBGD3gTnPKWmJLYU9R7T
lemonhub-io pushed a commit to OpenFork-org/objectstack that referenced this pull request Aug 8, 2026
…tack-ai#6083) (objectstack-ai#6279)
* feat(spec)!: ADR-0122 phase 2 — the bare type name is the AUTHOR state (objectstack-ai#6083)
Flips all 1384 bare `export type X = z.infer<typeof XSchema>` aliases in
`packages/spec/src/**/*.zod.ts` to `z.input`, so `const c: Connector = { … }` is
correct by default in every domain — the ADR-0033 keystroke argument that decided
ADR-0122. Phase 1 (objectstack-ai#5551 / PR objectstack-ai#6072) had already given every schema with two
distinct shapes its `XParsed` name, so nothing is stranded by the move.
Retires 102 `XInput` aliases. After the flip each one denotes character-for-
character what its bare name denotes, and D3 forbids a permanent synonym. Nine
`*Input` names survive because they are not synonyms: four are the bare aliases of
their own `…InputSchema`, five are composed (recursive / `Partial`-shaped) types no
bare alias denotes.
Inverts `check:spec-parsed-alias` rather than extending it. The flip empties the
population the phase-1 gate was written over, and measured on this tree that gate
reports 0 coverage findings (vacuously green) and 754 stale-pin findings (all
false) — one arm silently stops working while the other misfires on the whole
registry. The gate now refuses a bare name that reads `z.infer` (the flip,
enforced), refuses an `XInput` synonym of a bare name (the retirement, enforced),
and keeps the paired-or-pinned and stale-pin arms on the flipped form.
Inverting it widened it, and the widening found real work: the 86 aliases that
already read `z.input` had never been asked whether their parsed state was named.
57 were neither paired nor pinned; the same type-level probe that chose phase 1's
split (with its control assertion, which failed as required) split them 22 differ /
35 isomorphic. The 22 gained an `XParsed`, the 35 became pins — 719 → 754. That is
objectstack-ai#5507's remaining scope, absorbed here.
Consumer migration is compiler-named except in one place: a parse result is
structurally assignable to the author state, so a function's declared return type
can silently start promising less. 24 `defineX` factories were therefore re-declared
as `XParsed` by hand; every other migration in this change was a tsc error first.
Evidence the direction is right: `test-typecheck-debt.json`, whose own header
describes its contents as fixture literals annotated with the OUTPUT type while
holding an authored INPUT literal, went from 79 files / 691 errors to 59 / 270
without a single fixture being edited.
No runtime behaviour changes. `json-schema/` and `authorable-surface/` are
byte-identical — those generators read runtime `z.ZodType` exports, never aliases.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
* fix: migrate the consumers the flip named, and the two classes it cannot
Everything here is a site where a value's type said "defaults applied" and the
flip made that claim testable. Three shapes:
1. **Holders of a parse result** move to `XParsed` — `buildAccessMatrix`'s return,
the health-monitor / hot-reload internal config maps, `DEFAULT_METADATA_TYPE_REGISTRY`,
`MetadataManager.typeRegistry`, `MigrationExecutor`'s operations, the seed-loader
service's request/result/graph plumbing, the FLS mask the permission evaluator
builds. Each was `z.infer` before the flip; naming `XParsed` restores exactly
that and changes no runtime behaviour.
2. **Readers of a defaulted key off an author-state value** state the schema's own
default at the read — `ObjectLogger`'s `rotation`, the REST server's three
nested operation maps, `ScriptBody.capabilities`, the delegated-admin report's
flags. Per key, not per object: the schemas default *inside* those objects, so
`??` on the whole object only ever filled a wholly absent one.
3. **Factories the compiler cannot name.** `createEvalUser` returns a complete
user and declared `EvalUser`; a parse result is assignable to the author state,
so it kept compiling while promising less. Now `EvalUserParsed`, like the 24
`defineX` factories.
`IDataEngine.find`/`findOne` keep the parsed query type. Loosening them to accept
the author state is a real improvement and deliberately NOT taken here: it needs
the engine to apply `SortNode`'s `order` default at runtime, which is a behaviour
change and belongs in its own change.
Also updates the three gates that pinned a retired spelling: the
`EnvironmentArtifact` dual-source pin (which named `EnvironmentArtifactInput`),
`def-key-collisions`' mutation anchor, and `connector-author-shape`, whose
"`ConnectorInput` is the author shape" probes now read `Connector` / `ConnectorParsed`
with the literal and both verdicts unchanged — the flip's claim as a test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
* fix: finish the consumer sweep — repo-wide typecheck is green
The remaining sites the compiler named, all the same shape as the last commit:
`metadata-protocol`'s seed-loader and search query plumbing hold parse results,
so they name `XParsed`; `ISeedLoaderService`'s request/result/graph follow, and
its contract test's mocks with them.
Two test-typecheck ledgers move because the flip fixed what they recorded, not
because anything was suppressed:
@objectstack/spec 79 files / 691 errors -> 58 / 268
@objectstack/client 3 files / 6 errors -> 0 / 0 (ledger now empty)
Both ledgers' own headers describe their contents as "fixture literals annotated
with a schema OUTPUT type while holding an authored INPUT literal" — which is the
defect ADR-0122 exists to end. No fixture was edited to achieve this; the bare
name simply means the input type now. The four entries that reached zero are
deleted, which is what the shrink-only ratchet requires.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
* docs(changeset): state the measured consumer-migration size
The FROM→TO section claimed the compiler named "1 file outside packages/spec".
It named 40, across 1127 candidate files — still the point being made (every one
was a compile error, none silent), but the number was wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
* refactor(plugin-security): type HeldScope as what resolveHeldScopes builds
`resolveHeldScopes` already normalises every adminScope flag to a boolean
(`!== false` / `=== true`) and the allowlist to a `string[]`, so `HeldScope.scope`
is the PARSED shape and now says so. The previous commit had reached for
`?? true` / `?? false` at the report boundary instead — correct output, but dead
code that reads like a default being applied where none is needed.
The authored scope, which really does arrive as raw JSON with any subset of the
flags stated, keeps the bare `AdminScope` at the two `parseMaybeJson` sites. Both
containment paths there already read it with `!== false` / `=== true`, so the
author state was the honest type for them all along — the flip is what made the
two shapes distinguishable enough to say which is which.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
* fix(spec): make the new ADR-0087 entry render correctly in the upgrade guide
Two defects visible only in the generated output, caught by reading it:
- `surface` carried backticks. `build-upgrade-guide.ts` renders that field INSIDE a
code span (and again inside a table cell), so the nested backticks broke the span —
the reader saw stray backticks mid-sentence. Every neighbouring entry writes
`surface` as plain text for this reason (`actor-user-roles-to-positions` is
`'action body / AI route: ctx.user.roles (req.user.roles)'`); this one now matches,
and is shortened so the table cell stays readable.
- the `rg` command in `acceptanceCriteria` had lost a backslash in escaping and read
`\bw+Input\b` — it matches the literal `w`, so a reader running the acceptance check
verbatim would get no hits and conclude the migration was already done. Now
`\b\w+Input\b`.
Regenerated spec-changes.json and the upgrade guide from the corrected entry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
* fix(core,objectql): migrate the debt-frozen test fixtures the flip named
`check:type-check-debt` caught 19 raw errors the strict-zone typecheck could not:
both packages exclude their own tests, so these live behind a DEBT/TEST_DEBT entry
and only surface on the ratchet's re-measure. Every one is the same misalignment
already fixed in the strict packages, so all 19 are FIXED — neither ledger entry is
raised, and neither `note` needed rewriting.
@objectstack/core, DEBT 111 -> 98 (the recorded number, exactly):
All 13 were TS2345, a code the entry's note did not list at all — the pile's
composition is unchanged otherwise (code-tier still exactly 3: TS18046/TS2739/
TS2352; config-tier 23; noise 72). The fixtures in health-monitor.test.ts and
hot-reload.test.ts write every key of their config explicitly — they ARE parsed
values — but were annotated with the bare name, which this branch made the author
state. Annotating them `PluginHealthCheckParsed` / `HotReloadConfigParsed` restores
exactly what they meant before the flip, which is also what `registerPlugin` takes.
@objectstack/objectql, TEST_DEBT 361 -> 348:
The gate reported +6 against the ledger's 355, but origin/main actually measures
351 — the ledger is 4 above main because of an improvement someone else landed and
did not re-record. Measured against main rather than against the stale number, this
branch added 13 and removed 3, and all 13 are the same shape: `EngineQueryOptions`
annotations on query bags handed to `find`/`findOne`. The engine's parameter type is
byte-identical to main's (`EngineQueryOptionsParsed` IS what main called
`EngineQueryOptions`), so the fix is on the annotation, and it keeps these bags TYPED
rather than cast — `hook-input-shape-contract.test.ts` and the objectstack-ai#4918 ratchet both
exist to stop an erased query-options bag, in test code included.
The entry is deliberately NOT lowered to 348. Four of the seven points of headroom
are that other author's improvement; harvesting them into this PR's ledger edit would
bank someone else's work under this change's name. The ratchet only forbids upward
drift, so leaving 355 is green and honest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015bLZKxxUUk4mahNfn3g3Ed
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationprotocol:aiprotocol:dataprotocol:systemprotocol:uisize/xlteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@qq9340100@claude