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