Schedule Organization
Schedule Organization protocol schemas
The ACTING ORGANIZATION of a time-triggered flow — the one start-node key that says which organization a scheduled run executes as.
Why the key exists
A record-change flow inherits its organization from the write that fired it:
the triggering session's tenantId rides the AutomationContext into
the run, so every tenant-scoped write below it — sys_inbox_message,
sys_notification_delivery, sys_automation_run — resolves an organization
the way a session write does. A TIME-triggered flow has no such session. The
schedule trigger and the time-relative sweep launch their runs from a job
tick, and a job tick carries no identity at all, so the run reached the
tenancy guard (system-write-organization.ts) with nothing to offer it. On
an install holding more than one sys_organization that guard refuses,
correctly and by design — and the refusal landed on rows the run never
reported: the notification wrote with organization_id = NULL, every
tenant-scoped row beneath it was refused, and the tick still summarised
itself as healthy.
The ruling this key implements
Maintainer, 2026-09-08, verbatim:
多组织定时任务本来只能在组织内运行,应该带组织ID,不允许跨组织的定时任务。
A time-triggered flow is organization-scoped by construction: it names
one organization and the run executes as that organization. There is
deliberately no fan-out — a tenant that wants the same sweep in N
organizations declares it N times — and there is deliberately no fallback: a
flow that names none is a DECLARATION ERROR, not a run that quietly picks
one. Guessing is the failure this key exists to prevent, and the platform
organization is not a safe guess: a wrong organization_id is worse than a
null, because a null is visibly missing while a wrong value is silently
authoritative to every report, export and cleanup script that filters by
organization.
Where it lives, and why there
On the flow's START node config, beside the cadence it scopes:
config: {
schedule: { type: 'cron', expression: '0 8 * * *' },
organization: 'org_msokm9oaz0cal87q',
}The start node is where every other trigger-binding fact already lives —
FlowSchema refuses a top-level schedule in as many words ("a schedule
flow declares its cron/interval as config.schedule on the START node, not
at the flow top level"), and resolveTriggerBinding hands the whole start
config to the trigger. Putting the organization at the flow top level would
split one binding across two layers; putting it inside the schedule
descriptor would make it invisible to the time-relative sweep, which carries
its cadence in the same slot but binds through a different descriptor. One
key, one layer, both time triggers.
Why ScheduleOrganization… and not FlowActingOrganization…
The key governs both trigger kinds, and FlowTriggerKind lists
time_relative and schedule as two of its four members — so the name looks
inaccurate for half its subjects. It is not, and the deciding reading is the
AUTHORABLE surface rather than the derived kind: FlowSchema.type is
z.enum(['autolaunched', 'record_change', 'schedule', 'screen', 'api']) and
has no time_relative member at all. A time-relative sweep is authored as
type: 'schedule' with a timeRelative descriptor on its start node — the
docs say so in as many words ("a schedule flow whose start node declares
a timeRelative descriptor"), and TimeRelativeTriggerSchema's own opening
line says the trigger "sweeps an object on a schedule". FlowTriggerKind
splits the two because the ENGINE routes them to different triggers; its
precedence note distinguishes a sweep from "a plain schedule flow", which is
a split inside the schedule family, not out of it.
⇒ Every flow this key applies to declares type: 'schedule'. The name is
accurate for both subjects, and a minor freezes it, so this is recorded
rather than left to be re-litigated.
Source: packages/spec/src/automation/schedule-organization.zod.ts
TypeScript Usage
import { ScheduleOrganizationSchema } from '@objectstack/spec/automation';
import type { ScheduleOrganization } from '@objectstack/spec/automation';
// Validate data
const result = ScheduleOrganizationSchema.parse(data);ScheduleOrganization
Organization id (sys_organization.id) this scheduled/time-relative flow runs as. Required: a time-triggered run has no session to inherit a tenant from.
Type: string