@sanity/workflow-engine 0.27.0 → 0.29.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/CHANGELOG.md +197 -0
- package/DATAMODEL.md +107 -0
- package/dist/_chunks-cjs/invariants.cjs +212 -442
- package/dist/_chunks-es/invariants.js +209 -405
- package/dist/define.cjs +1 -6
- package/dist/define.d.cts +232 -24
- package/dist/define.d.ts +232 -24
- package/dist/define.js +2 -7
- package/dist/index.cjs +1216 -510
- package/dist/index.d.cts +452 -34
- package/dist/index.d.ts +452 -34
- package/dist/index.js +1106 -415
- package/package.json +1 -1
|
@@ -20,6 +20,41 @@ function _interopNamespaceCompat(e) {
|
|
|
20
20
|
|
|
21
21
|
var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
|
|
22
22
|
|
|
23
|
+
function isCascadeFired(action) {
|
|
24
|
+
return action.when !== void 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function deriveActivityKind(activity) {
|
|
28
|
+
if (activity.target !== void 0) return "manual";
|
|
29
|
+
const actions = activity.actions ?? [];
|
|
30
|
+
return actions.some(a => !isCascadeFired(a)) ? "user" : actions.some(a => (a.effects ?? []).length > 0 || a.spawn !== void 0) ? "service" : actions.length > 0 ? "receive" : "script";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function deriveExecutorClassification(activity) {
|
|
34
|
+
if (activity.target !== void 0) return "off-system";
|
|
35
|
+
const actions = activity.actions ?? [], cascadeFired = actions.filter(isCascadeFired).length;
|
|
36
|
+
return cascadeFired === actions.length ? "autonomous" : cascadeFired === 0 ? "interactive" : "hybrid";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function driverKind(actor) {
|
|
40
|
+
return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function errorMessage(err) {
|
|
44
|
+
return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function rethrowWithContext(err, context) {
|
|
48
|
+
throw new Error(`${context}: ${errorMessage(err)}`, {
|
|
49
|
+
cause: err
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function andConditions(parts) {
|
|
54
|
+
const present = parts.filter(p => p !== void 0);
|
|
55
|
+
if (present.length !== 0) return present.length === 1 ? present[0] : present.map(p => `(${p})`).join(" && ");
|
|
56
|
+
}
|
|
57
|
+
|
|
23
58
|
class WorkflowError extends Error {
|
|
24
59
|
kind;
|
|
25
60
|
constructor(kind, message, options) {
|
|
@@ -93,322 +128,6 @@ function effectNotFoundMessage(args) {
|
|
|
93
128
|
return args.settled.status === "cancelled" ? `${base} — it was cancelled at ${args.settled.ranAt}${cause}` : `${base} — it already settled "${args.settled.status}" at ${args.settled.ranAt}${cause}`;
|
|
94
129
|
}
|
|
95
130
|
|
|
96
|
-
function errorMessage(err) {
|
|
97
|
-
return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function rethrowWithContext(err, context) {
|
|
101
|
-
throw new Error(`${context}: ${errorMessage(err)}`, {
|
|
102
|
-
cause: err
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const WORKFLOW_INSTANCE_TYPE = "sanity.workflow.instance";
|
|
107
|
-
|
|
108
|
-
function terminalState(instance) {
|
|
109
|
-
return instance.abortedAt !== void 0 ? "aborted" : instance.completedAt !== void 0 ? "completed" : "in-flight";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function isUnprimed(instance) {
|
|
113
|
-
return instance.stages.length === 0 && terminalState(instance) === "in-flight";
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function parseDefinitionSnapshotValue(instance) {
|
|
117
|
-
try {
|
|
118
|
-
return normalizeLegacyActivityRequirements(JSON.parse(instance.definitionSnapshot));
|
|
119
|
-
} catch (err) {
|
|
120
|
-
rethrowWithContext(err, `Failed to parse definitionSnapshot on instance "${instance._id}"`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function normalizeLegacyActivityRequirements(value) {
|
|
125
|
-
for (const stage of arrayMember(value, "stages")) for (const activity of arrayMember(stage, "activities")) normalizeLegacyRequirementMap(activity);
|
|
126
|
-
return value;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function arrayMember(value, key) {
|
|
130
|
-
if (typeof value != "object" || value === null) return [];
|
|
131
|
-
const member = value[key];
|
|
132
|
-
return Array.isArray(member) ? member : [];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function normalizeLegacyRequirementMap(value) {
|
|
136
|
-
if (typeof value != "object" || value === null) return;
|
|
137
|
-
const activity = value, requirements = activity.requirements;
|
|
138
|
-
typeof requirements != "object" || requirements === null || Array.isArray(requirements) || (activity.requirements = Object.entries(requirements).map(([name, query]) => ({
|
|
139
|
-
type: "groq",
|
|
140
|
-
name: name,
|
|
141
|
-
query: query
|
|
142
|
-
})));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function parseDefinitionSnapshot(instance) {
|
|
146
|
-
return parseDefinitionSnapshotValue(instance);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function parentRef(instance) {
|
|
150
|
-
return instance.ancestors.at(-1);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const DATA_MODEL_VERSION = 5, DATA_MODEL_MIN_READER = 4, READER_MODEL_ROLLOUT_URL = "https://www.sanity.io/docs/editorial-workflows/prerelease";
|
|
154
|
-
|
|
155
|
-
class ReaderModelAcknowledgementError extends WorkflowError {
|
|
156
|
-
code="WORKFLOW_READER_MODEL_ACKNOWLEDGEMENT_MISMATCH";
|
|
157
|
-
expectedMinReaderModel;
|
|
158
|
-
engineMinReaderModel=DATA_MODEL_MIN_READER;
|
|
159
|
-
engineModelVersion=DATA_MODEL_VERSION;
|
|
160
|
-
documentationUrl=READER_MODEL_ROLLOUT_URL;
|
|
161
|
-
constructor(expectedMinReaderModel, context = "Deployment") {
|
|
162
|
-
const expected = expectedMinReaderModel === void 0 ? "missing" : String(expectedMinReaderModel);
|
|
163
|
-
super("reader-model-acknowledgement", `${context} expected reader floor ${expected}; the installed engine requires acknowledgement ${DATA_MODEL_MIN_READER}. Do not change the acknowledgement yet: accepting ${DATA_MODEL_MIN_READER} authorizes this engine to write documents that older readers will refuse. Upgrade every Studio, CLI, MCP server, Function, and other runtime that reads engine-owned documents; verify that rollout in every environment sharing the workflow resource; then change the literal in deployment configuration and deploy the writer. Rollout guide: ${READER_MODEL_ROLLOUT_URL}`),
|
|
164
|
-
this.name = "ReaderModelAcknowledgementError", this.expectedMinReaderModel = expectedMinReaderModel;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function assertReaderModelAcknowledgement(expectedMinReaderModel, context) {
|
|
169
|
-
if (typeof expectedMinReaderModel != "number" || !Number.isFinite(expectedMinReaderModel) || !Number.isInteger(expectedMinReaderModel) || expectedMinReaderModel < 0 || expectedMinReaderModel !== DATA_MODEL_MIN_READER) throw new ReaderModelAcknowledgementError(expectedMinReaderModel, context);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const DATA_MODEL_CHANGES = Object.freeze([ Object.freeze({
|
|
173
|
-
id: "governed-model-stamps",
|
|
174
|
-
introducedInModel: 1,
|
|
175
|
-
minReaderModel: 0,
|
|
176
|
-
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
177
|
-
compatibility: "additive",
|
|
178
|
-
applicability: "unconditional",
|
|
179
|
-
summary: "Definition and instance documents carry model provenance and reader-floor stamps."
|
|
180
|
-
}), Object.freeze({
|
|
181
|
-
id: "subject-field-kind",
|
|
182
|
-
introducedInModel: 2,
|
|
183
|
-
minReaderModel: 0,
|
|
184
|
-
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
185
|
-
compatibility: "additive",
|
|
186
|
-
applicability: "detectable",
|
|
187
|
-
summary: "A workflow-level subject field identifies the document a workflow is about."
|
|
188
|
-
}), Object.freeze({
|
|
189
|
-
id: "typed-scalar-choice-lists",
|
|
190
|
-
introducedInModel: 2,
|
|
191
|
-
minReaderModel: 2,
|
|
192
|
-
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
193
|
-
compatibility: "reader-floor",
|
|
194
|
-
applicability: "detectable",
|
|
195
|
-
summary: "Scalar fields may constrain writes to a persisted typed choice list."
|
|
196
|
-
}), Object.freeze({
|
|
197
|
-
id: "action-semantics",
|
|
198
|
-
introducedInModel: 2,
|
|
199
|
-
minReaderModel: 0,
|
|
200
|
-
documentTypes: Object.freeze([ "definition" ]),
|
|
201
|
-
compatibility: "additive",
|
|
202
|
-
applicability: "detectable",
|
|
203
|
-
summary: "Ordinary actions may carry a closed bag of advisory workflow semantics."
|
|
204
|
-
}), Object.freeze({
|
|
205
|
-
id: "inclusive-scalar-bounds",
|
|
206
|
-
introducedInModel: 2,
|
|
207
|
-
minReaderModel: 2,
|
|
208
|
-
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
209
|
-
compatibility: "reader-floor",
|
|
210
|
-
applicability: "detectable",
|
|
211
|
-
summary: "String, text, and number values may carry persisted inclusive bounds."
|
|
212
|
-
}), Object.freeze({
|
|
213
|
-
id: "progress-field-kind",
|
|
214
|
-
introducedInModel: 3,
|
|
215
|
-
minReaderModel: 0,
|
|
216
|
-
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
217
|
-
compatibility: "additive",
|
|
218
|
-
applicability: "detectable",
|
|
219
|
-
summary: "A progress field kind carries application-defined 0–100 completion."
|
|
220
|
-
}), Object.freeze({
|
|
221
|
-
id: "effect-claim-tokens",
|
|
222
|
-
introducedInModel: 3,
|
|
223
|
-
minReaderModel: 0,
|
|
224
|
-
documentTypes: Object.freeze([ "instance" ]),
|
|
225
|
-
compatibility: "additive",
|
|
226
|
-
applicability: "detectable",
|
|
227
|
-
summary: "Pending-effect claims carry an exact-claim token gating mid-dispatch state reports."
|
|
228
|
-
}), Object.freeze({
|
|
229
|
-
id: "classified-principal-ids",
|
|
230
|
-
introducedInModel: 4,
|
|
231
|
-
minReaderModel: 4,
|
|
232
|
-
documentTypes: Object.freeze([ "instance" ]),
|
|
233
|
-
compatibility: "reader-floor",
|
|
234
|
-
applicability: "unconditional",
|
|
235
|
-
summary: "Principal ids are namespace-classified: actor and assignee writes carry the account-global user id only, and readers resolve legacy project-scoped ids through the prefix classifier at the instance read funnel."
|
|
236
|
-
}), Object.freeze({
|
|
237
|
-
id: "readiness-requirements",
|
|
238
|
-
introducedInModel: 4,
|
|
239
|
-
minReaderModel: 4,
|
|
240
|
-
documentTypes: Object.freeze([ "definition" ]),
|
|
241
|
-
compatibility: "reader-floor",
|
|
242
|
-
applicability: "detectable",
|
|
243
|
-
summary: "Start and activity readiness use named polymorphic requirement arrays."
|
|
244
|
-
}), Object.freeze({
|
|
245
|
-
id: "due-date-field-kinds",
|
|
246
|
-
introducedInModel: 5,
|
|
247
|
-
minReaderModel: 0,
|
|
248
|
-
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
249
|
-
compatibility: "additive",
|
|
250
|
-
applicability: "detectable",
|
|
251
|
-
summary: "Due-date field kinds (dueDate, dueDatetime) mark a level deadline, elevated aliases of date/datetime carrying the same stored value."
|
|
252
|
-
}) ]);
|
|
253
|
-
|
|
254
|
-
function recordOf(value) {
|
|
255
|
-
return value !== null && typeof value == "object" && !Array.isArray(value) ? value : void 0;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function recordsAt(record, key) {
|
|
259
|
-
const value = record[key];
|
|
260
|
-
return Array.isArray(value) ? value.map(recordOf).filter(item => item !== void 0) : [];
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function nestedFieldEntries(entries) {
|
|
264
|
-
return entries.flatMap(entry => [ entry, ...nestedFieldEntries(recordsAt(entry, "fields")), ...nestedFieldEntries(recordsAt(entry, "of")) ]);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function parsedDefinitionSnapshot(root) {
|
|
268
|
-
if (typeof root.definitionSnapshot == "string") return recordOf(parseDefinitionSnapshotValue({
|
|
269
|
-
_id: typeof root._id == "string" ? root._id : "<unknown instance>",
|
|
270
|
-
definitionSnapshot: root.definitionSnapshot
|
|
271
|
-
}));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function persistedFieldEntries(document) {
|
|
275
|
-
const root = recordOf(document);
|
|
276
|
-
if (root === void 0) return [];
|
|
277
|
-
const snapshot = parsedDefinitionSnapshot(root), roots = snapshot === void 0 ? [ root ] : [ root, snapshot ], stages = roots.flatMap(candidate => recordsAt(candidate, "stages")), activities = stages.flatMap(stage => recordsAt(stage, "activities")), actions = activities.flatMap(activity => recordsAt(activity, "actions")), effects = actions.flatMap(action => recordsAt(action, "effects"));
|
|
278
|
-
return nestedFieldEntries([ ...roots.flatMap(candidate => recordsAt(candidate, "fields")), ...stages.flatMap(stage => recordsAt(stage, "fields")), ...activities.flatMap(activity => recordsAt(activity, "fields")), ...actions.flatMap(action => recordsAt(action, "params")), ...effects.flatMap(effect => recordsAt(effect, "outputs")) ]);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function hasChoiceList(document) {
|
|
282
|
-
return persistedFieldEntries(document).some(entry => {
|
|
283
|
-
const options = recordOf(entry.options);
|
|
284
|
-
return options !== void 0 && Array.isArray(options.list);
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function hasFieldKind(document, kind) {
|
|
289
|
-
return persistedFieldEntries(document).some(entry => entry.type === kind || entry._type === kind);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function hasActionSemantics(document) {
|
|
293
|
-
const root = recordOf(document);
|
|
294
|
-
return root === void 0 ? !1 : recordsAt(root, "stages").flatMap(stage => recordsAt(stage, "activities")).flatMap(activity => recordsAt(activity, "actions")).some(action => Array.isArray(action.semantics));
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function hasScalarValidation(document) {
|
|
298
|
-
return persistedFieldEntries(document).some(entry => {
|
|
299
|
-
const validation = recordOf(entry.validation);
|
|
300
|
-
return typeof validation?.min == "number" || typeof validation?.max == "number";
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function hasClaimTokens(document) {
|
|
305
|
-
const root = recordOf(document);
|
|
306
|
-
return root === void 0 ? !1 : recordsAt(root, "pendingEffects").some(entry => {
|
|
307
|
-
const claim = recordOf(entry.claim);
|
|
308
|
-
return claim !== void 0 && typeof claim.claimToken == "string";
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function hasReadinessRequirements(document) {
|
|
313
|
-
const root = recordOf(document);
|
|
314
|
-
return root === void 0 ? !1 : Array.isArray(recordOf(root.start)?.requirements) ? !0 : recordsAt(root, "stages").some(stage => recordsAt(stage, "activities").some(activity => Array.isArray(activity.requirements)));
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const featureDetectors = {
|
|
318
|
-
"governed-model-stamps": () => !0,
|
|
319
|
-
"subject-field-kind": document => hasFieldKind(document, "subject"),
|
|
320
|
-
"typed-scalar-choice-lists": hasChoiceList,
|
|
321
|
-
"action-semantics": hasActionSemantics,
|
|
322
|
-
"inclusive-scalar-bounds": hasScalarValidation,
|
|
323
|
-
"progress-field-kind": document => hasFieldKind(document, "progress"),
|
|
324
|
-
"effect-claim-tokens": hasClaimTokens,
|
|
325
|
-
"classified-principal-ids": () => !0,
|
|
326
|
-
"readiness-requirements": hasReadinessRequirements,
|
|
327
|
-
"due-date-field-kinds": document => hasFieldKind(document, "dueDate") || hasFieldKind(document, "dueDatetime")
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
function requiredModelFeatures(documentType, document) {
|
|
331
|
-
return DATA_MODEL_CHANGES.filter(change => change.documentTypes.some(candidate => candidate === documentType) && featureDetectors[change.id](document));
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function requiredReaderModel(documentType, document) {
|
|
335
|
-
return Math.max(0, ...requiredModelFeatures(documentType, document).map(change => change.minReaderModel));
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function modelStampFor(args) {
|
|
339
|
-
return {
|
|
340
|
-
modelVersion: DATA_MODEL_VERSION,
|
|
341
|
-
minReaderModel: Math.max(DATA_MODEL_MIN_READER, args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function fieldTreeShape(value) {
|
|
346
|
-
if (Array.isArray(value)) return value.map(fieldTreeShape);
|
|
347
|
-
if (value === null) return "null";
|
|
348
|
-
if (typeof value == "object") {
|
|
349
|
-
const record = value;
|
|
350
|
-
return Object.fromEntries(Object.keys(record).toSorted().map(key => [ key, fieldTreeShape(record[key]) ]));
|
|
351
|
-
}
|
|
352
|
-
return typeof value;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
function modelVersionOf(doc) {
|
|
356
|
-
const stamp = doc.modelVersion;
|
|
357
|
-
return typeof stamp == "number" ? stamp : 0;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function minReaderModelOf(doc) {
|
|
361
|
-
const floor = doc.minReaderModel;
|
|
362
|
-
return typeof floor == "number" ? floor : modelVersionOf(doc);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
class ModelVersionAheadError extends WorkflowError {
|
|
366
|
-
documentId;
|
|
367
|
-
documentModelVersion;
|
|
368
|
-
requiredReaderModel;
|
|
369
|
-
engineModelVersion;
|
|
370
|
-
constructor(args) {
|
|
371
|
-
super("model-version-ahead", `Document "${args.documentId}" was written by engine data model ${args.documentModelVersion} and requires a reader at model ${args.requiredReaderModel} or newer; this engine reads up to model ${DATA_MODEL_VERSION}. Upgrade @sanity/workflow-engine to a version that understands it.`),
|
|
372
|
-
this.name = "ModelVersionAheadError", this.documentId = args.documentId, this.documentModelVersion = args.documentModelVersion,
|
|
373
|
-
this.requiredReaderModel = args.requiredReaderModel, this.engineModelVersion = DATA_MODEL_VERSION;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function assertReadableModel(doc) {
|
|
378
|
-
const documentReaderModel = minReaderModelOf(doc);
|
|
379
|
-
if (documentReaderModel > DATA_MODEL_VERSION) throw new ModelVersionAheadError({
|
|
380
|
-
documentId: doc._id,
|
|
381
|
-
documentModelVersion: modelVersionOf(doc),
|
|
382
|
-
requiredReaderModel: documentReaderModel
|
|
383
|
-
});
|
|
384
|
-
return doc;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function isCascadeFired(action) {
|
|
388
|
-
return action.when !== void 0;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function deriveActivityKind(activity) {
|
|
392
|
-
if (activity.target !== void 0) return "manual";
|
|
393
|
-
const actions = activity.actions ?? [];
|
|
394
|
-
return actions.some(a => !isCascadeFired(a)) ? "user" : actions.some(a => (a.effects ?? []).length > 0 || a.spawn !== void 0) ? "service" : actions.length > 0 ? "receive" : "script";
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function deriveExecutorClassification(activity) {
|
|
398
|
-
if (activity.target !== void 0) return "off-system";
|
|
399
|
-
const actions = activity.actions ?? [], cascadeFired = actions.filter(isCascadeFired).length;
|
|
400
|
-
return cascadeFired === actions.length ? "autonomous" : cascadeFired === 0 ? "interactive" : "hybrid";
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function driverKind(actor) {
|
|
404
|
-
return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function andConditions(parts) {
|
|
408
|
-
const present = parts.filter(p => p !== void 0);
|
|
409
|
-
if (present.length !== 0) return present.length === 1 ? present[0] : present.map(p => `(${p})`).join(" && ");
|
|
410
|
-
}
|
|
411
|
-
|
|
412
131
|
const KNOWN_SCHEMES = /* @__PURE__ */ new Set([ "dataset", "canvas", "media-library", "dashboard" ]), KNOWN_SCHEMES_TEXT = [ ...KNOWN_SCHEMES ].join(", ");
|
|
413
132
|
|
|
414
133
|
class VersionSpecificDatasetGdrError extends Error {
|
|
@@ -820,6 +539,7 @@ function desugarWorkflow(authoring) {
|
|
|
820
539
|
return {
|
|
821
540
|
...stripUndefined({
|
|
822
541
|
name: stage.name,
|
|
542
|
+
semantics: stage.semantics,
|
|
823
543
|
title: stage.title,
|
|
824
544
|
description: stage.description,
|
|
825
545
|
groups: stage.groups,
|
|
@@ -843,6 +563,7 @@ function desugarWorkflow(authoring) {
|
|
|
843
563
|
definition: {
|
|
844
564
|
...stripUndefined({
|
|
845
565
|
name: authoring.name,
|
|
566
|
+
semantics: authoring.semantics,
|
|
846
567
|
title: authoring.title,
|
|
847
568
|
description: authoring.description,
|
|
848
569
|
groups: authoring.groups,
|
|
@@ -1069,6 +790,7 @@ function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ct
|
|
|
1069
790
|
return {
|
|
1070
791
|
...stripUndefined({
|
|
1071
792
|
name: activity.name,
|
|
793
|
+
semantics: activity.semantics,
|
|
1072
794
|
title: activity.title,
|
|
1073
795
|
description: activity.description,
|
|
1074
796
|
groups: activity.groups,
|
|
@@ -1540,7 +1262,58 @@ function isTerminalActivityStatus(status) {
|
|
|
1540
1262
|
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
1541
1263
|
}
|
|
1542
1264
|
|
|
1543
|
-
const
|
|
1265
|
+
const SIGNAL_SEMANTICS = [ "signal.positive", "signal.caution", "signal.critical" ], DECISION_SEMANTICS = [ "decision.accept", "decision.decline" ], ACTION_SEMANTICS = [ ...DECISION_SEMANTICS, ...SIGNAL_SEMANTICS ], FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISSIONS = [ "create", "read", "update" ], MUTATION_GUARD_ACTIONS = [ "create", "update", "delete", "publish", "unpublish" ], ACTIVITY_KINDS = [ "user", "service", "script", "manual", "receive" ], EXECUTOR_CLASSIFICATIONS = [ "autonomous", "interactive", "off-system", "hybrid" ], GROUP_KINDS = [ "core", "details" ], DRIVER_KINDS = [ "person", "agent", "service", "engine" ];
|
|
1266
|
+
|
|
1267
|
+
function releaseDocId(releaseName) {
|
|
1268
|
+
return `_.releases.${releaseName}`;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
function releaseRef({res: res, releaseName: releaseName}) {
|
|
1272
|
+
if (releaseName.length === 0) throw new ContractViolationError("releaseRef: releaseName must be a non-empty release name");
|
|
1273
|
+
return {
|
|
1274
|
+
id: gdrFromResource(res, releaseDocId(releaseName)),
|
|
1275
|
+
type: "system.release",
|
|
1276
|
+
releaseName: releaseName
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
function isAlwaysArrayFieldKind(kind) {
|
|
1281
|
+
return kind === "doc.refs" || kind === "assignees" || kind === "array";
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function isSingleDocRefKind(kind) {
|
|
1285
|
+
return kind === "doc.ref" || kind === "subject";
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function refKindAcceptsTypes(kind) {
|
|
1289
|
+
return isSingleDocRefKind(kind) || kind === "doc.refs";
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function isSingleDocRefEntry(entry) {
|
|
1293
|
+
return isSingleDocRefKind(entry._type);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function isTodoListItem(row) {
|
|
1297
|
+
if (typeof row != "object" || row === null) return !1;
|
|
1298
|
+
const candidate = row, status = candidate.status;
|
|
1299
|
+
return typeof candidate._key == "string" && typeof candidate.label == "string" && (status == null || typeof status == "string");
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
function declaredRowColumns(entry) {
|
|
1303
|
+
if (("_type" in entry ? entry._type : entry.type) === "array") return new Set((("of" in entry ? entry.of : void 0) ?? []).map(shape => shape.name));
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
function isTodoListEntry(entry) {
|
|
1307
|
+
const columns = declaredRowColumns(entry);
|
|
1308
|
+
return columns !== void 0 && columns.has("label") && columns.has("status");
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function isNotesEntry(entry) {
|
|
1312
|
+
const columns = declaredRowColumns(entry);
|
|
1313
|
+
return columns !== void 0 && columns.has("body") && columns.has("actor") && columns.has("at");
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const CONDITION_VARS = [ {
|
|
1544
1317
|
name: "self",
|
|
1545
1318
|
binding: "always",
|
|
1546
1319
|
label: "this workflow instance",
|
|
@@ -1615,6 +1388,11 @@ const ACTION_SEMANTICS = [ "decision.accept", "decision.decline" ], FIELD_SCOPES
|
|
|
1615
1388
|
binding: "caller",
|
|
1616
1389
|
label: "your permissions",
|
|
1617
1390
|
description: "Advisory per-permission booleans computed from the caller's grants; `undefined` without grants. Bound wherever grants ride the evaluation: the projection's rendered scope (fireAction-action filters, activity requirements, editability predicates) and the fireAction/editField commit gates. Deploy rejects it at every site that evaluates without grants: transition `when`s, activity filters, cascade-fired actions' `when`/`filter`, effect bindings, where-op `where`s, and the spawn `forEach`/`with`/`context` sites."
|
|
1391
|
+
}, {
|
|
1392
|
+
name: "attributes",
|
|
1393
|
+
binding: "caller",
|
|
1394
|
+
label: "your attributes",
|
|
1395
|
+
description: "Advisory org-level User Attributes for the caller (Enterprise — same values as lake `user::attributes()`), keyed by attribute name with each active scalar or array value. `undefined` on expected HTTP absence; unexpected fetch failures throw; empty page binds `{}`. Bound wherever grants ride the evaluation (same sites as `$can`). Soft-gate paths fetch at most 100 attributes (no further pages) and warn when the envelope reports `hasMore: true` (partial bag still binds). Not a security boundary — the Content Lake remains the only enforcement point."
|
|
1618
1396
|
}, {
|
|
1619
1397
|
name: "row",
|
|
1620
1398
|
binding: "spawn",
|
|
@@ -1780,54 +1558,7 @@ function documentIdOf(doc) {
|
|
|
1780
1558
|
return "(unknown id)";
|
|
1781
1559
|
}
|
|
1782
1560
|
|
|
1783
|
-
const ACTOR_KINDS = [ "person", "agent", "system" ]
|
|
1784
|
-
|
|
1785
|
-
function releaseDocId(releaseName) {
|
|
1786
|
-
return `_.releases.${releaseName}`;
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
function releaseRef({res: res, releaseName: releaseName}) {
|
|
1790
|
-
if (releaseName.length === 0) throw new ContractViolationError("releaseRef: releaseName must be a non-empty release name");
|
|
1791
|
-
return {
|
|
1792
|
-
id: gdrFromResource(res, releaseDocId(releaseName)),
|
|
1793
|
-
type: "system.release",
|
|
1794
|
-
releaseName: releaseName
|
|
1795
|
-
};
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
function isSingleDocRefKind(kind) {
|
|
1799
|
-
return kind === "doc.ref" || kind === "subject";
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
function refKindAcceptsTypes(kind) {
|
|
1803
|
-
return isSingleDocRefKind(kind) || kind === "doc.refs";
|
|
1804
|
-
}
|
|
1805
|
-
|
|
1806
|
-
function isSingleDocRefEntry(entry) {
|
|
1807
|
-
return isSingleDocRefKind(entry._type);
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
function isTodoListItem(row) {
|
|
1811
|
-
if (typeof row != "object" || row === null) return !1;
|
|
1812
|
-
const candidate = row, status = candidate.status;
|
|
1813
|
-
return typeof candidate._key == "string" && typeof candidate.label == "string" && (status == null || typeof status == "string");
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
function declaredRowColumns(entry) {
|
|
1817
|
-
if (("_type" in entry ? entry._type : entry.type) === "array") return new Set((("of" in entry ? entry.of : void 0) ?? []).map(shape => shape.name));
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
|
-
function isTodoListEntry(entry) {
|
|
1821
|
-
const columns = declaredRowColumns(entry);
|
|
1822
|
-
return columns !== void 0 && columns.has("label") && columns.has("status");
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
function isNotesEntry(entry) {
|
|
1826
|
-
const columns = declaredRowColumns(entry);
|
|
1827
|
-
return columns !== void 0 && columns.has("body") && columns.has("actor") && columns.has("at");
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
const ANONYMOUS_IDENTITY = "<anonymous>", SYSTEM_IDENTITY = "<system>", E_PREFIXED_PROJECT_ID = /^e-(.+)$/;
|
|
1561
|
+
const ACTOR_KINDS = [ "person", "agent", "system" ], ANONYMOUS_IDENTITY = "<anonymous>", SYSTEM_IDENTITY = "<system>", E_PREFIXED_PROJECT_ID = /^e-(.+)$/;
|
|
1831
1562
|
|
|
1832
1563
|
function classifyPrincipalId(id) {
|
|
1833
1564
|
if (id === ANONYMOUS_IDENTITY || id === SYSTEM_IDENTITY) return {
|
|
@@ -2256,6 +1987,10 @@ function opSchemas(targetSchema) {
|
|
|
2256
1987
|
type: v__namespace.literal("field.set"),
|
|
2257
1988
|
target: targetSchema,
|
|
2258
1989
|
value: ValueExprSchema
|
|
1990
|
+
}), v__namespace.strictObject({
|
|
1991
|
+
type: v__namespace.literal("field.setIfMissing"),
|
|
1992
|
+
target: targetSchema,
|
|
1993
|
+
value: ValueExprSchema
|
|
2259
1994
|
}), v__namespace.strictObject({
|
|
2260
1995
|
type: v__namespace.literal("field.unset"),
|
|
2261
1996
|
target: targetSchema
|
|
@@ -2263,6 +1998,14 @@ function opSchemas(targetSchema) {
|
|
|
2263
1998
|
type: v__namespace.literal("field.append"),
|
|
2264
1999
|
target: targetSchema,
|
|
2265
2000
|
value: ValueExprSchema
|
|
2001
|
+
}), v__namespace.strictObject({
|
|
2002
|
+
type: v__namespace.literal("field.inc"),
|
|
2003
|
+
target: targetSchema,
|
|
2004
|
+
value: v__namespace.optional(ValueExprSchema)
|
|
2005
|
+
}), v__namespace.strictObject({
|
|
2006
|
+
type: v__namespace.literal("field.dec"),
|
|
2007
|
+
target: targetSchema,
|
|
2008
|
+
value: v__namespace.optional(ValueExprSchema)
|
|
2266
2009
|
}), v__namespace.strictObject({
|
|
2267
2010
|
type: v__namespace.literal("field.updateWhere"),
|
|
2268
2011
|
target: targetSchema,
|
|
@@ -2479,17 +2222,27 @@ const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("
|
|
|
2479
2222
|
required: v__namespace.optional(v__namespace.boolean()),
|
|
2480
2223
|
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
2481
2224
|
validation: v__namespace.optional(ScalarValidationSchema)
|
|
2482
|
-
}), choiceOptionsCheck(), scalarValidationCheck());
|
|
2225
|
+
}), choiceOptionsCheck(), scalarValidationCheck()), CUSTOM_SEMANTIC_HINT = "`custom.<camelCaseMeaning>`", CustomSemanticSchema = v__namespace.custom(input => typeof input == "string" && /^custom\.[a-z][a-zA-Z0-9]*$/.test(input)), SemanticSchema = v__namespace.union([ picklist(SIGNAL_SEMANTICS), CustomSemanticSchema ], `expected ${SIGNAL_SEMANTICS.join(", ")}, or ${CUSTOM_SEMANTIC_HINT}`), ActionSemanticSchema = v__namespace.union([ picklist(ACTION_SEMANTICS), CustomSemanticSchema ], `expected ${ACTION_SEMANTICS.join(", ")}, or ${CUSTOM_SEMANTIC_HINT}`);
|
|
2226
|
+
|
|
2227
|
+
function semanticNamespace(semantic) {
|
|
2228
|
+
return semantic.startsWith("custom.") ? semantic : semantic.split(".", 1)[0] ?? semantic;
|
|
2229
|
+
}
|
|
2483
2230
|
|
|
2484
2231
|
function hasUniqueSemanticNamespaces(semantics) {
|
|
2485
|
-
const namespaces = semantics.map(
|
|
2232
|
+
const namespaces = semantics.map(semanticNamespace);
|
|
2486
2233
|
return new Set(namespaces).size === namespaces.length;
|
|
2487
2234
|
}
|
|
2488
2235
|
|
|
2236
|
+
function semanticsFieldSchema(semantic) {
|
|
2237
|
+
return v__namespace.optional(v__namespace.pipe(v__namespace.array(semantic), v__namespace.minLength(1, "declare at least one semantic, or omit `semantics`"), v__namespace.check(hasUniqueSemanticNamespaces, "declare at most one semantic from each namespace")));
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
const SemanticsFieldSchema = semanticsFieldSchema(SemanticSchema), ActionSemanticsFieldSchema = semanticsFieldSchema(ActionSemanticSchema);
|
|
2241
|
+
|
|
2489
2242
|
function actionFields(op, group) {
|
|
2490
2243
|
return {
|
|
2491
2244
|
name: NonEmpty,
|
|
2492
|
-
semantics:
|
|
2245
|
+
semantics: ActionSemanticsFieldSchema,
|
|
2493
2246
|
title: v__namespace.optional(v__namespace.string()),
|
|
2494
2247
|
description: v__namespace.optional(v__namespace.string()),
|
|
2495
2248
|
group: v__namespace.optional(group),
|
|
@@ -2536,6 +2289,7 @@ const StoredActionSchema = pinned()(v__namespace.strictObject({
|
|
|
2536
2289
|
function activityFields({field: field, action: action, target: target, group: group}) {
|
|
2537
2290
|
return {
|
|
2538
2291
|
name: NonEmpty,
|
|
2292
|
+
semantics: SemanticsFieldSchema,
|
|
2539
2293
|
title: v__namespace.optional(v__namespace.string()),
|
|
2540
2294
|
description: v__namespace.optional(v__namespace.string()),
|
|
2541
2295
|
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
@@ -2609,6 +2363,7 @@ const GuardSchema = v__namespace.strictObject(guardFields(NonEmpty)), AuthoringG
|
|
|
2609
2363
|
function stageFields({field: field, activity: activity, transition: transition, guard: guard, editable: editable}) {
|
|
2610
2364
|
return {
|
|
2611
2365
|
name: NonEmpty,
|
|
2366
|
+
semantics: SemanticsFieldSchema,
|
|
2612
2367
|
title: v__namespace.optional(v__namespace.string()),
|
|
2613
2368
|
description: v__namespace.optional(v__namespace.string()),
|
|
2614
2369
|
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
@@ -2647,6 +2402,7 @@ const StoredStartSchema = pinned()(v__namespace.strictObject(startFields(picklis
|
|
|
2647
2402
|
function workflowFields({field: field, stage: stage, start: start}) {
|
|
2648
2403
|
return {
|
|
2649
2404
|
name: NonEmpty,
|
|
2405
|
+
semantics: SemanticsFieldSchema,
|
|
2650
2406
|
title: NonEmpty,
|
|
2651
2407
|
description: v__namespace.optional(v__namespace.string()),
|
|
2652
2408
|
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
@@ -2843,11 +2599,20 @@ function checkStageReachability({def: def, stageNames: stageNames, issues: issue
|
|
|
2843
2599
|
});
|
|
2844
2600
|
}
|
|
2845
2601
|
|
|
2602
|
+
function actionSites(def) {
|
|
2603
|
+
return def.stages.flatMap((stage, i) => (stage.activities ?? []).flatMap((activity, j) => (activity.actions ?? []).map((action, a) => ({
|
|
2604
|
+
action: action,
|
|
2605
|
+
activity: activity,
|
|
2606
|
+
stage: stage,
|
|
2607
|
+
path: [ "stages", i, "activities", j, "actions", a ]
|
|
2608
|
+
}))));
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2846
2611
|
function effectNameSites(def) {
|
|
2847
2612
|
const sites = [];
|
|
2848
|
-
for (const
|
|
2613
|
+
for (const {action: action, path: path} of actionSites(def)) collectEffects({
|
|
2849
2614
|
effects: action.effects,
|
|
2850
|
-
path: [
|
|
2615
|
+
path: [ ...path, "effects" ],
|
|
2851
2616
|
sites: sites
|
|
2852
2617
|
});
|
|
2853
2618
|
return sites;
|
|
@@ -2978,13 +2743,22 @@ function checkUnboundConditionVars(def, issues) {
|
|
|
2978
2743
|
}
|
|
2979
2744
|
}
|
|
2980
2745
|
|
|
2746
|
+
const SOFT_GATE_VARS = {
|
|
2747
|
+
can: {
|
|
2748
|
+
noun: "the caller's grants"
|
|
2749
|
+
},
|
|
2750
|
+
attributes: {
|
|
2751
|
+
noun: "the caller's org-level user attributes"
|
|
2752
|
+
}
|
|
2753
|
+
}, SOFT_GATE_VAR_NAMES = Object.keys(SOFT_GATE_VARS);
|
|
2754
|
+
|
|
2981
2755
|
function unboundVarsAt(site) {
|
|
2982
2756
|
const callerVars = unboundCallerVars(site.policy);
|
|
2983
2757
|
return site.bindsRow === !0 ? callerVars : [ ...callerVars, "row" ];
|
|
2984
2758
|
}
|
|
2985
2759
|
|
|
2986
2760
|
function unboundCallerVars(policy) {
|
|
2987
|
-
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : policy === "triggered-payload" ? [
|
|
2761
|
+
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : policy === "triggered-payload" ? [ ...SOFT_GATE_VAR_NAMES, "params" ] : SOFT_GATE_VAR_NAMES;
|
|
2988
2762
|
}
|
|
2989
2763
|
|
|
2990
2764
|
const ROW_BINDING_CLAUSE = "$row (the discovered row in a spawn projection; the stored row under test in a where-op) is bound only while a spawn `with` projection or a where-op `where` evaluates";
|
|
@@ -2994,7 +2768,12 @@ function rowVarMessage(site) {
|
|
|
2994
2768
|
}
|
|
2995
2769
|
|
|
2996
2770
|
function callerVarMessage(site, name) {
|
|
2997
|
-
|
|
2771
|
+
if (site.policy === "cascade") return `${site.label} reads $${name} — cascade gates (transition \`when\`s, activity \`filter\`s, a cascade-fired action's \`when\`/\`filter\`) must resolve identically no matter whose token drives the cascade ($assigned is constant false, the other caller vars hold no value); gate on instance state (e.g. a field an action wrote), or pin executing identities with \`roles\``;
|
|
2772
|
+
if (site.policy === "caller-bound") return `${site.label} reads $${name} — $params (the firing action's args) is bound only while the action's effect bindings and where-op \`where\`s evaluate; this site never binds it, so the condition could never pass. Bind $params in an effect binding or a where-op instead, or gate on a field an action wrote`;
|
|
2773
|
+
if (site.policy === "triggered-payload" && name === "params") return `${site.label} reads $params — a cascade-fired action has no caller to supply args, so $params never holds a value in its payload; read fields or effect outputs instead`;
|
|
2774
|
+
const softGate = SOFT_GATE_VARS[name];
|
|
2775
|
+
if (softGate !== void 0) return `${site.label} reads $${name} — $${name} (${softGate.noun}) is bound only in the caller-bound projection (action filters, requirements, editable predicates); this site never binds it. Move the check to one of those sites or drop $${name}`;
|
|
2776
|
+
throw new Error(`callerVarMessage: unreachable for $${name} at ${site.label} (policy ${site.policy})`);
|
|
2998
2777
|
}
|
|
2999
2778
|
|
|
3000
2779
|
function conditionSites(def) {
|
|
@@ -3249,9 +3028,9 @@ function belowScopeNestedSites(args) {
|
|
|
3249
3028
|
}
|
|
3250
3029
|
|
|
3251
3030
|
function checkLevelKindEffectOutputs(def, issues) {
|
|
3252
|
-
for (const
|
|
3031
|
+
for (const {action: action, path: path} of actionSites(def)) pushOutputIssues({
|
|
3253
3032
|
action: action,
|
|
3254
|
-
path:
|
|
3033
|
+
path: path,
|
|
3255
3034
|
issues: issues
|
|
3256
3035
|
});
|
|
3257
3036
|
}
|
|
@@ -3333,18 +3112,18 @@ function storedRolesIssue(action) {
|
|
|
3333
3112
|
}
|
|
3334
3113
|
|
|
3335
3114
|
function checkStoredRolesPlacement(def, issues) {
|
|
3336
|
-
for (const
|
|
3115
|
+
for (const {action: action, path: path} of actionSites(def)) {
|
|
3337
3116
|
const message = storedRolesIssue(action);
|
|
3338
3117
|
message !== void 0 && issues.push({
|
|
3339
|
-
path: [
|
|
3118
|
+
path: [ ...path, "roles" ],
|
|
3340
3119
|
message: message
|
|
3341
3120
|
});
|
|
3342
3121
|
}
|
|
3343
3122
|
}
|
|
3344
3123
|
|
|
3345
3124
|
function checkTriggeredActionParams(def, issues) {
|
|
3346
|
-
for (const
|
|
3347
|
-
path: [
|
|
3125
|
+
for (const {action: action, path: path} of actionSites(def)) action.when === void 0 || (action.params ?? []).length === 0 || issues.push({
|
|
3126
|
+
path: [ ...path, "params" ],
|
|
3348
3127
|
message: `action "${action.name}" declares params but is cascade-fired (\`when\`) — no caller ever supplies args to a trigger. Drop the params, or drop \`when\` to make it a fireAction-fired action`
|
|
3349
3128
|
});
|
|
3350
3129
|
}
|
|
@@ -3389,13 +3168,20 @@ function checkSingleSubjectRequirements(def, issues) {
|
|
|
3389
3168
|
|
|
3390
3169
|
function checkStartFilterReads(def, issues) {
|
|
3391
3170
|
const filter = def.start?.filter;
|
|
3392
|
-
filter
|
|
3171
|
+
if (filter === void 0) return;
|
|
3172
|
+
const params = conditionParameterNames(filter);
|
|
3173
|
+
params.has("fields") && issues.push({
|
|
3393
3174
|
path: [ "start", "filter" ],
|
|
3394
3175
|
message: "start.filter reads $fields, but the filter is browse-time-pure — a start surface evaluates it per document, before any inputs exist, so $fields cannot be bound. Move the input-dependent rule to a start requirement, the start-time readiness predicate that binds $fields and is enforced by startInstance"
|
|
3395
|
-
})
|
|
3176
|
+
});
|
|
3177
|
+
for (const name of CALLER_BOUND_VARS) params.has(name) && issues.push({
|
|
3178
|
+
path: [ "start", "filter" ],
|
|
3179
|
+
message: `start.filter reads $${name} — start.filter is a read-side document-visibility rule (definitionsForDocument / start controls); startInstance never evaluates it, and no caller bag binds there, so the read is GROQ null and the filter can never match. Drop $${name} from start.filter. A "who may start" rule belongs in a start requirement, not in the browse-time filter`
|
|
3180
|
+
});
|
|
3181
|
+
readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
3396
3182
|
path: [ "start", "filter" ],
|
|
3397
3183
|
message: "start.filter reads the candidate document (its root), but the definition declares no `subject` entry — a read surface would never have a document to bind as root, so every root read is GROQ null and the filter silently misevaluates. Declare a `subject` entry (the document the workflow is about), or gate on the dataset instead"
|
|
3398
|
-
})
|
|
3184
|
+
});
|
|
3399
3185
|
}
|
|
3400
3186
|
|
|
3401
3187
|
function checkStartRequirementReads(def, issues) {
|
|
@@ -3691,15 +3477,13 @@ function seedEarlierSiblingTarget(args) {
|
|
|
3691
3477
|
}
|
|
3692
3478
|
|
|
3693
3479
|
function opSites(def) {
|
|
3694
|
-
|
|
3695
|
-
for (const [i, stage] of def.stages.entries()) for (const [j, activity] of (stage.activities ?? []).entries()) for (const [a, action] of (activity.actions ?? []).entries()) sites.push({
|
|
3480
|
+
return actionSites(def).map(({action: action, activity: activity, stage: stage, path: path}) => ({
|
|
3696
3481
|
ops: action.ops,
|
|
3697
|
-
path: [
|
|
3482
|
+
path: [ ...path, "ops" ],
|
|
3698
3483
|
label: `action "${action.name}"`,
|
|
3699
3484
|
stage: stage,
|
|
3700
3485
|
activity: activity
|
|
3701
|
-
});
|
|
3702
|
-
return sites;
|
|
3486
|
+
}));
|
|
3703
3487
|
}
|
|
3704
3488
|
|
|
3705
3489
|
function checkFieldReadOpValues(def, issues) {
|
|
@@ -3716,7 +3500,7 @@ function checkFieldReadOpValues(def, issues) {
|
|
|
3716
3500
|
}
|
|
3717
3501
|
|
|
3718
3502
|
function checkOpsFieldReads({ops: ops, path: path, label: label, ...ctx}) {
|
|
3719
|
-
for (const [o, op] of (ops ?? []).entries()) if ("value" in op) for (const {read: read, path: readPath} of fieldReadsIn(op.value, [ ...path, o, "value" ])) checkOpFieldRead({
|
|
3503
|
+
for (const [o, op] of (ops ?? []).entries()) if (!(!("value" in op) || op.value === void 0)) for (const {read: read, path: readPath} of fieldReadsIn(op.value, [ ...path, o, "value" ])) checkOpFieldRead({
|
|
3720
3504
|
...ctx,
|
|
3721
3505
|
read: read,
|
|
3722
3506
|
path: readPath,
|
|
@@ -3767,35 +3551,55 @@ function opFieldReadMissMessage({read: read, where: where, hosts: hosts}) {
|
|
|
3767
3551
|
return `${where} reads field "${read.field}", which is not declared at ${searched} — the read resolves to undefined at op time, so the write silently lands empty. Known: ${known.join(", ") || "(none)"}`;
|
|
3768
3552
|
}
|
|
3769
3553
|
|
|
3770
|
-
function
|
|
3554
|
+
function checkFieldTargetOps(def, issues) {
|
|
3771
3555
|
const workflow = def.fields ?? [];
|
|
3772
3556
|
for (const site of opSites(def)) for (const [o, op] of (site.ops ?? []).entries()) {
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
workflow: workflow,
|
|
3776
|
-
stage: site.stage.fields ?? [],
|
|
3777
|
-
activity: site.activity.fields ?? []
|
|
3778
|
-
};
|
|
3779
|
-
checkUpdateWhereTargetKind({
|
|
3557
|
+
const path = [ ...site.path, o ], rule = TARGET_KIND_RULES[op.type];
|
|
3558
|
+
rule !== void 0 && "target" in op && checkTargetKind({
|
|
3780
3559
|
op: op,
|
|
3781
|
-
path: [ ...
|
|
3560
|
+
path: [ ...path, "target" ],
|
|
3782
3561
|
label: site.label,
|
|
3783
|
-
scopes:
|
|
3562
|
+
scopes: opTargetScopes(workflow, site),
|
|
3563
|
+
rule: rule,
|
|
3784
3564
|
issues: issues
|
|
3785
|
-
}), checkUpdateWhereMergeKeys({
|
|
3565
|
+
}), op.type === "field.updateWhere" && checkUpdateWhereMergeKeys({
|
|
3786
3566
|
op: op,
|
|
3787
|
-
path:
|
|
3567
|
+
path: path,
|
|
3788
3568
|
label: site.label,
|
|
3789
3569
|
issues: issues
|
|
3790
3570
|
});
|
|
3791
3571
|
}
|
|
3792
3572
|
}
|
|
3793
3573
|
|
|
3794
|
-
|
|
3574
|
+
const arithmeticTargetRule = {
|
|
3575
|
+
accepts: target => target.type === "number",
|
|
3576
|
+
issue: () => "arithmetic ops target `number` entries only"
|
|
3577
|
+
}, TARGET_KIND_RULES = {
|
|
3578
|
+
"field.inc": arithmeticTargetRule,
|
|
3579
|
+
"field.dec": arithmeticTargetRule,
|
|
3580
|
+
"field.setIfMissing": {
|
|
3581
|
+
accepts: target => !isAlwaysArrayFieldKind(target.type),
|
|
3582
|
+
issue: () => "setIfMissing applies to nullable entries only; an empty array entry already holds []"
|
|
3583
|
+
},
|
|
3584
|
+
"field.updateWhere": {
|
|
3585
|
+
accepts: target => target.type === "array",
|
|
3586
|
+
issue: target => "updateWhere merges declared row sub-fields, so its target must be an `array` entry" + rowOpsHint(target.type)
|
|
3587
|
+
}
|
|
3588
|
+
};
|
|
3589
|
+
|
|
3590
|
+
function opTargetScopes(workflow, site) {
|
|
3591
|
+
return {
|
|
3592
|
+
workflow: workflow,
|
|
3593
|
+
stage: site.stage.fields ?? [],
|
|
3594
|
+
activity: site.activity.fields ?? []
|
|
3595
|
+
};
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
function checkTargetKind({op: op, path: path, label: label, scopes: scopes, rule: rule, issues: issues}) {
|
|
3795
3599
|
const target = scopes[op.target.scope]?.find(entry => entry.name === op.target.field);
|
|
3796
|
-
target === void 0 || target
|
|
3797
|
-
path:
|
|
3798
|
-
message: `${label}
|
|
3600
|
+
target === void 0 || rule.accepts(target) || issues.push({
|
|
3601
|
+
path: path,
|
|
3602
|
+
message: `${label} ${op.type} targets ${op.target.scope}-scope "${op.target.field}" (${target.type}) — ${rule.issue(target)}`
|
|
3799
3603
|
});
|
|
3800
3604
|
}
|
|
3801
3605
|
|
|
@@ -4027,7 +3831,7 @@ function checkWorkflowInvariants(def) {
|
|
|
4027
3831
|
}), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues),
|
|
4028
3832
|
checkRequiredField(def, issues), checkStart(def, issues), checkPredicates(def, issues),
|
|
4029
3833
|
checkUnboundConditionVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
|
|
4030
|
-
checkFieldReadOpValues(def, issues),
|
|
3834
|
+
checkFieldReadOpValues(def, issues), checkFieldTargetOps(def, issues), checkGuardFieldReads(def, issues),
|
|
4031
3835
|
checkAssigneesEntries(def, issues), checkDueDateEntries(def, issues), checkSubjectEntries(def, issues),
|
|
4032
3836
|
checkLevelKindEffectOutputs(def, issues), checkActivityTerminalPaths(def, issues),
|
|
4033
3837
|
checkTerminalStageActivities(def, issues), checkTriggeredActionParams(def, issues),
|
|
@@ -4068,11 +3872,7 @@ exports.CONDITION_VARS = CONDITION_VARS;
|
|
|
4068
3872
|
|
|
4069
3873
|
exports.ContractViolationError = ContractViolationError;
|
|
4070
3874
|
|
|
4071
|
-
exports.
|
|
4072
|
-
|
|
4073
|
-
exports.DATA_MODEL_MIN_READER = DATA_MODEL_MIN_READER;
|
|
4074
|
-
|
|
4075
|
-
exports.DATA_MODEL_VERSION = DATA_MODEL_VERSION;
|
|
3875
|
+
exports.DECISION_SEMANTICS = DECISION_SEMANTICS;
|
|
4076
3876
|
|
|
4077
3877
|
exports.DEFAULT_TRANSITION_WHEN = DEFAULT_TRANSITION_WHEN;
|
|
4078
3878
|
|
|
@@ -4116,19 +3916,15 @@ exports.IsoTimestamp = IsoTimestamp;
|
|
|
4116
3916
|
|
|
4117
3917
|
exports.MUTATION_GUARD_ACTIONS = MUTATION_GUARD_ACTIONS;
|
|
4118
3918
|
|
|
4119
|
-
exports.ModelVersionAheadError = ModelVersionAheadError;
|
|
4120
|
-
|
|
4121
3919
|
exports.NonEmptyString = NonEmptyString;
|
|
4122
3920
|
|
|
4123
3921
|
exports.PersistedDocShapeError = PersistedDocShapeError;
|
|
4124
3922
|
|
|
4125
|
-
exports.READER_MODEL_ROLLOUT_URL = READER_MODEL_ROLLOUT_URL;
|
|
4126
|
-
|
|
4127
3923
|
exports.RESERVED_CONDITION_VARS = RESERVED_CONDITION_VARS;
|
|
4128
3924
|
|
|
4129
3925
|
exports.RESOURCE_ALIAS_NAME_SOURCE = RESOURCE_ALIAS_NAME_SOURCE;
|
|
4130
3926
|
|
|
4131
|
-
exports.
|
|
3927
|
+
exports.SIGNAL_SEMANTICS = SIGNAL_SEMANTICS;
|
|
4132
3928
|
|
|
4133
3929
|
exports.START_FILTER_VARS = START_FILTER_VARS;
|
|
4134
3930
|
|
|
@@ -4144,8 +3940,6 @@ exports.VersionSpecificDatasetGdrError = VersionSpecificDatasetGdrError;
|
|
|
4144
3940
|
|
|
4145
3941
|
exports.WORKFLOW_DEFINITION_TYPE = WORKFLOW_DEFINITION_TYPE;
|
|
4146
3942
|
|
|
4147
|
-
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
4148
|
-
|
|
4149
3943
|
exports.WorkflowConfigSchema = WorkflowConfigSchema;
|
|
4150
3944
|
|
|
4151
3945
|
exports.WorkflowError = WorkflowError;
|
|
@@ -4154,10 +3948,6 @@ exports.actorFulfillsRole = actorFulfillsRole;
|
|
|
4154
3948
|
|
|
4155
3949
|
exports.andConditions = andConditions;
|
|
4156
3950
|
|
|
4157
|
-
exports.assertReadableModel = assertReadableModel;
|
|
4158
|
-
|
|
4159
|
-
exports.assertReaderModelAcknowledgement = assertReaderModelAcknowledgement;
|
|
4160
|
-
|
|
4161
3951
|
exports.checkWorkflowInvariants = checkWorkflowInvariants;
|
|
4162
3952
|
|
|
4163
3953
|
exports.choiceValueIssues = choiceValueIssues;
|
|
@@ -4198,8 +3988,6 @@ exports.evaluatePredicates = evaluatePredicates;
|
|
|
4198
3988
|
|
|
4199
3989
|
exports.extractDocumentId = extractDocumentId;
|
|
4200
3990
|
|
|
4201
|
-
exports.fieldTreeShape = fieldTreeShape;
|
|
4202
|
-
|
|
4203
3991
|
exports.fieldValueSchemas = fieldValueSchemas;
|
|
4204
3992
|
|
|
4205
3993
|
exports.firstCarriedGlobalId = firstCarriedGlobalId;
|
|
@@ -4222,6 +4010,8 @@ exports.groq = groq;
|
|
|
4222
4010
|
|
|
4223
4011
|
exports.groupMembershipNames = groupMembershipNames;
|
|
4224
4012
|
|
|
4013
|
+
exports.isAlwaysArrayFieldKind = isAlwaysArrayFieldKind;
|
|
4014
|
+
|
|
4225
4015
|
exports.isBareSeedId = isBareSeedId;
|
|
4226
4016
|
|
|
4227
4017
|
exports.isCascadeFired = isCascadeFired;
|
|
@@ -4254,24 +4044,10 @@ exports.isTodoListItem = isTodoListItem;
|
|
|
4254
4044
|
|
|
4255
4045
|
exports.isUnevaluable = isUnevaluable;
|
|
4256
4046
|
|
|
4257
|
-
exports.isUnprimed = isUnprimed;
|
|
4258
|
-
|
|
4259
4047
|
exports.labelFor = labelFor;
|
|
4260
4048
|
|
|
4261
4049
|
exports.lakePrincipalId = lakePrincipalId;
|
|
4262
4050
|
|
|
4263
|
-
exports.minReaderModelOf = minReaderModelOf;
|
|
4264
|
-
|
|
4265
|
-
exports.modelStampFor = modelStampFor;
|
|
4266
|
-
|
|
4267
|
-
exports.modelVersionOf = modelVersionOf;
|
|
4268
|
-
|
|
4269
|
-
exports.parentRef = parentRef;
|
|
4270
|
-
|
|
4271
|
-
exports.parseDefinitionSnapshot = parseDefinitionSnapshot;
|
|
4272
|
-
|
|
4273
|
-
exports.parseDefinitionSnapshotValue = parseDefinitionSnapshotValue;
|
|
4274
|
-
|
|
4275
4051
|
exports.parseFieldValue = parseFieldValue;
|
|
4276
4052
|
|
|
4277
4053
|
exports.parseGdr = parseGdr;
|
|
@@ -4304,10 +4080,6 @@ exports.releaseDocId = releaseDocId;
|
|
|
4304
4080
|
|
|
4305
4081
|
exports.releaseRef = releaseRef;
|
|
4306
4082
|
|
|
4307
|
-
exports.requiredModelFeatures = requiredModelFeatures;
|
|
4308
|
-
|
|
4309
|
-
exports.requiredReaderModel = requiredReaderModel;
|
|
4310
|
-
|
|
4311
4083
|
exports.resourceAliasesToMap = resourceAliasesToMap;
|
|
4312
4084
|
|
|
4313
4085
|
exports.resourceFromGdrUri = resourceFromGdrUri;
|
|
@@ -4332,8 +4104,6 @@ exports.startKindOf = startKindOf;
|
|
|
4332
4104
|
|
|
4333
4105
|
exports.tagScopeFilter = tagScopeFilter;
|
|
4334
4106
|
|
|
4335
|
-
exports.terminalState = terminalState;
|
|
4336
|
-
|
|
4337
4107
|
exports.toBareId = toBareId;
|
|
4338
4108
|
|
|
4339
4109
|
exports.toPhysicalGdr = toPhysicalGdr;
|