feat(spec)!: reject unknown keys on control-flow + state-machine (#4001 批 10) - #4973
Merged
Merged
Conversation
…achine (#4001 批 10) 11 strip sites closed across two files: - control-flow.zod.ts (5): FlowRegion / Loop / ParallelBranch / Parallel / TryCatch - state-machine.zod.ts (6): ActionRef / GuardRef / Transition / StateNode (+.meta) / StateMachine Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
- strictness ledger: automation/ 67->56 strip (rows for the two closed files deleted per the reverse pin), authorable 41->30; both (p) verdicts resolved with the evidence that resolved them - region-slots.test.ts probe rebuilt: it depended on .strip - parse-config.ts doc corrected: unknown keys are no longer this seam's blind spot - major changeset with the full FROM -> TO migration table - regenerated content/docs/references + skill refs (check:generated 8/8) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
批 9 (#4925) landed the same automation/ ledger section. Both waves deleted their own rows and decremented the section header by their own count, so git merged the ROWS cleanly and conflicted only on the header — while the authorable subtotal one line below, which conflicted with nothing, merged clean while being wrong on both branches. Header recomputed from the surviving rows (42 strip of 75, authorable 16 of 42) rather than resolved in favour of either side; check:strictness-ledger's header arithmetic is the arbiter and reads 45 open files / 299 strip sites. os-regen four-step run: merge -> checkout origin/main for every path in .gitattributes -> wholesale regenerate -> assert siblings survived. 批 9's three reference pages and #4909's bulk-action page are byte-identical to origin/main. 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
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
xuyushun441-sys
marked this pull request as ready for review
August 3, 2026 18:00
xuyushun441-sys
pushed a commit
that referenced
this pull request
Aug 3, 2026
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
This was referenced Aug 3, 2026
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 ——
automation/主体第二波(批 9 = #4925 已落地)。11 个 strip 站点关闭,两个文件:
automation/control-flow.zod.tsFlowRegion/Loop/ParallelBranch/Parallel/TryCatchautomation/state-machine.zod.tsActionRef/GuardRef/Transition/StateNode(+.meta)/StateMachine台账实测:
45 open file(s) / 299 strip site(s)(合并 origin/main 前本分支单独读数 49/314;批 9 的 14 站点合并进来后 = 299)。一、
(p)的解决 —— 不是形式,差点得出相反结论两个文件都标
authorable (p),任务书特别点名state-machine的(p)未解决。核下来这一条几乎翻盘:ADR-0020 把这个 XState 形状作为「记录生命周期声明」退役了 —— 顶层
workflow元数据类型和object.stateMachines都已删除,记录的合法流转改由state_machine校验规则(data/validation.zod.ts的扁平{ from: [to] }表,批 3b 已关)承载。若只有这两扇门,本文件就是死表面,按战役自己的规矩该改分类而不是收紧。活下来的门只有一扇,而它确实是授权门:
ai/agent.zod.ts的lifecycle就是StateMachineSchema,而agent是注册元数据类型 ——defineStack({ agents })、POST /api/v1/meta/types/agent、Studio agent 表单三条路径全都经AgentSchema.parse()到达这里。**不是读出来的,是 parse 出来的。**收紧前:
→ 成功,返回
stats没了,meta两个键没了,onn(离on一个键位)连同作者声明的每一条流转一起没了。一个全部意义在于「拒绝未声明流转」的声明,被静默清空成零流转,并报告 valid。 这条实测已固化为测试。control-flow的(p)同样落在执行期 parse(parseNodeConfig,#4277)与validateControlFlow的区域 parse 上。二、兄弟守卫
validateControlFlow—— 不打架,原因值得写下来任务书要求确认二者不冲突。不冲突,因为它们回答的不是一个问题:守卫判结构(single-entry / single-exit / 无环),键闸门判键成员,两者互相都判不了。
它们只在一处相遇,而且是设计上的:
validateControlFlow在分析前对每个 region slot 做safeParse,所以从本批起,区域的未声明键也在那里浮现,形式是守卫的框架(哪个容器、哪个区域、嵌套路径)包着 schema 的处方,没有重复检测,也没有删掉任何东西 —— 守卫值钱的那部分(结构散文)原样保留,它只是不再静默修补自己的输入再去判它。嵌套深度也跟着走(
loop 'c1' body → try_catch 'inner' try,#4389 的同一条路径)。一条连带的姿态变化:
regionSlotsOf给branches[]选ParallelBranchSchema、给其余 slot 选FlowRegionSchema,原本是保真选择(区域 schema 会 strip 掉name);两边都 strict 之后变成正确性选择(区域 schema 会拒绝一个合法分支)。同一行代码,赌注变了,已就地改注释并钉测试。三、两条策展条目是测出来的,不是想出来的
1.
itemVariable—— 战役自己的 helper 在给出一条自信的错答案裸的编辑距离建议器对
itemVariable回答indexVariable:作者要的是当前项,被指向绑定下标的键。照做得到一个变量里装着数字的 loop —— 静默的错误行为,由这场战役的 helper 亲手开出处方。这是账本第 7 条形状的第三例(
pii→min、triggerPhrase→triggerPhrases之后)。alias 优先于编辑距离,所以点名即修复。测试把裸建议器的错误答案也一起钉住,删掉 alias 它会立刻回来 —— 这条 alias 不会被后人当冗余清理掉。
2.
join/joinGateway需要两段不同的散文guidance每个键逐字输出一条 bullet,共用一个字符串会把同一段话印两遍,读起来像错误本身有 bug。已拆成两条:join讲隐式 join(ADR-0031 §Decision 2),joinGateway讲它是 BPMN interop 节点类型(§Decision 5)。其余策展一律要求仓内证据:
guard → cond(schema 自己的.describe()就写着 "Condition (Guard)",且 XState v5 正是把cond改名为guard)、label → name于 parallel 分支(FlowNodeSchema.label在同一个字面量里的每个nodes[]元素上都是必填)、flowName指回map节点、finally指向容器之后的节点、transitions指向对象级校验规则、context讲清它与contextSchema不是改名关系(XState 的context是初始值,这里只声明形状)。四、
StateNode.meta—— 查过 #4909 的开放槽情形,判关闭XState 惯例把
meta当开放袋,所以这是本批唯一需要论证而非直接关闭的一处。三条事实指向关闭:StateNodeConfig类型恰好声明四个meta键 ——passthrough会让 Zod 开着而tsc关着,凭空造出一个新的「声明 ≠ 强制」裂口;meta键(aiInstructions除本文件测试外零引用);meta到达时是{}。没有需要保留的开放性,只有需要终止的静默。 与 #4909 恰是镜像:那里散文说开放而代码 strip,修法是让代码追上散文;这里代码与类型都说关闭,只有 XState 惯例暗示开放。
五、一个 strip 时代的测试仪器被修好(战役第三类教训)
region-slots.test.ts的探针把每个候选键一次性喂给每个 schema(给loop喂try/catch/branches),靠.strip丢弃不属于它的那些。三个容器配置一旦 strict,整个探针 payload 就不再 parse,函数对每个构件都返回[]—— 一个对账测试报告「没有任何 schema 接受区域」。它大声失败了,这也是它在这里只是脚注而不是第四例 finding-3 的唯一原因。修法保持行为式提问不变、去掉对 strip 的依赖:payload 改由 schema 自己声明的键构造,每个构件只被问它真有的 slot。比原来更诚实 —— 原来
loop会被问catch,答案读都不读就丢了。同时补了反向的反空洞断言(探针现在读.shape,读不到就返回[],所以「空结果」必须意味着「没有区域」而不是「问不出来」)。parse-config.ts的文档也就地更正:Unknown keys are NOT this seam's job: Zod's default .strip() drops them silently here这句从本批起不成立。六、验证(每条绿色都先证过红)
六处蓄意破坏,逐一观察报红后恢复:
itemVariablealias× Loop: itemVariable is aliased… expected … to contain 'itemVariable→iteratorVariable'× Parallel: the two join spellings get DISTINCT prescriptionsFlowRegion退回z.objectStateNode.meta改looseObject× refuses undeclared keys at all three depths+× meta is CLOSEDguard → condaliasActionRefunion 分支退回z.object台账闸门两个方向都诱发过红: 反向钉(给已关闭文件留行)→
control-flow.zod.ts has NO strip sites left, but still has a row;表头算术 →header says 67 strip, rows sum to 56。十个
check:*闸门全绿(check:generated8/8,另外 8 个纯检查逐一跑过:liveness / empty-state / react-declaration-parity / skill-examples / variant-docs / strictness-ledger / exported-any / dual-source-exports)。**ADR-0087:无需转换。**三个示例应用
validate全过(showcase 是唯一带真实loop/parallel/try_catch节点的那个):api-surface.json/authorable-surface.json/spec-changes.json/protocol-upgrade-guide.md相对origin/main零 delta(收紧不增删导出、不增删可授权键)。七、合并
origin/main:一次「干净的合并不是能用的合并」的实例批 9(#4925)落地了同一个
automation/台账段。两边都删掉各自的行、并按各自的计数减表头 —— 于是 git 把行干净合并(不重叠),只在表头冲突;而紧挨其下的 authorable 小计与任何东西都不冲突,干净合并,且在两个分支上都是错的。两个数字单独看都不是失误,各自是对一波删除算出来的。表头因此从存活的行重算(42 strip of 75,authorable 16 of 42),而不是选边,由
check:strictness-ledger的表头算术裁定 —— 读数45 open file(s) / 299 strip site(s)。os-regen 四步已走完(merge → 按
.gitattributes逐路径checkout origin/main→ 整体重生成 → 断言兄弟条目存活)。批 9 的三个参考页与 #4909 的bulk-action.mdx与origin/main逐字节相同。⛔
content/docs/releases/零触碰。changeset 为major,带完整 FROM → TO 迁移表;.changeset/pre.json复核仍mode: pre/tag: rc,v17 窗口未关。八、范围外发现(PD#10,已立案未修)
#4971 ——
formatZodError把 union 分支的拒绝压成"Invalid input"。zod 对 union 只抛一个
invalid_union,message是字面量"Invalid input",分支的真实 issue 嵌在issue.errors[]。formatZodError(从包根导出,CLI 路径用)只 map 顶层 issue,不下降。同一格式化器、同一类错误的对照:处方在 payload 里完好(
ZodError.message、REST 错误体都带着),丢的是压成单行的消费者。未在本 PR 修:formatZodError是公开导出,改它会改变全仓每一个 union 的 CLI 输出,不该搭在 spec 收紧 PR 上。批 10 已就地钉住现状(CONTROL — a non-union shape renders its full prescription through formatZodError),修好的那天这条钉会红,提醒把断言翻过来。🤖 Generated with Claude Code
https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9
Generated by Claude Code