@sanity/workflow-engine 0.16.0 → 0.18.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 +87 -0
- package/DATAMODEL.md +244 -42
- package/README.md +23 -0
- package/dist/_chunks-cjs/invariants.cjs +1029 -225
- package/dist/_chunks-es/invariants.js +944 -226
- package/dist/define.cjs +6 -1
- package/dist/define.d.cts +220 -48
- package/dist/define.d.ts +220 -48
- package/dist/define.js +7 -2
- package/dist/index.cjs +4371 -2591
- package/dist/index.d.cts +1456 -302
- package/dist/index.d.ts +1456 -302
- package/dist/index.js +4162 -2477
- package/package.json +2 -2
|
@@ -20,6 +20,291 @@ 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 = 2, 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
|
+
}) ]);
|
|
192
|
+
|
|
193
|
+
function recordOf(value) {
|
|
194
|
+
return value !== null && typeof value == "object" && !Array.isArray(value) ? value : void 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function recordsAt(record, key) {
|
|
198
|
+
const value = record[key];
|
|
199
|
+
return Array.isArray(value) ? value.map(recordOf).filter(item => item !== void 0) : [];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function nestedFieldEntries(entries) {
|
|
203
|
+
return entries.flatMap(entry => [ entry, ...nestedFieldEntries(recordsAt(entry, "fields")), ...nestedFieldEntries(recordsAt(entry, "of")) ]);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function parsedDefinitionSnapshot(root) {
|
|
207
|
+
if (typeof root.definitionSnapshot == "string") return recordOf(parseDefinitionSnapshotValue({
|
|
208
|
+
_id: typeof root._id == "string" ? root._id : "<unknown instance>",
|
|
209
|
+
definitionSnapshot: root.definitionSnapshot
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function persistedFieldEntries(document) {
|
|
214
|
+
const root = recordOf(document);
|
|
215
|
+
if (root === void 0) return [];
|
|
216
|
+
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"));
|
|
217
|
+
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")) ]);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function hasChoiceList(document) {
|
|
221
|
+
return persistedFieldEntries(document).some(entry => {
|
|
222
|
+
const options = recordOf(entry.options);
|
|
223
|
+
return options !== void 0 && Array.isArray(options.list);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function hasSubjectField(document) {
|
|
228
|
+
return persistedFieldEntries(document).some(entry => entry.type === "subject" || entry._type === "subject");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function hasActionSemantics(document) {
|
|
232
|
+
const root = recordOf(document);
|
|
233
|
+
return root === void 0 ? !1 : recordsAt(root, "stages").flatMap(stage => recordsAt(stage, "activities")).flatMap(activity => recordsAt(activity, "actions")).some(action => Array.isArray(action.semantics));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function hasScalarValidation(document) {
|
|
237
|
+
return persistedFieldEntries(document).some(entry => {
|
|
238
|
+
const validation = recordOf(entry.validation);
|
|
239
|
+
return typeof validation?.min == "number" || typeof validation?.max == "number";
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const featureDetectors = {
|
|
244
|
+
"governed-model-stamps": () => !0,
|
|
245
|
+
"subject-field-kind": hasSubjectField,
|
|
246
|
+
"typed-scalar-choice-lists": hasChoiceList,
|
|
247
|
+
"action-semantics": hasActionSemantics,
|
|
248
|
+
"inclusive-scalar-bounds": hasScalarValidation
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
function requiredModelFeatures(documentType, document) {
|
|
252
|
+
return DATA_MODEL_CHANGES.filter(change => change.documentTypes.some(candidate => candidate === documentType) && featureDetectors[change.id](document));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function requiredReaderModel(documentType, document) {
|
|
256
|
+
return Math.max(0, ...requiredModelFeatures(documentType, document).map(change => change.minReaderModel));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function modelStampFor(args) {
|
|
260
|
+
return {
|
|
261
|
+
modelVersion: DATA_MODEL_VERSION,
|
|
262
|
+
minReaderModel: Math.max(args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function fieldTreeShape(value) {
|
|
267
|
+
if (Array.isArray(value)) return value.map(fieldTreeShape);
|
|
268
|
+
if (value === null) return "null";
|
|
269
|
+
if (typeof value == "object") {
|
|
270
|
+
const record = value;
|
|
271
|
+
return Object.fromEntries(Object.keys(record).toSorted().map(key => [ key, fieldTreeShape(record[key]) ]));
|
|
272
|
+
}
|
|
273
|
+
return typeof value;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function modelVersionOf(doc) {
|
|
277
|
+
const stamp = doc.modelVersion;
|
|
278
|
+
return typeof stamp == "number" ? stamp : 0;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function minReaderModelOf(doc) {
|
|
282
|
+
const floor = doc.minReaderModel;
|
|
283
|
+
return typeof floor == "number" ? floor : modelVersionOf(doc);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
class ModelVersionAheadError extends WorkflowError {
|
|
287
|
+
documentId;
|
|
288
|
+
documentModelVersion;
|
|
289
|
+
requiredReaderModel;
|
|
290
|
+
engineModelVersion;
|
|
291
|
+
constructor(args) {
|
|
292
|
+
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.`),
|
|
293
|
+
this.name = "ModelVersionAheadError", this.documentId = args.documentId, this.documentModelVersion = args.documentModelVersion,
|
|
294
|
+
this.requiredReaderModel = args.requiredReaderModel, this.engineModelVersion = DATA_MODEL_VERSION;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function assertReadableModel(doc) {
|
|
299
|
+
const documentReaderModel = minReaderModelOf(doc);
|
|
300
|
+
if (documentReaderModel > DATA_MODEL_VERSION) throw new ModelVersionAheadError({
|
|
301
|
+
documentId: doc._id,
|
|
302
|
+
documentModelVersion: modelVersionOf(doc),
|
|
303
|
+
requiredReaderModel: documentReaderModel
|
|
304
|
+
});
|
|
305
|
+
return doc;
|
|
306
|
+
}
|
|
307
|
+
|
|
23
308
|
function isCascadeFired(action) {
|
|
24
309
|
return action.when !== void 0;
|
|
25
310
|
}
|
|
@@ -40,16 +325,6 @@ function driverKind(actor) {
|
|
|
40
325
|
return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
|
|
41
326
|
}
|
|
42
327
|
|
|
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
328
|
function andConditions(parts) {
|
|
54
329
|
const present = parts.filter(p => p !== void 0);
|
|
55
330
|
if (present.length !== 0) return present.length === 1 ? present[0] : present.map(p => `(${p})`).join(" && ");
|
|
@@ -250,82 +525,17 @@ function isGdr(value) {
|
|
|
250
525
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
251
526
|
}
|
|
252
527
|
|
|
253
|
-
|
|
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
|
-
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
528
|
+
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
529
|
|
|
320
530
|
function validateTag(tag) {
|
|
321
|
-
if (!
|
|
531
|
+
if (!LAKE_ID_SEGMENT_RE.test(tag)) throw new ContractViolationError(`tag: invalid tag "${tag}" — must match ${LAKE_ID_SEGMENT_RE.source} (${LAKE_ID_SEGMENT_GLOSS})`);
|
|
322
532
|
}
|
|
323
533
|
|
|
324
534
|
function tagScopeFilter() {
|
|
325
535
|
return "tag == $tag";
|
|
326
536
|
}
|
|
327
537
|
|
|
328
|
-
const NonEmptyString = v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty("must not be empty"));
|
|
538
|
+
const NonEmptyString$1 = v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty("must not be empty"));
|
|
329
539
|
|
|
330
540
|
function asPredicate(validate) {
|
|
331
541
|
return value => {
|
|
@@ -339,21 +549,22 @@ function asPredicate(validate) {
|
|
|
339
549
|
|
|
340
550
|
const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts), WorkflowResourceSchema = v__namespace.variant("type", [ v__namespace.object({
|
|
341
551
|
type: v__namespace.literal("dataset"),
|
|
342
|
-
id: v__namespace.pipe(NonEmptyString, v__namespace.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
|
|
552
|
+
id: v__namespace.pipe(NonEmptyString$1, v__namespace.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
|
|
343
553
|
}), v__namespace.object({
|
|
344
554
|
type: v__namespace.literal("canvas"),
|
|
345
|
-
id: NonEmptyString
|
|
555
|
+
id: NonEmptyString$1
|
|
346
556
|
}), v__namespace.object({
|
|
347
557
|
type: v__namespace.literal("media-library"),
|
|
348
|
-
id: NonEmptyString
|
|
558
|
+
id: NonEmptyString$1
|
|
349
559
|
}), v__namespace.object({
|
|
350
560
|
type: v__namespace.literal("dashboard"),
|
|
351
|
-
id: NonEmptyString
|
|
561
|
+
id: NonEmptyString$1
|
|
352
562
|
}) ]), ResourceBindingSchema = v__namespace.object({
|
|
353
|
-
name: v__namespace.pipe(NonEmptyString, v__namespace.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
563
|
+
name: v__namespace.pipe(NonEmptyString$1, v__namespace.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
354
564
|
resource: WorkflowResourceSchema
|
|
355
565
|
}), 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: NonEmptyString,
|
|
566
|
+
name: NonEmptyString$1,
|
|
567
|
+
expectedMinReaderModel: v__namespace.optional(v__namespace.custom(() => !0), void 0),
|
|
357
568
|
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")),
|
|
358
569
|
workflowResource: WorkflowResourceSchema,
|
|
359
570
|
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"))),
|
|
@@ -518,6 +729,9 @@ function desugarStart(start) {
|
|
|
518
729
|
kind: start.kind ?? "interactive",
|
|
519
730
|
...start.filter !== void 0 ? {
|
|
520
731
|
filter: start.filter
|
|
732
|
+
} : {},
|
|
733
|
+
...start.allowed !== void 0 ? {
|
|
734
|
+
allowed: start.allowed
|
|
521
735
|
} : {}
|
|
522
736
|
};
|
|
523
737
|
}
|
|
@@ -593,6 +807,8 @@ function desugarFieldEntry({entry: entry, path: path, ctx: ctx}) {
|
|
|
593
807
|
required: entry.required,
|
|
594
808
|
initialValue: entry.initialValue,
|
|
595
809
|
editable: editable,
|
|
810
|
+
options: entry.options,
|
|
811
|
+
validation: entry.validation,
|
|
596
812
|
types: entry.types,
|
|
597
813
|
fields: entry.fields,
|
|
598
814
|
of: entry.of
|
|
@@ -735,7 +951,7 @@ function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ct
|
|
|
735
951
|
};
|
|
736
952
|
}
|
|
737
953
|
|
|
738
|
-
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "release.ref" ];
|
|
954
|
+
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref" ];
|
|
739
955
|
|
|
740
956
|
function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
741
957
|
if (target === void 0 || target.type === "url") return target;
|
|
@@ -756,18 +972,15 @@ function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
|
756
972
|
};
|
|
757
973
|
}
|
|
758
974
|
|
|
759
|
-
function
|
|
760
|
-
|
|
761
|
-
action: action,
|
|
762
|
-
path: path,
|
|
763
|
-
env: env,
|
|
764
|
-
ctx: ctx
|
|
765
|
-
});
|
|
766
|
-
action.roles !== void 0 && action.roles.length === 0 && ctx.issues.push({
|
|
975
|
+
function reportEmptyActionRoles({action: action, path: path, ctx: ctx}) {
|
|
976
|
+
action.roles === void 0 || action.roles.length > 0 || ctx.issues.push({
|
|
767
977
|
path: [ ...path, "roles" ],
|
|
768
978
|
message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
|
|
769
979
|
});
|
|
770
|
-
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function desugarActionOps(args) {
|
|
983
|
+
const {action: action, path: path, env: env, activityName: activityName, ctx: ctx} = args, ops = desugarOps({
|
|
771
984
|
ops: action.ops,
|
|
772
985
|
path: [ ...path, "ops" ],
|
|
773
986
|
env: env,
|
|
@@ -778,9 +991,32 @@ function desugarAction({action: action, path: path, env: env, activityName: acti
|
|
|
778
991
|
type: "status.set",
|
|
779
992
|
activity: activityName,
|
|
780
993
|
status: action.status
|
|
781
|
-
}),
|
|
994
|
+
}), ops;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
|
|
998
|
+
if ("type" in action) return desugarClaimAction({
|
|
999
|
+
action: action,
|
|
1000
|
+
path: path,
|
|
1001
|
+
env: env,
|
|
1002
|
+
ctx: ctx
|
|
1003
|
+
});
|
|
1004
|
+
reportEmptyActionRoles({
|
|
1005
|
+
action: action,
|
|
1006
|
+
path: path,
|
|
1007
|
+
ctx: ctx
|
|
1008
|
+
});
|
|
1009
|
+
const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarActionOps({
|
|
1010
|
+
action: action,
|
|
1011
|
+
path: path,
|
|
1012
|
+
env: env,
|
|
1013
|
+
activityName: activityName,
|
|
1014
|
+
ctx: ctx
|
|
1015
|
+
});
|
|
1016
|
+
return {
|
|
782
1017
|
...stripUndefined({
|
|
783
1018
|
name: action.name,
|
|
1019
|
+
semantics: action.semantics,
|
|
784
1020
|
title: action.title,
|
|
785
1021
|
description: action.description,
|
|
786
1022
|
group: normalizeGroup(action.group),
|
|
@@ -1066,12 +1302,43 @@ function conditionParameterNames(groq2) {
|
|
|
1066
1302
|
}
|
|
1067
1303
|
|
|
1068
1304
|
function conditionFieldReadNames(groq2) {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1305
|
+
return new Set(conditionFieldReads(groq2).map(read => read.name));
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
function conditionFieldReads(groq2) {
|
|
1309
|
+
const reads = /* @__PURE__ */ new Map;
|
|
1310
|
+
return collectFieldReads(tryParseGroq(groq2), reads), [ ...reads.values() ];
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
function collectFieldReads(node, reads) {
|
|
1314
|
+
if (Array.isArray(node)) {
|
|
1315
|
+
for (const item of node) collectFieldReads(item, reads);
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
if (typeof node != "object" || node === null) return;
|
|
1319
|
+
const chain = fieldsAttributeChain(node);
|
|
1320
|
+
if (chain !== void 0) {
|
|
1321
|
+
const [name, ...tail] = chain, path = tail.length > 0 ? tail.join(".") : void 0;
|
|
1322
|
+
reads.set(`${name}.${path ?? ""}`, {
|
|
1323
|
+
name: name,
|
|
1324
|
+
path: path
|
|
1325
|
+
});
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
for (const value of Object.values(node)) collectFieldReads(value, reads);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function fieldsAttributeChain(node) {
|
|
1332
|
+
const names = [];
|
|
1333
|
+
let current = node;
|
|
1334
|
+
for (;typeof current == "object" && current !== null; ) {
|
|
1335
|
+
const typed = current;
|
|
1336
|
+
if (typed.type !== "AccessAttribute" || typeof typed.name != "string") return;
|
|
1337
|
+
names.unshift(typed.name);
|
|
1338
|
+
const base = typed.base;
|
|
1339
|
+
if (base?.type === "Parameter" && base.name === "fields") return names;
|
|
1340
|
+
current = base;
|
|
1341
|
+
}
|
|
1075
1342
|
}
|
|
1076
1343
|
|
|
1077
1344
|
function conditionEffectReads(groq2) {
|
|
@@ -1136,7 +1403,7 @@ function isTerminalActivityStatus(status) {
|
|
|
1136
1403
|
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
1137
1404
|
}
|
|
1138
1405
|
|
|
1139
|
-
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 = [ {
|
|
1406
|
+
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 = [ {
|
|
1140
1407
|
name: "self",
|
|
1141
1408
|
binding: "always",
|
|
1142
1409
|
label: "this workflow instance",
|
|
@@ -1145,7 +1412,7 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
|
|
|
1145
1412
|
name: "fields",
|
|
1146
1413
|
binding: "always",
|
|
1147
1414
|
label: "the workflow's fields",
|
|
1148
|
-
description: "Declared field entries rendered by name (`$fields.<name>` is the value, no
|
|
1415
|
+
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."
|
|
1149
1416
|
}, {
|
|
1150
1417
|
name: "parent",
|
|
1151
1418
|
binding: "always",
|
|
@@ -1215,7 +1482,7 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
|
|
|
1215
1482
|
name: "row",
|
|
1216
1483
|
binding: "spawn",
|
|
1217
1484
|
label: "the spawned row",
|
|
1218
|
-
description: "One `spawn.forEach` result row, bound while its `with` map evaluates — and the row under test while a where-op `where` evaluates (per row)."
|
|
1485
|
+
description: "One `spawn.forEach` result row, bound while its `with` map evaluates — and the row under test while a where-op `where` evaluates (per row). Deploy rejects a read at every other site (including `spawn.forEach` itself and `spawn.context`), where it is GROQ null."
|
|
1219
1486
|
}, {
|
|
1220
1487
|
name: "params",
|
|
1221
1488
|
binding: "caller",
|
|
@@ -1226,25 +1493,161 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
|
|
|
1226
1493
|
binding: "always",
|
|
1227
1494
|
label: "the spawned subworkflows",
|
|
1228
1495
|
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`."
|
|
1229
|
-
} ], 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 = [ {
|
|
1496
|
+
} ], 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 = [ {
|
|
1230
1497
|
name: "tag",
|
|
1498
|
+
label: "this engine tag",
|
|
1231
1499
|
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
1232
1500
|
}, {
|
|
1233
1501
|
name: "definition",
|
|
1234
|
-
|
|
1502
|
+
label: "this workflow definition",
|
|
1503
|
+
description: "The `name` of the definition under evaluation (its own start block binds it)."
|
|
1235
1504
|
}, {
|
|
1236
1505
|
name: "now",
|
|
1506
|
+
label: "the current time",
|
|
1237
1507
|
description: "The ISO clock reading of the evaluating engine."
|
|
1238
1508
|
}, {
|
|
1509
|
+
name: SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR,
|
|
1510
|
+
label: "the subject already has an in-flight workflow",
|
|
1511
|
+
description: "Whether any instance in this engine tag, across all definitions, has the same resource-qualified subject and no `completedAt`. Advisory under concurrent starts."
|
|
1512
|
+
} ], START_ALLOWED_VARS = [ ...START_FILTER_VARS, {
|
|
1239
1513
|
name: "fields",
|
|
1240
|
-
|
|
1514
|
+
label: "the start's input fields",
|
|
1515
|
+
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."
|
|
1241
1516
|
} ], GUARD_PREDICATE_VARS = [ {
|
|
1242
1517
|
name: "guard",
|
|
1243
1518
|
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
1244
1519
|
}, {
|
|
1245
1520
|
name: "mutation",
|
|
1246
1521
|
description: "The attempted mutation — `mutation.action` is the write kind being gated."
|
|
1247
|
-
} ],
|
|
1522
|
+
} ], NonEmptyString = v__namespace.pipe(v__namespace.string(), v__namespace.minLength(1, "must be a non-empty string"));
|
|
1523
|
+
|
|
1524
|
+
function isParseableInstant(value) {
|
|
1525
|
+
return typeof value == "string" && !Number.isNaN(Date.parse(value));
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
const IsoTimestamp = v__namespace.pipe(v__namespace.string(), v__namespace.check(s => isParseableInstant(s), "must be an ISO-8601 datetime string"));
|
|
1529
|
+
|
|
1530
|
+
function tolerantObject() {
|
|
1531
|
+
return (entries, ..._exact) => v__namespace.looseObject(entries);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
function tolerantEntries() {
|
|
1535
|
+
return (entries, ..._exact) => entries;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
function schemaTreeShape(schema) {
|
|
1539
|
+
return walkSchemaShape(schema, /* @__PURE__ */ new Set);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
function walkSchemaShape(node, path) {
|
|
1543
|
+
if (typeof node != "object" || node === null) return "unknown";
|
|
1544
|
+
if (path.has(node)) return "(circular)";
|
|
1545
|
+
path.add(node);
|
|
1546
|
+
try {
|
|
1547
|
+
const schema = node;
|
|
1548
|
+
return containerShape(schema, path) ?? leafShape(schema);
|
|
1549
|
+
} finally {
|
|
1550
|
+
path.delete(node);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
const objectWalker = (schema, path) => objectEntriesShape(schema.entries, path), unionWalker = (schema, path) => ({
|
|
1555
|
+
union: schema.options.map(option => walkSchemaShape(option, path))
|
|
1556
|
+
}), unwrapWalker = (schema, path) => walkSchemaShape(schema.wrapped, path), CONTAINER_WALKERS = {
|
|
1557
|
+
strict_object: objectWalker,
|
|
1558
|
+
loose_object: objectWalker,
|
|
1559
|
+
object: objectWalker,
|
|
1560
|
+
array: (schema, path) => ({
|
|
1561
|
+
array: walkSchemaShape(schema.item, path)
|
|
1562
|
+
}),
|
|
1563
|
+
record: (schema, path) => ({
|
|
1564
|
+
record: walkSchemaShape(schema.value, path)
|
|
1565
|
+
}),
|
|
1566
|
+
union: unionWalker,
|
|
1567
|
+
variant: unionWalker,
|
|
1568
|
+
lazy: (schema, path) => walkSchemaShape(schema.getter(void 0), path),
|
|
1569
|
+
exact_optional: unwrapWalker,
|
|
1570
|
+
optional: unwrapWalker,
|
|
1571
|
+
nullable: unwrapWalker
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1574
|
+
function containerShape(schema, path) {
|
|
1575
|
+
return CONTAINER_WALKERS[String(schema.type)]?.(schema, path);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
function objectEntriesShape(entries, path) {
|
|
1579
|
+
const out = {};
|
|
1580
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
1581
|
+
const optional = isOptionalEntry(entry), wrapped = optional ? entry.wrapped : entry;
|
|
1582
|
+
out[optional ? `${key}?` : key] = walkSchemaShape(wrapped, path);
|
|
1583
|
+
}
|
|
1584
|
+
return Object.fromEntries(Object.entries(out).sort(([a], [b]) => a < b ? -1 : 1));
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
function isOptionalEntry(entry) {
|
|
1588
|
+
if (typeof entry != "object" || entry === null) return !1;
|
|
1589
|
+
const kind = entry.type;
|
|
1590
|
+
return kind === "exact_optional" || kind === "optional";
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
const LEAF_KINDS = /* @__PURE__ */ new Set([ "string", "number", "boolean", "null", "undefined", "unknown", "any" ]);
|
|
1594
|
+
|
|
1595
|
+
function leafShape(schema) {
|
|
1596
|
+
if (schema.type === "picklist") return schema.options.join(" | ");
|
|
1597
|
+
if (schema.type === "literal") return `literal ${String(schema.literal)}`;
|
|
1598
|
+
if (schema.type === "custom") return typeof schema.message == "string" ? `custom(${schema.message})` : "custom";
|
|
1599
|
+
if (!LEAF_KINDS.has(String(schema.type))) throw new Error(`schemaTreeShape: unhandled schema kind "${String(schema.type)}" — extend the walker before regenerating the model ledger`);
|
|
1600
|
+
return String(schema.type);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function formatValidationError(label, issues) {
|
|
1604
|
+
const lines = issues.map(issue => ` - ${issue.path.length === 0 ? "(root)" : formatIssuePath(issue.path)}: ${issue.message}`);
|
|
1605
|
+
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
function issuesFromValibot(issues) {
|
|
1609
|
+
return issues.map(issue => ({
|
|
1610
|
+
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
1611
|
+
message: issue.message
|
|
1612
|
+
}));
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
function formatIssuePath(path) {
|
|
1616
|
+
let out = "";
|
|
1617
|
+
for (const seg of path) typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
1618
|
+
return out;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
class PersistedDocShapeError extends WorkflowError {
|
|
1622
|
+
documentId;
|
|
1623
|
+
documentType;
|
|
1624
|
+
issues;
|
|
1625
|
+
constructor(args) {
|
|
1626
|
+
super("persisted-doc-shape", formatValidationError(`Persisted ${args.documentType} document "${args.documentId}"`, args.issues)),
|
|
1627
|
+
this.name = "PersistedDocShapeError", this.documentId = args.documentId, this.documentType = args.documentType,
|
|
1628
|
+
this.issues = args.issues;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
function parsePersistedDoc(args) {
|
|
1633
|
+
const result = v__namespace.safeParse(args.schema, args.doc);
|
|
1634
|
+
if (!result.success) throw new PersistedDocShapeError({
|
|
1635
|
+
documentId: documentIdOf(args.doc),
|
|
1636
|
+
documentType: args.docType,
|
|
1637
|
+
issues: issuesFromValibot(result.issues)
|
|
1638
|
+
});
|
|
1639
|
+
return result.output;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
function documentIdOf(doc) {
|
|
1643
|
+
if (typeof doc == "object" && doc !== null) {
|
|
1644
|
+
const id = doc._id;
|
|
1645
|
+
if (typeof id == "string") return id;
|
|
1646
|
+
}
|
|
1647
|
+
return "(unknown id)";
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
const ACTOR_KINDS = [ "person", "agent", "system" ];
|
|
1248
1651
|
|
|
1249
1652
|
function releaseDocId(releaseName) {
|
|
1250
1653
|
return `_.releases.${releaseName}`;
|
|
@@ -1259,6 +1662,18 @@ function releaseRef({res: res, releaseName: releaseName}) {
|
|
|
1259
1662
|
};
|
|
1260
1663
|
}
|
|
1261
1664
|
|
|
1665
|
+
function isSingleDocRefKind(kind) {
|
|
1666
|
+
return kind === "doc.ref" || kind === "subject";
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
function refKindAcceptsTypes(kind) {
|
|
1670
|
+
return isSingleDocRefKind(kind) || kind === "doc.refs";
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
function isSingleDocRefEntry(entry) {
|
|
1674
|
+
return isSingleDocRefKind(entry._type);
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1262
1677
|
function isTodoListItem(row) {
|
|
1263
1678
|
if (typeof row != "object" || row === null) return !1;
|
|
1264
1679
|
const candidate = row, status = candidate.status;
|
|
@@ -1291,29 +1706,53 @@ class FieldValueShapeError extends WorkflowError {
|
|
|
1291
1706
|
}
|
|
1292
1707
|
}
|
|
1293
1708
|
|
|
1294
|
-
const
|
|
1295
|
-
id:
|
|
1296
|
-
type:
|
|
1297
|
-
}), ReleaseRefShape = v__namespace.pipe(
|
|
1298
|
-
id:
|
|
1709
|
+
const GdrUriSchema = v__namespace.custom(s => typeof s == "string" && isGdrUri(s), "must be a GDR URI"), GdrShape = tolerantObject()({
|
|
1710
|
+
id: GdrUriSchema,
|
|
1711
|
+
type: NonEmptyString
|
|
1712
|
+
}), ReleaseRefShape = v__namespace.pipe(tolerantObject()({
|
|
1713
|
+
id: GdrUriSchema,
|
|
1299
1714
|
type: v__namespace.literal("system.release"),
|
|
1300
|
-
releaseName:
|
|
1301
|
-
}), 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 =
|
|
1715
|
+
releaseName: NonEmptyString
|
|
1716
|
+
}), 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 = tolerantObject()({
|
|
1302
1717
|
kind: v__namespace.picklist(ACTOR_KINDS),
|
|
1303
|
-
id:
|
|
1304
|
-
roles: v__namespace.
|
|
1305
|
-
onBehalfOf: v__namespace.
|
|
1306
|
-
}), AssigneeShape = v__namespace.union([
|
|
1718
|
+
id: NonEmptyString,
|
|
1719
|
+
roles: v__namespace.exactOptional(v__namespace.array(v__namespace.string())),
|
|
1720
|
+
onBehalfOf: v__namespace.exactOptional(v__namespace.string())
|
|
1721
|
+
}), AssigneeShape = v__namespace.union([ tolerantObject()({
|
|
1307
1722
|
type: v__namespace.literal("user"),
|
|
1308
|
-
id:
|
|
1309
|
-
}),
|
|
1723
|
+
id: NonEmptyString
|
|
1724
|
+
}), tolerantObject()({
|
|
1310
1725
|
type: v__namespace.literal("role"),
|
|
1311
|
-
role:
|
|
1312
|
-
}) ]), 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(),
|
|
1726
|
+
role: NonEmptyString
|
|
1727
|
+
}) ]), 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, CHOICE_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "url", "date", "datetime", "dateTime" ]);
|
|
1728
|
+
|
|
1729
|
+
function normalizedChoiceKind(kind) {
|
|
1730
|
+
return kind === "dateTime" ? "datetime" : kind;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
function checkChoiceList(args) {
|
|
1734
|
+
const {entryType: entryType, options: options, validation: validation} = args;
|
|
1735
|
+
if (options === void 0) return;
|
|
1736
|
+
if (!CHOICE_KINDS.has(entryType)) return [ `\`options\` is not valid on "${entryType}" values` ];
|
|
1737
|
+
const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => checkValueAgainst({
|
|
1738
|
+
entryType: kind,
|
|
1739
|
+
value: option.value,
|
|
1740
|
+
validation: validation
|
|
1741
|
+
}, valueSchemas)?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
|
|
1742
|
+
for (const option of options.list) seen.has(option.value) && issues.push(`duplicate option value ${JSON.stringify(option.value)}`),
|
|
1743
|
+
seen.add(option.value);
|
|
1744
|
+
return issues.length === 0 ? void 0 : issues;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
function choiceValueIssues(options, value) {
|
|
1748
|
+
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(", ")}` ];
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
const fieldValueSchemas = {
|
|
1313
1752
|
"doc.ref": v__namespace.union([ v__namespace.null(), GdrShape ]),
|
|
1314
1753
|
"doc.refs": v__namespace.array(GdrShape),
|
|
1754
|
+
subject: v__namespace.union([ v__namespace.null(), GdrShape ]),
|
|
1315
1755
|
"release.ref": v__namespace.union([ v__namespace.null(), ReleaseRefShape ]),
|
|
1316
|
-
query: v__namespace.any(),
|
|
1317
1756
|
string: NullableString,
|
|
1318
1757
|
text: NullableString,
|
|
1319
1758
|
number: NullableNumber,
|
|
@@ -1324,10 +1763,62 @@ const GdrShape = v__namespace.looseObject({
|
|
|
1324
1763
|
actor: v__namespace.union([ v__namespace.null(), ActorShape ]),
|
|
1325
1764
|
assignee: v__namespace.union([ v__namespace.null(), AssigneeShape ]),
|
|
1326
1765
|
assignees: v__namespace.array(AssigneeShape)
|
|
1766
|
+
}, valueSchemas = {
|
|
1767
|
+
...fieldValueSchemas,
|
|
1768
|
+
query: v__namespace.any()
|
|
1327
1769
|
};
|
|
1328
1770
|
|
|
1329
1771
|
function shapeValueSchema(shape, leaf) {
|
|
1330
|
-
|
|
1772
|
+
if (shape.type === "object") return objectSchema(shape.fields ?? [], leaf);
|
|
1773
|
+
if (shape.type === "array") return v__namespace.array(objectSchema(shape.of ?? [], leaf));
|
|
1774
|
+
const schema = leaf[shape.type] ?? v__namespace.any();
|
|
1775
|
+
return constrainedScalarSchema({
|
|
1776
|
+
schema: schema,
|
|
1777
|
+
entryType: shape.type,
|
|
1778
|
+
...shape
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
function scalarMeasurement(entryType, value) {
|
|
1783
|
+
if (entryType === "number" && typeof value == "number") return value;
|
|
1784
|
+
if ((entryType === "string" || entryType === "text") && typeof value == "string") return value.length;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
function scalarBoundIssue(args) {
|
|
1788
|
+
const {entryType: entryType, measured: measured, bound: bound, limit: limit} = args;
|
|
1789
|
+
return bound === void 0 || limit === "min" && measured >= bound || limit === "max" && measured <= bound ? void 0 : `${entryType === "number" ? "" : "length "}must be ${limit === "min" ? "greater than or equal to" : "less than or equal to"} ${bound}`;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
function scalarValidationIssues(args) {
|
|
1793
|
+
const {entryType: entryType, validation: validation, value: value} = args;
|
|
1794
|
+
if (validation === void 0 || value === null || value === void 0) return;
|
|
1795
|
+
const measured = scalarMeasurement(entryType, value);
|
|
1796
|
+
if (measured === void 0) return;
|
|
1797
|
+
const issues = [ scalarBoundIssue({
|
|
1798
|
+
entryType: entryType,
|
|
1799
|
+
measured: measured,
|
|
1800
|
+
bound: validation.min,
|
|
1801
|
+
limit: "min"
|
|
1802
|
+
}), scalarBoundIssue({
|
|
1803
|
+
entryType: entryType,
|
|
1804
|
+
measured: measured,
|
|
1805
|
+
bound: validation.max,
|
|
1806
|
+
limit: "max"
|
|
1807
|
+
}) ].filter(issue => issue !== void 0);
|
|
1808
|
+
return issues.length === 0 ? void 0 : issues;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
function constrainedScalarSchema(args) {
|
|
1812
|
+
const {schema: schema, entryType: entryType, options: options, validation: validation} = args;
|
|
1813
|
+
return options === void 0 && validation === void 0 ? schema : v__namespace.pipe(schema, v__namespace.check(value => choiceValueIssues(options, value) === void 0 && scalarValidationIssues({
|
|
1814
|
+
entryType: entryType,
|
|
1815
|
+
validation: validation,
|
|
1816
|
+
value: value
|
|
1817
|
+
}) === void 0, issue => [ ...choiceValueIssues(options, issue.input) ?? [], ...scalarValidationIssues({
|
|
1818
|
+
entryType: entryType,
|
|
1819
|
+
validation: validation,
|
|
1820
|
+
value: issue.input
|
|
1821
|
+
}) ?? [] ].join("; ")));
|
|
1331
1822
|
}
|
|
1332
1823
|
|
|
1333
1824
|
function objectSchema(fields, leaf) {
|
|
@@ -1350,7 +1841,7 @@ function appendItemSchema(entryType, shape) {
|
|
|
1350
1841
|
function rejectedRefTypes(args) {
|
|
1351
1842
|
const {entryType: entryType, types: types, value: value} = args;
|
|
1352
1843
|
if (types === void 0 || value === null || value === void 0) return [];
|
|
1353
|
-
if (entryType
|
|
1844
|
+
if (!refKindAcceptsTypes(entryType)) return [];
|
|
1354
1845
|
let items = [ value ];
|
|
1355
1846
|
return entryType === "doc.refs" && (items = Array.isArray(value) ? value : []),
|
|
1356
1847
|
[ ...new Set(items.map(gdrTypeOf).filter(t => t !== void 0 && !types.includes(t))) ];
|
|
@@ -1385,6 +1876,10 @@ function checkValueAgainst(args, leaf) {
|
|
|
1385
1876
|
entryType: args.entryType,
|
|
1386
1877
|
types: args.types,
|
|
1387
1878
|
value: args.value
|
|
1879
|
+
}) ?? choiceValueIssues(args.options, args.value) ?? scalarValidationIssues({
|
|
1880
|
+
entryType: args.entryType,
|
|
1881
|
+
validation: args.validation,
|
|
1882
|
+
value: args.value
|
|
1388
1883
|
}) : formatIssues(result.issues);
|
|
1389
1884
|
}
|
|
1390
1885
|
|
|
@@ -1410,15 +1905,16 @@ function isBareSeedId(id) {
|
|
|
1410
1905
|
|
|
1411
1906
|
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({
|
|
1412
1907
|
id: AuthoringRefId,
|
|
1413
|
-
type:
|
|
1908
|
+
type: NonEmptyString
|
|
1414
1909
|
}), seedValueSchemas = {
|
|
1415
1910
|
...valueSchemas,
|
|
1416
1911
|
"doc.ref": v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
|
|
1417
1912
|
"doc.refs": v__namespace.array(AuthoringGdrShape),
|
|
1913
|
+
subject: v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
|
|
1418
1914
|
"release.ref": v__namespace.union([ v__namespace.null(), v__namespace.looseObject({
|
|
1419
1915
|
id: AuthoringRefId,
|
|
1420
1916
|
type: v__namespace.literal("system.release"),
|
|
1421
|
-
releaseName:
|
|
1917
|
+
releaseName: NonEmptyString
|
|
1422
1918
|
}) ])
|
|
1423
1919
|
};
|
|
1424
1920
|
|
|
@@ -1454,14 +1950,14 @@ function validateFieldAppendItem(args) {
|
|
|
1454
1950
|
});
|
|
1455
1951
|
}
|
|
1456
1952
|
|
|
1457
|
-
function formatIssues(issues) {
|
|
1953
|
+
function formatIssues(issues, formatMessage = issue => issue.message) {
|
|
1458
1954
|
return issues.map(i => {
|
|
1459
1955
|
const keys = i.path?.map(p => p.key) ?? [];
|
|
1460
|
-
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i
|
|
1956
|
+
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(i)}`;
|
|
1461
1957
|
});
|
|
1462
1958
|
}
|
|
1463
1959
|
|
|
1464
|
-
const NonEmpty =
|
|
1960
|
+
const NonEmpty = NonEmptyString, PositiveInt = v__namespace.pipe(v__namespace.number(), v__namespace.integer(), v__namespace.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1465
1961
|
|
|
1466
1962
|
function groqIdentifier(referencedAs) {
|
|
1467
1963
|
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`));
|
|
@@ -1583,7 +2079,15 @@ function groupMembershipNames(group) {
|
|
|
1583
2079
|
return group === void 0 ? [] : typeof group == "string" ? [ group ] : [ ...group ];
|
|
1584
2080
|
}
|
|
1585
2081
|
|
|
1586
|
-
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>`")
|
|
2082
|
+
const FIELD_VALUE_KINDS = [ "doc.ref", "doc.refs", "subject", "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>`"), FiniteNumber = v__namespace.pipe(v__namespace.number(), v__namespace.finite("must be finite")), ScalarValidationSchema = v__namespace.pipe(v__namespace.strictObject({
|
|
2083
|
+
min: v__namespace.optional(FiniteNumber),
|
|
2084
|
+
max: v__namespace.optional(FiniteNumber)
|
|
2085
|
+
}), 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({
|
|
2086
|
+
list: v__namespace.pipe(v__namespace.array(v__namespace.strictObject({
|
|
2087
|
+
title: NonEmpty,
|
|
2088
|
+
value: v__namespace.union([ v__namespace.string(), v__namespace.number() ])
|
|
2089
|
+
})), v__namespace.minLength(1, "declare at least one choice, or omit `options`"))
|
|
2090
|
+
});
|
|
1587
2091
|
|
|
1588
2092
|
function asShape(input) {
|
|
1589
2093
|
return typeof input == "object" && input !== null ? input : {};
|
|
@@ -1617,14 +2121,16 @@ function compositeChecked(entries) {
|
|
|
1617
2121
|
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\``));
|
|
1618
2122
|
}
|
|
1619
2123
|
|
|
1620
|
-
const FieldShapeSchema = v__namespace.lazy(() => compositeChecked({
|
|
2124
|
+
const FieldShapeSchema = v__namespace.lazy(() => v__namespace.pipe(compositeChecked({
|
|
1621
2125
|
type: FieldValueKindSchema,
|
|
1622
2126
|
name: FieldEntryName,
|
|
1623
2127
|
title: v__namespace.optional(v__namespace.string()),
|
|
1624
2128
|
description: v__namespace.optional(v__namespace.string()),
|
|
2129
|
+
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
2130
|
+
validation: v__namespace.optional(ScalarValidationSchema),
|
|
1625
2131
|
fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
1626
2132
|
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
1627
|
-
})), StoredEditableSchema = v__namespace.union([ v__namespace.literal(!0), NonEmpty ]), AuthoringEditableSchema = v__namespace.union([ v__namespace.literal(!0), v__namespace.array(NonEmpty), NonEmpty ]);
|
|
2133
|
+
}), choiceOptionsCheck(), scalarValidationCheck())), StoredEditableSchema = v__namespace.union([ v__namespace.literal(!0), NonEmpty ]), AuthoringEditableSchema = v__namespace.union([ v__namespace.literal(!0), v__namespace.array(NonEmpty), NonEmpty ]);
|
|
1628
2134
|
|
|
1629
2135
|
function fieldBase(editable, group) {
|
|
1630
2136
|
return {
|
|
@@ -1642,6 +2148,8 @@ function fieldEntryFields(editable, group) {
|
|
|
1642
2148
|
return {
|
|
1643
2149
|
type: FieldKindSchema,
|
|
1644
2150
|
...fieldBase(editable, group),
|
|
2151
|
+
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
2152
|
+
validation: v__namespace.optional(ScalarValidationSchema),
|
|
1645
2153
|
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"))),
|
|
1646
2154
|
fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
|
|
1647
2155
|
of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
|
|
@@ -1654,7 +2162,9 @@ function literalSeedIssues(entry) {
|
|
|
1654
2162
|
value: entry.initialValue.value,
|
|
1655
2163
|
types: entry.types,
|
|
1656
2164
|
fields: entry.fields,
|
|
1657
|
-
of: entry.of
|
|
2165
|
+
of: entry.of,
|
|
2166
|
+
options: entry.options,
|
|
2167
|
+
validation: entry.validation
|
|
1658
2168
|
});
|
|
1659
2169
|
}
|
|
1660
2170
|
|
|
@@ -1663,10 +2173,37 @@ function literalSeedCheck() {
|
|
|
1663
2173
|
}
|
|
1664
2174
|
|
|
1665
2175
|
function refTypesCheck() {
|
|
1666
|
-
return v__namespace.check(entry => entry.types === void 0 || entry.type
|
|
2176
|
+
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}"`);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
function choiceOptionsCheck() {
|
|
2180
|
+
return v__namespace.check(entry => checkChoiceList({
|
|
2181
|
+
entryType: entry.type,
|
|
2182
|
+
options: entry.options,
|
|
2183
|
+
validation: entry.validation
|
|
2184
|
+
}) === void 0, issue => (checkChoiceList({
|
|
2185
|
+
entryType: issue.input.type,
|
|
2186
|
+
options: issue.input.options,
|
|
2187
|
+
validation: issue.input.validation
|
|
2188
|
+
}) ?? []).join("; "));
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
const SCALAR_VALIDATION_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number" ]);
|
|
2192
|
+
|
|
2193
|
+
function scalarValidationCheck() {
|
|
2194
|
+
return v__namespace.check(entry => scalarValidationDeclarationIssues(entry) === void 0, issue => (scalarValidationDeclarationIssues(issue.input) ?? []).join("; "));
|
|
1667
2195
|
}
|
|
1668
2196
|
|
|
1669
|
-
|
|
2197
|
+
function scalarValidationDeclarationIssues(entry) {
|
|
2198
|
+
const {type: type, validation: validation} = entry;
|
|
2199
|
+
if (validation === void 0) return;
|
|
2200
|
+
if (!SCALAR_VALIDATION_KINDS.has(type)) return [ `\`validation\` is only valid on \`string\` / \`text\` / \`number\` values, not "${type}"` ];
|
|
2201
|
+
if (type === "number") return;
|
|
2202
|
+
const issues = Object.entries(validation).flatMap(([bound, value]) => Number.isInteger(value) && value >= 0 ? [] : [ `\`validation.${bound}\` must be a non-negative integer for ${type} length` ]);
|
|
2203
|
+
return issues.length === 0 ? void 0 : issues;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
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({
|
|
1670
2207
|
type: v__namespace.literal("claim"),
|
|
1671
2208
|
name: FieldEntryName,
|
|
1672
2209
|
title: v__namespace.optional(v__namespace.string()),
|
|
@@ -1697,17 +2234,25 @@ const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("
|
|
|
1697
2234
|
with: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1698
2235
|
context: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
|
|
1699
2236
|
onExit: v__namespace.optional(picklist([ "detach", "abort" ]))
|
|
1700
|
-
}), ActionParamSchema = v__namespace.strictObject({
|
|
2237
|
+
}), ActionParamSchema = v__namespace.pipe(v__namespace.strictObject({
|
|
1701
2238
|
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
1702
2239
|
name: NonEmpty,
|
|
1703
2240
|
title: v__namespace.optional(v__namespace.string()),
|
|
1704
2241
|
description: v__namespace.optional(v__namespace.string()),
|
|
1705
|
-
required: v__namespace.optional(v__namespace.boolean())
|
|
1706
|
-
|
|
2242
|
+
required: v__namespace.optional(v__namespace.boolean()),
|
|
2243
|
+
options: v__namespace.optional(ChoiceOptionsSchema),
|
|
2244
|
+
validation: v__namespace.optional(ScalarValidationSchema)
|
|
2245
|
+
}), choiceOptionsCheck(), scalarValidationCheck());
|
|
2246
|
+
|
|
2247
|
+
function hasUniqueSemanticNamespaces(semantics) {
|
|
2248
|
+
const namespaces = semantics.map(semantic => semantic.split(".", 1)[0]);
|
|
2249
|
+
return new Set(namespaces).size === namespaces.length;
|
|
2250
|
+
}
|
|
1707
2251
|
|
|
1708
2252
|
function actionFields(op, group) {
|
|
1709
2253
|
return {
|
|
1710
2254
|
name: NonEmpty,
|
|
2255
|
+
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"))),
|
|
1711
2256
|
title: v__namespace.optional(v__namespace.string()),
|
|
1712
2257
|
description: v__namespace.optional(v__namespace.string()),
|
|
1713
2258
|
group: v__namespace.optional(group),
|
|
@@ -1844,7 +2389,8 @@ const StoredStageSchema = pinned()(v__namespace.strictObject(stageFields({
|
|
|
1844
2389
|
function startFields(kind) {
|
|
1845
2390
|
return {
|
|
1846
2391
|
kind: kind,
|
|
1847
|
-
filter: v__namespace.optional(ConditionSchema)
|
|
2392
|
+
filter: v__namespace.optional(ConditionSchema),
|
|
2393
|
+
allowed: v__namespace.optional(ConditionSchema)
|
|
1848
2394
|
};
|
|
1849
2395
|
}
|
|
1850
2396
|
|
|
@@ -1895,31 +2441,13 @@ function startKindOf(definition) {
|
|
|
1895
2441
|
}
|
|
1896
2442
|
|
|
1897
2443
|
function isSubjectEntry(entry) {
|
|
1898
|
-
return entry.
|
|
2444
|
+
return entry.type === "subject";
|
|
1899
2445
|
}
|
|
1900
2446
|
|
|
1901
2447
|
function isInputSourced(entry) {
|
|
1902
2448
|
return entry.initialValue?.type === "input";
|
|
1903
2449
|
}
|
|
1904
2450
|
|
|
1905
|
-
function formatValidationError(label, issues) {
|
|
1906
|
-
const lines = issues.map(issue => ` - ${issue.path.length === 0 ? "(root)" : formatIssuePath(issue.path)}: ${issue.message}`);
|
|
1907
|
-
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
function issuesFromValibot(issues) {
|
|
1911
|
-
return issues.map(issue => ({
|
|
1912
|
-
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
1913
|
-
message: issue.message
|
|
1914
|
-
}));
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
function formatIssuePath(path) {
|
|
1918
|
-
let out = "";
|
|
1919
|
-
for (const seg of path) typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
1920
|
-
return out;
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
2451
|
function parseOrThrow({schema: schema, input: input, label: label}) {
|
|
1924
2452
|
const result = v__namespace.safeParse(schema, input);
|
|
1925
2453
|
if (!result.success) throw new Error(formatValidationError(label, issuesFromValibot(result.issues)));
|
|
@@ -1948,6 +2476,13 @@ function checkDuplicates({names: names, what: what, issues: issues}) {
|
|
|
1948
2476
|
return seen;
|
|
1949
2477
|
}
|
|
1950
2478
|
|
|
2479
|
+
function checkDefinitionName(def, issues) {
|
|
2480
|
+
LAKE_ID_SEGMENT_RE.test(def.name) || issues.push({
|
|
2481
|
+
path: [ "name" ],
|
|
2482
|
+
message: `definition name "${def.name}" does not match the definition-name grammar ${LAKE_ID_SEGMENT_RE.source} (${LAKE_ID_SEGMENT_GLOSS}) — the name is interpolated into every deployed document id (\`<tag>.<name>.v<version>\`), which the engine keeps to the same segment grammar as the tag`
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
|
|
1951
2486
|
function checkStages(def, issues) {
|
|
1952
2487
|
const stageNames = checkDuplicates({
|
|
1953
2488
|
names: def.stages.map((stage, i) => ({
|
|
@@ -2084,6 +2619,10 @@ function checkGuardNames(def, issues) {
|
|
|
2084
2619
|
what: "guard name (the guard lake _id derives from it — unique per definition)",
|
|
2085
2620
|
issues: issues
|
|
2086
2621
|
});
|
|
2622
|
+
for (const {name: name, path: path} of sites) LAKE_ID_SEGMENT_RE.test(name) || issues.push({
|
|
2623
|
+
path: path,
|
|
2624
|
+
message: `guard name "${name}" does not match the guard-name grammar ${LAKE_ID_SEGMENT_RE.source} (${LAKE_ID_SEGMENT_GLOSS}) — the guard's lake _id derives from it at stage entry, so an invalid name would wedge the entering commit with an opaque lake error instead of failing here`
|
|
2625
|
+
});
|
|
2087
2626
|
}
|
|
2088
2627
|
|
|
2089
2628
|
function fieldScopes(def) {
|
|
@@ -2154,7 +2693,11 @@ function checkPredicateBody({name: name, groq: groq2, names: names, issues: issu
|
|
|
2154
2693
|
const reads = conditionParameterNames(groq2);
|
|
2155
2694
|
for (const caller of CALLER_BOUND_VARS) reads.has(caller) && issues.push({
|
|
2156
2695
|
path: [ "predicates", name ],
|
|
2157
|
-
message: `predicate "${name}" reads $${caller} — predicates
|
|
2696
|
+
message: `predicate "${name}" reads $${caller} — predicates pre-evaluate caller-free; compose caller gates at the condition site instead`
|
|
2697
|
+
});
|
|
2698
|
+
reads.has("row") && issues.push({
|
|
2699
|
+
path: [ "predicates", name ],
|
|
2700
|
+
message: `predicate "${name}" reads $row — ${ROW_BINDING_CLAUSE}; predicates pre-evaluate once per instance, so the read is GROQ null and the predicate can never hold`
|
|
2158
2701
|
});
|
|
2159
2702
|
for (const other of names) other === name || !reads.has(other) || issues.push({
|
|
2160
2703
|
path: [ "predicates", name ],
|
|
@@ -2162,24 +2705,35 @@ function checkPredicateBody({name: name, groq: groq2, names: names, issues: issu
|
|
|
2162
2705
|
});
|
|
2163
2706
|
}
|
|
2164
2707
|
|
|
2165
|
-
function
|
|
2166
|
-
return new
|
|
2708
|
+
function entryMap(entries) {
|
|
2709
|
+
return new Map((entries ?? []).map(entry => [ entry.name, entry ]));
|
|
2167
2710
|
}
|
|
2168
2711
|
|
|
2169
|
-
function
|
|
2712
|
+
function checkUnboundConditionVars(def, issues) {
|
|
2170
2713
|
for (const site of conditionSites(def)) {
|
|
2171
2714
|
const reads = conditionParameterNames(site.groq);
|
|
2172
|
-
for (const name of
|
|
2715
|
+
for (const name of unboundVarsAt(site)) reads.has(name) && issues.push({
|
|
2173
2716
|
path: site.path,
|
|
2174
|
-
message: callerVarMessage(site, name)
|
|
2717
|
+
message: name === "row" ? rowVarMessage(site) : callerVarMessage(site, name)
|
|
2175
2718
|
});
|
|
2176
2719
|
}
|
|
2177
2720
|
}
|
|
2178
2721
|
|
|
2722
|
+
function unboundVarsAt(site) {
|
|
2723
|
+
const callerVars = unboundCallerVars(site.policy);
|
|
2724
|
+
return site.bindsRow === !0 ? callerVars : [ ...callerVars, "row" ];
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2179
2727
|
function unboundCallerVars(policy) {
|
|
2180
2728
|
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : policy === "triggered-payload" ? [ "can", "params" ] : [ "can" ];
|
|
2181
2729
|
}
|
|
2182
2730
|
|
|
2731
|
+
const ROW_BINDING_CLAUSE = "$row (the discovered row in a spawn projection; the stored row under test in a where-op) is bound only while a spawn `with` projection or a where-op `where` evaluates";
|
|
2732
|
+
|
|
2733
|
+
function rowVarMessage(site) {
|
|
2734
|
+
return `${site.label} reads $row — ${ROW_BINDING_CLAUSE}; this site never binds it, so the read is GROQ null and the condition can never resolve. Move the per-row read into \`spawn.with\` or a where-op, or gate on instance state instead`;
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2183
2737
|
function callerVarMessage(site, name) {
|
|
2184
2738
|
return site.policy === "cascade" ? `${site.label} reads $${name} — cascade gates (transition \`when\`s, activity \`filter\`s, a cascade-fired action's \`when\`/\`filter\`) must resolve identically no matter whose token drives the cascade ($assigned is constant false, the other caller vars hold no value); gate on instance state (e.g. a field an action wrote), or pin executing identities with \`roles\`` : 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.policy === "triggered-payload" && name === "params" ? `${site.label} reads $params — a cascade-fired action has no caller to supply args, so $params never holds a value in its payload; read fields or effect outputs instead` : `${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`;
|
|
2185
2739
|
}
|
|
@@ -2187,7 +2741,7 @@ function callerVarMessage(site, name) {
|
|
|
2187
2741
|
function conditionSites(def) {
|
|
2188
2742
|
const sites = [], workflowLayer = {
|
|
2189
2743
|
scope: "workflow",
|
|
2190
|
-
|
|
2744
|
+
entries: entryMap(def.fields)
|
|
2191
2745
|
};
|
|
2192
2746
|
collectEditableSites({
|
|
2193
2747
|
entries: def.fields,
|
|
@@ -2208,10 +2762,10 @@ function conditionSites(def) {
|
|
|
2208
2762
|
function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflowLayer, sites: sites}) {
|
|
2209
2763
|
const stageLayer = {
|
|
2210
2764
|
scope: "stage",
|
|
2211
|
-
|
|
2765
|
+
entries: entryMap(stage.fields)
|
|
2212
2766
|
}, stageFields2 = [ stageLayer, workflowLayer ], editableWindow = [ stageLayer, workflowLayer, ...(stage.activities ?? []).map(activity => ({
|
|
2213
2767
|
scope: "activity",
|
|
2214
|
-
|
|
2768
|
+
entries: entryMap(activity.fields)
|
|
2215
2769
|
})) ];
|
|
2216
2770
|
for (const [k, t] of (stage.transitions ?? []).entries()) sites.push({
|
|
2217
2771
|
groq: t.when,
|
|
@@ -2225,6 +2779,7 @@ function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflow
|
|
|
2225
2779
|
path: [ "stages", i, "fields" ],
|
|
2226
2780
|
labelPrefix: `stage "${stage.name}"`,
|
|
2227
2781
|
fields: editableWindow,
|
|
2782
|
+
unionWindow: !0,
|
|
2228
2783
|
sites: sites
|
|
2229
2784
|
});
|
|
2230
2785
|
for (const [name, editable] of Object.entries(stage.editable ?? {})) typeof editable == "string" && sites.push({
|
|
@@ -2232,23 +2787,28 @@ function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflow
|
|
|
2232
2787
|
path: [ "stages", i, "editable", name ],
|
|
2233
2788
|
label: `stage "${stage.name}" editable override "${name}"`,
|
|
2234
2789
|
policy: "caller-bound",
|
|
2235
|
-
fields: editableWindow
|
|
2790
|
+
fields: editableWindow,
|
|
2791
|
+
unionWindow: !0
|
|
2236
2792
|
});
|
|
2237
2793
|
for (const [j, activity] of (stage.activities ?? []).entries()) collectActivityConditionSites({
|
|
2238
2794
|
activity: activity,
|
|
2239
2795
|
path: [ "stages", i, "activities", j ],
|
|
2240
2796
|
stageFields: stageFields2,
|
|
2797
|
+
workflowLayer: workflowLayer,
|
|
2241
2798
|
sites: sites
|
|
2242
2799
|
});
|
|
2243
2800
|
}
|
|
2244
2801
|
|
|
2245
|
-
function collectEditableSites({entries: entries, path: path, labelPrefix: labelPrefix, fields: fields, sites: sites}) {
|
|
2802
|
+
function collectEditableSites({entries: entries, path: path, labelPrefix: labelPrefix, fields: fields, unionWindow: unionWindow, sites: sites}) {
|
|
2246
2803
|
for (const [n, entry] of (entries ?? []).entries()) typeof entry.editable == "string" && sites.push({
|
|
2247
2804
|
groq: entry.editable,
|
|
2248
2805
|
path: [ ...path, n, "editable" ],
|
|
2249
2806
|
label: `${labelPrefix} field "${entry.name}" editable`,
|
|
2250
2807
|
policy: "caller-bound",
|
|
2251
|
-
fields: fields
|
|
2808
|
+
fields: fields,
|
|
2809
|
+
...unionWindow === !0 ? {
|
|
2810
|
+
unionWindow: !0
|
|
2811
|
+
} : {}
|
|
2252
2812
|
});
|
|
2253
2813
|
}
|
|
2254
2814
|
|
|
@@ -2260,14 +2820,15 @@ function collectOpWhereSites({ops: ops, path: path, label: label, policy: policy
|
|
|
2260
2820
|
...policy !== void 0 ? {
|
|
2261
2821
|
policy: policy
|
|
2262
2822
|
} : {},
|
|
2823
|
+
bindsRow: !0,
|
|
2263
2824
|
fields: fields
|
|
2264
2825
|
});
|
|
2265
2826
|
}
|
|
2266
2827
|
|
|
2267
|
-
function collectActivityConditionSites({activity: activity, path: path, stageFields: stageFields2, sites: sites}) {
|
|
2828
|
+
function collectActivityConditionSites({activity: activity, path: path, stageFields: stageFields2, workflowLayer: workflowLayer, sites: sites}) {
|
|
2268
2829
|
const fields = [ {
|
|
2269
2830
|
scope: "activity",
|
|
2270
|
-
|
|
2831
|
+
entries: entryMap(activity.fields)
|
|
2271
2832
|
}, ...stageFields2 ];
|
|
2272
2833
|
activity.filter !== void 0 && sites.push({
|
|
2273
2834
|
groq: activity.filter,
|
|
@@ -2293,11 +2854,12 @@ function collectActivityConditionSites({activity: activity, path: path, stageFie
|
|
|
2293
2854
|
activity: activity,
|
|
2294
2855
|
path: path,
|
|
2295
2856
|
fields: fields,
|
|
2857
|
+
workflowLayer: workflowLayer,
|
|
2296
2858
|
sites: sites
|
|
2297
2859
|
});
|
|
2298
2860
|
}
|
|
2299
2861
|
|
|
2300
|
-
function collectActionConditionSites({activity: activity, path: path, fields: fields, sites: sites}) {
|
|
2862
|
+
function collectActionConditionSites({activity: activity, path: path, fields: fields, workflowLayer: workflowLayer, sites: sites}) {
|
|
2301
2863
|
for (const [a, action] of (activity.actions ?? []).entries()) {
|
|
2302
2864
|
const actionPath = [ ...path, "actions", a ], cascadeFired = action.when !== void 0;
|
|
2303
2865
|
action.when !== void 0 && sites.push({
|
|
@@ -2332,12 +2894,13 @@ function collectActionConditionSites({activity: activity, path: path, fields: fi
|
|
|
2332
2894
|
action: action,
|
|
2333
2895
|
path: actionPath,
|
|
2334
2896
|
fields: fields,
|
|
2897
|
+
workflowLayer: workflowLayer,
|
|
2335
2898
|
sites: sites
|
|
2336
2899
|
});
|
|
2337
2900
|
}
|
|
2338
2901
|
}
|
|
2339
2902
|
|
|
2340
|
-
function collectSpawnSites({action: action, path: path, fields: fields, sites: sites}) {
|
|
2903
|
+
function collectSpawnSites({action: action, path: path, fields: fields, workflowLayer: workflowLayer, sites: sites}) {
|
|
2341
2904
|
const spawn = action.spawn;
|
|
2342
2905
|
if (spawn === void 0) return;
|
|
2343
2906
|
const spawnPath = [ ...path, "spawn" ];
|
|
@@ -2346,13 +2909,17 @@ function collectSpawnSites({action: action, path: path, fields: fields, sites: s
|
|
|
2346
2909
|
path: [ ...spawnPath, "forEach" ],
|
|
2347
2910
|
label: `action "${action.name}".spawn.forEach`,
|
|
2348
2911
|
policy: "cascade",
|
|
2349
|
-
fields:
|
|
2912
|
+
fields: [ workflowLayer ],
|
|
2913
|
+
windowNote: "spawn.forEach evaluates against workflow-scope $fields only — stage/activity fields are never bound at discovery time"
|
|
2350
2914
|
});
|
|
2351
|
-
for (const [group, record] of [ [ "with", spawn.with ], [ "context", spawn.context ] ]) for (const [key, groq2] of Object.entries(record ?? {})) sites.push({
|
|
2915
|
+
for (const [group, record, bindsRow] of [ [ "with", spawn.with, !0 ], [ "context", spawn.context, !1 ] ]) for (const [key, groq2] of Object.entries(record ?? {})) sites.push({
|
|
2352
2916
|
groq: groq2,
|
|
2353
2917
|
path: [ ...spawnPath, group, key ],
|
|
2354
2918
|
label: `action "${action.name}".spawn.${group} "${key}"`,
|
|
2355
2919
|
policy: "triggered-payload",
|
|
2920
|
+
...bindsRow ? {
|
|
2921
|
+
bindsRow: !0
|
|
2922
|
+
} : {},
|
|
2356
2923
|
fields: fields
|
|
2357
2924
|
});
|
|
2358
2925
|
}
|
|
@@ -2376,6 +2943,52 @@ function checkAssigneesEntries(def, issues) {
|
|
|
2376
2943
|
});
|
|
2377
2944
|
}
|
|
2378
2945
|
|
|
2946
|
+
function nestedSubjectPaths(shapes, base) {
|
|
2947
|
+
return (shapes ?? []).flatMap((shape, i) => isSubjectEntry(shape) ? [ [ ...base, i, "type" ] ] : [ ...nestedSubjectPaths(shape.fields, [ ...base, i, "fields" ]), ...nestedSubjectPaths(shape.of, [ ...base, i, "of" ]) ]);
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
function checkSubjectEffectOutputs(def, issues) {
|
|
2951
|
+
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({
|
|
2952
|
+
action: action,
|
|
2953
|
+
path: [ "stages", i, "activities", j, "actions", a ],
|
|
2954
|
+
issues: issues
|
|
2955
|
+
});
|
|
2956
|
+
}
|
|
2957
|
+
|
|
2958
|
+
function pushSubjectOutputIssues(args) {
|
|
2959
|
+
const {action: action, path: path, issues: issues} = args;
|
|
2960
|
+
for (const [e, effect] of (action.effects ?? []).entries()) {
|
|
2961
|
+
const base = [ ...path, "effects", e, "outputs" ];
|
|
2962
|
+
for (const nestedPath of nestedSubjectPaths(effect.outputs, base)) issues.push({
|
|
2963
|
+
path: nestedPath,
|
|
2964
|
+
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`
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
function checkSubjectEntries(def, issues) {
|
|
2970
|
+
for (const {entries: entries, scope: scope, path: path, label: label} of fieldScopes(def)) {
|
|
2971
|
+
const subjects = (entries ?? []).flatMap((entry, n) => isSubjectEntry(entry) ? [ {
|
|
2972
|
+
entry: entry,
|
|
2973
|
+
n: n
|
|
2974
|
+
} ] : []);
|
|
2975
|
+
if (scope !== "workflow") for (const {entry: entry, n: n} of subjects) issues.push({
|
|
2976
|
+
path: [ ...path, n, "type" ],
|
|
2977
|
+
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`
|
|
2978
|
+
}); else for (const {n: n} of subjects.slice(1)) issues.push({
|
|
2979
|
+
path: [ ...path, n ],
|
|
2980
|
+
message: "at most one subject-kind field entry per workflow — the runtime identifies THE subject by kind, and a second one makes it ambiguous"
|
|
2981
|
+
});
|
|
2982
|
+
for (const [n, entry] of (entries ?? []).entries()) {
|
|
2983
|
+
const nested = [ ...nestedSubjectPaths(entry.fields, [ ...path, n, "fields" ]), ...nestedSubjectPaths(entry.of, [ ...path, n, "of" ]) ];
|
|
2984
|
+
for (const nestedPath of nested) issues.push({
|
|
2985
|
+
path: nestedPath,
|
|
2986
|
+
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`
|
|
2987
|
+
});
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2379
2992
|
function checkActivityTerminalPaths(def, issues) {
|
|
2380
2993
|
for (const [i, stage] of def.stages.entries()) {
|
|
2381
2994
|
const resolvable = terminallyResolvableActivities(stage);
|
|
@@ -2426,23 +3039,69 @@ function checkStart(def, issues) {
|
|
|
2426
3039
|
if (def.start !== void 0 && (def.lifecycle === "child" && issues.push({
|
|
2427
3040
|
path: [ "start" ],
|
|
2428
3041
|
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'`"
|
|
2429
|
-
}), checkStartFilterReads(def, issues), def
|
|
3042
|
+
}), checkStartFilterReads(def, issues), checkStartAllowedReads(def, issues), checkStartSubjectVariable(def, issues),
|
|
3043
|
+
def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
|
|
2430
3044
|
path: [ "fields", n, "required" ],
|
|
2431
|
-
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
|
|
3045
|
+
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)`
|
|
2432
3046
|
});
|
|
2433
3047
|
}
|
|
2434
3048
|
|
|
3049
|
+
function checkStartSubjectVariable(def, issues) {
|
|
3050
|
+
const subject = (def.fields ?? []).find(isSubjectEntry);
|
|
3051
|
+
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)) {
|
|
3052
|
+
if (subject === void 0) {
|
|
3053
|
+
issues.push({
|
|
3054
|
+
path: [ "start", key ],
|
|
3055
|
+
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`
|
|
3056
|
+
});
|
|
3057
|
+
continue;
|
|
3058
|
+
}
|
|
3059
|
+
key === "allowed" && !isInputSourced(subject) && issues.push({
|
|
3060
|
+
path: [ "start", key ],
|
|
3061
|
+
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`
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
|
|
2435
3066
|
function checkStartFilterReads(def, issues) {
|
|
2436
3067
|
const filter = def.start?.filter;
|
|
2437
|
-
|
|
2438
|
-
const bindable = new Set((def.fields ?? []).filter(isInputSourced).map(entry => entry.name)), declared = new Set((def.fields ?? []).map(entry => entry.name));
|
|
2439
|
-
for (const name of conditionFieldReadNames(filter)) bindable.has(name) || issues.push({
|
|
3068
|
+
filter !== void 0 && (conditionParameterNames(filter).has("fields") && issues.push({
|
|
2440
3069
|
path: [ "start", "filter" ],
|
|
2441
|
-
message:
|
|
2442
|
-
})
|
|
2443
|
-
readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
3070
|
+
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"
|
|
3071
|
+
}), readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
2444
3072
|
path: [ "start", "filter" ],
|
|
2445
|
-
message: "start.filter reads the candidate document (its root), but the definition declares no
|
|
3073
|
+
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"
|
|
3074
|
+
}));
|
|
3075
|
+
}
|
|
3076
|
+
|
|
3077
|
+
function checkStartAllowedReads(def, issues) {
|
|
3078
|
+
const allowed = def.start?.allowed;
|
|
3079
|
+
if (allowed === void 0) return;
|
|
3080
|
+
const bindable = new Map((def.fields ?? []).filter(isInputSourced).map(entry => [ entry.name, entry ])), declared = new Set((def.fields ?? []).map(entry => entry.name)), nullVerdict = `so the read is GROQ null and null semantics decide the verdict (refuse-all or vacuously-allow, by the expression's shape), never the caller's real input. Bindable (input) fields: ${knownList(bindable.keys())}`;
|
|
3081
|
+
for (const read of conditionFieldReads(allowed)) {
|
|
3082
|
+
const entry = bindable.get(read.name);
|
|
3083
|
+
if (entry === void 0) {
|
|
3084
|
+
issues.push({
|
|
3085
|
+
path: [ "start", "allowed" ],
|
|
3086
|
+
message: declared.has(read.name) ? `start.allowed reads $fields.${read.name}, but "${read.name}" is not an \`input\`-sourced entry — $fields binds only the caller's input entries (query/literal/fieldRead entries resolve at materialisation, after the start gate), ${nullVerdict}` : `start.allowed reads $fields.${read.name}, but no workflow-scope field entry named "${read.name}" is declared — $fields binds only the caller's input entries, ${nullVerdict}`
|
|
3087
|
+
});
|
|
3088
|
+
continue;
|
|
3089
|
+
}
|
|
3090
|
+
pushFieldReadPathIssue({
|
|
3091
|
+
where: "start.allowed",
|
|
3092
|
+
read: {
|
|
3093
|
+
field: read.name,
|
|
3094
|
+
path: read.path
|
|
3095
|
+
},
|
|
3096
|
+
target: entry,
|
|
3097
|
+
path: [ "start", "allowed" ],
|
|
3098
|
+
issues: issues,
|
|
3099
|
+
nodes: START_ALLOWED_VALUE_NODES
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
readsRootDocument(allowed) && issues.push({
|
|
3103
|
+
path: [ "start", "allowed" ],
|
|
3104
|
+
message: "start.allowed reads the candidate document as its root, but no root ever binds in the start-allowed context — startInstance holds inputs, not a loaded document, so the read is GROQ null and null semantics decide the verdict, never the workflow's real state. Read the subject as $fields.<entry> (its GDR URI is $fields.<entry>.id); a per-document visibility rule belongs in start.filter"
|
|
2446
3105
|
});
|
|
2447
3106
|
}
|
|
2448
3107
|
|
|
@@ -2553,39 +3212,80 @@ function checkGroupMembership({group: group, reachable: reachable, path: path, l
|
|
|
2553
3212
|
}
|
|
2554
3213
|
|
|
2555
3214
|
function checkConditionFieldReads(def, issues) {
|
|
2556
|
-
const everywhere =
|
|
2557
|
-
for (const site of conditionSites(def))
|
|
3215
|
+
const everywhere = allDeclaredFieldEntries(def);
|
|
3216
|
+
for (const site of conditionSites(def)) checkSiteFieldReads({
|
|
2558
3217
|
site: site,
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
}) || issues.push({
|
|
2562
|
-
path: site.path,
|
|
2563
|
-
message: undeclaredFieldReadMessage(site, name)
|
|
3218
|
+
everywhere: everywhere,
|
|
3219
|
+
issues: issues
|
|
2564
3220
|
});
|
|
2565
|
-
for (const [name, groq2] of Object.entries(def.predicates ?? {}))
|
|
2566
|
-
|
|
2567
|
-
|
|
3221
|
+
for (const [name, groq2] of Object.entries(def.predicates ?? {})) checkSiteFieldReads({
|
|
3222
|
+
site: {
|
|
3223
|
+
groq: groq2,
|
|
3224
|
+
path: [ "predicates", name ],
|
|
3225
|
+
label: `predicate "${name}"`,
|
|
3226
|
+
fields: "context-dependent"
|
|
3227
|
+
},
|
|
3228
|
+
everywhere: everywhere,
|
|
3229
|
+
issues: issues
|
|
2568
3230
|
});
|
|
2569
3231
|
}
|
|
2570
3232
|
|
|
2571
|
-
function
|
|
2572
|
-
const
|
|
2573
|
-
|
|
2574
|
-
|
|
3233
|
+
function checkSiteFieldReads({site: site, everywhere: everywhere, issues: issues}) {
|
|
3234
|
+
for (const read of conditionFieldReads(site.groq)) {
|
|
3235
|
+
const targets = readTargets({
|
|
3236
|
+
site: site,
|
|
3237
|
+
read: read,
|
|
3238
|
+
everywhere: everywhere
|
|
3239
|
+
});
|
|
3240
|
+
if (targets === "undeclared") {
|
|
3241
|
+
issues.push({
|
|
3242
|
+
path: site.path,
|
|
3243
|
+
message: undeclaredFieldReadMessage(site, read.name)
|
|
3244
|
+
});
|
|
3245
|
+
continue;
|
|
3246
|
+
}
|
|
3247
|
+
const path = read.path;
|
|
3248
|
+
path !== void 0 && (targets.some(target => fieldValuePathIssue({
|
|
3249
|
+
target: target,
|
|
3250
|
+
path: path
|
|
3251
|
+
}) === void 0) || pushFieldReadPathIssue({
|
|
3252
|
+
where: site.label,
|
|
3253
|
+
read: {
|
|
3254
|
+
field: read.name,
|
|
3255
|
+
path: path
|
|
3256
|
+
},
|
|
3257
|
+
target: targets[0],
|
|
3258
|
+
path: site.path,
|
|
3259
|
+
issues: issues
|
|
3260
|
+
}));
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
function readTargets({site: site, read: read, everywhere: everywhere}) {
|
|
3265
|
+
if (site.fields === "context-dependent") return everywhere.get(read.name) ?? "undeclared";
|
|
3266
|
+
const layers = site.fields.filter(layer => layer.entries.has(read.name));
|
|
3267
|
+
if (layers.length === 0) return "undeclared";
|
|
3268
|
+
const entries = layers.map(layer => layer.entries.get(read.name));
|
|
3269
|
+
return site.unionWindow === !0 ? entries : [ entries[0] ];
|
|
2575
3270
|
}
|
|
2576
3271
|
|
|
2577
|
-
function
|
|
2578
|
-
|
|
3272
|
+
function allDeclaredFieldEntries(def) {
|
|
3273
|
+
const declared = /* @__PURE__ */ new Map;
|
|
3274
|
+
for (const {entries: entries} of fieldScopes(def)) for (const entry of entries ?? []) {
|
|
3275
|
+
const list = declared.get(entry.name);
|
|
3276
|
+
list === void 0 ? declared.set(entry.name, [ entry ]) : list.push(entry);
|
|
3277
|
+
}
|
|
3278
|
+
return declared;
|
|
2579
3279
|
}
|
|
2580
3280
|
|
|
2581
3281
|
function noFieldAnywhereMessage(label, name) {
|
|
2582
|
-
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
|
|
3282
|
+
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 misevaluates`;
|
|
2583
3283
|
}
|
|
2584
3284
|
|
|
2585
3285
|
function undeclaredFieldReadMessage(site, name) {
|
|
2586
3286
|
if (site.fields === "context-dependent") return noFieldAnywhereMessage(site.label, name);
|
|
2587
|
-
const visible = site.fields.flatMap(layer => [ ...layer.
|
|
2588
|
-
return `${site.label} reads $fields.${name}, but
|
|
3287
|
+
const visible = site.fields.flatMap(layer => [ ...layer.entries.keys() ].map(n => `${layer.scope}:${n}`)), window = site.windowNote === void 0 ? `no field entry named "${name}" is lexically visible here` : `"${name}" is not in this site's window (${site.windowNote})`;
|
|
3288
|
+
return `${site.label} reads $fields.${name}, but ${window} — the read evaluates to GROQ null, so the condition silently misevaluates. Visible: ${visible.join(", ") || "(none)"}`;
|
|
2589
3289
|
}
|
|
2590
3290
|
|
|
2591
3291
|
function checkFieldReadSeeds(def, issues) {
|
|
@@ -2687,7 +3387,7 @@ function checkFieldReadOpValues(def, issues) {
|
|
|
2687
3387
|
ops: site.ops,
|
|
2688
3388
|
path: site.path,
|
|
2689
3389
|
label: site.label,
|
|
2690
|
-
|
|
3390
|
+
activityEntries: site.activity.fields ?? []
|
|
2691
3391
|
});
|
|
2692
3392
|
}
|
|
2693
3393
|
|
|
@@ -2707,8 +3407,11 @@ function fieldReadsIn(value, path) {
|
|
|
2707
3407
|
} ] : value.type !== "object" ? [] : Object.entries(value.fields).flatMap(([key, sub]) => fieldReadsIn(sub, [ ...path, "fields", key ]));
|
|
2708
3408
|
}
|
|
2709
3409
|
|
|
2710
|
-
function checkOpFieldRead({read: read, where: where, path: path, workflowEntries: workflowEntries, stageEntries: stageEntries,
|
|
3410
|
+
function checkOpFieldRead({read: read, where: where, path: path, workflowEntries: workflowEntries, stageEntries: stageEntries, activityEntries: activityEntries, issues: issues}) {
|
|
2711
3411
|
const hosts = [ {
|
|
3412
|
+
scope: "activity",
|
|
3413
|
+
entries: activityEntries
|
|
3414
|
+
}, {
|
|
2712
3415
|
scope: "stage",
|
|
2713
3416
|
entries: stageEntries
|
|
2714
3417
|
}, {
|
|
@@ -2721,8 +3424,7 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
|
|
|
2721
3424
|
message: opFieldReadMissMessage({
|
|
2722
3425
|
read: read,
|
|
2723
3426
|
where: where,
|
|
2724
|
-
hosts: hosts
|
|
2725
|
-
activityNames: activityNames
|
|
3427
|
+
hosts: hosts
|
|
2726
3428
|
})
|
|
2727
3429
|
});
|
|
2728
3430
|
return;
|
|
@@ -2736,9 +3438,9 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
|
|
|
2736
3438
|
});
|
|
2737
3439
|
}
|
|
2738
3440
|
|
|
2739
|
-
function opFieldReadMissMessage({read: read, where: where, hosts: hosts
|
|
2740
|
-
const searched = read.scope === void 0 ? "stage or workflow scope" : `${read.scope} scope`,
|
|
2741
|
-
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
|
|
3441
|
+
function opFieldReadMissMessage({read: read, where: where, hosts: hosts}) {
|
|
3442
|
+
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}`));
|
|
3443
|
+
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)"}`;
|
|
2742
3444
|
}
|
|
2743
3445
|
|
|
2744
3446
|
function checkUpdateWhereOps(def, issues) {
|
|
@@ -2845,11 +3547,14 @@ function checkGuardFieldRead({name: name, valuePath: valuePath, where: where, pa
|
|
|
2845
3547
|
});
|
|
2846
3548
|
}
|
|
2847
3549
|
|
|
2848
|
-
function pushFieldReadPathIssue({where: where, read: read, target: target, path: path, issues: issues}) {
|
|
3550
|
+
function pushFieldReadPathIssue({where: where, read: read, target: target, path: path, issues: issues, nodes: nodes}) {
|
|
2849
3551
|
if (read.path === void 0) return;
|
|
2850
3552
|
const pathIssue = fieldValuePathIssue({
|
|
2851
3553
|
target: target,
|
|
2852
|
-
path: read.path
|
|
3554
|
+
path: read.path,
|
|
3555
|
+
...nodes ? {
|
|
3556
|
+
nodes: nodes
|
|
3557
|
+
} : {}
|
|
2853
3558
|
});
|
|
2854
3559
|
pathIssue !== void 0 && issues.push({
|
|
2855
3560
|
path: path,
|
|
@@ -2898,6 +3603,7 @@ const SCALAR = {
|
|
|
2898
3603
|
kind: "list",
|
|
2899
3604
|
item: GDR_VALUE
|
|
2900
3605
|
}),
|
|
3606
|
+
subject: () => DOC_CONTENT,
|
|
2901
3607
|
"release.ref": () => RELEASE_VALUE,
|
|
2902
3608
|
string: () => SCALAR,
|
|
2903
3609
|
text: () => SCALAR,
|
|
@@ -2920,22 +3626,27 @@ const SCALAR = {
|
|
|
2920
3626
|
kind: "rows",
|
|
2921
3627
|
of: shape.of ?? []
|
|
2922
3628
|
})
|
|
3629
|
+
}, START_ALLOWED_VALUE_NODES = {
|
|
3630
|
+
...VALUE_NODES,
|
|
3631
|
+
"doc.ref": () => GDR_VALUE,
|
|
3632
|
+
subject: () => GDR_VALUE
|
|
2923
3633
|
};
|
|
2924
3634
|
|
|
2925
|
-
function valueNodeFor(shape) {
|
|
2926
|
-
return Object.hasOwn(
|
|
3635
|
+
function valueNodeFor(shape, nodes) {
|
|
3636
|
+
return Object.hasOwn(nodes, shape.type) ? nodes[shape.type](shape) : void 0;
|
|
2927
3637
|
}
|
|
2928
3638
|
|
|
2929
3639
|
const INDEX_SEGMENT = /^\d+$/;
|
|
2930
3640
|
|
|
2931
|
-
function stepIntoValueNode(
|
|
3641
|
+
function stepIntoValueNode(args) {
|
|
3642
|
+
const {node: node, segment: segment, nodes: nodes} = args;
|
|
2932
3643
|
switch (node.kind) {
|
|
2933
3644
|
case "scalar":
|
|
2934
3645
|
return "the value is a scalar with no sub-paths";
|
|
2935
3646
|
|
|
2936
3647
|
case "object":
|
|
2937
3648
|
{
|
|
2938
|
-
const sub = node.fields.find(f => f.name === segment), next = sub === void 0 ? void 0 : valueNodeFor(sub);
|
|
3649
|
+
const sub = node.fields.find(f => f.name === segment), next = sub === void 0 ? void 0 : valueNodeFor(sub, nodes);
|
|
2939
3650
|
return next !== void 0 ? next : `an object value has sub-fields ${knownList(node.fields.map(f => f.name))} — "${segment}" is not one`;
|
|
2940
3651
|
}
|
|
2941
3652
|
|
|
@@ -2956,18 +3667,24 @@ function stepIntoValueNode(node, segment) {
|
|
|
2956
3667
|
}
|
|
2957
3668
|
}
|
|
2958
3669
|
|
|
2959
|
-
function fieldValuePathIssue({target: target, path: path}) {
|
|
2960
|
-
let node = valueNodeFor(target);
|
|
3670
|
+
function fieldValuePathIssue({target: target, path: path, nodes: nodes = VALUE_NODES}) {
|
|
3671
|
+
let node = valueNodeFor(target, nodes);
|
|
2961
3672
|
if (node === void 0) return `"${String(target.type)}" is not a known field kind`;
|
|
2962
3673
|
for (const segment of path.split(".")) {
|
|
2963
|
-
const next = stepIntoValueNode(
|
|
3674
|
+
const next = stepIntoValueNode({
|
|
3675
|
+
node: node,
|
|
3676
|
+
segment: segment,
|
|
3677
|
+
nodes: nodes
|
|
3678
|
+
});
|
|
2964
3679
|
if (typeof next == "string") return `at segment "${segment}": ${next}`;
|
|
2965
3680
|
node = next;
|
|
2966
3681
|
}
|
|
2967
3682
|
}
|
|
2968
3683
|
|
|
2969
3684
|
function checkWorkflowInvariants(def) {
|
|
2970
|
-
const issues = []
|
|
3685
|
+
const issues = [];
|
|
3686
|
+
checkDefinitionName(def, issues);
|
|
3687
|
+
const stageNames = checkStages(def, issues);
|
|
2971
3688
|
return checkInitialStage({
|
|
2972
3689
|
def: def,
|
|
2973
3690
|
stageNames: stageNames,
|
|
@@ -2982,17 +3699,24 @@ function checkWorkflowInvariants(def) {
|
|
|
2982
3699
|
issues: issues
|
|
2983
3700
|
}), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues),
|
|
2984
3701
|
checkRequiredField(def, issues), checkStart(def, issues), checkPredicates(def, issues),
|
|
2985
|
-
|
|
3702
|
+
checkUnboundConditionVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
|
|
2986
3703
|
checkFieldReadOpValues(def, issues), checkUpdateWhereOps(def, issues), checkGuardFieldReads(def, issues),
|
|
2987
|
-
checkAssigneesEntries(def, issues),
|
|
3704
|
+
checkAssigneesEntries(def, issues), checkSubjectEntries(def, issues), checkSubjectEffectOutputs(def, issues),
|
|
3705
|
+
checkActivityTerminalPaths(def, issues), checkTerminalStageActivities(def, issues),
|
|
2988
3706
|
checkTriggeredActionParams(def, issues), checkStoredRolesPlacement(def, issues),
|
|
2989
3707
|
checkGroups(def, issues), issues;
|
|
2990
3708
|
}
|
|
2991
3709
|
|
|
3710
|
+
exports.ACTION_SEMANTICS = ACTION_SEMANTICS;
|
|
3711
|
+
|
|
2992
3712
|
exports.ACTIVITY_KINDS = ACTIVITY_KINDS;
|
|
2993
3713
|
|
|
3714
|
+
exports.ACTIVITY_STATUSES = ACTIVITY_STATUSES;
|
|
3715
|
+
|
|
2994
3716
|
exports.ACTOR_KINDS = ACTOR_KINDS;
|
|
2995
3717
|
|
|
3718
|
+
exports.ActorShape = ActorShape;
|
|
3719
|
+
|
|
2996
3720
|
exports.AuthoringActionSchema = AuthoringActionSchema;
|
|
2997
3721
|
|
|
2998
3722
|
exports.AuthoringActivitySchema = AuthoringActivitySchema;
|
|
@@ -3015,6 +3739,12 @@ exports.CONDITION_VARS = CONDITION_VARS;
|
|
|
3015
3739
|
|
|
3016
3740
|
exports.ContractViolationError = ContractViolationError;
|
|
3017
3741
|
|
|
3742
|
+
exports.DATA_MODEL_CHANGES = DATA_MODEL_CHANGES;
|
|
3743
|
+
|
|
3744
|
+
exports.DATA_MODEL_MIN_READER = DATA_MODEL_MIN_READER;
|
|
3745
|
+
|
|
3746
|
+
exports.DATA_MODEL_VERSION = DATA_MODEL_VERSION;
|
|
3747
|
+
|
|
3018
3748
|
exports.DEFAULT_TRANSITION_WHEN = DEFAULT_TRANSITION_WHEN;
|
|
3019
3749
|
|
|
3020
3750
|
exports.DOCUMENT_VALUE_PERMISSIONS = DOCUMENT_VALUE_PERMISSIONS;
|
|
@@ -3035,6 +3765,10 @@ exports.EffectSchema = EffectSchema;
|
|
|
3035
3765
|
|
|
3036
3766
|
exports.FIELD_READ = FIELD_READ;
|
|
3037
3767
|
|
|
3768
|
+
exports.FIELD_SCOPES = FIELD_SCOPES;
|
|
3769
|
+
|
|
3770
|
+
exports.FIELD_VALUE_KINDS = FIELD_VALUE_KINDS;
|
|
3771
|
+
|
|
3038
3772
|
exports.FILTER_SCOPE_VARS = FILTER_SCOPE_VARS;
|
|
3039
3773
|
|
|
3040
3774
|
exports.FieldValueShapeError = FieldValueShapeError;
|
|
@@ -3043,20 +3777,44 @@ exports.GROUP_KINDS = GROUP_KINDS;
|
|
|
3043
3777
|
|
|
3044
3778
|
exports.GUARD_PREDICATE_VARS = GUARD_PREDICATE_VARS;
|
|
3045
3779
|
|
|
3780
|
+
exports.GdrShape = GdrShape;
|
|
3781
|
+
|
|
3046
3782
|
exports.GroupSchema = GroupSchema;
|
|
3047
3783
|
|
|
3048
3784
|
exports.InstanceNotFoundError = InstanceNotFoundError;
|
|
3049
3785
|
|
|
3786
|
+
exports.IsoTimestamp = IsoTimestamp;
|
|
3787
|
+
|
|
3788
|
+
exports.MUTATION_GUARD_ACTIONS = MUTATION_GUARD_ACTIONS;
|
|
3789
|
+
|
|
3790
|
+
exports.ModelVersionAheadError = ModelVersionAheadError;
|
|
3791
|
+
|
|
3792
|
+
exports.NonEmptyString = NonEmptyString;
|
|
3793
|
+
|
|
3794
|
+
exports.PersistedDocShapeError = PersistedDocShapeError;
|
|
3795
|
+
|
|
3796
|
+
exports.READER_MODEL_ROLLOUT_URL = READER_MODEL_ROLLOUT_URL;
|
|
3797
|
+
|
|
3050
3798
|
exports.RESERVED_CONDITION_VARS = RESERVED_CONDITION_VARS;
|
|
3051
3799
|
|
|
3052
3800
|
exports.RESOURCE_ALIAS_NAME_SOURCE = RESOURCE_ALIAS_NAME_SOURCE;
|
|
3053
3801
|
|
|
3802
|
+
exports.ReaderModelAcknowledgementError = ReaderModelAcknowledgementError;
|
|
3803
|
+
|
|
3804
|
+
exports.START_ALLOWED_VARS = START_ALLOWED_VARS;
|
|
3805
|
+
|
|
3054
3806
|
exports.START_FILTER_VARS = START_FILTER_VARS;
|
|
3055
3807
|
|
|
3808
|
+
exports.SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR = SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR;
|
|
3809
|
+
|
|
3810
|
+
exports.SpawnContractsInvalidError = SpawnContractsInvalidError;
|
|
3811
|
+
|
|
3056
3812
|
exports.StoredFieldOpSchema = StoredFieldOpSchema;
|
|
3057
3813
|
|
|
3058
3814
|
exports.WORKFLOW_DEFINITION_TYPE = WORKFLOW_DEFINITION_TYPE;
|
|
3059
3815
|
|
|
3816
|
+
exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
|
|
3817
|
+
|
|
3060
3818
|
exports.WorkflowConfigSchema = WorkflowConfigSchema;
|
|
3061
3819
|
|
|
3062
3820
|
exports.WorkflowError = WorkflowError;
|
|
@@ -3065,10 +3823,16 @@ exports.actorFulfillsRole = actorFulfillsRole;
|
|
|
3065
3823
|
|
|
3066
3824
|
exports.andConditions = andConditions;
|
|
3067
3825
|
|
|
3826
|
+
exports.assertReadableModel = assertReadableModel;
|
|
3827
|
+
|
|
3828
|
+
exports.assertReaderModelAcknowledgement = assertReaderModelAcknowledgement;
|
|
3829
|
+
|
|
3068
3830
|
exports.checkFieldValue = checkFieldValue;
|
|
3069
3831
|
|
|
3070
3832
|
exports.checkWorkflowInvariants = checkWorkflowInvariants;
|
|
3071
3833
|
|
|
3834
|
+
exports.choiceValueIssues = choiceValueIssues;
|
|
3835
|
+
|
|
3072
3836
|
exports.clientConfigFromResource = clientConfigFromResource;
|
|
3073
3837
|
|
|
3074
3838
|
exports.conditionEffectReads = conditionEffectReads;
|
|
@@ -3101,6 +3865,10 @@ exports.evaluatePredicates = evaluatePredicates;
|
|
|
3101
3865
|
|
|
3102
3866
|
exports.extractDocumentId = extractDocumentId;
|
|
3103
3867
|
|
|
3868
|
+
exports.fieldTreeShape = fieldTreeShape;
|
|
3869
|
+
|
|
3870
|
+
exports.fieldValueSchemas = fieldValueSchemas;
|
|
3871
|
+
|
|
3104
3872
|
exports.formatIssuePath = formatIssuePath;
|
|
3105
3873
|
|
|
3106
3874
|
exports.formatIssues = formatIssues;
|
|
@@ -3133,6 +3901,12 @@ exports.isInputSourced = isInputSourced;
|
|
|
3133
3901
|
|
|
3134
3902
|
exports.isNotesEntry = isNotesEntry;
|
|
3135
3903
|
|
|
3904
|
+
exports.isParseableInstant = isParseableInstant;
|
|
3905
|
+
|
|
3906
|
+
exports.isSingleDocRefEntry = isSingleDocRefEntry;
|
|
3907
|
+
|
|
3908
|
+
exports.isSingleDocRefKind = isSingleDocRefKind;
|
|
3909
|
+
|
|
3136
3910
|
exports.isStartableDefinition = isStartableDefinition;
|
|
3137
3911
|
|
|
3138
3912
|
exports.isSubjectEntry = isSubjectEntry;
|
|
@@ -3145,12 +3919,26 @@ exports.isTodoListItem = isTodoListItem;
|
|
|
3145
3919
|
|
|
3146
3920
|
exports.isUnevaluable = isUnevaluable;
|
|
3147
3921
|
|
|
3922
|
+
exports.isUnprimed = isUnprimed;
|
|
3923
|
+
|
|
3148
3924
|
exports.labelFor = labelFor;
|
|
3149
3925
|
|
|
3926
|
+
exports.minReaderModelOf = minReaderModelOf;
|
|
3927
|
+
|
|
3928
|
+
exports.modelStampFor = modelStampFor;
|
|
3929
|
+
|
|
3930
|
+
exports.modelVersionOf = modelVersionOf;
|
|
3931
|
+
|
|
3932
|
+
exports.parentRef = parentRef;
|
|
3933
|
+
|
|
3934
|
+
exports.parseDefinitionSnapshot = parseDefinitionSnapshot;
|
|
3935
|
+
|
|
3150
3936
|
exports.parseGdr = parseGdr;
|
|
3151
3937
|
|
|
3152
3938
|
exports.parseOrThrow = parseOrThrow;
|
|
3153
3939
|
|
|
3940
|
+
exports.parsePersistedDoc = parsePersistedDoc;
|
|
3941
|
+
|
|
3154
3942
|
exports.parseResourceGdr = parseResourceGdr;
|
|
3155
3943
|
|
|
3156
3944
|
exports.parseStoredDefinition = parseStoredDefinition;
|
|
@@ -3163,6 +3951,8 @@ exports.refDashboard = refDashboard;
|
|
|
3163
3951
|
|
|
3164
3952
|
exports.refDataset = refDataset;
|
|
3165
3953
|
|
|
3954
|
+
exports.refKindAcceptsTypes = refKindAcceptsTypes;
|
|
3955
|
+
|
|
3166
3956
|
exports.refMediaLibrary = refMediaLibrary;
|
|
3167
3957
|
|
|
3168
3958
|
exports.refTypeIssues = refTypeIssues;
|
|
@@ -3173,6 +3963,10 @@ exports.releaseDocId = releaseDocId;
|
|
|
3173
3963
|
|
|
3174
3964
|
exports.releaseRef = releaseRef;
|
|
3175
3965
|
|
|
3966
|
+
exports.requiredModelFeatures = requiredModelFeatures;
|
|
3967
|
+
|
|
3968
|
+
exports.requiredReaderModel = requiredReaderModel;
|
|
3969
|
+
|
|
3176
3970
|
exports.resourceAliasesToMap = resourceAliasesToMap;
|
|
3177
3971
|
|
|
3178
3972
|
exports.resourceFromGdrUri = resourceFromGdrUri;
|
|
@@ -3187,16 +3981,26 @@ exports.runGroq = runGroq;
|
|
|
3187
3981
|
|
|
3188
3982
|
exports.sameResource = sameResource;
|
|
3189
3983
|
|
|
3984
|
+
exports.scalarValidationIssues = scalarValidationIssues;
|
|
3985
|
+
|
|
3986
|
+
exports.schemaTreeShape = schemaTreeShape;
|
|
3987
|
+
|
|
3190
3988
|
exports.selfGdr = selfGdr;
|
|
3191
3989
|
|
|
3192
3990
|
exports.startKindOf = startKindOf;
|
|
3193
3991
|
|
|
3194
3992
|
exports.tagScopeFilter = tagScopeFilter;
|
|
3195
3993
|
|
|
3994
|
+
exports.terminalState = terminalState;
|
|
3995
|
+
|
|
3196
3996
|
exports.toBareId = toBareId;
|
|
3197
3997
|
|
|
3198
3998
|
exports.toPhysicalGdr = toPhysicalGdr;
|
|
3199
3999
|
|
|
4000
|
+
exports.tolerantEntries = tolerantEntries;
|
|
4001
|
+
|
|
4002
|
+
exports.tolerantObject = tolerantObject;
|
|
4003
|
+
|
|
3200
4004
|
exports.tryParseGdr = tryParseGdr;
|
|
3201
4005
|
|
|
3202
4006
|
exports.validateFieldAppendItem = validateFieldAppendItem;
|