config.triggers and are authored declaratively
alongside the rest of the graph.
The load-bearing idea is identity: an unattended run that touches an external
system authorizes as a service principal you bind to it — never as you, and
never as an anonymous system actor. The authoring surface enforces this, so an
unsafe automation is rejected when you write it, not at 06:00 when it fires.
The shape
Every trigger shares a small envelope and carries exactly one per-kind block:kind—cron,change, orthreshold. Exactly the matching per-kind block must be present.enabled— omit ortrueto arm the trigger; only an explicitfalsedisables it (a disabled trigger is inert but preserved, handy for rollback).runs_as— the service principal the fired run authorizes as. Required for any unattended side effect (see Firing identity).acknowledged— your explicit sign-off that an auto-firing writeback is intended (see Acknowledgement).id— server-minted and content-addressed; leave it unset when authoring. It stays stable across re-saves and only changes when the trigger’s definition changes.
Cron — fire on a schedule
expression— a standard 5-field cron (minute hour day-of-month month day-of-week).timezone— an IANA name (e.g.Europe/Dublin); omit for UTC.catchup(on the envelope, cron-only) —none(default) orone: run a single catch-up pass for fire windows the scheduler missed while it was down.
Change — fire when upstream data moves
upstream_node_ids— the upstream nodes to watch; an empty list watches all direct upstreams.debounce_ms— coalesce rapid upstream changes into a single fire within this window.predicate— optional. A serialized boolean expression (same shape as a threshold predicate) that gates whether a change actually fires; omit to fire on any change.
runs_as principal.
Threshold — fire a writeback when a predicate holds
A threshold trigger sits on the node whose output it watches and fires a named writeback only when a predicate over that output is true:fires_writeback_node_id— the writeback this threshold invokes. Required, and must resolve to a writeback node.predicate— required. A boolean expression over thetrigger.thresholdscope, which exposes the watched output’s summary stats:artifact.row_count, and per columnartifact.<col>__null_count,artifact.<col>__value_count,artifact.<col>__min,artifact.<col>__max. Example source strings:artifact.row_count = 0,artifact.amount__null_count > 10. It’s authored as an ExpressionSpec; the compiler pins it to a canonical AST hash, so author it through the tooling rather than hand-writing the hash.cooldown_ms— suppress re-firing within this window after a fire.input_mappings— optional map of writeback input name → a template expression resolved from the firing context.
core_threshold_dry_run
MCP tool: it reports whether the predicate evaluated, whether it matched the
node’s latest output, and whether the firing identity resolves — without firing
anything.
Firing identity
A credential plus an unattended trigger equals unattended external access, and must name a durable principal.A scheduled or reactive run authorizes as
system:scheduler, not as you.
Your own grants never apply to it. That is deliberate: the run outlives your
authoring session, you may leave, and your grants can change without anyone
touching the pipeline. So any unattended run that reaches an external system must
name a service principal — a durable, grantable identity — via the trigger’s
runs_as (or the node’s config.runs_as as a fallback).
Binding one is a three-step path an agent or the API can drive end to end:
- Create a service principal in the tenant.
- Grant it what the run needs — for a writeback,
Secret:READon the referenced secret andPipeline:RUNon the graph. - Set
runs_ason the trigger (act-as-gated at bind time, so you can only bind a principal you’re allowed to act as).
TRIGGER_IDENTITY_UNRESOLVED) rather than silently downgraded to a system
actor.
Acknowledgement
Identity says who fires; acknowledgement says you meant it to. An auto-firing (cron or change) trigger on a writeback node must setacknowledged: true —
an explicit confirmation that you intend an unattended external side effect.
Without it the write is rejected at authoring time.
What the authoring surface enforces
Three rules reject an unsafe automation the moment you write it, not when it fires:
The last rule is why a scheduled Python node that calls
to_sql with a stored
secret can’t ship without an identity: a credential plus a schedule is
unattended external access. The rejection names the remedy directly — “a
scheduled run authorizes as system:scheduler, not as you — your own grants do
not apply.”
Related
- Database writebacks — the governed delivery a trigger fires.
- Reliability policies — health, freshness, and incident rules that watch what your triggers produce.