fix(lint,cli): a filter reference that cannot resolve fails the build, not the run (#3426, #3810) - #3861
Merged
os-zhuang merged 2 commits intoJul 28, 2026
Conversation
…, not the run (#3426, #3810) `validateFlowTemplatePaths` called every `{record.<path>}` miss advisory, on the reasoning that an unresolved token renders a blank and the run completes. Since #3810 that stopped being true in one position: inside a CRUD node's `filter` an unresolved token does not blank a value, it DELETES the condition — and a removed condition matches MORE rows. Those nodes now refuse to execute. So the rule was warning about metadata whose runtime was already decided: `os validate` printed a yellow line, exited 0, and shipped a flow that cannot run. Severity now follows the runtime consequence, by position. - `filter` of get_record / update_record / delete_record -> error. The three nodes whose filter `resolveNodeFilter` guards. The message states what the runtime does and why this gates rather than warns (an absent condition WIDENS the query). `create_record` is excluded: it writes a payload, no filter. - Every other position -> warning, unchanged. The token still renders a blank, the run still completes, and the head object may come from another package. Both rules split this way, so a typo and a lookup hop are gated wherever the runtime refuses them. A reference used in both positions reports once, at error. `os validate` now enforces it. The step filtered this rule for `severity === 'warning'` and dropped everything else, so an error would have been invisible. It gates on errors first — rule id, config path, and `errors` in `--json` — mirroring the `validateReadonlyFlowWrites` step below it, which makes the same shift-left split. Verified against shipped metadata: 33 flows across app-todo, app-crm and app-showcase produce no new errors; the four pre-existing traversal warnings sit in script/notify/subflow/parallel positions and keep advisory severity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EdCvGtER9SzJopS4Yh3Pa1
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The query skill's filters rule described the #3810 runtime refusal as the only signal. Two of its three causes — a mistyped field and an un-expanded lookup hop — are statically decidable, and `objectstack validate` now fails on them in a filter position rather than warning. Records which half catches what, so an author reading the rule knows a filter typo is a build failure, the same reference outside a filter is still only a warning, and an unresolved flow variable remains run-time-only (it is not statically checkable). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EdCvGtER9SzJopS4Yh3Pa1
os-zhuang
marked this pull request as ready for review
July 28, 2026 12:55
os-zhuang
deleted the
claude/template-tokens-filters-notifications-nicmhd
branch
July 28, 2026 12:56
os-zhuang
added a commit
that referenced
this pull request
Jul 28, 2026
…compile` (#3583, #3810) (#3874) `validateFlowTemplatePaths` was wired by hand into `os validate` and nowhere else — the drift `REFERENCE_INTEGRITY_RULES` exists to end (#3583 §5 D5): one stack, a different rule subset depending on which command the author ran. It mattered more once #3861 gave the rule a gating severity. A `{record.<path>}` token in a CRUD node's `filter` naming an unknown field, or hopping through an un-expanded relation, makes the runtime REFUSE the node (#3810). `os validate` failed on it; `os lint` and `os compile` did not look, so a CI job running either one built and shipped a flow that cannot execute. The rule is now a suite member — it meets the suite's own admission criterion, since a `{record.<field>}` token is a name resolved against the bound object's declared fields. One line reaches all three commands; the hand-wiring in `validate.ts` is deleted rather than duplicated. Checked the three stack shapes the suite is handed before moving the call site: raw `config` (lint), `normalizeStackInput` output, and schema-parsed `result.data` (validate/compile), across all three example apps. They agree finding-for-finding, so the move does not change what is reported. End-to-end on app-showcase: all three commands pass unchanged on the real stack, and with one filter token corrupted to `{record.idd}` all three exit 1 — where previously only `validate` did. Also fixed in the same file: on a clean run `os validate --json` never reported the suite's warnings. `refWarnings` was assembled, printed, and included in the FAILURE payload, but omitted from the success-path `warnings` array — so adding the rule to the suite would have silently dropped its warnings for JSON consumers. Same bug shape as the dropped errors #3861 fixed: computed, then discarded.
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.
Why
validateFlowTemplatePaths(#3426) reported every{record.<path>}miss as advisory, on the reasoning that an unresolved token renders a blank and the run still completes.Since #3810 that reasoning no longer holds in one position. Inside a CRUD node's
filter, an unresolved token does not blank a value — it deletes the condition, and a removed condition matches MORE rows, not fewer.resolveNodeFilternow refuses to execute those nodes rather than run a widened query.So the rule was warning about metadata whose runtime is already decided:
os validateprinted a yellow line, exited 0, and shipped a flow that cannot run. The author finds out on the first execution instead of at build time — which is exactly the shift-left this rule exists to provide.What changed
Severity follows the runtime consequence, by position.
filterofget_record/update_record/delete_recordresolveNodeFilterguards — the runtime refuses themhttpurl, write payload)create_recordis deliberately excluded from the gating set: it writes a payload and has no filter to widen.Both rules split this way (
flow-template-unknown-fieldandflow-template-lookup-traversal), so a typo and a lookup hop are gated wherever the runtime refuses them. Filter-position leaves are collected first, so a reference used in both positions on one node is reported once, at error severity.os validatenow enforces it. The step filtered this rule's findings forseverity === 'warning'and dropped everything else on the floor — an error from it would have been silently invisible. It now gates on errors first (rule id, config path,errorsin--json), mirroring thevalidateReadonlyFlowWritesstep directly below it, which makes the same shift-left split: a certain runtime failure gates, a state-dependent one advises.Error-position findings also state the consequence rather than the symptom — "the node refuses to run at execution time" and "an absent condition WIDENS the query" — so the fix is obvious without opening #3810.
Verification
packages/lintsuite green: 514 passed, 9 new tests covering the severity split (per-node-type gating, non-filter positions staying advisory, both-position dedupe at error,create_recordexclusion, valid filters clean, andconfig.expandstill suppressing a filter traversal per Flow lookup-field template resolution ({record.account.name}) needs a read-identity design decision (follow-up to #3426) #3475).packages/cli/src/commands/validate.ts: 0 errors.app-todo,app-crmandapp-showcaseproduce no new errors. The four pre-existing lookup-traversal warnings sit inscript/notify/subflow/parallelpositions and keep their advisory severity.Compatibility
No authoring change is required for a correct filter. A filter this rule now fails is one the runtime would have refused anyway — the difference is finding out at
os validateinstead of at 3am.Generated by Claude Code