Skip to content

fix(spec): automation/etl.zod.ts 的九个别名回到 X / XParsed house convention (#4963) - #5514

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-4963-etl-parsed-aliases
Aug 5, 2026
Merged

fix(spec): automation/etl.zod.ts 的九个别名回到 X / XParsed house convention (#4963)#5514
os-zhuang merged 4 commits into
mainfrom
claude/issue-4963-etl-parsed-aliases

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#4963

裁定按认领评论取 A,一次做完:裸名翻转为 z.input(作者写的形状),新增九个 *Parsed = z.infer(parse 之后的形状),两个 factory 回归设计意图,SYNC_ARCHITECTURE.md 两段示例修到可编译。

前提复核(先证,再改)

九个别名现状 —— origin/main 上确认九个全是 z.infer 且零 *Parsed 对应物,issue 描述属实。

三仓零 importer —— 三个仓都先 git fetch,再对各自 origin/maingit -C 复测(不用 cd):

origin/main HEADETL 符号命中
objectstack0285f7f99packages/spec/src/automation/etl.{zod,test}.ts 自身 + 文档/changeset/生成物;etl.test.ts 只 import schema,不 import 任何类型别名
objectui4ed8250770
cloud715544180

两个 sibling 仓均有权限,无需退回本仓证据。迁移面为空。

为什么这不是纯风格问题(实测,不是目测)

本文件六个键带 .default(),外加 scheduleCronExpressionInputSchema(transform,输出是 { dialect, source } 信封)。在 z.infer 下这些全部必填、裸 cron 字符串被拒。把翻转前的 etl.zod.ts 放回、只留新测试跑一遍,编译器报的就是这些:

doc-example-0:
TS2741: Property 'continueOnError' is missing in type '{ type: "join"; ... }' (x3)
TS2322: Type 'string' is not assignable to type '{ dialect: "cel" | "cron" | "template"; ... }'
author-source:
TS2741: Property 'enabled' is missing in type '{ cursorField: string; }'
author-destination:
TS2741: Property 'writeMode' is missing in type '{ type: "database"; config: { table: string; }; }'
author-run:
TS2739: Type '{}' is missing the following properties ...: recordsRead, recordsWritten, recordsErrored, bytesProcessed

翻转后全部为空。

改了什么

  1. 九对别名(packages/spec/src/automation/etl.zod.ts)。裸名 = z.input,新增九个 *Parsed = z.infer。四个 enum 别名(ETLEndpointType / ETLTransformationType / ETLSyncMode / ETLRunStatus)两者同型,仍然成对 —— 理由写在文件里:约定的价值就是读者不必先知道九个里哪个带默认值才能选注解,而一个 enum 日后加 .transform() 就会重开这个 issue。ETLPipelineRun 是 wire 形状,也照同一条规则走,与 automation/ 里另一个 wire 形状 FlowVersionHistory 一致。
  2. 两个 factory 回归设计意图。删掉 enabled: true(纯粹在复述 schema 自己的默认值,只因 z.infer 让键必填才写);schedule 直接透传,不再 typeof s === 'string' ? { dialect: 'cron', source: s } : s 预包装 —— 归一化本来就该在 parse 时发生。保留各自的 syncMode / writeMode:那是两个 helper 各自决定的东西(incremental+upsert vs full+append),这组对比正是这对 helper 存在的理由,读者不该为看懂它去查两个默认值。
  3. SYNC_ARCHITECTURE.md。三段 ETLPipeline 示例全部修到可编译并以「作者形状」示范(默认键省略)。issue 说翻转后「只剩 source.config 必填一个错误」—— 对 "After (L2)" 成立,对 "Before (L2)" 不成立:那段还缺 namedestination(两个都是必填、与默认值无关),一并补齐。
  4. strictness 台账(docs/audits/2026-07-unknown-key-strictness-ledger.md)。etl.zod.ts 行的"仍未关闭"尾巴改为已关闭,并记下这批分类留下的教训:「因为导出的类型就是授权门,所以是 authorable」是一个需要编译验证的断言,不是可以假定的事实 —— 批 12 门读对了,没人编译过。
  5. 生成物:gen:api-surface 整体重生成,+9 −0,无手改。

测试

新增 packages/spec/src/automation/etl-author-shape.test.ts,走 compiler API 而不是类型级 pin —— #4642 已经确认 packages/spec 里的条件类型 pin 是 no-op(tsconfig.json 排除 **/*.test.ts,vitest 从不开 typecheck),所以 @ts-expect-error / expectTypeOf 在这个包里没人读。测试自带 anti-vacuity 守卫,含一个必须报错的 harness-self-test 探针,否则解析失败会表现为「三段示例全绿」。

  • SYNC_ARCHITECTURE.md 的三段 ETL 示例逐字编译(含 import 行,@objectstack/spec/automation 通过 paths 映射到 entry barrel),零诊断。块总数钉死为 6,新增块必须显式分类。
  • 同一份字面量在 ETLPipeline 下编译通过、在 ETLPipelineParsed 下必须红 —— 正负共用一份文档,所以红不可能是「字面量因为别的原因坏了」。
  • 顶层默认键单独一支:除 syncMode / enabled 外全部补齐的字面量,在 ETLPipelineParsed 下必须报 TS2739 并点名这两个键。(合并在上一支里做不到:TypeScript 只报它找到的最深层不匹配就停,嵌套的 writeMode / continueOnError 会把顶层两个键完全遮掉。)

反向验证的方向,是在跑之前先定的,结果分两类,如实报告:

  • 常规红:把翻转前的 etl.zod.ts 放回,author-* 探针和三段文档示例全红(诊断见上)。这一半按预期成立。

  • 红得不是地方:同一次回退里,parsed-* 探针报的是 TS2305: Module '"@objectstack/spec/automation"' has no exported member 'ETLPipelineParsed' / TS2724 … Did you mean 'ETLDestinationParsed' → 'ETLDestination'? —— 名字根本不存在,红是红,但和「方向对不对」无关,不构成证据。所以另做了一次真正的 sabotage:实现打上之后,单把 ETLPipelineParsed 指向 z.input,预期这三条 pin 转红且报「本该有诊断却是空的」。实测:

    × rejects the same pipeline literal under `ETLPipelineParsed`
    AssertionError: expected '' to contain 'writeMode'
    × requires `syncMode` and `enabled` on `ETLPipelineParsed` — the top-level defaults
    AssertionError: expected '' to contain 'TS2739'
    × accepts a bare cron string only on the author side
    AssertionError: expected '' to contain 'Type \'string\' is not assignable'
    Test Files 1 failed (1)
    Tests 3 failed | 13 passed (16)
    

    这才是 parsed 半边的 pin 是活的的证据。改回后 16/16 绿。

命令与结果(合并 origin/main 之后重跑):

pnpm --filter @objectstack/spec typecheck → tsc --noEmit,无输出
pnpm --filter @objectstack/spec test → Test Files 313 passed (313)
Tests 7978 passed (7978)
pnpm --filter @objectstack/spec check:generated → All 9 generated artifacts are up to date
pnpm --filter @objectstack/spec check:dual-source-exports
→ 4197 names / 0 accepted dual-source
pnpm --filter @objectstack/spec check:exported-any
→ 1822 types + 1560 schemas,无 any
pnpm check:doc-authoring / check:docs-audit-scope / check:type-check-coverage
/ check:release-notes / check:nul-bytes → 全绿
eslint packages/spec/src/automation/etl{.zod,-author-shape.test}.ts → 无输出

packages/spec/api-surface.json 相对 origin/main 的 delta:+9 类型,0 移除

范围外发现

未做的事

SYNC_ARCHITECTURE.md 的三段 L3 Connector 示例不在本 PR 的编译门内 —— 它们属于 integration/connector.zod.ts,其中两段用裸 ... 做省略(不是 TypeScript)。测试里把块总数钉死,就是为了让这条豁免不能悄悄扩大。

🤖 Generated with Claude Code

https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D


Generated by Claude Code

…ention (#4963)
裸名翻转为 `z.input`(作者写的形状),新增九个 `*Parsed` = `z.infer`(parse 之后的
形状),与 `shared/retry-policy.zod.ts` 记下的 house convention 一致。
翻转之前九个别名全是 `z.infer`,而本文件有六个带 `.default()` 的键,外加
`schedule` 是 `CronExpressionInputSchema` transform —— 于是
`const p: ETLPipeline = { … }`(三仓零 parse site,这就是本文件唯一的授权门)
根本编译不过。SYNC_ARCHITECTURE.md 的三段示例就是证据,同 PR 修到可编译并加
compiler-API 测试逐字编译它们。
两个 ETL factory 去掉为满足自身返回类型而写的 `enabled: true` 与 cron 预包装。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
@vercel

vercelBot commented Aug 5, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 5, 2026 2:55pm

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/l labels Aug 5, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

109 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 packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/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 packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/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/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-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

范围外发现补记 —— #5515

PR body 的「未做的事」里写了 L3 Connector 示例不在本 PR 的编译门内。既然探针已经在手,顺手量了一次,结果值得单独记:那段不是因为 ... 省略而不可编译,它是完整字面量,报四条诊断,其中三条是写了 schema 会拒收的键名/取值:

  • fieldMappings[].sourceField / targetField → schema 是 source / target(shared/mapping.zod.ts:101)。而且 sourceField 是挂了 curated alias 的被拒键 —— connector.test.ts:1028 明写 // a real alias, deliberately: the message must name it
  • transform.type: 'custom' → 枚举只有 map / lookup / constant / cast / javascript
  • webhooks[].retryPolicyWebhookConfigSchema 没有这个键。

第四条是 cron 字符串被拒,根因和本 issue 同款:connector.zod.ts:742Connectorz.infer(该文件用的是第三种命名 ConnectorInput,不是 house convention 的 XParsed)。但那边迁移面不为空(20 个 z.infer 裸名别名,且在 live parse path 上),不能照搬本 PR「零 importer 所以一次做完」的路线,所以没有顺手带进本 PR。

已 file 为 #5515(未打标签,交 PM triage)。本 PR 里把该文档 ```typescript 块总数钉死为 6,就是为了让 L3 那三段进不进门是一个必须显式做的决定。


Generated by Claude Code

原文写「六个键」却列了五个具名键 + 一整个 retry 块,内部不自洽;改为逐类点名
(五个具名键 + retry 的五个 + stats 的四个),不再给一个含混的总数。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
原注释说三段 L3 块「两段用 ... 省略」,读起来像第三段没问题;实测第三段
(sapConnector)是完整字面量、报四条诊断,其中三条写的是 schema 会拒收的
键名/取值。已 file 为 #5515,注释直接点名,免得下一个读者以为那是已审阅的豁免。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
@os-zhuang
os-zhuang marked this pull request as ready for review August 5, 2026 15:49
@os-zhuang
os-zhuang added this pull request to the merge queueAug 5, 2026
Merged via the queue into main with commit 4dfd002Aug 5, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4963-etl-parsed-aliases branch August 5, 2026 16:12
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
…d gate it (objectstack-ai#5515) (objectstack-ai#5603)
The `sapConnector` example taught four spellings `integration/connector.zod.ts`
turns down. Measured with the compiler API before the fix, verbatim:
TS2353 'sourceField' does not exist in '{ source: string; target: string; ... }'
TS2322 '"custom"' is not assignable to
'"map" | "lookup" | "constant" | "cast" | "javascript"'
TS2353 'retryPolicy' does not exist in the webhook shape
TS2322 'string' is not assignable to '{ dialect: "cel"|"cron"|"template"; ... }'
Fixed in the document:
- fieldMappings[].sourceField/targetField -> source/target, the canonical
spelling of the base protocol in shared/mapping.zod.ts.
- transform { type: 'custom', function } -> { type: 'javascript', expression },
the nearest real member of the five-way discriminated union. The bare
string is ExpressionInput shorthand and parses to { dialect, source }.
- webhooks[].retryPolicy removed. WebhookSchema is a strictObject and
already carries a curated tombstone for it (objectstack-ai#3494): delivery retries are
owned by the messaging outbox on a fixed schedule. There is no equivalent
key, so the block becomes a comment saying why -- and saying that the
sibling `retryConfig` is a different thing (the connector's own calls).
- the annotation is now ConnectorInput (z.input), which is what an author
writes. The bare `Connector` is z.infer on this file, so it is the shape a
parse RETURNS. Flipping this file's 20 aliases to the objectstack-ai#4963 X / XParsed
house convention is a real but separate appetite -- filed as objectstack-ai#5551 -- and
is deliberately NOT done here.
New gate: packages/spec/src/integration/connector-author-shape.test.ts, a
sibling of automation/etl-author-shape.test.ts (objectstack-ai#4963 / PR objectstack-ai#5514) owned by the
schema that owns the example. It compiles the L3 block verbatim, import line
included, with a harness self-test against vacuity; classifies the document's
three `Connector` blocks (two Migration-Guide sketches elide with a bare `...`
and are not TypeScript); restores each of the four defects as a probe that must
go red with a named diagnostic; and pins what the schema SAYS at runtime for
each -- a curated tombstone for `retryPolicy`, a silent strip plus missing
`source`/`target` for `sourceField` (the curated alias for that word lives on
./data's ImportFieldMappingSchema, not this one), a value verdict naming the
five members for 'custom'.
The document's total ```typescript block count is unchanged at 6; the sibling
gate's pin holds.
Fixesobjectstack-ai#5515
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
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

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-zhuang@claude