feat(spec)!: 收紧 automation/ 主体剩余九个可授权形状的未知键(#4001 批 11) - #4974
Merged
Conversation
…keys (#4001 batch 11) zod's default `.strip` discards an undeclared key and parses on. Batch 11 closes the nine remaining shapes in `automation/`'s main body, each rejection naming the surface, the key, and — where the word is recognisable — the canonical spelling. `flow.zod.ts`: the four OUTER shapes were closed earlier; their six nested authoring blocks were not, so the gate rejected `nodee:` at node level while `connectorConfig: { connectorID }` parsed clean and dispatched against an undefined connector id. Now strict: `FlowNode.connectorConfig` / `.position` / `.inputSchema` / `.waitEventConfig` / `.boundaryConfig`, `Flow.errorHandling`. Deliberately still open, now pinned in code AND tests so a later sweep stops rather than "finishing" the file: the node `config` slot (ADR-0018) and `FlowVersionHistorySchema` (emitted, not authored — its `definition` is `FlowSchema`, so the authored half is gated anyway). Every alias is a real in-repo spelling of the same knob on a neighbouring surface, taken from an AST census over the repo's own payloads, not guessed: `backoffMs` from `shared/retry-policy.zod.ts` (#4661), `initialDelayMs` / `maxDelayMs` from connector `RetryConfig`, `attachedToRef` / `cancelActivity` from BPMN 2.0. Two cases get a prescription instead of a rename because a rename would be wrong: `inputSchema.optional` is the opposite polarity of `required`, and `errorHandling.maxAttempts` counts the first attempt while `maxRetries` does not — renaming it alone would quietly run one attempt fewer. `time-relative-trigger.zod.ts`: the schema #4852 made visible (its only site was line-wrapped, the old counter read zero, and a zero-site file is skipped by the coverage walk). It sits under the open node `config` slot, so it is the only key gate there is — and it is `safeParse`d at BIND time, so `offsetDay` beside a valid mode used to bind a sweep with the author's narrowing discarded and report itself configured. `flow-function.zod.ts`: binds at authoring only (boot reads `normalizeFlowFunctionEntry`), stated in the code rather than implied. That is why it matters: the reader takes two keys and ignores the rest by construction, so a misspelled `effect` was dropped and then not looked for — the function ran and its writes were counted as none, keeping #4354's broken-sweep query silent. `webhook.zod.ts`: the ledger's `(p)` resolved to the opposite of "spec-only" — three parse doors, one of them `bootstrapDeclaredWebhooks` at BOOT, which warns and SKIPS on failure. Hence the ADR-0010 envelope in the same commit: `applyProtection` stamps `_packageId`/`_provenance` on every type, so closing the shape without declaring them would have turned every package-shipped webhook into a skipped subscription after a redeploy. Strictness also rides `.extend()` onto the connector `WebhookConfigSchema` — verified against real zod and pinned in `connector.test.ts`. Ledger: `automation/` 67 → 58 strip; three rows reached zero and were deleted by the #4852 reverse pin. Gate re-proved red in both directions before trusting it, and every new assertion was proved to depend on the tightening by neutering `strictObject` (19 red, restored byte-identical). Part of #4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
`gen:docs` only — the property tables for `automation/Webhook` and the `integration/WebhookConfig` extension gain the `protection` block and the seven `_lock*`/`_provenance`/`_package*` rows. Checked for the #3746 trap: no page description or frontmatter moved, so no internal comment leaked onto a public reference page. Part of #4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Self-review against the actual behaviour, before the claim shipped. The
`connectorConfig` / `boundaryConfig` / `position` / `inputSchema` prose named a
slip on a REQUIRED key (`connectorID`, `attachedToRef`) as the silent case. It
never was: a required key spelled wrong then reads as MISSING, which zod has
always rejected loudly. Nothing was silent about it.
What `.strip` actually swallowed is the OPTIONAL half — the mapped `input` map
(dispatched as `input ?? {}`: a successful connector call carrying nothing),
BPMN's `cancelActivity: false` (so `interrupting` fell to its `true` default and
a NON-interrupting event cancelled the host anyway), `optional: false` (so
`required` fell to false and `validateNodeInputSchemas` had nothing to require),
a canvas hint beside x/y. I.e. exactly the keys an author adds to CONSTRAIN
behaviour, replaced by a permissive default.
The corrected version is the stronger argument as well as the true one, and
getting it wrong is the failure mode this campaign has already paid for four
times (the withdrawn guidance prescriptions): rejection prose is behaviour, not
documentation. Fixed in the schemas, the module comment, the tests and the
ledger row; a test now pins the required-key case as loud so the distinction
cannot quietly re-blur.
Part of #4001
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Ledger conflict was expected and is the three-way `automation/` header the dispatch note warned about: 批 9 (#4925) landed on main while this branch was open, and both sides edit the same section. Resolved by keeping BOTH sides' row edits and recomputing the header from the merged rows rather than taking a side: 批 9 removed builtin-node-config (8) / schemaless-node-config (4) / io-node-config (2), and rewrote their triage notes 批 11 removed flow-function (1) / time-relative-trigger (1) / webhook (1), and dropped flow.zod.ts 7 → 1 header 67 → 44 strip of 75 (13+10+1+6+5+5+4, summed from the rows) authorable 41 → 18 (etl 7 + state-machine 6 + control-flow 5) `check:strictness-ledger` arbitrates that arithmetic and passes; the header was NOT copied from either side. os-regen four-step ran in full: merge → `git checkout origin/main --` every `merge=os-regen` path → `pnpm install --frozen-lockfile` + rebuild spec + `check:generated --fix` (regenerated `content/docs/references/**` only) → assert BOTH sides survive. Sibling entries verified present after the wholesale regeneration: 批 9's NotifyConfig / HttpConfig / Script node-config pages and #4922's `data/SortNode` are all in `authorable-surface.json` beside this branch's `automation/Webhook:_packageId` / `:protection`. Merged-tree verification (§10: main touched packages/spec, the same package as this diff, so the full suite was re-run rather than scoped): check:generated 8/8 · all 8 pure audits PASS (incl. strictness-ledger) @objectstack/spec 297 files / 7450 tests · tsc --noEmit clean Part of #4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
Second serial sync. One conflict, the same `automation/` section as last time, and it arrived exactly as 批 10 predicted in the paragraph it left there. Resolution — both waves' rows kept, header and subtotal recomputed from the SURVIVING rows rather than resolved in favour of a side: surviving rows execution 13 · etl 10 · flow 1 · bpmn-interop 5 · node-executor 4 header 44 (HEAD) / 42 (main) -> 33 strip of 75 subtotal 18 (HEAD) / 16 (main) -> 7 authorable of 33 Both incoming numbers were right against their own branch and wrong against the merge: mine counted 批 10's deletions as still present, main's counted mine. That is the third instance in one day of the shape 批 10 wrote down — the header CONFLICTS and gets attention, while the subtotal line conflicts with nothing and merges clean while being wrong on both sides. The paragraph is rewritten as a per-wave table plus the rule stated mechanically, so the next batch does not have to rediscover it a fourth time. `check:strictness-ledger` arbitrates, and passes. Milestone worth noting in the ledger prose: with 批 9 + 批 10 + 批 11 merged, `automation/`'s authorable strip is **etl.zod.ts alone** (7 sites). The other 26 are wire — including `flow.zod.ts`'s last site `FlowVersionHistorySchema`, which is why that file still has a row while having 0 authorable left and must not be read as unfinished work. os-regen four-step ran in full. The driver deferred `authorable-surface.json` and `references/integration/connector.mdx` this time (recorded in os-regen-pending) rather than text-merging them. checkout origin/main for every `merge=os-regen` path -> install + rebuild spec -> `check:generated --fix` (regenerated `content/docs/references/**` only) -> assert BOTH waves survive: 批 10's control-flow/state-machine reference pages and its 21 TryCatch/Loop/StateNode/Transition authorable-surface entries coexist with this branch's `automation/Webhook:_packageId` / `:protection` and its two pages. The only deletions in the merged artifacts belong to main's own `connector.rateLimitConfig` retirement (#4911/#4947), not to either batch. Verification on the merged tree: check:generated 8/8 · all 8 pure audits PASS check:strictness-ledger: 42 open file(s) / 289 strip site(s) — counts match @objectstack/spec 297 files / 7474 tests · tsc --noEmit clean Part of #4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
xuyushun441-sys
marked this pull request as ready for review
August 3, 2026 18:26
xuyushun441-sys
enabled auto-merge
August 3, 2026 18:26
xuyushun441-sys
pushed a commit
that referenced
this pull request
Aug 3, 2026
The merge of origin/main resolved `authorable-surface.json` to this branch's side, silently dropping the 16 rows #4974 (#4001 批 11) added for `automation/Webhook` and `integration/WebhookConfig` — main's own committed baseline had them. Regenerating via `gen:schema` (the gate's own proof path, never a hand edit) restores both sides: the sibling's webhook rows and this branch's `ui/DashboardWidget:responsive [RETIRED]`. Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #4001
批 11:
automation/主体剩余九个 strip 站点(重测报告 comment 5165088968 的批次表 A 第 11 行)。零猜测项,无 needs_decision。改了什么
automation/flow.zod.tsFlowNode.connectorConfig/.position/.inputSchema/.waitEventConfig/.boundaryConfig、Flow.errorHandling→strictObjectautomation/time-relative-trigger.zod.tsTimeRelativeTriggerSchema→strictObject(.refine保留)automation/flow-function.zod.tsFlowFunctionDeclarationSchema→strictObjectautomation/webhook.zod.tsWebhookSchema→strictObject+ ADR-0010 信封⛔ 刻意留开、且已在代码与测试里就地钉住(不是漏网):
FlowVersionHistorySchema(本文件唯一的 wire 形状)与 nodeconfig槽位(ADR-0018)。两处都写了理由,让下一轮扫荡在这里停下而不是「把文件做完」。三件值得单独看的
一、
flow.zod.ts是本战役第 17 条发现出现在它自己的文件里四个外壳(flow / node / edge / variable)在第一步就关了,内层六个块没关。于是闸门会拒绝节点级的
nodee:,却让干净通过 —— executor 读的是
input ?? {},整张映射输入变成空对象,调用成功、什么也没带。写守卫的人站在哪扇门,就把门装在哪。这里有一处必须说准,因为最顺口的例子是错的:写错一个必填键(
connectorID/attachedToRef)从来都是响的 —— 必填键随即读作缺失。.strip吞掉的是可选的那一半:输入映射、重试预算、interrupting: false、required: true—— 恰恰是作者用来收紧行为的那些键,被无声换回宽松默认值。三处 history 文案与测试都按这个事实写,而不是按更好听的那个(自查发现,单独一个 commit 改正)。二、
webhook的(p)解出来是「spec-only」的反面,而且必须同时补信封台账原行写
authorable (p)/ 「spec-only (#3461)」。实测三个 parse 门,其中一个是 boot:defineWebhook();defineStack({ webhooks })(StackSchema持z.array(WebhookSchema));plugin-webhooks的bootstrapDeclaredWebhooks(bootstrap-declared-webhooks.ts:114)在物化进sys_webhook前逐条.parse(),失败即 warn + 跳过。第 3 条同时是信封必须同批落地的原因:两条元数据加载路径都对每一个类型调
applyProtection,所以随包加载的 webhook 到达 boot 解析时已经带着_packageId/_provenance。.strip把它们悄悄丢掉;只收紧而不声明,会把每一个随包发布的 webhook 变成 redeploy 之后消失的订阅,只留一行 warn。这正是注册类型批次还过八次的「信封欠债」——webhook不是注册类型(BUILTIN_METADATA_TYPE_SCHEMAS里没有它),所以守那八个的不变量测试从来没往这边看过。顺带:strictness 会沿
.extend()传到integration/connector.zod.ts的WebhookConfigSchema(对着真 zod 验过,不是推断),已在connector.test.ts就地钉住。三、
time-relative-trigger的收紧落在 BIND 时,不只是授权时它是 #4852 让其可见的那个文件。实测
TimeRelativeTriggerPlugin.start()对binding.config.timeRelative做safeParse(time-relative-trigger.ts:164)。而这个描述符住在刻意开放的 nodeconfig槽位下面,所以它是唯一的键闸门。行为差:{ …valid, offsetDay: 7 }以前会绑上一个每天跑、但作者的收窄被丢掉的 sweep,并报告「已配置」;现在拒绝绑定,warning 里带着键名和改法。flow-function则相反,只在授权时绑(boot 读的是normalizeFlowFunctionEntry,不是 parse)—— 这条写进了代码,因为一次收紧不该宣称自己够不到的射程。它仍然值得做,理由恰是让它初看多余的那一点:那个 reader 按构造只取两个键、忽略其余,所以拼错的effect先被 schema 丢掉、再被 reader 不去找 —— 函数照常注册、照常运行,它的写入被计为零,正好让 #4354 的 broken-sweep 查询在最该响的那次运行上保持沉默。别名表:每一条都是仓里的实测拼法,不是设想的错别字
先跑了一遍全仓 AST 普查(按 flow-node / flow / webhook / functions-entry 形状定位真实载荷,而不是按键名瞎抓 —— 按键名抓
handler会得到 490 个命中,没有一个是这个 schema 的载荷),再据实策展:errorHandling.backoffMsretryDelayMsshared/retry-policy.zod.ts(#4661 收敛后的正统拼法,且它给retryDelayMs立了墓碑)errorHandling.initialDelayMs/maxDelayMsretryDelayMs/maxRetryDelayMsintegration/connector.zod.tsRetryConfigSchemaboundaryConfig.attachedToRef/cancelActivityattachedToNodeId/interruptingbpmn-interop.zod.ts存在就是为了导入第三方定义)webhook.object_name/activeobject/isActivesys_webhook的列名(mapWebhookToRow的 remap),从行反向重写元数据的人正是这条路径timeRelative.field/filters/objectNamedateField/filter/object两处刻意不给改名,给处方 —— 改名在这两处是错的:
inputSchema.optional:与required极性相反。只说「你是不是想写required」而不说往哪边翻,就是本战役自己撤回过的那四条「自信写错的处方」。errorHandling.maxAttempts:connectorRetryConfig的maxAttempts含首次尝试,maxRetries不含。裸改名会静默少跑一次,所以写成maxRetries: ( maxAttempts - 1 )。同理,
waitEventConfig.timeout的处方指向timerDuration而绝不指向墓碑timeoutMs—— 那是第 7 条发现(triggerPhrase→triggerPhrases→ 墓碑)的形状。webhook的extraKeys也因此故意不列events:它同时是本表的别名目标,列进去会让基面作者被走两次拒绝、走进一个基面不接受的键 —— 第 7 条经由手写extraKeys而非 shape 到达的新入口(acceptsNothing守的是前者,后者无人守)。先证红,再信绿
strictObject的返回改成z.object(shape)(strip),四个文件一次覆盖 → 19 条断言报红,恢复后 helper 与origin/main逐字节相同(git diff空)。即每一条新断言都真的依赖这次收紧,没有一条是恒真的。`automation/flow.zod.ts` declares 2 strip of 11, found 1 of 11;把已归零的webhook.zod.ts行塞回去 →has NO strip sites left, but still has a row+header says 58 strip, rows sum to 59。恢复后绿。.extend()继承:先用独立 zod 探针证明.strict()与 error map 都会被.extend()带走,再据此写extraKeys与connector.test.ts的钉子 —— 台账第 16 条发现(.extend()陷阱)反向验证。ADR-0087:三个示例应用的构建产物逐条按新契约 parse
不是读源码,是读
dist/objectstack.json—— 过了defineStack、转换层与 handler lowering 之后、运行时真正拿到的那份形状。一条如实说明:三个 app 的
functions都写成裸 handler,所以构建产物里没有声明记录(只有 lowered string ref),FlowFunctionDeclarationSchema这条在产物侧无覆盖 —— 覆盖来自单测与defineStack那扇门。顺带核实过 CLI 的lowerCallables确实把声明记录原样保留进产物,而mergeRuntimeModule在任何人解析之前就把 callable 装回记录里,所以 schema 永远不会看到 lowered 形态(artifact-function-declarations.test.ts已就地钉住)。一度怀疑这里有effect在构建后丢失的缺口,实测不成立,故未立单。三个
validate全过:✓ Validation passed(showcase 903ms / crm 271ms / todo 247ms)。台账 + 连续两轮串行同步(已发生,已解)
本批单独作用:
automation/剩余 strip −9 站点 / −3 文件(flow.zod.ts7 → 1,另三个文件归零、行由 #4852 反向闸删除)。批 9(#4925)与批 10(#4973)先后落 main,本分支各做了一轮同步。两次冲突都在同一段,且第二次正是批 10 就地写下的那个形状:
builtin-node-config(8) ·schemaless-node-config(4) ·io-node-config(2)control-flow(5) ·state-machine(6)flow-function(1) ·time-relative-trigger(1) ·webhook(1)flow.zod.ts7 → 1每个 PR 都只按自己的删除量去减表头。于是 git 把行干净合并(互不重叠),只留表头冲突 —— 而紧挨着的小计行不冲突任何东西,干净地合并成一个两边都错的数。表头 44(本分支)/ 42(main),小计 18 / 16,各自对自己的分支都是对的。
双方全留,表头与小计一律从存活行重算:存活行 =
execution13 +etl10 +flow1 +bpmn-interop5 +node-executor4 → 表头 33 strip of 75,可授权 7 of 33。check:strictness-ledger是裁判,实跑:批 10 那段散文已改写成「逐波次表格 + 机械化规则」,并记上这是同一天内第三次,免得下一批第四次重新发现它。
顺带一个里程碑:三批合流后,
automation/的可授权 strip 只剩etl.zod.ts一个文件 7 个站点(批 12 的目标)。其余 26 个全是 wire —— 包括flow.zod.ts最后那个FlowVersionHistorySchema,这也是为什么该文件还有行却已经 0 可授权,不该被读成未完成的工作。os-regen 四步两轮都全走(⛔ 未 rebase / 未 force-push)。第二轮驱动把
authorable-surface.json与references/integration/connector.mdx记进 os-regen-pending 而非文本合并,整体重生成后 pre-commit 校验为 current 并清除标记。断言两边条目共存:批 10 的 control-flow / state-machine 参考页与它的 21 条TryCatch/Loop/StateNode/Transitionauthorable-surface 条目,和本分支的automation/Webhook:_packageId/:protection及两张参考页并存;合并产物里唯一的删除属于 main 自己的connector.rateLimitConfig退役(#4911/#4947),不属于任何一批。验证
本批改动本身(合并前):
@objectstack/spec全量 295 文件 / 7393 用例通过,tsc --noEmit干净@objectstack/lint/@objectstack/metadata/@objectstack/runtime全绿(runtime 80 文件 / 1094 用例)pnpm build71/71合并批 10 后复验(§10:main 动的是
packages/spec,与本 diff 同包,故重跑全量):check:generated8/8livenessempty-statereact-declaration-parityskill-examplesvariant-docsstrictness-ledgerexported-anydual-source-exports逐个实跑 PASS@objectstack/spec297 文件 / 7474 用例通过,tsc --noEmit干净gen:docs重生成的参考页已按 #3746 的坑逐行核对 —— 页面 description / frontmatter 零变动,没有内部注释漏进公开参考页。一处顺带的文档更正
content/docs/automation/flows.mdx的waitEventConfig行把timeoutMs/onTimeout列为成员 —— 这两个键 17 就退役了(#4158),手写文档没跟上。就在本次收紧的那个块上,留着等于教作者写会被拒绝的键,故一行改掉。⛔content/docs/releases/零触碰。衍生(均实核存在,已立单,本 PR 不修)
waitEventConfig.onTimeout,发布即 422 objectui#3316 —— Flow 设计器的defaultNodeExtras('wait')给每一个新建 wait 节点种下 spec 17 已退役的onTimeout,发布即硬错。不是本批造成的(retiredKey今天就拒)。同一次退役的 i18n 侧 objectui 已经跟上(fix(create-objectstack): scope skill installs to the curated skills/ catalog #3101 就地写着),产出这个键的默认块没跟上。flow.errorHandling是匿名内联块,检查照不到它 #4964 —— 重试策略在仓里有三份形状、两种拼法。spec 双源清账 C8:RetryPolicy / RetryPolicySchema(./automation ≠ ./system)—— 2 条 #4661 收敛的判据是「同名导出」,而flow.errorHandling是匿名内联块,按构造不在那个检查的视野里。作者先读较新的文件学到backoffMs,写到 flow 上以前是静默降到默认 1000ms。方向题(收编 vs 保留两份),已给两轴论证与倾向,待维护者裁。lint-flow-patterns.test.ts的 fixture 教了一个永远绑不上的timeRelative描述符(#4001 第一类发现的第八例) #4966 ——packages/lint的 fixture 教了一个永远绑不上的timeRelative描述符(field而非dateField、offsetDays: -1而非数组)。收紧后依然全绿,因为它根本不经过那个 schema —— 与 spec 自己的 flow fixture 教了三种跑不通的形状:CRUD 的recordId、decision 的condition、object(#4001 第一类发现的第七例) #4924 同类第八例。