@sanity/workflow-engine 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/define.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as v from "valibot";
2
- import { andConditions, expandRequiredRoles, GROQ_IDENTIFIER, formatValidationError, issuesFromValibot, AuthoringWorkflowSchema, AuthoringStageSchema, AuthoringTaskSchema, AuthoringActionSchema, AuthoringTransitionSchema, AuthoringFieldEntrySchema, AuthoringOpSchema, GuardSchema, EffectSchema } from "./_chunks-es/schema.js";
2
+ import { andConditions, expandRequiredRoles, normalizeRoleAliases, GROQ_IDENTIFIER, formatValidationError, issuesFromValibot, AuthoringWorkflowSchema, AuthoringStageSchema, AuthoringActivitySchema, AuthoringActionSchema, AuthoringTransitionSchema, AuthoringFieldEntrySchema, AuthoringOpSchema, GuardSchema, EffectSchema } from "./_chunks-es/schema.js";
3
3
  function groq(strings, ...values) {
4
4
  return strings.reduce((out, part, i) => i === 0 ? part : `${out}${serializeGroqValue(values[i - 1])}${part}`, "");
5
5
  }
@@ -12,27 +12,29 @@ function serializeGroqValue(value) {
12
12
  return serialized;
13
13
  }
14
14
  function desugarWorkflow(authoring) {
15
- const ctx = {
16
- issues: [],
15
+ const issues = [];
16
+ checkReservedRoleAliasKeys(authoring.roleAliases, issues);
17
+ const roleAliases = normalizeRoleAliases(authoring.roleAliases), ctx = {
18
+ issues,
17
19
  claimFields: /* @__PURE__ */ new Map(),
18
20
  claimedFields: /* @__PURE__ */ new Set(),
19
- roleAliases: authoring.roleAliases
20
- }, workflowFields = desugarFieldEntries(authoring.fields, ["fields"], ctx), workflowLayer = layerOf(workflowFields), stages = authoring.stages.map((stage, i) => {
21
- const path = ["stages", i], stageFields = desugarFieldEntries(stage.fields, [...path, "fields"], ctx), stageEnv = {
21
+ roleAliases
22
+ }, workflowFields = desugarFieldEntries({ entries: authoring.fields, path: ["fields"], ctx }), workflowLayer = layerOf(workflowFields), stages = authoring.stages.map((stage, i) => {
23
+ const path = ["stages", i], stageFields = desugarFieldEntries({ entries: stage.fields, path: [...path, "fields"], ctx }), stageEnv = {
22
24
  layers: [
23
25
  { scope: "stage", entries: layerOf(stageFields) },
24
26
  { scope: "workflow", entries: workflowLayer }
25
27
  ]
26
- }, tasks = (stage.tasks ?? []).map(
27
- (task, j) => desugarTask(task, [...path, "tasks", j], stageEnv, ctx)
28
+ }, activities = (stage.activities ?? []).map(
29
+ (activity, j) => desugarActivity({ activity, path: [...path, "activities", j], stageEnv, ctx })
28
30
  ), transitions = (stage.transitions ?? []).map(
29
- (transition, k) => desugarTransition(transition, [...path, "transitions", k], stageEnv, ctx)
30
- ), editable = desugarStageEditable(
31
- stage.editable,
32
- editableSlotNames(workflowFields, stageFields, tasks),
33
- [...path, "editable"],
31
+ (transition, k) => desugarTransition({ transition, path: [...path, "transitions", k], stageEnv, ctx })
32
+ ), editable = desugarStageEditable({
33
+ overrides: stage.editable,
34
+ inScope: editableSlotNames({ workflowFields, stageFields, activities }),
35
+ path: [...path, "editable"],
34
36
  ctx
35
- );
37
+ });
36
38
  return {
37
39
  ...stripUndefined({
38
40
  name: stage.name,
@@ -41,7 +43,7 @@ function desugarWorkflow(authoring) {
41
43
  guards: stage.guards
42
44
  }),
43
45
  ...stageFields ? { fields: stageFields } : {},
44
- ...tasks.length > 0 ? { tasks } : {},
46
+ ...activities.length > 0 ? { activities } : {},
45
47
  ...transitions.length > 0 ? { transitions } : {},
46
48
  ...editable ? { editable } : {}
47
49
  };
@@ -49,43 +51,99 @@ function desugarWorkflow(authoring) {
49
51
  return checkUnclaimedClaimFields(ctx), { definition: {
50
52
  ...stripUndefined({
51
53
  name: authoring.name,
52
- version: authoring.version,
53
54
  title: authoring.title,
54
55
  description: authoring.description,
55
56
  role: authoring.role,
56
57
  initialStage: authoring.initialStage,
57
58
  predicates: authoring.predicates,
58
- roleAliases: authoring.roleAliases
59
+ roleAliases
59
60
  }),
60
61
  ...workflowFields ? { fields: workflowFields } : {},
61
62
  stages
62
63
  }, issues: ctx.issues };
63
64
  }
64
- function desugarFieldEntries(entries, path, ctx) {
65
- return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => {
66
- if (entry.type === "claim") {
67
- const desugared = {
68
- ...stripUndefined({ name: entry.name, title: entry.title, description: entry.description }),
69
- type: "value.actor",
70
- source: { type: "write" }
71
- };
72
- return ctx.claimFields.set(desugared, { name: entry.name, path: [...path, i] }), desugared;
73
- }
74
- const editable = normalizeEditable(entry.editable, [...path, i, "editable"], ctx);
75
- return {
76
- ...stripUndefined({
77
- type: entry.type,
78
- name: entry.name,
79
- title: entry.title,
80
- description: entry.description,
81
- required: entry.required,
82
- editable
83
- }),
84
- source: entry.source
85
- };
86
- });
65
+ function checkReservedRoleAliasKeys(aliases, issues) {
66
+ for (const key of Object.keys(aliases ?? {}))
67
+ key.startsWith("$") && issues.push({
68
+ path: ["roleAliases", key],
69
+ message: `role alias key "${key}" uses the reserved "$" prefix \u2014 that namespace is the engine's stored spelling for the universal fulfiller. Use "*" to mean "fulfills any gate", or rename the role.`
70
+ });
87
71
  }
88
- function normalizeEditable(editable, path, ctx) {
72
+ const TODOLIST_OF = [
73
+ { type: "string", name: "label", title: "Label" },
74
+ { type: "string", name: "status", title: "Status" },
75
+ { type: "assignee", name: "assignee", title: "Assignee" },
76
+ { type: "date", name: "dueDate", title: "Due date" }
77
+ ], NOTES_OF = [
78
+ { type: "text", name: "body", title: "Body" },
79
+ { type: "actor", name: "actor", title: "Actor" },
80
+ { type: "datetime", name: "at", title: "At" }
81
+ ];
82
+ function desugarFieldEntries({
83
+ entries,
84
+ path,
85
+ ctx
86
+ }) {
87
+ return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => desugarFieldEntry({ entry, path: [...path, i], ctx }));
88
+ }
89
+ function desugarFieldEntry({
90
+ entry,
91
+ path,
92
+ ctx
93
+ }) {
94
+ if (entry.type === "claim") return desugarClaimField({ entry, path, ctx });
95
+ if (entry.type === "todoList" || entry.type === "notes")
96
+ return desugarListField({ entry, path, ctx });
97
+ const editable = normalizeEditable({ editable: entry.editable, path: [...path, "editable"], ctx });
98
+ return {
99
+ ...stripUndefined({
100
+ type: entry.type,
101
+ name: entry.name,
102
+ title: entry.title,
103
+ description: entry.description,
104
+ required: entry.required,
105
+ initialValue: entry.initialValue,
106
+ editable,
107
+ fields: entry.fields,
108
+ of: entry.of
109
+ })
110
+ };
111
+ }
112
+ function desugarClaimField({
113
+ entry,
114
+ path,
115
+ ctx
116
+ }) {
117
+ const desugared = {
118
+ ...stripUndefined({ name: entry.name, title: entry.title, description: entry.description }),
119
+ type: "actor"
120
+ };
121
+ return ctx.claimFields.set(desugared, { name: entry.name, path }), desugared;
122
+ }
123
+ function desugarListField({
124
+ entry,
125
+ path,
126
+ ctx
127
+ }) {
128
+ const editable = normalizeEditable({ editable: entry.editable, path: [...path, "editable"], ctx });
129
+ return {
130
+ ...stripUndefined({
131
+ name: entry.name,
132
+ title: entry.title,
133
+ description: entry.description,
134
+ required: entry.required,
135
+ initialValue: entry.initialValue,
136
+ editable
137
+ }),
138
+ type: "array",
139
+ of: entry.type === "todoList" ? TODOLIST_OF : NOTES_OF
140
+ };
141
+ }
142
+ function normalizeEditable({
143
+ editable,
144
+ path,
145
+ ctx
146
+ }) {
89
147
  if (editable === void 0 || editable === !0) return editable;
90
148
  if (Array.isArray(editable)) {
91
149
  const condition = rolesCondition(editable, ctx.roleAliases);
@@ -100,26 +158,35 @@ function normalizeEditable(editable, path, ctx) {
100
158
  }
101
159
  return editable;
102
160
  }
103
- function editableSlotNames(workflowFields, stageFields, tasks) {
161
+ function editableSlotNames({
162
+ workflowFields,
163
+ stageFields,
164
+ activities
165
+ }) {
104
166
  const names = /* @__PURE__ */ new Set(), collect = (entries) => {
105
167
  for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
106
168
  };
107
169
  collect(workflowFields), collect(stageFields);
108
- for (const task of tasks) collect(task.fields);
170
+ for (const activity of activities) collect(activity.fields);
109
171
  return names;
110
172
  }
111
- function desugarStageEditable(overrides, inScope, path, ctx) {
173
+ function desugarStageEditable({
174
+ overrides,
175
+ inScope,
176
+ path,
177
+ ctx
178
+ }) {
112
179
  if (overrides === void 0) return;
113
180
  const out = {};
114
181
  for (const [name, value] of Object.entries(overrides)) {
115
182
  if (!inScope.has(name)) {
116
183
  ctx.issues.push({
117
184
  path: [...path, name],
118
- message: `stage editable override "${name}" does not narrow an editable slot in scope \u2014 name a workflow/stage/task slot of this stage that declares \`editable\``
185
+ message: `stage editable override "${name}" does not narrow an editable slot in scope \u2014 name a workflow/stage/activity slot of this stage that declares \`editable\``
119
186
  });
120
187
  continue;
121
188
  }
122
- const normalized = normalizeEditable(value, [...path, name], ctx);
189
+ const normalized = normalizeEditable({ editable: value, path: [...path, name], ctx });
123
190
  normalized !== void 0 && (out[name] = normalized);
124
191
  }
125
192
  return Object.keys(out).length > 0 ? out : void 0;
@@ -127,46 +194,72 @@ function desugarStageEditable(overrides, inScope, path, ctx) {
127
194
  function layerOf(entries) {
128
195
  return new Map((entries ?? []).map((entry) => [entry.name, entry]));
129
196
  }
130
- function desugarTask(task, path, stageEnv, ctx) {
131
- const taskFields = desugarFieldEntries(task.fields, [...path, "fields"], ctx), env = {
132
- layers: [{ scope: "task", entries: layerOf(taskFields) }, ...stageEnv.layers]
133
- }, ops = desugarOps(task.ops, [...path, "ops"], env, task.name, ctx), actions = (task.actions ?? []).map(
134
- (action, a) => desugarAction(action, [...path, "actions", a], env, task.name, ctx)
135
- ), target = desugarTarget(task.target, env, [...path, "target"], ctx);
197
+ function desugarActivity({
198
+ activity,
199
+ path,
200
+ stageEnv,
201
+ ctx
202
+ }) {
203
+ const activityFields = desugarFieldEntries({
204
+ entries: activity.fields,
205
+ path: [...path, "fields"],
206
+ ctx
207
+ }), env = {
208
+ layers: [{ scope: "activity", entries: layerOf(activityFields) }, ...stageEnv.layers]
209
+ }, ops = desugarOps({
210
+ ops: activity.ops,
211
+ path: [...path, "ops"],
212
+ env,
213
+ firingActivity: activity.name,
214
+ ctx
215
+ }), actions = (activity.actions ?? []).map(
216
+ (action, a) => desugarAction({ action, path: [...path, "actions", a], env, activityName: activity.name, ctx })
217
+ ), target = desugarTarget({ target: activity.target, env, path: [...path, "target"], ctx });
136
218
  return {
137
219
  ...stripUndefined({
138
- name: task.name,
139
- title: task.title,
140
- description: task.description,
141
- kind: task.kind,
142
- filter: task.filter,
143
- requirements: task.requirements,
144
- completeWhen: task.completeWhen,
145
- failWhen: task.failWhen,
146
- effects: task.effects,
147
- subworkflows: task.subworkflows
220
+ name: activity.name,
221
+ title: activity.title,
222
+ description: activity.description,
223
+ kind: activity.kind,
224
+ filter: activity.filter,
225
+ requirements: activity.requirements,
226
+ completeWhen: activity.completeWhen,
227
+ failWhen: activity.failWhen,
228
+ effects: activity.effects,
229
+ subworkflows: activity.subworkflows
148
230
  }),
149
- activation: task.activation ?? "manual",
231
+ activation: activity.activation ?? "manual",
150
232
  ...target ? { target } : {},
151
- ...taskFields ? { fields: taskFields } : {},
233
+ ...activityFields ? { fields: activityFields } : {},
152
234
  ...ops ? { ops } : {},
153
235
  ...actions.length > 0 ? { actions } : {}
154
236
  };
155
237
  }
156
238
  const TARGET_DOC_KINDS = ["doc.ref", "doc.refs", "release.ref"];
157
- function desugarTarget(target, env, path, ctx) {
239
+ function desugarTarget({
240
+ target,
241
+ env,
242
+ path,
243
+ ctx
244
+ }) {
158
245
  if (target === void 0 || target.type === "url") return target;
159
- const ref = typeof target.field == "string" ? { field: target.field } : target.field, field = resolveRef(ref, env, [...path, "field"], ctx) ?? fallbackRef(ref), entry = entryAt(env, field);
246
+ const ref = typeof target.field == "string" ? { field: target.field } : target.field, field = resolveRef({ ref, env, path: [...path, "field"], ctx }) ?? fallbackRef(ref), entry = entryAt(env, field);
160
247
  return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
161
248
  path: [...path, "field"],
162
- message: `manual task target references "${field.field}" of kind "${entry.type}" \u2014 a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
249
+ message: `manual activity target references "${field.field}" of kind "${entry.type}" \u2014 a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
163
250
  }), { type: "field", field };
164
251
  }
165
- function desugarAction(action, path, env, taskName, ctx) {
252
+ function desugarAction({
253
+ action,
254
+ path,
255
+ env,
256
+ activityName,
257
+ ctx
258
+ }) {
166
259
  if ("type" in action)
167
- return desugarClaimAction(action, path, env, ctx);
168
- const filter = andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps(action.ops, [...path, "ops"], env, taskName, ctx) ?? [];
169
- return action.status !== void 0 && ops.push({ type: "status.set", task: taskName, status: action.status }), {
260
+ return desugarClaimAction({ action, path, env, ctx });
261
+ const filter = andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps({ ops: action.ops, path: [...path, "ops"], env, firingActivity: activityName, ctx }) ?? [];
262
+ return action.status !== void 0 && ops.push({ type: "status.set", activity: activityName, status: action.status }), {
170
263
  ...stripUndefined({
171
264
  name: action.name,
172
265
  title: action.title,
@@ -178,14 +271,26 @@ function desugarAction(action, path, env, taskName, ctx) {
178
271
  ...ops.length > 0 ? { ops } : {}
179
272
  };
180
273
  }
181
- function desugarClaimAction(action, path, env, ctx) {
182
- const ref = typeof action.field == "string" ? { field: action.field } : action.field, resolved = resolveRef(ref, env, [...path, "field"], ctx);
274
+ function desugarClaimAction({
275
+ action,
276
+ path,
277
+ env,
278
+ ctx
279
+ }) {
280
+ const ref = typeof action.field == "string" ? { field: action.field } : action.field, resolved = resolveRef({ ref, env, path: [...path, "field"], ctx });
183
281
  if (resolved) {
184
282
  const entry = entryAt(env, resolved);
185
- entry && entry.type !== "value.actor" && ctx.issues.push({
283
+ entry && entry.type !== "actor" && ctx.issues.push({
284
+ path: [...path, "field"],
285
+ message: `claim action "${action.name}" references "${resolved.field}" of kind "${entry.type}" \u2014 a claim pair needs an actor-valued entry (a "claim" or "actor" field declaration)`
286
+ }), checkShadowedClaimTarget({
287
+ actionName: action.name,
288
+ field: ref.field,
289
+ resolved,
290
+ env,
186
291
  path: [...path, "field"],
187
- message: `claim action "${action.name}" references "${resolved.field}" of kind "${entry.type}" \u2014 a claim pair needs an actor-valued entry (a "claim" or "value.actor" field declaration)`
188
- }), checkShadowedClaimTarget(action.name, ref.field, resolved, env, [...path, "field"], ctx), entry && ctx.claimedFields.add(entry);
292
+ ctx
293
+ }), entry && ctx.claimedFields.add(entry);
189
294
  }
190
295
  const noSteal = `!defined($fields.${ref.field})`, filter = andConditions([
191
296
  noSteal,
@@ -204,7 +309,14 @@ function desugarClaimAction(action, path, env, ctx) {
204
309
  ...ops.length > 0 ? { ops } : {}
205
310
  };
206
311
  }
207
- function checkShadowedClaimTarget(actionName, field, resolved, env, path, ctx) {
312
+ function checkShadowedClaimTarget({
313
+ actionName,
314
+ field,
315
+ resolved,
316
+ env,
317
+ path,
318
+ ctx
319
+ }) {
208
320
  const nearest = env.layers.find((l) => l.entries.has(field));
209
321
  nearest === void 0 || nearest.scope === resolved.scope || ctx.issues.push({
210
322
  path,
@@ -215,11 +327,22 @@ function checkUnclaimedClaimFields(ctx) {
215
327
  for (const [entry, { name, path }] of ctx.claimFields)
216
328
  ctx.claimedFields.has(entry) || ctx.issues.push({
217
329
  path,
218
- message: `claim field "${name}" is never referenced by a claim action \u2014 you announced the pattern and wrote half of it. Declare a defineAction({ type: "claim", field: "${name}" }) or use a raw value.actor entry`
330
+ message: `claim field "${name}" is never referenced by a claim action \u2014 you announced the pattern and wrote half of it. Declare a defineAction({ type: "claim", field: "${name}" }) or use a raw actor entry`
219
331
  });
220
332
  }
221
- function desugarTransition(transition, path, stageEnv, ctx) {
222
- const ops = desugarOps(transition.ops, [...path, "ops"], stageEnv, void 0, ctx);
333
+ function desugarTransition({
334
+ transition,
335
+ path,
336
+ stageEnv,
337
+ ctx
338
+ }) {
339
+ const ops = desugarOps({
340
+ ops: transition.ops,
341
+ path: [...path, "ops"],
342
+ env: stageEnv,
343
+ firingActivity: void 0,
344
+ ctx
345
+ });
223
346
  return {
224
347
  ...stripUndefined({
225
348
  name: transition.name,
@@ -228,29 +351,46 @@ function desugarTransition(transition, path, stageEnv, ctx) {
228
351
  to: transition.to,
229
352
  effects: transition.effects
230
353
  }),
231
- filter: transition.filter ?? "$allTasksDone",
354
+ filter: transition.filter ?? "$allActivitiesDone",
232
355
  ...ops ? { ops } : {}
233
356
  };
234
357
  }
235
- function desugarOps(ops, path, env, firingTask, ctx) {
358
+ function desugarOps({
359
+ ops,
360
+ path,
361
+ env,
362
+ firingActivity,
363
+ ctx
364
+ }) {
236
365
  if (ops)
237
- return ops.map((op, i) => desugarOp(op, [...path, i], env, firingTask, ctx));
238
- }
239
- function desugarOp(op, path, env, firingTask, ctx) {
366
+ return ops.map((op, i) => desugarOp({ op, path: [...path, i], env, firingActivity, ctx }));
367
+ }
368
+ function desugarOp({
369
+ op,
370
+ path,
371
+ env,
372
+ firingActivity,
373
+ ctx
374
+ }) {
240
375
  if (op.type === "status.set") {
241
- const task = op.task ?? firingTask;
242
- return task === void 0 && ctx.issues.push({
243
- path: [...path, "task"],
244
- message: "status.set outside an action must name its target task"
245
- }), { type: "status.set", task: task ?? "", status: op.status };
376
+ const activity = op.activity ?? firingActivity;
377
+ return activity === void 0 && ctx.issues.push({
378
+ path: [...path, "activity"],
379
+ message: "status.set outside an action must name its target activity"
380
+ }), { type: "status.set", activity: activity ?? "", status: op.status };
246
381
  }
247
- if (op.type === "audit") return desugarAuditOp(op, path, env, ctx);
248
- const target = resolveRef(op.target, env, [...path, "target"], ctx) ?? fallbackRef(op.target);
382
+ if (op.type === "audit") return desugarAuditOp({ op, path, env, ctx });
383
+ const target = resolveRef({ ref: op.target, env, path: [...path, "target"], ctx }) ?? fallbackRef(op.target);
249
384
  return { ...op, target };
250
385
  }
251
386
  const AUDIT_STAMPS = { actor: { type: "actor" }, at: { type: "now" } };
252
- function desugarAuditOp(op, path, env, ctx) {
253
- const target = resolveRef(op.target, env, [...path, "target"], ctx) ?? fallbackRef(op.target);
387
+ function desugarAuditOp({
388
+ op,
389
+ path,
390
+ env,
391
+ ctx
392
+ }) {
393
+ const target = resolveRef({ ref: op.target, env, path: [...path, "target"], ctx }) ?? fallbackRef(op.target);
254
394
  if (op.value.type !== "object")
255
395
  return ctx.issues.push({
256
396
  path: [...path, "value"],
@@ -272,7 +412,12 @@ function desugarAuditOp(op, path, env, ctx) {
272
412
  }
273
413
  return { type: "field.append", target, value: { type: "object", fields } };
274
414
  }
275
- function resolveRef(ref, env, path, ctx) {
415
+ function resolveRef({
416
+ ref,
417
+ env,
418
+ path,
419
+ ctx
420
+ }) {
276
421
  const layers = ref.scope === void 0 ? env.layers : env.layers.filter((l) => l.scope === ref.scope);
277
422
  for (const layer of layers)
278
423
  if (layer.entries.has(ref.field)) return { scope: layer.scope, field: ref.field };
@@ -306,83 +451,115 @@ const RESERVED_CONDITION_VARS = [
306
451
  "row",
307
452
  "now",
308
453
  "effects",
309
- "tasks",
454
+ "activities",
310
455
  "subworkflows",
311
456
  "self",
312
457
  "stage",
313
458
  "parent",
314
459
  "ancestors",
315
- "allTasksDone",
316
- "anyTaskFailed"
460
+ "allActivitiesDone",
461
+ "anyActivityFailed"
317
462
  ], PREDICATE_CALLER_VARS = ["actor", "assigned", "can"];
318
463
  function knownList(ids) {
319
464
  return [...ids].map((s) => `"${s}"`).join(", ");
320
465
  }
321
- function checkDuplicates(names, what, issues) {
466
+ function checkDuplicates({
467
+ names,
468
+ what,
469
+ issues
470
+ }) {
322
471
  const seen = /* @__PURE__ */ new Set();
323
472
  for (const { name, path } of names)
324
473
  seen.has(name) && issues.push({ path, message: `duplicate ${what} "${name}" (already declared earlier)` }), seen.add(name);
325
474
  return seen;
326
475
  }
327
476
  function checkStages(def, issues) {
328
- const stageNames = checkDuplicates(
329
- def.stages.map((stage, i) => ({ name: stage.name, path: ["stages", i, "name"] })),
330
- "stage name",
477
+ const stageNames = checkDuplicates({
478
+ names: def.stages.map((stage, i) => ({ name: stage.name, path: ["stages", i, "name"] })),
479
+ what: "stage name",
331
480
  issues
332
- );
481
+ });
333
482
  for (const [i, stage] of def.stages.entries()) {
334
- const taskNames = checkDuplicates(
335
- (stage.tasks ?? []).map((task, j) => ({
336
- name: task.name,
337
- path: ["stages", i, "tasks", j, "name"]
483
+ const activityNames = checkDuplicates({
484
+ names: (stage.activities ?? []).map((activity, j) => ({
485
+ name: activity.name,
486
+ path: ["stages", i, "activities", j, "name"]
338
487
  })),
339
- `task name in stage "${stage.name}"`,
488
+ what: `activity name in stage "${stage.name}"`,
340
489
  issues
341
- );
342
- checkDuplicates(
343
- (stage.transitions ?? []).map((t, k) => ({
490
+ });
491
+ checkDuplicates({
492
+ names: (stage.transitions ?? []).map((t, k) => ({
344
493
  name: t.name,
345
494
  path: ["stages", i, "transitions", k, "name"]
346
495
  })),
347
- `transition name in stage "${stage.name}"`,
496
+ what: `transition name in stage "${stage.name}"`,
348
497
  issues
349
- ), checkTasks(def, i, taskNames, issues);
498
+ }), checkActivities({ def, i, activityNames, issues });
350
499
  }
351
500
  return stageNames;
352
501
  }
353
- function checkTasks(def, i, taskNames, issues) {
502
+ function checkActivities({
503
+ def,
504
+ i,
505
+ activityNames,
506
+ issues
507
+ }) {
354
508
  const stage = def.stages[i];
355
- for (const [j, task] of (stage.tasks ?? []).entries()) {
356
- const path = ["stages", i, "tasks", j];
357
- checkDuplicates(
358
- (task.actions ?? []).map((action, a) => ({
509
+ for (const [j, activity] of (stage.activities ?? []).entries()) {
510
+ const path = ["stages", i, "activities", j];
511
+ checkDuplicates({
512
+ names: (activity.actions ?? []).map((action, a) => ({
359
513
  name: action.name,
360
514
  path: [...path, "actions", a, "name"]
361
515
  })),
362
- `action name in task "${task.name}"`,
516
+ what: `action name in activity "${activity.name}"`,
363
517
  issues
364
- ), checkStatusSetTargets(task, taskNames, path, issues);
518
+ }), checkStatusSetTargets({ activity, activityNames, path, issues });
365
519
  }
366
520
  }
367
- function checkStatusSetTargets(task, taskNames, path, issues) {
368
- checkStatusSetOps(task.ops, taskNames, [...path, "ops"], issues);
369
- for (const [a, action] of (task.actions ?? []).entries())
370
- checkStatusSetOps(action.ops, taskNames, [...path, "actions", a, "ops"], issues);
521
+ function checkStatusSetTargets({
522
+ activity,
523
+ activityNames,
524
+ path,
525
+ issues
526
+ }) {
527
+ checkStatusSetOps({ ops: activity.ops, activityNames, path: [...path, "ops"], issues });
528
+ for (const [a, action] of (activity.actions ?? []).entries())
529
+ checkStatusSetOps({
530
+ ops: action.ops,
531
+ activityNames,
532
+ path: [...path, "actions", a, "ops"],
533
+ issues
534
+ });
371
535
  }
372
- function checkStatusSetOps(ops, taskNames, path, issues) {
536
+ function checkStatusSetOps({
537
+ ops,
538
+ activityNames,
539
+ path,
540
+ issues
541
+ }) {
373
542
  for (const [o, op] of (ops ?? []).entries())
374
- op.type !== "status.set" || taskNames.has(op.task) || issues.push({
375
- path: [...path, o, "task"],
376
- message: `status.set targets task "${op.task}" which is not declared in this stage. Known tasks: ${knownList(taskNames)}`
543
+ op.type !== "status.set" || activityNames.has(op.activity) || issues.push({
544
+ path: [...path, o, "activity"],
545
+ message: `status.set targets activity "${op.activity}" which is not declared in this stage. Known activities: ${knownList(activityNames)}`
377
546
  });
378
547
  }
379
- function checkInitialStage(def, stageNames, issues) {
548
+ function checkInitialStage({
549
+ def,
550
+ stageNames,
551
+ issues
552
+ }) {
380
553
  stageNames.has(def.initialStage) || issues.push({
381
554
  path: ["initialStage"],
382
555
  message: `initialStage "${def.initialStage}" is not a declared stage. Known stages: ${knownList(stageNames)}`
383
556
  });
384
557
  }
385
- function checkTransitionTargets(def, stageNames, issues) {
558
+ function checkTransitionTargets({
559
+ def,
560
+ stageNames,
561
+ issues
562
+ }) {
386
563
  for (const [i, stage] of def.stages.entries())
387
564
  for (const [k, t] of (stage.transitions ?? []).entries())
388
565
  stageNames.has(t.to) || issues.push({
@@ -393,17 +570,33 @@ function checkTransitionTargets(def, stageNames, issues) {
393
570
  function checkEffectNames(def, issues) {
394
571
  const sites = [];
395
572
  for (const [i, stage] of def.stages.entries()) {
396
- for (const [j, task] of (stage.tasks ?? []).entries()) {
397
- collectEffects(task.effects, ["stages", i, "tasks", j, "effects"], sites);
398
- for (const [a, action] of (task.actions ?? []).entries())
399
- collectEffects(action.effects, ["stages", i, "tasks", j, "actions", a, "effects"], sites);
573
+ for (const [j, activity] of (stage.activities ?? []).entries()) {
574
+ collectEffects({
575
+ effects: activity.effects,
576
+ path: ["stages", i, "activities", j, "effects"],
577
+ sites
578
+ });
579
+ for (const [a, action] of (activity.actions ?? []).entries())
580
+ collectEffects({
581
+ effects: action.effects,
582
+ path: ["stages", i, "activities", j, "actions", a, "effects"],
583
+ sites
584
+ });
400
585
  }
401
586
  for (const [k, t] of (stage.transitions ?? []).entries())
402
- collectEffects(t.effects, ["stages", i, "transitions", k, "effects"], sites);
587
+ collectEffects({ effects: t.effects, path: ["stages", i, "transitions", k, "effects"], sites });
403
588
  }
404
- checkDuplicates(sites, "effect name (registry key \u2014 unique per definition)", issues);
589
+ checkDuplicates({
590
+ names: sites,
591
+ what: "effect name (registry key \u2014 unique per definition)",
592
+ issues
593
+ });
405
594
  }
406
- function collectEffects(effects, path, sites) {
595
+ function collectEffects({
596
+ effects,
597
+ path,
598
+ sites
599
+ }) {
407
600
  for (const [e, effect] of (effects ?? []).entries())
408
601
  sites.push({ name: effect.name, path: [...path, e, "name"] });
409
602
  }
@@ -414,11 +607,11 @@ function checkGuardNames(def, issues) {
414
607
  path: ["stages", i, "guards", g, "name"]
415
608
  }))
416
609
  );
417
- checkDuplicates(
418
- sites,
419
- "guard name (the guard lake _id derives from it \u2014 unique per definition)",
610
+ checkDuplicates({
611
+ names: sites,
612
+ what: "guard name (the guard lake _id derives from it \u2014 unique per definition)",
420
613
  issues
421
- );
614
+ });
422
615
  }
423
616
  function fieldScopes(def) {
424
617
  const scopes = [
@@ -431,12 +624,12 @@ function fieldScopes(def) {
431
624
  path: ["stages", i, "fields"],
432
625
  label: `stage "${stage.name}" fields`
433
626
  });
434
- for (const [j, task] of (stage.tasks ?? []).entries())
627
+ for (const [j, activity] of (stage.activities ?? []).entries())
435
628
  scopes.push({
436
- entries: task.fields,
437
- scope: "task",
438
- path: ["stages", i, "tasks", j, "fields"],
439
- label: `task "${task.name}" fields`
629
+ entries: activity.fields,
630
+ scope: "activity",
631
+ path: ["stages", i, "activities", j, "fields"],
632
+ label: `activity "${activity.name}" fields`
440
633
  });
441
634
  }
442
635
  return scopes;
@@ -444,21 +637,28 @@ function fieldScopes(def) {
444
637
  function checkRequiredField(def, issues) {
445
638
  for (const { entries, scope, path, label } of fieldScopes(def))
446
639
  for (const [n, entry] of (entries ?? []).entries())
447
- entry.required === !0 && (scope !== "workflow" ? issues.push({
448
- path: [...path, n, "required"],
449
- message: `${label} entry "${entry.name}" is \`required\`, but \`required\` applies only to workflow-scope \`init\` entries \u2014 only those are caller-supplied at start/spawn`
450
- }) : entry.source.type !== "init" && issues.push({
451
- path: [...path, n, "required"],
452
- message: `field entry "${entry.name}" is \`required\` but its source is "${entry.source.type}" \u2014 \`required\` applies only to \`init\`-sourced entries (the caller fills them at start/spawn)`
453
- }));
640
+ if (entry.required === !0) {
641
+ if (scope !== "workflow")
642
+ issues.push({
643
+ path: [...path, n, "required"],
644
+ message: `${label} entry "${entry.name}" is \`required\`, but \`required\` applies only to workflow-scope \`input\` entries \u2014 only those are caller-supplied at start/spawn`
645
+ });
646
+ else if (entry.initialValue?.type !== "input") {
647
+ const seed = entry.initialValue?.type ?? "working memory (no initialValue)";
648
+ issues.push({
649
+ path: [...path, n, "required"],
650
+ message: `field entry "${entry.name}" has initialValue "${seed}" \u2014 \`required\` applies only to \`input\`-sourced entries (the caller fills them at start/spawn)`
651
+ });
652
+ }
653
+ }
454
654
  }
455
655
  function checkFieldEntryNames(def, issues) {
456
656
  for (const { entries, path, label } of fieldScopes(def))
457
- checkDuplicates(
458
- (entries ?? []).map((entry, n) => ({ name: entry.name, path: [...path, n, "name"] })),
459
- `field entry name in ${label}`,
657
+ checkDuplicates({
658
+ names: (entries ?? []).map((entry, n) => ({ name: entry.name, path: [...path, n, "name"] })),
659
+ what: `field entry name in ${label}`,
460
660
  issues
461
- );
661
+ });
462
662
  }
463
663
  function checkPredicates(def, issues) {
464
664
  const reserved = new Set(RESERVED_CONDITION_VARS), names = Object.keys(def.predicates ?? {});
@@ -468,9 +668,14 @@ function checkPredicates(def, issues) {
468
668
  message: `predicate "${name}" shadows the built-in $${name} \u2014 pick another name`
469
669
  });
470
670
  for (const [name, groq2] of Object.entries(def.predicates ?? {}))
471
- checkPredicateBody(name, groq2, names, issues);
472
- }
473
- function checkPredicateBody(name, groq2, names, issues) {
671
+ checkPredicateBody({ name, groq: groq2, names, issues });
672
+ }
673
+ function checkPredicateBody({
674
+ name,
675
+ groq: groq2,
676
+ names,
677
+ issues
678
+ }) {
474
679
  for (const caller of PREDICATE_CALLER_VARS)
475
680
  referencesVar(groq2, caller) && issues.push({
476
681
  path: ["predicates", name],
@@ -500,40 +705,53 @@ function nonActionFilterConditionSites(def) {
500
705
  groq: t.filter,
501
706
  path: ["stages", i, "transitions", k, "filter"],
502
707
  label: `transition "${t.name}" filter`
503
- }), collectBindingSites(
504
- t.effects,
505
- ["stages", i, "transitions", k, "effects"],
506
- `transition "${t.name}"`,
708
+ }), collectBindingSites({
709
+ effects: t.effects,
710
+ path: ["stages", i, "transitions", k, "effects"],
711
+ label: `transition "${t.name}"`,
507
712
  sites
508
- );
509
- for (const [j, task] of (stage.tasks ?? []).entries())
510
- collectTaskConditionSites(task, ["stages", i, "tasks", j], sites);
713
+ });
714
+ for (const [j, activity] of (stage.activities ?? []).entries())
715
+ collectActivityConditionSites({ activity, path: ["stages", i, "activities", j], sites });
511
716
  }
512
717
  return sites;
513
718
  }
514
- function collectTaskConditionSites(task, path, sites) {
719
+ function collectActivityConditionSites({
720
+ activity,
721
+ path,
722
+ sites
723
+ }) {
515
724
  for (const field of ["filter", "completeWhen", "failWhen"]) {
516
- const groq2 = task[field];
517
- groq2 !== void 0 && sites.push({ groq: groq2, path: [...path, field], label: `task "${task.name}".${field}` });
725
+ const groq2 = activity[field];
726
+ groq2 !== void 0 && sites.push({ groq: groq2, path: [...path, field], label: `activity "${activity.name}".${field}` });
518
727
  }
519
- collectBindingSites(task.effects, [...path, "effects"], `task "${task.name}"`, sites);
520
- for (const [a, action] of (task.actions ?? []).entries())
521
- collectBindingSites(
522
- action.effects,
523
- [...path, "actions", a, "effects"],
524
- `action "${action.name}"`,
728
+ collectBindingSites({
729
+ effects: activity.effects,
730
+ path: [...path, "effects"],
731
+ label: `activity "${activity.name}"`,
732
+ sites
733
+ });
734
+ for (const [a, action] of (activity.actions ?? []).entries())
735
+ collectBindingSites({
736
+ effects: action.effects,
737
+ path: [...path, "actions", a, "effects"],
738
+ label: `action "${action.name}"`,
525
739
  sites
526
- );
527
- collectSubworkflowSites(task, path, sites);
528
- }
529
- function collectSubworkflowSites(task, path, sites) {
530
- const sub = task.subworkflows;
740
+ });
741
+ collectSubworkflowSites({ activity, path, sites });
742
+ }
743
+ function collectSubworkflowSites({
744
+ activity,
745
+ path,
746
+ sites
747
+ }) {
748
+ const sub = activity.subworkflows;
531
749
  if (sub === void 0) return;
532
750
  const subPath = [...path, "subworkflows"];
533
751
  sites.push({
534
752
  groq: sub.forEach,
535
753
  path: [...subPath, "forEach"],
536
- label: `task "${task.name}".subworkflows.forEach`
754
+ label: `activity "${activity.name}".subworkflows.forEach`
537
755
  });
538
756
  for (const [group, record] of [
539
757
  ["with", sub.with],
@@ -543,10 +761,15 @@ function collectSubworkflowSites(task, path, sites) {
543
761
  sites.push({
544
762
  groq: groq2,
545
763
  path: [...subPath, group, key],
546
- label: `task "${task.name}".subworkflows.${group} "${key}"`
764
+ label: `activity "${activity.name}".subworkflows.${group} "${key}"`
547
765
  });
548
766
  }
549
- function collectBindingSites(effects, path, label, sites) {
767
+ function collectBindingSites({
768
+ effects,
769
+ path,
770
+ label,
771
+ sites
772
+ }) {
550
773
  for (const [e, effect] of (effects ?? []).entries())
551
774
  for (const [key, groq2] of Object.entries(effect.bindings ?? {}))
552
775
  sites.push({
@@ -562,12 +785,12 @@ function checkAssigneesEntries(def, issues) {
562
785
  message: "at most one assignees-kind field entry per scope \u2014 the inbox reads it by kind"
563
786
  });
564
787
  }
565
- function taskShape(task) {
788
+ function activityShape(activity) {
566
789
  return {
567
- hasActions: (task.actions ?? []).length > 0,
568
- hasEffects: (task.effects ?? []).length > 0,
569
- hasCompleteWhen: task.completeWhen !== void 0,
570
- hasSubworkflows: task.subworkflows !== void 0
790
+ hasActions: (activity.actions ?? []).length > 0,
791
+ hasEffects: (activity.effects ?? []).length > 0,
792
+ hasCompleteWhen: activity.completeWhen !== void 0,
793
+ hasSubworkflows: activity.subworkflows !== void 0
571
794
  };
572
795
  }
573
796
  const KIND_SHAPE_RULES = {
@@ -575,7 +798,7 @@ const KIND_SHAPE_RULES = {
575
798
  service: (s) => s.hasEffects ? [] : ["needs at least one effect \u2014 the automated work it performs"],
576
799
  manual: (s) => [
577
800
  s.hasActions || s.hasCompleteWhen ? void 0 : "needs a way to complete \u2014 an action to acknowledge it, or a `completeWhen` predicate to verify it (otherwise it auto-resolves on activation)",
578
- s.hasSubworkflows ? "is off-system work, not a fan-out \u2014 drop `subworkflows` or move it to its own task" : void 0
801
+ s.hasSubworkflows ? "is off-system work, not a fan-out \u2014 drop `subworkflows` or move it to its own activity" : void 0
579
802
  ].filter((m) => m !== void 0),
580
803
  receive: (s) => [
581
804
  s.hasCompleteWhen || s.hasSubworkflows ? void 0 : "needs a `completeWhen` (or `subworkflows`) condition to wait on",
@@ -587,53 +810,77 @@ const KIND_SHAPE_RULES = {
587
810
  s.hasSubworkflows ? "resolves on activation, so it cannot fan out with `subworkflows`" : void 0
588
811
  ].filter((m) => m !== void 0)
589
812
  };
590
- function checkTaskKinds(def, issues) {
813
+ function checkActivityKinds(def, issues) {
591
814
  for (const [i, stage] of def.stages.entries())
592
- for (const [j, task] of (stage.tasks ?? []).entries()) {
593
- const path = ["stages", i, "tasks", j];
594
- if (task.target !== void 0 && task.kind !== "manual" && issues.push({
815
+ for (const [j, activity] of (stage.activities ?? []).entries()) {
816
+ const path = ["stages", i, "activities", j];
817
+ if (activity.target !== void 0 && activity.kind !== "manual" && issues.push({
595
818
  path: [...path, "target"],
596
- message: `task "${task.name}" has a \`target\` but is not \`kind: "manual"\` \u2014 a target is the deep-link for off-system manual work`
597
- }), task.kind !== void 0)
598
- for (const fault of KIND_SHAPE_RULES[task.kind](taskShape(task)))
819
+ message: `activity "${activity.name}" has a \`target\` but is not \`kind: "manual"\` \u2014 a target is the deep-link for off-system manual work`
820
+ }), activity.kind !== void 0)
821
+ for (const fault of KIND_SHAPE_RULES[activity.kind](activityShape(activity)))
599
822
  issues.push({
600
823
  path: [...path, "kind"],
601
- message: `task "${task.name}" declares kind "${task.kind}" but ${fault}`
824
+ message: `activity "${activity.name}" declares kind "${activity.kind}" but ${fault}`
602
825
  });
603
826
  }
604
827
  }
605
828
  function checkWorkflowInvariants(def) {
606
829
  const issues = [], stageNames = checkStages(def, issues);
607
- return checkInitialStage(def, stageNames, issues), checkTransitionTargets(def, stageNames, issues), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues), checkRequiredField(def, issues), checkPredicates(def, issues), checkCanOutsideActionFilters(def, issues), checkAssigneesEntries(def, issues), checkTaskKinds(def, issues), issues;
830
+ return checkInitialStage({ def, stageNames, issues }), checkTransitionTargets({ def, stageNames, issues }), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues), checkRequiredField(def, issues), checkPredicates(def, issues), checkCanOutsideActionFilters(def, issues), checkAssigneesEntries(def, issues), checkActivityKinds(def, issues), issues;
608
831
  }
609
832
  function defineWorkflow(definition) {
610
- const label = labelFor("defineWorkflow", definition), parsed = parseOrThrow(AuthoringWorkflowSchema, definition, label), { definition: stored, issues: desugarIssues } = desugarWorkflow(parsed), issues = [...desugarIssues, ...checkWorkflowInvariants(stored)];
833
+ const label = labelFor("defineWorkflow", definition), parsed = parseOrThrow({ schema: AuthoringWorkflowSchema, input: definition, label }), { definition: stored, issues: desugarIssues } = desugarWorkflow(parsed), issues = [...desugarIssues, ...checkWorkflowInvariants(stored)];
611
834
  if (issues.length > 0) throw new Error(formatValidationError(label, issues));
612
835
  return stored;
613
836
  }
614
837
  function defineStage(stage) {
615
- return parseOrThrow(AuthoringStageSchema, stage, labelFor("defineStage", stage));
838
+ return parseOrThrow({
839
+ schema: AuthoringStageSchema,
840
+ input: stage,
841
+ label: labelFor("defineStage", stage)
842
+ });
616
843
  }
617
- function defineTask(task) {
618
- return parseOrThrow(AuthoringTaskSchema, task, labelFor("defineTask", task));
844
+ function defineActivity(activity) {
845
+ return parseOrThrow({
846
+ schema: AuthoringActivitySchema,
847
+ input: activity,
848
+ label: labelFor("defineActivity", activity)
849
+ });
619
850
  }
620
851
  function defineAction(action) {
621
- return parseOrThrow(AuthoringActionSchema, action, labelFor("defineAction", action));
852
+ return parseOrThrow({
853
+ schema: AuthoringActionSchema,
854
+ input: action,
855
+ label: labelFor("defineAction", action)
856
+ });
622
857
  }
623
858
  function defineTransition(transition) {
624
- return parseOrThrow(AuthoringTransitionSchema, transition, transitionLabel(transition));
859
+ return parseOrThrow({
860
+ schema: AuthoringTransitionSchema,
861
+ input: transition,
862
+ label: transitionLabel(transition)
863
+ });
625
864
  }
626
865
  function defineField(entry) {
627
- return parseOrThrow(AuthoringFieldEntrySchema, entry, labelFor("defineField", entry));
866
+ return parseOrThrow({
867
+ schema: AuthoringFieldEntrySchema,
868
+ input: entry,
869
+ label: labelFor("defineField", entry)
870
+ });
628
871
  }
629
872
  function defineOp(op) {
630
- return parseOrThrow(AuthoringOpSchema, op, "defineOp");
873
+ return parseOrThrow({ schema: AuthoringOpSchema, input: op, label: "defineOp" });
631
874
  }
632
875
  function defineGuard(guard) {
633
- return parseOrThrow(GuardSchema, guard, labelFor("defineGuard", guard));
876
+ return parseOrThrow({ schema: GuardSchema, input: guard, label: labelFor("defineGuard", guard) });
634
877
  }
635
878
  function defineEffect(effect) {
636
- return parseOrThrow(EffectSchema, effect, labelFor("defineEffect", effect));
879
+ return parseOrThrow({
880
+ schema: EffectSchema,
881
+ input: effect,
882
+ label: labelFor("defineEffect", effect)
883
+ });
637
884
  }
638
885
  function transitionLabel(transition) {
639
886
  const { name, to } = transition;
@@ -643,7 +890,11 @@ function defineEffectDescriptor(descriptor) {
643
890
  if (!descriptor.name) throw new Error("EffectDescriptor missing name");
644
891
  return descriptor;
645
892
  }
646
- function parseOrThrow(schema, input, label) {
893
+ function parseOrThrow({
894
+ schema,
895
+ input,
896
+ label
897
+ }) {
647
898
  const result = v.safeParse(schema, input);
648
899
  if (!result.success)
649
900
  throw new Error(formatValidationError(label, issuesFromValibot(result.issues)));
@@ -658,13 +909,13 @@ function labelFor(fn, value) {
658
909
  }
659
910
  export {
660
911
  defineAction,
912
+ defineActivity,
661
913
  defineEffect,
662
914
  defineEffectDescriptor,
663
915
  defineField,
664
916
  defineGuard,
665
917
  defineOp,
666
918
  defineStage,
667
- defineTask,
668
919
  defineTransition,
669
920
  defineWorkflow,
670
921
  groq