@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
|
@@ -4,6 +4,291 @@ import { conditionOutcome, runGroq as runGroq$1, evaluateConditionOutcome as eva
|
|
|
4
4
|
|
|
5
5
|
import { parse } from "groq-js";
|
|
6
6
|
|
|
7
|
+
class WorkflowError extends Error {
|
|
8
|
+
kind;
|
|
9
|
+
constructor(kind, message, options) {
|
|
10
|
+
super(message, options), this.kind = kind;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class ContractViolationError extends WorkflowError {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super("contract-violation", message), this.name = "ContractViolationError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class InstanceNotFoundError extends WorkflowError {
|
|
21
|
+
instanceId;
|
|
22
|
+
constructor(args) {
|
|
23
|
+
super("instance-not-found", `Workflow instance ${args.instanceId} not found${args.detail ? ` (${args.detail})` : ""}`),
|
|
24
|
+
this.name = "InstanceNotFoundError", this.instanceId = args.instanceId;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class DefinitionNotFoundError extends WorkflowError {
|
|
29
|
+
definition;
|
|
30
|
+
version;
|
|
31
|
+
constructor(args) {
|
|
32
|
+
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`),
|
|
33
|
+
this.name = "DefinitionNotFoundError", this.definition = args.definition, args.version !== void 0 && (this.version = args.version);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class SpawnContractsInvalidError extends WorkflowError {
|
|
38
|
+
issues;
|
|
39
|
+
constructor(args) {
|
|
40
|
+
super("spawn-contracts-invalid", args.message), this.name = "SpawnContractsInvalidError",
|
|
41
|
+
this.issues = args.issues;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class DefinitionInUseError extends WorkflowError {
|
|
46
|
+
definition;
|
|
47
|
+
blockedBy;
|
|
48
|
+
constructor(args) {
|
|
49
|
+
super("definition-in-use", definitionInUseMessage(args.definition, args.blockedBy)),
|
|
50
|
+
this.name = "DefinitionInUseError", this.definition = args.definition, this.blockedBy = args.blockedBy;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function definitionInUseMessage(definition, blockedBy) {
|
|
55
|
+
if (blockedBy.reason === "non-terminal-instances") {
|
|
56
|
+
const head = blockedBy.instanceIds.slice(0, 3).join(", "), preview = blockedBy.instanceIds.length > 3 ? `${head}, …` : head;
|
|
57
|
+
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.`;
|
|
58
|
+
}
|
|
59
|
+
const names = blockedBy.referrers.map(r => `${r.definition} v${r.version}`).join(", ");
|
|
60
|
+
return `Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class EffectNotFoundError extends WorkflowError {
|
|
64
|
+
instanceId;
|
|
65
|
+
effectKey;
|
|
66
|
+
settled;
|
|
67
|
+
constructor(args) {
|
|
68
|
+
super("effect-not-found", effectNotFoundMessage(args)), this.name = "EffectNotFoundError",
|
|
69
|
+
this.instanceId = args.instanceId, this.effectKey = args.effectKey, args.settled !== void 0 && (this.settled = args.settled);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function effectNotFoundMessage(args) {
|
|
74
|
+
const base = `Pending effect "${args.effectKey}" not found on instance ${args.instanceId}`;
|
|
75
|
+
if (args.settled === void 0) return base;
|
|
76
|
+
const cause = args.settled.detail !== void 0 ? ` (${args.settled.detail})` : "";
|
|
77
|
+
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}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function errorMessage(err) {
|
|
81
|
+
return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function rethrowWithContext(err, context) {
|
|
85
|
+
throw new Error(`${context}: ${errorMessage(err)}`, {
|
|
86
|
+
cause: err
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const WORKFLOW_INSTANCE_TYPE = "sanity.workflow.instance";
|
|
91
|
+
|
|
92
|
+
function terminalState(instance) {
|
|
93
|
+
return instance.abortedAt !== void 0 ? "aborted" : instance.completedAt !== void 0 ? "completed" : "in-flight";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isUnprimed(instance) {
|
|
97
|
+
return instance.stages.length === 0 && terminalState(instance) === "in-flight";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseDefinitionSnapshotValue(instance) {
|
|
101
|
+
try {
|
|
102
|
+
return JSON.parse(instance.definitionSnapshot);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
rethrowWithContext(err, `Failed to parse definitionSnapshot on instance "${instance._id}"`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function parseDefinitionSnapshot(instance) {
|
|
109
|
+
return parseDefinitionSnapshotValue(instance);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parentRef(instance) {
|
|
113
|
+
return instance.ancestors.at(-1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
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";
|
|
117
|
+
|
|
118
|
+
class ReaderModelAcknowledgementError extends WorkflowError {
|
|
119
|
+
code="WORKFLOW_READER_MODEL_ACKNOWLEDGEMENT_MISMATCH";
|
|
120
|
+
expectedMinReaderModel;
|
|
121
|
+
engineMinReaderModel=DATA_MODEL_MIN_READER;
|
|
122
|
+
engineModelVersion=DATA_MODEL_VERSION;
|
|
123
|
+
documentationUrl=READER_MODEL_ROLLOUT_URL;
|
|
124
|
+
constructor(expectedMinReaderModel, context = "Deployment") {
|
|
125
|
+
const expected = expectedMinReaderModel === void 0 ? "missing" : String(expectedMinReaderModel);
|
|
126
|
+
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}`),
|
|
127
|
+
this.name = "ReaderModelAcknowledgementError", this.expectedMinReaderModel = expectedMinReaderModel;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function assertReaderModelAcknowledgement(expectedMinReaderModel, context) {
|
|
132
|
+
if (typeof expectedMinReaderModel != "number" || !Number.isFinite(expectedMinReaderModel) || !Number.isInteger(expectedMinReaderModel) || expectedMinReaderModel < 0 || expectedMinReaderModel !== DATA_MODEL_MIN_READER) throw new ReaderModelAcknowledgementError(expectedMinReaderModel, context);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const DATA_MODEL_CHANGES = Object.freeze([ Object.freeze({
|
|
136
|
+
id: "governed-model-stamps",
|
|
137
|
+
introducedInModel: 1,
|
|
138
|
+
minReaderModel: 0,
|
|
139
|
+
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
140
|
+
compatibility: "additive",
|
|
141
|
+
applicability: "unconditional",
|
|
142
|
+
summary: "Definition and instance documents carry model provenance and reader-floor stamps."
|
|
143
|
+
}), Object.freeze({
|
|
144
|
+
id: "subject-field-kind",
|
|
145
|
+
introducedInModel: 2,
|
|
146
|
+
minReaderModel: 0,
|
|
147
|
+
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
148
|
+
compatibility: "additive",
|
|
149
|
+
applicability: "detectable",
|
|
150
|
+
summary: "A workflow-level subject field identifies the document a workflow is about."
|
|
151
|
+
}), Object.freeze({
|
|
152
|
+
id: "typed-scalar-choice-lists",
|
|
153
|
+
introducedInModel: 2,
|
|
154
|
+
minReaderModel: 2,
|
|
155
|
+
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
156
|
+
compatibility: "reader-floor",
|
|
157
|
+
applicability: "detectable",
|
|
158
|
+
summary: "Scalar fields may constrain writes to a persisted typed choice list."
|
|
159
|
+
}), Object.freeze({
|
|
160
|
+
id: "action-semantics",
|
|
161
|
+
introducedInModel: 2,
|
|
162
|
+
minReaderModel: 0,
|
|
163
|
+
documentTypes: Object.freeze([ "definition" ]),
|
|
164
|
+
compatibility: "additive",
|
|
165
|
+
applicability: "detectable",
|
|
166
|
+
summary: "Ordinary actions may carry a closed bag of advisory workflow semantics."
|
|
167
|
+
}), Object.freeze({
|
|
168
|
+
id: "inclusive-scalar-bounds",
|
|
169
|
+
introducedInModel: 2,
|
|
170
|
+
minReaderModel: 2,
|
|
171
|
+
documentTypes: Object.freeze([ "definition", "instance" ]),
|
|
172
|
+
compatibility: "reader-floor",
|
|
173
|
+
applicability: "detectable",
|
|
174
|
+
summary: "String, text, and number values may carry persisted inclusive bounds."
|
|
175
|
+
}) ]);
|
|
176
|
+
|
|
177
|
+
function recordOf(value) {
|
|
178
|
+
return value !== null && typeof value == "object" && !Array.isArray(value) ? value : void 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function recordsAt(record, key) {
|
|
182
|
+
const value = record[key];
|
|
183
|
+
return Array.isArray(value) ? value.map(recordOf).filter(item => item !== void 0) : [];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function nestedFieldEntries(entries) {
|
|
187
|
+
return entries.flatMap(entry => [ entry, ...nestedFieldEntries(recordsAt(entry, "fields")), ...nestedFieldEntries(recordsAt(entry, "of")) ]);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function parsedDefinitionSnapshot(root) {
|
|
191
|
+
if (typeof root.definitionSnapshot == "string") return recordOf(parseDefinitionSnapshotValue({
|
|
192
|
+
_id: typeof root._id == "string" ? root._id : "<unknown instance>",
|
|
193
|
+
definitionSnapshot: root.definitionSnapshot
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function persistedFieldEntries(document) {
|
|
198
|
+
const root = recordOf(document);
|
|
199
|
+
if (root === void 0) return [];
|
|
200
|
+
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"));
|
|
201
|
+
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")) ]);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function hasChoiceList(document) {
|
|
205
|
+
return persistedFieldEntries(document).some(entry => {
|
|
206
|
+
const options = recordOf(entry.options);
|
|
207
|
+
return options !== void 0 && Array.isArray(options.list);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function hasSubjectField(document) {
|
|
212
|
+
return persistedFieldEntries(document).some(entry => entry.type === "subject" || entry._type === "subject");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function hasActionSemantics(document) {
|
|
216
|
+
const root = recordOf(document);
|
|
217
|
+
return root === void 0 ? !1 : recordsAt(root, "stages").flatMap(stage => recordsAt(stage, "activities")).flatMap(activity => recordsAt(activity, "actions")).some(action => Array.isArray(action.semantics));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function hasScalarValidation(document) {
|
|
221
|
+
return persistedFieldEntries(document).some(entry => {
|
|
222
|
+
const validation = recordOf(entry.validation);
|
|
223
|
+
return typeof validation?.min == "number" || typeof validation?.max == "number";
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const featureDetectors = {
|
|
228
|
+
"governed-model-stamps": () => !0,
|
|
229
|
+
"subject-field-kind": hasSubjectField,
|
|
230
|
+
"typed-scalar-choice-lists": hasChoiceList,
|
|
231
|
+
"action-semantics": hasActionSemantics,
|
|
232
|
+
"inclusive-scalar-bounds": hasScalarValidation
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
function requiredModelFeatures(documentType, document) {
|
|
236
|
+
return DATA_MODEL_CHANGES.filter(change => change.documentTypes.some(candidate => candidate === documentType) && featureDetectors[change.id](document));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function requiredReaderModel(documentType, document) {
|
|
240
|
+
return Math.max(0, ...requiredModelFeatures(documentType, document).map(change => change.minReaderModel));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function modelStampFor(args) {
|
|
244
|
+
return {
|
|
245
|
+
modelVersion: DATA_MODEL_VERSION,
|
|
246
|
+
minReaderModel: Math.max(args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function fieldTreeShape(value) {
|
|
251
|
+
if (Array.isArray(value)) return value.map(fieldTreeShape);
|
|
252
|
+
if (value === null) return "null";
|
|
253
|
+
if (typeof value == "object") {
|
|
254
|
+
const record = value;
|
|
255
|
+
return Object.fromEntries(Object.keys(record).toSorted().map(key => [ key, fieldTreeShape(record[key]) ]));
|
|
256
|
+
}
|
|
257
|
+
return typeof value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function modelVersionOf(doc) {
|
|
261
|
+
const stamp = doc.modelVersion;
|
|
262
|
+
return typeof stamp == "number" ? stamp : 0;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function minReaderModelOf(doc) {
|
|
266
|
+
const floor = doc.minReaderModel;
|
|
267
|
+
return typeof floor == "number" ? floor : modelVersionOf(doc);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
class ModelVersionAheadError extends WorkflowError {
|
|
271
|
+
documentId;
|
|
272
|
+
documentModelVersion;
|
|
273
|
+
requiredReaderModel;
|
|
274
|
+
engineModelVersion;
|
|
275
|
+
constructor(args) {
|
|
276
|
+
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.`),
|
|
277
|
+
this.name = "ModelVersionAheadError", this.documentId = args.documentId, this.documentModelVersion = args.documentModelVersion,
|
|
278
|
+
this.requiredReaderModel = args.requiredReaderModel, this.engineModelVersion = DATA_MODEL_VERSION;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function assertReadableModel(doc) {
|
|
283
|
+
const documentReaderModel = minReaderModelOf(doc);
|
|
284
|
+
if (documentReaderModel > DATA_MODEL_VERSION) throw new ModelVersionAheadError({
|
|
285
|
+
documentId: doc._id,
|
|
286
|
+
documentModelVersion: modelVersionOf(doc),
|
|
287
|
+
requiredReaderModel: documentReaderModel
|
|
288
|
+
});
|
|
289
|
+
return doc;
|
|
290
|
+
}
|
|
291
|
+
|
|
7
292
|
function isCascadeFired(action) {
|
|
8
293
|
return action.when !== void 0;
|
|
9
294
|
}
|
|
@@ -24,16 +309,6 @@ function driverKind(actor) {
|
|
|
24
309
|
return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
|
|
25
310
|
}
|
|
26
311
|
|
|
27
|
-
function errorMessage(err) {
|
|
28
|
-
return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function rethrowWithContext(err, context) {
|
|
32
|
-
throw new Error(`${context}: ${errorMessage(err)}`, {
|
|
33
|
-
cause: err
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
312
|
function andConditions(parts) {
|
|
38
313
|
const present = parts.filter(p => p !== void 0);
|
|
39
314
|
if (present.length !== 0) return present.length === 1 ? present[0] : present.map(p => `(${p})`).join(" && ");
|
|
@@ -234,82 +509,17 @@ function isGdr(value) {
|
|
|
234
509
|
return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
|
|
235
510
|
}
|
|
236
511
|
|
|
237
|
-
|
|
238
|
-
kind;
|
|
239
|
-
constructor(kind, message, options) {
|
|
240
|
-
super(message, options), this.kind = kind;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
class ContractViolationError extends WorkflowError {
|
|
245
|
-
constructor(message) {
|
|
246
|
-
super("contract-violation", message), this.name = "ContractViolationError";
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
class InstanceNotFoundError extends WorkflowError {
|
|
251
|
-
instanceId;
|
|
252
|
-
constructor(args) {
|
|
253
|
-
super("instance-not-found", `Workflow instance ${args.instanceId} not found${args.detail ? ` (${args.detail})` : ""}`),
|
|
254
|
-
this.name = "InstanceNotFoundError", this.instanceId = args.instanceId;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
class DefinitionNotFoundError extends WorkflowError {
|
|
259
|
-
definition;
|
|
260
|
-
version;
|
|
261
|
-
constructor(args) {
|
|
262
|
-
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`),
|
|
263
|
-
this.name = "DefinitionNotFoundError", this.definition = args.definition, args.version !== void 0 && (this.version = args.version);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
class DefinitionInUseError extends WorkflowError {
|
|
268
|
-
definition;
|
|
269
|
-
blockedBy;
|
|
270
|
-
constructor(args) {
|
|
271
|
-
super("definition-in-use", definitionInUseMessage(args.definition, args.blockedBy)),
|
|
272
|
-
this.name = "DefinitionInUseError", this.definition = args.definition, this.blockedBy = args.blockedBy;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function definitionInUseMessage(definition, blockedBy) {
|
|
277
|
-
if (blockedBy.reason === "non-terminal-instances") {
|
|
278
|
-
const head = blockedBy.instanceIds.slice(0, 3).join(", "), preview = blockedBy.instanceIds.length > 3 ? `${head}, …` : head;
|
|
279
|
-
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.`;
|
|
280
|
-
}
|
|
281
|
-
const names = blockedBy.referrers.map(r => `${r.definition} v${r.version}`).join(", ");
|
|
282
|
-
return `Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
class EffectNotFoundError extends WorkflowError {
|
|
286
|
-
instanceId;
|
|
287
|
-
effectKey;
|
|
288
|
-
settled;
|
|
289
|
-
constructor(args) {
|
|
290
|
-
super("effect-not-found", effectNotFoundMessage(args)), this.name = "EffectNotFoundError",
|
|
291
|
-
this.instanceId = args.instanceId, this.effectKey = args.effectKey, args.settled !== void 0 && (this.settled = args.settled);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function effectNotFoundMessage(args) {
|
|
296
|
-
const base = `Pending effect "${args.effectKey}" not found on instance ${args.instanceId}`;
|
|
297
|
-
if (args.settled === void 0) return base;
|
|
298
|
-
const cause = args.settled.detail !== void 0 ? ` (${args.settled.detail})` : "";
|
|
299
|
-
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}`;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
512
|
+
const LAKE_ID_SEGMENT_RE = /^[a-z0-9][a-z0-9-]*$/, LAKE_ID_SEGMENT_GLOSS = "ASCII lowercase + digits + dashes, no leading dash, no dots";
|
|
303
513
|
|
|
304
514
|
function validateTag(tag) {
|
|
305
|
-
if (!
|
|
515
|
+
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})`);
|
|
306
516
|
}
|
|
307
517
|
|
|
308
518
|
function tagScopeFilter() {
|
|
309
519
|
return "tag == $tag";
|
|
310
520
|
}
|
|
311
521
|
|
|
312
|
-
const NonEmptyString = v.pipe(v.string(), v.nonEmpty("must not be empty"));
|
|
522
|
+
const NonEmptyString$1 = v.pipe(v.string(), v.nonEmpty("must not be empty"));
|
|
313
523
|
|
|
314
524
|
function asPredicate(validate) {
|
|
315
525
|
return value => {
|
|
@@ -323,21 +533,22 @@ function asPredicate(validate) {
|
|
|
323
533
|
|
|
324
534
|
const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts), WorkflowResourceSchema = v.variant("type", [ v.object({
|
|
325
535
|
type: v.literal("dataset"),
|
|
326
|
-
id: v.pipe(NonEmptyString, v.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
|
|
536
|
+
id: v.pipe(NonEmptyString$1, v.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
|
|
327
537
|
}), v.object({
|
|
328
538
|
type: v.literal("canvas"),
|
|
329
|
-
id: NonEmptyString
|
|
539
|
+
id: NonEmptyString$1
|
|
330
540
|
}), v.object({
|
|
331
541
|
type: v.literal("media-library"),
|
|
332
|
-
id: NonEmptyString
|
|
542
|
+
id: NonEmptyString$1
|
|
333
543
|
}), v.object({
|
|
334
544
|
type: v.literal("dashboard"),
|
|
335
|
-
id: NonEmptyString
|
|
545
|
+
id: NonEmptyString$1
|
|
336
546
|
}) ]), ResourceBindingSchema = v.object({
|
|
337
|
-
name: v.pipe(NonEmptyString, v.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
547
|
+
name: v.pipe(NonEmptyString$1, v.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
338
548
|
resource: WorkflowResourceSchema
|
|
339
549
|
}), DefinitionSchema = v.custom(input => typeof input == "object" && input !== null && typeof input.name == "string", "expected a workflow definition (an object with a string `name`)"), DeploymentSchema = v.object({
|
|
340
|
-
name: NonEmptyString,
|
|
550
|
+
name: NonEmptyString$1,
|
|
551
|
+
expectedMinReaderModel: v.optional(v.custom(() => !0), void 0),
|
|
341
552
|
tag: v.pipe(v.string(), v.nonEmpty(), v.check(isValidTag, "invalid tag — lowercase letters, digits and dashes only, no leading dash, no dots")),
|
|
342
553
|
workflowResource: WorkflowResourceSchema,
|
|
343
554
|
resourceAliases: v.optional(v.pipe(v.array(ResourceBindingSchema), v.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"))),
|
|
@@ -502,6 +713,9 @@ function desugarStart(start) {
|
|
|
502
713
|
kind: start.kind ?? "interactive",
|
|
503
714
|
...start.filter !== void 0 ? {
|
|
504
715
|
filter: start.filter
|
|
716
|
+
} : {},
|
|
717
|
+
...start.allowed !== void 0 ? {
|
|
718
|
+
allowed: start.allowed
|
|
505
719
|
} : {}
|
|
506
720
|
};
|
|
507
721
|
}
|
|
@@ -577,6 +791,8 @@ function desugarFieldEntry({entry: entry, path: path, ctx: ctx}) {
|
|
|
577
791
|
required: entry.required,
|
|
578
792
|
initialValue: entry.initialValue,
|
|
579
793
|
editable: editable,
|
|
794
|
+
options: entry.options,
|
|
795
|
+
validation: entry.validation,
|
|
580
796
|
types: entry.types,
|
|
581
797
|
fields: entry.fields,
|
|
582
798
|
of: entry.of
|
|
@@ -719,7 +935,7 @@ function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ct
|
|
|
719
935
|
};
|
|
720
936
|
}
|
|
721
937
|
|
|
722
|
-
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "release.ref" ];
|
|
938
|
+
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref" ];
|
|
723
939
|
|
|
724
940
|
function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
725
941
|
if (target === void 0 || target.type === "url") return target;
|
|
@@ -740,18 +956,15 @@ function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
|
740
956
|
};
|
|
741
957
|
}
|
|
742
958
|
|
|
743
|
-
function
|
|
744
|
-
|
|
745
|
-
action: action,
|
|
746
|
-
path: path,
|
|
747
|
-
env: env,
|
|
748
|
-
ctx: ctx
|
|
749
|
-
});
|
|
750
|
-
action.roles !== void 0 && action.roles.length === 0 && ctx.issues.push({
|
|
959
|
+
function reportEmptyActionRoles({action: action, path: path, ctx: ctx}) {
|
|
960
|
+
action.roles === void 0 || action.roles.length > 0 || ctx.issues.push({
|
|
751
961
|
path: [ ...path, "roles" ],
|
|
752
962
|
message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
|
|
753
963
|
});
|
|
754
|
-
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function desugarActionOps(args) {
|
|
967
|
+
const {action: action, path: path, env: env, activityName: activityName, ctx: ctx} = args, ops = desugarOps({
|
|
755
968
|
ops: action.ops,
|
|
756
969
|
path: [ ...path, "ops" ],
|
|
757
970
|
env: env,
|
|
@@ -762,9 +975,32 @@ function desugarAction({action: action, path: path, env: env, activityName: acti
|
|
|
762
975
|
type: "status.set",
|
|
763
976
|
activity: activityName,
|
|
764
977
|
status: action.status
|
|
765
|
-
}),
|
|
978
|
+
}), ops;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
|
|
982
|
+
if ("type" in action) return desugarClaimAction({
|
|
983
|
+
action: action,
|
|
984
|
+
path: path,
|
|
985
|
+
env: env,
|
|
986
|
+
ctx: ctx
|
|
987
|
+
});
|
|
988
|
+
reportEmptyActionRoles({
|
|
989
|
+
action: action,
|
|
990
|
+
path: path,
|
|
991
|
+
ctx: ctx
|
|
992
|
+
});
|
|
993
|
+
const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarActionOps({
|
|
994
|
+
action: action,
|
|
995
|
+
path: path,
|
|
996
|
+
env: env,
|
|
997
|
+
activityName: activityName,
|
|
998
|
+
ctx: ctx
|
|
999
|
+
});
|
|
1000
|
+
return {
|
|
766
1001
|
...stripUndefined({
|
|
767
1002
|
name: action.name,
|
|
1003
|
+
semantics: action.semantics,
|
|
768
1004
|
title: action.title,
|
|
769
1005
|
description: action.description,
|
|
770
1006
|
group: normalizeGroup(action.group),
|
|
@@ -1050,12 +1286,43 @@ function conditionParameterNames(groq2) {
|
|
|
1050
1286
|
}
|
|
1051
1287
|
|
|
1052
1288
|
function conditionFieldReadNames(groq2) {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1289
|
+
return new Set(conditionFieldReads(groq2).map(read => read.name));
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function conditionFieldReads(groq2) {
|
|
1293
|
+
const reads = /* @__PURE__ */ new Map;
|
|
1294
|
+
return collectFieldReads(tryParseGroq(groq2), reads), [ ...reads.values() ];
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function collectFieldReads(node, reads) {
|
|
1298
|
+
if (Array.isArray(node)) {
|
|
1299
|
+
for (const item of node) collectFieldReads(item, reads);
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
if (typeof node != "object" || node === null) return;
|
|
1303
|
+
const chain = fieldsAttributeChain(node);
|
|
1304
|
+
if (chain !== void 0) {
|
|
1305
|
+
const [name, ...tail] = chain, path = tail.length > 0 ? tail.join(".") : void 0;
|
|
1306
|
+
reads.set(`${name}.${path ?? ""}`, {
|
|
1307
|
+
name: name,
|
|
1308
|
+
path: path
|
|
1309
|
+
});
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
for (const value of Object.values(node)) collectFieldReads(value, reads);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
function fieldsAttributeChain(node) {
|
|
1316
|
+
const names = [];
|
|
1317
|
+
let current = node;
|
|
1318
|
+
for (;typeof current == "object" && current !== null; ) {
|
|
1319
|
+
const typed = current;
|
|
1320
|
+
if (typed.type !== "AccessAttribute" || typeof typed.name != "string") return;
|
|
1321
|
+
names.unshift(typed.name);
|
|
1322
|
+
const base = typed.base;
|
|
1323
|
+
if (base?.type === "Parameter" && base.name === "fields") return names;
|
|
1324
|
+
current = base;
|
|
1325
|
+
}
|
|
1059
1326
|
}
|
|
1060
1327
|
|
|
1061
1328
|
function conditionEffectReads(groq2) {
|
|
@@ -1120,7 +1387,7 @@ function isTerminalActivityStatus(status) {
|
|
|
1120
1387
|
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
1121
1388
|
}
|
|
1122
1389
|
|
|
1123
|
-
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 = [ {
|
|
1390
|
+
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 = [ {
|
|
1124
1391
|
name: "self",
|
|
1125
1392
|
binding: "always",
|
|
1126
1393
|
label: "this workflow instance",
|
|
@@ -1129,7 +1396,7 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
|
|
|
1129
1396
|
name: "fields",
|
|
1130
1397
|
binding: "always",
|
|
1131
1398
|
label: "the workflow's fields",
|
|
1132
|
-
description: "Declared field entries rendered by name (`$fields.<name>` is the value, no
|
|
1399
|
+
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."
|
|
1133
1400
|
}, {
|
|
1134
1401
|
name: "parent",
|
|
1135
1402
|
binding: "always",
|
|
@@ -1199,7 +1466,7 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
|
|
|
1199
1466
|
name: "row",
|
|
1200
1467
|
binding: "spawn",
|
|
1201
1468
|
label: "the spawned row",
|
|
1202
|
-
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)."
|
|
1469
|
+
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."
|
|
1203
1470
|
}, {
|
|
1204
1471
|
name: "params",
|
|
1205
1472
|
binding: "caller",
|
|
@@ -1210,25 +1477,161 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
|
|
|
1210
1477
|
binding: "always",
|
|
1211
1478
|
label: "the spawned subworkflows",
|
|
1212
1479
|
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`."
|
|
1213
|
-
} ], 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 = [ {
|
|
1480
|
+
} ], 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 = [ {
|
|
1214
1481
|
name: "tag",
|
|
1482
|
+
label: "this engine tag",
|
|
1215
1483
|
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
1216
1484
|
}, {
|
|
1217
1485
|
name: "definition",
|
|
1218
|
-
|
|
1486
|
+
label: "this workflow definition",
|
|
1487
|
+
description: "The `name` of the definition under evaluation (its own start block binds it)."
|
|
1219
1488
|
}, {
|
|
1220
1489
|
name: "now",
|
|
1490
|
+
label: "the current time",
|
|
1221
1491
|
description: "The ISO clock reading of the evaluating engine."
|
|
1222
1492
|
}, {
|
|
1493
|
+
name: SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR,
|
|
1494
|
+
label: "the subject already has an in-flight workflow",
|
|
1495
|
+
description: "Whether any instance in this engine tag, across all definitions, has the same resource-qualified subject and no `completedAt`. Advisory under concurrent starts."
|
|
1496
|
+
} ], START_ALLOWED_VARS = [ ...START_FILTER_VARS, {
|
|
1223
1497
|
name: "fields",
|
|
1224
|
-
|
|
1498
|
+
label: "the start's input fields",
|
|
1499
|
+
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."
|
|
1225
1500
|
} ], GUARD_PREDICATE_VARS = [ {
|
|
1226
1501
|
name: "guard",
|
|
1227
1502
|
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
1228
1503
|
}, {
|
|
1229
1504
|
name: "mutation",
|
|
1230
1505
|
description: "The attempted mutation — `mutation.action` is the write kind being gated."
|
|
1231
|
-
} ],
|
|
1506
|
+
} ], NonEmptyString = v.pipe(v.string(), v.minLength(1, "must be a non-empty string"));
|
|
1507
|
+
|
|
1508
|
+
function isParseableInstant(value) {
|
|
1509
|
+
return typeof value == "string" && !Number.isNaN(Date.parse(value));
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
const IsoTimestamp = v.pipe(v.string(), v.check(s => isParseableInstant(s), "must be an ISO-8601 datetime string"));
|
|
1513
|
+
|
|
1514
|
+
function tolerantObject() {
|
|
1515
|
+
return (entries, ..._exact) => v.looseObject(entries);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function tolerantEntries() {
|
|
1519
|
+
return (entries, ..._exact) => entries;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
function schemaTreeShape(schema) {
|
|
1523
|
+
return walkSchemaShape(schema, /* @__PURE__ */ new Set);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
function walkSchemaShape(node, path) {
|
|
1527
|
+
if (typeof node != "object" || node === null) return "unknown";
|
|
1528
|
+
if (path.has(node)) return "(circular)";
|
|
1529
|
+
path.add(node);
|
|
1530
|
+
try {
|
|
1531
|
+
const schema = node;
|
|
1532
|
+
return containerShape(schema, path) ?? leafShape(schema);
|
|
1533
|
+
} finally {
|
|
1534
|
+
path.delete(node);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
const objectWalker = (schema, path) => objectEntriesShape(schema.entries, path), unionWalker = (schema, path) => ({
|
|
1539
|
+
union: schema.options.map(option => walkSchemaShape(option, path))
|
|
1540
|
+
}), unwrapWalker = (schema, path) => walkSchemaShape(schema.wrapped, path), CONTAINER_WALKERS = {
|
|
1541
|
+
strict_object: objectWalker,
|
|
1542
|
+
loose_object: objectWalker,
|
|
1543
|
+
object: objectWalker,
|
|
1544
|
+
array: (schema, path) => ({
|
|
1545
|
+
array: walkSchemaShape(schema.item, path)
|
|
1546
|
+
}),
|
|
1547
|
+
record: (schema, path) => ({
|
|
1548
|
+
record: walkSchemaShape(schema.value, path)
|
|
1549
|
+
}),
|
|
1550
|
+
union: unionWalker,
|
|
1551
|
+
variant: unionWalker,
|
|
1552
|
+
lazy: (schema, path) => walkSchemaShape(schema.getter(void 0), path),
|
|
1553
|
+
exact_optional: unwrapWalker,
|
|
1554
|
+
optional: unwrapWalker,
|
|
1555
|
+
nullable: unwrapWalker
|
|
1556
|
+
};
|
|
1557
|
+
|
|
1558
|
+
function containerShape(schema, path) {
|
|
1559
|
+
return CONTAINER_WALKERS[String(schema.type)]?.(schema, path);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function objectEntriesShape(entries, path) {
|
|
1563
|
+
const out = {};
|
|
1564
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
1565
|
+
const optional = isOptionalEntry(entry), wrapped = optional ? entry.wrapped : entry;
|
|
1566
|
+
out[optional ? `${key}?` : key] = walkSchemaShape(wrapped, path);
|
|
1567
|
+
}
|
|
1568
|
+
return Object.fromEntries(Object.entries(out).sort(([a], [b]) => a < b ? -1 : 1));
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
function isOptionalEntry(entry) {
|
|
1572
|
+
if (typeof entry != "object" || entry === null) return !1;
|
|
1573
|
+
const kind = entry.type;
|
|
1574
|
+
return kind === "exact_optional" || kind === "optional";
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
const LEAF_KINDS = /* @__PURE__ */ new Set([ "string", "number", "boolean", "null", "undefined", "unknown", "any" ]);
|
|
1578
|
+
|
|
1579
|
+
function leafShape(schema) {
|
|
1580
|
+
if (schema.type === "picklist") return schema.options.join(" | ");
|
|
1581
|
+
if (schema.type === "literal") return `literal ${String(schema.literal)}`;
|
|
1582
|
+
if (schema.type === "custom") return typeof schema.message == "string" ? `custom(${schema.message})` : "custom";
|
|
1583
|
+
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`);
|
|
1584
|
+
return String(schema.type);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
function formatValidationError(label, issues) {
|
|
1588
|
+
const lines = issues.map(issue => ` - ${issue.path.length === 0 ? "(root)" : formatIssuePath(issue.path)}: ${issue.message}`);
|
|
1589
|
+
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
function issuesFromValibot(issues) {
|
|
1593
|
+
return issues.map(issue => ({
|
|
1594
|
+
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
1595
|
+
message: issue.message
|
|
1596
|
+
}));
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
function formatIssuePath(path) {
|
|
1600
|
+
let out = "";
|
|
1601
|
+
for (const seg of path) typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
1602
|
+
return out;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
class PersistedDocShapeError extends WorkflowError {
|
|
1606
|
+
documentId;
|
|
1607
|
+
documentType;
|
|
1608
|
+
issues;
|
|
1609
|
+
constructor(args) {
|
|
1610
|
+
super("persisted-doc-shape", formatValidationError(`Persisted ${args.documentType} document "${args.documentId}"`, args.issues)),
|
|
1611
|
+
this.name = "PersistedDocShapeError", this.documentId = args.documentId, this.documentType = args.documentType,
|
|
1612
|
+
this.issues = args.issues;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
function parsePersistedDoc(args) {
|
|
1617
|
+
const result = v.safeParse(args.schema, args.doc);
|
|
1618
|
+
if (!result.success) throw new PersistedDocShapeError({
|
|
1619
|
+
documentId: documentIdOf(args.doc),
|
|
1620
|
+
documentType: args.docType,
|
|
1621
|
+
issues: issuesFromValibot(result.issues)
|
|
1622
|
+
});
|
|
1623
|
+
return result.output;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function documentIdOf(doc) {
|
|
1627
|
+
if (typeof doc == "object" && doc !== null) {
|
|
1628
|
+
const id = doc._id;
|
|
1629
|
+
if (typeof id == "string") return id;
|
|
1630
|
+
}
|
|
1631
|
+
return "(unknown id)";
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
const ACTOR_KINDS = [ "person", "agent", "system" ];
|
|
1232
1635
|
|
|
1233
1636
|
function releaseDocId(releaseName) {
|
|
1234
1637
|
return `_.releases.${releaseName}`;
|
|
@@ -1243,6 +1646,18 @@ function releaseRef({res: res, releaseName: releaseName}) {
|
|
|
1243
1646
|
};
|
|
1244
1647
|
}
|
|
1245
1648
|
|
|
1649
|
+
function isSingleDocRefKind(kind) {
|
|
1650
|
+
return kind === "doc.ref" || kind === "subject";
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
function refKindAcceptsTypes(kind) {
|
|
1654
|
+
return isSingleDocRefKind(kind) || kind === "doc.refs";
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
function isSingleDocRefEntry(entry) {
|
|
1658
|
+
return isSingleDocRefKind(entry._type);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1246
1661
|
function isTodoListItem(row) {
|
|
1247
1662
|
if (typeof row != "object" || row === null) return !1;
|
|
1248
1663
|
const candidate = row, status = candidate.status;
|
|
@@ -1275,29 +1690,53 @@ class FieldValueShapeError extends WorkflowError {
|
|
|
1275
1690
|
}
|
|
1276
1691
|
}
|
|
1277
1692
|
|
|
1278
|
-
const
|
|
1279
|
-
id:
|
|
1280
|
-
type:
|
|
1281
|
-
}), ReleaseRefShape = v.pipe(
|
|
1282
|
-
id:
|
|
1693
|
+
const GdrUriSchema = v.custom(s => typeof s == "string" && isGdrUri(s), "must be a GDR URI"), GdrShape = tolerantObject()({
|
|
1694
|
+
id: GdrUriSchema,
|
|
1695
|
+
type: NonEmptyString
|
|
1696
|
+
}), ReleaseRefShape = v.pipe(tolerantObject()({
|
|
1697
|
+
id: GdrUriSchema,
|
|
1283
1698
|
type: v.literal("system.release"),
|
|
1284
|
-
releaseName:
|
|
1285
|
-
}), v.check(ref => !isGdrUri(ref.id) || extractDocumentId(ref.id) === releaseDocId(ref.releaseName), "id must point at the `_.releases.<releaseName>` doc named by releaseName")), ActorShape =
|
|
1699
|
+
releaseName: NonEmptyString
|
|
1700
|
+
}), v.check(ref => !isGdrUri(ref.id) || extractDocumentId(ref.id) === releaseDocId(ref.releaseName), "id must point at the `_.releases.<releaseName>` doc named by releaseName")), ActorShape = tolerantObject()({
|
|
1286
1701
|
kind: v.picklist(ACTOR_KINDS),
|
|
1287
|
-
id:
|
|
1288
|
-
roles: v.
|
|
1289
|
-
onBehalfOf: v.
|
|
1290
|
-
}), AssigneeShape = v.union([
|
|
1702
|
+
id: NonEmptyString,
|
|
1703
|
+
roles: v.exactOptional(v.array(v.string())),
|
|
1704
|
+
onBehalfOf: v.exactOptional(v.string())
|
|
1705
|
+
}), AssigneeShape = v.union([ tolerantObject()({
|
|
1291
1706
|
type: v.literal("user"),
|
|
1292
|
-
id:
|
|
1293
|
-
}),
|
|
1707
|
+
id: NonEmptyString
|
|
1708
|
+
}), tolerantObject()({
|
|
1294
1709
|
type: v.literal("role"),
|
|
1295
|
-
role:
|
|
1296
|
-
}) ]), NullableString = v.union([ v.null(), v.string() ]), NullableNumber = v.union([ v.null(), v.number() ]), NullableBoolean = v.union([ v.null(), v.boolean() ]), NullableDateTime = v.union([ v.null(),
|
|
1710
|
+
role: NonEmptyString
|
|
1711
|
+
}) ]), NullableString = v.union([ v.null(), v.string() ]), NullableNumber = v.union([ v.null(), v.number() ]), NullableBoolean = v.union([ v.null(), v.boolean() ]), NullableDateTime = v.union([ v.null(), IsoTimestamp ]), NullableDate = v.union([ v.null(), v.pipe(v.string(), v.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" ]);
|
|
1712
|
+
|
|
1713
|
+
function normalizedChoiceKind(kind) {
|
|
1714
|
+
return kind === "dateTime" ? "datetime" : kind;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
function checkChoiceList(args) {
|
|
1718
|
+
const {entryType: entryType, options: options, validation: validation} = args;
|
|
1719
|
+
if (options === void 0) return;
|
|
1720
|
+
if (!CHOICE_KINDS.has(entryType)) return [ `\`options\` is not valid on "${entryType}" values` ];
|
|
1721
|
+
const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => checkValueAgainst({
|
|
1722
|
+
entryType: kind,
|
|
1723
|
+
value: option.value,
|
|
1724
|
+
validation: validation
|
|
1725
|
+
}, valueSchemas)?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
|
|
1726
|
+
for (const option of options.list) seen.has(option.value) && issues.push(`duplicate option value ${JSON.stringify(option.value)}`),
|
|
1727
|
+
seen.add(option.value);
|
|
1728
|
+
return issues.length === 0 ? void 0 : issues;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
function choiceValueIssues(options, value) {
|
|
1732
|
+
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(", ")}` ];
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
const fieldValueSchemas = {
|
|
1297
1736
|
"doc.ref": v.union([ v.null(), GdrShape ]),
|
|
1298
1737
|
"doc.refs": v.array(GdrShape),
|
|
1738
|
+
subject: v.union([ v.null(), GdrShape ]),
|
|
1299
1739
|
"release.ref": v.union([ v.null(), ReleaseRefShape ]),
|
|
1300
|
-
query: v.any(),
|
|
1301
1740
|
string: NullableString,
|
|
1302
1741
|
text: NullableString,
|
|
1303
1742
|
number: NullableNumber,
|
|
@@ -1308,10 +1747,62 @@ const GdrShape = v.looseObject({
|
|
|
1308
1747
|
actor: v.union([ v.null(), ActorShape ]),
|
|
1309
1748
|
assignee: v.union([ v.null(), AssigneeShape ]),
|
|
1310
1749
|
assignees: v.array(AssigneeShape)
|
|
1750
|
+
}, valueSchemas = {
|
|
1751
|
+
...fieldValueSchemas,
|
|
1752
|
+
query: v.any()
|
|
1311
1753
|
};
|
|
1312
1754
|
|
|
1313
1755
|
function shapeValueSchema(shape, leaf) {
|
|
1314
|
-
|
|
1756
|
+
if (shape.type === "object") return objectSchema(shape.fields ?? [], leaf);
|
|
1757
|
+
if (shape.type === "array") return v.array(objectSchema(shape.of ?? [], leaf));
|
|
1758
|
+
const schema = leaf[shape.type] ?? v.any();
|
|
1759
|
+
return constrainedScalarSchema({
|
|
1760
|
+
schema: schema,
|
|
1761
|
+
entryType: shape.type,
|
|
1762
|
+
...shape
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
function scalarMeasurement(entryType, value) {
|
|
1767
|
+
if (entryType === "number" && typeof value == "number") return value;
|
|
1768
|
+
if ((entryType === "string" || entryType === "text") && typeof value == "string") return value.length;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
function scalarBoundIssue(args) {
|
|
1772
|
+
const {entryType: entryType, measured: measured, bound: bound, limit: limit} = args;
|
|
1773
|
+
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}`;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
function scalarValidationIssues(args) {
|
|
1777
|
+
const {entryType: entryType, validation: validation, value: value} = args;
|
|
1778
|
+
if (validation === void 0 || value === null || value === void 0) return;
|
|
1779
|
+
const measured = scalarMeasurement(entryType, value);
|
|
1780
|
+
if (measured === void 0) return;
|
|
1781
|
+
const issues = [ scalarBoundIssue({
|
|
1782
|
+
entryType: entryType,
|
|
1783
|
+
measured: measured,
|
|
1784
|
+
bound: validation.min,
|
|
1785
|
+
limit: "min"
|
|
1786
|
+
}), scalarBoundIssue({
|
|
1787
|
+
entryType: entryType,
|
|
1788
|
+
measured: measured,
|
|
1789
|
+
bound: validation.max,
|
|
1790
|
+
limit: "max"
|
|
1791
|
+
}) ].filter(issue => issue !== void 0);
|
|
1792
|
+
return issues.length === 0 ? void 0 : issues;
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
function constrainedScalarSchema(args) {
|
|
1796
|
+
const {schema: schema, entryType: entryType, options: options, validation: validation} = args;
|
|
1797
|
+
return options === void 0 && validation === void 0 ? schema : v.pipe(schema, v.check(value => choiceValueIssues(options, value) === void 0 && scalarValidationIssues({
|
|
1798
|
+
entryType: entryType,
|
|
1799
|
+
validation: validation,
|
|
1800
|
+
value: value
|
|
1801
|
+
}) === void 0, issue => [ ...choiceValueIssues(options, issue.input) ?? [], ...scalarValidationIssues({
|
|
1802
|
+
entryType: entryType,
|
|
1803
|
+
validation: validation,
|
|
1804
|
+
value: issue.input
|
|
1805
|
+
}) ?? [] ].join("; ")));
|
|
1315
1806
|
}
|
|
1316
1807
|
|
|
1317
1808
|
function objectSchema(fields, leaf) {
|
|
@@ -1334,7 +1825,7 @@ function appendItemSchema(entryType, shape) {
|
|
|
1334
1825
|
function rejectedRefTypes(args) {
|
|
1335
1826
|
const {entryType: entryType, types: types, value: value} = args;
|
|
1336
1827
|
if (types === void 0 || value === null || value === void 0) return [];
|
|
1337
|
-
if (entryType
|
|
1828
|
+
if (!refKindAcceptsTypes(entryType)) return [];
|
|
1338
1829
|
let items = [ value ];
|
|
1339
1830
|
return entryType === "doc.refs" && (items = Array.isArray(value) ? value : []),
|
|
1340
1831
|
[ ...new Set(items.map(gdrTypeOf).filter(t => t !== void 0 && !types.includes(t))) ];
|
|
@@ -1369,6 +1860,10 @@ function checkValueAgainst(args, leaf) {
|
|
|
1369
1860
|
entryType: args.entryType,
|
|
1370
1861
|
types: args.types,
|
|
1371
1862
|
value: args.value
|
|
1863
|
+
}) ?? choiceValueIssues(args.options, args.value) ?? scalarValidationIssues({
|
|
1864
|
+
entryType: args.entryType,
|
|
1865
|
+
validation: args.validation,
|
|
1866
|
+
value: args.value
|
|
1372
1867
|
}) : formatIssues(result.issues);
|
|
1373
1868
|
}
|
|
1374
1869
|
|
|
@@ -1394,15 +1889,16 @@ function isBareSeedId(id) {
|
|
|
1394
1889
|
|
|
1395
1890
|
const AuthoringRefId = v.pipe(v.string(), v.check(isAuthoringRefId, "must be a bare document id, a GDR URI, or a portable `@<alias>:<id>` reference")), AuthoringGdrShape = v.looseObject({
|
|
1396
1891
|
id: AuthoringRefId,
|
|
1397
|
-
type:
|
|
1892
|
+
type: NonEmptyString
|
|
1398
1893
|
}), seedValueSchemas = {
|
|
1399
1894
|
...valueSchemas,
|
|
1400
1895
|
"doc.ref": v.union([ v.null(), AuthoringGdrShape ]),
|
|
1401
1896
|
"doc.refs": v.array(AuthoringGdrShape),
|
|
1897
|
+
subject: v.union([ v.null(), AuthoringGdrShape ]),
|
|
1402
1898
|
"release.ref": v.union([ v.null(), v.looseObject({
|
|
1403
1899
|
id: AuthoringRefId,
|
|
1404
1900
|
type: v.literal("system.release"),
|
|
1405
|
-
releaseName:
|
|
1901
|
+
releaseName: NonEmptyString
|
|
1406
1902
|
}) ])
|
|
1407
1903
|
};
|
|
1408
1904
|
|
|
@@ -1438,14 +1934,14 @@ function validateFieldAppendItem(args) {
|
|
|
1438
1934
|
});
|
|
1439
1935
|
}
|
|
1440
1936
|
|
|
1441
|
-
function formatIssues(issues) {
|
|
1937
|
+
function formatIssues(issues, formatMessage = issue => issue.message) {
|
|
1442
1938
|
return issues.map(i => {
|
|
1443
1939
|
const keys = i.path?.map(p => p.key) ?? [];
|
|
1444
|
-
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i
|
|
1940
|
+
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(i)}`;
|
|
1445
1941
|
});
|
|
1446
1942
|
}
|
|
1447
1943
|
|
|
1448
|
-
const NonEmpty =
|
|
1944
|
+
const NonEmpty = NonEmptyString, PositiveInt = v.pipe(v.number(), v.integer(), v.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1449
1945
|
|
|
1450
1946
|
function groqIdentifier(referencedAs) {
|
|
1451
1947
|
return v.pipe(v.string(), v.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`));
|
|
@@ -1567,7 +2063,15 @@ function groupMembershipNames(group) {
|
|
|
1567
2063
|
return group === void 0 ? [] : typeof group == "string" ? [ group ] : [ ...group ];
|
|
1568
2064
|
}
|
|
1569
2065
|
|
|
1570
|
-
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>`")
|
|
2066
|
+
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.pipe(v.number(), v.finite("must be finite")), ScalarValidationSchema = v.pipe(v.strictObject({
|
|
2067
|
+
min: v.optional(FiniteNumber),
|
|
2068
|
+
max: v.optional(FiniteNumber)
|
|
2069
|
+
}), v.check(validation => validation.min !== void 0 || validation.max !== void 0, "declare at least one bound, or omit `validation`"), v.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.strictObject({
|
|
2070
|
+
list: v.pipe(v.array(v.strictObject({
|
|
2071
|
+
title: NonEmpty,
|
|
2072
|
+
value: v.union([ v.string(), v.number() ])
|
|
2073
|
+
})), v.minLength(1, "declare at least one choice, or omit `options`"))
|
|
2074
|
+
});
|
|
1571
2075
|
|
|
1572
2076
|
function asShape(input) {
|
|
1573
2077
|
return typeof input == "object" && input !== null ? input : {};
|
|
@@ -1601,14 +2105,16 @@ function compositeChecked(entries) {
|
|
|
1601
2105
|
return v.pipe(v.strictObject(entries), v.check(input => compositeShapeOk(input), issue => compositeShapeMessage(issue.input)), v.check(input => duplicateSubfieldName(input) === void 0, issue => `duplicate sub-field name "${duplicateSubfieldName(issue.input)}" — sub-field names must be unique within \`fields\` / \`of\``));
|
|
1602
2106
|
}
|
|
1603
2107
|
|
|
1604
|
-
const FieldShapeSchema = v.lazy(() => compositeChecked({
|
|
2108
|
+
const FieldShapeSchema = v.lazy(() => v.pipe(compositeChecked({
|
|
1605
2109
|
type: FieldValueKindSchema,
|
|
1606
2110
|
name: FieldEntryName,
|
|
1607
2111
|
title: v.optional(v.string()),
|
|
1608
2112
|
description: v.optional(v.string()),
|
|
2113
|
+
options: v.optional(ChoiceOptionsSchema),
|
|
2114
|
+
validation: v.optional(ScalarValidationSchema),
|
|
1609
2115
|
fields: v.optional(v.array(FieldShapeSchema)),
|
|
1610
2116
|
of: v.optional(v.array(FieldShapeSchema))
|
|
1611
|
-
})), StoredEditableSchema = v.union([ v.literal(!0), NonEmpty ]), AuthoringEditableSchema = v.union([ v.literal(!0), v.array(NonEmpty), NonEmpty ]);
|
|
2117
|
+
}), choiceOptionsCheck(), scalarValidationCheck())), StoredEditableSchema = v.union([ v.literal(!0), NonEmpty ]), AuthoringEditableSchema = v.union([ v.literal(!0), v.array(NonEmpty), NonEmpty ]);
|
|
1612
2118
|
|
|
1613
2119
|
function fieldBase(editable, group) {
|
|
1614
2120
|
return {
|
|
@@ -1626,6 +2132,8 @@ function fieldEntryFields(editable, group) {
|
|
|
1626
2132
|
return {
|
|
1627
2133
|
type: FieldKindSchema,
|
|
1628
2134
|
...fieldBase(editable, group),
|
|
2135
|
+
options: v.optional(ChoiceOptionsSchema),
|
|
2136
|
+
validation: v.optional(ScalarValidationSchema),
|
|
1629
2137
|
types: v.optional(v.pipe(v.array(NonEmpty), v.minLength(1, "declare at least one accepted type, or omit `types` to accept any"))),
|
|
1630
2138
|
fields: v.optional(v.array(FieldShapeSchema)),
|
|
1631
2139
|
of: v.optional(v.array(FieldShapeSchema))
|
|
@@ -1638,7 +2146,9 @@ function literalSeedIssues(entry) {
|
|
|
1638
2146
|
value: entry.initialValue.value,
|
|
1639
2147
|
types: entry.types,
|
|
1640
2148
|
fields: entry.fields,
|
|
1641
|
-
of: entry.of
|
|
2149
|
+
of: entry.of,
|
|
2150
|
+
options: entry.options,
|
|
2151
|
+
validation: entry.validation
|
|
1642
2152
|
});
|
|
1643
2153
|
}
|
|
1644
2154
|
|
|
@@ -1647,10 +2157,37 @@ function literalSeedCheck() {
|
|
|
1647
2157
|
}
|
|
1648
2158
|
|
|
1649
2159
|
function refTypesCheck() {
|
|
1650
|
-
return v.check(entry => entry.types === void 0 || entry.type
|
|
2160
|
+
return v.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}"`);
|
|
1651
2161
|
}
|
|
1652
2162
|
|
|
1653
|
-
|
|
2163
|
+
function choiceOptionsCheck() {
|
|
2164
|
+
return v.check(entry => checkChoiceList({
|
|
2165
|
+
entryType: entry.type,
|
|
2166
|
+
options: entry.options,
|
|
2167
|
+
validation: entry.validation
|
|
2168
|
+
}) === void 0, issue => (checkChoiceList({
|
|
2169
|
+
entryType: issue.input.type,
|
|
2170
|
+
options: issue.input.options,
|
|
2171
|
+
validation: issue.input.validation
|
|
2172
|
+
}) ?? []).join("; "));
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
const SCALAR_VALIDATION_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number" ]);
|
|
2176
|
+
|
|
2177
|
+
function scalarValidationCheck() {
|
|
2178
|
+
return v.check(entry => scalarValidationDeclarationIssues(entry) === void 0, issue => (scalarValidationDeclarationIssues(issue.input) ?? []).join("; "));
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
function scalarValidationDeclarationIssues(entry) {
|
|
2182
|
+
const {type: type, validation: validation} = entry;
|
|
2183
|
+
if (validation === void 0) return;
|
|
2184
|
+
if (!SCALAR_VALIDATION_KINDS.has(type)) return [ `\`validation\` is only valid on \`string\` / \`text\` / \`number\` values, not "${type}"` ];
|
|
2185
|
+
if (type === "number") return;
|
|
2186
|
+
const issues = Object.entries(validation).flatMap(([bound, value]) => Number.isInteger(value) && value >= 0 ? [] : [ `\`validation.${bound}\` must be a non-negative integer for ${type} length` ]);
|
|
2187
|
+
return issues.length === 0 ? void 0 : issues;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
const FieldEntrySchema = pinned()(v.pipe(compositeChecked(fieldEntryFields(StoredEditableSchema, StoredGroupMembershipSchema)), refTypesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v.pipe(compositeChecked(fieldEntryFields(AuthoringEditableSchema, AuthoringGroupMembershipSchema)), refTypesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), ClaimFieldSchema = pinned()(v.strictObject({
|
|
1654
2191
|
type: v.literal("claim"),
|
|
1655
2192
|
name: FieldEntryName,
|
|
1656
2193
|
title: v.optional(v.string()),
|
|
@@ -1681,17 +2218,25 @@ const TodoListFieldSchema = pinned()(v.strictObject(listSugarFields("todoList"))
|
|
|
1681
2218
|
with: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
1682
2219
|
context: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
1683
2220
|
onExit: v.optional(picklist([ "detach", "abort" ]))
|
|
1684
|
-
}), ActionParamSchema = v.strictObject({
|
|
2221
|
+
}), ActionParamSchema = v.pipe(v.strictObject({
|
|
1685
2222
|
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
1686
2223
|
name: NonEmpty,
|
|
1687
2224
|
title: v.optional(v.string()),
|
|
1688
2225
|
description: v.optional(v.string()),
|
|
1689
|
-
required: v.optional(v.boolean())
|
|
1690
|
-
|
|
2226
|
+
required: v.optional(v.boolean()),
|
|
2227
|
+
options: v.optional(ChoiceOptionsSchema),
|
|
2228
|
+
validation: v.optional(ScalarValidationSchema)
|
|
2229
|
+
}), choiceOptionsCheck(), scalarValidationCheck());
|
|
2230
|
+
|
|
2231
|
+
function hasUniqueSemanticNamespaces(semantics) {
|
|
2232
|
+
const namespaces = semantics.map(semantic => semantic.split(".", 1)[0]);
|
|
2233
|
+
return new Set(namespaces).size === namespaces.length;
|
|
2234
|
+
}
|
|
1691
2235
|
|
|
1692
2236
|
function actionFields(op, group) {
|
|
1693
2237
|
return {
|
|
1694
2238
|
name: NonEmpty,
|
|
2239
|
+
semantics: v.optional(v.pipe(v.array(picklist(ACTION_SEMANTICS)), v.minLength(1, "declare at least one semantic, or omit `semantics`"), v.check(hasUniqueSemanticNamespaces, "declare at most one semantic from each namespace"))),
|
|
1695
2240
|
title: v.optional(v.string()),
|
|
1696
2241
|
description: v.optional(v.string()),
|
|
1697
2242
|
group: v.optional(group),
|
|
@@ -1828,7 +2373,8 @@ const StoredStageSchema = pinned()(v.strictObject(stageFields({
|
|
|
1828
2373
|
function startFields(kind) {
|
|
1829
2374
|
return {
|
|
1830
2375
|
kind: kind,
|
|
1831
|
-
filter: v.optional(ConditionSchema)
|
|
2376
|
+
filter: v.optional(ConditionSchema),
|
|
2377
|
+
allowed: v.optional(ConditionSchema)
|
|
1832
2378
|
};
|
|
1833
2379
|
}
|
|
1834
2380
|
|
|
@@ -1879,31 +2425,13 @@ function startKindOf(definition) {
|
|
|
1879
2425
|
}
|
|
1880
2426
|
|
|
1881
2427
|
function isSubjectEntry(entry) {
|
|
1882
|
-
return entry.
|
|
2428
|
+
return entry.type === "subject";
|
|
1883
2429
|
}
|
|
1884
2430
|
|
|
1885
2431
|
function isInputSourced(entry) {
|
|
1886
2432
|
return entry.initialValue?.type === "input";
|
|
1887
2433
|
}
|
|
1888
2434
|
|
|
1889
|
-
function formatValidationError(label, issues) {
|
|
1890
|
-
const lines = issues.map(issue => ` - ${issue.path.length === 0 ? "(root)" : formatIssuePath(issue.path)}: ${issue.message}`);
|
|
1891
|
-
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
function issuesFromValibot(issues) {
|
|
1895
|
-
return issues.map(issue => ({
|
|
1896
|
-
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
1897
|
-
message: issue.message
|
|
1898
|
-
}));
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
function formatIssuePath(path) {
|
|
1902
|
-
let out = "";
|
|
1903
|
-
for (const seg of path) typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
1904
|
-
return out;
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
2435
|
function parseOrThrow({schema: schema, input: input, label: label}) {
|
|
1908
2436
|
const result = v.safeParse(schema, input);
|
|
1909
2437
|
if (!result.success) throw new Error(formatValidationError(label, issuesFromValibot(result.issues)));
|
|
@@ -1932,6 +2460,13 @@ function checkDuplicates({names: names, what: what, issues: issues}) {
|
|
|
1932
2460
|
return seen;
|
|
1933
2461
|
}
|
|
1934
2462
|
|
|
2463
|
+
function checkDefinitionName(def, issues) {
|
|
2464
|
+
LAKE_ID_SEGMENT_RE.test(def.name) || issues.push({
|
|
2465
|
+
path: [ "name" ],
|
|
2466
|
+
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`
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
|
|
1935
2470
|
function checkStages(def, issues) {
|
|
1936
2471
|
const stageNames = checkDuplicates({
|
|
1937
2472
|
names: def.stages.map((stage, i) => ({
|
|
@@ -2068,6 +2603,10 @@ function checkGuardNames(def, issues) {
|
|
|
2068
2603
|
what: "guard name (the guard lake _id derives from it — unique per definition)",
|
|
2069
2604
|
issues: issues
|
|
2070
2605
|
});
|
|
2606
|
+
for (const {name: name, path: path} of sites) LAKE_ID_SEGMENT_RE.test(name) || issues.push({
|
|
2607
|
+
path: path,
|
|
2608
|
+
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`
|
|
2609
|
+
});
|
|
2071
2610
|
}
|
|
2072
2611
|
|
|
2073
2612
|
function fieldScopes(def) {
|
|
@@ -2138,7 +2677,11 @@ function checkPredicateBody({name: name, groq: groq2, names: names, issues: issu
|
|
|
2138
2677
|
const reads = conditionParameterNames(groq2);
|
|
2139
2678
|
for (const caller of CALLER_BOUND_VARS) reads.has(caller) && issues.push({
|
|
2140
2679
|
path: [ "predicates", name ],
|
|
2141
|
-
message: `predicate "${name}" reads $${caller} — predicates
|
|
2680
|
+
message: `predicate "${name}" reads $${caller} — predicates pre-evaluate caller-free; compose caller gates at the condition site instead`
|
|
2681
|
+
});
|
|
2682
|
+
reads.has("row") && issues.push({
|
|
2683
|
+
path: [ "predicates", name ],
|
|
2684
|
+
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`
|
|
2142
2685
|
});
|
|
2143
2686
|
for (const other of names) other === name || !reads.has(other) || issues.push({
|
|
2144
2687
|
path: [ "predicates", name ],
|
|
@@ -2146,24 +2689,35 @@ function checkPredicateBody({name: name, groq: groq2, names: names, issues: issu
|
|
|
2146
2689
|
});
|
|
2147
2690
|
}
|
|
2148
2691
|
|
|
2149
|
-
function
|
|
2150
|
-
return new
|
|
2692
|
+
function entryMap(entries) {
|
|
2693
|
+
return new Map((entries ?? []).map(entry => [ entry.name, entry ]));
|
|
2151
2694
|
}
|
|
2152
2695
|
|
|
2153
|
-
function
|
|
2696
|
+
function checkUnboundConditionVars(def, issues) {
|
|
2154
2697
|
for (const site of conditionSites(def)) {
|
|
2155
2698
|
const reads = conditionParameterNames(site.groq);
|
|
2156
|
-
for (const name of
|
|
2699
|
+
for (const name of unboundVarsAt(site)) reads.has(name) && issues.push({
|
|
2157
2700
|
path: site.path,
|
|
2158
|
-
message: callerVarMessage(site, name)
|
|
2701
|
+
message: name === "row" ? rowVarMessage(site) : callerVarMessage(site, name)
|
|
2159
2702
|
});
|
|
2160
2703
|
}
|
|
2161
2704
|
}
|
|
2162
2705
|
|
|
2706
|
+
function unboundVarsAt(site) {
|
|
2707
|
+
const callerVars = unboundCallerVars(site.policy);
|
|
2708
|
+
return site.bindsRow === !0 ? callerVars : [ ...callerVars, "row" ];
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2163
2711
|
function unboundCallerVars(policy) {
|
|
2164
2712
|
return policy === "cascade" ? CALLER_BOUND_VARS : policy === "caller-bound" ? [ "params" ] : policy === "triggered-payload" ? [ "can", "params" ] : [ "can" ];
|
|
2165
2713
|
}
|
|
2166
2714
|
|
|
2715
|
+
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";
|
|
2716
|
+
|
|
2717
|
+
function rowVarMessage(site) {
|
|
2718
|
+
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`;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2167
2721
|
function callerVarMessage(site, name) {
|
|
2168
2722
|
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`;
|
|
2169
2723
|
}
|
|
@@ -2171,7 +2725,7 @@ function callerVarMessage(site, name) {
|
|
|
2171
2725
|
function conditionSites(def) {
|
|
2172
2726
|
const sites = [], workflowLayer = {
|
|
2173
2727
|
scope: "workflow",
|
|
2174
|
-
|
|
2728
|
+
entries: entryMap(def.fields)
|
|
2175
2729
|
};
|
|
2176
2730
|
collectEditableSites({
|
|
2177
2731
|
entries: def.fields,
|
|
@@ -2192,10 +2746,10 @@ function conditionSites(def) {
|
|
|
2192
2746
|
function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflowLayer, sites: sites}) {
|
|
2193
2747
|
const stageLayer = {
|
|
2194
2748
|
scope: "stage",
|
|
2195
|
-
|
|
2749
|
+
entries: entryMap(stage.fields)
|
|
2196
2750
|
}, stageFields2 = [ stageLayer, workflowLayer ], editableWindow = [ stageLayer, workflowLayer, ...(stage.activities ?? []).map(activity => ({
|
|
2197
2751
|
scope: "activity",
|
|
2198
|
-
|
|
2752
|
+
entries: entryMap(activity.fields)
|
|
2199
2753
|
})) ];
|
|
2200
2754
|
for (const [k, t] of (stage.transitions ?? []).entries()) sites.push({
|
|
2201
2755
|
groq: t.when,
|
|
@@ -2209,6 +2763,7 @@ function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflow
|
|
|
2209
2763
|
path: [ "stages", i, "fields" ],
|
|
2210
2764
|
labelPrefix: `stage "${stage.name}"`,
|
|
2211
2765
|
fields: editableWindow,
|
|
2766
|
+
unionWindow: !0,
|
|
2212
2767
|
sites: sites
|
|
2213
2768
|
});
|
|
2214
2769
|
for (const [name, editable] of Object.entries(stage.editable ?? {})) typeof editable == "string" && sites.push({
|
|
@@ -2216,23 +2771,28 @@ function collectStageConditionSites({stage: stage, i: i, workflowLayer: workflow
|
|
|
2216
2771
|
path: [ "stages", i, "editable", name ],
|
|
2217
2772
|
label: `stage "${stage.name}" editable override "${name}"`,
|
|
2218
2773
|
policy: "caller-bound",
|
|
2219
|
-
fields: editableWindow
|
|
2774
|
+
fields: editableWindow,
|
|
2775
|
+
unionWindow: !0
|
|
2220
2776
|
});
|
|
2221
2777
|
for (const [j, activity] of (stage.activities ?? []).entries()) collectActivityConditionSites({
|
|
2222
2778
|
activity: activity,
|
|
2223
2779
|
path: [ "stages", i, "activities", j ],
|
|
2224
2780
|
stageFields: stageFields2,
|
|
2781
|
+
workflowLayer: workflowLayer,
|
|
2225
2782
|
sites: sites
|
|
2226
2783
|
});
|
|
2227
2784
|
}
|
|
2228
2785
|
|
|
2229
|
-
function collectEditableSites({entries: entries, path: path, labelPrefix: labelPrefix, fields: fields, sites: sites}) {
|
|
2786
|
+
function collectEditableSites({entries: entries, path: path, labelPrefix: labelPrefix, fields: fields, unionWindow: unionWindow, sites: sites}) {
|
|
2230
2787
|
for (const [n, entry] of (entries ?? []).entries()) typeof entry.editable == "string" && sites.push({
|
|
2231
2788
|
groq: entry.editable,
|
|
2232
2789
|
path: [ ...path, n, "editable" ],
|
|
2233
2790
|
label: `${labelPrefix} field "${entry.name}" editable`,
|
|
2234
2791
|
policy: "caller-bound",
|
|
2235
|
-
fields: fields
|
|
2792
|
+
fields: fields,
|
|
2793
|
+
...unionWindow === !0 ? {
|
|
2794
|
+
unionWindow: !0
|
|
2795
|
+
} : {}
|
|
2236
2796
|
});
|
|
2237
2797
|
}
|
|
2238
2798
|
|
|
@@ -2244,14 +2804,15 @@ function collectOpWhereSites({ops: ops, path: path, label: label, policy: policy
|
|
|
2244
2804
|
...policy !== void 0 ? {
|
|
2245
2805
|
policy: policy
|
|
2246
2806
|
} : {},
|
|
2807
|
+
bindsRow: !0,
|
|
2247
2808
|
fields: fields
|
|
2248
2809
|
});
|
|
2249
2810
|
}
|
|
2250
2811
|
|
|
2251
|
-
function collectActivityConditionSites({activity: activity, path: path, stageFields: stageFields2, sites: sites}) {
|
|
2812
|
+
function collectActivityConditionSites({activity: activity, path: path, stageFields: stageFields2, workflowLayer: workflowLayer, sites: sites}) {
|
|
2252
2813
|
const fields = [ {
|
|
2253
2814
|
scope: "activity",
|
|
2254
|
-
|
|
2815
|
+
entries: entryMap(activity.fields)
|
|
2255
2816
|
}, ...stageFields2 ];
|
|
2256
2817
|
activity.filter !== void 0 && sites.push({
|
|
2257
2818
|
groq: activity.filter,
|
|
@@ -2277,11 +2838,12 @@ function collectActivityConditionSites({activity: activity, path: path, stageFie
|
|
|
2277
2838
|
activity: activity,
|
|
2278
2839
|
path: path,
|
|
2279
2840
|
fields: fields,
|
|
2841
|
+
workflowLayer: workflowLayer,
|
|
2280
2842
|
sites: sites
|
|
2281
2843
|
});
|
|
2282
2844
|
}
|
|
2283
2845
|
|
|
2284
|
-
function collectActionConditionSites({activity: activity, path: path, fields: fields, sites: sites}) {
|
|
2846
|
+
function collectActionConditionSites({activity: activity, path: path, fields: fields, workflowLayer: workflowLayer, sites: sites}) {
|
|
2285
2847
|
for (const [a, action] of (activity.actions ?? []).entries()) {
|
|
2286
2848
|
const actionPath = [ ...path, "actions", a ], cascadeFired = action.when !== void 0;
|
|
2287
2849
|
action.when !== void 0 && sites.push({
|
|
@@ -2316,12 +2878,13 @@ function collectActionConditionSites({activity: activity, path: path, fields: fi
|
|
|
2316
2878
|
action: action,
|
|
2317
2879
|
path: actionPath,
|
|
2318
2880
|
fields: fields,
|
|
2881
|
+
workflowLayer: workflowLayer,
|
|
2319
2882
|
sites: sites
|
|
2320
2883
|
});
|
|
2321
2884
|
}
|
|
2322
2885
|
}
|
|
2323
2886
|
|
|
2324
|
-
function collectSpawnSites({action: action, path: path, fields: fields, sites: sites}) {
|
|
2887
|
+
function collectSpawnSites({action: action, path: path, fields: fields, workflowLayer: workflowLayer, sites: sites}) {
|
|
2325
2888
|
const spawn = action.spawn;
|
|
2326
2889
|
if (spawn === void 0) return;
|
|
2327
2890
|
const spawnPath = [ ...path, "spawn" ];
|
|
@@ -2330,13 +2893,17 @@ function collectSpawnSites({action: action, path: path, fields: fields, sites: s
|
|
|
2330
2893
|
path: [ ...spawnPath, "forEach" ],
|
|
2331
2894
|
label: `action "${action.name}".spawn.forEach`,
|
|
2332
2895
|
policy: "cascade",
|
|
2333
|
-
fields:
|
|
2896
|
+
fields: [ workflowLayer ],
|
|
2897
|
+
windowNote: "spawn.forEach evaluates against workflow-scope $fields only — stage/activity fields are never bound at discovery time"
|
|
2334
2898
|
});
|
|
2335
|
-
for (const [group, record] of [ [ "with", spawn.with ], [ "context", spawn.context ] ]) for (const [key, groq2] of Object.entries(record ?? {})) sites.push({
|
|
2899
|
+
for (const [group, record, bindsRow] of [ [ "with", spawn.with, !0 ], [ "context", spawn.context, !1 ] ]) for (const [key, groq2] of Object.entries(record ?? {})) sites.push({
|
|
2336
2900
|
groq: groq2,
|
|
2337
2901
|
path: [ ...spawnPath, group, key ],
|
|
2338
2902
|
label: `action "${action.name}".spawn.${group} "${key}"`,
|
|
2339
2903
|
policy: "triggered-payload",
|
|
2904
|
+
...bindsRow ? {
|
|
2905
|
+
bindsRow: !0
|
|
2906
|
+
} : {},
|
|
2340
2907
|
fields: fields
|
|
2341
2908
|
});
|
|
2342
2909
|
}
|
|
@@ -2360,6 +2927,52 @@ function checkAssigneesEntries(def, issues) {
|
|
|
2360
2927
|
});
|
|
2361
2928
|
}
|
|
2362
2929
|
|
|
2930
|
+
function nestedSubjectPaths(shapes, base) {
|
|
2931
|
+
return (shapes ?? []).flatMap((shape, i) => isSubjectEntry(shape) ? [ [ ...base, i, "type" ] ] : [ ...nestedSubjectPaths(shape.fields, [ ...base, i, "fields" ]), ...nestedSubjectPaths(shape.of, [ ...base, i, "of" ]) ]);
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
function checkSubjectEffectOutputs(def, issues) {
|
|
2935
|
+
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({
|
|
2936
|
+
action: action,
|
|
2937
|
+
path: [ "stages", i, "activities", j, "actions", a ],
|
|
2938
|
+
issues: issues
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
function pushSubjectOutputIssues(args) {
|
|
2943
|
+
const {action: action, path: path, issues: issues} = args;
|
|
2944
|
+
for (const [e, effect] of (action.effects ?? []).entries()) {
|
|
2945
|
+
const base = [ ...path, "effects", e, "outputs" ];
|
|
2946
|
+
for (const nestedPath of nestedSubjectPaths(effect.outputs, base)) issues.push({
|
|
2947
|
+
path: nestedPath,
|
|
2948
|
+
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`
|
|
2949
|
+
});
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2953
|
+
function checkSubjectEntries(def, issues) {
|
|
2954
|
+
for (const {entries: entries, scope: scope, path: path, label: label} of fieldScopes(def)) {
|
|
2955
|
+
const subjects = (entries ?? []).flatMap((entry, n) => isSubjectEntry(entry) ? [ {
|
|
2956
|
+
entry: entry,
|
|
2957
|
+
n: n
|
|
2958
|
+
} ] : []);
|
|
2959
|
+
if (scope !== "workflow") for (const {entry: entry, n: n} of subjects) issues.push({
|
|
2960
|
+
path: [ ...path, n, "type" ],
|
|
2961
|
+
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`
|
|
2962
|
+
}); else for (const {n: n} of subjects.slice(1)) issues.push({
|
|
2963
|
+
path: [ ...path, n ],
|
|
2964
|
+
message: "at most one subject-kind field entry per workflow — the runtime identifies THE subject by kind, and a second one makes it ambiguous"
|
|
2965
|
+
});
|
|
2966
|
+
for (const [n, entry] of (entries ?? []).entries()) {
|
|
2967
|
+
const nested = [ ...nestedSubjectPaths(entry.fields, [ ...path, n, "fields" ]), ...nestedSubjectPaths(entry.of, [ ...path, n, "of" ]) ];
|
|
2968
|
+
for (const nestedPath of nested) issues.push({
|
|
2969
|
+
path: nestedPath,
|
|
2970
|
+
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`
|
|
2971
|
+
});
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2363
2976
|
function checkActivityTerminalPaths(def, issues) {
|
|
2364
2977
|
for (const [i, stage] of def.stages.entries()) {
|
|
2365
2978
|
const resolvable = terminallyResolvableActivities(stage);
|
|
@@ -2410,23 +3023,69 @@ function checkStart(def, issues) {
|
|
|
2410
3023
|
if (def.start !== void 0 && (def.lifecycle === "child" && issues.push({
|
|
2411
3024
|
path: [ "start" ],
|
|
2412
3025
|
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'`"
|
|
2413
|
-
}), checkStartFilterReads(def, issues), def
|
|
3026
|
+
}), checkStartFilterReads(def, issues), checkStartAllowedReads(def, issues), checkStartSubjectVariable(def, issues),
|
|
3027
|
+
def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
|
|
2414
3028
|
path: [ "fields", n, "required" ],
|
|
2415
|
-
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
|
|
3029
|
+
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)`
|
|
2416
3030
|
});
|
|
2417
3031
|
}
|
|
2418
3032
|
|
|
3033
|
+
function checkStartSubjectVariable(def, issues) {
|
|
3034
|
+
const subject = (def.fields ?? []).find(isSubjectEntry);
|
|
3035
|
+
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)) {
|
|
3036
|
+
if (subject === void 0) {
|
|
3037
|
+
issues.push({
|
|
3038
|
+
path: [ "start", key ],
|
|
3039
|
+
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`
|
|
3040
|
+
});
|
|
3041
|
+
continue;
|
|
3042
|
+
}
|
|
3043
|
+
key === "allowed" && !isInputSourced(subject) && issues.push({
|
|
3044
|
+
path: [ "start", key ],
|
|
3045
|
+
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`
|
|
3046
|
+
});
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
|
|
2419
3050
|
function checkStartFilterReads(def, issues) {
|
|
2420
3051
|
const filter = def.start?.filter;
|
|
2421
|
-
|
|
2422
|
-
const bindable = new Set((def.fields ?? []).filter(isInputSourced).map(entry => entry.name)), declared = new Set((def.fields ?? []).map(entry => entry.name));
|
|
2423
|
-
for (const name of conditionFieldReadNames(filter)) bindable.has(name) || issues.push({
|
|
3052
|
+
filter !== void 0 && (conditionParameterNames(filter).has("fields") && issues.push({
|
|
2424
3053
|
path: [ "start", "filter" ],
|
|
2425
|
-
message:
|
|
2426
|
-
})
|
|
2427
|
-
readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
3054
|
+
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"
|
|
3055
|
+
}), readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
2428
3056
|
path: [ "start", "filter" ],
|
|
2429
|
-
message: "start.filter reads the candidate document (its root), but the definition declares no
|
|
3057
|
+
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"
|
|
3058
|
+
}));
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
function checkStartAllowedReads(def, issues) {
|
|
3062
|
+
const allowed = def.start?.allowed;
|
|
3063
|
+
if (allowed === void 0) return;
|
|
3064
|
+
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())}`;
|
|
3065
|
+
for (const read of conditionFieldReads(allowed)) {
|
|
3066
|
+
const entry = bindable.get(read.name);
|
|
3067
|
+
if (entry === void 0) {
|
|
3068
|
+
issues.push({
|
|
3069
|
+
path: [ "start", "allowed" ],
|
|
3070
|
+
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}`
|
|
3071
|
+
});
|
|
3072
|
+
continue;
|
|
3073
|
+
}
|
|
3074
|
+
pushFieldReadPathIssue({
|
|
3075
|
+
where: "start.allowed",
|
|
3076
|
+
read: {
|
|
3077
|
+
field: read.name,
|
|
3078
|
+
path: read.path
|
|
3079
|
+
},
|
|
3080
|
+
target: entry,
|
|
3081
|
+
path: [ "start", "allowed" ],
|
|
3082
|
+
issues: issues,
|
|
3083
|
+
nodes: START_ALLOWED_VALUE_NODES
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
readsRootDocument(allowed) && issues.push({
|
|
3087
|
+
path: [ "start", "allowed" ],
|
|
3088
|
+
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"
|
|
2430
3089
|
});
|
|
2431
3090
|
}
|
|
2432
3091
|
|
|
@@ -2537,39 +3196,80 @@ function checkGroupMembership({group: group, reachable: reachable, path: path, l
|
|
|
2537
3196
|
}
|
|
2538
3197
|
|
|
2539
3198
|
function checkConditionFieldReads(def, issues) {
|
|
2540
|
-
const everywhere =
|
|
2541
|
-
for (const site of conditionSites(def))
|
|
3199
|
+
const everywhere = allDeclaredFieldEntries(def);
|
|
3200
|
+
for (const site of conditionSites(def)) checkSiteFieldReads({
|
|
2542
3201
|
site: site,
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
}) || issues.push({
|
|
2546
|
-
path: site.path,
|
|
2547
|
-
message: undeclaredFieldReadMessage(site, name)
|
|
3202
|
+
everywhere: everywhere,
|
|
3203
|
+
issues: issues
|
|
2548
3204
|
});
|
|
2549
|
-
for (const [name, groq2] of Object.entries(def.predicates ?? {}))
|
|
2550
|
-
|
|
2551
|
-
|
|
3205
|
+
for (const [name, groq2] of Object.entries(def.predicates ?? {})) checkSiteFieldReads({
|
|
3206
|
+
site: {
|
|
3207
|
+
groq: groq2,
|
|
3208
|
+
path: [ "predicates", name ],
|
|
3209
|
+
label: `predicate "${name}"`,
|
|
3210
|
+
fields: "context-dependent"
|
|
3211
|
+
},
|
|
3212
|
+
everywhere: everywhere,
|
|
3213
|
+
issues: issues
|
|
2552
3214
|
});
|
|
2553
3215
|
}
|
|
2554
3216
|
|
|
2555
|
-
function
|
|
2556
|
-
const
|
|
2557
|
-
|
|
2558
|
-
|
|
3217
|
+
function checkSiteFieldReads({site: site, everywhere: everywhere, issues: issues}) {
|
|
3218
|
+
for (const read of conditionFieldReads(site.groq)) {
|
|
3219
|
+
const targets = readTargets({
|
|
3220
|
+
site: site,
|
|
3221
|
+
read: read,
|
|
3222
|
+
everywhere: everywhere
|
|
3223
|
+
});
|
|
3224
|
+
if (targets === "undeclared") {
|
|
3225
|
+
issues.push({
|
|
3226
|
+
path: site.path,
|
|
3227
|
+
message: undeclaredFieldReadMessage(site, read.name)
|
|
3228
|
+
});
|
|
3229
|
+
continue;
|
|
3230
|
+
}
|
|
3231
|
+
const path = read.path;
|
|
3232
|
+
path !== void 0 && (targets.some(target => fieldValuePathIssue({
|
|
3233
|
+
target: target,
|
|
3234
|
+
path: path
|
|
3235
|
+
}) === void 0) || pushFieldReadPathIssue({
|
|
3236
|
+
where: site.label,
|
|
3237
|
+
read: {
|
|
3238
|
+
field: read.name,
|
|
3239
|
+
path: path
|
|
3240
|
+
},
|
|
3241
|
+
target: targets[0],
|
|
3242
|
+
path: site.path,
|
|
3243
|
+
issues: issues
|
|
3244
|
+
}));
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
function readTargets({site: site, read: read, everywhere: everywhere}) {
|
|
3249
|
+
if (site.fields === "context-dependent") return everywhere.get(read.name) ?? "undeclared";
|
|
3250
|
+
const layers = site.fields.filter(layer => layer.entries.has(read.name));
|
|
3251
|
+
if (layers.length === 0) return "undeclared";
|
|
3252
|
+
const entries = layers.map(layer => layer.entries.get(read.name));
|
|
3253
|
+
return site.unionWindow === !0 ? entries : [ entries[0] ];
|
|
2559
3254
|
}
|
|
2560
3255
|
|
|
2561
|
-
function
|
|
2562
|
-
|
|
3256
|
+
function allDeclaredFieldEntries(def) {
|
|
3257
|
+
const declared = /* @__PURE__ */ new Map;
|
|
3258
|
+
for (const {entries: entries} of fieldScopes(def)) for (const entry of entries ?? []) {
|
|
3259
|
+
const list = declared.get(entry.name);
|
|
3260
|
+
list === void 0 ? declared.set(entry.name, [ entry ]) : list.push(entry);
|
|
3261
|
+
}
|
|
3262
|
+
return declared;
|
|
2563
3263
|
}
|
|
2564
3264
|
|
|
2565
3265
|
function noFieldAnywhereMessage(label, name) {
|
|
2566
|
-
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
|
|
3266
|
+
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`;
|
|
2567
3267
|
}
|
|
2568
3268
|
|
|
2569
3269
|
function undeclaredFieldReadMessage(site, name) {
|
|
2570
3270
|
if (site.fields === "context-dependent") return noFieldAnywhereMessage(site.label, name);
|
|
2571
|
-
const visible = site.fields.flatMap(layer => [ ...layer.
|
|
2572
|
-
return `${site.label} reads $fields.${name}, but
|
|
3271
|
+
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})`;
|
|
3272
|
+
return `${site.label} reads $fields.${name}, but ${window} — the read evaluates to GROQ null, so the condition silently misevaluates. Visible: ${visible.join(", ") || "(none)"}`;
|
|
2573
3273
|
}
|
|
2574
3274
|
|
|
2575
3275
|
function checkFieldReadSeeds(def, issues) {
|
|
@@ -2671,7 +3371,7 @@ function checkFieldReadOpValues(def, issues) {
|
|
|
2671
3371
|
ops: site.ops,
|
|
2672
3372
|
path: site.path,
|
|
2673
3373
|
label: site.label,
|
|
2674
|
-
|
|
3374
|
+
activityEntries: site.activity.fields ?? []
|
|
2675
3375
|
});
|
|
2676
3376
|
}
|
|
2677
3377
|
|
|
@@ -2691,8 +3391,11 @@ function fieldReadsIn(value, path) {
|
|
|
2691
3391
|
} ] : value.type !== "object" ? [] : Object.entries(value.fields).flatMap(([key, sub]) => fieldReadsIn(sub, [ ...path, "fields", key ]));
|
|
2692
3392
|
}
|
|
2693
3393
|
|
|
2694
|
-
function checkOpFieldRead({read: read, where: where, path: path, workflowEntries: workflowEntries, stageEntries: stageEntries,
|
|
3394
|
+
function checkOpFieldRead({read: read, where: where, path: path, workflowEntries: workflowEntries, stageEntries: stageEntries, activityEntries: activityEntries, issues: issues}) {
|
|
2695
3395
|
const hosts = [ {
|
|
3396
|
+
scope: "activity",
|
|
3397
|
+
entries: activityEntries
|
|
3398
|
+
}, {
|
|
2696
3399
|
scope: "stage",
|
|
2697
3400
|
entries: stageEntries
|
|
2698
3401
|
}, {
|
|
@@ -2705,8 +3408,7 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
|
|
|
2705
3408
|
message: opFieldReadMissMessage({
|
|
2706
3409
|
read: read,
|
|
2707
3410
|
where: where,
|
|
2708
|
-
hosts: hosts
|
|
2709
|
-
activityNames: activityNames
|
|
3411
|
+
hosts: hosts
|
|
2710
3412
|
})
|
|
2711
3413
|
});
|
|
2712
3414
|
return;
|
|
@@ -2720,9 +3422,9 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
|
|
|
2720
3422
|
});
|
|
2721
3423
|
}
|
|
2722
3424
|
|
|
2723
|
-
function opFieldReadMissMessage({read: read, where: where, hosts: hosts
|
|
2724
|
-
const searched = read.scope === void 0 ? "stage or workflow scope" : `${read.scope} scope`,
|
|
2725
|
-
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
|
|
3425
|
+
function opFieldReadMissMessage({read: read, where: where, hosts: hosts}) {
|
|
3426
|
+
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}`));
|
|
3427
|
+
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)"}`;
|
|
2726
3428
|
}
|
|
2727
3429
|
|
|
2728
3430
|
function checkUpdateWhereOps(def, issues) {
|
|
@@ -2829,11 +3531,14 @@ function checkGuardFieldRead({name: name, valuePath: valuePath, where: where, pa
|
|
|
2829
3531
|
});
|
|
2830
3532
|
}
|
|
2831
3533
|
|
|
2832
|
-
function pushFieldReadPathIssue({where: where, read: read, target: target, path: path, issues: issues}) {
|
|
3534
|
+
function pushFieldReadPathIssue({where: where, read: read, target: target, path: path, issues: issues, nodes: nodes}) {
|
|
2833
3535
|
if (read.path === void 0) return;
|
|
2834
3536
|
const pathIssue = fieldValuePathIssue({
|
|
2835
3537
|
target: target,
|
|
2836
|
-
path: read.path
|
|
3538
|
+
path: read.path,
|
|
3539
|
+
...nodes ? {
|
|
3540
|
+
nodes: nodes
|
|
3541
|
+
} : {}
|
|
2837
3542
|
});
|
|
2838
3543
|
pathIssue !== void 0 && issues.push({
|
|
2839
3544
|
path: path,
|
|
@@ -2882,6 +3587,7 @@ const SCALAR = {
|
|
|
2882
3587
|
kind: "list",
|
|
2883
3588
|
item: GDR_VALUE
|
|
2884
3589
|
}),
|
|
3590
|
+
subject: () => DOC_CONTENT,
|
|
2885
3591
|
"release.ref": () => RELEASE_VALUE,
|
|
2886
3592
|
string: () => SCALAR,
|
|
2887
3593
|
text: () => SCALAR,
|
|
@@ -2904,22 +3610,27 @@ const SCALAR = {
|
|
|
2904
3610
|
kind: "rows",
|
|
2905
3611
|
of: shape.of ?? []
|
|
2906
3612
|
})
|
|
3613
|
+
}, START_ALLOWED_VALUE_NODES = {
|
|
3614
|
+
...VALUE_NODES,
|
|
3615
|
+
"doc.ref": () => GDR_VALUE,
|
|
3616
|
+
subject: () => GDR_VALUE
|
|
2907
3617
|
};
|
|
2908
3618
|
|
|
2909
|
-
function valueNodeFor(shape) {
|
|
2910
|
-
return Object.hasOwn(
|
|
3619
|
+
function valueNodeFor(shape, nodes) {
|
|
3620
|
+
return Object.hasOwn(nodes, shape.type) ? nodes[shape.type](shape) : void 0;
|
|
2911
3621
|
}
|
|
2912
3622
|
|
|
2913
3623
|
const INDEX_SEGMENT = /^\d+$/;
|
|
2914
3624
|
|
|
2915
|
-
function stepIntoValueNode(
|
|
3625
|
+
function stepIntoValueNode(args) {
|
|
3626
|
+
const {node: node, segment: segment, nodes: nodes} = args;
|
|
2916
3627
|
switch (node.kind) {
|
|
2917
3628
|
case "scalar":
|
|
2918
3629
|
return "the value is a scalar with no sub-paths";
|
|
2919
3630
|
|
|
2920
3631
|
case "object":
|
|
2921
3632
|
{
|
|
2922
|
-
const sub = node.fields.find(f => f.name === segment), next = sub === void 0 ? void 0 : valueNodeFor(sub);
|
|
3633
|
+
const sub = node.fields.find(f => f.name === segment), next = sub === void 0 ? void 0 : valueNodeFor(sub, nodes);
|
|
2923
3634
|
return next !== void 0 ? next : `an object value has sub-fields ${knownList(node.fields.map(f => f.name))} — "${segment}" is not one`;
|
|
2924
3635
|
}
|
|
2925
3636
|
|
|
@@ -2940,18 +3651,24 @@ function stepIntoValueNode(node, segment) {
|
|
|
2940
3651
|
}
|
|
2941
3652
|
}
|
|
2942
3653
|
|
|
2943
|
-
function fieldValuePathIssue({target: target, path: path}) {
|
|
2944
|
-
let node = valueNodeFor(target);
|
|
3654
|
+
function fieldValuePathIssue({target: target, path: path, nodes: nodes = VALUE_NODES}) {
|
|
3655
|
+
let node = valueNodeFor(target, nodes);
|
|
2945
3656
|
if (node === void 0) return `"${String(target.type)}" is not a known field kind`;
|
|
2946
3657
|
for (const segment of path.split(".")) {
|
|
2947
|
-
const next = stepIntoValueNode(
|
|
3658
|
+
const next = stepIntoValueNode({
|
|
3659
|
+
node: node,
|
|
3660
|
+
segment: segment,
|
|
3661
|
+
nodes: nodes
|
|
3662
|
+
});
|
|
2948
3663
|
if (typeof next == "string") return `at segment "${segment}": ${next}`;
|
|
2949
3664
|
node = next;
|
|
2950
3665
|
}
|
|
2951
3666
|
}
|
|
2952
3667
|
|
|
2953
3668
|
function checkWorkflowInvariants(def) {
|
|
2954
|
-
const issues = []
|
|
3669
|
+
const issues = [];
|
|
3670
|
+
checkDefinitionName(def, issues);
|
|
3671
|
+
const stageNames = checkStages(def, issues);
|
|
2955
3672
|
return checkInitialStage({
|
|
2956
3673
|
def: def,
|
|
2957
3674
|
stageNames: stageNames,
|
|
@@ -2966,11 +3683,12 @@ function checkWorkflowInvariants(def) {
|
|
|
2966
3683
|
issues: issues
|
|
2967
3684
|
}), checkEffectNames(def, issues), checkGuardNames(def, issues), checkFieldEntryNames(def, issues),
|
|
2968
3685
|
checkRequiredField(def, issues), checkStart(def, issues), checkPredicates(def, issues),
|
|
2969
|
-
|
|
3686
|
+
checkUnboundConditionVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
|
|
2970
3687
|
checkFieldReadOpValues(def, issues), checkUpdateWhereOps(def, issues), checkGuardFieldReads(def, issues),
|
|
2971
|
-
checkAssigneesEntries(def, issues),
|
|
3688
|
+
checkAssigneesEntries(def, issues), checkSubjectEntries(def, issues), checkSubjectEffectOutputs(def, issues),
|
|
3689
|
+
checkActivityTerminalPaths(def, issues), checkTerminalStageActivities(def, issues),
|
|
2972
3690
|
checkTriggeredActionParams(def, issues), checkStoredRolesPlacement(def, issues),
|
|
2973
3691
|
checkGroups(def, issues), issues;
|
|
2974
3692
|
}
|
|
2975
3693
|
|
|
2976
|
-
export { ACTIVITY_KINDS, ACTOR_KINDS, AuthoringActionSchema, AuthoringActivitySchema, AuthoringFieldEntrySchema, AuthoringGuardSchema, AuthoringOpSchema, AuthoringStageSchema, AuthoringTransitionSchema, AuthoringWorkflowSchema, CALLER_BOUND_VARS, CONDITION_VARS, ContractViolationError, DEFAULT_TRANSITION_WHEN, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DefinitionInUseError, DefinitionNotFoundError, EFFECTS_READ, EXECUTOR_CLASSIFICATIONS, EffectNotFoundError, EffectSchema, FIELD_READ, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GUARD_PREDICATE_VARS, GroupSchema, InstanceNotFoundError, RESERVED_CONDITION_VARS, RESOURCE_ALIAS_NAME_SOURCE, START_FILTER_VARS, StoredFieldOpSchema, WORKFLOW_DEFINITION_TYPE, WorkflowConfigSchema, WorkflowError, actorFulfillsRole, andConditions, checkFieldValue, checkWorkflowInvariants, clientConfigFromResource, conditionEffectReads, conditionFieldReadNames, conditionParameterNames, conditionSyntaxIssues, datasetResourceParts, definitionDocId, deriveActivityKind, deriveExecutorClassification, desugarWorkflow, driverKind, errorMessage, evaluateCondition, evaluateConditionOutcome, evaluatePredicates, extractDocumentId, formatIssuePath, formatIssues, formatValidationError, gdrFromResource, gdrRef, gdrResourcePrefix, gdrUri, groq, groupMembershipNames, isBareSeedId, isCascadeFired, isGdr, isGdrUri, isGuardReadExpr, isInputSourced, isNotesEntry, isStartableDefinition, isSubjectEntry, isTerminalActivityStatus, isTodoListEntry, isTodoListItem, isUnevaluable, labelFor, parseGdr, parseOrThrow, parseResourceGdr, parseStoredDefinition, readsRootDocument, refCanvas, refDashboard, refDataset, refMediaLibrary, refTypeIssues, rejectedRefTypes, releaseDocId, releaseRef, resourceAliasesToMap, resourceFromGdrUri, resourceFromParsed, resourceGdr, rethrowWithContext, runGroq, sameResource, selfGdr, startKindOf, tagScopeFilter, toBareId, toPhysicalGdr, tryParseGdr, validateFieldAppendItem, validateFieldValue, validateResourceAliasName, validateTag };
|
|
3694
|
+
export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_STATUSES, ACTOR_KINDS, ActorShape, AuthoringActionSchema, AuthoringActivitySchema, AuthoringFieldEntrySchema, AuthoringGuardSchema, AuthoringOpSchema, AuthoringStageSchema, AuthoringTransitionSchema, AuthoringWorkflowSchema, CALLER_BOUND_VARS, CONDITION_VARS, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_TRANSITION_WHEN, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DefinitionInUseError, DefinitionNotFoundError, EFFECTS_READ, EXECUTOR_CLASSIFICATIONS, EffectNotFoundError, EffectSchema, FIELD_READ, FIELD_SCOPES, FIELD_VALUE_KINDS, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GUARD_PREDICATE_VARS, GdrShape, GroupSchema, InstanceNotFoundError, IsoTimestamp, MUTATION_GUARD_ACTIONS, ModelVersionAheadError, NonEmptyString, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, RESOURCE_ALIAS_NAME_SOURCE, ReaderModelAcknowledgementError, START_ALLOWED_VARS, START_FILTER_VARS, SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR, SpawnContractsInvalidError, StoredFieldOpSchema, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowConfigSchema, WorkflowError, actorFulfillsRole, andConditions, assertReadableModel, assertReaderModelAcknowledgement, checkFieldValue, checkWorkflowInvariants, choiceValueIssues, clientConfigFromResource, conditionEffectReads, conditionFieldReadNames, conditionParameterNames, conditionSyntaxIssues, datasetResourceParts, definitionDocId, deriveActivityKind, deriveExecutorClassification, desugarWorkflow, driverKind, errorMessage, evaluateCondition, evaluateConditionOutcome, evaluatePredicates, extractDocumentId, fieldTreeShape, fieldValueSchemas, formatIssuePath, formatIssues, formatValidationError, gdrFromResource, gdrRef, gdrResourcePrefix, gdrUri, groq, groupMembershipNames, isBareSeedId, isCascadeFired, isGdr, isGdrUri, isGuardReadExpr, isInputSourced, isNotesEntry, isParseableInstant, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTerminalActivityStatus, isTodoListEntry, isTodoListItem, isUnevaluable, isUnprimed, labelFor, minReaderModelOf, modelStampFor, modelVersionOf, parentRef, parseDefinitionSnapshot, parseGdr, parseOrThrow, parsePersistedDoc, parseResourceGdr, parseStoredDefinition, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refTypeIssues, rejectedRefTypes, releaseDocId, releaseRef, requiredModelFeatures, requiredReaderModel, resourceAliasesToMap, resourceFromGdrUri, resourceFromParsed, resourceGdr, rethrowWithContext, runGroq, sameResource, scalarValidationIssues, schemaTreeShape, selfGdr, startKindOf, tagScopeFilter, terminalState, toBareId, toPhysicalGdr, tolerantEntries, tolerantObject, tryParseGdr, validateFieldAppendItem, validateFieldValue, validateResourceAliasName, validateTag };
|