Skip to content

feat(spec)!: reject unknown keys on the ETL authoring contracts (#4001 批 12) - #4979

Merged
xuyushun441-sys merged 5 commits into
mainfrom
claude/issue-4001-automation-batch12
Aug 3, 2026
Merged

feat(spec)!: reject unknown keys on the ETL authoring contracts (#4001 批 12)#4979
xuyushun441-sys merged 5 commits into
mainfrom
claude/issue-4001-automation-batch12

Conversation

@xuyushun441-sys

@xuyushun441-sys xuyushun441-sys commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Part of #4001 — 批 12,automation/ 主体的最后一批。

automation/etl.zod.ts 的 10 个 strip 点里,7 个收紧、3 个明确留开。本批贡献 −7;叠加期间落地的批 10(#4973)与批 11(#4974)之后,automation/ 剩余 strip 53 → 26,authorable 27 → 0

本 PR 合并后,ruling 的 automation/ 主体清零。 该目录剩下的 26 个 strip 点全部是 wire、全部在强制范围之外:execution 13、bpmn-interop 5、node-executor 4、etl 自己的 3 个 run-state 形状,以及 flow.zod.ts 最后一个 FlowVersionHistorySchema

收紧的 7 个(授权面)

ETLSourceSchema(含 .incremental)、ETLDestinationSchemaETLTransformationSchemaETLPipelineSchema(含 .retry.notifications)。

留开的 3 个,以及为什么这条豁免写在了三个地方

ETLPipelineRunSchema + .stats + .error 保持 tolerant。这些键全部是引擎对已经发生过的一次运行产出的事实:它铸的 id、它到达的 status、它累加的计数器、它捕获的 error。没有人「编写」一份运行结果 —— 手写一份不是用例,是对历史撒谎。所以收紧在这里买不到任何作者保护,却会让「未来引擎多报一个计数器」变成所有既有 reader 的 parse 崩溃(#3712HookContextSchema.provenance 上的形状)。与 FlowVersionHistorySchema 和整个 execution.zod.ts 同一处置、同一理由。

豁免记录在 schema 自己的 JSDoc + etl.test.ts 的 pin + ledger 行 三处,而不只在 ledger 里 —— 一条只有 ledger 记得的决定,下一次扫描时和「没人做完」没有区别。批 11 在 flow.zod.ts 上独立得出了同样的做法(它的 FlowVersionHistorySchema 豁免现在也落在 schema 旁边 + flow.test.ts),两个并行批次收敛到同一形状,这比任何一边单独主张都更能说明它是对的。

verify-before-tightening:结论,以及这次验证的边界

分类与 remeasure 的 7+3 完全一致(analyzeSites 逐点确认)。但验证的方式必须如实说明:

etl.zod.ts 在 objectstack / objectui / cloud 三仓没有任何 parse 点,ETL 也不可达于 ObjectStackDefinitionSchema(实测:stack 根的 JSON Schema 里 cursorField / writeMode / ETL 一个都不出现)。所以两半都无法靠「指向一次活的调用」来判定。

  • 7 个是 authorable,因为导出的 schema 和导出的类型就是授权门:SYNC_ARCHITECTURE.md 与本模块自己的 @example 都在手写 const p: ETLPipeline = { … }。这正是 ledger 已经为 webhook.zod.ts 记录的 spec-only 姿态。
  • 3 个是 wire,依据是形状语义 + 本 campaign 已经 settled 的同类先例,不是任何人今天能指出的 emit 点。这一点写进了代码注释和 ledger:哪天真有 ETL 引擎落地,这条注释就是要重读的东西 —— 如果 run result 变成运维会手写的东西(replay stub、backfill marker),判定就得改。

curation:没有 payload 可扫时,每条都锚在本仓库存在的兄弟契约

批 9 的 curation 是从 630 个真实 flow-node payload 扫出来的。这里一个 payload 都没有,所以换了另一种可核验的锚:每条 alias / guidance 都指向仓库里另一个把同一个意图拼成别的词的契约,并在注释里点名。

条目
timestampFieldcursorField integration/connector.zod.tsDataSyncConfig.timestampField(活的 parse 路径),describe 写的是同一件事
strategywriteMode(destination)/ syncMode(pipeline) connector 的一个 SyncStrategySchema 枚举 full / incremental / upsert / append_only,四个值恰好劈成两半:写入半边是 writeMode,抽取半边是 syncMode
direction → 无此键 DataSyncConfig.direction。ETL 的方向是结构性表达的(谁是 source 谁是 destination),要反向就交换两端
maxRetriesmaxAttempts;retryDelayMsbackoffMs;backoffMultiplier / maxRetryDelayMs / jitter记录在案的缺席 shared/retry-policy.zod.ts(#4661 收敛后的唯一声明)
onErroronFailure 三个仓内 surface 把失败钩子拼成 onError(ui/widget.zod.tsdata/hook.zod.ts 的声明键表、kernel/plugin-loading.zod.ts)

strategy 一词在两个面上解析到不同的键,是这批里最值得单独测的一条:一条全局 alias 会以十足的信心把其中一半的作者引到错的键上。测试专门 pin 了这对。

ETLTransformationSchema 没有 curated table,并在注释里说明原因 —— 沿用 HttpConfigSchema(批 9)的先例:凭空发明无人能证伪的条目,正是这个 campaign 曾经一次性发出四条自信错误处方的方式。

这个文件的主要失败模式不是拼错,是放错层

source / destination / transformation 三者都是「一个小的封闭键集 + 一个开放的 config: z.record(…) 袋子」。所以未声明键在这里更常是错层而非笔误:tableschemaendpointpathformatconditiongroupBy 都是真实且承重的设置,只是应该在下一层。.strip 把它们就地删掉,于是管道 parse 干净、然后拿着一个恰好缺了作者写的那条设置的 config 去跑。

这条指路写在 history 里(附加到该面每一条未知键消息上),而不是逐键的 guidance 表 —— 因为错放的键取自开放袋子的无界词表,逐条枚举就是猜测,而指明目的地不是。

ADR-0087:不需要 conversion,而且这个「绿」是先证红过的

三个 example app 的构建产物(dist/objectstack.json)全量走查:3930 个节点,0 个被新契约拒绝;先注入一个带 timestampField 的合成 pipeline 作负控,探针确实报红,green 才成立。三个 objectstack validate 全部通过。

探针第一版过匹配了,值得记一笔:ETLTransformationSchema 的必需形状 { type, config } 是 flow node 的真子集,且它的 type 枚举与 flow 节点类型词表在 script / map / filter / merge 上重叠。于是 showcase 的两个 flow 节点(showcase_task_completedscriptshowcase_release_signoffmap)被报成 ETL transformation 的回归。它们不是 —— 没有任何东西把 flow node 路由到 ETL 契约,那两个节点归批 9 的 ScriptConfigSchema / MapConfigSchema 管。探针改成 provenance-aware 之后如实排除。照第一版数字报告,就会凭空发明一个 blast radius。

一处发布产物确实动了,不是 no-op

campaign 的「收紧不改变已发布 JSON Schema」这条断言是按方向成立的:build-schemas.ts 首选 io: 'output',而 output 模式下 strip 对象本来就发 additionalProperties: false

ETLPipelineSchema 恰好是那条断言不适用的情形 —— 它在 output 模式下根本转不出来(scheduleCronExpressionInputSchema,一个 transform,"Transforms cannot be represented in JSON Schema"),于是 build 回退到 input 模式;而 input 模式下 strip 什么都不发、strict 发 false。所以发布出来的 pipeline schema 从「未指定」收窄为「封闭」。方向是对的(发布终于和 parse 一致,而不是比 parse 更宽松),但它是一次真实收窄,测试按两个方向都 pin 了。ETLPipelineRun 走 output 模式,未移动(实测其 required 不含带 default 的键,可据此判定走的是哪个方向)。

范围外发现(按 Prime Directive #10 立 issue,未在本 PR 修)

两条都给了双轴分析和建议,都需要维护者裁决,因此没有塞进这个 strictness 批次里顺手做掉。

Ledger,以及两轮串行同步

etl.zod.ts 的 triage 行改为 mixed 并写明 7/3 与分类边界;remaining-strip 行 10 | 10 | mixed3 | 10 | wire

冲突按预期发生了两次 —— 开 PR 后批 10 落地,随后批 11 落地 —— 每次都是行干净合并、只有表头与其下的小计冲突。两次都按同一条规则解决:保留各方的行编辑,由存活的行重算表头与小计,谁的数都不采信,再让 check:strictness-ledger 的算术裁决。

最终存活行:execution 13 + etl 3(本批) + flow 1(批 11) + bpmn-interop 5 + node-executor 4 = 26 strip of 75,authorable 0

散文按双方内容合成:保留批 11 的 wave 表(补上批 12 一行)与它记录的「不冲突的小计反而在每个分支上都是错的」这条教训 —— 并把计数从三次更新为四次,同时点明批 12 一家就撞了两次(对批 10 一次、对批 11 一次),所以规律不是「每批一次」,而是每一对在飞行中重叠的批次一次

并修正了本 PR 自己先前写错的一句:原文声称 etl 是 automation/ 里第一个「缩小但不消失」的行 —— 批 11 的 flow.zod.ts(7 → 1)先做到了,且先落地。已改为第二个,并说明两批是并行独立收敛到同一形状的。

验证(批 11 合并之后重跑,scoped + flock)

pnpm --filter @objectstack/spec test        → 297 files / 7499 tests passed
pnpm --filter @objectstack/spec typecheck   → clean
pnpm --filter @objectstack/spec check:generated → 8/8 up to date
check:strictness-ledger → ✓ 42 open file(s) / 282 strip site(s)
check:liveness / check:exported-any / check:dual-source-exports → ✓
objectstack validate × 3 (showcase / crm / todo) → all pass(批 10 轮次实测)
ADR-0087 probe: 3930 nodes, 0 newly rejected, negative control proven red

新增 25 个测试。三次 sabotage 证明仪器会红:把 ETLDestinationSchema 改回 strip → 4 红;把 ETLPipelineRunSchema 收紧 → 2 红(豁免 pin 生效);把 destination 的 strategy alias 改成 syncMode → 1 红。恢复后全绿。

os-regen 四步在三次 merge 后各走了一遍,三次全量重生成均产出零 diff;每次都断言各方条目共存 —— 本批 35 条 automation/ETL* + 19 个 ETL 导出;批 11 的四张参考页(flow / flow-function / time-relative-trigger / webhook.mdx)与 webhook 信封 19 条 + TimeRelativeTrigger 6 条 + FlowNode 11 条;批 10 的 StateNode 8 条与 StateMachine/ControlFlow 导出;批 9 的 NotifyConfigSchema / ScriptConfigSchema

⛔ 仍为 Draft,由 coordinator ready + auto-merge。

claude added 3 commits August 3, 2026 17:45
…批 12)

Seven strip sites in `automation/etl.zod.ts` close — `ETLSource` (+ its
`.incremental` block), `ETLDestination`, `ETLTransformation`, and
`ETLPipeline` (+ its `.retry` and `.notifications` blocks). `automation/`
remaining-strip: 53 → 46 (authorable 27 → 20).

The file's other three sites — `ETLPipelineRun` + `.stats` + `.error` — are
deliberately left OPEN. Every key on them is a fact the engine produces about
a run that already happened; nobody authors a run result, so strictness buys
no author protection there and would turn a future counter into a parse crash
for every existing reader (the #3712 shape). Same disposition, same reason, as
`FlowVersionHistorySchema` and all of `execution.zod.ts`. The exemption is
recorded on the schema itself and pinned in `etl.test.ts`, not only in the
ledger — a note only the ledger carries is a note the next sweep misses.

Verified before tightening, and the verification has a stated limit: this file
has NO parse site in objectstack / objectui / cloud, so neither half could be
settled by pointing at a live call. The seven are authorable because the
exported schema and type ARE the authoring door (SYNC_ARCHITECTURE.md and the
module's own `@example` both hand-write `const p: ETLPipeline = { … }`) — the
same posture the ledger already records for `webhook.zod.ts`. The three are
wire on the shapes' semantics plus settled precedent, not on an emit site.

Curation is anchored, not invented. With no stored payloads to scan, every
alias and guidance entry names a sibling contract in this repo that spells the
same intent differently: `timestampField` → `cursorField` (connector
`DataSyncConfig`), `onError` → `onFailure` (three in-repo surfaces),
`maxRetries` → `maxAttempts` and the retired `retryDelayMs` → `backoffMs`
(the converged `shared/RetryPolicySchema`, #4661). The connector's one
`strategy` enum splits across two keys here, so it resolves to `writeMode` on
a destination and `syncMode` on a pipeline — a single global alias would
misdirect one of them with full confidence. `direction` gets a documented
absence rather than a rename: an ETL pipeline states direction structurally.

The dominant failure on this file is misplacement, not typing — `table`,
`endpoint`, `path`, `condition` are real settings one level down, inside the
open `config` bag. That pointer lives in `history` (appended to every message)
rather than a per-key table, because the open bag's vocabulary is unbounded
and enumerating it would be guesswork.

One published-artifact consequence, stated rather than glossed: the campaign's
"strictness does not move the JSON Schema" claim is per-direction, and
`ETLPipelineSchema` is the case where the usual direction does not apply — it
cannot convert in output mode (`schedule` is a transform), so build-schemas
falls back to input mode, where strip emits nothing and strict emits
`additionalProperties: false`. The publication now matches the parse; it is a
real narrowing, not a no-op, and it is pinned in both directions.

Two out-of-scope findings filed rather than fixed: #4962 (`retry` is a third
retry-policy vocabulary #4661's convergence never reached) and #4963 (all nine
type aliases export the parsed shape under the bare name, which is why the
SYNC_ARCHITECTURE.md pipeline examples do not compile).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
@vercel

vercel Bot commented Aug 3, 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 3, 2026 6:57pm

Request Review

…omation-batch12

# Conflicts:
#	docs/audits/2026-07-unknown-key-strictness-ledger.md
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

106 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/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/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/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.

…omation-batch12

# Conflicts:
#	docs/audits/2026-07-unknown-key-strictness-ledger.md
@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 18:59
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 3cb0618 Aug 3, 2026
26 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-4001-automation-batch12 branch August 3, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants