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
钉住的同构 schema 717
// 之前
export type Connector = z.infer< typeof ConnectorSchema >;
export type ConnectorInput = z.input< typeof ConnectorSchema >;

// 之后 —— 只加了中间这行
export type Connector = z.infer< typeof ConnectorSchema >;
export type ConnectorParsed = z.infer< typeof ConnectorSchema >;
export type ConnectorInput = z.input< typeof ConnectorSchema >;

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 typecheck TYPECHECK_EXIT=0
pnpm --filter @objectstack/spec test Test Files 326 passed (326) / Tests 8311 passed (8311)
pnpm --filter @objectstack/lint test Test Files 61 passed (61) / Tests 1449 passed | 4 skipped
pnpm check:spec-parsed-alias(含 --self-test) 11 断言过 + 全仓 OK
pnpm check:adr-anchors OK(36 anchored files)
pnpm check:nul-bytes OK(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

claude added 2 commits August 6, 2026 15:59
`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

vercel Bot commented Aug 6, 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 4:44am

Request Review

@github-actions

github-actions Bot commented Aug 6, 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.

@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation ci/cd dependencies Pull requests that update a dependency file protocol:data protocol:system tests protocol:ui protocol:ai tooling labels Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

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

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

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

job conclusion 备注
TypeScript Type Check success 本改动风险最高的 job —— spec 全部八个生成物门都在这里跑
ESLint queued,未分配 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 queue Aug 7, 2026
Merged via the queue into main with commit 7f713b6 Aug 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 free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants