feat(formula,lint): advisory type-soundness warnings for expressions (#1928 tier 4)#3178
Merged
Merged
Conversation
…1928 tier 4) A `Field.formula` or record-scoped predicate that uses a text or boolean field with an arithmetic (`+ - * / %`) or ordering (`< > <= >=`) operator against a number faults the runtime overload and silently evaluates to null (e.g. `record.title * 2`, `record.is_active + 1`). The build now surfaces this as a NON-blocking warning naming the offending field. Honours the ADR-0032 design law — flag only what the runtime would also fail: number/currency/percent/date/datetime fields are declared `dyn`, so the cases the runtime rescues never warn (`amount / 100` via registerOperator, `due == today()` and numeric-string/ISO-date values via the string-hydration retry, numeric-coded select options). Equality (`==`/`!=`) is excluded — a heterogeneous equality is runtime-safe. - formula: new `firstTypeMismatch` + optional `fieldTypes` hint on `validateExpression`; a narrow spec-type -> CEL-type map (only free-text -> string, boolean/toggle -> bool; everything else dyn). - lint: `validateStackExpressions` threads each object's field types into every record-scoped site (formula fields, validations, action/hook/sharing predicates). Warnings are advisory in build/validate, fatal only under --strict. Tests: formula 215 (+9), lint 230 (+3). Green on cel-js 8.0.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hnji7EEYR2mGt6pY53a8Bm
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 6 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…nditions (#1928) `firstTypeMismatch` gains a `scope` param: in addition to record-scoped sites (`record.<field>`), it now covers bare-field flow/automation conditions (`status - 1`, `is_active + 1`). The flattened env binds each field as a top-level typed variable with `unlistedVariablesAreDyn: true`, so flow variables stay `dyn` and are never flagged; equality stays excluded. `validateExpression` runs the check in the flattened branch too, so the lint's flow-condition validation surfaces the same advisory warning. Message uses the bare `field` form in flattened scope, `record.field` in record scope. Tests: formula 219 (+4 flattened cases), lint 232 (+2 flow-condition cases). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hnji7EEYR2mGt6pY53a8Bm
os-zhuang
marked this pull request as ready for review
July 18, 2026 07:13
This was referenced Jul 18, 2026
Closed
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.
What
Closes the last open guardrail from #1928. A
Field.formula, record-scoped predicate, or flow/automation condition that uses a text or boolean field with an arithmetic (+ - * / %) or ordering (< > <= >=) operator against a number faults the runtime overload and silently evaluates tonull(e.g.record.title * 2,record.is_active + 1, a flow conditionstatus - 1 > 0). The build now surfaces this as a non-blocking warning naming the offending field and the fix.This is Tier 4 from the issue's staged plan — deferred by the maintainer until a warning channel existed. It now does (shipped for tier 3 in #1933), so this lands as advisory warnings.
Soundness (the ADR-0032 design law — flag only what the runtime would also fail)
The build checker's type map deliberately mirrors runtime, verified empirically against the pinned cel-js (8.0.0):
dyn, so the cases the runtime rescues never warn:record.amount / 100— the fix(formula): register mixed double/int arithmetic overloads (#1928) #1930registerOperator(double×int) fix.record.due == today()and numeric-string / ISO-date values — the string-hydration retry.selectoption values (e.g.priority >= 3).==/!=) is excluded — a heterogeneous equality is runtime-safe (evaluates tofalse), never a fault. The realexample-crmformularecord.status == "converted" || …is correctly not flagged.dyn(unlisted → dyn) and are never flagged — only a typed field misused is.text, textarea, email, url, phone, markdown, html, richtext) →string, andboolean/toggle→bool.Changes
@objectstack/formula— newfirstTypeMismatch(source, fieldCelTypes, scope)(builds a typed cel-js env — arecordstruct for record scope, bare top-level variables for flattened flow conditions — and reports arithmetic/ordering overload faults), plus an optionalfieldTypeshint onvalidateExpression. A narrow spec-type → CEL-type map keeps false positives near-zero.@objectstack/lint—validateStackExpressionsthreads each object's field types into every checked site:record.<field>) — formula fields, validation rules, action / hook / sharing predicates;field).Warnings are advisory in
objectstack build/validate(fatal only under--strict), matching the tier-3 channel.Acceptance (#1928)
amount / 100, date-vs-today(), equality, string concat, null-guards, flow variables) are not flagged.Field.formula/ predicates and flow conditions.Testing
@objectstack/formula— 219 green (+13: record + flattened cases; number/date/select/equality/concat/null-guard/flow-variable no-warn; scope gating).@objectstack/lint— 232 green (+5 integration cases, incl. flow conditions).7.6.1 → 8.0.0bump; DTS builds type-check clean.Not done (needs a decision, out of scope)
The issue also floated wiring this into an agent-callable
validate_expressiontool for authoring-time feedback. That tool does not exist in the codebase today, and the only runtime hook (validateFlowExpressionsatregisterFlow) has no object-schema access and only raises errors — so bringing tier-4 there is a runtime change (object-registry injection + a warning channel), not a small follow-up. Left for a separate, sign-off-gated PR.Refs: #1928, ADR-0032. Runtime arithmetic fix #1930, tier-1 #1931, tier-3 #1933.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Hnji7EEYR2mGt6pY53a8Bm