Skip to content

feat(workflow-executor): evaluate the operators a list view filter offers, relative dates included - #1880

Open
Scra3 wants to merge 3 commits into
mainfrom
feature/prd-1147-list-filter-operators
Open

feat(workflow-executor): evaluate the operators a list view filter offers, relative dates included#1880
Scra3 wants to merge 3 commits into
mainfrom
feature/prd-1147-list-filter-operators

Conversation

@Scra3

@Scra3 Scra3 commented Sep 4, 2026

Copy link
Copy Markdown
Member

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 exposes before/after and 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_OPERATORS goes from 12 to 23: the three orphans leave, fourteen arrive.

Family Operators
Absolute dates before, after
Relative dates past, future, today, yesterday, previous_x_days, previous_x_days_to_date, before_x_hours_ago, after_x_hours_ago
Strings starts_with, ends_with, i_contains
Arrays includes_all

Relative 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.timezone is new; the mapper falls back to UTC when 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's GreaterThan / GreaterThanOrEqual split, include their start for a calendar date only, previous_x_days excludes today, previous_x_days_to_date includes 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_contains folds case and keeps accents, measured on Postgres: 'É' ILIKE '%é%' holds, 'é' ILIKE '%e%' does not.

The trace records evaluatedAt and timezone. 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)

  • Dates are evaluated at the instant the condition is evaluated.
  • Timezone: project's, fallback UTC. Not the user's (automated runs have none), not the host's.
  • A retry may route differently. Accepted.
  • All operators ship together.

Compatibility

Dropping greater_than_or_equal, less_than_or_equal and not_in makes 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

  • forestadmin-server: the contract list, the value rules per operator, and sending project.timezone on the run.
  • forestadmin: the editor's allowed list and the run view.

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 in Pacific/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-evaluator

  • Expands the deterministic condition operator set to match list-view filters: adds includes_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, and after_x_hours_ago; removes greater_than_or_equal, less_than_or_equal, and not_in from step-definition.ts
  • Introduces a Clock carrying the current instant and project timezone; ConditionStepExecutor.evaluateDeterministically captures one clock per step and passes it through every condition evaluation in condition-step-executor.ts
  • Date-only values are interpreted as midnight in the project timezone; SQL-style datetime strings (space separator, bare-hour offset) are now parseable via deterministic-condition-evaluator.ts
  • ExecutionContext and AvailableStepExecutionSchema now require a non-empty timezone string; run-to-available-step-mapper forwards valid IANA zones and defaults to UTC when absent or invalid
  • Persists evaluatedAt and timezone in DeterministicConditionExecutionParams alongside existing evaluation results
  • Behavioral Change: EVALUATORS no longer provides greater_than_or_equal, less_than_or_equal, or not_in; existing conditions using these operators will fail validation against DeterministicConditionSchema. All non-presence evaluations now require an injected Clock, so direct callers of evaluateOperator must supply one.

Macroscope summarized 66fbb91.

…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>
@linear-code

linear-code Bot commented Sep 4, 2026

Copy link
Copy Markdown

PRD-1147

PRD-1130

@qltysh

qltysh Bot commented Sep 4, 2026

Copy link
Copy Markdown

4 new issues

Tool Category Rule Count
qlty Structure Function with many returns (count = 4): evaluateDeterministically 2
qlty Structure High total complexity (count = 66) 1
qlty Structure Function with many parameters (count = 4): evaluateOperator 1

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),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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.

@qltysh

qltysh Bot commented Sep 4, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (4)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
...ow-executor/src/executors/deterministic-condition-evaluator.ts100.0%
Coverage rating: A Coverage rating: A
...ges/workflow-executor/src/executors/condition-step-executor.ts100.0%
Coverage rating: A Coverage rating: A
...workflow-executor/src/adapters/run-to-available-step-mapper.ts100.0%
Coverage rating: A Coverage rating: A
packages/workflow-executor/src/types/validated/step-definition.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

alban bertolini and others added 2 commits September 4, 2026 21:35
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant