@sanity/workflow-engine 0.10.0 → 0.12.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.cjs CHANGED
@@ -29,24 +29,26 @@ function serializeGroqValue(value) {
29
29
  return serialized;
30
30
  }
31
31
  function desugarWorkflow(authoring) {
32
- const ctx = {
33
- issues: [],
32
+ const issues = [];
33
+ checkReservedRoleAliasKeys(authoring.roleAliases, issues);
34
+ const roleAliases = schema.normalizeRoleAliases(authoring.roleAliases), ctx = {
35
+ issues,
34
36
  claimFields: /* @__PURE__ */ new Map(),
35
37
  claimedFields: /* @__PURE__ */ new Set(),
36
- roleAliases: authoring.roleAliases
38
+ roleAliases
37
39
  }, workflowFields = desugarFieldEntries(authoring.fields, ["fields"], ctx), workflowLayer = layerOf(workflowFields), stages = authoring.stages.map((stage, i) => {
38
40
  const path = ["stages", i], stageFields = desugarFieldEntries(stage.fields, [...path, "fields"], ctx), stageEnv = {
39
41
  layers: [
40
42
  { scope: "stage", entries: layerOf(stageFields) },
41
43
  { scope: "workflow", entries: workflowLayer }
42
44
  ]
43
- }, tasks = (stage.tasks ?? []).map(
44
- (task, j) => desugarTask(task, [...path, "tasks", j], stageEnv, ctx)
45
+ }, activities = (stage.activities ?? []).map(
46
+ (activity, j) => desugarActivity(activity, [...path, "activities", j], stageEnv, ctx)
45
47
  ), transitions = (stage.transitions ?? []).map(
46
48
  (transition, k) => desugarTransition(transition, [...path, "transitions", k], stageEnv, ctx)
47
49
  ), editable = desugarStageEditable(
48
50
  stage.editable,
49
- editableSlotNames(workflowFields, stageFields, tasks),
51
+ editableSlotNames(workflowFields, stageFields, activities),
50
52
  [...path, "editable"],
51
53
  ctx
52
54
  );
@@ -58,7 +60,7 @@ function desugarWorkflow(authoring) {
58
60
  guards: stage.guards
59
61
  }),
60
62
  ...stageFields ? { fields: stageFields } : {},
61
- ...tasks.length > 0 ? { tasks } : {},
63
+ ...activities.length > 0 ? { activities } : {},
62
64
  ...transitions.length > 0 ? { transitions } : {},
63
65
  ...editable ? { editable } : {}
64
66
  };
@@ -66,41 +68,76 @@ function desugarWorkflow(authoring) {
66
68
  return checkUnclaimedClaimFields(ctx), { definition: {
67
69
  ...stripUndefined({
68
70
  name: authoring.name,
69
- version: authoring.version,
70
71
  title: authoring.title,
71
72
  description: authoring.description,
72
73
  role: authoring.role,
73
74
  initialStage: authoring.initialStage,
74
75
  predicates: authoring.predicates,
75
- roleAliases: authoring.roleAliases
76
+ roleAliases
76
77
  }),
77
78
  ...workflowFields ? { fields: workflowFields } : {},
78
79
  stages
79
80
  }, issues: ctx.issues };
80
81
  }
82
+ function checkReservedRoleAliasKeys(aliases, issues) {
83
+ for (const key of Object.keys(aliases ?? {}))
84
+ key.startsWith("$") && issues.push({
85
+ path: ["roleAliases", key],
86
+ 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.`
87
+ });
88
+ }
89
+ const TODOLIST_OF = [
90
+ { type: "string", name: "label", title: "Label" },
91
+ { type: "string", name: "status", title: "Status" },
92
+ { type: "assignee", name: "assignee", title: "Assignee" },
93
+ { type: "date", name: "dueDate", title: "Due date" }
94
+ ], NOTES_OF = [
95
+ { type: "text", name: "body", title: "Body" },
96
+ { type: "actor", name: "actor", title: "Actor" },
97
+ { type: "datetime", name: "at", title: "At" }
98
+ ];
81
99
  function desugarFieldEntries(entries, path, ctx) {
82
- return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => {
83
- if (entry.type === "claim") {
84
- const desugared = {
85
- ...stripUndefined({ name: entry.name, title: entry.title, description: entry.description }),
86
- type: "value.actor",
87
- source: { type: "write" }
88
- };
89
- return ctx.claimFields.set(desugared, { name: entry.name, path: [...path, i] }), desugared;
90
- }
91
- const editable = normalizeEditable(entry.editable, [...path, i, "editable"], ctx);
92
- return {
93
- ...stripUndefined({
94
- type: entry.type,
95
- name: entry.name,
96
- title: entry.title,
97
- description: entry.description,
98
- required: entry.required,
99
- editable
100
- }),
101
- source: entry.source
102
- };
103
- });
100
+ return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => desugarFieldEntry(entry, [...path, i], ctx));
101
+ }
102
+ function desugarFieldEntry(entry, path, ctx) {
103
+ if (entry.type === "claim") return desugarClaimField(entry, path, ctx);
104
+ if (entry.type === "todoList" || entry.type === "notes") return desugarListField(entry, path, ctx);
105
+ const editable = normalizeEditable(entry.editable, [...path, "editable"], ctx);
106
+ return {
107
+ ...stripUndefined({
108
+ type: entry.type,
109
+ name: entry.name,
110
+ title: entry.title,
111
+ description: entry.description,
112
+ required: entry.required,
113
+ initialValue: entry.initialValue,
114
+ editable,
115
+ fields: entry.fields,
116
+ of: entry.of
117
+ })
118
+ };
119
+ }
120
+ function desugarClaimField(entry, path, ctx) {
121
+ const desugared = {
122
+ ...stripUndefined({ name: entry.name, title: entry.title, description: entry.description }),
123
+ type: "actor"
124
+ };
125
+ return ctx.claimFields.set(desugared, { name: entry.name, path }), desugared;
126
+ }
127
+ function desugarListField(entry, path, ctx) {
128
+ const editable = normalizeEditable(entry.editable, [...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
+ };
104
141
  }
105
142
  function normalizeEditable(editable, path, ctx) {
106
143
  if (editable === void 0 || editable === !0) return editable;
@@ -117,12 +154,12 @@ function normalizeEditable(editable, path, ctx) {
117
154
  }
118
155
  return editable;
119
156
  }
120
- function editableSlotNames(workflowFields, stageFields, tasks) {
157
+ function editableSlotNames(workflowFields, stageFields, activities) {
121
158
  const names = /* @__PURE__ */ new Set(), collect = (entries) => {
122
159
  for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
123
160
  };
124
161
  collect(workflowFields), collect(stageFields);
125
- for (const task of tasks) collect(task.fields);
162
+ for (const activity of activities) collect(activity.fields);
126
163
  return names;
127
164
  }
128
165
  function desugarStageEditable(overrides, inScope, path, ctx) {
@@ -132,7 +169,7 @@ function desugarStageEditable(overrides, inScope, path, ctx) {
132
169
  if (!inScope.has(name)) {
133
170
  ctx.issues.push({
134
171
  path: [...path, name],
135
- 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\``
172
+ 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\``
136
173
  });
137
174
  continue;
138
175
  }
@@ -144,35 +181,46 @@ function desugarStageEditable(overrides, inScope, path, ctx) {
144
181
  function layerOf(entries) {
145
182
  return new Map((entries ?? []).map((entry) => [entry.name, entry]));
146
183
  }
147
- function desugarTask(task, path, stageEnv, ctx) {
148
- const taskFields = desugarFieldEntries(task.fields, [...path, "fields"], ctx), env = {
149
- layers: [{ scope: "task", entries: layerOf(taskFields) }, ...stageEnv.layers]
150
- }, ops = desugarOps(task.ops, [...path, "ops"], env, task.name, ctx), actions = (task.actions ?? []).map(
151
- (action, a) => desugarAction(action, [...path, "actions", a], env, task.name, ctx)
152
- );
184
+ function desugarActivity(activity, path, stageEnv, ctx) {
185
+ const activityFields = desugarFieldEntries(activity.fields, [...path, "fields"], ctx), env = {
186
+ layers: [{ scope: "activity", entries: layerOf(activityFields) }, ...stageEnv.layers]
187
+ }, ops = desugarOps(activity.ops, [...path, "ops"], env, activity.name, ctx), actions = (activity.actions ?? []).map(
188
+ (action, a) => desugarAction(action, [...path, "actions", a], env, activity.name, ctx)
189
+ ), target = desugarTarget(activity.target, env, [...path, "target"], ctx);
153
190
  return {
154
191
  ...stripUndefined({
155
- name: task.name,
156
- title: task.title,
157
- description: task.description,
158
- filter: task.filter,
159
- requirements: task.requirements,
160
- completeWhen: task.completeWhen,
161
- failWhen: task.failWhen,
162
- effects: task.effects,
163
- subworkflows: task.subworkflows
192
+ name: activity.name,
193
+ title: activity.title,
194
+ description: activity.description,
195
+ kind: activity.kind,
196
+ filter: activity.filter,
197
+ requirements: activity.requirements,
198
+ completeWhen: activity.completeWhen,
199
+ failWhen: activity.failWhen,
200
+ effects: activity.effects,
201
+ subworkflows: activity.subworkflows
164
202
  }),
165
- activation: task.activation ?? "manual",
166
- ...taskFields ? { fields: taskFields } : {},
203
+ activation: activity.activation ?? "manual",
204
+ ...target ? { target } : {},
205
+ ...activityFields ? { fields: activityFields } : {},
167
206
  ...ops ? { ops } : {},
168
207
  ...actions.length > 0 ? { actions } : {}
169
208
  };
170
209
  }
171
- function desugarAction(action, path, env, taskName, ctx) {
210
+ const TARGET_DOC_KINDS = ["doc.ref", "doc.refs", "release.ref"];
211
+ function desugarTarget(target, env, path, ctx) {
212
+ if (target === void 0 || target.type === "url") return target;
213
+ const ref = typeof target.field == "string" ? { field: target.field } : target.field, field = resolveRef(ref, env, [...path, "field"], ctx) ?? fallbackRef(ref), entry = entryAt(env, field);
214
+ return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
215
+ path: [...path, "field"],
216
+ 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(", ")})`
217
+ }), { type: "field", field };
218
+ }
219
+ function desugarAction(action, path, env, activityName, ctx) {
172
220
  if ("type" in action)
173
221
  return desugarClaimAction(action, path, env, ctx);
174
- const filter = schema.andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps(action.ops, [...path, "ops"], env, taskName, ctx) ?? [];
175
- return action.status !== void 0 && ops.push({ type: "status.set", task: taskName, status: action.status }), {
222
+ const filter = schema.andConditions([rolesCondition(action.roles, ctx.roleAliases), action.filter]), ops = desugarOps(action.ops, [...path, "ops"], env, activityName, ctx) ?? [];
223
+ return action.status !== void 0 && ops.push({ type: "status.set", activity: activityName, status: action.status }), {
176
224
  ...stripUndefined({
177
225
  name: action.name,
178
226
  title: action.title,
@@ -188,9 +236,9 @@ function desugarClaimAction(action, path, env, ctx) {
188
236
  const ref = typeof action.field == "string" ? { field: action.field } : action.field, resolved = resolveRef(ref, env, [...path, "field"], ctx);
189
237
  if (resolved) {
190
238
  const entry = entryAt(env, resolved);
191
- entry && entry.type !== "value.actor" && ctx.issues.push({
239
+ entry && entry.type !== "actor" && ctx.issues.push({
192
240
  path: [...path, "field"],
193
- 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)`
241
+ 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)`
194
242
  }), checkShadowedClaimTarget(action.name, ref.field, resolved, env, [...path, "field"], ctx), entry && ctx.claimedFields.add(entry);
195
243
  }
196
244
  const noSteal = `!defined($fields.${ref.field})`, filter = schema.andConditions([
@@ -221,7 +269,7 @@ function checkUnclaimedClaimFields(ctx) {
221
269
  for (const [entry, { name, path }] of ctx.claimFields)
222
270
  ctx.claimedFields.has(entry) || ctx.issues.push({
223
271
  path,
224
- 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`
272
+ 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`
225
273
  });
226
274
  }
227
275
  function desugarTransition(transition, path, stageEnv, ctx) {
@@ -234,21 +282,21 @@ function desugarTransition(transition, path, stageEnv, ctx) {
234
282
  to: transition.to,
235
283
  effects: transition.effects
236
284
  }),
237
- filter: transition.filter ?? "$allTasksDone",
285
+ filter: transition.filter ?? "$allActivitiesDone",
238
286
  ...ops ? { ops } : {}
239
287
  };
240
288
  }
241
- function desugarOps(ops, path, env, firingTask, ctx) {
289
+ function desugarOps(ops, path, env, firingActivity, ctx) {
242
290
  if (ops)
243
- return ops.map((op, i) => desugarOp(op, [...path, i], env, firingTask, ctx));
291
+ return ops.map((op, i) => desugarOp(op, [...path, i], env, firingActivity, ctx));
244
292
  }
245
- function desugarOp(op, path, env, firingTask, ctx) {
293
+ function desugarOp(op, path, env, firingActivity, ctx) {
246
294
  if (op.type === "status.set") {
247
- const task = op.task ?? firingTask;
248
- return task === void 0 && ctx.issues.push({
249
- path: [...path, "task"],
250
- message: "status.set outside an action must name its target task"
251
- }), { type: "status.set", task: task ?? "", status: op.status };
295
+ const activity = op.activity ?? firingActivity;
296
+ return activity === void 0 && ctx.issues.push({
297
+ path: [...path, "activity"],
298
+ message: "status.set outside an action must name its target activity"
299
+ }), { type: "status.set", activity: activity ?? "", status: op.status };
252
300
  }
253
301
  if (op.type === "audit") return desugarAuditOp(op, path, env, ctx);
254
302
  const target = resolveRef(op.target, env, [...path, "target"], ctx) ?? fallbackRef(op.target);
@@ -312,14 +360,14 @@ const RESERVED_CONDITION_VARS = [
312
360
  "row",
313
361
  "now",
314
362
  "effects",
315
- "tasks",
363
+ "activities",
316
364
  "subworkflows",
317
365
  "self",
318
366
  "stage",
319
367
  "parent",
320
368
  "ancestors",
321
- "allTasksDone",
322
- "anyTaskFailed"
369
+ "allActivitiesDone",
370
+ "anyActivityFailed"
323
371
  ], PREDICATE_CALLER_VARS = ["actor", "assigned", "can"];
324
372
  function knownList(ids) {
325
373
  return [...ids].map((s) => `"${s}"`).join(", ");
@@ -337,12 +385,12 @@ function checkStages(def, issues) {
337
385
  issues
338
386
  );
339
387
  for (const [i, stage] of def.stages.entries()) {
340
- const taskNames = checkDuplicates(
341
- (stage.tasks ?? []).map((task, j) => ({
342
- name: task.name,
343
- path: ["stages", i, "tasks", j, "name"]
388
+ const activityNames = checkDuplicates(
389
+ (stage.activities ?? []).map((activity, j) => ({
390
+ name: activity.name,
391
+ path: ["stages", i, "activities", j, "name"]
344
392
  })),
345
- `task name in stage "${stage.name}"`,
393
+ `activity name in stage "${stage.name}"`,
346
394
  issues
347
395
  );
348
396
  checkDuplicates(
@@ -352,34 +400,34 @@ function checkStages(def, issues) {
352
400
  })),
353
401
  `transition name in stage "${stage.name}"`,
354
402
  issues
355
- ), checkTasks(def, i, taskNames, issues);
403
+ ), checkActivities(def, i, activityNames, issues);
356
404
  }
357
405
  return stageNames;
358
406
  }
359
- function checkTasks(def, i, taskNames, issues) {
407
+ function checkActivities(def, i, activityNames, issues) {
360
408
  const stage = def.stages[i];
361
- for (const [j, task] of (stage.tasks ?? []).entries()) {
362
- const path = ["stages", i, "tasks", j];
409
+ for (const [j, activity] of (stage.activities ?? []).entries()) {
410
+ const path = ["stages", i, "activities", j];
363
411
  checkDuplicates(
364
- (task.actions ?? []).map((action, a) => ({
412
+ (activity.actions ?? []).map((action, a) => ({
365
413
  name: action.name,
366
414
  path: [...path, "actions", a, "name"]
367
415
  })),
368
- `action name in task "${task.name}"`,
416
+ `action name in activity "${activity.name}"`,
369
417
  issues
370
- ), checkStatusSetTargets(task, taskNames, path, issues);
418
+ ), checkStatusSetTargets(activity, activityNames, path, issues);
371
419
  }
372
420
  }
373
- function checkStatusSetTargets(task, taskNames, path, issues) {
374
- checkStatusSetOps(task.ops, taskNames, [...path, "ops"], issues);
375
- for (const [a, action] of (task.actions ?? []).entries())
376
- checkStatusSetOps(action.ops, taskNames, [...path, "actions", a, "ops"], issues);
421
+ function checkStatusSetTargets(activity, activityNames, path, issues) {
422
+ checkStatusSetOps(activity.ops, activityNames, [...path, "ops"], issues);
423
+ for (const [a, action] of (activity.actions ?? []).entries())
424
+ checkStatusSetOps(action.ops, activityNames, [...path, "actions", a, "ops"], issues);
377
425
  }
378
- function checkStatusSetOps(ops, taskNames, path, issues) {
426
+ function checkStatusSetOps(ops, activityNames, path, issues) {
379
427
  for (const [o, op] of (ops ?? []).entries())
380
- op.type !== "status.set" || taskNames.has(op.task) || issues.push({
381
- path: [...path, o, "task"],
382
- message: `status.set targets task "${op.task}" which is not declared in this stage. Known tasks: ${knownList(taskNames)}`
428
+ op.type !== "status.set" || activityNames.has(op.activity) || issues.push({
429
+ path: [...path, o, "activity"],
430
+ message: `status.set targets activity "${op.activity}" which is not declared in this stage. Known activities: ${knownList(activityNames)}`
383
431
  });
384
432
  }
385
433
  function checkInitialStage(def, stageNames, issues) {
@@ -399,10 +447,14 @@ function checkTransitionTargets(def, stageNames, issues) {
399
447
  function checkEffectNames(def, issues) {
400
448
  const sites = [];
401
449
  for (const [i, stage] of def.stages.entries()) {
402
- for (const [j, task] of (stage.tasks ?? []).entries()) {
403
- collectEffects(task.effects, ["stages", i, "tasks", j, "effects"], sites);
404
- for (const [a, action] of (task.actions ?? []).entries())
405
- collectEffects(action.effects, ["stages", i, "tasks", j, "actions", a, "effects"], sites);
450
+ for (const [j, activity] of (stage.activities ?? []).entries()) {
451
+ collectEffects(activity.effects, ["stages", i, "activities", j, "effects"], sites);
452
+ for (const [a, action] of (activity.actions ?? []).entries())
453
+ collectEffects(
454
+ action.effects,
455
+ ["stages", i, "activities", j, "actions", a, "effects"],
456
+ sites
457
+ );
406
458
  }
407
459
  for (const [k, t] of (stage.transitions ?? []).entries())
408
460
  collectEffects(t.effects, ["stages", i, "transitions", k, "effects"], sites);
@@ -437,12 +489,12 @@ function fieldScopes(def) {
437
489
  path: ["stages", i, "fields"],
438
490
  label: `stage "${stage.name}" fields`
439
491
  });
440
- for (const [j, task] of (stage.tasks ?? []).entries())
492
+ for (const [j, activity] of (stage.activities ?? []).entries())
441
493
  scopes.push({
442
- entries: task.fields,
443
- scope: "task",
444
- path: ["stages", i, "tasks", j, "fields"],
445
- label: `task "${task.name}" fields`
494
+ entries: activity.fields,
495
+ scope: "activity",
496
+ path: ["stages", i, "activities", j, "fields"],
497
+ label: `activity "${activity.name}" fields`
446
498
  });
447
499
  }
448
500
  return scopes;
@@ -450,13 +502,20 @@ function fieldScopes(def) {
450
502
  function checkRequiredField(def, issues) {
451
503
  for (const { entries, scope, path, label } of fieldScopes(def))
452
504
  for (const [n, entry] of (entries ?? []).entries())
453
- entry.required === !0 && (scope !== "workflow" ? issues.push({
454
- path: [...path, n, "required"],
455
- 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`
456
- }) : entry.source.type !== "init" && issues.push({
457
- path: [...path, n, "required"],
458
- 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)`
459
- }));
505
+ if (entry.required === !0) {
506
+ if (scope !== "workflow")
507
+ issues.push({
508
+ path: [...path, n, "required"],
509
+ 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`
510
+ });
511
+ else if (entry.initialValue?.type !== "input") {
512
+ const seed = entry.initialValue?.type ?? "working memory (no initialValue)";
513
+ issues.push({
514
+ path: [...path, n, "required"],
515
+ message: `field entry "${entry.name}" has initialValue "${seed}" \u2014 \`required\` applies only to \`input\`-sourced entries (the caller fills them at start/spawn)`
516
+ });
517
+ }
518
+ }
460
519
  }
461
520
  function checkFieldEntryNames(def, issues) {
462
521
  for (const { entries, path, label } of fieldScopes(def))
@@ -512,34 +571,34 @@ function nonActionFilterConditionSites(def) {
512
571
  `transition "${t.name}"`,
513
572
  sites
514
573
  );
515
- for (const [j, task] of (stage.tasks ?? []).entries())
516
- collectTaskConditionSites(task, ["stages", i, "tasks", j], sites);
574
+ for (const [j, activity] of (stage.activities ?? []).entries())
575
+ collectActivityConditionSites(activity, ["stages", i, "activities", j], sites);
517
576
  }
518
577
  return sites;
519
578
  }
520
- function collectTaskConditionSites(task, path, sites) {
579
+ function collectActivityConditionSites(activity, path, sites) {
521
580
  for (const field of ["filter", "completeWhen", "failWhen"]) {
522
- const groq2 = task[field];
523
- groq2 !== void 0 && sites.push({ groq: groq2, path: [...path, field], label: `task "${task.name}".${field}` });
581
+ const groq2 = activity[field];
582
+ groq2 !== void 0 && sites.push({ groq: groq2, path: [...path, field], label: `activity "${activity.name}".${field}` });
524
583
  }
525
- collectBindingSites(task.effects, [...path, "effects"], `task "${task.name}"`, sites);
526
- for (const [a, action] of (task.actions ?? []).entries())
584
+ collectBindingSites(activity.effects, [...path, "effects"], `activity "${activity.name}"`, sites);
585
+ for (const [a, action] of (activity.actions ?? []).entries())
527
586
  collectBindingSites(
528
587
  action.effects,
529
588
  [...path, "actions", a, "effects"],
530
589
  `action "${action.name}"`,
531
590
  sites
532
591
  );
533
- collectSubworkflowSites(task, path, sites);
592
+ collectSubworkflowSites(activity, path, sites);
534
593
  }
535
- function collectSubworkflowSites(task, path, sites) {
536
- const sub = task.subworkflows;
594
+ function collectSubworkflowSites(activity, path, sites) {
595
+ const sub = activity.subworkflows;
537
596
  if (sub === void 0) return;
538
597
  const subPath = [...path, "subworkflows"];
539
598
  sites.push({
540
599
  groq: sub.forEach,
541
600
  path: [...subPath, "forEach"],
542
- label: `task "${task.name}".subworkflows.forEach`
601
+ label: `activity "${activity.name}".subworkflows.forEach`
543
602
  });
544
603
  for (const [group, record] of [
545
604
  ["with", sub.with],
@@ -549,7 +608,7 @@ function collectSubworkflowSites(task, path, sites) {
549
608
  sites.push({
550
609
  groq: groq2,
551
610
  path: [...subPath, group, key],
552
- label: `task "${task.name}".subworkflows.${group} "${key}"`
611
+ label: `activity "${activity.name}".subworkflows.${group} "${key}"`
553
612
  });
554
613
  }
555
614
  function collectBindingSites(effects, path, label, sites) {
@@ -568,9 +627,49 @@ function checkAssigneesEntries(def, issues) {
568
627
  message: "at most one assignees-kind field entry per scope \u2014 the inbox reads it by kind"
569
628
  });
570
629
  }
630
+ function activityShape(activity) {
631
+ return {
632
+ hasActions: (activity.actions ?? []).length > 0,
633
+ hasEffects: (activity.effects ?? []).length > 0,
634
+ hasCompleteWhen: activity.completeWhen !== void 0,
635
+ hasSubworkflows: activity.subworkflows !== void 0
636
+ };
637
+ }
638
+ const KIND_SHAPE_RULES = {
639
+ user: (s) => s.hasActions ? [] : ["needs at least one action for a person to fire"],
640
+ service: (s) => s.hasEffects ? [] : ["needs at least one effect \u2014 the automated work it performs"],
641
+ manual: (s) => [
642
+ 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)",
643
+ s.hasSubworkflows ? "is off-system work, not a fan-out \u2014 drop `subworkflows` or move it to its own activity" : void 0
644
+ ].filter((m) => m !== void 0),
645
+ receive: (s) => [
646
+ s.hasCompleteWhen || s.hasSubworkflows ? void 0 : "needs a `completeWhen` (or `subworkflows`) condition to wait on",
647
+ s.hasActions ? "has no actor, so it cannot carry actions" : void 0
648
+ ].filter((m) => m !== void 0),
649
+ script: (s) => [
650
+ s.hasActions ? "runs inline with no actor, so it cannot carry actions" : void 0,
651
+ s.hasCompleteWhen ? "resolves on activation, so it cannot carry a `completeWhen` (that is a `receive`/`service` wait)" : void 0,
652
+ s.hasSubworkflows ? "resolves on activation, so it cannot fan out with `subworkflows`" : void 0
653
+ ].filter((m) => m !== void 0)
654
+ };
655
+ function checkActivityKinds(def, issues) {
656
+ for (const [i, stage] of def.stages.entries())
657
+ for (const [j, activity] of (stage.activities ?? []).entries()) {
658
+ const path = ["stages", i, "activities", j];
659
+ if (activity.target !== void 0 && activity.kind !== "manual" && issues.push({
660
+ path: [...path, "target"],
661
+ message: `activity "${activity.name}" has a \`target\` but is not \`kind: "manual"\` \u2014 a target is the deep-link for off-system manual work`
662
+ }), activity.kind !== void 0)
663
+ for (const fault of KIND_SHAPE_RULES[activity.kind](activityShape(activity)))
664
+ issues.push({
665
+ path: [...path, "kind"],
666
+ message: `activity "${activity.name}" declares kind "${activity.kind}" but ${fault}`
667
+ });
668
+ }
669
+ }
571
670
  function checkWorkflowInvariants(def) {
572
671
  const issues = [], stageNames = checkStages(def, issues);
573
- 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), issues;
672
+ 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;
574
673
  }
575
674
  function defineWorkflow(definition) {
576
675
  const label = labelFor("defineWorkflow", definition), parsed = parseOrThrow(schema.AuthoringWorkflowSchema, definition, label), { definition: stored, issues: desugarIssues } = desugarWorkflow(parsed), issues = [...desugarIssues, ...checkWorkflowInvariants(stored)];
@@ -580,8 +679,8 @@ function defineWorkflow(definition) {
580
679
  function defineStage(stage) {
581
680
  return parseOrThrow(schema.AuthoringStageSchema, stage, labelFor("defineStage", stage));
582
681
  }
583
- function defineTask(task) {
584
- return parseOrThrow(schema.AuthoringTaskSchema, task, labelFor("defineTask", task));
682
+ function defineActivity(activity) {
683
+ return parseOrThrow(schema.AuthoringActivitySchema, activity, labelFor("defineActivity", activity));
585
684
  }
586
685
  function defineAction(action) {
587
686
  return parseOrThrow(schema.AuthoringActionSchema, action, labelFor("defineAction", action));
@@ -623,13 +722,13 @@ function labelFor(fn, value) {
623
722
  return fn;
624
723
  }
625
724
  exports.defineAction = defineAction;
725
+ exports.defineActivity = defineActivity;
626
726
  exports.defineEffect = defineEffect;
627
727
  exports.defineEffectDescriptor = defineEffectDescriptor;
628
728
  exports.defineField = defineField;
629
729
  exports.defineGuard = defineGuard;
630
730
  exports.defineOp = defineOp;
631
731
  exports.defineStage = defineStage;
632
- exports.defineTask = defineTask;
633
732
  exports.defineTransition = defineTransition;
634
733
  exports.defineWorkflow = defineWorkflow;
635
734
  exports.groq = groq;