Measured on the pinned @objectstack/lint@17.3.0 artifact as installed by objectstack-ai/hotcrm, ⛔ not on the platform source tree. Filed unassigned by the hotcrm step-3 rule survey (hotcrm#1613); the local assertion covering the flow surface stays in place there.
The gap
validateFilterTokens walks a fixed root list:
[ { key: "objects", kind: "object" },
{ key: "views", kind: "view" },
{ key: "reports", kind: "report" },
{ key: "datasets",kind: "dataset" },
{ key: "pages", kind: "page" },
{ key: "apps", kind: "app" } ]
flows is not in it. A flow node's config.filter is a filter in every sense that matters — it is handed to the data engine and it decides which rows a sweep touches — but no root reaches it, so classifyFilterToken is never called on those strings.
The rule's own message says what the cost is: "It is sent to the data engine as a literal string, matches no record, and the surface renders empty." On a scheduled sweep there is no surface to render empty. The sweep simply acts on nothing, nightly, at exit 0.
Measurement, with a working control
One committed tree (hotcrm at a0362a37), one injection at a time, restored by blob hash. Clean-tree baseline: 0 error(s), 17 warning(s), 12 suggestion(s), exit 0. The same token string in both rows.
where '{TOMORROW()}' was injected |
pnpm lint |
control — src/views/account.view.ts, a list view's filter[].value |
filter-token-unknown error, exit 1 |
src/flows/opportunity-stagnation.flow.ts, the query_stalled node's config.filter |
nothing fires — 17 warnings, exit 0, byte-identical to baseline |
Same command, same run design, same string. The control fires, so the second row is a reading about the walk's roots.
{TOMORROW()} is genuinely unresolvable on this pin: it is not a context token and not a date macro, and the view-side finding names it as such.
Why this one is sharper than it looks on hotcrm
That repo already carries test/flow-filter-today-token.test.ts, which exists because {TODAY()} in a flow filter changed meaning under it at the rc.5 to rc.6 bump (hotcrm#1107). The answer there was that the flow template engine resolves TODAY() before ObjectQL sees it — so flow filters have their own token vocabulary, resolved by a different layer than the one classifyFilterToken knows. That is a good reason for the flow surface to need its own treatment; it is not a reason for it to have none.
A token the flow template pre-pass does not consume reaches ObjectQL verbatim, and one neither layer knows fails the run rather than querying. Both of those are run-time outcomes with no author-time signal.
Suggested direction, not a prescription
Adding { key: "flows", kind: "flow" } to the root list is the obvious shape, but ⚠️ it is very likely wrong as-is: it would immediately red every legitimate {TODAY() - 45} in every sweep, because those are resolved by the automation template evaluator and not by the filter-token vocabulary. Whatever lands needs the flow token grammar as its reference set, not the ObjectQL one — which is why this is filed as a finding for the owner to scope rather than as a one-line fix.
Related, and why this is not a duplicate
Refs: hotcrm#1613 · hotcrm#1582 (F1) · hotcrm#1583 (F2) · hotcrm#1584 (F3) — filter-token-unknown is claimed by three of the six family cards, and the flow-surface half is unreachable for all of them.
Measured on the pinned
@objectstack/lint@17.3.0artifact as installed byobjectstack-ai/hotcrm, ⛔ not on the platform source tree. Filed unassigned by the hotcrm step-3 rule survey (hotcrm#1613); the local assertion covering the flow surface stays in place there.The gap
validateFilterTokenswalks a fixed root list:flowsis not in it. A flow node'sconfig.filteris a filter in every sense that matters — it is handed to the data engine and it decides which rows a sweep touches — but no root reaches it, soclassifyFilterTokenis never called on those strings.The rule's own message says what the cost is: "It is sent to the data engine as a literal string, matches no record, and the surface renders empty." On a scheduled sweep there is no surface to render empty. The sweep simply acts on nothing, nightly, at exit 0.
Measurement, with a working control
One committed tree (hotcrm at
a0362a37), one injection at a time, restored by blob hash. Clean-tree baseline:0 error(s), 17 warning(s), 12 suggestion(s), exit 0. The same token string in both rows.'{TOMORROW()}'was injectedpnpm lintsrc/views/account.view.ts, a list view'sfilter[].valuefilter-token-unknownerror, exit 1src/flows/opportunity-stagnation.flow.ts, thequery_stallednode'sconfig.filterSame command, same run design, same string. The control fires, so the second row is a reading about the walk's roots.
{TOMORROW()}is genuinely unresolvable on this pin: it is not a context token and not a date macro, and the view-side finding names it as such.Why this one is sharper than it looks on hotcrm
That repo already carries
test/flow-filter-today-token.test.ts, which exists because{TODAY()}in a flow filter changed meaning under it at the rc.5 to rc.6 bump (hotcrm#1107). The answer there was that the flow template engine resolvesTODAY()before ObjectQL sees it — so flow filters have their own token vocabulary, resolved by a different layer than the oneclassifyFilterTokenknows. That is a good reason for the flow surface to need its own treatment; it is not a reason for it to have none.A token the flow template pre-pass does not consume reaches ObjectQL verbatim, and one neither layer knows fails the run rather than querying. Both of those are run-time outcomes with no author-time signal.
Suggested direction, not a prescription
Adding⚠️ it is very likely wrong as-is: it would immediately red every legitimate
{ key: "flows", kind: "flow" }to the root list is the obvious shape, but{TODAY() - 45}in every sweep, because those are resolved by the automation template evaluator and not by the filter-token vocabulary. Whatever lands needs the flow token grammar as its reference set, not the ObjectQL one — which is why this is filed as a finding for the owner to scope rather than as a one-line fix.Related, and why this is not a duplicate
{…}before the query engine sees it #3810 (closed) — flow node filters silently blanking date macros: the template engine consuming{…}before the query engine sees it. Same seam, run-time half, and the reason a naive root addition would misfire.dashboard-filter-field-unknownstill skips every dotted filter field and resolves against the flatSYSTEM_FIELDSunion — the seam next to it now answers both #14275 (closed) —dashboard-filter-field-unknownskipping dotted filter fields; a different rule with the same "the walk does not reach this position" shape.Refs: hotcrm#1613 · hotcrm#1582 (F1) · hotcrm#1583 (F2) · hotcrm#1584 (F3) —
filter-token-unknownis claimed by three of the six family cards, and the flow-surface half is unreachable for all of them.