Skip to content

lint: flow-template-unknown-field / flow-template-lookup-traversal never reach a {record.FIELD} template outside a node filter — the declared warning half is unreachable #16111

Description

@os-steve

Found while surveying which platform rules fire on objectstack-ai/hotcrm (its #1621, step 3 of epic #1579). Read-only survey, report-first, unassigned.

Measured against the pinned @objectstack/lint@17.3.0, hotcrm at 1670557. Every injection proven on disk before a verdict was read; every one restored by blob hash verified by observation.

What the shipped rule declares

Both ids carry a per-position severity, keyed off leaf.inFilter:

severity: inFilter ? "error" : "warning",
rule: FLOW_TEMPLATE_UNKNOWN_FIELD,

and the message body branches the same way — nodeType filter references '{record.…}' versus template references '{record.…}', the latter explaining that "the token resolves to an empty string on every run (silently)". So the non-filter position is deliberately in scope, at warning.

Measurement — one file, four injections, same run design

src/flows/billing-handoff.flow.ts carries both position kinds against the same triggering record: a get_record node with filter: { id: '{record.crm_account}' }, and a payload block that interpolates {record.id}, {record.name}, {record.amount}, {record.close_date}, … outside any filter.

injection position result exit
filter: { id: '{record.crm_account_nope}' } in filter flow-template-unknown-field at error, x2 1
filter: { id: '{record.crm_account.owner_id}' } in filter flow-template-lookup-traversal at error, x2 1
amount: '{record.amount_nope}' payload, not a filter passed: true, no finding 0
amount: '{record.crm_account.name}' payload, not a filter passed: true, no finding 0

The first two rows are the working control: the file is walked, the node is walked, recordRefsIn resolves {record.…} shapes, and the object's field map is loaded. Only the non-filter leaves are absent from leaves.

⚠️ Declared: my first attempt at the non-filter half used {billingAccount.phone_nope} — a variable ref, not a record ref — and so was out of the rule's class, not evidence of anything. It is reported here as a no-op rather than silently replaced; the table above is the redone measurement with {record.FIELD} in both positions.

Consequence

The half of these two rules that fires is the half whose consequence is loud — a filter token that resolves to nothing drops the condition and the node refuses at execution time. The half that is silent at authoring time is the half whose runtime consequence is also silent: per the rule's own message, the token renders as an empty string on every run. A hand-off payload that interpolates a renamed field ships an empty column to the billing system, and nothing anywhere says so.

Related, not duplicating

Dedupe

Targeted search returned 4 on-topic results (the three above plus #15793) — non-zero and on-topic, which is the control that the search was not silently answering empty.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions