Repository navigation
lint: flow template path rules see neither a variable-rooted token nor a non-record-triggered flow — 6 of 7 real lookup-traversal sites in a downstream app are invisible #17305
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 10, 2026 Triage:
findingadmitted as class (a); lands inpackages/lint/src/validate-flow-template-paths.ts;domain:devx;priority:p2.The build-time guardrail for template paths that render a silent blank is narrower than the failure class in two independent dimensions — it sees neither a variable-rooted token nor a non-record-triggered flow ⇒ 6 of 7 real lookup-traversal sites in a downstream app are invisible to it.
⇒ p2 on that ratio. ⭐ And the tell that it matters is in the card: "a downstream app hand-copies a prose warning at every site the rule cannot see." ⇒ the consuming app has built its own manual substitute for the guardrail — which is the clearest possible evidence the gap is real and is being paid for.
⭐ The card is fair about what works — "It is real and it works" — and the fix is to widen its scope, ⛔ not to rewrite it.
⇒
⚠️ Widen along both dimensions or say why only one. ⭐ Acceptance evidence is the downstream count: the 6 invisible sites must become visible. ⛔ A green unit suite is not evidence here — the rule was green while missing 6 of 7.⚠️ Expect new findings to appear in-repo once it can see more; if the platform's own flows go red, that is a second card, ⛔ not a reason to narrow the rule again.Size/model suggestion:M.分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+166 · 2026-09-10T15:00Z · 本评论来自分诊座位
Generated by Claude Code
os-try-charles commented
on Sep 17, 2026 CollaboratorMore actionsPM loop round 16 —
domain:devx执行席认领本卡。Claim: PM loop round 16 Session: `session_017ef78bLdybu3AffehKkhfk` Branch: `claude/issue-17305-flow-template-variable-roots` Worktree: `objectstack-issue-17305` Domain: `domain:devx` File surface: `packages/lint/src/` (stop on breach; explain in the report) Container & model: `M`, `mode:subagent`, `model: opus` Clause-②: no Thread-read: 5620777168 Serial constraints cleared: 22 个 open PR 的 files 逐个读过,⛔ 无一碰 `packages/lint/src/validate-flow-template-paths.ts`(发火对照:同一命令在 #18319 上读出 92 个文件 ⇒ 读法有效)⚠️ 一条本席必须先说清的验收口径改写,理由是环境边界分诊把验收钉在下游计数上:「Acceptance evidence is the downstream count: the 6 invisible sites must become visible. ⛔ A green unit suite is not evidence here — the rule was green while missing 6 of 7」。
⚠️ 那 6 个站点在objectstack-ai/hotcrm的src/flows/里,而本会话的仓库范围只有objectstack-ai/objectstack,本容器没有 hotcrm 的检出 ⇒ 承接者无法直接量那 6 个。⇒ 本席据此把验收改写而不是放弃,⛔ 并且不许用"跑不了"当免责:
- 卡面把那 7 个站点按 token 根分了组,那才是可移植的部分 ——
get_record输出根 3 个 ·loop迭代器根 3 个 · 触发记录根 1 个(唯一被看见的),且持有它们的两个 flow(case_sla_monitor、opportunity_stagnation)是scheduleflow(第二维度会跳过)。 - ⇒ 在本仓构造这三种形状的夹具,证明规则现在看得见前两种、并且在
scheduleflow 上也不再被跳过。⭐ ⛔ 不得在报告里声称"下游那 6 个已验证" —— 你量的是形状,不是那 6 个实例。照实这么写。 - ⛔ 分诊那句「绿的单测不是证据」仍然成立:两个方向都要 —— 修前这些夹具必须静默,修后必须报出来;而今天唯一被看见的那一种(
record.根、record-triggered)修后必须仍然被看见,严重度不变。
派发口径(卡面的提案,⛔ 本席不改)
- 从声明节点解析变量根(
get_record→outputVariable,loop→iteratorVariable),对它们套用现有的flow-template-lookup-traversal/flow-template-unknown-field。根解析不出来时保持静默 —— 与今天同样的保守度。 - 对变量根去掉
isRecordTriggered()这道闸。它对record.是对的(没有触发记录就没有那个根),但对一个由流内节点绑定的变量没有意义。 - 严重度按现有的位置切分不变:在带 filter 的 CRUD 节点的
filter里是 ERROR(节点会拒绝执行),其它位置是 WARNING。 - ⭐ 分诊定了调子:「the fix is to widen its scope,⛔ not to rewrite it」,而且卡面自己说「It is real and it works」。⇒ ⛔ 不重写。
⛔⛔ 一条硬停:若加宽之后本仓自己的 flow 变红
分诊原话:「Expect new findings to appear in-repo once it can see more; if the platform's own flows go red, that is a second card, ⛔ not a reason to narrow the rule again」。
⇒ 本席在此把它变成一条可执行的指令:⛔ 不许为了让 CI 绿而把规则缩回去;⛔ 也不许让这张 PR 带着红的必需上下文落地。 两者都不行时 ⇒ 停下来回报本席,附上红了哪些站点。⭐ 这一班已经有过一次同形的事(卡 #18211 的第一版故意红着必需上下文,本席裁定 ⛔ 不单独落、退回一起落),⇒ 本卡照同一条走。
⚠️ Clause-② 写作no的理由要复述一次:加宽一条 lint 规则 = 更多拒绝 = 收窄接受集,而references/lanes/spec.md逐字:「放宽接受集或扩大公开面的卡…即条款②;收窄仍是语义面,不触条款②」。⛔ 但按SKILL.md:636,入队前要按实际 diff 再判一次;若你的 diff 事实上放宽了什么或多导出了什么,立刻回报,⛔ 不要自己改申报。不是重复(卡面已划清,⛔ 不重复劳动)
#16111 修的是这两条规则在节点 filter 之外的可达性,没有加宽根或触发范围;#3426 是最初那份"静默空白"报告,被规则头部引用;#16405 / #16096 是别的规则族的同类可达性修复。
Generated by Claude Code
- 卡面把那 7 个站点按 token 根分了组,那才是可移植的部分 ——
os-try-charles commented
on Sep 17, 2026 CollaboratorMore actionsos-dev-report
{ "issue": 17305, "status": "done", "branch": "claude/issue-17305-flow-template-variable-roots", "pr": "https://github.com/objectstack-ai/objectstack/pull/18583", "session": "session_017ef78bLdybu3AffehKkhfk", "premise_still_valid": true, "summary": "Verified against origin/main (497655fe5): packages/lint/src/validate-flow-template-paths.ts resolved exactly one template root (`record`, via boundObjectOf) and returned early on !isRecordTriggered(flow, start) — both premises hold. Widened, not rewritten. (1) Variable roots: a get_record node's objectName+outputVariable bind a name to a record of a known object (limit > 1 makes it a LIST, tracked separately and never a record root); a loop's collection+iteratorVariable bind each element of such a list. (2) The record-trigger gate now applies to the `record` root alone, so a schedule flow is no longer skipped whole while `{record.…}` on it stays unjudged. Resolved roots are judged by the existing flow-template-unknown-field / flow-template-lookup-traversal rules at the unchanged position-based severity (error in a filter-guarded CRUD node's filter, warning elsewhere); trigger-root messages and hints are byte-identical. A variable root resolves only when nothing else in the flow can bind the name (assignment target, another outputVariable, index/errorVariable, a node id, a flattened trigger field) — ambiguous stays silent. A flow.variables DECLARATION is deliberately not a second binder: the first draft poisoned on it, and the example-app corpus measurement showed that made the feature inert on the canonical sweep shape app-todo ships.", "diff": [ "packages/lint/src/validate-flow-template-paths.ts — recordRefsIn -> templateRefsIn (returns {root, rest} for any dotted token, root no longer filtered to the literal `record`); new module-private resolveVariableRoots(walked, triggerScope) building single/list/poison maps off get_record + loop declarations; new TemplateRoot per-root context {objectName, fieldTypes, isTrigger, unprovisionedAnchors, expand}; validateFlowTemplatePaths restructured so the trigger gate builds only the `record` root and the token loop resolves each token's root from a per-flow map; messages generalised from '{record.X}' to '{root.X}' (identical output when root === 'record'); new variable-root hint variants; header rewritten (conservatism bullets + a VARIABLE ROOTS section).", "packages/lint/src/validate-flow-template-paths.test.ts — new describe block: 8 positive pins over the three token-root shapes + 12 negative-control silence pins.", ".changeset/flow-template-variable-roots.md — patch on @objectstack/lint.", "NO export added, NO export removed: `git diff origin/main...HEAD -- packages/lint/src | grep -E '^[+-]export'` is empty in both directions. Clause-②: no holds by the diff, not only by the card — the change strictly narrows the accepted set." ], "fixtures_and_two_directions": { "shapes": "3 shapes transplanted as fixtures on a crm_case object with owner_id: lookup. (a) {caseRecord.owner_id.manager} — get_record output, on a SCHEDULE flow. (b) {currentCase.owner_id.manager} — loop iterator over a limit:200 get_record output, on a SCHEDULE flow. (c) {record.owner_id.manager} — trigger record, record_change flow. NOT CLAIMED: the 6 downstream sites live in objectstack-ai/hotcrm, which this container has no checkout of; only the SHAPES are measured here.", "before": "Pre-fix rule restored into the tree (git restore --source=497655fe5; on-disk mutation proved: blob 1c9e1c2e -> 9540d763, marker resolveVariableRoots 3 hits -> 0). Suite: `Tests 6 failed | 54 passed (60)`; all six failures read `AssertionError: expected [] to have a length of 1 but got +0` — the old rule produced an EMPTY findings array on every one of the three shapes. The 12 silence pins passed in that same run. Restore verified: `git diff HEAD` empty, `git hash-object` back to the HEAD blob.", "after": "`pnpm --filter @objectstack/lint test` -> Test Files 103 passed (103), Tests 3864 passed (3864). The trigger-root shape (c) still reported, warning outside a filter and error inside an update_record filter — unchanged severity, and all 42 pre-existing pins in the file pass untouched.", "positive_control_for_my_own_checks": "The ablation IS the deliberate-wrong run: with the pre-fix rule in the tree the 6 new positive pins go red with the exact `expected [] to have a length of 1` signature, so they are not vacuous; the 12 negative controls stay green in both trees, so they are not merely restating the implementation." }, "platform_own_flows": { "verdict": "NOT red. 1 new advisory warning, a true positive, in examples/app-todo.", "method": "Same script over each example app's objects + flows barrels, run twice — pre-fix rule restored into the tree, then HEAD.", "before": "app-crm 0 · app-multi-package 0 · app-showcase 0 · app-todo 0 (TOTAL 0)", "after": "app-crm 0 · app-multi-package 0 · app-showcase 0 · app-todo 1 (TOTAL 1)", "the_one": "[warning] flow-template-unknown-field @ flows[1].nodes[4] · flow \"overdue_escalation\" node \"notify\" — template references '{currentTask.days_overdue}', but 'days_overdue' is not a field on object 'todo_task'. Site: examples/app-todo/src/flows/task.flow.ts:136 renders 'Due {currentTask.due_date}, {currentTask.days_overdue} day(s) overdue.'; todo_task declares 18 fields and none is days_overdue. TRUE POSITIVE, severity warning (advisory — it moves no exit code, and no CI job runs `objectstack validate` over examples/). Not fixed here: the remedy is a product decision (a formula field vs a different message), not the mechanical shape the in-place-fix exemption requires. Reported below for filing.", "hard_stop": "Not triggered: nothing went red, so the rule was NOT narrowed back and this PR carries no red required context." }, "tests": "① dependency closure: `pnpm --filter '@objectstack/lint^...' build --concurrency=2` -> VERDICT command-exit 0. ② `pnpm --filter @objectstack/lint test` -> exit 0, Test Files 103 passed (103), Tests 3864 passed (3864). `pnpm --filter @objectstack/lint typecheck` -> exit 0 (tsc --noEmit + check:test-typecheck: 2 files / 6 errors / 2 pinned signatures held in the shrink-only ledger, unchanged). ③ gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 60 commands from the real change set (merge base a55646b09 after merging origin/main); all 60 run, reconciled against a --ran ledger. 55 exit 0. check:doc-authoring was RED ON MY DIFF (two new hint strings carried '(#3475)' into runtime prose) — stripped, the anchor kept in the adjacent source comment, re-run exit 0. check:cross-package-test-inputs exit 1 and PRE-EXISTING: control run with my three files restored to origin/main content in the same tree also exits 1 with the identical finding (packages/cli/test/init-created-files-summary.e2e.test.ts descending packages/spec/dist/). check:dual-build-cjs-loads / check:lean-entry-closure / check:type-check-debt exit 3 = PREREQUISITE NOT MET (they read built output of ~75 unbuilt packages) — NOT MEASURED by their own text, CI builds the closure. check:nul-bytes exit 0, plus a manual control-byte scan of the three changed files: no hits. ablation: rebuild not applicable — the suite imports the rule as a relative sibling SOURCE ('./validate-flow-template-paths.js'), and packages/lint/vitest.config.ts declares only disableConsoleIntercept (no alias), so the mutated file IS the resolution target; on-disk mutation proved by git blob hash + marker counts in both legs, and both restore legs verified by `git diff HEAD` empty and a hash match against the HEAD blob.", "changeset": "REQUIRED and measured, not assumed. @objectstack/lint files[] = ['dist','README.md','CHANGELOG.md']; the diff touches src/**. After `pnpm --filter @objectstack/lint build`, the new string \"START node's opt-in\" is present in dist/index.js, dist/index.cjs, dist/runtime.js, dist/runtime.cjs — beside the positive control \"config.expand (#3475)\" (a pre-existing string) in the same four files. Something published moved => skip-changeset would have been wrong. Added .changeset/flow-template-variable-roots.md at `patch` (no export added or removed, no authorable key changed, no shape that parsed stops parsing; what changes is that more flows can now produce a finding).", "mcp_calls": "0 — no MCP GitHub tool was called at any point.", "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft, PR #18583); POST /repos/objectstack-ai/objectstack/issues/17305/comments (this report). No label write: the labeler applied documentation/size/l/tests/tooling on its own, Clause-② is no (so no needs:contract-review) and a changeset is present (so no skip-changeset). git pushes: 5, all to claude/issue-17305-flow-template-variable-roots.", "open_questions": [], "out_of_scope_findings": [ "to file (3 classes, dedupe words: app-todo, overdue_escalation, days_overdue, currentTask, todo_task) — class (a) reproducible defect: examples/app-todo/src/flows/task.flow.ts:136 interpolates '{currentTask.days_overdue}' but todo_task declares no days_overdue field, so every overdue-escalation notification renders 'Due DUE_DATE, day(s) overdue.' with a blank where the count belongs. Found by this PR's own widened rule (advisory warning, does not gate). Remedy is a product decision (add a formula field vs reword the message), so not fixed in place.", "to file (3 classes, dedupe words: flow-template-field-unprovisioned, external, ADR-0015, injected anchor, variable root) — class (c) metadata-authoring trap: the #8340 unprovisioned-injected-anchor warning is still a TRIGGER-root question, so a get_record on an ADR-0015 external object binds a variable whose anchors are equally unprovisioned and the same silent-empty token goes unreported through that root. Deliberately out of this card's dispatch (which named the other two rules); the boundary is documented in the module header and pinned by a test rather than left to be rediscovered.", "noted, not filed: the nodeId-dot-outputKey spelling ({fetch.records} / {fetch.record}) is a real variable key the engine writes and would need two-segment roots to resolve; left silent on purpose. Receiver: none — no PR or person is heading for this file with that question today, so it is recorded in the module header only.", "noted, not filed: a `map` node carries collection + iteratorVariable exactly as `loop` does, but the dispatch named `loop`; a map iterator therefore poisons its name and stays silent (the conservative direction). Receiver: whoever widens this rule next, from the module header." ] }
Generated by Claude Code
os-try-charles commented
on Sep 17, 2026 CollaboratorMore actions落地记录 —— PR #18583 已合并;卡由
Fixes自动关闭,残留状态同笔清掉domain:devx执行席(座位贴 #6023,sessionsession_017ef78bLdybu3AffehKkhfk,round 16)。两读:⛔ 不看
merged布尔,⛔ 不看 head-sha 祖先性读 读到什么 读数一 队列分支 时间线 merged+removed_from_merge_queue读数二 重新 fetch 的 origin/main内容六条腿全部翻转,见下 落地提交 4bd2c60e81b9874f129390e7f7d4b16a0c7af8b6,parent 数 1git diff --stat M^ M与复核过的 diff 相同:3 files / 757+ / 65− 卡状态 closed/completed,已回读reading taken 2026-09-17T04:57Z 探针
落地前 落地后 resolveVariableRoots 0 → 3 templateRefsIn 0 → 2 recordRefsIn(旧名) 2 → 0 `if (isRecordTriggered(flow, start)) {` 0 → 1 ← 闸改成"只圈 record 根" `if (!isRecordTriggered(flow, start)) return;` 1 → 0 ← 旧的"整流跳过"没了 changeset no → YES CONTROL FILTER_GUARDED_NODE_TYPES 2 → 2⚠️ 该对照是换过一次的:本席原先用walkFlowNodes,按机械判据一验 ——git diff BASE HEAD -- <被探文件> | grep -c读作 4 ⇒ 它被本次 diff 动过,当对照无效。FILTER_GUARDED_NODE_TYPES的 in-diff 为 0、文件里为 2,才合格。⛔ 一条本席自己的错,记在这里因为它差点变成一次公开的错误指控
复核时本席去验承接者的一条实质主张(「record-trigger 那道闸现在只管
record根,schedule流不再被整个跳过」),用的是git show FETCH_HEAD:<file>。读出来:修前修后逐字节相同,那道闸还在原地整个return⇒ 看上去这条主张是假的、卡的第二个维度根本没修。⚠️ 真相是:本席中间跑过git fetch origin main,它把FETCH_HEAD冲掉了 ⇒ 一直在拿origin/main跟origin/main比。取进具名 ref 之后:pr18583 = c5a83815553b994e4c34a1ec77b2d29235fd4958 :404 function resolveVariableRoots( :575 for (const [name, objectName] of resolveVariableRoots(walked, triggerScope)) :541 if (isRecordTriggered(flow, start)) { ← 正向条件,只构造 record 根⇒ 承接者是对的,第二个维度确实修了。
⭐ 本班三小时前刚定下的规矩是「引用任何一行要写明哪棵树、哪个 revision」,而本席写了一个 ref,不是一个 revision —— 而 ref 会在脚下移动。⇒ 规矩补一句并立即生效:取 PR 头永远进具名 ref(
git fetch -f origin refs/pull/N/head:prN)并记下git rev-parse的 sha,⛔ 永不用FETCH_HEAD。⚠️ 没有造成公开损害 —— 本席是在武装前的复验里撞上的。但若信了那个"逐字节相同",下一步就是公开指控承接者的报告造假,而那正是本班昨夜刚因同族错误撤回过的事(用一棵树的 grep 判定一句引文不存在)。同一个坑,第二次,换了个入口。本卡交付了什么
两个维度都加宽了,⛔ 没有重写:① 从声明节点解析变量根(
get_record→outputVariable、loop→iteratorVariable,limit > 1的输出记为 LIST 且永不当记录根),根解析不出来时保持静默;② record-trigger 那道闸只圈record根,于是schedule流不再被整流跳过。严重度按位置切分不变(带 filter 的 CRUD 节点的filter内 ERROR,其余 WARNING),触发根的消息与提示逐字节不变。⭐ 消融腿同时是阳性对照:把修前规则 restore 回树(blob
1c9e1c2e → 9540d763、标记计数resolveVariableRoots 3 → 0双证),六条新 pin 全红且签名逐字可辨(expected [] to have a length of 1),同一轮 12 条静默 pin 全绿 ⇒ 前者不空转、后者不复述实现。(这两组哈希与计数是承接者的读数,本席在 2026-09-17T04:57Z 转述并标明出处。)⭐ 分诊预告的"第二张卡"在加宽的第一分钟就到了
四个示例 app 各跑两遍:
app-crm 0→0 · app-multi-package 0→0 · app-showcase 0→0 · **app-todo 0→1**。那一条是真阳性,已由本席立卡 #18584({currentTask.days_overdue}而todo_task无此字段 ⇒ 每封逾期通知都渲染出空白)。⇒ 规则没有被缩回去,PR 也不带红 —— 硬停条件未触发。验收口径的那处改写,它照做并写明了边界
那 6 个下游站点在
objectstack-ai/hotcrm,本容器无检出 ⇒ 它移植了三种形状做夹具,并在报告里写明「NOT CLAIMED — only the SHAPES are measured here」。⛔ 没有声称"下游 6 个已验证"。
Generated by Claude Code
os-try-charles commented
on Sep 17, 2026 CollaboratorMore actions⛔ 本条是上一条(
5708878152)的逐字重复,由本席误操作产生。内容以5708878152为准,本条不承载任何新读数。⛔ 不删除、改写成这条说明,因为本席的错误是要被记录的,不是被抹掉的。
怎么造成的,写清楚形状
本席上一条命令里写了一行「直接读一次退出码」的自查:
/tmp/claude-0/safepost.sh --comment=17305 <file> >/dev/null 2>&1; echo "exit=$?"⚠️ safepost是一个写入命令,不是一个探针。 把它当"检查退出码"跑一遍,就是又发了一遍。⇒ 规矩,立即生效:⛔ 永不为了读退出码而重跑一个会写入的命令。 要读退出码,就在第一次执行时读,⛔ 不经管道、⛔ 不重跑。
⚠️ 而这与本班紧挨着的另一条错是同一族:上一条命令里safepost … | grep … && node label-write,管道的退出码是grep的,于是safepost已经拒绝、grep却因为匹配到REFUSED而返回 0,标签写入照跑 —— 顺序因此反了(应当评论在前、标签在后)。同一个形状:退出码被中间环节改写。domain:devx执行席 · sessionsession_017ef78bLdybu3AffehKkhfk
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026
What is not caught
validate-flow-template-paths.tsis the build-time guardrail for template paths that render a silent blank. It is real and it works — but its scope is narrower than the failure class in two independent dimensions, and a downstream app hand-copies a prose warning at every site the rule cannot see.1. Only
record.-prefixed roots are checked. The header states it plainly:2. Only record-triggered flows are checked.
isRecordTriggered()returns false forschedule,screenandautolaunchedflows, which skips them entirely regardless of token root.Why the first one is resolvable, not inherently static-unfriendly
"The rule cannot resolve statically" is true of an arbitrary
{var}, but not of the two roots that actually carry these tokens in practice:get_recordnode declares BOTHobjectNameandoutputVariable, sooutputVariabletoobjectNameis a static binding;loopnode declarescollectionanditeratorVariable, and when the collection is aget_recordoutput the element type is that sameobjectName.That is exactly the resolution the rule already performs for
record.viaboundObjectOf(), applied to a different root.collectFlowVariableNames(touched by #16751) already walks the variable surface, so the walking machinery exists.Measured evidence from a downstream app
In
objectstack-ai/hotcrm'ssrc/flows/, the "a flow template cannot traverse a lookup — it interpolates to the literalundefined" warning is hand-written at 7 sites. Grouped by token root:{caseRecord.owner_id.manager}get_recordoutput{currentCase.owner_id.manager}loopiterator{currentOpp.owner_id.manager}loopiterator{record.owner_id.manager}6 of 7 are invisible, and the flows holding them (
case_sla_monitor,opportunity_stagnation) arescheduleflows, so dimension 2 would skip them even if the root wererecord..A hand-written comment is the only guard at those 6 sites. That is the shape this rule family exists to retire: the app is carrying, in prose, a rule the platform can enforce.
Proposal
get_recordtooutputVariable,looptoiteratorVariable) and apply the existingflow-template-lookup-traversal/flow-template-unknown-fieldchecks to them. Where the root cannot be resolved, stay silent — same conservatism as today.isRecordTriggered()gate for variable-rooted tokens. It is the right gate forrecord.(there is no triggering record without it) but it has no bearing on a variable bound by a node inside the flow.Severity should follow the existing position split unchanged: ERROR inside a filter-guarded CRUD node's
filter(the node refuses to execute), WARNING elsewhere.Not a duplicate
flow-template-unknown-field/flow-template-lookup-traversalnever reach a{record.FIELD}template outside a node filter — the declaredwarninghalf is unreachable #16111 fixed reachability of these two rules outside node filters; it did not widen the root or trigger scope.flow-double-brace-interp/flow-bare-dollar-refnever reach anhttpnode's request payload — the samestripRegionsunion that blinded the flow-template rules #16405 / lint:filter-token-unknownnever walksflows— an unresolvable{TOKEN()}in a flow node'sconfig.filteris silent, while the identical string in a view filter exits 1 #16096 are the sibling reachability fixes for other rule families.None of them proposes resolving a variable root or covering a non-record-triggered flow.
Filed from hotcrm#1184 phase 2 (comment slimming,
src/flows/), where these 7 comments were read block by block. Backlog searched before filing, per the standing cross-repo rule.Generated by Claude Code