@sanity/workflow-engine 0.17.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,317 @@ 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 = 3, 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
+ }), Object.freeze({
176
+ id: "progress-field-kind",
177
+ introducedInModel: 3,
178
+ minReaderModel: 0,
179
+ documentTypes: Object.freeze([ "definition", "instance" ]),
180
+ compatibility: "additive",
181
+ applicability: "detectable",
182
+ summary: "A progress field kind carries application-defined 0–100 completion."
183
+ }), Object.freeze({
184
+ id: "effect-claim-tokens",
185
+ introducedInModel: 3,
186
+ minReaderModel: 0,
187
+ documentTypes: Object.freeze([ "instance" ]),
188
+ compatibility: "additive",
189
+ applicability: "detectable",
190
+ summary: "Pending-effect claims carry an exact-claim token gating mid-dispatch state reports."
191
+ }) ]);
192
+
193
+ function recordOf(value) {
194
+ return value !== null && typeof value == "object" && !Array.isArray(value) ? value : void 0;
195
+ }
196
+
197
+ function recordsAt(record, key) {
198
+ const value = record[key];
199
+ return Array.isArray(value) ? value.map(recordOf).filter(item => item !== void 0) : [];
200
+ }
201
+
202
+ function nestedFieldEntries(entries) {
203
+ return entries.flatMap(entry => [ entry, ...nestedFieldEntries(recordsAt(entry, "fields")), ...nestedFieldEntries(recordsAt(entry, "of")) ]);
204
+ }
205
+
206
+ function parsedDefinitionSnapshot(root) {
207
+ if (typeof root.definitionSnapshot == "string") return recordOf(parseDefinitionSnapshotValue({
208
+ _id: typeof root._id == "string" ? root._id : "<unknown instance>",
209
+ definitionSnapshot: root.definitionSnapshot
210
+ }));
211
+ }
212
+
213
+ function persistedFieldEntries(document) {
214
+ const root = recordOf(document);
215
+ if (root === void 0) return [];
216
+ const snapshot = parsedDefinitionSnapshot(root), roots = snapshot === void 0 ? [ root ] : [ root, snapshot ], stages = roots.flatMap(candidate => recordsAt(candidate, "stages")), activities = stages.flatMap(stage => recordsAt(stage, "activities")), actions = activities.flatMap(activity => recordsAt(activity, "actions")), effects = actions.flatMap(action => recordsAt(action, "effects"));
217
+ return nestedFieldEntries([ ...roots.flatMap(candidate => recordsAt(candidate, "fields")), ...stages.flatMap(stage => recordsAt(stage, "fields")), ...activities.flatMap(activity => recordsAt(activity, "fields")), ...actions.flatMap(action => recordsAt(action, "params")), ...effects.flatMap(effect => recordsAt(effect, "outputs")) ]);
218
+ }
219
+
220
+ function hasChoiceList(document) {
221
+ return persistedFieldEntries(document).some(entry => {
222
+ const options = recordOf(entry.options);
223
+ return options !== void 0 && Array.isArray(options.list);
224
+ });
225
+ }
226
+
227
+ function hasFieldKind(document, kind) {
228
+ return persistedFieldEntries(document).some(entry => entry.type === kind || entry._type === kind);
229
+ }
230
+
231
+ function hasActionSemantics(document) {
232
+ const root = recordOf(document);
233
+ return root === void 0 ? !1 : recordsAt(root, "stages").flatMap(stage => recordsAt(stage, "activities")).flatMap(activity => recordsAt(activity, "actions")).some(action => Array.isArray(action.semantics));
234
+ }
235
+
236
+ function hasScalarValidation(document) {
237
+ return persistedFieldEntries(document).some(entry => {
238
+ const validation = recordOf(entry.validation);
239
+ return typeof validation?.min == "number" || typeof validation?.max == "number";
240
+ });
241
+ }
242
+
243
+ function hasClaimTokens(document) {
244
+ const root = recordOf(document);
245
+ return root === void 0 ? !1 : recordsAt(root, "pendingEffects").some(entry => {
246
+ const claim = recordOf(entry.claim);
247
+ return claim !== void 0 && typeof claim.claimToken == "string";
248
+ });
249
+ }
250
+
251
+ const featureDetectors = {
252
+ "governed-model-stamps": () => !0,
253
+ "subject-field-kind": document => hasFieldKind(document, "subject"),
254
+ "typed-scalar-choice-lists": hasChoiceList,
255
+ "action-semantics": hasActionSemantics,
256
+ "inclusive-scalar-bounds": hasScalarValidation,
257
+ "progress-field-kind": document => hasFieldKind(document, "progress"),
258
+ "effect-claim-tokens": hasClaimTokens
259
+ };
260
+
261
+ function requiredModelFeatures(documentType, document) {
262
+ return DATA_MODEL_CHANGES.filter(change => change.documentTypes.some(candidate => candidate === documentType) && featureDetectors[change.id](document));
263
+ }
264
+
265
+ function requiredReaderModel(documentType, document) {
266
+ return Math.max(0, ...requiredModelFeatures(documentType, document).map(change => change.minReaderModel));
267
+ }
268
+
269
+ function modelStampFor(args) {
270
+ return {
271
+ modelVersion: DATA_MODEL_VERSION,
272
+ minReaderModel: Math.max(args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
273
+ };
274
+ }
275
+
276
+ function fieldTreeShape(value) {
277
+ if (Array.isArray(value)) return value.map(fieldTreeShape);
278
+ if (value === null) return "null";
279
+ if (typeof value == "object") {
280
+ const record = value;
281
+ return Object.fromEntries(Object.keys(record).toSorted().map(key => [ key, fieldTreeShape(record[key]) ]));
282
+ }
283
+ return typeof value;
284
+ }
285
+
286
+ function modelVersionOf(doc) {
287
+ const stamp = doc.modelVersion;
288
+ return typeof stamp == "number" ? stamp : 0;
289
+ }
290
+
291
+ function minReaderModelOf(doc) {
292
+ const floor = doc.minReaderModel;
293
+ return typeof floor == "number" ? floor : modelVersionOf(doc);
294
+ }
295
+
296
+ class ModelVersionAheadError extends WorkflowError {
297
+ documentId;
298
+ documentModelVersion;
299
+ requiredReaderModel;
300
+ engineModelVersion;
301
+ constructor(args) {
302
+ 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.`),
303
+ this.name = "ModelVersionAheadError", this.documentId = args.documentId, this.documentModelVersion = args.documentModelVersion,
304
+ this.requiredReaderModel = args.requiredReaderModel, this.engineModelVersion = DATA_MODEL_VERSION;
305
+ }
306
+ }
307
+
308
+ function assertReadableModel(doc) {
309
+ const documentReaderModel = minReaderModelOf(doc);
310
+ if (documentReaderModel > DATA_MODEL_VERSION) throw new ModelVersionAheadError({
311
+ documentId: doc._id,
312
+ documentModelVersion: modelVersionOf(doc),
313
+ requiredReaderModel: documentReaderModel
314
+ });
315
+ return doc;
316
+ }
317
+
7
318
  function isCascadeFired(action) {
8
319
  return action.when !== void 0;
9
320
  }
@@ -24,16 +335,6 @@ function driverKind(actor) {
24
335
  return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
25
336
  }
26
337
 
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
338
  function andConditions(parts) {
38
339
  const present = parts.filter(p => p !== void 0);
39
340
  if (present.length !== 0) return present.length === 1 ? present[0] : present.map(p => `(${p})`).join(" && ");
@@ -234,71 +535,6 @@ function isGdr(value) {
234
535
  return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
235
536
  }
236
537
 
237
- class WorkflowError extends Error {
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
538
  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
539
 
304
540
  function validateTag(tag) {
@@ -321,7 +557,13 @@ function asPredicate(validate) {
321
557
  };
322
558
  }
323
559
 
324
- const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts), WorkflowResourceSchema = v.variant("type", [ v.object({
560
+ const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts);
561
+
562
+ function lakeSegment(label) {
563
+ return v.pipe(v.string(), v.nonEmpty(), v.check(isValidTag, `invalid ${label} — ${LAKE_ID_SEGMENT_GLOSS}`));
564
+ }
565
+
566
+ const WorkflowResourceSchema = v.variant("type", [ v.object({
325
567
  type: v.literal("dataset"),
326
568
  id: v.pipe(NonEmptyString$1, v.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
327
569
  }), v.object({
@@ -337,13 +579,46 @@ const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(vali
337
579
  name: v.pipe(NonEmptyString$1, v.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
338
580
  resource: WorkflowResourceSchema
339
581
  }), 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$1,
341
- tag: v.pipe(v.string(), v.nonEmpty(), v.check(isValidTag, "invalid tag — lowercase letters, digits and dashes only, no leading dash, no dots")),
582
+ name: lakeSegment("name"),
583
+ expectedMinReaderModel: v.optional(v.custom(() => !0), void 0),
584
+ tag: lakeSegment("tag"),
342
585
  workflowResource: WorkflowResourceSchema,
343
- 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"))),
586
+ resourceAliases: v.optional(v.pipe(v.array(ResourceBindingSchema), v.check(bindings => duplicateHandleMessage(bindings) === void 0, issue => duplicateHandleMessage(issue.input) ?? "duplicate resource handle name"))),
344
587
  definitions: v.pipe(v.array(DefinitionSchema), v.minLength(1, "a deployment needs at least one definition"))
345
- }), TelemetryLoggerSchema = v.custom(input => typeof input == "object" && input !== null && typeof input.log == "function", "expected a telemetry logger (an object with a `log` function)"), WorkflowConfigSchema = v.object({
346
- deployments: v.pipe(v.array(DeploymentSchema), v.minLength(1, "a config needs at least one deployment"), v.check(deployments => new Set(deployments.map(deployment => deployment.tag)).size === deployments.length, "duplicate deployment tag — each deployment must use a unique tag")),
588
+ });
589
+
590
+ function firstDuplicatePair(items, keyOf) {
591
+ const seen = /* @__PURE__ */ new Map;
592
+ for (const item of items) {
593
+ const key = keyOf(item), earlier = seen.get(key);
594
+ if (earlier !== void 0) return [ earlier, item ];
595
+ seen.set(key, item);
596
+ }
597
+ }
598
+
599
+ function duplicateHandleMessage(bindings) {
600
+ const pair = firstDuplicatePair(bindings, binding => binding.name);
601
+ if (pair !== void 0) return `duplicate resource handle name "${pair[1].name}" — each binding name must be unique within a deployment`;
602
+ }
603
+
604
+ function duplicateNameMessage(deployments) {
605
+ const pair = firstDuplicatePair(deployments, deployment => deployment.name);
606
+ if (pair !== void 0) return `duplicate deployment name "${pair[1].name}" — each deployment must use a unique name`;
607
+ }
608
+
609
+ function partitionKey(deployment) {
610
+ return `${resourceGdr(deployment.workflowResource)} ${deployment.tag}`;
611
+ }
612
+
613
+ function partitionCollisionMessage(deployments) {
614
+ const pair = firstDuplicatePair(deployments, partitionKey);
615
+ if (pair === void 0) return;
616
+ const [first, second] = pair;
617
+ return `deployments "${first.name}" and "${second.name}" share workflow resource + tag "${second.tag}" — both would write into the same partition; change one deployment’s tag or resource`;
618
+ }
619
+
620
+ const TelemetryLoggerSchema = v.custom(input => typeof input == "object" && input !== null && typeof input.log == "function", "expected a telemetry logger (an object with a `log` function)"), WorkflowConfigSchema = v.object({
621
+ deployments: v.pipe(v.array(DeploymentSchema), v.minLength(1, "a config needs at least one deployment"), v.check(deployments => duplicateNameMessage(deployments) === void 0, issue => duplicateNameMessage(issue.input) ?? "duplicate deployment name"), v.check(deployments => partitionCollisionMessage(deployments) === void 0, issue => partitionCollisionMessage(issue.input) ?? "duplicate deployment partition")),
347
622
  telemetry: v.optional(TelemetryLoggerSchema)
348
623
  });
349
624
 
@@ -580,6 +855,8 @@ function desugarFieldEntry({entry: entry, path: path, ctx: ctx}) {
580
855
  required: entry.required,
581
856
  initialValue: entry.initialValue,
582
857
  editable: editable,
858
+ options: entry.options,
859
+ validation: entry.validation,
583
860
  types: entry.types,
584
861
  fields: entry.fields,
585
862
  of: entry.of
@@ -722,7 +999,7 @@ function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ct
722
999
  };
723
1000
  }
724
1001
 
725
- const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "release.ref" ];
1002
+ const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref" ];
726
1003
 
727
1004
  function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
728
1005
  if (target === void 0 || target.type === "url") return target;
@@ -743,18 +1020,15 @@ function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
743
1020
  };
744
1021
  }
745
1022
 
746
- function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
747
- if ("type" in action) return desugarClaimAction({
748
- action: action,
749
- path: path,
750
- env: env,
751
- ctx: ctx
752
- });
753
- action.roles !== void 0 && action.roles.length === 0 && ctx.issues.push({
1023
+ function reportEmptyActionRoles({action: action, path: path, ctx: ctx}) {
1024
+ action.roles === void 0 || action.roles.length > 0 || ctx.issues.push({
754
1025
  path: [ ...path, "roles" ],
755
1026
  message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
756
1027
  });
757
- const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarOps({
1028
+ }
1029
+
1030
+ function desugarActionOps(args) {
1031
+ const {action: action, path: path, env: env, activityName: activityName, ctx: ctx} = args, ops = desugarOps({
758
1032
  ops: action.ops,
759
1033
  path: [ ...path, "ops" ],
760
1034
  env: env,
@@ -765,9 +1039,32 @@ function desugarAction({action: action, path: path, env: env, activityName: acti
765
1039
  type: "status.set",
766
1040
  activity: activityName,
767
1041
  status: action.status
768
- }), {
1042
+ }), ops;
1043
+ }
1044
+
1045
+ function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
1046
+ if ("type" in action) return desugarClaimAction({
1047
+ action: action,
1048
+ path: path,
1049
+ env: env,
1050
+ ctx: ctx
1051
+ });
1052
+ reportEmptyActionRoles({
1053
+ action: action,
1054
+ path: path,
1055
+ ctx: ctx
1056
+ });
1057
+ const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarActionOps({
1058
+ action: action,
1059
+ path: path,
1060
+ env: env,
1061
+ activityName: activityName,
1062
+ ctx: ctx
1063
+ });
1064
+ return {
769
1065
  ...stripUndefined({
770
1066
  name: action.name,
1067
+ semantics: action.semantics,
771
1068
  title: action.title,
772
1069
  description: action.description,
773
1070
  group: normalizeGroup(action.group),
@@ -1154,7 +1451,7 @@ function isTerminalActivityStatus(status) {
1154
1451
  return TERMINAL_ACTIVITY_STATUSES.includes(status);
1155
1452
  }
1156
1453
 
1157
- 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 = [ {
1454
+ 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 = [ {
1158
1455
  name: "self",
1159
1456
  binding: "always",
1160
1457
  label: "this workflow instance",
@@ -1163,7 +1460,7 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
1163
1460
  name: "fields",
1164
1461
  binding: "always",
1165
1462
  label: "the workflow's fields",
1166
- description: "Declared field entries rendered by name (`$fields.<name>` is the value, no wrapper). Stage/activity scopes overlay lexically. What a read puts in your hand follows the declared kind: a singular `doc.ref` DEREFERENCES into the hydrated document (lake `_id`/`_type` plus content fields — which may themselves be named `id`/`type`), while `doc.refs` elements and `release.ref` stay REFERENCES — `{id, type[, releaseName]}` with `id` a GDR URI — because identity reads (membership, counting, joins) must stay total without hydrating every target, and targets may live in resources the evaluation cannot fetch from. Conditions evaluate against the in-memory snapshot only, so a dereferencing plural kind would silently hole wherever hydration lagged."
1463
+ 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."
1167
1464
  }, {
1168
1465
  name: "parent",
1169
1466
  binding: "always",
@@ -1244,18 +1541,26 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
1244
1541
  binding: "always",
1245
1542
  label: "the spawned subworkflows",
1246
1543
  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`."
1247
- } ], 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 = [ {
1544
+ } ], 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 = [ {
1248
1545
  name: "tag",
1546
+ label: "this engine tag",
1249
1547
  description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
1250
1548
  }, {
1251
1549
  name: "definition",
1550
+ label: "this workflow definition",
1252
1551
  description: "The `name` of the definition under evaluation (its own start block binds it)."
1253
1552
  }, {
1254
1553
  name: "now",
1554
+ label: "the current time",
1255
1555
  description: "The ISO clock reading of the evaluating engine."
1556
+ }, {
1557
+ name: SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR,
1558
+ label: "the subject already has an in-flight workflow",
1559
+ description: "Whether any instance in this engine tag, across all definitions, has the same resource-qualified subject and no `completedAt`. Advisory under concurrent starts."
1256
1560
  } ], START_ALLOWED_VARS = [ ...START_FILTER_VARS, {
1257
1561
  name: "fields",
1258
- 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` included (nothing hydrates at the gate or the pre-flight). Pathed reads are deploy-checked against these envelope shapes."
1562
+ label: "the start's input fields",
1563
+ 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."
1259
1564
  } ], GUARD_PREDICATE_VARS = [ {
1260
1565
  name: "guard",
1261
1566
  description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
@@ -1405,6 +1710,18 @@ function releaseRef({res: res, releaseName: releaseName}) {
1405
1710
  };
1406
1711
  }
1407
1712
 
1713
+ function isSingleDocRefKind(kind) {
1714
+ return kind === "doc.ref" || kind === "subject";
1715
+ }
1716
+
1717
+ function refKindAcceptsTypes(kind) {
1718
+ return isSingleDocRefKind(kind) || kind === "doc.refs";
1719
+ }
1720
+
1721
+ function isSingleDocRefEntry(entry) {
1722
+ return isSingleDocRefKind(entry._type);
1723
+ }
1724
+
1408
1725
  function isTodoListItem(row) {
1409
1726
  if (typeof row != "object" || row === null) return !1;
1410
1727
  const candidate = row, status = candidate.status;
@@ -1455,13 +1772,39 @@ const GdrUriSchema = v.custom(s => typeof s == "string" && isGdrUri(s), "must be
1455
1772
  }), tolerantObject()({
1456
1773
  type: v.literal("role"),
1457
1774
  role: NonEmptyString
1458
- }) ]), 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, fieldValueSchemas = {
1775
+ }) ]), NullableString = v.union([ v.null(), v.string() ]), NullableNumber = v.union([ v.null(), v.number() ]), NullableBoolean = v.union([ v.null(), v.boolean() ]), NullableProgress = v.union([ v.null(), v.pipe(v.number(), v.finite("progress must be a finite number"), v.minValue(0, "progress must be at least 0"), v.maxValue(100, "progress must be at most 100")) ]), 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" ]);
1776
+
1777
+ function normalizedChoiceKind(kind) {
1778
+ return kind === "dateTime" ? "datetime" : kind;
1779
+ }
1780
+
1781
+ function checkChoiceList(args) {
1782
+ const {entryType: entryType, options: options, validation: validation} = args;
1783
+ if (options === void 0) return;
1784
+ if (!CHOICE_KINDS.has(entryType)) return [ `\`options\` is not valid on "${entryType}" values` ];
1785
+ const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => checkValueAgainst({
1786
+ entryType: kind,
1787
+ value: option.value,
1788
+ validation: validation
1789
+ }, valueSchemas)?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
1790
+ for (const option of options.list) seen.has(option.value) && issues.push(`duplicate option value ${JSON.stringify(option.value)}`),
1791
+ seen.add(option.value);
1792
+ return issues.length === 0 ? void 0 : issues;
1793
+ }
1794
+
1795
+ function choiceValueIssues(options, value) {
1796
+ 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(", ")}` ];
1797
+ }
1798
+
1799
+ const fieldValueSchemas = {
1459
1800
  "doc.ref": v.union([ v.null(), GdrShape ]),
1460
1801
  "doc.refs": v.array(GdrShape),
1802
+ subject: v.union([ v.null(), GdrShape ]),
1461
1803
  "release.ref": v.union([ v.null(), ReleaseRefShape ]),
1462
1804
  string: NullableString,
1463
1805
  text: NullableString,
1464
1806
  number: NullableNumber,
1807
+ progress: NullableProgress,
1465
1808
  boolean: NullableBoolean,
1466
1809
  date: NullableDate,
1467
1810
  datetime: NullableDateTime,
@@ -1475,7 +1818,56 @@ const GdrUriSchema = v.custom(s => typeof s == "string" && isGdrUri(s), "must be
1475
1818
  };
1476
1819
 
1477
1820
  function shapeValueSchema(shape, leaf) {
1478
- return shape.type === "object" ? objectSchema(shape.fields ?? [], leaf) : shape.type === "array" ? v.array(objectSchema(shape.of ?? [], leaf)) : leaf[shape.type] ?? v.any();
1821
+ if (shape.type === "object") return objectSchema(shape.fields ?? [], leaf);
1822
+ if (shape.type === "array") return v.array(objectSchema(shape.of ?? [], leaf));
1823
+ const schema = leaf[shape.type] ?? v.any();
1824
+ return constrainedScalarSchema({
1825
+ schema: schema,
1826
+ entryType: shape.type,
1827
+ ...shape
1828
+ });
1829
+ }
1830
+
1831
+ function scalarMeasurement(entryType, value) {
1832
+ if ((entryType === "number" || entryType === "progress") && typeof value == "number") return value;
1833
+ if ((entryType === "string" || entryType === "text") && typeof value == "string") return value.length;
1834
+ }
1835
+
1836
+ function scalarBoundIssue(args) {
1837
+ const {entryType: entryType, measured: measured, bound: bound, limit: limit} = args;
1838
+ return bound === void 0 || limit === "min" && measured >= bound || limit === "max" && measured <= bound ? void 0 : `${entryType === "number" || entryType === "progress" ? "" : "length "}must be ${limit === "min" ? "greater than or equal to" : "less than or equal to"} ${bound}`;
1839
+ }
1840
+
1841
+ function scalarValidationIssues(args) {
1842
+ const {entryType: entryType, validation: validation, value: value} = args;
1843
+ if (validation === void 0 || value === null || value === void 0) return;
1844
+ const measured = scalarMeasurement(entryType, value);
1845
+ if (measured === void 0) return;
1846
+ const issues = [ scalarBoundIssue({
1847
+ entryType: entryType,
1848
+ measured: measured,
1849
+ bound: validation.min,
1850
+ limit: "min"
1851
+ }), scalarBoundIssue({
1852
+ entryType: entryType,
1853
+ measured: measured,
1854
+ bound: validation.max,
1855
+ limit: "max"
1856
+ }) ].filter(issue => issue !== void 0);
1857
+ return issues.length === 0 ? void 0 : issues;
1858
+ }
1859
+
1860
+ function constrainedScalarSchema(args) {
1861
+ const {schema: schema, entryType: entryType, options: options, validation: validation} = args;
1862
+ return options === void 0 && validation === void 0 ? schema : v.pipe(schema, v.check(value => choiceValueIssues(options, value) === void 0 && scalarValidationIssues({
1863
+ entryType: entryType,
1864
+ validation: validation,
1865
+ value: value
1866
+ }) === void 0, issue => [ ...choiceValueIssues(options, issue.input) ?? [], ...scalarValidationIssues({
1867
+ entryType: entryType,
1868
+ validation: validation,
1869
+ value: issue.input
1870
+ }) ?? [] ].join("; ")));
1479
1871
  }
1480
1872
 
1481
1873
  function objectSchema(fields, leaf) {
@@ -1498,7 +1890,7 @@ function appendItemSchema(entryType, shape) {
1498
1890
  function rejectedRefTypes(args) {
1499
1891
  const {entryType: entryType, types: types, value: value} = args;
1500
1892
  if (types === void 0 || value === null || value === void 0) return [];
1501
- if (entryType !== "doc.ref" && entryType !== "doc.refs") return [];
1893
+ if (!refKindAcceptsTypes(entryType)) return [];
1502
1894
  let items = [ value ];
1503
1895
  return entryType === "doc.refs" && (items = Array.isArray(value) ? value : []),
1504
1896
  [ ...new Set(items.map(gdrTypeOf).filter(t => t !== void 0 && !types.includes(t))) ];
@@ -1533,6 +1925,10 @@ function checkValueAgainst(args, leaf) {
1533
1925
  entryType: args.entryType,
1534
1926
  types: args.types,
1535
1927
  value: args.value
1928
+ }) ?? choiceValueIssues(args.options, args.value) ?? scalarValidationIssues({
1929
+ entryType: args.entryType,
1930
+ validation: args.validation,
1931
+ value: args.value
1536
1932
  }) : formatIssues(result.issues);
1537
1933
  }
1538
1934
 
@@ -1563,6 +1959,7 @@ const AuthoringRefId = v.pipe(v.string(), v.check(isAuthoringRefId, "must be a b
1563
1959
  ...valueSchemas,
1564
1960
  "doc.ref": v.union([ v.null(), AuthoringGdrShape ]),
1565
1961
  "doc.refs": v.array(AuthoringGdrShape),
1962
+ subject: v.union([ v.null(), AuthoringGdrShape ]),
1566
1963
  "release.ref": v.union([ v.null(), v.looseObject({
1567
1964
  id: AuthoringRefId,
1568
1965
  type: v.literal("system.release"),
@@ -1602,10 +1999,10 @@ function validateFieldAppendItem(args) {
1602
1999
  });
1603
2000
  }
1604
2001
 
1605
- function formatIssues(issues) {
2002
+ function formatIssues(issues, formatMessage = issue => issue.message) {
1606
2003
  return issues.map(i => {
1607
2004
  const keys = i.path?.map(p => p.key) ?? [];
1608
- return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
2005
+ return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(i)}`;
1609
2006
  });
1610
2007
  }
1611
2008
 
@@ -1731,7 +2128,15 @@ function groupMembershipNames(group) {
1731
2128
  return group === void 0 ? [] : typeof group == "string" ? [ group ] : [ ...group ];
1732
2129
  }
1733
2130
 
1734
- 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>`");
2131
+ const FIELD_VALUE_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref", "string", "text", "number", "progress", "boolean", "date", "datetime", "url", "actor", "assignee", "assignees", "object", "array" ], FieldValueKindSchema = picklist(FIELD_VALUE_KINDS), FieldKindSchema = picklist(FIELD_VALUE_KINDS), FieldEntryName = groqIdentifier("`$fields.<name>`"), FiniteNumber = v.pipe(v.number(), v.finite("must be finite")), ScalarValidationSchema = v.pipe(v.strictObject({
2132
+ min: v.optional(FiniteNumber),
2133
+ max: v.optional(FiniteNumber)
2134
+ }), 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({
2135
+ list: v.pipe(v.array(v.strictObject({
2136
+ title: NonEmpty,
2137
+ value: v.union([ v.string(), v.number() ])
2138
+ })), v.minLength(1, "declare at least one choice, or omit `options`"))
2139
+ });
1735
2140
 
1736
2141
  function asShape(input) {
1737
2142
  return typeof input == "object" && input !== null ? input : {};
@@ -1765,14 +2170,16 @@ function compositeChecked(entries) {
1765
2170
  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\``));
1766
2171
  }
1767
2172
 
1768
- const FieldShapeSchema = v.lazy(() => compositeChecked({
2173
+ const FieldShapeSchema = v.lazy(() => v.pipe(compositeChecked({
1769
2174
  type: FieldValueKindSchema,
1770
2175
  name: FieldEntryName,
1771
2176
  title: v.optional(v.string()),
1772
2177
  description: v.optional(v.string()),
2178
+ options: v.optional(ChoiceOptionsSchema),
2179
+ validation: v.optional(ScalarValidationSchema),
1773
2180
  fields: v.optional(v.array(FieldShapeSchema)),
1774
2181
  of: v.optional(v.array(FieldShapeSchema))
1775
- })), StoredEditableSchema = v.union([ v.literal(!0), NonEmpty ]), AuthoringEditableSchema = v.union([ v.literal(!0), v.array(NonEmpty), NonEmpty ]);
2182
+ }), choiceOptionsCheck(), scalarValidationCheck())), StoredEditableSchema = v.union([ v.literal(!0), NonEmpty ]), AuthoringEditableSchema = v.union([ v.literal(!0), v.array(NonEmpty), NonEmpty ]);
1776
2183
 
1777
2184
  function fieldBase(editable, group) {
1778
2185
  return {
@@ -1790,6 +2197,8 @@ function fieldEntryFields(editable, group) {
1790
2197
  return {
1791
2198
  type: FieldKindSchema,
1792
2199
  ...fieldBase(editable, group),
2200
+ options: v.optional(ChoiceOptionsSchema),
2201
+ validation: v.optional(ScalarValidationSchema),
1793
2202
  types: v.optional(v.pipe(v.array(NonEmpty), v.minLength(1, "declare at least one accepted type, or omit `types` to accept any"))),
1794
2203
  fields: v.optional(v.array(FieldShapeSchema)),
1795
2204
  of: v.optional(v.array(FieldShapeSchema))
@@ -1802,7 +2211,9 @@ function literalSeedIssues(entry) {
1802
2211
  value: entry.initialValue.value,
1803
2212
  types: entry.types,
1804
2213
  fields: entry.fields,
1805
- of: entry.of
2214
+ of: entry.of,
2215
+ options: entry.options,
2216
+ validation: entry.validation
1806
2217
  });
1807
2218
  }
1808
2219
 
@@ -1811,10 +2222,41 @@ function literalSeedCheck() {
1811
2222
  }
1812
2223
 
1813
2224
  function refTypesCheck() {
1814
- return v.check(entry => entry.types === void 0 || entry.type === "doc.ref" || entry.type === "doc.refs", issue => `\`types\` is only valid on \`doc.ref\` / \`doc.refs\` entries, not "${issue.input.type}"`);
2225
+ 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}"`);
1815
2226
  }
1816
2227
 
1817
- const FieldEntrySchema = pinned()(v.pipe(compositeChecked(fieldEntryFields(StoredEditableSchema, StoredGroupMembershipSchema)), refTypesCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v.pipe(compositeChecked(fieldEntryFields(AuthoringEditableSchema, AuthoringGroupMembershipSchema)), refTypesCheck(), literalSeedCheck())), ClaimFieldSchema = pinned()(v.strictObject({
2228
+ function choiceOptionsCheck() {
2229
+ return v.check(entry => checkChoiceList({
2230
+ entryType: entry.type,
2231
+ options: entry.options,
2232
+ validation: entry.validation
2233
+ }) === void 0, issue => (checkChoiceList({
2234
+ entryType: issue.input.type,
2235
+ options: issue.input.options,
2236
+ validation: issue.input.validation
2237
+ }) ?? []).join("; "));
2238
+ }
2239
+
2240
+ const SCALAR_VALIDATION_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "progress" ]);
2241
+
2242
+ function scalarValidationCheck() {
2243
+ return v.check(entry => scalarValidationDeclarationIssues(entry) === void 0, issue => (scalarValidationDeclarationIssues(issue.input) ?? []).join("; "));
2244
+ }
2245
+
2246
+ function scalarValidationDeclarationIssues(entry) {
2247
+ const {type: type, validation: validation} = entry;
2248
+ if (validation === void 0) return;
2249
+ if (!SCALAR_VALIDATION_KINDS.has(type)) return [ `\`validation\` is only valid on \`string\` / \`text\` / \`number\` / \`progress\` values, not "${type}"` ];
2250
+ if (type === "progress") {
2251
+ const issues2 = Object.entries(validation).flatMap(([bound, value]) => typeof value == "number" && value >= 0 && value <= 100 ? [] : [ `\`validation.${bound}\` must stay within the progress kind's 0–100 contract` ]);
2252
+ return issues2.length === 0 ? void 0 : issues2;
2253
+ }
2254
+ if (type === "number") return;
2255
+ const issues = Object.entries(validation).flatMap(([bound, value]) => Number.isInteger(value) && value >= 0 ? [] : [ `\`validation.${bound}\` must be a non-negative integer for ${type} length` ]);
2256
+ return issues.length === 0 ? void 0 : issues;
2257
+ }
2258
+
2259
+ 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({
1818
2260
  type: v.literal("claim"),
1819
2261
  name: FieldEntryName,
1820
2262
  title: v.optional(v.string()),
@@ -1845,17 +2287,25 @@ const TodoListFieldSchema = pinned()(v.strictObject(listSugarFields("todoList"))
1845
2287
  with: v.optional(v.record(NonEmpty, ConditionSchema)),
1846
2288
  context: v.optional(v.record(NonEmpty, ConditionSchema)),
1847
2289
  onExit: v.optional(picklist([ "detach", "abort" ]))
1848
- }), ActionParamSchema = v.strictObject({
2290
+ }), ActionParamSchema = v.pipe(v.strictObject({
1849
2291
  type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
1850
2292
  name: NonEmpty,
1851
2293
  title: v.optional(v.string()),
1852
2294
  description: v.optional(v.string()),
1853
- required: v.optional(v.boolean())
1854
- });
2295
+ required: v.optional(v.boolean()),
2296
+ options: v.optional(ChoiceOptionsSchema),
2297
+ validation: v.optional(ScalarValidationSchema)
2298
+ }), choiceOptionsCheck(), scalarValidationCheck());
2299
+
2300
+ function hasUniqueSemanticNamespaces(semantics) {
2301
+ const namespaces = semantics.map(semantic => semantic.split(".", 1)[0]);
2302
+ return new Set(namespaces).size === namespaces.length;
2303
+ }
1855
2304
 
1856
2305
  function actionFields(op, group) {
1857
2306
  return {
1858
2307
  name: NonEmpty,
2308
+ 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"))),
1859
2309
  title: v.optional(v.string()),
1860
2310
  description: v.optional(v.string()),
1861
2311
  group: v.optional(group),
@@ -2044,7 +2494,7 @@ function startKindOf(definition) {
2044
2494
  }
2045
2495
 
2046
2496
  function isSubjectEntry(entry) {
2047
- return entry.required === !0 && (entry.type === "doc.ref" || entry.type === "doc.refs");
2497
+ return entry.type === "subject";
2048
2498
  }
2049
2499
 
2050
2500
  function isInputSourced(entry) {
@@ -2546,6 +2996,52 @@ function checkAssigneesEntries(def, issues) {
2546
2996
  });
2547
2997
  }
2548
2998
 
2999
+ function nestedSubjectPaths(shapes, base) {
3000
+ return (shapes ?? []).flatMap((shape, i) => isSubjectEntry(shape) ? [ [ ...base, i, "type" ] ] : [ ...nestedSubjectPaths(shape.fields, [ ...base, i, "fields" ]), ...nestedSubjectPaths(shape.of, [ ...base, i, "of" ]) ]);
3001
+ }
3002
+
3003
+ function checkSubjectEffectOutputs(def, issues) {
3004
+ 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({
3005
+ action: action,
3006
+ path: [ "stages", i, "activities", j, "actions", a ],
3007
+ issues: issues
3008
+ });
3009
+ }
3010
+
3011
+ function pushSubjectOutputIssues(args) {
3012
+ const {action: action, path: path, issues: issues} = args;
3013
+ for (const [e, effect] of (action.effects ?? []).entries()) {
3014
+ const base = [ ...path, "effects", e, "outputs" ];
3015
+ for (const nestedPath of nestedSubjectPaths(effect.outputs, base)) issues.push({
3016
+ path: nestedPath,
3017
+ 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`
3018
+ });
3019
+ }
3020
+ }
3021
+
3022
+ function checkSubjectEntries(def, issues) {
3023
+ for (const {entries: entries, scope: scope, path: path, label: label} of fieldScopes(def)) {
3024
+ const subjects = (entries ?? []).flatMap((entry, n) => isSubjectEntry(entry) ? [ {
3025
+ entry: entry,
3026
+ n: n
3027
+ } ] : []);
3028
+ if (scope !== "workflow") for (const {entry: entry, n: n} of subjects) issues.push({
3029
+ path: [ ...path, n, "type" ],
3030
+ 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`
3031
+ }); else for (const {n: n} of subjects.slice(1)) issues.push({
3032
+ path: [ ...path, n ],
3033
+ message: "at most one subject-kind field entry per workflow — the runtime identifies THE subject by kind, and a second one makes it ambiguous"
3034
+ });
3035
+ for (const [n, entry] of (entries ?? []).entries()) {
3036
+ const nested = [ ...nestedSubjectPaths(entry.fields, [ ...path, n, "fields" ]), ...nestedSubjectPaths(entry.of, [ ...path, n, "of" ]) ];
3037
+ for (const nestedPath of nested) issues.push({
3038
+ path: nestedPath,
3039
+ 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`
3040
+ });
3041
+ }
3042
+ }
3043
+ }
3044
+
2549
3045
  function checkActivityTerminalPaths(def, issues) {
2550
3046
  for (const [i, stage] of def.stages.entries()) {
2551
3047
  const resolvable = terminallyResolvableActivities(stage);
@@ -2596,12 +3092,30 @@ function checkStart(def, issues) {
2596
3092
  if (def.start !== void 0 && (def.lifecycle === "child" && issues.push({
2597
3093
  path: [ "start" ],
2598
3094
  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'`"
2599
- }), checkStartFilterReads(def, issues), checkStartAllowedReads(def, issues), def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
3095
+ }), checkStartFilterReads(def, issues), checkStartAllowedReads(def, issues), checkStartSubjectVariable(def, issues),
3096
+ def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
2600
3097
  path: [ "fields", n, "required" ],
2601
- 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 a document reference. Make it optional, seed it another way (query/literal), or declare it doc.ref / doc.refs`
3098
+ 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)`
2602
3099
  });
2603
3100
  }
2604
3101
 
3102
+ function checkStartSubjectVariable(def, issues) {
3103
+ const subject = (def.fields ?? []).find(isSubjectEntry);
3104
+ 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)) {
3105
+ if (subject === void 0) {
3106
+ issues.push({
3107
+ path: [ "start", key ],
3108
+ 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`
3109
+ });
3110
+ continue;
3111
+ }
3112
+ key === "allowed" && !isInputSourced(subject) && issues.push({
3113
+ path: [ "start", key ],
3114
+ 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`
3115
+ });
3116
+ }
3117
+ }
3118
+
2605
3119
  function checkStartFilterReads(def, issues) {
2606
3120
  const filter = def.start?.filter;
2607
3121
  filter !== void 0 && (conditionParameterNames(filter).has("fields") && issues.push({
@@ -2609,7 +3123,7 @@ function checkStartFilterReads(def, issues) {
2609
3123
  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"
2610
3124
  }), readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
2611
3125
  path: [ "start", "filter" ],
2612
- message: "start.filter reads the candidate document (its root), but the definition declares no required subject entry (a required doc.ref / doc.refs input) — 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 required subject entry, or gate on the dataset instead"
3126
+ 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"
2613
3127
  }));
2614
3128
  }
2615
3129
 
@@ -2926,7 +3440,7 @@ function checkFieldReadOpValues(def, issues) {
2926
3440
  ops: site.ops,
2927
3441
  path: site.path,
2928
3442
  label: site.label,
2929
- activityEntries: entryMap(site.activity.fields)
3443
+ activityEntries: site.activity.fields ?? []
2930
3444
  });
2931
3445
  }
2932
3446
 
@@ -2948,6 +3462,9 @@ function fieldReadsIn(value, path) {
2948
3462
 
2949
3463
  function checkOpFieldRead({read: read, where: where, path: path, workflowEntries: workflowEntries, stageEntries: stageEntries, activityEntries: activityEntries, issues: issues}) {
2950
3464
  const hosts = [ {
3465
+ scope: "activity",
3466
+ entries: activityEntries
3467
+ }, {
2951
3468
  scope: "stage",
2952
3469
  entries: stageEntries
2953
3470
  }, {
@@ -2960,8 +3477,7 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
2960
3477
  message: opFieldReadMissMessage({
2961
3478
  read: read,
2962
3479
  where: where,
2963
- hosts: hosts,
2964
- activityEntries: activityEntries
3480
+ hosts: hosts
2965
3481
  })
2966
3482
  });
2967
3483
  return;
@@ -2975,9 +3491,9 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
2975
3491
  });
2976
3492
  }
2977
3493
 
2978
- function opFieldReadMissMessage({read: read, where: where, hosts: hosts, activityEntries: activityEntries}) {
2979
- const searched = read.scope === void 0 ? "stage or workflow scope" : `${read.scope} scope`, activityHint = activityEntries?.has(read.field) ? ` "${read.field}" is an activity-scope field, and op-time field reads resolve against stage and workflow scopes only — declare it at stage scope to read it from an op.` : "", known = hosts.flatMap(host => host.entries.map(entry => `${host.scope}:${entry.name}`));
2980
- return `${where} reads field "${read.field}", which is not declared at ${searched} — the read resolves to undefined at op time, so the write silently lands empty.${activityHint} Known: ${known.join(", ") || "(none)"}`;
3494
+ function opFieldReadMissMessage({read: read, where: where, hosts: hosts}) {
3495
+ 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}`));
3496
+ 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)"}`;
2981
3497
  }
2982
3498
 
2983
3499
  function checkUpdateWhereOps(def, issues) {
@@ -3140,10 +3656,12 @@ const SCALAR = {
3140
3656
  kind: "list",
3141
3657
  item: GDR_VALUE
3142
3658
  }),
3659
+ subject: () => DOC_CONTENT,
3143
3660
  "release.ref": () => RELEASE_VALUE,
3144
3661
  string: () => SCALAR,
3145
3662
  text: () => SCALAR,
3146
3663
  number: () => SCALAR,
3664
+ progress: () => SCALAR,
3147
3665
  boolean: () => SCALAR,
3148
3666
  date: () => SCALAR,
3149
3667
  datetime: () => SCALAR,
@@ -3164,7 +3682,8 @@ const SCALAR = {
3164
3682
  })
3165
3683
  }, START_ALLOWED_VALUE_NODES = {
3166
3684
  ...VALUE_NODES,
3167
- "doc.ref": () => GDR_VALUE
3685
+ "doc.ref": () => GDR_VALUE,
3686
+ subject: () => GDR_VALUE
3168
3687
  };
3169
3688
 
3170
3689
  function valueNodeFor(shape, nodes) {
@@ -3236,9 +3755,10 @@ function checkWorkflowInvariants(def) {
3236
3755
  checkRequiredField(def, issues), checkStart(def, issues), checkPredicates(def, issues),
3237
3756
  checkUnboundConditionVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
3238
3757
  checkFieldReadOpValues(def, issues), checkUpdateWhereOps(def, issues), checkGuardFieldReads(def, issues),
3239
- checkAssigneesEntries(def, issues), checkActivityTerminalPaths(def, issues), checkTerminalStageActivities(def, issues),
3758
+ checkAssigneesEntries(def, issues), checkSubjectEntries(def, issues), checkSubjectEffectOutputs(def, issues),
3759
+ checkActivityTerminalPaths(def, issues), checkTerminalStageActivities(def, issues),
3240
3760
  checkTriggeredActionParams(def, issues), checkStoredRolesPlacement(def, issues),
3241
3761
  checkGroups(def, issues), issues;
3242
3762
  }
3243
3763
 
3244
- export { ACTIVITY_KINDS, ACTIVITY_STATUSES, ACTOR_KINDS, ActorShape, 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, FIELD_SCOPES, FIELD_VALUE_KINDS, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GUARD_PREDICATE_VARS, GdrShape, GroupSchema, InstanceNotFoundError, IsoTimestamp, MUTATION_GUARD_ACTIONS, NonEmptyString, PersistedDocShapeError, RESERVED_CONDITION_VARS, RESOURCE_ALIAS_NAME_SOURCE, START_ALLOWED_VARS, 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, fieldValueSchemas, formatIssuePath, formatIssues, formatValidationError, gdrFromResource, gdrRef, gdrResourcePrefix, gdrUri, groq, groupMembershipNames, isBareSeedId, isCascadeFired, isGdr, isGdrUri, isGuardReadExpr, isInputSourced, isNotesEntry, isParseableInstant, isStartableDefinition, isSubjectEntry, isTerminalActivityStatus, isTodoListEntry, isTodoListItem, isUnevaluable, labelFor, parseGdr, parseOrThrow, parsePersistedDoc, parseResourceGdr, parseStoredDefinition, readsRootDocument, refCanvas, refDashboard, refDataset, refMediaLibrary, refTypeIssues, rejectedRefTypes, releaseDocId, releaseRef, resourceAliasesToMap, resourceFromGdrUri, resourceFromParsed, resourceGdr, rethrowWithContext, runGroq, sameResource, schemaTreeShape, selfGdr, startKindOf, tagScopeFilter, toBareId, toPhysicalGdr, tolerantEntries, tolerantObject, tryParseGdr, validateFieldAppendItem, validateFieldValue, validateResourceAliasName, validateTag };
3764
+ 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 };