发现于 #4966 (fixture 修正)的实测。属 PD #10 的范围外发现,#4966 内刻意不修(PM 明确把该 issue 的「建议 2」排除在那一单外),交 PM 定级。
事实
一个 flow start 节点写:
config: { timeRelative : { object : 'task' , field : 'due_at' , offsetDays : - 1 } }
TimeRelativeTriggerSchema(packages/spec/src/automation/time-relative-trigger.zod.ts)拒绝它,三条 issue:dateField 缺失、offsetDays 声明是 z.array(z.number().int()).min(1) 而这里是标量、field 是 unrecognized key(它出现在 aliases 表里,但 strictObject 的 aliases 只在 unrecognized_keys 诊断路径上被查阅 —— 是提示表,不是可接受键)。
TimeRelativeTriggerPlugin.start()(packages/triggers/trigger-schedule/src/time-relative-trigger.ts:164)在 bind 期 safeParse,失败即 warn + return —— sweep 永远不装。
authoring 期两条相关 lint 全部沉默。 实测:stack 里 task 对象存在、flow runAs: 'system'、status: 'active'(即除描述符外一切合规):
lintFlowPatterns = []
flowTriggerReadiness = []
原因:
packages/lint/src/lint-flow-patterns.ts:185 —— if (startCfg.timeRelative != null) return 'time-relative';,只看非空,不看形状;
packages/lint/src/validate-flow-trigger-readiness.ts 的 §1b —— 已经读进描述符内部,但只校验 timeRelative.object 是不是本 stack 定义的对象,dateField / withinDays / offsetDays / 未知键一概不看。
节点 config 按 ADR-0018 是开放槽,外层 flow 闸门看不进去;唯一能判定这个描述符的 schema 只在 bind 期跑。所以作者拿到的唯一反馈,是运行时服务器日志里的一行 warn。
建议方向(供 PM 定级,不预设结论)
在 validate-flow-trigger-readiness.ts 里 safeParse(倾向) :该文件已有 §1b 读进 config.timeRelative,失败叙事也完全一致(「the runtime stays quiet about it」)。新增规则 id 例如 flow-time-relative-descriptor-invalid;诊断文案直接转发 zod 的 issue 列表(schema 的 unknown-key 报错本身已带 surface 名 + 「Did you mean」),不需要在 lint 里重写一遍形状知识。severity 由 PM 定:从后果看(声明了 time-relative 触发、运行时永远不绑)接近 error,但该文件现有规则多为 warning。
放到 lint-flow-patterns.ts :判定 time-relative 的代码在那里(userLessTriggerKind),但那个文件是「反模式/形状陷阱」层,直接依赖某个具体 spec schema 会改变它的定位。
什么都不做 :bind 期 warn 已经存在,而且 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001 收紧之后那条 warn 会点名具体键。
倾向 1 的理由在「让 AI 写的元数据难以写错」这一轴上:bind 期的 warn 只在服务器日志里出现一次,AI 作者的反馈回路通常看不到它;os validate 的输出才是它读的东西。同时这不引入任何消费端宽容(PD #12 )—— 只是把 schema 已经作出的判定提前到 authoring 期,判定权仍然唯一地留在 TimeRelativeTriggerSchema。
与 #5482 的关系(交叉引用)
#5482 的方案 1(multi: true 且 filter 为空时告警)与本条同族:flow 节点 config 里「schema 或引擎已经能判定、但 authoring 期无人报警」的约束 。两条若都采纳,建议落在同一层(同一文件、同一批规则 id 前缀),避免第三次各造一处。差别值得写清楚:#5482 那条是「合法但危险」(引擎接受、后果不可逆),本条是「非法且静默」(引擎拒绝、什么也没发生)。
完成判据(若采纳 1)
上面那个描述符在 os validate 下产生一条点名 config.timeRelative 的诊断,文案含 zod 给出的键名;
canonical 描述符({ object, dateField, offsetDays: [-1] })零诊断 —— showcase 的 Task Due Reminder(examples/app-showcase/src/automation/flows/index.ts:1571)与 content/docs/** 的例子必须保持干净;
与 §1b 的 unknown-object 规则不重复报同一件事(对象名错 + 形状同时错时的输出要可读)。
关联:#4966 (本条的来源)、#5482 (同族的另一条)、#4001 、#1874 、ADR-0078、ADR-0018。
发现于 #4966(fixture 修正)的实测。属 PD #10 的范围外发现,#4966 内刻意不修(PM 明确把该 issue 的「建议 2」排除在那一单外),交 PM 定级。
事实
一个 flow start 节点写:
TimeRelativeTriggerSchema(packages/spec/src/automation/time-relative-trigger.zod.ts)拒绝它,三条 issue:dateField缺失、offsetDays声明是z.array(z.number().int()).min(1)而这里是标量、field是 unrecognized key(它出现在aliases表里,但strictObject的aliases只在unrecognized_keys诊断路径上被查阅 —— 是提示表,不是可接受键)。TimeRelativeTriggerPlugin.start()(packages/triggers/trigger-schedule/src/time-relative-trigger.ts:164)在 bind 期safeParse,失败即 warn +return—— sweep 永远不装。task对象存在、flowrunAs: 'system'、status: 'active'(即除描述符外一切合规):原因:
packages/lint/src/lint-flow-patterns.ts:185——if (startCfg.timeRelative != null) return 'time-relative';,只看非空,不看形状;packages/lint/src/validate-flow-trigger-readiness.ts的 §1b —— 已经读进描述符内部,但只校验timeRelative.object是不是本 stack 定义的对象,dateField/withinDays/offsetDays/ 未知键一概不看。节点
config按 ADR-0018 是开放槽,外层 flow 闸门看不进去;唯一能判定这个描述符的 schema 只在 bind 期跑。所以作者拿到的唯一反馈,是运行时服务器日志里的一行 warn。建议方向(供 PM 定级,不预设结论)
validate-flow-trigger-readiness.ts里safeParse(倾向):该文件已有 §1b 读进config.timeRelative,失败叙事也完全一致(「the runtime stays quiet about it」)。新增规则 id 例如flow-time-relative-descriptor-invalid;诊断文案直接转发 zod 的 issue 列表(schema 的 unknown-key 报错本身已带 surface 名 + 「Did you mean」),不需要在 lint 里重写一遍形状知识。severity 由 PM 定:从后果看(声明了 time-relative 触发、运行时永远不绑)接近 error,但该文件现有规则多为 warning。lint-flow-patterns.ts:判定 time-relative 的代码在那里(userLessTriggerKind),但那个文件是「反模式/形状陷阱」层,直接依赖某个具体 spec schema 会改变它的定位。倾向 1 的理由在「让 AI 写的元数据难以写错」这一轴上:bind 期的 warn 只在服务器日志里出现一次,AI 作者的反馈回路通常看不到它;
os validate的输出才是它读的东西。同时这不引入任何消费端宽容(PD #12)—— 只是把 schema 已经作出的判定提前到 authoring 期,判定权仍然唯一地留在TimeRelativeTriggerSchema。与 #5482 的关系(交叉引用)
#5482 的方案 1(
multi: true且filter为空时告警)与本条同族:flow 节点 config 里「schema 或引擎已经能判定、但 authoring 期无人报警」的约束。两条若都采纳,建议落在同一层(同一文件、同一批规则 id 前缀),避免第三次各造一处。差别值得写清楚:#5482 那条是「合法但危险」(引擎接受、后果不可逆),本条是「非法且静默」(引擎拒绝、什么也没发生)。完成判据(若采纳 1)
os validate下产生一条点名config.timeRelative的诊断,文案含 zod 给出的键名;{ object, dateField, offsetDays: [-1] })零诊断 —— showcase 的Task Due Reminder(examples/app-showcase/src/automation/flows/index.ts:1571)与content/docs/**的例子必须保持干净;关联:#4966(本条的来源)、#5482(同族的另一条)、#4001、#1874、ADR-0078、ADR-0018。