@sanity/workflow-engine 0.17.0 → 0.19.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 +53 -0
- package/DATAMODEL.md +193 -13
- package/README.md +23 -0
- package/dist/_chunks-cjs/invariants.cjs +696 -122
- package/dist/_chunks-es/invariants.js +643 -123
- package/dist/define.cjs +6 -1
- package/dist/define.d.cts +397 -40
- package/dist/define.d.ts +397 -40
- package/dist/define.js +7 -2
- package/dist/index.cjs +3089 -2159
- package/dist/index.d.cts +1199 -205
- package/dist/index.d.ts +1199 -205
- package/dist/index.js +3012 -2132
- package/package.json +1 -1
|
@@ -20,6 +20,317 @@ function _interopNamespaceCompat(e) {
|
|
|
20
20
|
|
|
21
21
|
var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
|
|
22
22
|
|
|
23
|
+
class WorkflowError extends Error {
|
|
24
|
+
kind;
|
|
25
|
+
constructor(kind, message, options) {
|
|
26
|
+
super(message, options), this.kind = kind;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class ContractViolationError extends WorkflowError {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super("contract-violation", message), this.name = "ContractViolationError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class InstanceNotFoundError extends WorkflowError {
|
|
37
|
+
instanceId;
|
|
38
|
+
constructor(args) {
|
|
39
|
+
super("instance-not-found", `Workflow instance ${args.instanceId} not found${args.detail ? ` (${args.detail})` : ""}`),
|
|
40
|
+
this.name = "InstanceNotFoundError", this.instanceId = args.instanceId;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class DefinitionNotFoundError extends WorkflowError {
|
|
45
|
+
definition;
|
|
46
|
+
version;
|
|
47
|
+
constructor(args) {
|
|
48
|
+
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`),
|
|
49
|
+
this.name = "DefinitionNotFoundError", this.definition = args.definition, args.version !== void 0 && (this.version = args.version);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class SpawnContractsInvalidError extends WorkflowError {
|
|
54
|
+
issues;
|
|
55
|
+
constructor(args) {
|
|
56
|
+
super("spawn-contracts-invalid", args.message), this.name = "SpawnContractsInvalidError",
|
|
57
|
+
this.issues = args.issues;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class DefinitionInUseError extends WorkflowError {
|
|
62
|
+
definition;
|
|
63
|
+
blockedBy;
|
|
64
|
+
constructor(args) {
|
|
65
|
+
super("definition-in-use", definitionInUseMessage(args.definition, args.blockedBy)),
|
|
66
|
+
this.name = "DefinitionInUseError", this.definition = args.definition, this.blockedBy = args.blockedBy;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function definitionInUseMessage(definition, blockedBy) {
|
|
71
|
+
if (blockedBy.reason === "non-terminal-instances") {
|
|
72
|
+
const head = blockedBy.instanceIds.slice(0, 3).join(", "), preview = blockedBy.instanceIds.length > 3 ? `${head}, …` : head;
|
|
73
|
+
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.`;
|
|
74
|
+
}
|
|
75
|
+
const names = blockedBy.referrers.map(r => `${r.definition} v${r.version}`).join(", ");
|
|
76
|
+
return `Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class EffectNotFoundError extends WorkflowError {
|
|
80
|
+
instanceId;
|
|
81
|
+
effectKey;
|
|
82
|
+
settled;
|
|
83
|
+
constructor(args) {
|
|
84
|
+
super("effect-not-found", effectNotFoundMessage(args)), this.name = "EffectNotFoundError",
|
|
85
|
+
this.instanceId = args.instanceId, this.effectKey = args.effectKey, args.settled !== void 0 && (this.settled = args.settled);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function effectNotFoundMessage(args) {
|
|
90
|
+
const base = `Pending effect "${args.effectKey}" not found on instance ${args.instanceId}`;
|
|
91
|
+
if (args.settled === void 0) return base;
|
|
92
|
+
const cause = args.settled.detail !== void 0 ? ` (${args.settled.detail})` : "";
|
|
93
|
+
return args.settled.status === "cancelled" ? `${base} — it was cancelled at ${args.settled.ranAt}${cause}` : `${base} — it already settled "${args.settled.status}" at ${args.settled.ranAt}${cause}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function errorMessage(err) {
|
|
97
|
+
return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function rethrowWithContext(err, context) {
|
|
101
|
+
throw new Error(`${context}: ${errorMessage(err)}`, {
|
|
102
|
+
cause: err
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const WORKFLOW_INSTANCE_TYPE = "sanity.workflow.instance";
|
|
107
|
+
|
|
108
|
+
function terminalState(instance) {
|
|
109
|
+
return instance.abortedAt !== void 0 ? "aborted" : instance.completedAt !== void 0 ? "completed" : "in-flight";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isUnprimed(instance) {
|
|
113
|
+
return instance.stages.length === 0 && terminalState(instance) === "in-flight";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function parseDefinitionSnapshotValue(instance) {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(instance.definitionSnapshot);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
rethrowWithContext(err, `Failed to parse definitionSnapshot on instance "${instance._id}"`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseDefinitionSnapshot(instance) {
|
|
125
|
+
return parseDefinitionSnapshotValue(instance);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function parentRef(instance) {
|
|
129
|
+
return instance.ancestors.at(-1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const DATA_MODEL_VERSION = 3, DATA_MODEL_MIN_READER = 2, READER_MODEL_ROLLOUT_URL = "https://github.com/sanity-io/workflows/blob/main/docs/reader-model-rollout.md";
|
|
133
|
+
|
|
134
|
+
class ReaderModelAcknowledgementError extends WorkflowError {
|
|
135
|
+
code="WORKFLOW_READER_MODEL_ACKNOWLEDGEMENT_MISMATCH";
|
|
136
|
+
expectedMinReaderModel;
|
|
137
|
+
engineMinReaderModel=DATA_MODEL_MIN_READER;
|
|
138
|
+
engineModelVersion=DATA_MODEL_VERSION;
|
|
139
|
+
documentationUrl=READER_MODEL_ROLLOUT_URL;
|
|
140
|
+
constructor(expectedMinReaderModel, context = "Deployment") {
|
|
141
|
+
const expected = expectedMinReaderModel === void 0 ? "missing" : String(expectedMinReaderModel);
|
|
142
|
+
super("reader-model-acknowledgement", `${context} expected reader floor ${expected}; the installed engine requires acknowledgement ${DATA_MODEL_MIN_READER}. Dependency upgrades can change writer compatibility. Upgrade all readers and Functions before accepting a higher floor; after verifying the rollout, change the literal in deployment configuration. ${READER_MODEL_ROLLOUT_URL}`),
|
|
143
|
+
this.name = "ReaderModelAcknowledgementError", this.expectedMinReaderModel = expectedMinReaderModel;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function assertReaderModelAcknowledgement(expectedMinReaderModel, context) {
|
|
148
|
+
if (typeof expectedMinReaderModel != "number" || !Number.isFinite(expectedMinReaderModel) || !Number.isInteger(expectedMinReaderModel) || expectedMinReaderModel < 0 || expectedMinReaderModel !== DATA_MODEL_MIN_READER) throw new ReaderModelAcknowledgementError(expectedMinReaderModel, context);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const DATA_MODEL_CHANGES = Object.freeze([ Object.freeze({
|
|
152
|
+
id: "governed-model-stamps",
|
|
153
|
+
introducedInModel: 1,
|
|
154
|
+
minReaderModel: 0,
|
|
155
|
+
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
156
|
+
compatibility: "additive",
|
|
157
|
+
applicability: "unconditional",
|
|
158
|
+
summary: "Definition and instance documents carry model provenance and reader-floor stamps."
|
|
159
|
+
}), Object.freeze({
|
|
160
|
+
id: "subject-field-kind",
|
|
161
|
+
introducedInModel: 2,
|
|
162
|
+
minReaderModel: 0,
|
|
163
|
+
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
164
|
+
compatibility: "additive",
|
|
165
|
+
applicability: "detectable",
|
|
166
|
+
summary: "A workflow-level subject field identifies the document a workflow is about."
|
|
167
|
+
}), Object.freeze({
|
|
168
|
+
id: "typed-scalar-choice-lists",
|
|
169
|
+
introducedInModel: 2,
|
|
170
|
+
minReaderModel: 2,
|
|
171
|
+
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
172
|
+
compatibility: "reader-floor",
|
|
173
|
+
applicability: "detectable",
|
|
174
|
+
summary: "Scalar fields may constrain writes to a persisted typed choice list."
|
|
175
|
+
}), Object.freeze({
|
|
176
|
+
id: "action-semantics",
|
|
177
|
+
introducedInModel: 2,
|
|
178
|
+
minReaderModel: 0,
|
|
179
|
+
documentTypes: Object.freeze([ "definition" ]),
|
|
180
|
+
compatibility: "additive",
|
|
181
|
+
applicability: "detectable",
|
|
182
|
+
summary: "Ordinary actions may carry a closed bag of advisory workflow semantics."
|
|
183
|
+
}), Object.freeze({
|
|
184
|
+
id: "inclusive-scalar-bounds",
|
|
185
|
+
introducedInModel: 2,
|
|
186
|
+
minReaderModel: 2,
|
|
187
|
+
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
188
|
+
compatibility: "reader-floor",
|
|
189
|
+
applicability: "detectable",
|
|
190
|
+
summary: "String, text, and number values may carry persisted inclusive bounds."
|
|
191
|
+
}), Object.freeze({
|
|
192
|
+
id: "progress-field-kind",
|
|
193
|
+
introducedInModel: 3,
|
|
194
|
+
minReaderModel: 0,
|
|
195
|
+
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
196
|
+
compatibility: "additive",
|
|
197
|
+
applicability: "detectable",
|
|
198
|
+
summary: "A progress field kind carries application-defined 0–100 completion."
|
|
199
|
+
}), Object.freeze({
|
|
200
|
+
id: "effect-claim-tokens",
|
|
201
|
+
introducedInModel: 3,
|
|
202
|
+
minReaderModel: 0,
|
|
203
|
+
documentTypes: Object.freeze([ "instance" ]),
|
|
204
|
+
compatibility: "additive",
|
|
205
|
+
applicability: "detectable",
|
|
206
|
+
summary: "Pending-effect claims carry an exact-claim token gating mid-dispatch state reports."
|
|
207
|
+
}) ]);
|
|
208
|
+
|
|
209
|
+
function recordOf(value) {
|
|
210
|
+
return value !== null && typeof value == "object" && !Array.isArray(value) ? value : void 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function recordsAt(record, key) {
|
|
214
|
+
const value = record[key];
|
|
215
|
+
return Array.isArray(value) ? value.map(recordOf).filter(item => item !== void 0) : [];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function nestedFieldEntries(entries) {
|
|
219
|
+
return entries.flatMap(entry => [ entry, ...nestedFieldEntries(recordsAt(entry, "fields")), ...nestedFieldEntries(recordsAt(entry, "of")) ]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parsedDefinitionSnapshot(root) {
|
|
223
|
+
if (typeof root.definitionSnapshot == "string") return recordOf(parseDefinitionSnapshotValue({
|
|
224
|
+
_id: typeof root._id == "string" ? root._id : "<unknown instance>",
|
|
225
|
+
definitionSnapshot: root.definitionSnapshot
|
|
226
|
+
}));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function persistedFieldEntries(document) {
|
|
230
|
+
const root = recordOf(document);
|
|
231
|
+
if (root === void 0) return [];
|
|
232
|
+
const snapshot = parsedDefinitionSnapshot(root), roots = snapshot === void 0 ? [ root ] : [ root, snapshot ], stages = roots.flatMap(candidate => recordsAt(candidate, "stages")), activities = stages.flatMap(stage => recordsAt(stage, "activities")), actions = activities.flatMap(activity => recordsAt(activity, "actions")), effects = actions.flatMap(action => recordsAt(action, "effects"));
|
|
233
|
+
return nestedFieldEntries([ ...roots.flatMap(candidate => recordsAt(candidate, "fields")), ...stages.flatMap(stage => recordsAt(stage, "fields")), ...activities.flatMap(activity => recordsAt(activity, "fields")), ...actions.flatMap(action => recordsAt(action, "params")), ...effects.flatMap(effect => recordsAt(effect, "outputs")) ]);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function hasChoiceList(document) {
|
|
237
|
+
return persistedFieldEntries(document).some(entry => {
|
|
238
|
+
const options = recordOf(entry.options);
|
|
239
|
+
return options !== void 0 && Array.isArray(options.list);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function hasFieldKind(document, kind) {
|
|
244
|
+
return persistedFieldEntries(document).some(entry => entry.type === kind || entry._type === kind);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function hasActionSemantics(document) {
|
|
248
|
+
const root = recordOf(document);
|
|
249
|
+
return root === void 0 ? !1 : recordsAt(root, "stages").flatMap(stage => recordsAt(stage, "activities")).flatMap(activity => recordsAt(activity, "actions")).some(action => Array.isArray(action.semantics));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function hasScalarValidation(document) {
|
|
253
|
+
return persistedFieldEntries(document).some(entry => {
|
|
254
|
+
const validation = recordOf(entry.validation);
|
|
255
|
+
return typeof validation?.min == "number" || typeof validation?.max == "number";
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function hasClaimTokens(document) {
|
|
260
|
+
const root = recordOf(document);
|
|
261
|
+
return root === void 0 ? !1 : recordsAt(root, "pendingEffects").some(entry => {
|
|
262
|
+
const claim = recordOf(entry.claim);
|
|
263
|
+
return claim !== void 0 && typeof claim.claimToken == "string";
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const featureDetectors = {
|
|
268
|
+
"governed-model-stamps": () => !0,
|
|
269
|
+
"subject-field-kind": document => hasFieldKind(document, "subject"),
|
|
270
|
+
"typed-scalar-choice-lists": hasChoiceList,
|
|
271
|
+
"action-semantics": hasActionSemantics,
|
|
272
|
+
"inclusive-scalar-bounds": hasScalarValidation,
|
|
273
|
+
"progress-field-kind": document => hasFieldKind(document, "progress"),
|
|
274
|
+
"effect-claim-tokens": hasClaimTokens
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
function requiredModelFeatures(documentType, document) {
|
|
278
|
+
return DATA_MODEL_CHANGES.filter(change => change.documentTypes.some(candidate => candidate === documentType) && featureDetectors[change.id](document));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function requiredReaderModel(documentType, document) {
|
|
282
|
+
return Math.max(0, ...requiredModelFeatures(documentType, document).map(change => change.minReaderModel));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function modelStampFor(args) {
|
|
286
|
+
return {
|
|
287
|
+
modelVersion: DATA_MODEL_VERSION,
|
|
288
|
+
minReaderModel: Math.max(args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function fieldTreeShape(value) {
|
|
293
|
+
if (Array.isArray(value)) return value.map(fieldTreeShape);
|
|
294
|
+
if (value === null) return "null";
|
|
295
|
+
if (typeof value == "object") {
|
|
296
|
+
const record = value;
|
|
297
|
+
return Object.fromEntries(Object.keys(record).toSorted().map(key => [ key, fieldTreeShape(record[key]) ]));
|
|
298
|
+
}
|
|
299
|
+
return typeof value;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function modelVersionOf(doc) {
|
|
303
|
+
const stamp = doc.modelVersion;
|
|
304
|
+
return typeof stamp == "number" ? stamp : 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function minReaderModelOf(doc) {
|
|
308
|
+
const floor = doc.minReaderModel;
|
|
309
|
+
return typeof floor == "number" ? floor : modelVersionOf(doc);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
class ModelVersionAheadError extends WorkflowError {
|
|
313
|
+
documentId;
|
|
314
|
+
documentModelVersion;
|
|
315
|
+
requiredReaderModel;
|
|
316
|
+
engineModelVersion;
|
|
317
|
+
constructor(args) {
|
|
318
|
+
super("model-version-ahead", `Document "${args.documentId}" was written by engine data model ${args.documentModelVersion} and requires a reader at model ${args.requiredReaderModel} or newer; this engine reads up to model ${DATA_MODEL_VERSION}. Upgrade @sanity/workflow-engine to a version that understands it.`),
|
|
319
|
+
this.name = "ModelVersionAheadError", this.documentId = args.documentId, this.documentModelVersion = args.documentModelVersion,
|
|
320
|
+
this.requiredReaderModel = args.requiredReaderModel, this.engineModelVersion = DATA_MODEL_VERSION;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function assertReadableModel(doc) {
|
|
325
|
+
const documentReaderModel = minReaderModelOf(doc);
|
|
326
|
+
if (documentReaderModel > DATA_MODEL_VERSION) throw new ModelVersionAheadError({
|
|
327
|
+
documentId: doc._id,
|
|
328
|
+
documentModelVersion: modelVersionOf(doc),
|
|
329
|
+
requiredReaderModel: documentReaderModel
|
|
330
|
+
});
|
|
331
|
+
return doc;
|
|
332
|
+
}
|
|
333
|
+
|
|
23
334
|
function isCascadeFired(action) {
|
|
24
335
|
return action.when !== void 0;
|
|
25
336
|
}
|
|
@@ -40,16 +351,6 @@ function driverKind(actor) {
|
|
|
40
351
|
return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
|
|
41
352
|
}
|
|
42
353
|
|
|
43
|
-
function errorMessage(err) {
|
|
44
|
-
return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function rethrowWithContext(err, context) {
|
|
48
|
-
throw new Error(`${context}: ${errorMessage(err)}`, {
|
|
49
|
-
cause: err
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
354
|
function andConditions(parts) {
|
|
54
355
|
const present = parts.filter(p => p !== void 0);
|
|
55
356
|
if (present.length !== 0) return present.length === 1 ? present[0] : present.map(p => `(${p})`).join(" && ");
|
|
@@ -250,71 +551,6 @@ function isGdr(value) {
|
|
|
250
551
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
251
552
|
}
|
|
252
553
|
|
|
253
|
-
class WorkflowError extends Error {
|
|
254
|
-
kind;
|
|
255
|
-
constructor(kind, message, options) {
|
|
256
|
-
super(message, options), this.kind = kind;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
class ContractViolationError extends WorkflowError {
|
|
261
|
-
constructor(message) {
|
|
262
|
-
super("contract-violation", message), this.name = "ContractViolationError";
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
class InstanceNotFoundError extends WorkflowError {
|
|
267
|
-
instanceId;
|
|
268
|
-
constructor(args) {
|
|
269
|
-
super("instance-not-found", `Workflow instance ${args.instanceId} not found${args.detail ? ` (${args.detail})` : ""}`),
|
|
270
|
-
this.name = "InstanceNotFoundError", this.instanceId = args.instanceId;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
class DefinitionNotFoundError extends WorkflowError {
|
|
275
|
-
definition;
|
|
276
|
-
version;
|
|
277
|
-
constructor(args) {
|
|
278
|
-
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`),
|
|
279
|
-
this.name = "DefinitionNotFoundError", this.definition = args.definition, args.version !== void 0 && (this.version = args.version);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
class DefinitionInUseError extends WorkflowError {
|
|
284
|
-
definition;
|
|
285
|
-
blockedBy;
|
|
286
|
-
constructor(args) {
|
|
287
|
-
super("definition-in-use", definitionInUseMessage(args.definition, args.blockedBy)),
|
|
288
|
-
this.name = "DefinitionInUseError", this.definition = args.definition, this.blockedBy = args.blockedBy;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function definitionInUseMessage(definition, blockedBy) {
|
|
293
|
-
if (blockedBy.reason === "non-terminal-instances") {
|
|
294
|
-
const head = blockedBy.instanceIds.slice(0, 3).join(", "), preview = blockedBy.instanceIds.length > 3 ? `${head}, …` : head;
|
|
295
|
-
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.`;
|
|
296
|
-
}
|
|
297
|
-
const names = blockedBy.referrers.map(r => `${r.definition} v${r.version}`).join(", ");
|
|
298
|
-
return `Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
class EffectNotFoundError extends WorkflowError {
|
|
302
|
-
instanceId;
|
|
303
|
-
effectKey;
|
|
304
|
-
settled;
|
|
305
|
-
constructor(args) {
|
|
306
|
-
super("effect-not-found", effectNotFoundMessage(args)), this.name = "EffectNotFoundError",
|
|
307
|
-
this.instanceId = args.instanceId, this.effectKey = args.effectKey, args.settled !== void 0 && (this.settled = args.settled);
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function effectNotFoundMessage(args) {
|
|
312
|
-
const base = `Pending effect "${args.effectKey}" not found on instance ${args.instanceId}`;
|
|
313
|
-
if (args.settled === void 0) return base;
|
|
314
|
-
const cause = args.settled.detail !== void 0 ? ` (${args.settled.detail})` : "";
|
|
315
|
-
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}`;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
554
|
const LAKE_ID_SEGMENT_RE = /^[a-z0-9][a-z0-9-]*$/, LAKE_ID_SEGMENT_GLOSS = "ASCII lowercase + digits + dashes, no leading dash, no dots";
|
|
319
555
|
|
|
320
556
|
function validateTag(tag) {
|
|
@@ -337,7 +573,13 @@ function asPredicate(validate) {
|
|
|
337
573
|
};
|
|
338
574
|
}
|
|
339
575
|
|
|
340
|
-
const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts)
|
|
576
|
+
const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts);
|
|
577
|
+
|
|
578
|
+
function lakeSegment(label) {
|
|
579
|
+
return v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty(), v__namespace.check(isValidTag, `invalid ${label} — ${LAKE_ID_SEGMENT_GLOSS}`));
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const WorkflowResourceSchema = v__namespace.variant("type", [ v__namespace.object({
|
|
341
583
|
type: v__namespace.literal("dataset"),
|
|
342
584
|
id: v__namespace.pipe(NonEmptyString$1, v__namespace.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
|
|
343
585
|
}), v__namespace.object({
|
|
@@ -353,13 +595,46 @@ const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(vali
|
|
|
353
595
|
name: v__namespace.pipe(NonEmptyString$1, v__namespace.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
354
596
|
resource: WorkflowResourceSchema
|
|
355
597
|
}), 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({
|
|
356
|
-
name:
|
|
357
|
-
|
|
598
|
+
name: lakeSegment("name"),
|
|
599
|
+
expectedMinReaderModel: v__namespace.optional(v__namespace.custom(() => !0), void 0),
|
|
600
|
+
tag: lakeSegment("tag"),
|
|
358
601
|
workflowResource: WorkflowResourceSchema,
|
|
359
|
-
resourceAliases: v__namespace.optional(v__namespace.pipe(v__namespace.array(ResourceBindingSchema), v__namespace.check(bindings =>
|
|
602
|
+
resourceAliases: v__namespace.optional(v__namespace.pipe(v__namespace.array(ResourceBindingSchema), v__namespace.check(bindings => duplicateHandleMessage(bindings) === void 0, issue => duplicateHandleMessage(issue.input) ?? "duplicate resource handle name"))),
|
|
360
603
|
definitions: v__namespace.pipe(v__namespace.array(DefinitionSchema), v__namespace.minLength(1, "a deployment needs at least one definition"))
|
|
361
|
-
})
|
|
362
|
-
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
function firstDuplicatePair(items, keyOf) {
|
|
607
|
+
const seen = /* @__PURE__ */ new Map;
|
|
608
|
+
for (const item of items) {
|
|
609
|
+
const key = keyOf(item), earlier = seen.get(key);
|
|
610
|
+
if (earlier !== void 0) return [ earlier, item ];
|
|
611
|
+
seen.set(key, item);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function duplicateHandleMessage(bindings) {
|
|
616
|
+
const pair = firstDuplicatePair(bindings, binding => binding.name);
|
|
617
|
+
if (pair !== void 0) return `duplicate resource handle name "${pair[1].name}" — each binding name must be unique within a deployment`;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function duplicateNameMessage(deployments) {
|
|
621
|
+
const pair = firstDuplicatePair(deployments, deployment => deployment.name);
|
|
622
|
+
if (pair !== void 0) return `duplicate deployment name "${pair[1].name}" — each deployment must use a unique name`;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function partitionKey(deployment) {
|
|
626
|
+
return `${resourceGdr(deployment.workflowResource)} ${deployment.tag}`;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function partitionCollisionMessage(deployments) {
|
|
630
|
+
const pair = firstDuplicatePair(deployments, partitionKey);
|
|
631
|
+
if (pair === void 0) return;
|
|
632
|
+
const [first, second] = pair;
|
|
633
|
+
return `deployments "${first.name}" and "${second.name}" share workflow resource + tag "${second.tag}" — both would write into the same partition; change one deployment’s tag or resource`;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const 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({
|
|
637
|
+
deployments: v__namespace.pipe(v__namespace.array(DeploymentSchema), v__namespace.minLength(1, "a config needs at least one deployment"), v__namespace.check(deployments => duplicateNameMessage(deployments) === void 0, issue => duplicateNameMessage(issue.input) ?? "duplicate deployment name"), v__namespace.check(deployments => partitionCollisionMessage(deployments) === void 0, issue => partitionCollisionMessage(issue.input) ?? "duplicate deployment partition")),
|
|
363
638
|
telemetry: v__namespace.optional(TelemetryLoggerSchema)
|
|
364
639
|
});
|
|
365
640
|
|
|
@@ -596,6 +871,8 @@ function desugarFieldEntry({entry: entry, path: path, ctx: ctx}) {
|
|
|
596
871
|
required: entry.required,
|
|
597
872
|
initialValue: entry.initialValue,
|
|
598
873
|
editable: editable,
|
|
874
|
+
options: entry.options,
|
|
875
|
+
validation: entry.validation,
|
|
599
876
|
types: entry.types,
|
|
600
877
|
fields: entry.fields,
|
|
601
878
|
of: entry.of
|
|
@@ -738,7 +1015,7 @@ function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ct
|
|
|
738
1015
|
};
|
|
739
1016
|
}
|
|
740
1017
|
|
|
741
|
-
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "release.ref" ];
|
|
1018
|
+
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref" ];
|
|
742
1019
|
|
|
743
1020
|
function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
744
1021
|
if (target === void 0 || target.type === "url") return target;
|
|
@@ -759,18 +1036,15 @@ function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
|
759
1036
|
};
|
|
760
1037
|
}
|
|
761
1038
|
|
|
762
|
-
function
|
|
763
|
-
|
|
764
|
-
action: action,
|
|
765
|
-
path: path,
|
|
766
|
-
env: env,
|
|
767
|
-
ctx: ctx
|
|
768
|
-
});
|
|
769
|
-
action.roles !== void 0 && action.roles.length === 0 && ctx.issues.push({
|
|
1039
|
+
function reportEmptyActionRoles({action: action, path: path, ctx: ctx}) {
|
|
1040
|
+
action.roles === void 0 || action.roles.length > 0 || ctx.issues.push({
|
|
770
1041
|
path: [ ...path, "roles" ],
|
|
771
1042
|
message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
|
|
772
1043
|
});
|
|
773
|
-
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function desugarActionOps(args) {
|
|
1047
|
+
const {action: action, path: path, env: env, activityName: activityName, ctx: ctx} = args, ops = desugarOps({
|
|
774
1048
|
ops: action.ops,
|
|
775
1049
|
path: [ ...path, "ops" ],
|
|
776
1050
|
env: env,
|
|
@@ -781,9 +1055,32 @@ function desugarAction({action: action, path: path, env: env, activityName: acti
|
|
|
781
1055
|
type: "status.set",
|
|
782
1056
|
activity: activityName,
|
|
783
1057
|
status: action.status
|
|
784
|
-
}),
|
|
1058
|
+
}), ops;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
|
|
1062
|
+
if ("type" in action) return desugarClaimAction({
|
|
1063
|
+
action: action,
|
|
1064
|
+
path: path,
|
|
1065
|
+
env: env,
|
|
1066
|
+
ctx: ctx
|
|
1067
|
+
});
|
|
1068
|
+
reportEmptyActionRoles({
|
|
1069
|
+
action: action,
|
|
1070
|
+
path: path,
|
|
1071
|
+
ctx: ctx
|
|
1072
|
+
});
|
|
1073
|
+
const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarActionOps({
|
|
1074
|
+
action: action,
|
|
1075
|
+
path: path,
|
|
1076
|
+
env: env,
|
|
1077
|
+
activityName: activityName,
|
|
1078
|
+
ctx: ctx
|
|
1079
|
+
});
|
|
1080
|
+
return {
|
|
785
1081
|
...stripUndefined({
|
|
786
1082
|
name: action.name,
|
|
1083
|
+
semantics: action.semantics,
|
|
787
1084
|
title: action.title,
|
|
788
1085
|
description: action.description,
|
|
789
1086
|
group: normalizeGroup(action.group),
|
|
@@ -1170,7 +1467,7 @@ function isTerminalActivityStatus(status) {
|
|
|
1170
1467
|
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
1171
1468
|
}
|
|
1172
1469
|
|
|
1173
|
-
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" ], EXECUTOR_CLASSIFICATIONS = [ "autonomous", "interactive", "off-system", "hybrid" ], GROUP_KINDS = [ "core", "details" ], DRIVER_KINDS = [ "person", "agent", "service", "engine" ], CONDITION_VARS = [ {
|
|
1470
|
+
const ACTION_SEMANTICS = [ "decision.accept", "decision.decline" ], FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISSIONS = [ "create", "read", "update" ], MUTATION_GUARD_ACTIONS = [ "create", "update", "delete", "publish", "unpublish" ], ACTIVITY_KINDS = [ "user", "service", "script", "manual", "receive" ], EXECUTOR_CLASSIFICATIONS = [ "autonomous", "interactive", "off-system", "hybrid" ], GROUP_KINDS = [ "core", "details" ], DRIVER_KINDS = [ "person", "agent", "service", "engine" ], CONDITION_VARS = [ {
|
|
1174
1471
|
name: "self",
|
|
1175
1472
|
binding: "always",
|
|
1176
1473
|
label: "this workflow instance",
|
|
@@ -1179,7 +1476,7 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
|
|
|
1179
1476
|
name: "fields",
|
|
1180
1477
|
binding: "always",
|
|
1181
1478
|
label: "the workflow's fields",
|
|
1182
|
-
description: "Declared field entries rendered by name (`$fields.<name>` is the value, no wrapper). Stage/activity scopes overlay lexically. What a read puts in your hand follows the declared kind: a singular `doc.ref` DEREFERENCES into the hydrated document (lake `_id`/`_type` plus content fields — which may themselves be named `id`/`type`), while `doc.refs` elements and `release.ref` stay REFERENCES — `{id, type[, releaseName]}` with `id` a GDR URI — because identity reads (membership, counting, joins) must stay total without hydrating every target, and targets may live in resources the evaluation cannot fetch from. Conditions evaluate against the in-memory snapshot only, so a dereferencing plural kind would silently hole wherever hydration lagged."
|
|
1479
|
+
description: "Declared field entries rendered by name (`$fields.<name>` is the value, no wrapper). Stage/activity scopes overlay lexically. What a read puts in your hand follows the declared kind: a singular `doc.ref` (or `subject`) DEREFERENCES into the hydrated document (lake `_id`/`_type` plus content fields — which may themselves be named `id`/`type`), while `doc.refs` elements and `release.ref` stay REFERENCES — `{id, type[, releaseName]}` with `id` a GDR URI — because identity reads (membership, counting, joins) must stay total without hydrating every target, and targets may live in resources the evaluation cannot fetch from. Conditions evaluate against the in-memory snapshot only, so a dereferencing plural kind would silently hole wherever hydration lagged."
|
|
1183
1480
|
}, {
|
|
1184
1481
|
name: "parent",
|
|
1185
1482
|
binding: "always",
|
|
@@ -1260,18 +1557,26 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
|
|
|
1260
1557
|
binding: "always",
|
|
1261
1558
|
label: "the spawned subworkflows",
|
|
1262
1559
|
description: "Every row of the instance's subworkflow registry, faceted by `activity`/`action`/`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 `when`s, requirements, any stage's gates; the settled gate is `count($subworkflows[activity == <name> && current && status == 'active']) == 0`."
|
|
1263
|
-
} ], 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), START_FILTER_VARS = [ {
|
|
1560
|
+
} ], SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR = "subjectHasInFlightInstance", RESERVED_CONDITION_VARS = [ ...CONDITION_VARS.map(v2 => v2.name), SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR ], 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), START_FILTER_VARS = [ {
|
|
1264
1561
|
name: "tag",
|
|
1562
|
+
label: "this engine tag",
|
|
1265
1563
|
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
1266
1564
|
}, {
|
|
1267
1565
|
name: "definition",
|
|
1566
|
+
label: "this workflow definition",
|
|
1268
1567
|
description: "The `name` of the definition under evaluation (its own start block binds it)."
|
|
1269
1568
|
}, {
|
|
1270
1569
|
name: "now",
|
|
1570
|
+
label: "the current time",
|
|
1271
1571
|
description: "The ISO clock reading of the evaluating engine."
|
|
1572
|
+
}, {
|
|
1573
|
+
name: SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR,
|
|
1574
|
+
label: "the subject already has an in-flight workflow",
|
|
1575
|
+
description: "Whether any instance in this engine tag, across all definitions, has the same resource-qualified subject and no `completedAt`. Advisory under concurrent starts."
|
|
1272
1576
|
} ], START_ALLOWED_VARS = [ ...START_FILTER_VARS, {
|
|
1273
1577
|
name: "fields",
|
|
1274
|
-
|
|
1578
|
+
label: "the start's input fields",
|
|
1579
|
+
description: "The caller's input entries by name (`initialFields` — at `startInstance`, the values the start would seed; at a pre-flight, the values gathered so far, so a read of a not-yet-supplied entry is GROQ null). Document references bind as GDR envelopes — `$fields.<entry>.id` is the GDR URI, never a string authors assemble — a singular `doc.ref` or `subject` included (nothing hydrates at the gate or the pre-flight). Pathed reads are deploy-checked against these envelope shapes."
|
|
1275
1580
|
} ], GUARD_PREDICATE_VARS = [ {
|
|
1276
1581
|
name: "guard",
|
|
1277
1582
|
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
@@ -1421,6 +1726,18 @@ function releaseRef({res: res, releaseName: releaseName}) {
|
|
|
1421
1726
|
};
|
|
1422
1727
|
}
|
|
1423
1728
|
|
|
1729
|
+
function isSingleDocRefKind(kind) {
|
|
1730
|
+
return kind === "doc.ref" || kind === "subject";
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function refKindAcceptsTypes(kind) {
|
|
1734
|
+
return isSingleDocRefKind(kind) || kind === "doc.refs";
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
function isSingleDocRefEntry(entry) {
|
|
1738
|
+
return isSingleDocRefKind(entry._type);
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1424
1741
|
function isTodoListItem(row) {
|
|
1425
1742
|
if (typeof row != "object" || row === null) return !1;
|
|
1426
1743
|
const candidate = row, status = candidate.status;
|
|
@@ -1471,13 +1788,39 @@ const GdrUriSchema = v__namespace.custom(s => typeof s == "string" && isGdrUri(s
|
|
|
1471
1788
|
}), tolerantObject()({
|
|
1472
1789
|
type: v__namespace.literal("role"),
|
|
1473
1790
|
role: NonEmptyString
|
|
1474
|
-
}) ]), 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(), IsoTimestamp ]), 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,
|
|
1791
|
+
}) ]), 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() ]), NullableProgress = v__namespace.union([ v__namespace.null(), v__namespace.pipe(v__namespace.number(), v__namespace.finite("progress must be a finite number"), v__namespace.minValue(0, "progress must be at least 0"), v__namespace.maxValue(100, "progress must be at most 100")) ]), NullableDateTime = v__namespace.union([ v__namespace.null(), IsoTimestamp ]), 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, CHOICE_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "url", "date", "datetime", "dateTime" ]);
|
|
1792
|
+
|
|
1793
|
+
function normalizedChoiceKind(kind) {
|
|
1794
|
+
return kind === "dateTime" ? "datetime" : kind;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
function checkChoiceList(args) {
|
|
1798
|
+
const {entryType: entryType, options: options, validation: validation} = args;
|
|
1799
|
+
if (options === void 0) return;
|
|
1800
|
+
if (!CHOICE_KINDS.has(entryType)) return [ `\`options\` is not valid on "${entryType}" values` ];
|
|
1801
|
+
const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => checkValueAgainst({
|
|
1802
|
+
entryType: kind,
|
|
1803
|
+
value: option.value,
|
|
1804
|
+
validation: validation
|
|
1805
|
+
}, valueSchemas)?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
|
|
1806
|
+
for (const option of options.list) seen.has(option.value) && issues.push(`duplicate option value ${JSON.stringify(option.value)}`),
|
|
1807
|
+
seen.add(option.value);
|
|
1808
|
+
return issues.length === 0 ? void 0 : issues;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
function choiceValueIssues(options, value) {
|
|
1812
|
+
if (!(options === void 0 || value === null || value === void 0)) return options.list.some(option => Object.is(option.value, value)) ? void 0 : [ `value ${JSON.stringify(value)} is not declared in \`options.list\`; expected one of ${options.list.map(option => JSON.stringify(option.value)).join(", ")}` ];
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
const fieldValueSchemas = {
|
|
1475
1816
|
"doc.ref": v__namespace.union([ v__namespace.null(), GdrShape ]),
|
|
1476
1817
|
"doc.refs": v__namespace.array(GdrShape),
|
|
1818
|
+
subject: v__namespace.union([ v__namespace.null(), GdrShape ]),
|
|
1477
1819
|
"release.ref": v__namespace.union([ v__namespace.null(), ReleaseRefShape ]),
|
|
1478
1820
|
string: NullableString,
|
|
1479
1821
|
text: NullableString,
|
|
1480
1822
|
number: NullableNumber,
|
|
1823
|
+
progress: NullableProgress,
|
|
1481
1824
|
boolean: NullableBoolean,
|
|
1482
1825
|
date: NullableDate,
|
|
1483
1826
|
datetime: NullableDateTime,
|
|
@@ -1491,7 +1834,56 @@ const GdrUriSchema = v__namespace.custom(s => typeof s == "string" && isGdrUri(s
|
|
|
1491
1834
|
};
|
|
1492
1835
|
|
|
1493
1836
|
function shapeValueSchema(shape, leaf) {
|
|
1494
|
-
|
|
1837
|
+
if (shape.type === "object") return objectSchema(shape.fields ?? [], leaf);
|
|
1838
|
+
if (shape.type === "array") return v__namespace.array(objectSchema(shape.of ?? [], leaf));
|
|
1839
|
+
const schema = leaf[shape.type] ?? v__namespace.any();
|
|
1840
|
+
return constrainedScalarSchema({
|
|
1841
|
+
schema: schema,
|
|
1842
|
+
entryType: shape.type,
|
|
1843
|
+
...shape
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function scalarMeasurement(entryType, value) {
|
|
1848
|
+
if ((entryType === "number" || entryType === "progress") && typeof value == "number") return value;
|
|
1849
|
+
if ((entryType === "string" || entryType === "text") && typeof value == "string") return value.length;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
function scalarBoundIssue(args) {
|
|
1853
|
+
const {entryType: entryType, measured: measured, bound: bound, limit: limit} = args;
|
|
1854
|
+
return bound === void 0 || limit === "min" && measured >= bound || limit === "max" && measured <= bound ? void 0 : `${entryType === "number" || entryType === "progress" ? "" : "length "}must be ${limit === "min" ? "greater than or equal to" : "less than or equal to"} ${bound}`;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
function scalarValidationIssues(args) {
|
|
1858
|
+
const {entryType: entryType, validation: validation, value: value} = args;
|
|
1859
|
+
if (validation === void 0 || value === null || value === void 0) return;
|
|
1860
|
+
const measured = scalarMeasurement(entryType, value);
|
|
1861
|
+
if (measured === void 0) return;
|
|
1862
|
+
const issues = [ scalarBoundIssue({
|
|
1863
|
+
entryType: entryType,
|
|
1864
|
+
measured: measured,
|
|
1865
|
+
bound: validation.min,
|
|
1866
|
+
limit: "min"
|
|
1867
|
+
}), scalarBoundIssue({
|
|
1868
|
+
entryType: entryType,
|
|
1869
|
+
measured: measured,
|
|
1870
|
+
bound: validation.max,
|
|
1871
|
+
limit: "max"
|
|
1872
|
+
}) ].filter(issue => issue !== void 0);
|
|
1873
|
+
return issues.length === 0 ? void 0 : issues;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function constrainedScalarSchema(args) {
|
|
1877
|
+
const {schema: schema, entryType: entryType, options: options, validation: validation} = args;
|
|
1878
|
+
return options === void 0 && validation === void 0 ? schema : v__namespace.pipe(schema, v__namespace.check(value => choiceValueIssues(options, value) === void 0 && scalarValidationIssues({
|
|
1879
|
+
entryType: entryType,
|
|
1880
|
+
validation: validation,
|
|
1881
|
+
value: value
|
|
1882
|
+
}) === void 0, issue => [ ...choiceValueIssues(options, issue.input) ?? [], ...scalarValidationIssues({
|
|
1883
|
+
entryType: entryType,
|
|
1884
|
+
validation: validation,
|
|
1885
|
+
value: issue.input
|
|
1886
|
+
}) ?? [] ].join("; ")));
|
|
1495
1887
|
}
|
|
1496
1888
|
|
|
1497
1889
|
function objectSchema(fields, leaf) {
|
|
@@ -1514,7 +1906,7 @@ function appendItemSchema(entryType, shape) {
|
|
|
1514
1906
|
function rejectedRefTypes(args) {
|
|
1515
1907
|
const {entryType: entryType, types: types, value: value} = args;
|
|
1516
1908
|
if (types === void 0 || value === null || value === void 0) return [];
|
|
1517
|
-
if (entryType
|
|
1909
|
+
if (!refKindAcceptsTypes(entryType)) return [];
|
|
1518
1910
|
let items = [ value ];
|
|
1519
1911
|
return entryType === "doc.refs" && (items = Array.isArray(value) ? value : []),
|
|
1520
1912
|
[ ...new Set(items.map(gdrTypeOf).filter(t => t !== void 0 && !types.includes(t))) ];
|
|
@@ -1549,6 +1941,10 @@ function checkValueAgainst(args, leaf) {
|
|
|
1549
1941
|
entryType: args.entryType,
|
|
1550
1942
|
types: args.types,
|
|
1551
1943
|
value: args.value
|
|
1944
|
+
}) ?? choiceValueIssues(args.options, args.value) ?? scalarValidationIssues({
|
|
1945
|
+
entryType: args.entryType,
|
|
1946
|
+
validation: args.validation,
|
|
1947
|
+
value: args.value
|
|
1552
1948
|
}) : formatIssues(result.issues);
|
|
1553
1949
|
}
|
|
1554
1950
|
|
|
@@ -1579,6 +1975,7 @@ const AuthoringRefId = v__namespace.pipe(v__namespace.string(), v__namespace.che
|
|
|
1579
1975
|
...valueSchemas,
|
|
1580
1976
|
"doc.ref": v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
|
|
1581
1977
|
"doc.refs": v__namespace.array(AuthoringGdrShape),
|
|
1978
|
+
subject: v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
|
|
1582
1979
|
"release.ref": v__namespace.union([ v__namespace.null(), v__namespace.looseObject({
|
|
1583
1980
|
id: AuthoringRefId,
|
|
1584
1981
|
type: v__namespace.literal("system.release"),
|
|
@@ -1618,10 +2015,10 @@ function validateFieldAppendItem(args) {
|
|
|
1618
2015
|
});
|
|
1619
2016
|
}
|
|
1620
2017
|
|
|
1621
|
-
function formatIssues(issues) {
|
|
2018
|
+
function formatIssues(issues, formatMessage = issue => issue.message) {
|
|
1622
2019
|
return issues.map(i => {
|
|
1623
2020
|
const keys = i.path?.map(p => p.key) ?? [];
|
|
1624
|
-
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i
|
|
2021
|
+
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(i)}`;
|
|
1625
2022
|
});
|
|
1626
2023
|
}
|
|
1627
2024
|
|
|
@@ -1747,7 +2144,15 @@ function groupMembershipNames(group) {
|
|
|
1747
2144
|
return group === void 0 ? [] : typeof group == "string" ? [ group ] : [ ...group ];
|
|
1748
2145
|
}
|
|
1749
2146
|
|
|
1750
|
-
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>`")
|
|
2147
|
+
const FIELD_VALUE_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref", "string", "text", "number", "progress", "boolean", "date", "datetime", "url", "actor", "assignee", "assignees", "object", "array" ], FieldValueKindSchema = picklist(FIELD_VALUE_KINDS), FieldKindSchema = picklist(FIELD_VALUE_KINDS), FieldEntryName = groqIdentifier("`$fields.<name>`"), FiniteNumber = v__namespace.pipe(v__namespace.number(), v__namespace.finite("must be finite")), ScalarValidationSchema = v__namespace.pipe(v__namespace.strictObject({
|
|
2148
|
+
min: v__namespace.optional(FiniteNumber),
|
|
2149
|
+
max: v__namespace.optional(FiniteNumber)
|
|
2150
|
+
}), v__namespace.check(validation => validation.min !== void 0 || validation.max !== void 0, "declare at least one bound, or omit `validation`"), v__namespace.check(validation => validation.min === void 0 || validation.max === void 0 || validation.min <= validation.max, "`min` must be less than or equal to `max`")), ChoiceOptionsSchema = v__namespace.strictObject({
|
|
2151
|
+
list: v__namespace.pipe(v__namespace.array(v__namespace.strictObject({
|
|
2152
|
+
title: NonEmpty,
|
|
2153
|
+
value: v__namespace.union([ v__namespace.string(), v__namespace.number() ])
|
|
2154
|
+
})), v__namespace.minLength(1, "declare at least one choice, or omit `options`"))
|
|
2155
|
+
});
|
|
1751
2156
|
|
|
1752
2157
|
function asShape(input) {
|
|
1753
2158
|
return typeof input == "object" && input !== null ? input : {};
|
|
@@ -1781,14 +2186,16 @@ function compositeChecked(entries) {
|
|
|
1781
2186
|
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\``));
|
|
1782
2187
|
}
|
|
1783
2188
|
|
|
1784
|
-
const FieldShapeSchema = v__namespace.lazy(() => compositeChecked({
|
|
2189
|
+
const FieldShapeSchema = v__namespace.lazy(() => v__namespace.pipe(compositeChecked({
|
|
1785
2190
|
type: FieldValueKindSchema,
|
|
1786
2191
|
name: FieldEntryName,
|
|
1787
2192
|
title: v__namespace.optional(v__namespace.string()),
|
|
1788
2193
|
description: v__namespace.optional(v__namespace.string()),
|
|
2194
|
+
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
2195
|
+
validation: v__namespace.optional(ScalarValidationSchema),
|
|
1789
2196
|
fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
1790
2197
|
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
1791
|
-
})), StoredEditableSchema = v__namespace.union([ v__namespace.literal(!0), NonEmpty ]), AuthoringEditableSchema = v__namespace.union([ v__namespace.literal(!0), v__namespace.array(NonEmpty), NonEmpty ]);
|
|
2198
|
+
}), choiceOptionsCheck(), scalarValidationCheck())), StoredEditableSchema = v__namespace.union([ v__namespace.literal(!0), NonEmpty ]), AuthoringEditableSchema = v__namespace.union([ v__namespace.literal(!0), v__namespace.array(NonEmpty), NonEmpty ]);
|
|
1792
2199
|
|
|
1793
2200
|
function fieldBase(editable, group) {
|
|
1794
2201
|
return {
|
|
@@ -1806,6 +2213,8 @@ function fieldEntryFields(editable, group) {
|
|
|
1806
2213
|
return {
|
|
1807
2214
|
type: FieldKindSchema,
|
|
1808
2215
|
...fieldBase(editable, group),
|
|
2216
|
+
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
2217
|
+
validation: v__namespace.optional(ScalarValidationSchema),
|
|
1809
2218
|
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"))),
|
|
1810
2219
|
fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
1811
2220
|
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
@@ -1818,7 +2227,9 @@ function literalSeedIssues(entry) {
|
|
|
1818
2227
|
value: entry.initialValue.value,
|
|
1819
2228
|
types: entry.types,
|
|
1820
2229
|
fields: entry.fields,
|
|
1821
|
-
of: entry.of
|
|
2230
|
+
of: entry.of,
|
|
2231
|
+
options: entry.options,
|
|
2232
|
+
validation: entry.validation
|
|
1822
2233
|
});
|
|
1823
2234
|
}
|
|
1824
2235
|
|
|
@@ -1827,10 +2238,41 @@ function literalSeedCheck() {
|
|
|
1827
2238
|
}
|
|
1828
2239
|
|
|
1829
2240
|
function refTypesCheck() {
|
|
1830
|
-
return v__namespace.check(entry => entry.types === void 0 || entry.type
|
|
2241
|
+
return v__namespace.check(entry => entry.types === void 0 || refKindAcceptsTypes(entry.type), issue => `\`types\` is only valid on \`doc.ref\` / \`doc.refs\` / \`subject\` entries, not "${issue.input.type}"`);
|
|
1831
2242
|
}
|
|
1832
2243
|
|
|
1833
|
-
|
|
2244
|
+
function choiceOptionsCheck() {
|
|
2245
|
+
return v__namespace.check(entry => checkChoiceList({
|
|
2246
|
+
entryType: entry.type,
|
|
2247
|
+
options: entry.options,
|
|
2248
|
+
validation: entry.validation
|
|
2249
|
+
}) === void 0, issue => (checkChoiceList({
|
|
2250
|
+
entryType: issue.input.type,
|
|
2251
|
+
options: issue.input.options,
|
|
2252
|
+
validation: issue.input.validation
|
|
2253
|
+
}) ?? []).join("; "));
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
const SCALAR_VALIDATION_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "progress" ]);
|
|
2257
|
+
|
|
2258
|
+
function scalarValidationCheck() {
|
|
2259
|
+
return v__namespace.check(entry => scalarValidationDeclarationIssues(entry) === void 0, issue => (scalarValidationDeclarationIssues(issue.input) ?? []).join("; "));
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
function scalarValidationDeclarationIssues(entry) {
|
|
2263
|
+
const {type: type, validation: validation} = entry;
|
|
2264
|
+
if (validation === void 0) return;
|
|
2265
|
+
if (!SCALAR_VALIDATION_KINDS.has(type)) return [ `\`validation\` is only valid on \`string\` / \`text\` / \`number\` / \`progress\` values, not "${type}"` ];
|
|
2266
|
+
if (type === "progress") {
|
|
2267
|
+
const issues2 = Object.entries(validation).flatMap(([bound, value]) => typeof value == "number" && value >= 0 && value <= 100 ? [] : [ `\`validation.${bound}\` must stay within the progress kind's 0–100 contract` ]);
|
|
2268
|
+
return issues2.length === 0 ? void 0 : issues2;
|
|
2269
|
+
}
|
|
2270
|
+
if (type === "number") return;
|
|
2271
|
+
const issues = Object.entries(validation).flatMap(([bound, value]) => Number.isInteger(value) && value >= 0 ? [] : [ `\`validation.${bound}\` must be a non-negative integer for ${type} length` ]);
|
|
2272
|
+
return issues.length === 0 ? void 0 : issues;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
const FieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields(StoredEditableSchema, StoredGroupMembershipSchema)), refTypesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields(AuthoringEditableSchema, AuthoringGroupMembershipSchema)), refTypesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), ClaimFieldSchema = pinned()(v__namespace.strictObject({
|
|
1834
2276
|
type: v__namespace.literal("claim"),
|
|
1835
2277
|
name: FieldEntryName,
|
|
1836
2278
|
title: v__namespace.optional(v__namespace.string()),
|
|
@@ -1861,17 +2303,25 @@ const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("
|
|
|
1861
2303
|
with: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1862
2304
|
context: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1863
2305
|
onExit: v__namespace.optional(picklist([ "detach", "abort" ]))
|
|
1864
|
-
}), ActionParamSchema = v__namespace.strictObject({
|
|
2306
|
+
}), ActionParamSchema = v__namespace.pipe(v__namespace.strictObject({
|
|
1865
2307
|
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
1866
2308
|
name: NonEmpty,
|
|
1867
2309
|
title: v__namespace.optional(v__namespace.string()),
|
|
1868
2310
|
description: v__namespace.optional(v__namespace.string()),
|
|
1869
|
-
required: v__namespace.optional(v__namespace.boolean())
|
|
1870
|
-
|
|
2311
|
+
required: v__namespace.optional(v__namespace.boolean()),
|
|
2312
|
+
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
2313
|
+
validation: v__namespace.optional(ScalarValidationSchema)
|
|
2314
|
+
}), choiceOptionsCheck(), scalarValidationCheck());
|
|
2315
|
+
|
|
2316
|
+
function hasUniqueSemanticNamespaces(semantics) {
|
|
2317
|
+
const namespaces = semantics.map(semantic => semantic.split(".", 1)[0]);
|
|
2318
|
+
return new Set(namespaces).size === namespaces.length;
|
|
2319
|
+
}
|
|
1871
2320
|
|
|
1872
2321
|
function actionFields(op, group) {
|
|
1873
2322
|
return {
|
|
1874
2323
|
name: NonEmpty,
|
|
2324
|
+
semantics: v__namespace.optional(v__namespace.pipe(v__namespace.array(picklist(ACTION_SEMANTICS)), v__namespace.minLength(1, "declare at least one semantic, or omit `semantics`"), v__namespace.check(hasUniqueSemanticNamespaces, "declare at most one semantic from each namespace"))),
|
|
1875
2325
|
title: v__namespace.optional(v__namespace.string()),
|
|
1876
2326
|
description: v__namespace.optional(v__namespace.string()),
|
|
1877
2327
|
group: v__namespace.optional(group),
|
|
@@ -2060,7 +2510,7 @@ function startKindOf(definition) {
|
|
|
2060
2510
|
}
|
|
2061
2511
|
|
|
2062
2512
|
function isSubjectEntry(entry) {
|
|
2063
|
-
return entry.
|
|
2513
|
+
return entry.type === "subject";
|
|
2064
2514
|
}
|
|
2065
2515
|
|
|
2066
2516
|
function isInputSourced(entry) {
|
|
@@ -2562,6 +3012,52 @@ function checkAssigneesEntries(def, issues) {
|
|
|
2562
3012
|
});
|
|
2563
3013
|
}
|
|
2564
3014
|
|
|
3015
|
+
function nestedSubjectPaths(shapes, base) {
|
|
3016
|
+
return (shapes ?? []).flatMap((shape, i) => isSubjectEntry(shape) ? [ [ ...base, i, "type" ] ] : [ ...nestedSubjectPaths(shape.fields, [ ...base, i, "fields" ]), ...nestedSubjectPaths(shape.of, [ ...base, i, "of" ]) ]);
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
function checkSubjectEffectOutputs(def, issues) {
|
|
3020
|
+
for (const [i, stage] of def.stages.entries()) for (const [j, activity] of (stage.activities ?? []).entries()) for (const [a, action] of (activity.actions ?? []).entries()) pushSubjectOutputIssues({
|
|
3021
|
+
action: action,
|
|
3022
|
+
path: [ "stages", i, "activities", j, "actions", a ],
|
|
3023
|
+
issues: issues
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
function pushSubjectOutputIssues(args) {
|
|
3028
|
+
const {action: action, path: path, issues: issues} = args;
|
|
3029
|
+
for (const [e, effect] of (action.effects ?? []).entries()) {
|
|
3030
|
+
const base = [ ...path, "effects", e, "outputs" ];
|
|
3031
|
+
for (const nestedPath of nestedSubjectPaths(effect.outputs, base)) issues.push({
|
|
3032
|
+
path: nestedPath,
|
|
3033
|
+
message: `effect "${effect.name}" declares a \`subject\` output shape — no subject reader ever looks at effect outputs. Declare \`doc.ref\` for a document-valued output`
|
|
3034
|
+
});
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
function checkSubjectEntries(def, issues) {
|
|
3039
|
+
for (const {entries: entries, scope: scope, path: path, label: label} of fieldScopes(def)) {
|
|
3040
|
+
const subjects = (entries ?? []).flatMap((entry, n) => isSubjectEntry(entry) ? [ {
|
|
3041
|
+
entry: entry,
|
|
3042
|
+
n: n
|
|
3043
|
+
} ] : []);
|
|
3044
|
+
if (scope !== "workflow") for (const {entry: entry, n: n} of subjects) issues.push({
|
|
3045
|
+
path: [ ...path, n, "type" ],
|
|
3046
|
+
message: `${label} entry "${entry.name}" is a \`subject\` — the subject names the document the WORKFLOW is about, so it is valid only on a workflow-scope entry. Declare \`doc.ref\` for a plain reference here`
|
|
3047
|
+
}); else for (const {n: n} of subjects.slice(1)) issues.push({
|
|
3048
|
+
path: [ ...path, n ],
|
|
3049
|
+
message: "at most one subject-kind field entry per workflow — the runtime identifies THE subject by kind, and a second one makes it ambiguous"
|
|
3050
|
+
});
|
|
3051
|
+
for (const [n, entry] of (entries ?? []).entries()) {
|
|
3052
|
+
const nested = [ ...nestedSubjectPaths(entry.fields, [ ...path, n, "fields" ]), ...nestedSubjectPaths(entry.of, [ ...path, n, "of" ]) ];
|
|
3053
|
+
for (const nestedPath of nested) issues.push({
|
|
3054
|
+
path: nestedPath,
|
|
3055
|
+
message: `${label} entry "${entry.name}" declares a \`subject\` sub-field — a nested subject can never be read as the workflow's subject. Declare \`doc.ref\` for a nested reference`
|
|
3056
|
+
});
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
|
|
2565
3061
|
function checkActivityTerminalPaths(def, issues) {
|
|
2566
3062
|
for (const [i, stage] of def.stages.entries()) {
|
|
2567
3063
|
const resolvable = terminallyResolvableActivities(stage);
|
|
@@ -2612,12 +3108,30 @@ function checkStart(def, issues) {
|
|
|
2612
3108
|
if (def.start !== void 0 && (def.lifecycle === "child" && issues.push({
|
|
2613
3109
|
path: [ "start" ],
|
|
2614
3110
|
message: "a spawn-only (lifecycle 'child') definition declares `start` — children are instantiated by a parent's `spawn`, never started standalone, so the block would never apply. Remove `start`, or drop `lifecycle: 'child'`"
|
|
2615
|
-
}), checkStartFilterReads(def, issues), checkStartAllowedReads(def, issues), def
|
|
3111
|
+
}), checkStartFilterReads(def, issues), checkStartAllowedReads(def, issues), checkStartSubjectVariable(def, issues),
|
|
3112
|
+
def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
|
|
2616
3113
|
path: [ "fields", n, "required" ],
|
|
2617
|
-
message: `start.kind 'autonomous' means runs are initiated by a system reacting to a document, so every required input must be derivable from that triggering document — required entry "${entry.name}" (kind "${entry.type}") is not
|
|
3114
|
+
message: `start.kind 'autonomous' means runs are initiated by a system reacting to a document, so every required input must be derivable from that triggering document — required entry "${entry.name}" (kind "${entry.type}") is not the workflow's subject. Make it optional, seed it another way (query/literal), or declare it the \`subject\` entry (the document the run is about)`
|
|
2618
3115
|
});
|
|
2619
3116
|
}
|
|
2620
3117
|
|
|
3118
|
+
function checkStartSubjectVariable(def, issues) {
|
|
3119
|
+
const subject = (def.fields ?? []).find(isSubjectEntry);
|
|
3120
|
+
for (const [key, condition] of [ [ "filter", def.start?.filter ], [ "allowed", def.start?.allowed ] ]) if (condition !== void 0 && conditionParameterNames(condition).has(SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR)) {
|
|
3121
|
+
if (subject === void 0) {
|
|
3122
|
+
issues.push({
|
|
3123
|
+
path: [ "start", key ],
|
|
3124
|
+
message: `start.${key} reads $${SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR}, but the definition declares no \`subject\` entry — the engine has no prospective subject identity to match. Declare a \`subject\` entry (the document the workflow is about), or remove the variable`
|
|
3125
|
+
});
|
|
3126
|
+
continue;
|
|
3127
|
+
}
|
|
3128
|
+
key === "allowed" && !isInputSourced(subject) && issues.push({
|
|
3129
|
+
path: [ "start", key ],
|
|
3130
|
+
message: `start.allowed reads $${SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR}, but the definition's subject entry "${subject.name}" is not \`input\`-sourced — the start gate cannot bind its prospective subject. Make the subject input-sourced, or remove the variable`
|
|
3131
|
+
});
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
|
|
2621
3135
|
function checkStartFilterReads(def, issues) {
|
|
2622
3136
|
const filter = def.start?.filter;
|
|
2623
3137
|
filter !== void 0 && (conditionParameterNames(filter).has("fields") && issues.push({
|
|
@@ -2625,7 +3139,7 @@ function checkStartFilterReads(def, issues) {
|
|
|
2625
3139
|
message: "start.filter reads $fields, but the filter is browse-time-pure — a start surface evaluates it per document, before any inputs exist, so $fields cannot be bound. Move the input-dependent rule to start.allowed, the start-time permission predicate that binds $fields and is enforced by startInstance"
|
|
2626
3140
|
}), readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
2627
3141
|
path: [ "start", "filter" ],
|
|
2628
|
-
message: "start.filter reads the candidate document (its root), but the definition declares no
|
|
3142
|
+
message: "start.filter reads the candidate document (its root), but the definition declares no `subject` entry — a read surface would never have a document to bind as root, so every root read is GROQ null and the filter silently misevaluates. Declare a `subject` entry (the document the workflow is about), or gate on the dataset instead"
|
|
2629
3143
|
}));
|
|
2630
3144
|
}
|
|
2631
3145
|
|
|
@@ -2942,7 +3456,7 @@ function checkFieldReadOpValues(def, issues) {
|
|
|
2942
3456
|
ops: site.ops,
|
|
2943
3457
|
path: site.path,
|
|
2944
3458
|
label: site.label,
|
|
2945
|
-
activityEntries:
|
|
3459
|
+
activityEntries: site.activity.fields ?? []
|
|
2946
3460
|
});
|
|
2947
3461
|
}
|
|
2948
3462
|
|
|
@@ -2964,6 +3478,9 @@ function fieldReadsIn(value, path) {
|
|
|
2964
3478
|
|
|
2965
3479
|
function checkOpFieldRead({read: read, where: where, path: path, workflowEntries: workflowEntries, stageEntries: stageEntries, activityEntries: activityEntries, issues: issues}) {
|
|
2966
3480
|
const hosts = [ {
|
|
3481
|
+
scope: "activity",
|
|
3482
|
+
entries: activityEntries
|
|
3483
|
+
}, {
|
|
2967
3484
|
scope: "stage",
|
|
2968
3485
|
entries: stageEntries
|
|
2969
3486
|
}, {
|
|
@@ -2976,8 +3493,7 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
|
|
|
2976
3493
|
message: opFieldReadMissMessage({
|
|
2977
3494
|
read: read,
|
|
2978
3495
|
where: where,
|
|
2979
|
-
hosts: hosts
|
|
2980
|
-
activityEntries: activityEntries
|
|
3496
|
+
hosts: hosts
|
|
2981
3497
|
})
|
|
2982
3498
|
});
|
|
2983
3499
|
return;
|
|
@@ -2991,9 +3507,9 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
|
|
|
2991
3507
|
});
|
|
2992
3508
|
}
|
|
2993
3509
|
|
|
2994
|
-
function opFieldReadMissMessage({read: read, where: where, hosts: hosts
|
|
2995
|
-
const searched = read.scope === void 0 ? "stage or workflow scope" : `${read.scope} scope`,
|
|
2996
|
-
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
|
|
3510
|
+
function opFieldReadMissMessage({read: read, where: where, hosts: hosts}) {
|
|
3511
|
+
const searched = read.scope === void 0 ? "activity, stage, or workflow scope" : `${read.scope} scope`, known = hosts.flatMap(host => host.entries.map(entry => `${host.scope}:${entry.name}`));
|
|
3512
|
+
return `${where} reads field "${read.field}", which is not declared at ${searched} — the read resolves to undefined at op time, so the write silently lands empty. Known: ${known.join(", ") || "(none)"}`;
|
|
2997
3513
|
}
|
|
2998
3514
|
|
|
2999
3515
|
function checkUpdateWhereOps(def, issues) {
|
|
@@ -3156,10 +3672,12 @@ const SCALAR = {
|
|
|
3156
3672
|
kind: "list",
|
|
3157
3673
|
item: GDR_VALUE
|
|
3158
3674
|
}),
|
|
3675
|
+
subject: () => DOC_CONTENT,
|
|
3159
3676
|
"release.ref": () => RELEASE_VALUE,
|
|
3160
3677
|
string: () => SCALAR,
|
|
3161
3678
|
text: () => SCALAR,
|
|
3162
3679
|
number: () => SCALAR,
|
|
3680
|
+
progress: () => SCALAR,
|
|
3163
3681
|
boolean: () => SCALAR,
|
|
3164
3682
|
date: () => SCALAR,
|
|
3165
3683
|
datetime: () => SCALAR,
|
|
@@ -3180,7 +3698,8 @@ const SCALAR = {
|
|
|
3180
3698
|
})
|
|
3181
3699
|
}, START_ALLOWED_VALUE_NODES = {
|
|
3182
3700
|
...VALUE_NODES,
|
|
3183
|
-
"doc.ref": () => GDR_VALUE
|
|
3701
|
+
"doc.ref": () => GDR_VALUE,
|
|
3702
|
+
subject: () => GDR_VALUE
|
|
3184
3703
|
};
|
|
3185
3704
|
|
|
3186
3705
|
function valueNodeFor(shape, nodes) {
|
|
@@ -3252,11 +3771,14 @@ function checkWorkflowInvariants(def) {
|
|
|
3252
3771
|
checkRequiredField(def, issues), checkStart(def, issues), checkPredicates(def, issues),
|
|
3253
3772
|
checkUnboundConditionVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
|
|
3254
3773
|
checkFieldReadOpValues(def, issues), checkUpdateWhereOps(def, issues), checkGuardFieldReads(def, issues),
|
|
3255
|
-
checkAssigneesEntries(def, issues),
|
|
3774
|
+
checkAssigneesEntries(def, issues), checkSubjectEntries(def, issues), checkSubjectEffectOutputs(def, issues),
|
|
3775
|
+
checkActivityTerminalPaths(def, issues), checkTerminalStageActivities(def, issues),
|
|
3256
3776
|
checkTriggeredActionParams(def, issues), checkStoredRolesPlacement(def, issues),
|
|
3257
3777
|
checkGroups(def, issues), issues;
|
|
3258
3778
|
}
|
|
3259
3779
|
|
|
3780
|
+
exports.ACTION_SEMANTICS = ACTION_SEMANTICS;
|
|
3781
|
+
|
|
3260
3782
|
exports.ACTIVITY_KINDS = ACTIVITY_KINDS;
|
|
3261
3783
|
|
|
3262
3784
|
exports.ACTIVITY_STATUSES = ACTIVITY_STATUSES;
|
|
@@ -3287,6 +3809,12 @@ exports.CONDITION_VARS = CONDITION_VARS;
|
|
|
3287
3809
|
|
|
3288
3810
|
exports.ContractViolationError = ContractViolationError;
|
|
3289
3811
|
|
|
3812
|
+
exports.DATA_MODEL_CHANGES = DATA_MODEL_CHANGES;
|
|
3813
|
+
|
|
3814
|
+
exports.DATA_MODEL_MIN_READER = DATA_MODEL_MIN_READER;
|
|
3815
|
+
|
|
3816
|
+
exports.DATA_MODEL_VERSION = DATA_MODEL_VERSION;
|
|
3817
|
+
|
|
3290
3818
|
exports.DEFAULT_TRANSITION_WHEN = DEFAULT_TRANSITION_WHEN;
|
|
3291
3819
|
|
|
3292
3820
|
exports.DOCUMENT_VALUE_PERMISSIONS = DOCUMENT_VALUE_PERMISSIONS;
|
|
@@ -3329,22 +3857,34 @@ exports.IsoTimestamp = IsoTimestamp;
|
|
|
3329
3857
|
|
|
3330
3858
|
exports.MUTATION_GUARD_ACTIONS = MUTATION_GUARD_ACTIONS;
|
|
3331
3859
|
|
|
3860
|
+
exports.ModelVersionAheadError = ModelVersionAheadError;
|
|
3861
|
+
|
|
3332
3862
|
exports.NonEmptyString = NonEmptyString;
|
|
3333
3863
|
|
|
3334
3864
|
exports.PersistedDocShapeError = PersistedDocShapeError;
|
|
3335
3865
|
|
|
3866
|
+
exports.READER_MODEL_ROLLOUT_URL = READER_MODEL_ROLLOUT_URL;
|
|
3867
|
+
|
|
3336
3868
|
exports.RESERVED_CONDITION_VARS = RESERVED_CONDITION_VARS;
|
|
3337
3869
|
|
|
3338
3870
|
exports.RESOURCE_ALIAS_NAME_SOURCE = RESOURCE_ALIAS_NAME_SOURCE;
|
|
3339
3871
|
|
|
3872
|
+
exports.ReaderModelAcknowledgementError = ReaderModelAcknowledgementError;
|
|
3873
|
+
|
|
3340
3874
|
exports.START_ALLOWED_VARS = START_ALLOWED_VARS;
|
|
3341
3875
|
|
|
3342
3876
|
exports.START_FILTER_VARS = START_FILTER_VARS;
|
|
3343
3877
|
|
|
3878
|
+
exports.SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR = SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR;
|
|
3879
|
+
|
|
3880
|
+
exports.SpawnContractsInvalidError = SpawnContractsInvalidError;
|
|
3881
|
+
|
|
3344
3882
|
exports.StoredFieldOpSchema = StoredFieldOpSchema;
|
|
3345
3883
|
|
|
3346
3884
|
exports.WORKFLOW_DEFINITION_TYPE = WORKFLOW_DEFINITION_TYPE;
|
|
3347
3885
|
|
|
3886
|
+
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
3887
|
+
|
|
3348
3888
|
exports.WorkflowConfigSchema = WorkflowConfigSchema;
|
|
3349
3889
|
|
|
3350
3890
|
exports.WorkflowError = WorkflowError;
|
|
@@ -3353,10 +3893,16 @@ exports.actorFulfillsRole = actorFulfillsRole;
|
|
|
3353
3893
|
|
|
3354
3894
|
exports.andConditions = andConditions;
|
|
3355
3895
|
|
|
3896
|
+
exports.assertReadableModel = assertReadableModel;
|
|
3897
|
+
|
|
3898
|
+
exports.assertReaderModelAcknowledgement = assertReaderModelAcknowledgement;
|
|
3899
|
+
|
|
3356
3900
|
exports.checkFieldValue = checkFieldValue;
|
|
3357
3901
|
|
|
3358
3902
|
exports.checkWorkflowInvariants = checkWorkflowInvariants;
|
|
3359
3903
|
|
|
3904
|
+
exports.choiceValueIssues = choiceValueIssues;
|
|
3905
|
+
|
|
3360
3906
|
exports.clientConfigFromResource = clientConfigFromResource;
|
|
3361
3907
|
|
|
3362
3908
|
exports.conditionEffectReads = conditionEffectReads;
|
|
@@ -3389,6 +3935,8 @@ exports.evaluatePredicates = evaluatePredicates;
|
|
|
3389
3935
|
|
|
3390
3936
|
exports.extractDocumentId = extractDocumentId;
|
|
3391
3937
|
|
|
3938
|
+
exports.fieldTreeShape = fieldTreeShape;
|
|
3939
|
+
|
|
3392
3940
|
exports.fieldValueSchemas = fieldValueSchemas;
|
|
3393
3941
|
|
|
3394
3942
|
exports.formatIssuePath = formatIssuePath;
|
|
@@ -3425,6 +3973,10 @@ exports.isNotesEntry = isNotesEntry;
|
|
|
3425
3973
|
|
|
3426
3974
|
exports.isParseableInstant = isParseableInstant;
|
|
3427
3975
|
|
|
3976
|
+
exports.isSingleDocRefEntry = isSingleDocRefEntry;
|
|
3977
|
+
|
|
3978
|
+
exports.isSingleDocRefKind = isSingleDocRefKind;
|
|
3979
|
+
|
|
3428
3980
|
exports.isStartableDefinition = isStartableDefinition;
|
|
3429
3981
|
|
|
3430
3982
|
exports.isSubjectEntry = isSubjectEntry;
|
|
@@ -3437,8 +3989,20 @@ exports.isTodoListItem = isTodoListItem;
|
|
|
3437
3989
|
|
|
3438
3990
|
exports.isUnevaluable = isUnevaluable;
|
|
3439
3991
|
|
|
3992
|
+
exports.isUnprimed = isUnprimed;
|
|
3993
|
+
|
|
3440
3994
|
exports.labelFor = labelFor;
|
|
3441
3995
|
|
|
3996
|
+
exports.minReaderModelOf = minReaderModelOf;
|
|
3997
|
+
|
|
3998
|
+
exports.modelStampFor = modelStampFor;
|
|
3999
|
+
|
|
4000
|
+
exports.modelVersionOf = modelVersionOf;
|
|
4001
|
+
|
|
4002
|
+
exports.parentRef = parentRef;
|
|
4003
|
+
|
|
4004
|
+
exports.parseDefinitionSnapshot = parseDefinitionSnapshot;
|
|
4005
|
+
|
|
3442
4006
|
exports.parseGdr = parseGdr;
|
|
3443
4007
|
|
|
3444
4008
|
exports.parseOrThrow = parseOrThrow;
|
|
@@ -3457,6 +4021,8 @@ exports.refDashboard = refDashboard;
|
|
|
3457
4021
|
|
|
3458
4022
|
exports.refDataset = refDataset;
|
|
3459
4023
|
|
|
4024
|
+
exports.refKindAcceptsTypes = refKindAcceptsTypes;
|
|
4025
|
+
|
|
3460
4026
|
exports.refMediaLibrary = refMediaLibrary;
|
|
3461
4027
|
|
|
3462
4028
|
exports.refTypeIssues = refTypeIssues;
|
|
@@ -3467,6 +4033,10 @@ exports.releaseDocId = releaseDocId;
|
|
|
3467
4033
|
|
|
3468
4034
|
exports.releaseRef = releaseRef;
|
|
3469
4035
|
|
|
4036
|
+
exports.requiredModelFeatures = requiredModelFeatures;
|
|
4037
|
+
|
|
4038
|
+
exports.requiredReaderModel = requiredReaderModel;
|
|
4039
|
+
|
|
3470
4040
|
exports.resourceAliasesToMap = resourceAliasesToMap;
|
|
3471
4041
|
|
|
3472
4042
|
exports.resourceFromGdrUri = resourceFromGdrUri;
|
|
@@ -3481,6 +4051,8 @@ exports.runGroq = runGroq;
|
|
|
3481
4051
|
|
|
3482
4052
|
exports.sameResource = sameResource;
|
|
3483
4053
|
|
|
4054
|
+
exports.scalarValidationIssues = scalarValidationIssues;
|
|
4055
|
+
|
|
3484
4056
|
exports.schemaTreeShape = schemaTreeShape;
|
|
3485
4057
|
|
|
3486
4058
|
exports.selfGdr = selfGdr;
|
|
@@ -3489,6 +4061,8 @@ exports.startKindOf = startKindOf;
|
|
|
3489
4061
|
|
|
3490
4062
|
exports.tagScopeFilter = tagScopeFilter;
|
|
3491
4063
|
|
|
4064
|
+
exports.terminalState = terminalState;
|
|
4065
|
+
|
|
3492
4066
|
exports.toBareId = toBareId;
|
|
3493
4067
|
|
|
3494
4068
|
exports.toPhysicalGdr = toPhysicalGdr;
|