@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/CHANGELOG.md +743 -0
- package/DATAMODEL.md +183 -0
- package/README.md +30 -0
- package/dist/_chunks-cjs/invariants.cjs +2461 -0
- package/dist/_chunks-es/invariants.js +2251 -0
- package/dist/define.cjs +676 -868
- package/dist/define.d.cts +1047 -85
- package/dist/define.d.ts +1047 -85
- package/dist/define.js +652 -865
- package/dist/index.cjs +9777 -5302
- package/dist/index.d.cts +3985 -733
- package/dist/index.d.ts +3985 -733
- package/dist/index.js +9401 -5359
- package/package.json +9 -5
- package/dist/_chunks-cjs/schema.cjs +0 -641
- package/dist/_chunks-cjs/schema.cjs.map +0 -1
- package/dist/_chunks-es/schema.js +0 -626
- package/dist/_chunks-es/schema.js.map +0 -1
- package/dist/define.cjs.map +0 -1
- package/dist/define.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,2461 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var v = require("valibot"), groqConditionDescribe = require("@sanity/groq-condition-describe"), groqJs = require("groq-js");
|
|
4
|
+
|
|
5
|
+
function _interopNamespaceCompat(e) {
|
|
6
|
+
if (e && typeof e == "object" && "default" in e) return e;
|
|
7
|
+
var n = /* @__PURE__ */ Object.create(null);
|
|
8
|
+
return e && Object.keys(e).forEach(function(k) {
|
|
9
|
+
if (k !== "default") {
|
|
10
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
11
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
12
|
+
enumerable: !0,
|
|
13
|
+
get: function() {
|
|
14
|
+
return e[k];
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}), n.default = e, Object.freeze(n);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
|
|
22
|
+
|
|
23
|
+
function errorMessage(err) {
|
|
24
|
+
return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function rethrowWithContext(err, context) {
|
|
28
|
+
throw new Error(`${context}: ${errorMessage(err)}`, {
|
|
29
|
+
cause: err
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function andConditions(parts) {
|
|
34
|
+
const present = parts.filter(p => p !== void 0);
|
|
35
|
+
if (present.length !== 0) return present.length === 1 ? present[0] : present.map(p => `(${p})`).join(" && ");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const KNOWN_SCHEMES = /* @__PURE__ */ new Set([ "dataset", "canvas", "media-library", "dashboard" ]), KNOWN_SCHEMES_TEXT = [ ...KNOWN_SCHEMES ].join(", ");
|
|
39
|
+
|
|
40
|
+
function parseGdr(uri) {
|
|
41
|
+
const colon = uri.indexOf(":");
|
|
42
|
+
if (colon < 0) throw new Error(`Invalid GDR "${uri}": must be a URI of form "<scheme>:<...id-parts>". Known schemes: ${KNOWN_SCHEMES_TEXT}.`);
|
|
43
|
+
const scheme = uri.slice(0, colon), rest = uri.slice(colon + 1);
|
|
44
|
+
if (!KNOWN_SCHEMES.has(scheme)) throw new Error(`Invalid GDR "${uri}": unknown scheme "${scheme}". Known: ${KNOWN_SCHEMES_TEXT}.`);
|
|
45
|
+
const parts = rest.split(":");
|
|
46
|
+
if (parts.some(part => part.length === 0)) throw new Error(`Invalid GDR "${uri}": id parts must be non-empty (no leading, trailing, or doubled ":").`);
|
|
47
|
+
if (scheme === "dataset") {
|
|
48
|
+
if (parts.length !== 3) throw new Error(`Invalid GDR "${uri}": dataset scheme requires <projectId>:<dataset>:<documentId> (3 parts after scheme); got ${parts.length}.`);
|
|
49
|
+
return {
|
|
50
|
+
scheme: "dataset",
|
|
51
|
+
projectId: parts[0],
|
|
52
|
+
dataset: parts[1],
|
|
53
|
+
documentId: parts[2]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (parts.length !== 2) throw new Error(`Invalid GDR "${uri}": ${scheme} scheme requires <resourceId>:<documentId> (2 parts after scheme); got ${parts.length}.`);
|
|
57
|
+
return {
|
|
58
|
+
scheme: scheme,
|
|
59
|
+
resourceId: parts[0],
|
|
60
|
+
documentId: parts[1]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function tryParseGdr(uri) {
|
|
65
|
+
try {
|
|
66
|
+
return parseGdr(uri);
|
|
67
|
+
} catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function gdrUri(parts) {
|
|
73
|
+
return parts.scheme === "dataset" ? `dataset:${parts.projectId}:${parts.dataset}:${parts.documentId}` : `${parts.scheme}:${parts.resourceId}:${parts.documentId}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function extractDocumentId(gdrUriString) {
|
|
77
|
+
return parseGdr(gdrUriString).documentId;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const RESOURCE_ALIAS_NAME_SOURCE = "[a-z0-9][a-z0-9-]*", RESOURCE_ALIAS_NAME_RE = new RegExp(`^${RESOURCE_ALIAS_NAME_SOURCE}$`);
|
|
81
|
+
|
|
82
|
+
function validateResourceAliasName(name) {
|
|
83
|
+
if (!RESOURCE_ALIAS_NAME_RE.test(name)) throw new Error(`Invalid resource alias name "${name}": expected lowercase letters, digits, and dashes (no leading dash).`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function toPhysicalGdr(id, home) {
|
|
87
|
+
return isGdrUri(id) ? id : gdrFromResource(home, id);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function refDataset({projectId: projectId, dataset: dataset, documentId: documentId, type: type}) {
|
|
91
|
+
return {
|
|
92
|
+
id: gdrUri({
|
|
93
|
+
scheme: "dataset",
|
|
94
|
+
projectId: projectId,
|
|
95
|
+
dataset: dataset,
|
|
96
|
+
documentId: documentId
|
|
97
|
+
}),
|
|
98
|
+
type: type
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function refCanvas({resourceId: resourceId, documentId: documentId, type: type}) {
|
|
103
|
+
return {
|
|
104
|
+
id: gdrUri({
|
|
105
|
+
scheme: "canvas",
|
|
106
|
+
resourceId: resourceId,
|
|
107
|
+
documentId: documentId
|
|
108
|
+
}),
|
|
109
|
+
type: type
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function refMediaLibrary({resourceId: resourceId, documentId: documentId, type: type}) {
|
|
114
|
+
return {
|
|
115
|
+
id: gdrUri({
|
|
116
|
+
scheme: "media-library",
|
|
117
|
+
resourceId: resourceId,
|
|
118
|
+
documentId: documentId
|
|
119
|
+
}),
|
|
120
|
+
type: type
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function refDashboard({resourceId: resourceId, documentId: documentId, type: type}) {
|
|
125
|
+
return {
|
|
126
|
+
id: gdrUri({
|
|
127
|
+
scheme: "dashboard",
|
|
128
|
+
resourceId: resourceId,
|
|
129
|
+
documentId: documentId
|
|
130
|
+
}),
|
|
131
|
+
type: type
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function datasetResourceParts(id) {
|
|
136
|
+
const dot = id.indexOf(".");
|
|
137
|
+
if (dot <= 0 || dot === id.length - 1) throw new Error(`Invalid dataset resource id "${id}": expected "<projectId>.<dataset>".`);
|
|
138
|
+
return {
|
|
139
|
+
projectId: id.slice(0, dot),
|
|
140
|
+
dataset: id.slice(dot + 1)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function clientConfigFromResource(res) {
|
|
145
|
+
return res.type === "dataset" ? datasetResourceParts(res.id) : {
|
|
146
|
+
resource: res
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function gdrFromResource(res, documentId) {
|
|
151
|
+
if (res.type === "dataset") {
|
|
152
|
+
const {projectId: projectId, dataset: dataset} = datasetResourceParts(res.id);
|
|
153
|
+
return gdrUri({
|
|
154
|
+
scheme: "dataset",
|
|
155
|
+
projectId: projectId,
|
|
156
|
+
dataset: dataset,
|
|
157
|
+
documentId: documentId
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return gdrUri({
|
|
161
|
+
scheme: res.type,
|
|
162
|
+
resourceId: res.id,
|
|
163
|
+
documentId: documentId
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function resourceGdr(res) {
|
|
168
|
+
return `${res.type}:${res.id}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function parseResourceGdr(uri) {
|
|
172
|
+
const expected = 'expected "<type>:<id>" with no document part, e.g. "dataset:<projectId>.<dataset>" or "media-library:<resourceId>"', parts = uri.split(":");
|
|
173
|
+
if (parts.length !== 2 || parts.some(part => part.length === 0)) throw new Error(`Invalid resource GDR "${uri}": ${expected}.`);
|
|
174
|
+
const [type, id] = parts;
|
|
175
|
+
if (!KNOWN_SCHEMES.has(type)) throw new Error(`Invalid resource GDR "${uri}": unknown resource type "${type}". Known: ${KNOWN_SCHEMES_TEXT}.`);
|
|
176
|
+
return type === "dataset" && datasetResourceParts(id), {
|
|
177
|
+
type: type,
|
|
178
|
+
id: id
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function gdrResourcePrefix(res) {
|
|
183
|
+
const full = gdrFromResource(res, "x");
|
|
184
|
+
return full.slice(0, full.length - 1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function resourceFromParsed(parsed) {
|
|
188
|
+
return parsed.scheme === "dataset" ? {
|
|
189
|
+
type: "dataset",
|
|
190
|
+
id: `${parsed.projectId}.${parsed.dataset}`
|
|
191
|
+
} : {
|
|
192
|
+
type: parsed.scheme,
|
|
193
|
+
id: parsed.resourceId ?? ""
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function resourceFromGdrUri(uri) {
|
|
198
|
+
const parsed = tryParseGdr(uri);
|
|
199
|
+
return parsed === void 0 ? void 0 : resourceFromParsed(parsed);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function sameResource(a, b) {
|
|
203
|
+
return a.type === b.type && a.id === b.id;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function selfGdr(doc) {
|
|
207
|
+
return gdrFromResource(doc.workflowResource, doc._id);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function definitionDocId({tag: tag, definition: definition, version: version}) {
|
|
211
|
+
return `${tag}.${definition}.v${version}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function isGdrUri(value) {
|
|
215
|
+
return typeof value == "string" && tryParseGdr(value) !== void 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function toBareId(id) {
|
|
219
|
+
return isGdrUri(id) ? extractDocumentId(id) : id;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function gdrRef({res: res, documentId: documentId, type: type}) {
|
|
223
|
+
return {
|
|
224
|
+
id: gdrFromResource(res, documentId),
|
|
225
|
+
type: type
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isGdr(value) {
|
|
230
|
+
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
class WorkflowError extends Error {
|
|
234
|
+
kind;
|
|
235
|
+
constructor(kind, message, options) {
|
|
236
|
+
super(message, options), this.kind = kind;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
class ContractViolationError extends WorkflowError {
|
|
241
|
+
constructor(message) {
|
|
242
|
+
super("contract-violation", message), this.name = "ContractViolationError";
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
class InstanceNotFoundError extends WorkflowError {
|
|
247
|
+
instanceId;
|
|
248
|
+
constructor(args) {
|
|
249
|
+
super("instance-not-found", `Workflow instance ${args.instanceId} not found${args.detail ? ` (${args.detail})` : ""}`),
|
|
250
|
+
this.name = "InstanceNotFoundError", this.instanceId = args.instanceId;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
class DefinitionNotFoundError extends WorkflowError {
|
|
255
|
+
definition;
|
|
256
|
+
version;
|
|
257
|
+
constructor(args) {
|
|
258
|
+
super("definition-not-found", args.version !== void 0 ? `Workflow definition ${args.definition} v${args.version} not deployed` : `Workflow definition ${args.definition} has no deployed versions`),
|
|
259
|
+
this.name = "DefinitionNotFoundError", this.definition = args.definition, args.version !== void 0 && (this.version = args.version);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
class DefinitionInUseError extends WorkflowError {
|
|
264
|
+
definition;
|
|
265
|
+
blockedBy;
|
|
266
|
+
constructor(args) {
|
|
267
|
+
super("definition-in-use", definitionInUseMessage(args.definition, args.blockedBy)),
|
|
268
|
+
this.name = "DefinitionInUseError", this.definition = args.definition, this.blockedBy = args.blockedBy;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function definitionInUseMessage(definition, blockedBy) {
|
|
273
|
+
if (blockedBy.reason === "non-terminal-instances") {
|
|
274
|
+
const head = blockedBy.instanceIds.slice(0, 3).join(", "), preview = blockedBy.instanceIds.length > 3 ? `${head}, …` : head;
|
|
275
|
+
return `Cannot delete ${definition}: ${blockedBy.instanceIds.length} non-terminal instance(s) exist (${preview}). Pass cascade to abort them first — instances are aborted in place, never deleted.`;
|
|
276
|
+
}
|
|
277
|
+
const names = blockedBy.referrers.map(r => `${r.definition} v${r.version}`).join(", ");
|
|
278
|
+
return `Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
class EffectNotFoundError extends WorkflowError {
|
|
282
|
+
instanceId;
|
|
283
|
+
effectKey;
|
|
284
|
+
settled;
|
|
285
|
+
constructor(args) {
|
|
286
|
+
super("effect-not-found", effectNotFoundMessage(args)), this.name = "EffectNotFoundError",
|
|
287
|
+
this.instanceId = args.instanceId, this.effectKey = args.effectKey, args.settled !== void 0 && (this.settled = args.settled);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function effectNotFoundMessage(args) {
|
|
292
|
+
const base = `Pending effect "${args.effectKey}" not found on instance ${args.instanceId}`;
|
|
293
|
+
if (args.settled === void 0) return base;
|
|
294
|
+
const cause = args.settled.detail !== void 0 ? ` (${args.settled.detail})` : "";
|
|
295
|
+
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}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
299
|
+
|
|
300
|
+
function validateTag(tag) {
|
|
301
|
+
if (!TAG_RE.test(tag)) throw new ContractViolationError(`tag: invalid tag "${tag}" — must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function tagScopeFilter() {
|
|
305
|
+
return "tag == $tag";
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const NonEmptyString = v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty("must not be empty"));
|
|
309
|
+
|
|
310
|
+
function asPredicate(validate) {
|
|
311
|
+
return value => {
|
|
312
|
+
try {
|
|
313
|
+
return validate(value), !0;
|
|
314
|
+
} catch {
|
|
315
|
+
return !1;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts), WorkflowResourceSchema = v__namespace.variant("type", [ v__namespace.object({
|
|
321
|
+
type: v__namespace.literal("dataset"),
|
|
322
|
+
id: v__namespace.pipe(NonEmptyString, v__namespace.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
|
|
323
|
+
}), v__namespace.object({
|
|
324
|
+
type: v__namespace.literal("canvas"),
|
|
325
|
+
id: NonEmptyString
|
|
326
|
+
}), v__namespace.object({
|
|
327
|
+
type: v__namespace.literal("media-library"),
|
|
328
|
+
id: NonEmptyString
|
|
329
|
+
}), v__namespace.object({
|
|
330
|
+
type: v__namespace.literal("dashboard"),
|
|
331
|
+
id: NonEmptyString
|
|
332
|
+
}) ]), ResourceBindingSchema = v__namespace.object({
|
|
333
|
+
name: v__namespace.pipe(NonEmptyString, v__namespace.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
334
|
+
resource: WorkflowResourceSchema
|
|
335
|
+
}), DefinitionSchema = v__namespace.custom(input => typeof input == "object" && input !== null && typeof input.name == "string", "expected a workflow definition (an object with a string `name`)"), DeploymentSchema = v__namespace.object({
|
|
336
|
+
name: NonEmptyString,
|
|
337
|
+
tag: v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty(), v__namespace.check(isValidTag, "invalid tag — lowercase letters, digits and dashes only, no leading dash, no dots")),
|
|
338
|
+
workflowResource: WorkflowResourceSchema,
|
|
339
|
+
resourceAliases: v__namespace.optional(v__namespace.pipe(v__namespace.array(ResourceBindingSchema), v__namespace.check(bindings => new Set(bindings.map(binding => binding.name)).size === bindings.length, "duplicate resource handle name — each binding name must be unique within a deployment"))),
|
|
340
|
+
definitions: v__namespace.pipe(v__namespace.array(DefinitionSchema), v__namespace.minLength(1, "a deployment needs at least one definition"))
|
|
341
|
+
}), TelemetryLoggerSchema = v__namespace.custom(input => typeof input == "object" && input !== null && typeof input.log == "function", "expected a telemetry logger (an object with a `log` function)"), WorkflowConfigSchema = v__namespace.object({
|
|
342
|
+
deployments: v__namespace.pipe(v__namespace.array(DeploymentSchema), v__namespace.minLength(1, "a config needs at least one deployment"), v__namespace.check(deployments => new Set(deployments.map(deployment => deployment.tag)).size === deployments.length, "duplicate deployment tag — each deployment must use a unique tag")),
|
|
343
|
+
telemetry: v__namespace.optional(TelemetryLoggerSchema)
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
function resourceAliasesToMap(resourceAliases) {
|
|
347
|
+
return Object.fromEntries((resourceAliases ?? []).map(binding => [ binding.name, binding.resource ]));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
351
|
+
|
|
352
|
+
function isGuardReadExpr(expr) {
|
|
353
|
+
return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function printGuardRead(read) {
|
|
357
|
+
switch (read.type) {
|
|
358
|
+
case "self":
|
|
359
|
+
return "$self";
|
|
360
|
+
|
|
361
|
+
case "now":
|
|
362
|
+
return "$now";
|
|
363
|
+
|
|
364
|
+
case "fieldRead":
|
|
365
|
+
return read.path === void 0 ? `$fields.${read.field}` : `$fields.${read.field}.${read.path}`;
|
|
366
|
+
|
|
367
|
+
case "effectsRead":
|
|
368
|
+
return read.path === void 0 ? `$effects['${read.effect}']` : `$effects['${read.effect}'].${read.path}`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const UNIVERSAL_ROLE_ALIAS_KEY = "$all";
|
|
373
|
+
|
|
374
|
+
function normalizeRoleAliases(aliases) {
|
|
375
|
+
if (aliases === void 0 || !("*" in aliases)) return aliases;
|
|
376
|
+
const {["*"]: universal, ...perRole} = aliases;
|
|
377
|
+
return {
|
|
378
|
+
...perRole,
|
|
379
|
+
[UNIVERSAL_ROLE_ALIAS_KEY]: universal
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function expandRequiredRoles(required, aliases) {
|
|
384
|
+
if (aliases === void 0) return [ ...required ];
|
|
385
|
+
const out = /* @__PURE__ */ new Set;
|
|
386
|
+
for (const role of required) {
|
|
387
|
+
out.add(role);
|
|
388
|
+
for (const fulfiller of aliases[role] ?? []) out.add(fulfiller);
|
|
389
|
+
}
|
|
390
|
+
for (const fulfiller of aliases[UNIVERSAL_ROLE_ALIAS_KEY] ?? []) out.add(fulfiller);
|
|
391
|
+
return [ ...out ];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function actorFulfillsRole({actorRoles: actorRoles, required: required, aliases: aliases}) {
|
|
395
|
+
if (actorRoles === void 0 || actorRoles.length === 0) return !1;
|
|
396
|
+
const accepted = new Set(expandRequiredRoles([ required ], aliases));
|
|
397
|
+
return actorRoles.some(role => accepted.has(role));
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function isUnevaluable(result) {
|
|
401
|
+
return result == null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function evaluateConditionOutcome(args) {
|
|
405
|
+
const {condition: condition, snapshot: snapshot, params: params} = args;
|
|
406
|
+
return groqConditionDescribe.evaluateConditionOutcome({
|
|
407
|
+
condition: condition,
|
|
408
|
+
params: params,
|
|
409
|
+
dataset: snapshot.docs
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function evaluateCondition(args) {
|
|
414
|
+
return await evaluateConditionOutcome(args) === "satisfied";
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function evaluatePredicates(args) {
|
|
418
|
+
const out = {};
|
|
419
|
+
for (const [name, groq] of Object.entries(args.predicates ?? {})) {
|
|
420
|
+
const result = await runGroq({
|
|
421
|
+
groq: groq,
|
|
422
|
+
params: args.params,
|
|
423
|
+
snapshot: args.snapshot
|
|
424
|
+
}), outcome = groqConditionDescribe.conditionOutcome(result);
|
|
425
|
+
out[name] = outcome === "unevaluable" ? null : outcome === "satisfied";
|
|
426
|
+
}
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function runGroq({groq: groq, params: params, snapshot: snapshot}) {
|
|
431
|
+
return groqConditionDescribe.runGroq({
|
|
432
|
+
groq: groq,
|
|
433
|
+
params: params,
|
|
434
|
+
dataset: snapshot.docs
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function conditionSyntaxIssues(groq, boundVars) {
|
|
439
|
+
const issues = [];
|
|
440
|
+
let tree;
|
|
441
|
+
try {
|
|
442
|
+
tree = groqJs.parse(groq);
|
|
443
|
+
} catch (err) {
|
|
444
|
+
issues.push(errorMessage(err));
|
|
445
|
+
}
|
|
446
|
+
if (/\*\s*\[\s*_type\b/.test(groq) && issues.push('condition scans by `_type` — that\'s a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors + field-declared docs). To bring extra docs in scope, declare a `doc.ref` (or `doc.refs`) field entry on the workflow or this stage. For lake scans like "all articles in this release", use `subworkflows.forEach` instead.'),
|
|
447
|
+
boundVars !== void 0 && tree !== void 0) for (const name of conditionParameterNames(groq)) boundVars.includes(name) || issues.push(`reads $${name}, which this scope does not bind — an unbound variable evaluates to GROQ null, so the condition silently never matches. Bound here: ` + boundVars.map(n => `$${n}`).join(", "));
|
|
448
|
+
return issues;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function conditionParameterNames(groq) {
|
|
452
|
+
const read = /* @__PURE__ */ new Set;
|
|
453
|
+
return walkAstNodes(tryParseGroq(groq), node => {
|
|
454
|
+
node.type === "Parameter" && typeof node.name == "string" && read.add(node.name);
|
|
455
|
+
}), read;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function conditionFieldReadNames(groq) {
|
|
459
|
+
const read = /* @__PURE__ */ new Set;
|
|
460
|
+
return walkAstNodes(tryParseGroq(groq), node => {
|
|
461
|
+
if (node.type !== "AccessAttribute" || typeof node.name != "string") return;
|
|
462
|
+
const base = node.base;
|
|
463
|
+
base?.type === "Parameter" && base.name === "fields" && read.add(node.name);
|
|
464
|
+
}), read;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function conditionEffectReads(groq) {
|
|
468
|
+
const reads = [];
|
|
469
|
+
return walkAstNodes(tryParseGroq(groq), node => {
|
|
470
|
+
if (node.type !== "AccessAttribute" || typeof node.name != "string") return;
|
|
471
|
+
const base = node.base;
|
|
472
|
+
base?.type !== "AccessAttribute" || typeof base.name != "string" || base.base?.type === "Parameter" && base.base.name === "effects" && reads.push({
|
|
473
|
+
effect: base.name,
|
|
474
|
+
key: node.name
|
|
475
|
+
});
|
|
476
|
+
}), reads;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function tryParseGroq(groq) {
|
|
480
|
+
try {
|
|
481
|
+
return groqJs.parse(groq);
|
|
482
|
+
} catch {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function walkAstNodes(node, visit) {
|
|
488
|
+
if (Array.isArray(node)) {
|
|
489
|
+
for (const item of node) walkAstNodes(item, visit);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (!(typeof node != "object" || node === null)) {
|
|
493
|
+
visit(node);
|
|
494
|
+
for (const value of Object.values(node)) walkAstNodes(value, visit);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const CONDITION_VARS = [ {
|
|
499
|
+
name: "self",
|
|
500
|
+
binding: "always",
|
|
501
|
+
label: "this workflow instance",
|
|
502
|
+
description: "GDR URI of the instance document itself — `*[_id == $self][0]` reads the instance in the snapshot."
|
|
503
|
+
}, {
|
|
504
|
+
name: "fields",
|
|
505
|
+
binding: "always",
|
|
506
|
+
label: "the workflow's fields",
|
|
507
|
+
description: "Declared field entries rendered by name (`$fields.<name>` is the value, no envelope). Stage/activity scopes overlay lexically; `doc.ref` values render the hydrated document when the snapshot holds it."
|
|
508
|
+
}, {
|
|
509
|
+
name: "parent",
|
|
510
|
+
binding: "always",
|
|
511
|
+
label: "the parent workflow",
|
|
512
|
+
description: "The parent instance's GDR URI, `null` on a root instance."
|
|
513
|
+
}, {
|
|
514
|
+
name: "ancestors",
|
|
515
|
+
binding: "always",
|
|
516
|
+
label: "the workflow's ancestors",
|
|
517
|
+
description: "GDR URIs of the ancestor chain, root first."
|
|
518
|
+
}, {
|
|
519
|
+
name: "stage",
|
|
520
|
+
binding: "always",
|
|
521
|
+
label: "the current stage",
|
|
522
|
+
description: "The current stage's name."
|
|
523
|
+
}, {
|
|
524
|
+
name: "now",
|
|
525
|
+
binding: "always",
|
|
526
|
+
label: "the current time",
|
|
527
|
+
description: "The ISO clock reading shared by every condition in one evaluation pass, so they agree on the time."
|
|
528
|
+
}, {
|
|
529
|
+
name: "effects",
|
|
530
|
+
binding: "always",
|
|
531
|
+
label: "automation outputs",
|
|
532
|
+
description: "The `effectsContext` map: stable handler params seeded at start plus completed effects' outputs namespaced by effect name (`$effects['<effect>'].<output>`). Handler fuel — transition filters should read instance/lake state (or $effectStatus) instead."
|
|
533
|
+
}, {
|
|
534
|
+
name: "effectStatus",
|
|
535
|
+
binding: "always",
|
|
536
|
+
label: "automation status",
|
|
537
|
+
description: "Effect name → `'done'` | `'failed'` of the latest completed run queued during the current stage entry; absent until the effect drains for this entry. Re-entry-safe: runs queued under a prior entry never count, so `$effectStatus['<effect>'] == 'done'` (or `defined($effectStatus['<effect>'])` for settled-either-way) waits for the fresh run."
|
|
538
|
+
}, {
|
|
539
|
+
name: "activities",
|
|
540
|
+
binding: "always",
|
|
541
|
+
label: "this stage's activities",
|
|
542
|
+
description: "The current stage's activity rows, statuses included."
|
|
543
|
+
}, {
|
|
544
|
+
name: "allActivitiesDone",
|
|
545
|
+
binding: "always",
|
|
546
|
+
label: "all activities in this stage are finished",
|
|
547
|
+
description: "Every current-stage activity is `done` or `skipped` — the default transition gate."
|
|
548
|
+
}, {
|
|
549
|
+
name: "anyActivityFailed",
|
|
550
|
+
binding: "always",
|
|
551
|
+
label: "an activity in this stage has failed",
|
|
552
|
+
description: "Some current-stage activity is `failed`."
|
|
553
|
+
}, {
|
|
554
|
+
name: "actor",
|
|
555
|
+
binding: "caller",
|
|
556
|
+
label: "you",
|
|
557
|
+
description: "The acting identity (id + roles); `undefined` when no caller rides the evaluation. Never holds a value in the cascade gates (deploy-rejected there)."
|
|
558
|
+
}, {
|
|
559
|
+
name: "assigned",
|
|
560
|
+
binding: "caller",
|
|
561
|
+
label: "you are assigned to this activity",
|
|
562
|
+
description: "Whether the caller matches the activity's assignees-kind field entry (by user id, or by a role under the definition's `roleAliases`); `false` outside an activity context. Constant `false` in the cascade gates (deploy-rejected there)."
|
|
563
|
+
}, {
|
|
564
|
+
name: "can",
|
|
565
|
+
binding: "caller",
|
|
566
|
+
label: "your permissions",
|
|
567
|
+
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 (action filters, activity requirements, editability predicates) and the fireAction/editField commit gates. Deploy rejects it at every site that evaluates without grants: transition filters, activity `filter`/`completeWhen`/`failWhen`, effect bindings, where-op `where`s, and the subworkflow `forEach`/`with`/`context` sites."
|
|
568
|
+
}, {
|
|
569
|
+
name: "row",
|
|
570
|
+
binding: "spawn",
|
|
571
|
+
label: "the spawned row",
|
|
572
|
+
description: "One `subworkflows.forEach` result row, bound while its `with` map evaluates — and the row under test while a where-op `where` evaluates (per row)."
|
|
573
|
+
}, {
|
|
574
|
+
name: "params",
|
|
575
|
+
binding: "caller",
|
|
576
|
+
label: "the action's arguments",
|
|
577
|
+
description: "The firing action's args — they hold values only while that action's effect bindings and where-op `where`s evaluate (activity/transition boundaries bind `{}`; subworkflow sites don't bind it at all). Deploy rejects a read in the cascade gates and in the caller-bound projection (action filters, requirements, editable predicates): args exist only once the caller fires the action, after those sites have evaluated."
|
|
578
|
+
}, {
|
|
579
|
+
name: "subworkflows",
|
|
580
|
+
binding: "always",
|
|
581
|
+
label: "the spawned subworkflows",
|
|
582
|
+
description: "Every row of the instance's subworkflow registry, faceted by `activity`/`definition`/`rowKey`/`status` (`'active'|'done'|'aborted'`) with `current` marking the open stage entry's cohort and `stage` the child's current stage. Usable anywhere — transition filters, requirements, any stage's gates; the implicit spawn gate is `count($subworkflows[activity == <name> && current && status == 'active']) == 0`."
|
|
583
|
+
} ], RESERVED_CONDITION_VARS = CONDITION_VARS.map(v2 => v2.name), FILTER_SCOPE_VARS = CONDITION_VARS.filter(v2 => v2.binding === "always").map(v2 => v2.name), CALLER_BOUND_VARS = CONDITION_VARS.filter(v2 => v2.binding === "caller").map(v2 => v2.name), GUARD_PREDICATE_VARS = [ {
|
|
584
|
+
name: "guard",
|
|
585
|
+
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
586
|
+
}, {
|
|
587
|
+
name: "mutation",
|
|
588
|
+
description: "The attempted mutation — `mutation.action` is the write kind being gated."
|
|
589
|
+
} ], ACTOR_KINDS = [ "person", "agent", "system" ];
|
|
590
|
+
|
|
591
|
+
function releaseDocId(releaseName) {
|
|
592
|
+
return `_.releases.${releaseName}`;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function releaseRef({res: res, releaseName: releaseName}) {
|
|
596
|
+
if (releaseName.length === 0) throw new ContractViolationError("releaseRef: releaseName must be a non-empty release name");
|
|
597
|
+
return {
|
|
598
|
+
id: gdrFromResource(res, releaseDocId(releaseName)),
|
|
599
|
+
type: "system.release",
|
|
600
|
+
releaseName: releaseName
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function isTodoListItem(row) {
|
|
605
|
+
if (typeof row != "object" || row === null) return !1;
|
|
606
|
+
const candidate = row, status = candidate.status;
|
|
607
|
+
return typeof candidate._key == "string" && typeof candidate.label == "string" && (status == null || typeof status == "string");
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function declaredRowColumns(entry) {
|
|
611
|
+
if (("_type" in entry ? entry._type : entry.type) === "array") return new Set((("of" in entry ? entry.of : void 0) ?? []).map(shape => shape.name));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function isTodoListEntry(entry) {
|
|
615
|
+
const columns = declaredRowColumns(entry);
|
|
616
|
+
return columns !== void 0 && columns.has("label") && columns.has("status");
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function isNotesEntry(entry) {
|
|
620
|
+
const columns = declaredRowColumns(entry);
|
|
621
|
+
return columns !== void 0 && columns.has("body") && columns.has("actor") && columns.has("at");
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
class FieldValueShapeError extends WorkflowError {
|
|
625
|
+
entryType;
|
|
626
|
+
entryName;
|
|
627
|
+
issues;
|
|
628
|
+
constructor(args) {
|
|
629
|
+
const issueText = args.issues.join("; ");
|
|
630
|
+
super("field-value-shape", `Field entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`),
|
|
631
|
+
this.name = "FieldValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName,
|
|
632
|
+
this.issues = args.issues;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const GdrShape = v__namespace.looseObject({
|
|
637
|
+
id: v__namespace.pipe(v__namespace.string(), v__namespace.check(s => isGdrUri(s), "must be a GDR URI")),
|
|
638
|
+
type: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
639
|
+
}), ReleaseRefShape = v__namespace.pipe(v__namespace.looseObject({
|
|
640
|
+
id: v__namespace.pipe(v__namespace.string(), v__namespace.check(s => isGdrUri(s), "must be a GDR URI")),
|
|
641
|
+
type: v__namespace.literal("system.release"),
|
|
642
|
+
releaseName: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
643
|
+
}), v__namespace.check(ref => !isGdrUri(ref.id) || extractDocumentId(ref.id) === releaseDocId(ref.releaseName), "id must point at the `_.releases.<releaseName>` doc named by releaseName")), ActorShape = v__namespace.looseObject({
|
|
644
|
+
kind: v__namespace.picklist(ACTOR_KINDS),
|
|
645
|
+
id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1)),
|
|
646
|
+
roles: v__namespace.optional(v__namespace.array(v__namespace.string())),
|
|
647
|
+
onBehalfOf: v__namespace.optional(v__namespace.string())
|
|
648
|
+
}), AssigneeShape = v__namespace.union([ v__namespace.looseObject({
|
|
649
|
+
type: v__namespace.literal("user"),
|
|
650
|
+
id: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
651
|
+
}), v__namespace.looseObject({
|
|
652
|
+
type: v__namespace.literal("role"),
|
|
653
|
+
role: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
654
|
+
}) ]), NullableString = v__namespace.union([ v__namespace.null(), v__namespace.string() ]), NullableNumber = v__namespace.union([ v__namespace.null(), v__namespace.number() ]), NullableBoolean = v__namespace.union([ v__namespace.null(), v__namespace.boolean() ]), NullableDateTime = v__namespace.union([ v__namespace.null(), v__namespace.pipe(v__namespace.string(), v__namespace.check(s => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")) ]), NullableDate = v__namespace.union([ v__namespace.null(), v__namespace.pipe(v__namespace.string(), v__namespace.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date")) ]), NullableUrl = NullableString, valueSchemas = {
|
|
655
|
+
"doc.ref": v__namespace.union([ v__namespace.null(), GdrShape ]),
|
|
656
|
+
"doc.refs": v__namespace.array(GdrShape),
|
|
657
|
+
"release.ref": v__namespace.union([ v__namespace.null(), ReleaseRefShape ]),
|
|
658
|
+
query: v__namespace.any(),
|
|
659
|
+
string: NullableString,
|
|
660
|
+
text: NullableString,
|
|
661
|
+
number: NullableNumber,
|
|
662
|
+
boolean: NullableBoolean,
|
|
663
|
+
date: NullableDate,
|
|
664
|
+
datetime: NullableDateTime,
|
|
665
|
+
url: NullableUrl,
|
|
666
|
+
actor: v__namespace.union([ v__namespace.null(), ActorShape ]),
|
|
667
|
+
assignee: v__namespace.union([ v__namespace.null(), AssigneeShape ]),
|
|
668
|
+
assignees: v__namespace.array(AssigneeShape)
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
function shapeValueSchema(shape, leaf) {
|
|
672
|
+
return shape.type === "object" ? objectSchema(shape.fields ?? [], leaf) : shape.type === "array" ? v__namespace.array(objectSchema(shape.of ?? [], leaf)) : leaf[shape.type] ?? v__namespace.any();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function objectSchema(fields, leaf) {
|
|
676
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
677
|
+
for (const f of fields) entries[f.name] = v__namespace.optional(shapeValueSchema(f, leaf));
|
|
678
|
+
return v__namespace.looseObject(entries);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function wholeValueSchema(args) {
|
|
682
|
+
const {entryType: entryType, shape: shape, leaf: leaf} = args;
|
|
683
|
+
return entryType === "object" ? v__namespace.union([ v__namespace.null(), objectSchema(shape.fields ?? [], leaf) ]) : entryType === "array" ? v__namespace.array(objectSchema(shape.of ?? [], leaf)) : leaf[entryType];
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function appendItemSchema(entryType, shape) {
|
|
687
|
+
if (entryType === "array") return objectSchema(shape.of ?? [], valueSchemas);
|
|
688
|
+
if (entryType === "doc.refs") return GdrShape;
|
|
689
|
+
if (entryType === "assignees") return AssigneeShape;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function rejectedRefTypes(args) {
|
|
693
|
+
const {entryType: entryType, types: types, value: value} = args;
|
|
694
|
+
if (types === void 0 || value === null || value === void 0) return [];
|
|
695
|
+
if (entryType !== "doc.ref" && entryType !== "doc.refs") return [];
|
|
696
|
+
let items = [ value ];
|
|
697
|
+
return entryType === "doc.refs" && (items = Array.isArray(value) ? value : []),
|
|
698
|
+
[ ...new Set(items.map(gdrTypeOf).filter(t => t !== void 0 && !types.includes(t))) ];
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function refTypeIssues(args) {
|
|
702
|
+
const rejected = rejectedRefTypes(args);
|
|
703
|
+
if (rejected.length === 0) return;
|
|
704
|
+
const accepts = (args.types ?? []).map(t => `"${t}"`).join(", ");
|
|
705
|
+
return rejected.map(t => `document type "${t}" is not accepted — this entry accepts ${accepts} (a GDR's \`type\` names the target document's schema type)`);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function gdrTypeOf(item) {
|
|
709
|
+
if (typeof item != "object" || item === null) return;
|
|
710
|
+
const t = item.type;
|
|
711
|
+
return typeof t == "string" ? t : void 0;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function checkFieldValue(args) {
|
|
715
|
+
return checkValueAgainst(args, valueSchemas);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function checkValueAgainst(args, leaf) {
|
|
719
|
+
const schema = wholeValueSchema({
|
|
720
|
+
entryType: args.entryType,
|
|
721
|
+
shape: args,
|
|
722
|
+
leaf: leaf
|
|
723
|
+
});
|
|
724
|
+
if (schema === void 0) return [ `unknown field entry type ${args.entryType}` ];
|
|
725
|
+
const result = v__namespace.safeParse(schema, args.value);
|
|
726
|
+
return result.success ? refTypeIssues({
|
|
727
|
+
entryType: args.entryType,
|
|
728
|
+
types: args.types,
|
|
729
|
+
value: args.value
|
|
730
|
+
}) : formatIssues(result.issues);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function validateFieldValue(args) {
|
|
734
|
+
const issues = checkFieldValue(args);
|
|
735
|
+
if (issues !== void 0) throw new FieldValueShapeError({
|
|
736
|
+
entryType: args.entryType,
|
|
737
|
+
entryName: args.entryName,
|
|
738
|
+
issues: issues,
|
|
739
|
+
mode: "value"
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const BARE_ID_SOURCE = "[A-Za-z0-9_][A-Za-z0-9._-]*", BARE_ID_RE = new RegExp(`^${BARE_ID_SOURCE}$`), ALIAS_REF_RE = new RegExp(`^@${RESOURCE_ALIAS_NAME_SOURCE}:${BARE_ID_SOURCE}$`);
|
|
744
|
+
|
|
745
|
+
function isAuthoringRefId(id) {
|
|
746
|
+
return id.startsWith("@") ? ALIAS_REF_RE.test(id) : id.includes(":") ? isGdrUri(id) : BARE_ID_RE.test(id);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function isBareSeedId(id) {
|
|
750
|
+
return BARE_ID_RE.test(id);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const AuthoringRefId = v__namespace.pipe(v__namespace.string(), v__namespace.check(isAuthoringRefId, "must be a bare document id, a GDR URI, or a portable `@<alias>:<id>` reference")), AuthoringGdrShape = v__namespace.looseObject({
|
|
754
|
+
id: AuthoringRefId,
|
|
755
|
+
type: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
756
|
+
}), seedValueSchemas = {
|
|
757
|
+
...valueSchemas,
|
|
758
|
+
"doc.ref": v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
|
|
759
|
+
"doc.refs": v__namespace.array(AuthoringGdrShape),
|
|
760
|
+
"release.ref": v__namespace.union([ v__namespace.null(), v__namespace.looseObject({
|
|
761
|
+
id: AuthoringRefId,
|
|
762
|
+
type: v__namespace.literal("system.release"),
|
|
763
|
+
releaseName: v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1))
|
|
764
|
+
}) ])
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
function checkLiteralSeed(args) {
|
|
768
|
+
return args.value === null ? [ "a literal seed cannot be null — omit `initialValue` to start the field empty" ] : checkValueAgainst(args, seedValueSchemas);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function validateFieldAppendItem(args) {
|
|
772
|
+
const schema = appendItemSchema(args.entryType, args);
|
|
773
|
+
if (schema === void 0) throw new FieldValueShapeError({
|
|
774
|
+
entryType: args.entryType,
|
|
775
|
+
entryName: args.entryName,
|
|
776
|
+
issues: [ `field entry type ${args.entryType} does not support append` ],
|
|
777
|
+
mode: "item"
|
|
778
|
+
});
|
|
779
|
+
const result = v__namespace.safeParse(schema, args.item);
|
|
780
|
+
if (!result.success) throw new FieldValueShapeError({
|
|
781
|
+
entryType: args.entryType,
|
|
782
|
+
entryName: args.entryName,
|
|
783
|
+
issues: formatIssues(result.issues),
|
|
784
|
+
mode: "item"
|
|
785
|
+
});
|
|
786
|
+
const typeIssues = refTypeIssues({
|
|
787
|
+
entryType: args.entryType,
|
|
788
|
+
types: args.types,
|
|
789
|
+
value: [ args.item ]
|
|
790
|
+
});
|
|
791
|
+
if (typeIssues !== void 0) throw new FieldValueShapeError({
|
|
792
|
+
entryType: args.entryType,
|
|
793
|
+
entryName: args.entryName,
|
|
794
|
+
issues: typeIssues,
|
|
795
|
+
mode: "item"
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function formatIssues(issues) {
|
|
800
|
+
return issues.map(i => {
|
|
801
|
+
const keys = i.path?.map(p => p.key) ?? [];
|
|
802
|
+
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const ACTIVITY_STATUSES = [ "pending", "active", "done", "skipped", "failed" ], TERMINAL_ACTIVITY_STATUSES = [ "done", "skipped", "failed" ];
|
|
807
|
+
|
|
808
|
+
function isTerminalActivityStatus(status) {
|
|
809
|
+
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const 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" ], GROUP_KINDS = [ "core", "details" ], DRIVER_KINDS = [ "person", "agent", "service", "engine" ], NonEmpty = v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1, "must be a non-empty string")), PositiveInt = v__namespace.pipe(v__namespace.number(), v__namespace.integer(), v__namespace.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
813
|
+
|
|
814
|
+
function groqIdentifier(referencedAs) {
|
|
815
|
+
return v__namespace.pipe(v__namespace.string(), v__namespace.regex(GROQ_IDENTIFIER, `must be a GROQ-safe identifier (letters, digits, underscore; not starting with a digit) because it is referenced as ${referencedAs} in GROQ conditions`));
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function picklist(options) {
|
|
819
|
+
return v__namespace.picklist(options, `Invalid option: expected one of ${options.map(o => `"${o}"`).join("|")}`);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function pinned() {
|
|
823
|
+
return (schema, ..._exact) => schema;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const LiteralSchema = v__namespace.strictObject({
|
|
827
|
+
type: v__namespace.literal("literal"),
|
|
828
|
+
value: v__namespace.unknown()
|
|
829
|
+
}), FieldReadSchema = v__namespace.strictObject({
|
|
830
|
+
type: v__namespace.literal("fieldRead"),
|
|
831
|
+
scope: v__namespace.optional(v__namespace.union([ v__namespace.literal("workflow"), v__namespace.literal("stage") ])),
|
|
832
|
+
field: NonEmpty,
|
|
833
|
+
path: v__namespace.optional(v__namespace.string())
|
|
834
|
+
}), FieldSourceSchema = v__namespace.union([ v__namespace.strictObject({
|
|
835
|
+
type: v__namespace.literal("input")
|
|
836
|
+
}), v__namespace.strictObject({
|
|
837
|
+
type: v__namespace.literal("query"),
|
|
838
|
+
query: NonEmpty
|
|
839
|
+
}), LiteralSchema, FieldReadSchema ]), ValueExprSchema = v__namespace.lazy(() => v__namespace.union([ LiteralSchema, FieldReadSchema, v__namespace.strictObject({
|
|
840
|
+
type: v__namespace.literal("param"),
|
|
841
|
+
param: NonEmpty
|
|
842
|
+
}), v__namespace.strictObject({
|
|
843
|
+
type: v__namespace.literal("actor")
|
|
844
|
+
}), v__namespace.strictObject({
|
|
845
|
+
type: v__namespace.literal("now")
|
|
846
|
+
}), v__namespace.strictObject({
|
|
847
|
+
type: v__namespace.literal("self")
|
|
848
|
+
}), v__namespace.strictObject({
|
|
849
|
+
type: v__namespace.literal("stage")
|
|
850
|
+
}), v__namespace.strictObject({
|
|
851
|
+
type: v__namespace.literal("object"),
|
|
852
|
+
fields: v__namespace.record(NonEmpty, ValueExprSchema)
|
|
853
|
+
}) ])), StoredFieldRefSchema = v__namespace.strictObject({
|
|
854
|
+
scope: picklist(FIELD_SCOPES),
|
|
855
|
+
field: NonEmpty
|
|
856
|
+
}), AuthoringFieldRefSchema = v__namespace.strictObject({
|
|
857
|
+
scope: v__namespace.optional(picklist(FIELD_SCOPES)),
|
|
858
|
+
field: NonEmpty
|
|
859
|
+
}), HREF_SCHEMES = [ "http:", "https:" ];
|
|
860
|
+
|
|
861
|
+
function isHttpUrl(value) {
|
|
862
|
+
try {
|
|
863
|
+
return HREF_SCHEMES.includes(new URL(value).protocol);
|
|
864
|
+
} catch {
|
|
865
|
+
return !1;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const UrlString = v__namespace.pipe(v__namespace.string(), v__namespace.url("must be a valid URL"), v__namespace.check(isHttpUrl, "must be an http(s) URL"));
|
|
870
|
+
|
|
871
|
+
function manualTargetSchema(ref) {
|
|
872
|
+
return v__namespace.variant("type", [ v__namespace.strictObject({
|
|
873
|
+
type: v__namespace.literal("url"),
|
|
874
|
+
url: UrlString
|
|
875
|
+
}), v__namespace.strictObject({
|
|
876
|
+
type: v__namespace.literal("field"),
|
|
877
|
+
field: ref
|
|
878
|
+
}) ]);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const StoredManualTargetSchema = manualTargetSchema(StoredFieldRefSchema), AuthoringManualTargetSchema = manualTargetSchema(v__namespace.union([ NonEmpty, AuthoringFieldRefSchema ])), ConditionSchema = NonEmpty;
|
|
882
|
+
|
|
883
|
+
function opSchemas(targetSchema) {
|
|
884
|
+
return [ v__namespace.strictObject({
|
|
885
|
+
type: v__namespace.literal("field.set"),
|
|
886
|
+
target: targetSchema,
|
|
887
|
+
value: ValueExprSchema
|
|
888
|
+
}), v__namespace.strictObject({
|
|
889
|
+
type: v__namespace.literal("field.unset"),
|
|
890
|
+
target: targetSchema
|
|
891
|
+
}), v__namespace.strictObject({
|
|
892
|
+
type: v__namespace.literal("field.append"),
|
|
893
|
+
target: targetSchema,
|
|
894
|
+
value: ValueExprSchema
|
|
895
|
+
}), v__namespace.strictObject({
|
|
896
|
+
type: v__namespace.literal("field.updateWhere"),
|
|
897
|
+
target: targetSchema,
|
|
898
|
+
where: ConditionSchema,
|
|
899
|
+
value: ValueExprSchema
|
|
900
|
+
}), v__namespace.strictObject({
|
|
901
|
+
type: v__namespace.literal("field.removeWhere"),
|
|
902
|
+
target: targetSchema,
|
|
903
|
+
where: ConditionSchema
|
|
904
|
+
}) ];
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
const StoredFieldOpSchema = v__namespace.variant("type", [ ...opSchemas(StoredFieldRefSchema) ]), StoredOpSchema = v__namespace.variant("type", [ ...opSchemas(StoredFieldRefSchema), v__namespace.strictObject({
|
|
908
|
+
type: v__namespace.literal("status.set"),
|
|
909
|
+
activity: NonEmpty,
|
|
910
|
+
status: picklist(ACTIVITY_STATUSES)
|
|
911
|
+
}) ]), StoredTransitionOpSchema = StoredFieldOpSchema, AuditOpSchema = v__namespace.strictObject({
|
|
912
|
+
type: v__namespace.literal("audit"),
|
|
913
|
+
target: AuthoringFieldRefSchema,
|
|
914
|
+
value: ValueExprSchema,
|
|
915
|
+
stampFields: v__namespace.optional(v__namespace.strictObject({
|
|
916
|
+
actor: v__namespace.optional(NonEmpty),
|
|
917
|
+
at: v__namespace.optional(NonEmpty)
|
|
918
|
+
}))
|
|
919
|
+
}), AuthoringOpSchema = v__namespace.variant("type", [ ...opSchemas(AuthoringFieldRefSchema), v__namespace.strictObject({
|
|
920
|
+
type: v__namespace.literal("status.set"),
|
|
921
|
+
activity: v__namespace.optional(NonEmpty),
|
|
922
|
+
status: picklist(ACTIVITY_STATUSES)
|
|
923
|
+
}), AuditOpSchema ]), GroupName = v__namespace.pipe(v__namespace.string(), v__namespace.regex(GROQ_IDENTIFIER, "must be an identifier (letters, digits, underscore; not starting with a digit)")), GroupSchema = pinned()(v__namespace.strictObject({
|
|
924
|
+
name: GroupName,
|
|
925
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
926
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
927
|
+
kind: v__namespace.optional(picklist(GROUP_KINDS))
|
|
928
|
+
})), GroupNameList = v__namespace.pipe(v__namespace.array(GroupName), v__namespace.minLength(1, "name at least one group, or omit `group`"), v__namespace.check(names => new Set(names).size === names.length, "a group is listed more than once — list each group once")), StoredGroupMembershipSchema = GroupNameList, AuthoringGroupMembershipSchema = v__namespace.union([ GroupName, GroupNameList ]);
|
|
929
|
+
|
|
930
|
+
function groupMembershipNames(group) {
|
|
931
|
+
return group === void 0 ? [] : typeof group == "string" ? [ group ] : [ ...group ];
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const FIELD_VALUE_KINDS = [ "doc.ref", "doc.refs", "release.ref", "string", "text", "number", "boolean", "date", "datetime", "url", "actor", "assignee", "assignees", "object", "array" ], FieldValueKindSchema = picklist(FIELD_VALUE_KINDS), FieldKindSchema = picklist(FIELD_VALUE_KINDS), FieldEntryName = groqIdentifier("`$fields.<name>`");
|
|
935
|
+
|
|
936
|
+
function asShape(input) {
|
|
937
|
+
return typeof input == "object" && input !== null ? input : {};
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function compositeShapeOk(input) {
|
|
941
|
+
const shape = asShape(input);
|
|
942
|
+
return shape.type === "object" ? Array.isArray(shape.fields) && shape.fields.length > 0 && shape.of === void 0 : shape.type === "array" ? Array.isArray(shape.of) && shape.of.length > 0 && shape.fields === void 0 : shape.fields === void 0 && shape.of === void 0;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function compositeShapeMessage(input) {
|
|
946
|
+
const shape = asShape(input);
|
|
947
|
+
return shape.type === "object" ? shape.of !== void 0 ? "an `object` kind declares its sub-fields with `fields`, not `of`" : "an `object` kind needs a non-empty `fields` list of sub-field shapes" : shape.type === "array" ? shape.fields !== void 0 ? "an `array` kind declares its item shape with `of`, not `fields`" : "an `array` kind needs a non-empty `of` list of sub-field shapes" : `\`fields\` / \`of\` are only valid on the \`object\` / \`array\` kinds, not "${String(shape.type)}"`;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function duplicateSubfieldName(input) {
|
|
951
|
+
const shape = asShape(input);
|
|
952
|
+
let list = [];
|
|
953
|
+
Array.isArray(shape.fields) ? list = shape.fields : Array.isArray(shape.of) && (list = shape.of);
|
|
954
|
+
const seen = /* @__PURE__ */ new Set;
|
|
955
|
+
for (const item of list) {
|
|
956
|
+
const name = asShape(item).name;
|
|
957
|
+
if (typeof name == "string") {
|
|
958
|
+
if (seen.has(name)) return name;
|
|
959
|
+
seen.add(name);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function compositeChecked(entries) {
|
|
965
|
+
return v__namespace.pipe(v__namespace.strictObject(entries), v__namespace.check(input => compositeShapeOk(input), issue => compositeShapeMessage(issue.input)), v__namespace.check(input => duplicateSubfieldName(input) === void 0, issue => `duplicate sub-field name "${duplicateSubfieldName(issue.input)}" — sub-field names must be unique within \`fields\` / \`of\``));
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const FieldShapeSchema = v__namespace.lazy(() => compositeChecked({
|
|
969
|
+
type: FieldValueKindSchema,
|
|
970
|
+
name: FieldEntryName,
|
|
971
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
972
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
973
|
+
fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
974
|
+
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
975
|
+
})), StoredEditableSchema = v__namespace.union([ v__namespace.literal(!0), NonEmpty ]), AuthoringEditableSchema = v__namespace.union([ v__namespace.literal(!0), v__namespace.array(NonEmpty), NonEmpty ]);
|
|
976
|
+
|
|
977
|
+
function fieldBase(editable, group) {
|
|
978
|
+
return {
|
|
979
|
+
name: FieldEntryName,
|
|
980
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
981
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
982
|
+
group: v__namespace.optional(group),
|
|
983
|
+
required: v__namespace.optional(v__namespace.boolean()),
|
|
984
|
+
initialValue: v__namespace.optional(FieldSourceSchema),
|
|
985
|
+
editable: v__namespace.optional(editable)
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function fieldEntryFields(editable, group) {
|
|
990
|
+
return {
|
|
991
|
+
type: FieldKindSchema,
|
|
992
|
+
...fieldBase(editable, group),
|
|
993
|
+
types: v__namespace.optional(v__namespace.pipe(v__namespace.array(NonEmpty), v__namespace.minLength(1, "declare at least one accepted type, or omit `types` to accept any"))),
|
|
994
|
+
fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
995
|
+
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function literalSeedIssues(entry) {
|
|
1000
|
+
if (entry.initialValue?.type === "literal") return checkLiteralSeed({
|
|
1001
|
+
entryType: entry.type,
|
|
1002
|
+
value: entry.initialValue.value,
|
|
1003
|
+
types: entry.types,
|
|
1004
|
+
fields: entry.fields,
|
|
1005
|
+
of: entry.of
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function literalSeedCheck() {
|
|
1010
|
+
return v__namespace.check(entry => literalSeedIssues(entry) === void 0, issue => `initialValue literal does not fit the declared kind: ${(literalSeedIssues(issue.input) ?? []).join("; ")}`);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function refTypesCheck() {
|
|
1014
|
+
return v__namespace.check(entry => entry.types === void 0 || entry.type === "doc.ref" || entry.type === "doc.refs", issue => `\`types\` is only valid on \`doc.ref\` / \`doc.refs\` entries, not "${issue.input.type}"`);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const FieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields(StoredEditableSchema, StoredGroupMembershipSchema)), refTypesCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields(AuthoringEditableSchema, AuthoringGroupMembershipSchema)), refTypesCheck(), literalSeedCheck())), ClaimFieldSchema = pinned()(v__namespace.strictObject({
|
|
1018
|
+
type: v__namespace.literal("claim"),
|
|
1019
|
+
name: FieldEntryName,
|
|
1020
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1021
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1022
|
+
group: v__namespace.optional(AuthoringGroupMembershipSchema)
|
|
1023
|
+
}));
|
|
1024
|
+
|
|
1025
|
+
function listSugarFields(type) {
|
|
1026
|
+
return {
|
|
1027
|
+
type: v__namespace.literal(type),
|
|
1028
|
+
...fieldBase(AuthoringEditableSchema, AuthoringGroupMembershipSchema)
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("todoList"))), NotesFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("notes"))), AuthoringFieldEntrySchema = pinned()(v__namespace.union([ RawAuthoringFieldEntrySchema, ClaimFieldSchema, TodoListFieldSchema, NotesFieldSchema ])), EffectSchema = v__namespace.strictObject({
|
|
1033
|
+
name: NonEmpty,
|
|
1034
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1035
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1036
|
+
bindings: v__namespace.optional(v__namespace.record(v__namespace.string(), ConditionSchema)),
|
|
1037
|
+
input: v__namespace.optional(v__namespace.record(v__namespace.string(), v__namespace.unknown())),
|
|
1038
|
+
outputs: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
1039
|
+
}), ActionParamSchema = v__namespace.strictObject({
|
|
1040
|
+
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
1041
|
+
name: NonEmpty,
|
|
1042
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1043
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1044
|
+
required: v__namespace.optional(v__namespace.boolean())
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
function actionFields(op, group) {
|
|
1048
|
+
return {
|
|
1049
|
+
name: NonEmpty,
|
|
1050
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1051
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1052
|
+
group: v__namespace.optional(group),
|
|
1053
|
+
filter: v__namespace.optional(ConditionSchema),
|
|
1054
|
+
params: v__namespace.optional(v__namespace.array(ActionParamSchema)),
|
|
1055
|
+
ops: v__namespace.optional(v__namespace.array(op)),
|
|
1056
|
+
effects: v__namespace.optional(v__namespace.array(EffectSchema))
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const StoredActionSchema = pinned()(v__namespace.strictObject(actionFields(StoredOpSchema, StoredGroupMembershipSchema))), TerminalActivityStatus = picklist(TERMINAL_ACTIVITY_STATUSES), RawAuthoringActionSchema = pinned()(v__namespace.strictObject({
|
|
1061
|
+
...actionFields(AuthoringOpSchema, AuthoringGroupMembershipSchema),
|
|
1062
|
+
roles: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1063
|
+
status: v__namespace.optional(TerminalActivityStatus)
|
|
1064
|
+
})), ClaimActionSchema = pinned()(v__namespace.strictObject({
|
|
1065
|
+
type: v__namespace.literal("claim"),
|
|
1066
|
+
name: NonEmpty,
|
|
1067
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1068
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1069
|
+
group: v__namespace.optional(AuthoringGroupMembershipSchema),
|
|
1070
|
+
field: v__namespace.union([ NonEmpty, AuthoringFieldRefSchema ]),
|
|
1071
|
+
roles: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1072
|
+
filter: v__namespace.optional(ConditionSchema),
|
|
1073
|
+
params: v__namespace.optional(v__namespace.array(ActionParamSchema)),
|
|
1074
|
+
effects: v__namespace.optional(v__namespace.array(EffectSchema))
|
|
1075
|
+
})), AuthoringActionSchema = pinned()(v__namespace.union([ RawAuthoringActionSchema, ClaimActionSchema ])), DefinitionRefSchema = v__namespace.strictObject({
|
|
1076
|
+
name: NonEmpty,
|
|
1077
|
+
version: v__namespace.optional(v__namespace.union([ PositiveInt, v__namespace.literal("latest") ]))
|
|
1078
|
+
}), SubworkflowsSchema = v__namespace.strictObject({
|
|
1079
|
+
forEach: NonEmpty,
|
|
1080
|
+
definition: DefinitionRefSchema,
|
|
1081
|
+
with: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1082
|
+
context: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1083
|
+
onExit: v__namespace.optional(picklist([ "detach", "abort" ]))
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
function activityFields({field: field, action: action, op: op, target: target, group: group}) {
|
|
1087
|
+
return {
|
|
1088
|
+
name: NonEmpty,
|
|
1089
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1090
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1091
|
+
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
1092
|
+
group: v__namespace.optional(group),
|
|
1093
|
+
kind: v__namespace.optional(picklist(ACTIVITY_KINDS)),
|
|
1094
|
+
target: v__namespace.optional(target),
|
|
1095
|
+
activation: v__namespace.optional(picklist([ "auto", "manual" ])),
|
|
1096
|
+
filter: v__namespace.optional(ConditionSchema),
|
|
1097
|
+
requirements: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1098
|
+
completeWhen: v__namespace.optional(ConditionSchema),
|
|
1099
|
+
failWhen: v__namespace.optional(ConditionSchema),
|
|
1100
|
+
ops: v__namespace.optional(v__namespace.array(op)),
|
|
1101
|
+
effects: v__namespace.optional(v__namespace.array(EffectSchema)),
|
|
1102
|
+
actions: v__namespace.optional(v__namespace.array(action)),
|
|
1103
|
+
subworkflows: v__namespace.optional(SubworkflowsSchema),
|
|
1104
|
+
fields: v__namespace.optional(v__namespace.array(field))
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
const StoredActivitySchema = pinned()(v__namespace.strictObject(activityFields({
|
|
1109
|
+
field: FieldEntrySchema,
|
|
1110
|
+
action: StoredActionSchema,
|
|
1111
|
+
op: StoredOpSchema,
|
|
1112
|
+
target: StoredManualTargetSchema,
|
|
1113
|
+
group: StoredGroupMembershipSchema
|
|
1114
|
+
}))), AuthoringActivitySchema = pinned()(v__namespace.strictObject(activityFields({
|
|
1115
|
+
field: AuthoringFieldEntrySchema,
|
|
1116
|
+
action: AuthoringActionSchema,
|
|
1117
|
+
op: AuthoringOpSchema,
|
|
1118
|
+
target: AuthoringManualTargetSchema,
|
|
1119
|
+
group: AuthoringGroupMembershipSchema
|
|
1120
|
+
})));
|
|
1121
|
+
|
|
1122
|
+
function transitionFields(op, filter) {
|
|
1123
|
+
return {
|
|
1124
|
+
name: NonEmpty,
|
|
1125
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1126
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1127
|
+
to: NonEmpty,
|
|
1128
|
+
filter: filter,
|
|
1129
|
+
ops: v__namespace.optional(v__namespace.array(op)),
|
|
1130
|
+
effects: v__namespace.optional(v__namespace.array(EffectSchema))
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const StoredTransitionSchema = pinned()(v__namespace.strictObject(transitionFields(StoredTransitionOpSchema, ConditionSchema))), AuthoringTransitionOpSchema = v__namespace.variant("type", [ ...opSchemas(AuthoringFieldRefSchema), AuditOpSchema ]), AuthoringTransitionSchema = pinned()(v__namespace.strictObject(transitionFields(AuthoringTransitionOpSchema, v__namespace.optional(ConditionSchema)))), GuardActionSchema = picklist(MUTATION_GUARD_ACTIONS), GuardReadPath = v__namespace.pipe(NonEmpty, v__namespace.check(path => !/[\r\n\u2028\u2029]/.test(path), "a guard read path cannot contain a line break")), GuardReadSchema = v__namespace.variant("type", [ v__namespace.strictObject({
|
|
1135
|
+
type: v__namespace.literal("self")
|
|
1136
|
+
}), v__namespace.strictObject({
|
|
1137
|
+
type: v__namespace.literal("now")
|
|
1138
|
+
}), v__namespace.strictObject({
|
|
1139
|
+
type: v__namespace.literal("fieldRead"),
|
|
1140
|
+
field: FieldEntryName,
|
|
1141
|
+
path: v__namespace.optional(GuardReadPath)
|
|
1142
|
+
}), v__namespace.strictObject({
|
|
1143
|
+
type: v__namespace.literal("effectsRead"),
|
|
1144
|
+
effect: v__namespace.pipe(NonEmpty, v__namespace.check(name => !name.includes("'"), "an effect name cannot contain `'`")),
|
|
1145
|
+
path: v__namespace.optional(GuardReadPath)
|
|
1146
|
+
}) ]);
|
|
1147
|
+
|
|
1148
|
+
function guardMatchFields(read) {
|
|
1149
|
+
return {
|
|
1150
|
+
types: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1151
|
+
idRefs: v__namespace.optional(v__namespace.array(read)),
|
|
1152
|
+
idPatterns: v__namespace.optional(v__namespace.array(NonEmpty)),
|
|
1153
|
+
actions: v__namespace.pipe(v__namespace.array(GuardActionSchema), v__namespace.minLength(1, "a guard must match at least one action"))
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function guardFields(read) {
|
|
1158
|
+
return {
|
|
1159
|
+
name: NonEmpty,
|
|
1160
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1161
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1162
|
+
match: v__namespace.strictObject(guardMatchFields(read)),
|
|
1163
|
+
predicate: v__namespace.optional(v__namespace.string()),
|
|
1164
|
+
metadata: v__namespace.optional(v__namespace.record(NonEmpty, read))
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const GuardSchema = v__namespace.strictObject(guardFields(NonEmpty)), AuthoringGuardSchema = v__namespace.strictObject(guardFields(GuardReadSchema));
|
|
1169
|
+
|
|
1170
|
+
function stageFields({field: field, activity: activity, transition: transition, guard: guard, editable: editable}) {
|
|
1171
|
+
return {
|
|
1172
|
+
name: NonEmpty,
|
|
1173
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
1174
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1175
|
+
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
1176
|
+
activities: v__namespace.optional(v__namespace.array(activity)),
|
|
1177
|
+
transitions: v__namespace.optional(v__namespace.array(transition)),
|
|
1178
|
+
guards: v__namespace.optional(v__namespace.array(guard)),
|
|
1179
|
+
fields: v__namespace.optional(v__namespace.array(field)),
|
|
1180
|
+
editable: v__namespace.optional(v__namespace.record(FieldEntryName, editable))
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const StoredStageSchema = pinned()(v__namespace.strictObject(stageFields({
|
|
1185
|
+
field: FieldEntrySchema,
|
|
1186
|
+
activity: StoredActivitySchema,
|
|
1187
|
+
transition: StoredTransitionSchema,
|
|
1188
|
+
guard: GuardSchema,
|
|
1189
|
+
editable: StoredEditableSchema
|
|
1190
|
+
}))), AuthoringStageSchema = pinned()(v__namespace.strictObject(stageFields({
|
|
1191
|
+
field: AuthoringFieldEntrySchema,
|
|
1192
|
+
activity: AuthoringActivitySchema,
|
|
1193
|
+
transition: AuthoringTransitionSchema,
|
|
1194
|
+
guard: AuthoringGuardSchema,
|
|
1195
|
+
editable: AuthoringEditableSchema
|
|
1196
|
+
}))), RoleAliasesSchema = v__namespace.record(NonEmpty, v__namespace.pipe(v__namespace.array(NonEmpty), v__namespace.minLength(1, "a role alias must list at least one fulfilling role"))), WORKFLOW_LIFECYCLES = [ "standalone", "child" ];
|
|
1197
|
+
|
|
1198
|
+
function workflowFields(field, stage) {
|
|
1199
|
+
return {
|
|
1200
|
+
name: NonEmpty,
|
|
1201
|
+
title: NonEmpty,
|
|
1202
|
+
description: v__namespace.optional(v__namespace.string()),
|
|
1203
|
+
groups: v__namespace.optional(v__namespace.array(GroupSchema)),
|
|
1204
|
+
lifecycle: v__namespace.optional(picklist(WORKFLOW_LIFECYCLES)),
|
|
1205
|
+
applicableWhen: v__namespace.optional(ConditionSchema),
|
|
1206
|
+
initialStage: NonEmpty,
|
|
1207
|
+
fields: v__namespace.optional(v__namespace.array(field)),
|
|
1208
|
+
stages: v__namespace.pipe(v__namespace.array(stage), v__namespace.minLength(1, "must declare at least one stage")),
|
|
1209
|
+
predicates: v__namespace.optional(v__namespace.record(groqIdentifier("`$<name>`"), ConditionSchema)),
|
|
1210
|
+
roleAliases: v__namespace.optional(RoleAliasesSchema)
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const WorkflowDefinitionSchema = pinned()(v__namespace.strictObject(workflowFields(FieldEntrySchema, StoredStageSchema)));
|
|
1215
|
+
|
|
1216
|
+
function parseStoredDefinition(input, label) {
|
|
1217
|
+
return parseOrThrow({
|
|
1218
|
+
schema: WorkflowDefinitionSchema,
|
|
1219
|
+
input: input,
|
|
1220
|
+
label: label
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const AuthoringWorkflowSchema = pinned()(v__namespace.strictObject(workflowFields(AuthoringFieldEntrySchema, AuthoringStageSchema))), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
|
|
1225
|
+
|
|
1226
|
+
function isStartableDefinition(definition) {
|
|
1227
|
+
return definition.lifecycle !== "child";
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
function formatValidationError(label, issues) {
|
|
1231
|
+
const lines = issues.map(issue => ` - ${issue.path.length === 0 ? "(root)" : formatIssuePath(issue.path)}: ${issue.message}`);
|
|
1232
|
+
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function issuesFromValibot(issues) {
|
|
1236
|
+
return issues.map(issue => ({
|
|
1237
|
+
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
1238
|
+
message: issue.message
|
|
1239
|
+
}));
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
function formatIssuePath(path) {
|
|
1243
|
+
let out = "";
|
|
1244
|
+
for (const seg of path) typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
1245
|
+
return out;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
function parseOrThrow({schema: schema, input: input, label: label}) {
|
|
1249
|
+
const result = v__namespace.safeParse(schema, input);
|
|
1250
|
+
if (!result.success) throw new Error(formatValidationError(label, issuesFromValibot(result.issues)));
|
|
1251
|
+
return result.output;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function labelFor(fn, value) {
|
|
1255
|
+
if (value && typeof value == "object" && "name" in value) {
|
|
1256
|
+
const raw = value.name;
|
|
1257
|
+
if (typeof raw == "string") return `${fn}("${raw}")`;
|
|
1258
|
+
}
|
|
1259
|
+
return fn;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function knownList(ids) {
|
|
1263
|
+
const quoted = [ ...ids ].map(s => `"${s}"`);
|
|
1264
|
+
return quoted.length > 0 ? quoted.join(", ") : "(none)";
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function checkDuplicates({names: names, what: what, issues: issues}) {
|
|
1268
|
+
const seen = /* @__PURE__ */ new Set;
|
|
1269
|
+
for (const {name: name, path: path} of names) seen.has(name) && issues.push({
|
|
1270
|
+
path: path,
|
|
1271
|
+
message: `duplicate ${what} "${name}" (already declared earlier)`
|
|
1272
|
+
}), seen.add(name);
|
|
1273
|
+
return seen;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
function checkStages(def, issues) {
|
|
1277
|
+
const stageNames = checkDuplicates({
|
|
1278
|
+
names: def.stages.map((stage, i) => ({
|
|
1279
|
+
name: stage.name,
|
|
1280
|
+
path: [ "stages", i, "name" ]
|
|
1281
|
+
})),
|
|
1282
|
+
what: "stage name",
|
|
1283
|
+
issues: issues
|
|
1284
|
+
});
|
|
1285
|
+
for (const [i, stage] of def.stages.entries()) {
|
|
1286
|
+
const activityNames = checkDuplicates({
|
|
1287
|
+
names: (stage.activities ?? []).map((activity, j) => ({
|
|
1288
|
+
name: activity.name,
|
|
1289
|
+
path: [ "stages", i, "activities", j, "name" ]
|
|
1290
|
+
})),
|
|
1291
|
+
what: `activity name in stage "${stage.name}"`,
|
|
1292
|
+
issues: issues
|
|
1293
|
+
});
|
|
1294
|
+
checkDuplicates({
|
|
1295
|
+
names: (stage.transitions ?? []).map((t, k) => ({
|
|
1296
|
+
name: t.name,
|
|
1297
|
+
path: [ "stages", i, "transitions", k, "name" ]
|
|
1298
|
+
})),
|
|
1299
|
+
what: `transition name in stage "${stage.name}"`,
|
|
1300
|
+
issues: issues
|
|
1301
|
+
}), checkActivities({
|
|
1302
|
+
def: def,
|
|
1303
|
+
i: i,
|
|
1304
|
+
activityNames: activityNames,
|
|
1305
|
+
issues: issues
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
return stageNames;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function checkActivities({def: def, i: i, activityNames: activityNames, issues: issues}) {
|
|
1312
|
+
const stage = def.stages[i];
|
|
1313
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
1314
|
+
const path = [ "stages", i, "activities", j ];
|
|
1315
|
+
checkDuplicates({
|
|
1316
|
+
names: (activity.actions ?? []).map((action, a) => ({
|
|
1317
|
+
name: action.name,
|
|
1318
|
+
path: [ ...path, "actions", a, "name" ]
|
|
1319
|
+
})),
|
|
1320
|
+
what: `action name in activity "${activity.name}"`,
|
|
1321
|
+
issues: issues
|
|
1322
|
+
}), checkStatusSetTargets({
|
|
1323
|
+
activity: activity,
|
|
1324
|
+
activityNames: activityNames,
|
|
1325
|
+
path: path,
|
|
1326
|
+
issues: issues
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function checkStatusSetTargets({activity: activity, activityNames: activityNames, path: path, issues: issues}) {
|
|
1332
|
+
checkStatusSetOps({
|
|
1333
|
+
ops: activity.ops,
|
|
1334
|
+
activityNames: activityNames,
|
|
1335
|
+
path: [ ...path, "ops" ],
|
|
1336
|
+
issues: issues
|
|
1337
|
+
});
|
|
1338
|
+
for (const [a, action] of (activity.actions ?? []).entries()) checkStatusSetOps({
|
|
1339
|
+
ops: action.ops,
|
|
1340
|
+
activityNames: activityNames,
|
|
1341
|
+
path: [ ...path, "actions", a, "ops" ],
|
|
1342
|
+
issues: issues
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function checkStatusSetOps({ops: ops, activityNames: activityNames, path: path, issues: issues}) {
|
|
1347
|
+
for (const [o, op] of (ops ?? []).entries()) op.type !== "status.set" || activityNames.has(op.activity) || issues.push({
|
|
1348
|
+
path: [ ...path, o, "activity" ],
|
|
1349
|
+
message: `status.set targets activity "${op.activity}" which is not declared in this stage. Known activities: ${knownList(activityNames)}`
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
function checkInitialStage({def: def, stageNames: stageNames, issues: issues}) {
|
|
1354
|
+
stageNames.has(def.initialStage) || issues.push({
|
|
1355
|
+
path: [ "initialStage" ],
|
|
1356
|
+
message: `initialStage "${def.initialStage}" is not a declared stage. Known stages: ${knownList(stageNames)}`
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
function checkTransitionTargets({def: def, stageNames: stageNames, issues: issues}) {
|
|
1361
|
+
for (const [i, stage] of def.stages.entries()) for (const [k, t] of (stage.transitions ?? []).entries()) stageNames.has(t.to) || issues.push({
|
|
1362
|
+
path: [ "stages", i, "transitions", k, "to" ],
|
|
1363
|
+
message: `transition target "${t.to}" is not a declared stage. Known stages: ${knownList(stageNames)}`
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
function checkStageReachability({def: def, stageNames: stageNames, issues: issues}) {
|
|
1368
|
+
if (!stageNames.has(def.initialStage)) return;
|
|
1369
|
+
const stagesByName = /* @__PURE__ */ new Map;
|
|
1370
|
+
for (const stage of def.stages) stagesByName.has(stage.name) || stagesByName.set(stage.name, stage);
|
|
1371
|
+
const reached = /* @__PURE__ */ new Set([ def.initialStage ]), frontier = [ def.initialStage ];
|
|
1372
|
+
for (;frontier.length > 0; ) {
|
|
1373
|
+
const stage = stagesByName.get(frontier.pop());
|
|
1374
|
+
for (const t of stage?.transitions ?? []) reached.has(t.to) || !stageNames.has(t.to) || (reached.add(t.to),
|
|
1375
|
+
frontier.push(t.to));
|
|
1376
|
+
}
|
|
1377
|
+
for (const [i, stage] of def.stages.entries()) reached.has(stage.name) || issues.push({
|
|
1378
|
+
path: [ "stages", i, "name" ],
|
|
1379
|
+
message: `stage "${stage.name}" is unreachable — no transition path leads from initialStage "${def.initialStage}" to it, so no instance can ever enter it. Add a transition targeting it, or remove the stage`
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
function effectNameSites(def) {
|
|
1384
|
+
const sites = [];
|
|
1385
|
+
for (const [i, stage] of def.stages.entries()) {
|
|
1386
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
1387
|
+
collectEffects({
|
|
1388
|
+
effects: activity.effects,
|
|
1389
|
+
path: [ "stages", i, "activities", j, "effects" ],
|
|
1390
|
+
sites: sites
|
|
1391
|
+
});
|
|
1392
|
+
for (const [a, action] of (activity.actions ?? []).entries()) collectEffects({
|
|
1393
|
+
effects: action.effects,
|
|
1394
|
+
path: [ "stages", i, "activities", j, "actions", a, "effects" ],
|
|
1395
|
+
sites: sites
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
for (const [k, t] of (stage.transitions ?? []).entries()) collectEffects({
|
|
1399
|
+
effects: t.effects,
|
|
1400
|
+
path: [ "stages", i, "transitions", k, "effects" ],
|
|
1401
|
+
sites: sites
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
return sites;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
function checkEffectNames(def, issues) {
|
|
1408
|
+
checkDuplicates({
|
|
1409
|
+
names: effectNameSites(def),
|
|
1410
|
+
what: "effect name (registry key — unique per definition)",
|
|
1411
|
+
issues: issues
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function collectEffects({effects: effects, path: path, sites: sites}) {
|
|
1416
|
+
for (const [e, effect] of (effects ?? []).entries()) sites.push({
|
|
1417
|
+
name: effect.name,
|
|
1418
|
+
path: [ ...path, e, "name" ]
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function checkGuardNames(def, issues) {
|
|
1423
|
+
const sites = def.stages.flatMap((stage, i) => (stage.guards ?? []).map((guard, g) => ({
|
|
1424
|
+
name: guard.name,
|
|
1425
|
+
path: [ "stages", i, "guards", g, "name" ]
|
|
1426
|
+
})));
|
|
1427
|
+
checkDuplicates({
|
|
1428
|
+
names: sites,
|
|
1429
|
+
what: "guard name (the guard lake _id derives from it — unique per definition)",
|
|
1430
|
+
issues: issues
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function fieldScopes(def) {
|
|
1435
|
+
const scopes = [ {
|
|
1436
|
+
entries: def.fields,
|
|
1437
|
+
scope: "workflow",
|
|
1438
|
+
path: [ "fields" ],
|
|
1439
|
+
label: "workflow fields"
|
|
1440
|
+
} ];
|
|
1441
|
+
for (const [i, stage] of def.stages.entries()) {
|
|
1442
|
+
scopes.push({
|
|
1443
|
+
entries: stage.fields,
|
|
1444
|
+
scope: "stage",
|
|
1445
|
+
path: [ "stages", i, "fields" ],
|
|
1446
|
+
label: `stage "${stage.name}" fields`
|
|
1447
|
+
});
|
|
1448
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) scopes.push({
|
|
1449
|
+
entries: activity.fields,
|
|
1450
|
+
scope: "activity",
|
|
1451
|
+
path: [ "stages", i, "activities", j, "fields" ],
|
|
1452
|
+
label: `activity "${activity.name}" fields`
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
return scopes;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function checkRequiredField(def, issues) {
|
|
1459
|
+
for (const {entries: entries, scope: scope, path: path, label: label} of fieldScopes(def)) for (const [n, entry] of (entries ?? []).entries()) if (entry.required === !0) {
|
|
1460
|
+
if (scope !== "workflow") issues.push({
|
|
1461
|
+
path: [ ...path, n, "required" ],
|
|
1462
|
+
message: `${label} entry "${entry.name}" is \`required\`, but \`required\` applies only to workflow-scope \`input\` entries — only those are caller-supplied at start/spawn`
|
|
1463
|
+
}); else if (entry.initialValue?.type !== "input") {
|
|
1464
|
+
const seed = entry.initialValue?.type ?? "working memory (no initialValue)";
|
|
1465
|
+
issues.push({
|
|
1466
|
+
path: [ ...path, n, "required" ],
|
|
1467
|
+
message: `field entry "${entry.name}" has initialValue "${seed}" — \`required\` applies only to \`input\`-sourced entries (the caller fills them at start/spawn)`
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function checkFieldEntryNames(def, issues) {
|
|
1474
|
+
for (const {entries: entries, path: path, label: label} of fieldScopes(def)) checkDuplicates({
|
|
1475
|
+
names: (entries ?? []).map((entry, n) => ({
|
|
1476
|
+
name: entry.name,
|
|
1477
|
+
path: [ ...path, n, "name" ]
|
|
1478
|
+
})),
|
|
1479
|
+
what: `field entry name in ${label}`,
|
|
1480
|
+
issues: issues
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function checkPredicates(def, issues) {
|
|
1485
|
+
const reserved = new Set(RESERVED_CONDITION_VARS), names = Object.keys(def.predicates ?? {});
|
|
1486
|
+
for (const name of names) reserved.has(name) && issues.push({
|
|
1487
|
+
path: [ "predicates", name ],
|
|
1488
|
+
message: `predicate "${name}" shadows the built-in $${name} — pick another name`
|
|
1489
|
+
});
|
|
1490
|
+
for (const [name, groq] of Object.entries(def.predicates ?? {})) checkPredicateBody({
|
|
1491
|
+
name: name,
|
|
1492
|
+
groq: groq,
|
|
1493
|
+
names: names,
|
|
1494
|
+
issues: issues
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
function checkPredicateBody({name: name, groq: groq, names: names, issues: issues}) {
|
|
1499
|
+
const reads = conditionParameterNames(groq);
|
|
1500
|
+
for (const caller of CALLER_BOUND_VARS) reads.has(caller) && issues.push({
|
|
1501
|
+
path: [ "predicates", name ],
|
|
1502
|
+
message: `predicate "${name}" reads $${caller} — predicates are instance-level (pre-evaluated once, without a caller); compose caller gates at the condition site instead`
|
|
1503
|
+
});
|
|
1504
|
+
for (const other of names) other === name || !reads.has(other) || issues.push({
|
|
1505
|
+
path: [ "predicates", name ],
|
|
1506
|
+
message: `predicate "${name}" references predicate $${other} — predicates may read built-in vars only (no cross-references; compose at the condition site instead)`
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
function entryNames(entries) {
|
|
1511
|
+
return new Set((entries ?? []).map(entry => entry.name));
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
function checkUnboundCallerVars(def, issues) {
|
|
1515
|
+
for (const site of conditionSites(def)) {
|
|
1516
|
+
const reads = conditionParameterNames(site.groq);
|
|
1517
|
+
for (const name of unboundCallerVars(site.policy)) reads.has(name) && issues.push({
|
|
1518
|
+
path: site.path,
|
|
1519
|
+
message: callerVarMessage(site, name)
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
function unboundCallerVars(policy) {
|
|
1525
|
+
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : [ "can" ];
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
function callerVarMessage(site, name) {
|
|
1529
|
+
return site.policy === "cascade" ? `${site.label} reads $${name} — the cascade evaluates transition filters and activity gates (filter/completeWhen/failWhen) with no caller bound ($assigned is constant false, the other caller vars hold no value); gate on instance state (e.g. a field an action wrote) instead` : site.policy === "caller-bound" ? `${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` : `${site.label} reads $can — $can (the caller's grants) is bound only in the caller-bound projection (action filters, requirements, editable predicates) and never holds a value in cascade conditions; move the check to one of those sites or drop $can`;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
function conditionSites(def) {
|
|
1533
|
+
const sites = [], workflowLayer = {
|
|
1534
|
+
scope: "workflow",
|
|
1535
|
+
names: entryNames(def.fields)
|
|
1536
|
+
};
|
|
1537
|
+
collectEditableSites({
|
|
1538
|
+
entries: def.fields,
|
|
1539
|
+
path: [ "fields" ],
|
|
1540
|
+
labelPrefix: "workflow",
|
|
1541
|
+
fields: "context-dependent",
|
|
1542
|
+
sites: sites
|
|
1543
|
+
});
|
|
1544
|
+
for (const [i, stage] of def.stages.entries()) collectStageConditionSites({
|
|
1545
|
+
stage: stage,
|
|
1546
|
+
i: i,
|
|
1547
|
+
workflowLayer: workflowLayer,
|
|
1548
|
+
sites: sites
|
|
1549
|
+
});
|
|
1550
|
+
return sites;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflowLayer, sites: sites}) {
|
|
1554
|
+
const stageLayer = {
|
|
1555
|
+
scope: "stage",
|
|
1556
|
+
names: entryNames(stage.fields)
|
|
1557
|
+
}, stageFields2 = [ stageLayer, workflowLayer ], editableWindow = [ stageLayer, workflowLayer, ...(stage.activities ?? []).map(activity => ({
|
|
1558
|
+
scope: "activity",
|
|
1559
|
+
names: entryNames(activity.fields)
|
|
1560
|
+
})) ];
|
|
1561
|
+
for (const [k, t] of (stage.transitions ?? []).entries()) sites.push({
|
|
1562
|
+
groq: t.filter,
|
|
1563
|
+
path: [ "stages", i, "transitions", k, "filter" ],
|
|
1564
|
+
label: `transition "${t.name}" filter`,
|
|
1565
|
+
policy: "cascade",
|
|
1566
|
+
fields: stageFields2
|
|
1567
|
+
}), collectBindingSites({
|
|
1568
|
+
effects: t.effects,
|
|
1569
|
+
path: [ "stages", i, "transitions", k, "effects" ],
|
|
1570
|
+
label: `transition "${t.name}"`,
|
|
1571
|
+
fields: stageFields2,
|
|
1572
|
+
sites: sites
|
|
1573
|
+
}), collectOpWhereSites({
|
|
1574
|
+
ops: t.ops,
|
|
1575
|
+
path: [ "stages", i, "transitions", k, "ops" ],
|
|
1576
|
+
label: `transition "${t.name}"`,
|
|
1577
|
+
fields: stageFields2,
|
|
1578
|
+
sites: sites
|
|
1579
|
+
});
|
|
1580
|
+
collectEditableSites({
|
|
1581
|
+
entries: stage.fields,
|
|
1582
|
+
path: [ "stages", i, "fields" ],
|
|
1583
|
+
labelPrefix: `stage "${stage.name}"`,
|
|
1584
|
+
fields: editableWindow,
|
|
1585
|
+
sites: sites
|
|
1586
|
+
});
|
|
1587
|
+
for (const [name, editable] of Object.entries(stage.editable ?? {})) typeof editable == "string" && sites.push({
|
|
1588
|
+
groq: editable,
|
|
1589
|
+
path: [ "stages", i, "editable", name ],
|
|
1590
|
+
label: `stage "${stage.name}" editable override "${name}"`,
|
|
1591
|
+
policy: "caller-bound",
|
|
1592
|
+
fields: editableWindow
|
|
1593
|
+
});
|
|
1594
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) collectActivityConditionSites({
|
|
1595
|
+
activity: activity,
|
|
1596
|
+
path: [ "stages", i, "activities", j ],
|
|
1597
|
+
stageFields: stageFields2,
|
|
1598
|
+
sites: sites
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
function collectEditableSites({entries: entries, path: path, labelPrefix: labelPrefix, fields: fields, sites: sites}) {
|
|
1603
|
+
for (const [n, entry] of (entries ?? []).entries()) typeof entry.editable == "string" && sites.push({
|
|
1604
|
+
groq: entry.editable,
|
|
1605
|
+
path: [ ...path, n, "editable" ],
|
|
1606
|
+
label: `${labelPrefix} field "${entry.name}" editable`,
|
|
1607
|
+
policy: "caller-bound",
|
|
1608
|
+
fields: fields
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function collectOpWhereSites({ops: ops, path: path, label: label, fields: fields, sites: sites}) {
|
|
1613
|
+
for (const [o, op] of (ops ?? []).entries()) op.where !== void 0 && sites.push({
|
|
1614
|
+
groq: op.where,
|
|
1615
|
+
path: [ ...path, o, "where" ],
|
|
1616
|
+
label: `${label} ${op.type} where`,
|
|
1617
|
+
fields: fields
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
function collectActivityConditionSites({activity: activity, path: path, stageFields: stageFields2, sites: sites}) {
|
|
1622
|
+
const fields = [ {
|
|
1623
|
+
scope: "activity",
|
|
1624
|
+
names: entryNames(activity.fields)
|
|
1625
|
+
}, ...stageFields2 ];
|
|
1626
|
+
for (const gate of [ "filter", "completeWhen", "failWhen" ]) {
|
|
1627
|
+
const groq = activity[gate];
|
|
1628
|
+
groq !== void 0 && sites.push({
|
|
1629
|
+
groq: groq,
|
|
1630
|
+
path: [ ...path, gate ],
|
|
1631
|
+
label: `activity "${activity.name}".${gate}`,
|
|
1632
|
+
policy: "cascade",
|
|
1633
|
+
fields: fields
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
for (const [name, groq] of Object.entries(activity.requirements ?? {})) sites.push({
|
|
1637
|
+
groq: groq,
|
|
1638
|
+
path: [ ...path, "requirements", name ],
|
|
1639
|
+
label: `activity "${activity.name}" requirement "${name}"`,
|
|
1640
|
+
policy: "caller-bound",
|
|
1641
|
+
fields: fields
|
|
1642
|
+
});
|
|
1643
|
+
collectBindingSites({
|
|
1644
|
+
effects: activity.effects,
|
|
1645
|
+
path: [ ...path, "effects" ],
|
|
1646
|
+
label: `activity "${activity.name}"`,
|
|
1647
|
+
fields: fields,
|
|
1648
|
+
sites: sites
|
|
1649
|
+
}), collectOpWhereSites({
|
|
1650
|
+
ops: activity.ops,
|
|
1651
|
+
path: [ ...path, "ops" ],
|
|
1652
|
+
label: `activity "${activity.name}"`,
|
|
1653
|
+
fields: fields,
|
|
1654
|
+
sites: sites
|
|
1655
|
+
}), collectEditableSites({
|
|
1656
|
+
entries: activity.fields,
|
|
1657
|
+
path: [ ...path, "fields" ],
|
|
1658
|
+
labelPrefix: `activity "${activity.name}"`,
|
|
1659
|
+
fields: fields,
|
|
1660
|
+
sites: sites
|
|
1661
|
+
}), collectActionConditionSites({
|
|
1662
|
+
activity: activity,
|
|
1663
|
+
path: path,
|
|
1664
|
+
fields: fields,
|
|
1665
|
+
sites: sites
|
|
1666
|
+
}), collectSubworkflowSites({
|
|
1667
|
+
activity: activity,
|
|
1668
|
+
path: path,
|
|
1669
|
+
fields: fields,
|
|
1670
|
+
sites: sites
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
function collectActionConditionSites({activity: activity, path: path, fields: fields, sites: sites}) {
|
|
1675
|
+
for (const [a, action] of (activity.actions ?? []).entries()) action.filter !== void 0 && sites.push({
|
|
1676
|
+
groq: action.filter,
|
|
1677
|
+
path: [ ...path, "actions", a, "filter" ],
|
|
1678
|
+
label: `action "${action.name}" filter`,
|
|
1679
|
+
policy: "caller-bound",
|
|
1680
|
+
fields: fields
|
|
1681
|
+
}), collectBindingSites({
|
|
1682
|
+
effects: action.effects,
|
|
1683
|
+
path: [ ...path, "actions", a, "effects" ],
|
|
1684
|
+
label: `action "${action.name}"`,
|
|
1685
|
+
fields: fields,
|
|
1686
|
+
sites: sites
|
|
1687
|
+
}), collectOpWhereSites({
|
|
1688
|
+
ops: action.ops,
|
|
1689
|
+
path: [ ...path, "actions", a, "ops" ],
|
|
1690
|
+
label: `action "${action.name}"`,
|
|
1691
|
+
fields: fields,
|
|
1692
|
+
sites: sites
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
function collectSubworkflowSites({activity: activity, path: path, fields: fields, sites: sites}) {
|
|
1697
|
+
const sub = activity.subworkflows;
|
|
1698
|
+
if (sub === void 0) return;
|
|
1699
|
+
const subPath = [ ...path, "subworkflows" ];
|
|
1700
|
+
sites.push({
|
|
1701
|
+
groq: sub.forEach,
|
|
1702
|
+
path: [ ...subPath, "forEach" ],
|
|
1703
|
+
label: `activity "${activity.name}".subworkflows.forEach`,
|
|
1704
|
+
fields: fields
|
|
1705
|
+
});
|
|
1706
|
+
for (const [group, record] of [ [ "with", sub.with ], [ "context", sub.context ] ]) for (const [key, groq] of Object.entries(record ?? {})) sites.push({
|
|
1707
|
+
groq: groq,
|
|
1708
|
+
path: [ ...subPath, group, key ],
|
|
1709
|
+
label: `activity "${activity.name}".subworkflows.${group} "${key}"`,
|
|
1710
|
+
fields: fields
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
function collectBindingSites({effects: effects, path: path, label: label, fields: fields, sites: sites}) {
|
|
1715
|
+
for (const [e, effect] of (effects ?? []).entries()) for (const [key, groq] of Object.entries(effect.bindings ?? {})) sites.push({
|
|
1716
|
+
groq: groq,
|
|
1717
|
+
path: [ ...path, e, "bindings", key ],
|
|
1718
|
+
label: `${label} effect "${effect.name}" binding "${key}"`,
|
|
1719
|
+
fields: fields
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
function checkAssigneesEntries(def, issues) {
|
|
1724
|
+
for (const {entries: entries, path: path} of fieldScopes(def)) (entries ?? []).filter(e => e.type === "assignees").length <= 1 || issues.push({
|
|
1725
|
+
path: path,
|
|
1726
|
+
message: "at most one assignees-kind field entry per scope — the inbox reads it by kind"
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
function activityShape(activity) {
|
|
1731
|
+
return {
|
|
1732
|
+
hasActions: (activity.actions ?? []).length > 0,
|
|
1733
|
+
hasEffects: (activity.effects ?? []).length > 0,
|
|
1734
|
+
hasCompleteWhen: activity.completeWhen !== void 0,
|
|
1735
|
+
hasSubworkflows: activity.subworkflows !== void 0
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
const KIND_SHAPE_RULES = {
|
|
1740
|
+
user: s => s.hasActions ? [] : [ "needs at least one action for a person to fire" ],
|
|
1741
|
+
service: s => s.hasEffects ? [] : [ "needs at least one effect — the automated work it performs" ],
|
|
1742
|
+
manual: s => [ s.hasActions || s.hasCompleteWhen ? void 0 : "needs a way to complete — an action to acknowledge it, or a `completeWhen` predicate to verify it (otherwise it auto-resolves on activation)", s.hasSubworkflows ? "is off-system work, not a fan-out — drop `subworkflows` or move it to its own activity" : void 0 ].filter(m => m !== void 0),
|
|
1743
|
+
receive: s => [ s.hasCompleteWhen || s.hasSubworkflows ? void 0 : "needs a `completeWhen` (or `subworkflows`) condition to wait on", s.hasActions ? "has no actor, so it cannot carry actions" : void 0 ].filter(m => m !== void 0),
|
|
1744
|
+
script: s => [ s.hasActions ? "runs inline with no actor, so it cannot carry actions" : void 0, s.hasCompleteWhen ? "resolves on activation, so it cannot carry a `completeWhen` (that is a `receive`/`service` wait)" : void 0, s.hasSubworkflows ? "resolves on activation, so it cannot fan out with `subworkflows`" : void 0 ].filter(m => m !== void 0)
|
|
1745
|
+
};
|
|
1746
|
+
|
|
1747
|
+
function checkActivityKinds(def, issues) {
|
|
1748
|
+
for (const [i, stage] of def.stages.entries()) for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
1749
|
+
const path = [ "stages", i, "activities", j ];
|
|
1750
|
+
if (activity.target !== void 0 && activity.kind !== "manual" && issues.push({
|
|
1751
|
+
path: [ ...path, "target" ],
|
|
1752
|
+
message: `activity "${activity.name}" has a \`target\` but is not \`kind: "manual"\` — a target is the deep-link for off-system manual work`
|
|
1753
|
+
}), activity.kind !== void 0) for (const fault of KIND_SHAPE_RULES[activity.kind](activityShape(activity))) issues.push({
|
|
1754
|
+
path: [ ...path, "kind" ],
|
|
1755
|
+
message: `activity "${activity.name}" declares kind "${activity.kind}" but ${fault}`
|
|
1756
|
+
});
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
function checkGroups(def, issues) {
|
|
1761
|
+
const workflowGroups = checkGroupDeclarations({
|
|
1762
|
+
groups: def.groups,
|
|
1763
|
+
path: [ "groups" ],
|
|
1764
|
+
what: "group name",
|
|
1765
|
+
issues: issues
|
|
1766
|
+
});
|
|
1767
|
+
checkFieldMemberships({
|
|
1768
|
+
entries: def.fields,
|
|
1769
|
+
reachable: [ workflowGroups ],
|
|
1770
|
+
path: [ "fields" ],
|
|
1771
|
+
label: "workflow fields",
|
|
1772
|
+
issues: issues
|
|
1773
|
+
});
|
|
1774
|
+
for (const [i, stage] of def.stages.entries()) checkStageGroups({
|
|
1775
|
+
stage: stage,
|
|
1776
|
+
i: i,
|
|
1777
|
+
workflowGroups: workflowGroups,
|
|
1778
|
+
issues: issues
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
function checkGroupDeclarations({groups: groups, path: path, what: what, issues: issues}) {
|
|
1783
|
+
return checkDuplicates({
|
|
1784
|
+
names: (groups ?? []).map((group, n) => ({
|
|
1785
|
+
name: group.name,
|
|
1786
|
+
path: [ ...path, n, "name" ]
|
|
1787
|
+
})),
|
|
1788
|
+
what: what,
|
|
1789
|
+
issues: issues
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
function checkStageGroups({stage: stage, i: i, workflowGroups: workflowGroups, issues: issues}) {
|
|
1794
|
+
const path = [ "stages", i ], enclosing = [ checkGroupDeclarations({
|
|
1795
|
+
groups: stage.groups,
|
|
1796
|
+
path: [ ...path, "groups" ],
|
|
1797
|
+
what: `group name in stage "${stage.name}"`,
|
|
1798
|
+
issues: issues
|
|
1799
|
+
}), workflowGroups ];
|
|
1800
|
+
checkFieldMemberships({
|
|
1801
|
+
entries: stage.fields,
|
|
1802
|
+
reachable: enclosing,
|
|
1803
|
+
path: [ ...path, "fields" ],
|
|
1804
|
+
label: `stage "${stage.name}" fields`,
|
|
1805
|
+
issues: issues
|
|
1806
|
+
});
|
|
1807
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) checkActivityGroups({
|
|
1808
|
+
activity: activity,
|
|
1809
|
+
path: [ ...path, "activities", j ],
|
|
1810
|
+
enclosing: enclosing,
|
|
1811
|
+
issues: issues
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
function checkActivityGroups({activity: activity, path: path, enclosing: enclosing, issues: issues}) {
|
|
1816
|
+
checkGroupMembership({
|
|
1817
|
+
group: activity.group,
|
|
1818
|
+
reachable: enclosing,
|
|
1819
|
+
path: [ ...path, "group" ],
|
|
1820
|
+
label: `activity "${activity.name}"`,
|
|
1821
|
+
issues: issues
|
|
1822
|
+
});
|
|
1823
|
+
const inner = [ checkGroupDeclarations({
|
|
1824
|
+
groups: activity.groups,
|
|
1825
|
+
path: [ ...path, "groups" ],
|
|
1826
|
+
what: `group name in activity "${activity.name}"`,
|
|
1827
|
+
issues: issues
|
|
1828
|
+
}), ...enclosing ];
|
|
1829
|
+
checkFieldMemberships({
|
|
1830
|
+
entries: activity.fields,
|
|
1831
|
+
reachable: inner,
|
|
1832
|
+
path: [ ...path, "fields" ],
|
|
1833
|
+
label: `activity "${activity.name}" fields`,
|
|
1834
|
+
issues: issues
|
|
1835
|
+
});
|
|
1836
|
+
for (const [a, action] of (activity.actions ?? []).entries()) checkGroupMembership({
|
|
1837
|
+
group: action.group,
|
|
1838
|
+
reachable: inner,
|
|
1839
|
+
path: [ ...path, "actions", a, "group" ],
|
|
1840
|
+
label: `action "${action.name}"`,
|
|
1841
|
+
issues: issues
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
function checkFieldMemberships({entries: entries, reachable: reachable, path: path, label: label, issues: issues}) {
|
|
1846
|
+
for (const [n, entry] of (entries ?? []).entries()) checkGroupMembership({
|
|
1847
|
+
group: entry.group,
|
|
1848
|
+
reachable: reachable,
|
|
1849
|
+
path: [ ...path, n, "group" ],
|
|
1850
|
+
label: `${label} entry "${entry.name}"`,
|
|
1851
|
+
issues: issues
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
function checkGroupMembership({group: group, reachable: reachable, path: path, label: label, issues: issues}) {
|
|
1856
|
+
for (const name of groupMembershipNames(group)) {
|
|
1857
|
+
if (reachable.some(names => names.has(name))) continue;
|
|
1858
|
+
const declared = new Set(reachable.flatMap(names => [ ...names ]));
|
|
1859
|
+
issues.push({
|
|
1860
|
+
path: path,
|
|
1861
|
+
message: `${label} names group "${name}", which is not declared on its enclosing chain. Declared: ${knownList(declared)}`
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
function checkConditionFieldReads(def, issues) {
|
|
1867
|
+
const everywhere = allDeclaredFieldNames(def);
|
|
1868
|
+
for (const site of conditionSites(def)) for (const name of conditionFieldReadNames(site.groq)) fieldVisibleAtSite({
|
|
1869
|
+
site: site,
|
|
1870
|
+
name: name,
|
|
1871
|
+
everywhere: everywhere
|
|
1872
|
+
}) || issues.push({
|
|
1873
|
+
path: site.path,
|
|
1874
|
+
message: undeclaredFieldReadMessage(site, name)
|
|
1875
|
+
});
|
|
1876
|
+
for (const [name, groq] of Object.entries(def.predicates ?? {})) for (const read of conditionFieldReadNames(groq)) everywhere.has(read) || issues.push({
|
|
1877
|
+
path: [ "predicates", name ],
|
|
1878
|
+
message: noFieldAnywhereMessage(`predicate "${name}"`, read)
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
function allDeclaredFieldNames(def) {
|
|
1883
|
+
const names = /* @__PURE__ */ new Set;
|
|
1884
|
+
for (const {entries: entries} of fieldScopes(def)) for (const entry of entries ?? []) names.add(entry.name);
|
|
1885
|
+
return names;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
function fieldVisibleAtSite({site: site, name: name, everywhere: everywhere}) {
|
|
1889
|
+
return site.fields === "context-dependent" ? everywhere.has(name) : site.fields.some(layer => layer.names.has(name));
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
function noFieldAnywhereMessage(label, name) {
|
|
1893
|
+
return `${label} reads $fields.${name}, but no field entry named "${name}" is declared anywhere in the definition — the read evaluates to GROQ null, so the condition silently fails closed`;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
function undeclaredFieldReadMessage(site, name) {
|
|
1897
|
+
if (site.fields === "context-dependent") return noFieldAnywhereMessage(site.label, name);
|
|
1898
|
+
const visible = site.fields.flatMap(layer => [ ...layer.names ].map(n => `${layer.scope}:${n}`));
|
|
1899
|
+
return `${site.label} reads $fields.${name}, but no field entry named "${name}" is lexically visible here — the read evaluates to GROQ null, so the condition silently fails closed. Visible: ${visible.join(", ") || "(none)"}`;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
function checkFieldReadSeeds(def, issues) {
|
|
1903
|
+
const workflowEntries = def.fields ?? [];
|
|
1904
|
+
for (const {entries: entries, scope: scope, path: path, label: label} of fieldScopes(def)) for (const [n, entry] of (entries ?? []).entries()) {
|
|
1905
|
+
const read = entry.initialValue;
|
|
1906
|
+
read?.type === "fieldRead" && checkSeedRead({
|
|
1907
|
+
read: read,
|
|
1908
|
+
where: `${label} entry "${entry.name}"`,
|
|
1909
|
+
entryName: entry.name,
|
|
1910
|
+
scope: scope,
|
|
1911
|
+
siblings: entries ?? [],
|
|
1912
|
+
index: n,
|
|
1913
|
+
workflowEntries: workflowEntries,
|
|
1914
|
+
path: [ ...path, n, "initialValue" ],
|
|
1915
|
+
issues: issues
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
function checkSeedRead(args) {
|
|
1921
|
+
const {read: read, where: where, path: path, issues: issues} = args, misspelled = seedScopeSpellingIssue(args);
|
|
1922
|
+
if (misspelled !== void 0) {
|
|
1923
|
+
issues.push({
|
|
1924
|
+
path: path,
|
|
1925
|
+
message: `${where} ${misspelled}`
|
|
1926
|
+
});
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
const target = read.scope === "workflow" ? seedWorkflowTarget(args) : seedEarlierSiblingTarget(args);
|
|
1930
|
+
target !== void 0 && pushFieldReadPathIssue({
|
|
1931
|
+
where: where,
|
|
1932
|
+
read: read,
|
|
1933
|
+
target: target,
|
|
1934
|
+
path: path,
|
|
1935
|
+
issues: issues
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
function seedScopeSpellingIssue({read: read, scope: scope}) {
|
|
1940
|
+
if (read.scope === "workflow" && scope === "workflow") return 'seeds from a fieldRead with scope "workflow", but workflow-scope seeds resolve against earlier same-scope siblings only (no workflow layer exists yet at start/spawn) — omit `scope`';
|
|
1941
|
+
if (read.scope === "stage") {
|
|
1942
|
+
if (scope === "workflow") return 'seeds from a fieldRead with scope "stage", but a workflow-scope entry has no stage in scope — omit `scope` to read an earlier workflow-scope sibling';
|
|
1943
|
+
if (scope === "activity") return 'seeds from a fieldRead with scope "stage", but a seed resolves non-workflow reads against the scope being seeded, so an activity-scope entry cannot read stage fields — omit `scope` to read an earlier activity-scope sibling, or use scope "workflow"';
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
function seedWorkflowTarget({read: read, where: where, workflowEntries: workflowEntries, path: path, issues: issues}) {
|
|
1948
|
+
const target = workflowEntries.find(entry => entry.name === read.field);
|
|
1949
|
+
if (target !== void 0) return target;
|
|
1950
|
+
issues.push({
|
|
1951
|
+
path: path,
|
|
1952
|
+
message: `${where} seeds from workflow-scope field "${read.field}", which is not declared — the read would resolve to the default value. Known workflow fields: ${knownList(workflowEntries.map(entry => entry.name))}`
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
function seedEarlierSiblingTarget(args) {
|
|
1957
|
+
const {read: read, where: where, entryName: entryName, scope: scope, siblings: siblings, index: index, workflowEntries: workflowEntries, path: path, issues: issues} = args, targetIndex = siblings.findIndex(entry => entry.name === read.field);
|
|
1958
|
+
if (targetIndex !== -1 && targetIndex < index) return siblings[targetIndex];
|
|
1959
|
+
if (targetIndex === index) {
|
|
1960
|
+
issues.push({
|
|
1961
|
+
path: path,
|
|
1962
|
+
message: `${where} seeds from itself — a fieldRead cannot read the entry it seeds`
|
|
1963
|
+
});
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
if (targetIndex > index) {
|
|
1967
|
+
issues.push({
|
|
1968
|
+
path: path,
|
|
1969
|
+
message: `${where} seeds from "${read.field}", which is declared later in the same scope — entries resolve in declaration order, so the read would see its default. Move "${read.field}" before "${entryName}"`
|
|
1970
|
+
});
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
const workflowHint = scope !== "workflow" && workflowEntries.some(entry => entry.name === read.field) ? ' — a workflow-scope entry of that name exists; add `scope: "workflow"` to read it (an omitted scope reads same-scope siblings only)' : "";
|
|
1974
|
+
issues.push({
|
|
1975
|
+
path: path,
|
|
1976
|
+
message: `${where} seeds from "${read.field}", which is not an earlier ${scope}-scope sibling — the read would resolve to the default value${workflowHint}. Earlier siblings: ${knownList(siblings.slice(0, index).map(entry => entry.name))}`
|
|
1977
|
+
});
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
function checkFieldReadOpValues(def, issues) {
|
|
1981
|
+
const workflowEntries = def.fields ?? [];
|
|
1982
|
+
for (const [i, stage] of def.stages.entries()) {
|
|
1983
|
+
const shared = {
|
|
1984
|
+
workflowEntries: workflowEntries,
|
|
1985
|
+
stageEntries: stage.fields ?? [],
|
|
1986
|
+
issues: issues
|
|
1987
|
+
};
|
|
1988
|
+
for (const [k, t] of (stage.transitions ?? []).entries()) checkOpsFieldReads({
|
|
1989
|
+
...shared,
|
|
1990
|
+
ops: t.ops,
|
|
1991
|
+
path: [ "stages", i, "transitions", k, "ops" ],
|
|
1992
|
+
label: `transition "${t.name}"`
|
|
1993
|
+
});
|
|
1994
|
+
for (const [j, activity] of (stage.activities ?? []).entries()) {
|
|
1995
|
+
const activityNames = entryNames(activity.fields), activityPath = [ "stages", i, "activities", j ];
|
|
1996
|
+
checkOpsFieldReads({
|
|
1997
|
+
...shared,
|
|
1998
|
+
ops: activity.ops,
|
|
1999
|
+
path: [ ...activityPath, "ops" ],
|
|
2000
|
+
label: `activity "${activity.name}"`,
|
|
2001
|
+
activityNames: activityNames
|
|
2002
|
+
});
|
|
2003
|
+
for (const [a, action] of (activity.actions ?? []).entries()) checkOpsFieldReads({
|
|
2004
|
+
...shared,
|
|
2005
|
+
ops: action.ops,
|
|
2006
|
+
path: [ ...activityPath, "actions", a, "ops" ],
|
|
2007
|
+
label: `action "${action.name}"`,
|
|
2008
|
+
activityNames: activityNames
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
function checkOpsFieldReads({ops: ops, path: path, label: label, ...ctx}) {
|
|
2015
|
+
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({
|
|
2016
|
+
...ctx,
|
|
2017
|
+
read: read,
|
|
2018
|
+
path: readPath,
|
|
2019
|
+
where: `${label} ${op.type} value`
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
function fieldReadsIn(value, path) {
|
|
2024
|
+
return value.type === "fieldRead" ? [ {
|
|
2025
|
+
read: value,
|
|
2026
|
+
path: path
|
|
2027
|
+
} ] : value.type !== "object" ? [] : Object.entries(value.fields).flatMap(([key, sub]) => fieldReadsIn(sub, [ ...path, "fields", key ]));
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
function checkOpFieldRead({read: read, where: where, path: path, workflowEntries: workflowEntries, stageEntries: stageEntries, activityNames: activityNames, issues: issues}) {
|
|
2031
|
+
const hosts = [ {
|
|
2032
|
+
scope: "stage",
|
|
2033
|
+
entries: stageEntries
|
|
2034
|
+
}, {
|
|
2035
|
+
scope: "workflow",
|
|
2036
|
+
entries: workflowEntries
|
|
2037
|
+
} ].filter(host => read.scope === void 0 || host.scope === read.scope), target = hosts.flatMap(host => host.entries).find(entry => entry.name === read.field);
|
|
2038
|
+
if (target === void 0) {
|
|
2039
|
+
issues.push({
|
|
2040
|
+
path: path,
|
|
2041
|
+
message: opFieldReadMissMessage({
|
|
2042
|
+
read: read,
|
|
2043
|
+
where: where,
|
|
2044
|
+
hosts: hosts,
|
|
2045
|
+
activityNames: activityNames
|
|
2046
|
+
})
|
|
2047
|
+
});
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
pushFieldReadPathIssue({
|
|
2051
|
+
where: where,
|
|
2052
|
+
read: read,
|
|
2053
|
+
target: target,
|
|
2054
|
+
path: path,
|
|
2055
|
+
issues: issues
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
function opFieldReadMissMessage({read: read, where: where, hosts: hosts, activityNames: activityNames}) {
|
|
2060
|
+
const searched = read.scope === void 0 ? "stage or workflow scope" : `${read.scope} scope`, activityHint = activityNames?.has(read.field) ? ` "${read.field}" is an activity-scope field, and op-time field reads resolve against stage and workflow scopes only — declare it at stage scope to read it from an op.` : "", known = hosts.flatMap(host => host.entries.map(entry => `${host.scope}:${entry.name}`));
|
|
2061
|
+
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.${activityHint} Known: ${known.join(", ") || "(none)"}`;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
function checkGuardFieldReads(def, issues) {
|
|
2065
|
+
const reads = {
|
|
2066
|
+
workflowEntries: def.fields ?? [],
|
|
2067
|
+
effectNames: new Set(effectNameSites(def).map(site => site.name)),
|
|
2068
|
+
issues: issues
|
|
2069
|
+
};
|
|
2070
|
+
for (const [i, stage] of def.stages.entries()) for (const [g, guard] of (stage.guards ?? []).entries()) {
|
|
2071
|
+
const where = `stage "${stage.name}" guard "${guard.name}"`, guardPath = [ "stages", i, "guards", g ];
|
|
2072
|
+
for (const [k, expr] of (guard.match.idRefs ?? []).entries()) checkGuardRead({
|
|
2073
|
+
...reads,
|
|
2074
|
+
expr: expr,
|
|
2075
|
+
where: `${where} match.idRefs`,
|
|
2076
|
+
path: [ ...guardPath, "match", "idRefs", k ]
|
|
2077
|
+
});
|
|
2078
|
+
for (const [key, expr] of Object.entries(guard.metadata ?? {})) checkGuardRead({
|
|
2079
|
+
...reads,
|
|
2080
|
+
expr: expr,
|
|
2081
|
+
where: `${where} metadata "${key}"`,
|
|
2082
|
+
path: [ ...guardPath, "metadata", key ]
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
function checkGuardRead(args) {
|
|
2088
|
+
const fieldRead = FIELD_READ.exec(args.expr);
|
|
2089
|
+
if (fieldRead !== null) {
|
|
2090
|
+
checkGuardFieldRead({
|
|
2091
|
+
...args,
|
|
2092
|
+
name: fieldRead[1],
|
|
2093
|
+
valuePath: fieldRead[2]
|
|
2094
|
+
});
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
const effectName = EFFECTS_READ.exec(args.expr)?.[1];
|
|
2098
|
+
effectName === void 0 || args.effectNames.has(effectName) || args.issues.push({
|
|
2099
|
+
path: args.path,
|
|
2100
|
+
message: `${args.where} reads "${args.expr}", but no effect named "${effectName}" is declared — the read resolves to undefined and the guard deploys with a hole. Known effects: ${knownList(args.effectNames)}`
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
function checkGuardFieldRead({name: name, valuePath: valuePath, where: where, path: path, workflowEntries: workflowEntries, issues: issues}) {
|
|
2105
|
+
const target = workflowEntries.find(entry => entry.name === name);
|
|
2106
|
+
if (target === void 0) {
|
|
2107
|
+
issues.push({
|
|
2108
|
+
path: path,
|
|
2109
|
+
message: `${where} reads "$fields.${name}", but "${name}" is not a workflow-scope field entry — guard reads resolve against workflow-scope fields only. Known workflow fields: ${knownList(workflowEntries.map(entry => entry.name))}`
|
|
2110
|
+
});
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
pushFieldReadPathIssue({
|
|
2114
|
+
where: where,
|
|
2115
|
+
read: {
|
|
2116
|
+
field: name,
|
|
2117
|
+
path: valuePath
|
|
2118
|
+
},
|
|
2119
|
+
target: target,
|
|
2120
|
+
path: path,
|
|
2121
|
+
issues: issues
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
function pushFieldReadPathIssue({where: where, read: read, target: target, path: path, issues: issues}) {
|
|
2126
|
+
if (read.path === void 0) return;
|
|
2127
|
+
const pathIssue = fieldValuePathIssue({
|
|
2128
|
+
target: target,
|
|
2129
|
+
path: read.path
|
|
2130
|
+
});
|
|
2131
|
+
pathIssue !== void 0 && issues.push({
|
|
2132
|
+
path: path,
|
|
2133
|
+
message: `${where} reads "${read.field}" with path "${read.path}", which does not fit the declared shape of "${target.name ?? read.field}" (${target.type}) — ${pathIssue}`
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
const SCALAR = {
|
|
2138
|
+
kind: "scalar"
|
|
2139
|
+
}, DOC_CONTENT = {
|
|
2140
|
+
kind: "opaque"
|
|
2141
|
+
}, GDR_VALUE = {
|
|
2142
|
+
kind: "record",
|
|
2143
|
+
keys: {
|
|
2144
|
+
id: SCALAR,
|
|
2145
|
+
type: SCALAR
|
|
2146
|
+
}
|
|
2147
|
+
}, RELEASE_VALUE = {
|
|
2148
|
+
kind: "record",
|
|
2149
|
+
keys: {
|
|
2150
|
+
id: SCALAR,
|
|
2151
|
+
type: SCALAR,
|
|
2152
|
+
releaseName: SCALAR
|
|
2153
|
+
}
|
|
2154
|
+
}, ACTOR_VALUE = {
|
|
2155
|
+
kind: "record",
|
|
2156
|
+
keys: {
|
|
2157
|
+
kind: SCALAR,
|
|
2158
|
+
id: SCALAR,
|
|
2159
|
+
roles: {
|
|
2160
|
+
kind: "list",
|
|
2161
|
+
item: SCALAR
|
|
2162
|
+
},
|
|
2163
|
+
onBehalfOf: SCALAR
|
|
2164
|
+
}
|
|
2165
|
+
}, ASSIGNEE_VALUE = {
|
|
2166
|
+
kind: "record",
|
|
2167
|
+
keys: {
|
|
2168
|
+
type: SCALAR,
|
|
2169
|
+
id: SCALAR,
|
|
2170
|
+
role: SCALAR
|
|
2171
|
+
}
|
|
2172
|
+
}, VALUE_NODES = {
|
|
2173
|
+
"doc.ref": () => DOC_CONTENT,
|
|
2174
|
+
"doc.refs": () => ({
|
|
2175
|
+
kind: "list",
|
|
2176
|
+
item: GDR_VALUE
|
|
2177
|
+
}),
|
|
2178
|
+
"release.ref": () => RELEASE_VALUE,
|
|
2179
|
+
string: () => SCALAR,
|
|
2180
|
+
text: () => SCALAR,
|
|
2181
|
+
number: () => SCALAR,
|
|
2182
|
+
boolean: () => SCALAR,
|
|
2183
|
+
date: () => SCALAR,
|
|
2184
|
+
datetime: () => SCALAR,
|
|
2185
|
+
url: () => SCALAR,
|
|
2186
|
+
actor: () => ACTOR_VALUE,
|
|
2187
|
+
assignee: () => ASSIGNEE_VALUE,
|
|
2188
|
+
assignees: () => ({
|
|
2189
|
+
kind: "list",
|
|
2190
|
+
item: ASSIGNEE_VALUE
|
|
2191
|
+
}),
|
|
2192
|
+
object: shape => ({
|
|
2193
|
+
kind: "object",
|
|
2194
|
+
fields: shape.fields ?? []
|
|
2195
|
+
}),
|
|
2196
|
+
array: shape => ({
|
|
2197
|
+
kind: "rows",
|
|
2198
|
+
of: shape.of ?? []
|
|
2199
|
+
})
|
|
2200
|
+
};
|
|
2201
|
+
|
|
2202
|
+
function valueNodeFor(shape) {
|
|
2203
|
+
return Object.hasOwn(VALUE_NODES, shape.type) ? VALUE_NODES[shape.type](shape) : void 0;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
const INDEX_SEGMENT = /^\d+$/;
|
|
2207
|
+
|
|
2208
|
+
function stepIntoValueNode(node, segment) {
|
|
2209
|
+
switch (node.kind) {
|
|
2210
|
+
case "scalar":
|
|
2211
|
+
return "the value is a scalar with no sub-paths";
|
|
2212
|
+
|
|
2213
|
+
case "object":
|
|
2214
|
+
{
|
|
2215
|
+
const sub = node.fields.find(f => f.name === segment), next = sub === void 0 ? void 0 : valueNodeFor(sub);
|
|
2216
|
+
return next !== void 0 ? next : `an object value has sub-fields ${knownList(node.fields.map(f => f.name))} — "${segment}" is not one`;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
case "rows":
|
|
2220
|
+
return INDEX_SEGMENT.test(segment) ? {
|
|
2221
|
+
kind: "object",
|
|
2222
|
+
fields: node.of
|
|
2223
|
+
} : `an array value is addressed by numeric row index first (e.g. "0.<subField>") — "${segment}" is not an index`;
|
|
2224
|
+
|
|
2225
|
+
case "list":
|
|
2226
|
+
return INDEX_SEGMENT.test(segment) ? node.item : `an array value is addressed by numeric item index (e.g. "0") — "${segment}" is not an index`;
|
|
2227
|
+
|
|
2228
|
+
case "record":
|
|
2229
|
+
return Object.hasOwn(node.keys, segment) ? node.keys[segment] : `the value carries ${knownList(Object.keys(node.keys))} — "${segment}" is not one of them`;
|
|
2230
|
+
|
|
2231
|
+
case "opaque":
|
|
2232
|
+
return node;
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
function fieldValuePathIssue({target: target, path: path}) {
|
|
2237
|
+
let node = valueNodeFor(target);
|
|
2238
|
+
if (node === void 0) return `"${String(target.type)}" is not a known field kind`;
|
|
2239
|
+
for (const segment of path.split(".")) {
|
|
2240
|
+
const next = stepIntoValueNode(node, segment);
|
|
2241
|
+
if (typeof next == "string") return `at segment "${segment}": ${next}`;
|
|
2242
|
+
node = next;
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
function checkWorkflowInvariants(def) {
|
|
2247
|
+
const issues = [], stageNames = checkStages(def, issues);
|
|
2248
|
+
return checkInitialStage({
|
|
2249
|
+
def: def,
|
|
2250
|
+
stageNames: stageNames,
|
|
2251
|
+
issues: issues
|
|
2252
|
+
}), checkTransitionTargets({
|
|
2253
|
+
def: def,
|
|
2254
|
+
stageNames: stageNames,
|
|
2255
|
+
issues: issues
|
|
2256
|
+
}), checkStageReachability({
|
|
2257
|
+
def: def,
|
|
2258
|
+
stageNames: stageNames,
|
|
2259
|
+
issues: issues
|
|
2260
|
+
}), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues),
|
|
2261
|
+
checkRequiredField(def, issues), checkPredicates(def, issues), checkUnboundCallerVars(def, issues),
|
|
2262
|
+
checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues), checkFieldReadOpValues(def, issues),
|
|
2263
|
+
checkGuardFieldReads(def, issues), checkAssigneesEntries(def, issues), checkActivityKinds(def, issues),
|
|
2264
|
+
checkGroups(def, issues), issues;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
exports.ACTIVITY_KINDS = ACTIVITY_KINDS;
|
|
2268
|
+
|
|
2269
|
+
exports.ACTOR_KINDS = ACTOR_KINDS;
|
|
2270
|
+
|
|
2271
|
+
exports.AuthoringActionSchema = AuthoringActionSchema;
|
|
2272
|
+
|
|
2273
|
+
exports.AuthoringActivitySchema = AuthoringActivitySchema;
|
|
2274
|
+
|
|
2275
|
+
exports.AuthoringFieldEntrySchema = AuthoringFieldEntrySchema;
|
|
2276
|
+
|
|
2277
|
+
exports.AuthoringGuardSchema = AuthoringGuardSchema;
|
|
2278
|
+
|
|
2279
|
+
exports.AuthoringOpSchema = AuthoringOpSchema;
|
|
2280
|
+
|
|
2281
|
+
exports.AuthoringStageSchema = AuthoringStageSchema;
|
|
2282
|
+
|
|
2283
|
+
exports.AuthoringTransitionSchema = AuthoringTransitionSchema;
|
|
2284
|
+
|
|
2285
|
+
exports.AuthoringWorkflowSchema = AuthoringWorkflowSchema;
|
|
2286
|
+
|
|
2287
|
+
exports.CALLER_BOUND_VARS = CALLER_BOUND_VARS;
|
|
2288
|
+
|
|
2289
|
+
exports.CONDITION_VARS = CONDITION_VARS;
|
|
2290
|
+
|
|
2291
|
+
exports.ContractViolationError = ContractViolationError;
|
|
2292
|
+
|
|
2293
|
+
exports.DOCUMENT_VALUE_PERMISSIONS = DOCUMENT_VALUE_PERMISSIONS;
|
|
2294
|
+
|
|
2295
|
+
exports.DRIVER_KINDS = DRIVER_KINDS;
|
|
2296
|
+
|
|
2297
|
+
exports.DefinitionInUseError = DefinitionInUseError;
|
|
2298
|
+
|
|
2299
|
+
exports.DefinitionNotFoundError = DefinitionNotFoundError;
|
|
2300
|
+
|
|
2301
|
+
exports.EFFECTS_READ = EFFECTS_READ;
|
|
2302
|
+
|
|
2303
|
+
exports.EffectNotFoundError = EffectNotFoundError;
|
|
2304
|
+
|
|
2305
|
+
exports.EffectSchema = EffectSchema;
|
|
2306
|
+
|
|
2307
|
+
exports.FIELD_READ = FIELD_READ;
|
|
2308
|
+
|
|
2309
|
+
exports.FILTER_SCOPE_VARS = FILTER_SCOPE_VARS;
|
|
2310
|
+
|
|
2311
|
+
exports.FieldValueShapeError = FieldValueShapeError;
|
|
2312
|
+
|
|
2313
|
+
exports.GROUP_KINDS = GROUP_KINDS;
|
|
2314
|
+
|
|
2315
|
+
exports.GUARD_PREDICATE_VARS = GUARD_PREDICATE_VARS;
|
|
2316
|
+
|
|
2317
|
+
exports.GroupSchema = GroupSchema;
|
|
2318
|
+
|
|
2319
|
+
exports.InstanceNotFoundError = InstanceNotFoundError;
|
|
2320
|
+
|
|
2321
|
+
exports.RESERVED_CONDITION_VARS = RESERVED_CONDITION_VARS;
|
|
2322
|
+
|
|
2323
|
+
exports.RESOURCE_ALIAS_NAME_SOURCE = RESOURCE_ALIAS_NAME_SOURCE;
|
|
2324
|
+
|
|
2325
|
+
exports.StoredFieldOpSchema = StoredFieldOpSchema;
|
|
2326
|
+
|
|
2327
|
+
exports.WORKFLOW_DEFINITION_TYPE = WORKFLOW_DEFINITION_TYPE;
|
|
2328
|
+
|
|
2329
|
+
exports.WorkflowConfigSchema = WorkflowConfigSchema;
|
|
2330
|
+
|
|
2331
|
+
exports.WorkflowError = WorkflowError;
|
|
2332
|
+
|
|
2333
|
+
exports.actorFulfillsRole = actorFulfillsRole;
|
|
2334
|
+
|
|
2335
|
+
exports.andConditions = andConditions;
|
|
2336
|
+
|
|
2337
|
+
exports.checkFieldValue = checkFieldValue;
|
|
2338
|
+
|
|
2339
|
+
exports.checkWorkflowInvariants = checkWorkflowInvariants;
|
|
2340
|
+
|
|
2341
|
+
exports.clientConfigFromResource = clientConfigFromResource;
|
|
2342
|
+
|
|
2343
|
+
exports.conditionEffectReads = conditionEffectReads;
|
|
2344
|
+
|
|
2345
|
+
exports.conditionParameterNames = conditionParameterNames;
|
|
2346
|
+
|
|
2347
|
+
exports.conditionSyntaxIssues = conditionSyntaxIssues;
|
|
2348
|
+
|
|
2349
|
+
exports.datasetResourceParts = datasetResourceParts;
|
|
2350
|
+
|
|
2351
|
+
exports.definitionDocId = definitionDocId;
|
|
2352
|
+
|
|
2353
|
+
exports.errorMessage = errorMessage;
|
|
2354
|
+
|
|
2355
|
+
exports.evaluateCondition = evaluateCondition;
|
|
2356
|
+
|
|
2357
|
+
exports.evaluateConditionOutcome = evaluateConditionOutcome;
|
|
2358
|
+
|
|
2359
|
+
exports.evaluatePredicates = evaluatePredicates;
|
|
2360
|
+
|
|
2361
|
+
exports.expandRequiredRoles = expandRequiredRoles;
|
|
2362
|
+
|
|
2363
|
+
exports.extractDocumentId = extractDocumentId;
|
|
2364
|
+
|
|
2365
|
+
exports.formatIssuePath = formatIssuePath;
|
|
2366
|
+
|
|
2367
|
+
exports.formatIssues = formatIssues;
|
|
2368
|
+
|
|
2369
|
+
exports.formatValidationError = formatValidationError;
|
|
2370
|
+
|
|
2371
|
+
exports.gdrFromResource = gdrFromResource;
|
|
2372
|
+
|
|
2373
|
+
exports.gdrRef = gdrRef;
|
|
2374
|
+
|
|
2375
|
+
exports.gdrResourcePrefix = gdrResourcePrefix;
|
|
2376
|
+
|
|
2377
|
+
exports.gdrUri = gdrUri;
|
|
2378
|
+
|
|
2379
|
+
exports.groupMembershipNames = groupMembershipNames;
|
|
2380
|
+
|
|
2381
|
+
exports.isBareSeedId = isBareSeedId;
|
|
2382
|
+
|
|
2383
|
+
exports.isGdr = isGdr;
|
|
2384
|
+
|
|
2385
|
+
exports.isGdrUri = isGdrUri;
|
|
2386
|
+
|
|
2387
|
+
exports.isGuardReadExpr = isGuardReadExpr;
|
|
2388
|
+
|
|
2389
|
+
exports.isNotesEntry = isNotesEntry;
|
|
2390
|
+
|
|
2391
|
+
exports.isStartableDefinition = isStartableDefinition;
|
|
2392
|
+
|
|
2393
|
+
exports.isTerminalActivityStatus = isTerminalActivityStatus;
|
|
2394
|
+
|
|
2395
|
+
exports.isTodoListEntry = isTodoListEntry;
|
|
2396
|
+
|
|
2397
|
+
exports.isTodoListItem = isTodoListItem;
|
|
2398
|
+
|
|
2399
|
+
exports.isUnevaluable = isUnevaluable;
|
|
2400
|
+
|
|
2401
|
+
exports.labelFor = labelFor;
|
|
2402
|
+
|
|
2403
|
+
exports.normalizeRoleAliases = normalizeRoleAliases;
|
|
2404
|
+
|
|
2405
|
+
exports.parseGdr = parseGdr;
|
|
2406
|
+
|
|
2407
|
+
exports.parseOrThrow = parseOrThrow;
|
|
2408
|
+
|
|
2409
|
+
exports.parseResourceGdr = parseResourceGdr;
|
|
2410
|
+
|
|
2411
|
+
exports.parseStoredDefinition = parseStoredDefinition;
|
|
2412
|
+
|
|
2413
|
+
exports.printGuardRead = printGuardRead;
|
|
2414
|
+
|
|
2415
|
+
exports.refCanvas = refCanvas;
|
|
2416
|
+
|
|
2417
|
+
exports.refDashboard = refDashboard;
|
|
2418
|
+
|
|
2419
|
+
exports.refDataset = refDataset;
|
|
2420
|
+
|
|
2421
|
+
exports.refMediaLibrary = refMediaLibrary;
|
|
2422
|
+
|
|
2423
|
+
exports.refTypeIssues = refTypeIssues;
|
|
2424
|
+
|
|
2425
|
+
exports.rejectedRefTypes = rejectedRefTypes;
|
|
2426
|
+
|
|
2427
|
+
exports.releaseDocId = releaseDocId;
|
|
2428
|
+
|
|
2429
|
+
exports.releaseRef = releaseRef;
|
|
2430
|
+
|
|
2431
|
+
exports.resourceAliasesToMap = resourceAliasesToMap;
|
|
2432
|
+
|
|
2433
|
+
exports.resourceFromGdrUri = resourceFromGdrUri;
|
|
2434
|
+
|
|
2435
|
+
exports.resourceFromParsed = resourceFromParsed;
|
|
2436
|
+
|
|
2437
|
+
exports.resourceGdr = resourceGdr;
|
|
2438
|
+
|
|
2439
|
+
exports.rethrowWithContext = rethrowWithContext;
|
|
2440
|
+
|
|
2441
|
+
exports.runGroq = runGroq;
|
|
2442
|
+
|
|
2443
|
+
exports.sameResource = sameResource;
|
|
2444
|
+
|
|
2445
|
+
exports.selfGdr = selfGdr;
|
|
2446
|
+
|
|
2447
|
+
exports.tagScopeFilter = tagScopeFilter;
|
|
2448
|
+
|
|
2449
|
+
exports.toBareId = toBareId;
|
|
2450
|
+
|
|
2451
|
+
exports.toPhysicalGdr = toPhysicalGdr;
|
|
2452
|
+
|
|
2453
|
+
exports.tryParseGdr = tryParseGdr;
|
|
2454
|
+
|
|
2455
|
+
exports.validateFieldAppendItem = validateFieldAppendItem;
|
|
2456
|
+
|
|
2457
|
+
exports.validateFieldValue = validateFieldValue;
|
|
2458
|
+
|
|
2459
|
+
exports.validateResourceAliasName = validateResourceAliasName;
|
|
2460
|
+
|
|
2461
|
+
exports.validateTag = validateTag;
|