You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
spec/lint: a date-range PRESET name is refused only under ordering operators — the same preset on the same date field passes as a bare / $eq / $in comparand #16106
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/spec@17.3.0 and @objectstack/lint@17.3.0, hotcrm at 1670557. Injections proven on disk before reading a verdict; restored by blob hash verified by observation.
What is already shipped, and works
#8793 (closed, the ruled C half of #8690) refuses a declared preset name as a bare temporal comparand at publish time. Confirmed working, verbatim:
Injection: a dashboard widget filter, close_date: { $gte: '{12_months_ago}' } to close_date: { $gte: 'last_30_days' }.
Result: defineStack refuses, exit 1, with a precise message — "last_30_days" is a dashboard date-range PRESET name, not a filter value … As a bare "$gte" comparand nothing resolves it: a declared datetime/date field refuses the query at the engine (INVALID_FILTER / 400) …
The same refusal fires from a report runtimeFilter, so it is not dashboard-specific.
The residue
filter-preset-comparand judges only ORDERING_DOLLAR_OPS = {$gt, $gte, $lt, $lte}, ORDERING_INFIX_OPS and ORDERING_RULE_OPS, and the publish-time refusal covers the same set. Every non-ordering position on the same declared date field passes both gates. Three injections into the same widget filter, on close_date (a declared date column of crm_opportunity, the dataset's object):
authored comparand
defineStack
objectstack lint
exit
close_date: { $gte: 'last_30_days' }
refuses
not reached
1
close_date: 'last_30_days'
accepts
passed: true, no finding
0
close_date: { $eq: 'last_30_days' }
accepts
passed: true, no finding
0
close_date: { $in: ['last_30_days'] }
accepts
passed: true, no finding
0
Baseline for all four rows is identical (errors: 0, warnings: 17, suggestions: 12), and the ordering row is the working control that the harness does detect this value.
Why the residue matters
The consequence #8690 named is not specific to ordering operators: an unlowered preset string reaches the driver as a literal, compares false against every row, and the surface answers 200 with zero rows and no diagnostic. close_date == 'last_30_days' is exactly that shape — arguably the more likely authoring slip, since equality is what an author writes when they mean "in this window".
Two readings, and I do not have standing to choose between them:
The ruling's scope was "bare temporal comparand", and equality/$in positions are simply the rest of that scope — extend the publish-time refusal (and filter-preset-comparand) to any comparand position on a temporal field.
Recorded rather than assumed. filter-preset-comparand's own reachability is a consequence either way: on a defineStack-authored app the schema refuses the ordering positions first, so the lint rule id never appears in output at all.
Dedupe
#8690 and #8793 read in full; #8793 is closed/completed and covers the ordering half only. No open card names the equality/$in/bare positions.
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/spec@17.3.0and@objectstack/lint@17.3.0, hotcrm at1670557. Injections proven on disk before reading a verdict; restored by blob hash verified by observation.What is already shipped, and works
#8793 (closed, the ruled C half of #8690) refuses a declared preset name as a bare temporal comparand at publish time. Confirmed working, verbatim:
close_date: { $gte: '{12_months_ago}' }toclose_date: { $gte: 'last_30_days' }.defineStackrefuses, exit 1, with a precise message —"last_30_days" is a dashboard date-range PRESET name, not a filter value … As a bare "$gte" comparand nothing resolves it: a declared datetime/date field refuses the query at the engine (INVALID_FILTER / 400) …The same refusal fires from a report
runtimeFilter, so it is not dashboard-specific.The residue
filter-preset-comparandjudges onlyORDERING_DOLLAR_OPS = {$gt, $gte, $lt, $lte},ORDERING_INFIX_OPSandORDERING_RULE_OPS, and the publish-time refusal covers the same set. Every non-ordering position on the same declared date field passes both gates. Three injections into the same widget filter, onclose_date(a declared date column ofcrm_opportunity, the dataset's object):objectstack lintclose_date: { $gte: 'last_30_days' }close_date: 'last_30_days'passed: true, no findingclose_date: { $eq: 'last_30_days' }passed: true, no findingclose_date: { $in: ['last_30_days'] }passed: true, no findingBaseline for all four rows is identical (
errors: 0, warnings: 17, suggestions: 12), and the ordering row is the working control that the harness does detect this value.Why the residue matters
The consequence #8690 named is not specific to ordering operators: an unlowered preset string reaches the driver as a literal, compares false against every row, and the surface answers 200 with zero rows and no diagnostic.
close_date == 'last_30_days'is exactly that shape — arguably the more likely authoring slip, since equality is what an author writes when they mean "in this window".Two readings, and I do not have standing to choose between them:
$inpositions are simply the rest of that scope — extend the publish-time refusal (andfilter-preset-comparand) to any comparand position on a temporal field.Recorded rather than assumed.
filter-preset-comparand's own reachability is a consequence either way: on adefineStack-authored app the schema refuses the ordering positions first, so the lint rule id never appears in output at all.Dedupe
#8690and#8793read in full;#8793is closed/completed and covers the ordering half only. No open card names the equality/$in/bare positions.