fix(lint): report a config.timeRelative descriptor the sweep will refuse, at authoring time (#5496) - #5651
Merged
Conversation
…use (#5496) A flow start node declaring `config.timeRelative` got zero authoring-time diagnostics when the descriptor could not parse. The two rules that look at the slot each looked at something else: `lint-flow-patterns` decides "time-relative flow" from `timeRelative != null` alone, never the shape, and `validate-flow-trigger-readiness`'s existing check reads only `timeRelative.object`, to compare it against the stack's objects. `TimeRelativeTriggerSchema` does reject a bad descriptor, but the only place it ran was BIND time, inside `TimeRelativeTriggerPlugin.start()`, which warns and returns: the sweep is never installed, the flow reports itself armed, and the author's sole feedback is one line in a server log — outside an AI author's feedback loop entirely. New rule `flow-time-relative-descriptor-invalid` (warning) runs that same schema at authoring time and forwards its issue list verbatim, rendered exactly as the bind-time warning renders it. No shape knowledge is re-implemented and no consumer-side tolerance is added: the verdict and its wording stay the schema's, so the rule tracks the descriptor's contract instead of drifting from a second copy of it. The new rule and the existing object-name check decide different facts and cannot report the same one twice — only the stack knows whether an object name exists, only the schema knows the shape. Its guard mirrors the engine's routing predicate character for character, so the rule speaks for exactly the flows the engine hands to the time-relative trigger. Verified: every time-relative descriptor shipped in the repo parses, and `os validate` output on all three example apps is identical before and after. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 5, 2026 22:17
This was referenced Aug 5, 2026
Merged
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.
Fixes #5496
按分诊裁决执行方案 1:在
packages/lint/src/validate-flow-trigger-readiness.ts的 §1b 区域新增规则,对非空config.timeRelative跑TimeRelativeTriggerSchema.safeParse,失败时逐字转发 zod 的 issue 列表。判定权唯一留在 schema —— lint 里没有任何一行复写描述符的形状知识。前提复核(先证实,再动手)
issue 正文的前提在当前
main(e6b1bb0,#5635 今日改过lint-flow-patterns.ts之后)仍然成立。实测:task对象存在、status: 'active'、runAs: 'system',即除描述符外一切合规:两条 lint 全沉默,原因与正文一致:
lint-flow-patterns.ts:254的startCfg.timeRelative != null只看非空;§1b 只读timeRelative.object拿去比对 stack 里的对象名。TimeRelativeTriggerSchema确实拒绝这个描述符,但它唯一运行的地方是 bind 期(TimeRelativeTriggerPlugin.start():warn 后return)—— sweep 永不装,flow 自称已武装,作者唯一的反馈是服务器日志里的一行。对 AI 作者来说那行完全在反馈回路之外,它读的是os validate。改法
新规则
flow-time-relative-descriptor-invalid(severity warning,与该文件现行一致):AutomationEngine的 trigger 解析:config.timeRelative != null && typeof … === 'object'),所以规则只为「引擎真正交给 time-relative trigger 的那些 flow」发言,不多说也不少说。path: message拼接,与 bind 期 warn 的渲染方式完全一致(TimeRelativeTrigger.start()用的就是这个格式),两条通道讲同一个故事、同一种方言。仅做空白折叠:finding 在 CLI 里是一行(• where: message),而 schema 的 guidance 分条带换行。field→dateField),以及 wrong-layer 提示(schedule写在描述符里面时,告诉作者它是同级兄弟)。TimeRelativeTriggerSchema,所以描述符将来加键时规则自动跟上,不会和第二份副本漂移。没有引入任何消费端宽容(PD Add comprehensive test suite for Zod schema validation #12):不加??别名、不做 coercion。packages/lint/src/index.ts补了新规则 id 常量的 barrel 导出(该包只开"."/"./runtime"两个入口,不进 barrel 消费者就拿不到)。三条完成判据(逐条实测,非推断)
① 坏描述符点名
config.timeRelative且含 zod 键名 —— 把 showcase 的Task Due Reminder临时改成 issue 正文那个描述符,跑真的os validate(已还原):三条 zod issue(
dateField缺失、offsetDays需数组、field未知键)全部到位。② canonical 描述符零诊断(A/B 实测) —— 仓库里 10 个真实 time-relative 描述符逐个
safeParse,全部 spec-valid(showcaseTask Due Reminder、content/docs/automation/flows.mdx、content/docs/references/automation/time-relative-trigger.mdx三个例子、各包测试 fixture)。三个 example app 的os validate输出改动前后逐行一致:「before」是真的 before:stash 掉规则、重建
@objectstack/lintdist、重跑同一条命令。三个 app 都 exit=0,showcase 输出里task_due_reminder一次都没出现。③ 与 §1b unknown-object 不重复报同一件事 —— 对象名错 + 形状同时错时的实测输出:
两条 finding、两个 path、两件不同的事:只有 stack 知道对象名存不存在,只有 schema 知道形状。schema 没有 stack 知识,永远报不出对象名;这条规则不读
tr的任何其他键,也永远不报形状之外的事。测试里对此双向设了断言(名字那条不提dateField,形状那条不提'contract')。反向验证(方向是先预测再跑的)
预测:把新规则的
safeParse分支停掉,新增的坏描述符用例应该报零诊断(而不是报错的诊断)—— 因为这正是 issue 的前提。实测方向一致:值得记一笔:断言「沉默」的那几个用例(canonical 全绿、非对象标量不报)在没有规则时也通过 —— 它们本来就该那样,所以它们不是这条规则的红证据,真正的红证据是上面那 7 条。恢复后 31/31 全绿。
另设了一条防漂移 pin:测试从 schema 现场算出期望的 issue 文本再比对,所以 schema 措辞变了规则输出跟着变、测试照样绿,而手抄一份副本在规则里就不会。
验证
pnpm --filter @objectstack/lint testpnpm --filter @objectstack/lint typecheckpnpm --filter @objectstack/lint buildpnpm --filter @objectstack/cli testnode scripts/check-nul-bytes.mjseslint改动文件severity 建议
维持 warning。跑完真实输出后的实感:后果确实接近 error(声明了 time-relative 触发、运行时永远不绑),但转发的 zod 文案里
strictObject会带上 history 那句(「Until #4001 these were dropped silently — …」),在 error 的显示位上噪音偏大;而且该文件现行规则全是 warning,单独升一条会让 flow 家族的 severity 不成体系。若要升,建议与该文件其他 never-fire 类规则(flow-trigger-unknown-event)同批升,并单独一个 PR —— 已发布规则 id 的 severity 变更本身就该独立成 PR。边界(都没碰)
packages/spec的 schema;⛔ 未动 trigger-schedule 的 bind 期行为;⛔ 未动lint-flow-patterns.ts的userLessTriggerKind(反模式层,分诊已否决方案 2);⛔ 未动content/docs/releases/。multi: true且filter为空的 delete_record / update_record 是「按声明清空整个对象」,authoring 期零诊断 —— #3810 的守卫按「条件被抹掉」判定,不按「条件为空」判定 #5482 的接缝:规则 id 取flow-{descriptor}-{verdict}风格(常量 JSDoc 里写明了这是同族第一条、下一条照此取名),落位留在 §1b 之后、§1c 之前的相邻空间。⛔ 未实现、未预埋它的任何逻辑。@objectstack/spec会让gen:schema重写packages/spec/authorable-surface.base.json(重锚 baseRev)。那不是本 PR 的改动,已 revert,不在 diff 里。changeset
.changeset/flow-time-relative-descriptor-lint.md——"@objectstack/lint": patch。范围外发现(已另开,均未指派)
config.timeRelative(如timeRelative: 'daily')= 引擎解析不出任何 trigger,flow 永不触发且全层零输出 #5647 —— 标量config.timeRelative(如timeRelative: 'daily')时引擎解析不出任何 trigger,flow 永不触发且全层零输出(连 bind 期 warn 都没有,比本单修的缺口还差一档)。与 flow record trigger: array-formtriggerTypeis silently dead at every layer (no lint, no audit, no runtime warn) #3481 同构。不在本 PR 修:覆盖它需要放宽isTimeRelative,而该变量同时喂 §1b 的 unknown-object 规则和isAutoTriggered(draft-status 规则),会改变另外两条已发布规则的覆盖面。已实测证实沉默,非推断。FLOW_TRIGGER_UNKNOWN_EVENT规则 id 常量没从@objectstack/lint导出 —— 消费者拿不到,只能对字面量 #5648(finding标签,观察类)——FLOW_TRIGGER_UNKNOWN_EVENT规则 id 常量没进 barrel,消费者拿不到,只能对字面量。本 PR 只补了自己新增的那一行,补别人的漏项属范围外。