feat(workflow-executor): evaluate the operators a list view filter offers, relative dates included - #1880
feat(workflow-executor): evaluate the operators a list view filter offers, relative dates included#1880Scra3 wants to merge 3 commits into
Conversation
…fers, relative dates included The deterministic Decision accepted 12 operators, three of which the editor could never build, and none of the date family beyond equality. A Decision on a date could not say "before", "after" or "in the previous 7 days", which is the first thing anyone writes on a date. The operator set is now the one a list view filter offers, 23 names: the three orphans (>=, <=, not_in) go, and 14 arrive — before/after, past/future, today/yesterday, previous_x_days(_to_date), before/after_x_hours_ago, starts_with/ends_with/i_contains, includes_all. Relative dates read an injected Clock, never the machine's. The orchestrator sends the project's timezone on the run (mapper falls back to UTC when unset or when the orchestrator predates it); the step executor builds one clock per step so every row sees the same instant. Day windows are half open in that zone, mirroring datasource-toolkit's time transforms, and a calendar date is read at midnight in that zone rather than UTC, otherwise Honolulu's own today would count as yesterday. i_contains folds case and keeps accents, measured against Postgres ILIKE. The trace persists evaluatedAt and timezone, without which a check on "previous 7 days" cannot be explained a day later. Product decisions recorded on PRD-1147: evaluation instant, project timezone, Postgres semantics for text, all operators in one delivery, and a retry that may route differently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 new issues
|
| previousSteps: z.array(StepSchema), | ||
| user: StepUserSchema, | ||
| /** IANA zone the run's relative dates are read in: the project's, never the machine's. */ | ||
| timezone: z.string().min(1), |
There was a problem hiding this comment.
🟠 High validated/execution.ts:53
Invalid timezone values such as "Fantasia/Castle" pass AvailableStepExecutionSchema.parse, causing Luxon to evaluate relative-date windows with an invalid clock zone and silently route Decisions to their fallback. Validate the value as an IANA zone here, or normalize invalid values to UTC before they reach the evaluator.
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/workflow-executor/src/types/validated/execution.ts around line 53:
Invalid timezone values such as `"Fantasia/Castle"` pass `AvailableStepExecutionSchema.parse`, causing Luxon to evaluate relative-date windows with an invalid clock zone and silently route Decisions to their fallback. Validate the value as an IANA zone here, or normalize invalid values to `UTC` before they reach the evaluator.
|
Coverage Impact This PR will not change total coverage. Modified Files with Diff Coverage (4)
🛟 Help
|
…idate the project zone The start of a day window is included for a calendar date and excluded for a datetime, as datasource-toolkit's time transforms do. An unknown IANA zone falls back to UTC. includes_all is never met on an empty list. The SQL datetime form with a space is read as UTC too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… parse Postgres datetime forms A mid-day bound (past, before_x_hours_ago, the end of previous_x_days_to_date) is read at the start of its day when the value is a calendar date, as the toolkit formats the bound with toISODate for a Dateonly column. The SQL datetime form with a bare-hour or detached offset parses again. Schema tests cover every operator of the contract. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

fixes PRD-1147, fixes PRD-1130
Why
A deterministic Decision on a Date field could only say
is,is not,is present,is blank. Forest exposesbefore/afterand a relative family for dates, none of which were in the contract, so the first condition anyone writes on a date could not be written. Meanwhile the contract carried three operators (>=,<=,not_in) that the editor can never build, since the front registry does not define them.What
Operator set = what a list view filter offers.
CONDITION_OPERATORSgoes from 12 to 23: the three orphans leave, fourteen arrive.before,afterpast,future,today,yesterday,previous_x_days,previous_x_days_to_date,before_x_hours_ago,after_x_hours_agostarts_with,ends_with,i_containsincludes_allRelative dates read an injected
Clock,{ now, timezone }, never the machine's. The evaluator stays pure. The step executor builds one clock per step so every row of a Decision sees the same instant.Timezone is the project's, carried on the run.
AvailableStepExecution.timezoneis new; the mapper falls back toUTCwhen the project has none or the orchestrator predates the field. The machine's zone is never used: the fleet runs several executor instances and the same run must route the same on each.Semantics mirror
datasource-toolkit's time transforms, which is what the list filter runs on: day windows exclude their end and, like the toolkit'sGreaterThan/GreaterThanOrEqualsplit, include their start for a calendar date only,previous_x_daysexcludes today,previous_x_days_to_dateincludes it up to the instant. A calendar date (Dateonly) is read at midnight in the project zone, otherwise a Honolulu record's own today is counted as yesterday.i_containsfolds case and keeps accents, measured on Postgres:'É' ILIKE '%é%'holds,'é' ILIKE '%e%'does not.The trace records
evaluatedAtandtimezone. A relative condition makes routing depend on the clock; without the instant, a check on "previous 7 days" cannot be explained a day later.Decisions taken with product (on PRD-1147)
Compatibility
Dropping
greater_than_or_equal,less_than_or_equalandnot_inmakes a published workflow that still carries one of them fail at parse on this executor (and be refused by the orchestrator's parser). The editor never offered them, and the deterministic Decision has only been open on executor 1.27.0 since 2 September, so no such workflow should exist; a hand-edited BPMN is the only way to have one. The orchestrator and the front move their gate back to the sentinel until this ships, since 1.27.0 refuses the fourteen new operators.Paired with
project.timezoneon the run.Until the orchestrator sends the timezone, relative dates resolve in UTC.
Tests
1724 passing. Every new operator is pinned with a fixed clock in
Europe/Paris, the calendar-date case inPacific/Honolulu, and the condition executor proves the context timezone reaches the evaluator with fake timers straddling midnight.Note
Add list-view filter operators with timezone-aware relative date evaluation to
deterministic-condition-evaluatorincludes_all,starts_with,ends_with,i_contains,before,after,past,future,today,yesterday,previous_x_days,previous_x_days_to_date,before_x_hours_ago, andafter_x_hours_ago; removesgreater_than_or_equal,less_than_or_equal, andnot_infrom step-definition.tsClockcarrying the current instant and project timezone;ConditionStepExecutor.evaluateDeterministicallycaptures one clock per step and passes it through every condition evaluation in condition-step-executor.tsExecutionContextandAvailableStepExecutionSchemanow require a non-empty timezone string;run-to-available-step-mapperforwards valid IANA zones and defaults to UTC when absent or invalidevaluatedAtandtimezoneinDeterministicConditionExecutionParamsalongside existing evaluation resultsEVALUATORSno longer providesgreater_than_or_equal,less_than_or_equal, ornot_in; existing conditions using these operators will fail validation againstDeterministicConditionSchema. All non-presence evaluations now require an injectedClock, so direct callers ofevaluateOperatormust supply one.Macroscope summarized 66fbb91.