Skip to content

feat(workflow): inputs checked at start (#3a) - #762

Merged
viethqb merged 17 commits into
mainfrom
feat/workflow-inputs
Oct 28, 2026
Merged

viethqb merged 17 commits into
mainfrom
feat/workflow-inputs

Conversation

@viethqb

@viethqb viethqb commented Oct 28, 2026

Copy link
Copy Markdown
Member

Summary

Roadmap package #3a: workflow inputs are checked when an execution starts, as Kestra does, instead of failing later at render.

  • New pure checker inputs_check (required, empty-string rule, STRING/SECRET validator via fancy-regex, INT/FLOAT/BOOL, DATETIME/DATE/TIME/DURATION bounds, URI/EMAIL/JSON/YAML/ION, ARRAY/MULTISELECT, SELECT values, dependsOn, expression, autoSelectFirst) with Kestra's message texts.
  • Called at the one start chokepoint (before SECRET sealing), for the Subflow child and at backfill create. Replays, Flow-trigger starts and born-FAILED fires are not checked (Kestra does not either). The coerced object is stored.
  • Six save-time findings (unknown/cyclic dependsOn, defaults+prefill, FILE defaults scheme, bad validator, bad bound). Save/publish only; stored versions keep running.
  • Oracle: new Seam::Start; banks invalids/inputs-with-multiple-constraint-violations and invalids/inputs-bad-validator-syntax. Ratchet 301 / 52 / 52 / 0 -> 301 / 50 / 50 / 0.
  • Docs EN + VI: spec, plan, survey, roadmap.

Divergences (all recorded in the spec): BOOL is strict, FLOAT refuses NaN/Infinity, status 400 not 422, Flow-trigger inputs unchecked, SELECT checks values only when required (Kestra's quirk), jsonSchema still unchecked (no JSON Schema crate in the tree), non-integral INT bounds refused at save.

Behaviour changes to note: an optional input nobody posts is stored as null; undeclared keys are dropped; YAML/ION text inputs now render as parsed objects.

Not in this PR (#3b): the validate endpoint and the run form. No user-docs PR yet (notes ready for egret-data/docs).

Test plan

  • cargo test -p egret-workflow (only the 7 sql_starrocks_live fail: they need StarRocks)
  • cargo test -p egret-config -p egret-server (drift tests)
  • cargo fmt --check, cargo clippy -D warnings
  • Oracle ratchet 301 / 50 / 50 / 0

🤖 Generated with Claude Code

viethqb and others added 17 commits October 28, 2026 05:08
…at start

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add validator, min, max, after, before, values, expression,
allowCustomValue, isRadio, autoSelectFirst and jsonSchema to Input so a
Kestra flow round-trips verbatim, and stop warning not_honoured for them.
FORM inputs and REUSABLE_INPUTS ref stay warned.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…odule

`inputs_check::resolve` takes the flow's declared inputs and the posted
object and returns the coerced object or the first error in declared
order, with Kestra's message texts (spec D1-D5, D9, D10). Covers
required, the empty-string rule, STRING/SECRET validator (fancy-regex,
full match, backtrack limit), INT, FLOAT, BOOL (strict), DATETIME, DATE,
TIME, DURATION, URI, EMAIL and SELECT. Collections, JSON/YAML/ION and
dependsOn pass through until Task A3. Not wired into any start path yet.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…start

Extend inputs_check: ARRAY and MULTISELECT parse each element with
itemType and MULTISELECT checks every element against its values; JSON,
YAML and ION are parsed; dependsOn inputs and condition decide which
inputs are enabled (dependencies first, cycle-safe), through an
InputRenderer the caller supplies; a SELECT/MULTISELECT expression gives
the options and autoSelectFirst picks the first one. resolve_all lists
every input's outcome for the validate endpoint.

DATETIME, DATE, TIME and DURATION are now stored as posted, not in
Java's normalised form.

jsonSchema stays unchecked: no JSON Schema validator is in the
dependency tree (spec D12), so its not_honoured warning is back and the
treatment table measures it ignored.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Kestra's resolveInputs keeps the key with null; leaving it out fails
egret's strict renderer on {{ inputs.x }} once the start stores the
coerced object (valids/subflow-nullable-input-parent regressed).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
start_execution_in_on runs inputs_check::resolve before secret_inputs::split
(a SECRET validator sees the clear value) and stores the coerced object.
A refusal is Error::InvalidInput (HTTP 400). Replays and Flow-trigger
fires (flow_trigger_event_id set, born_failed included) are not checked,
as Kestra. The Subflow child calls the same checker; a refusal fails the
parent's run with 'subflow inputs: <message>'.

Existing tests that posted undeclared keys or left a required input out
now declare them. The start route's 400 description lists the new
refusals; openapi.json and the client digest are regenerated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A3 measured JsonInput.jsonSchema `ignored` (spec D12: no JSON Schema
validator) while models::Input still kept the key, so the flow schema no
longer offered a field the model accepted and
the_inputs_offer_exactly_the_fields_models_input_accepts failed.

Take jsonSchema out of models::Input instead, like every other key egret
ignores: the schema and the model agree again, the not_honoured warning
naming package #3 stays (now pinned by a test), and the NO_EFFECT entry is
no longer needed. Treatment counts are unchanged. The test's tail, which
still asserted A1's keys were not offered, now asserts they are.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inputs package #3a (spec D11). At save and publish only (never the run
gate), with Kestra's messages:

- dependsOn naming an unknown input, and dependsOn cycles
  (FlowValidator.checkFlowInputsDependencyGraph, CycleDependency path);
- defaults together with prefill (InputValidator);
- a FILE default whose URI scheme is not file/nsfile, or that does not
  render or parse as a URI (FileInputValidator);
- a STRING/SECRET validator that does not compile, with fancy-regex
  (RegexValidator: invalid pattern [<v>]). fancy-regex refuses the lone
  backslash of invalids/inputs-bad-validator-syntax as Java does.

"defaults needs required: true" is deliberately not added (D11).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…Start

The oracle driver returns Ran::StartRefused for a start that fails with
Error::InvalidInput instead of Broken. The ratchet matches it against a
golden refused entry only through a Seam::Start group, comparing egret's
lines with the 422 recorded in corpus.json as a multiset (Kestra reports
in HashSet order) and requiring the group's prefix on every line.

Banks invalids/inputs-with-multiple-constraint-violations (Seam::Start)
and invalids/inputs-bad-validator-syntax (Seam::Save,
input_validator_invalid); their STILL_OVER_ACCEPTED, UNATTRIBUTED and
baseline rows are gone. Ratchet 301/50/52/2 -> 301/50/50/0; census
unchanged (295 live, 25 validate-refused, 21 unknown type).
handler-surface regenerated (ceiling 281 -> 283).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…d roadmap row

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… at save

An INT/FLOAT/DURATION min or max, or a DATETIME/DATE/TIME after or before,
that does not parse was accepted at save and then refused every start that
gave the input a value, with a message that blamed the posted value. Kestra's
bound fields are typed, so Jackson refuses such a flow when it reads it.

Add the save-only finding `input_bound_invalid` (not the run gate), built on
the same parsers the start check uses. A non-integral INT bound, which
Jackson's ACCEPT_FLOAT_AS_INT would truncate, is refused instead (recorded
divergence); the start check keeps failing closed on a stored one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… its meaning

The start check wrapped the validator as `\A(?:v)\z` while save compiled `v`
alone. An extended-mode `#` comment swallowed the wrapper's `)\z`, so
`(?x) [a-z]+ # letters` saved clean and refused every start; and a stored
`abc)|(?:.*` wrapped to a pattern that accepted every value.

`inputs_check::anchored_validator` now compiles the pattern as written first
(as Java does), then the anchored form, putting a newline before the closing
`)\z` only when `v`'s last line holds a comment. Save and start both call it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… it once

The start check stored a JSON input's parsed value, and the render's
`with_declared_defaults` still read any string of a JSON input as JSON text.
A JSON string posted as `"123"` was stored as the string 123 and rendered as
the number 123 (likewise `"true"` and `"[1,2]"`), the double parse the run
form's `wireValue` exists to avoid.

A JSON input whose parsed value is a string is now stored as that string's
JSON text: the render reads it back to the string, and a re-check stores the
same thing again. Every other value is stored as parsed, so rows written
before the check render as they did.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The backfill create route checked only `required`, so a value every tick
would refuse (an INT below its `min`, a schedule's `n: abc`) was accepted and
the backfill ended at its first tick. Create now also runs
`checked_inputs` on the schedule's inputs overlaid by the backfill's (the
map each tick checks) and refuses with the start check's text. The existing
object/FILE/templated/required checks and their messages are unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…llow-ups

Spec (EN+VI): D1 no longer mentions a clock; D7's idempotence sentence names
the JSON-string storage that makes it hold; section 7 records the bound
finding (and the ACCEPT_FLOAT_AS_INT divergence), the validator anchoring,
the JSON-string storage, the backfill create check, and that YAML/ION text
inputs of live flows now store and render as parsed objects; section 6
records review findings M3, M4, M5 and M8 as follow-ups. Plan (EN+VI): the
SECRET-masking note for the validate endpoint under B1 and the stale
DATETIME/DURATION note under A2.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@viethqb
viethqb merged commit 1a72e82 into main Oct 28, 2026
6 checks passed
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