Skip to content

Commit 6bf13da

Browse files
committed
Fix Scheduled schedule block: time_cycle/tz, not cron/timezone
The Scheduled-trigger eval failed at release even after adding event: Schedule — 'missing timer_event_definition or schedule parameters for trigger'. Root cause (proven live end-to-end: import + release + delete): the schedule block's fields are time_cycle and tz, NOT cron and timezone. The wrong names import cleanly but fail release. Grounded in real Foundry sample workflows (anomali-threatstream, insider-risk-sailpoint/workday, ngsiem-importer), which all use schedule: {time_cycle, start_date, end_date, tz, skip_concurrent}. Scanning all foundry-sample-* workflows also surfaced a second valid shape: an event: Schedule trigger with NO schedule block at all (rapid-response, scalable-rtr) — a caller-scheduled job template where cadence is supplied at execution time. The validator must not reject that. Changes: - validate.py: flag schedule blocks using cron:/timezone: with rename hints to time_cycle:/tz:, and flag a schedule block missing time_cycle. Only fires when a schedule block is present, so caller-scheduled templates (no block) pass. - both trigger-types.md copies: correct the schedule block field names and document the caller-scheduled-template variant. - tests: wrong-field-names flagged, correct fields pass, no-block template passes. Verified live: a workflow with schedule: {time_cycle, tz, ...} imports AND releases (definition since deleted); the cron/timezone shape imports but fails release.
1 parent d019797 commit 6bf13da

4 files changed

Lines changed: 105 additions & 12 deletions

File tree

skills/authoring/references/trigger-types.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -254,22 +254,33 @@ Runs on a cron-like schedule.
254254

255255
Like a Signal trigger, a Scheduled trigger MUST carry an `event:` field naming its
256256
category, which is `Schedule`. Omitting it fails import with
257-
`code 2003: "unknown trigger event named "` — even when the `schedule:` block is
258-
present and well-formed. The cron and timezone live in `schedule:`; the `event:`
259-
is a separate, required top-level field.
257+
`code 2003: "unknown trigger event named "`. The schedule itself goes in a
258+
`schedule:` block whose fields are **`time_cycle`** (the cron expression) and
259+
**`tz`** (the timezone) — NOT `cron:`/`timezone:`. Those wrong names import but
260+
fail at release with "missing timer_event_definition or schedule parameters for
261+
trigger". Include `start_date`/`end_date` (empty strings unless bounding the
262+
window) and `skip_concurrent`.
260263

261264
```yaml
262265
trigger:
263266
next:
264267
- FirstAction
265-
name: Scheduled
266268
event: Schedule # REQUIRED — the trigger category
267269
type: Scheduled
268270
schedule:
269-
cron: "0 */6 * * *" # Every 6 hours
270-
timezone: UTC
271+
time_cycle: "0 */6 * * *" # cron expression — the field is time_cycle
272+
start_date: "" # empty unless bounding the start
273+
end_date: "" # empty unless bounding the end
274+
tz: Etc/UTC # timezone — the field is tz, not timezone
275+
skip_concurrent: true # skip a run if the previous one is still going
271276
```
272277

278+
A `schedule:` block is required only for a self-scheduled recurring workflow. An
279+
`event: Schedule` trigger with **no** `schedule:` block is also valid — that is a
280+
caller-scheduled job template, where the run cadence is supplied at execution time
281+
rather than baked into the definition (several Foundry sample workflows ship this
282+
shape).
283+
273284
**Warning**: Disable scheduled workflows after testing to avoid rate limiting.
274285

275286
---

skills/authoring/scripts/validate.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,37 @@ def structural_check(file_path):
869869
"timezone go in the 'schedule:' block; the 'event' is separate."
870870
)
871871

872+
# The Scheduled 'schedule' block's cron/timezone fields are named
873+
# 'time_cycle' and 'tz' — NOT 'cron'/'timezone'. The wrong names import
874+
# cleanly but fail at release with "missing timer_event_definition or
875+
# schedule parameters for trigger" (confirmed live). Flag them here so the
876+
# error surfaces at authoring time instead of release. Only checked when a
877+
# 'schedule:' block is present — an `event: Schedule` trigger with NO
878+
# schedule block is a valid on-demand job template (the schedule is
879+
# supplied by the caller at runtime), as several Foundry samples show.
880+
schedule = trigger.get("schedule")
881+
if isinstance(schedule, dict):
882+
if "cron" in schedule and "time_cycle" not in schedule:
883+
issues.append(
884+
"ERROR: Scheduled trigger's schedule uses 'cron:' — the release "
885+
"field is 'time_cycle:'. 'cron' imports but fails release with "
886+
"'missing timer_event_definition or schedule parameters'. Rename "
887+
"'cron' to 'time_cycle'."
888+
)
889+
if "timezone" in schedule and "tz" not in schedule:
890+
issues.append(
891+
"ERROR: Scheduled trigger's schedule uses 'timezone:' — the "
892+
"release field is 'tz:'. Rename 'timezone' to 'tz' (e.g. "
893+
"'tz: Etc/UTC')."
894+
)
895+
if "time_cycle" not in schedule:
896+
issues.append(
897+
"ERROR: Scheduled trigger's schedule block has no 'time_cycle' "
898+
"(the cron expression, e.g. '0 */6 * * *'). A schedule block "
899+
"must carry it, or omit the block entirely for a caller-scheduled "
900+
"job template. See references/trigger-types.md."
901+
)
902+
872903
# Signal triggers fire from an event and carry no caller-supplied input,
873904
# so a 'parameters' schema on them is invalid: any field it declares
874905
# becomes an undefined variable at release ("Signal triggers do not

skills/workflows/references/trigger-types.md

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,22 +196,33 @@ Runs on a cron-like schedule.
196196

197197
Like a Signal trigger, a Scheduled trigger MUST carry an `event:` field naming its
198198
category, which is `Schedule`. Omitting it fails import with
199-
`code 2003: "unknown trigger event named "` — even when the `schedule:` block is
200-
present and well-formed. The cron and timezone live in `schedule:`; the `event:`
201-
is a separate, required top-level field.
199+
`code 2003: "unknown trigger event named "`. The schedule itself goes in a
200+
`schedule:` block whose fields are **`time_cycle`** (the cron expression) and
201+
**`tz`** (the timezone) — NOT `cron:`/`timezone:`. Those wrong names import but
202+
fail at release with "missing timer_event_definition or schedule parameters for
203+
trigger". Include `start_date`/`end_date` (empty strings unless bounding the
204+
window) and `skip_concurrent`.
202205

203206
```yaml
204207
trigger:
205208
next:
206209
- FirstAction
207-
name: Scheduled
208210
event: Schedule # REQUIRED — the trigger category
209211
type: Scheduled
210212
schedule:
211-
cron: "0 */6 * * *" # Every 6 hours
212-
timezone: UTC
213+
time_cycle: "0 */6 * * *" # cron expression — the field is time_cycle
214+
start_date: "" # empty unless bounding the start
215+
end_date: "" # empty unless bounding the end
216+
tz: Etc/UTC # timezone — the field is tz, not timezone
217+
skip_concurrent: true # skip a run if the previous one is still going
213218
```
214219

220+
A `schedule:` block is required only for a self-scheduled recurring workflow. An
221+
`event: Schedule` trigger with **no** `schedule:` block is also valid — that is a
222+
caller-scheduled job template, where the run cadence is supplied at execution time
223+
rather than baked into the definition (several Foundry sample workflows ship this
224+
shape).
225+
215226
**Warning**: Disable scheduled workflows after testing to avoid rate limiting.
216227

217228
---

tests/test_validate.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,46 @@ def test_scheduled_trigger_with_event_ok(self, tmp_path):
376376
issues = validate.structural_check(str(f))
377377
assert not any("missing an 'event'" in i for i in issues)
378378

379+
def test_scheduled_schedule_cron_field_flagged(self, tmp_path):
380+
# A schedule block using 'cron:'/'timezone:' (instead of time_cycle/tz)
381+
# imports but fails release; flag the wrong field names at authoring time.
382+
f = tmp_path / "scheduled_wrong_fields.yaml"
383+
content = VALID_WORKFLOW.replace(
384+
" type: On demand",
385+
" type: Scheduled\n event: Schedule\n"
386+
" schedule:\n cron: '0 8 * * *'\n timezone: UTC",
387+
)
388+
f.write_text(content)
389+
issues = validate.structural_check(str(f))
390+
assert any("'cron:'" in i and "time_cycle" in i for i in issues)
391+
assert any("'timezone:'" in i and "tz" in i for i in issues)
392+
393+
def test_scheduled_schedule_correct_fields_ok(self, tmp_path):
394+
# A schedule block with the release-valid field names passes.
395+
f = tmp_path / "scheduled_right_fields.yaml"
396+
content = VALID_WORKFLOW.replace(
397+
" type: On demand",
398+
" type: Scheduled\n event: Schedule\n"
399+
" schedule:\n time_cycle: '0 8 * * *'\n tz: Etc/UTC\n"
400+
" start_date: ''\n end_date: ''\n skip_concurrent: true",
401+
)
402+
f.write_text(content)
403+
issues = validate.structural_check(str(f))
404+
assert not any("time_cycle" in i for i in issues)
405+
assert not any("schedule uses" in i for i in issues)
406+
407+
def test_scheduled_no_schedule_block_is_valid_template(self, tmp_path):
408+
# 'event: Schedule' with NO schedule block is a valid caller-scheduled job
409+
# template (several Foundry samples ship this) — must not be flagged for a
410+
# missing time_cycle.
411+
f = tmp_path / "scheduled_template.yaml"
412+
content = VALID_WORKFLOW.replace(
413+
"type: On demand", "type: Scheduled\n event: Schedule"
414+
)
415+
f.write_text(content)
416+
issues = validate.structural_check(str(f))
417+
assert not any("time_cycle" in i for i in issues)
418+
379419
def test_condition_without_default_or_expression_fails(self, tmp_path):
380420
# A condition with a bare 'next:' and neither 'default: true' nor a
381421
# cel_expression/expression is what the release-time validator rejects

0 commit comments

Comments
 (0)