@sanity/workflow-engine 0.17.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.
@@ -20,6 +20,291 @@ function _interopNamespaceCompat(e) {
20
20
 
21
21
  var v__namespace = /* @__PURE__ */ _interopNamespaceCompat(v);
22
22
 
23
+ class WorkflowError extends Error {
24
+ kind;
25
+ constructor(kind, message, options) {
26
+ super(message, options), this.kind = kind;
27
+ }
28
+ }
29
+
30
+ class ContractViolationError extends WorkflowError {
31
+ constructor(message) {
32
+ super("contract-violation", message), this.name = "ContractViolationError";
33
+ }
34
+ }
35
+
36
+ class InstanceNotFoundError extends WorkflowError {
37
+ instanceId;
38
+ constructor(args) {
39
+ super("instance-not-found", `Workflow instance ${args.instanceId} not found${args.detail ? ` (${args.detail})` : ""}`),
40
+ this.name = "InstanceNotFoundError", this.instanceId = args.instanceId;
41
+ }
42
+ }
43
+
44
+ class DefinitionNotFoundError extends WorkflowError {
45
+ definition;
46
+ version;
47
+ constructor(args) {
48
+ super("definition-not-found", args.version !== void 0 ? `Workflow definition ${args.definition} v${args.version} not deployed` : `Workflow definition ${args.definition} has no deployed versions`),
49
+ this.name = "DefinitionNotFoundError", this.definition = args.definition, args.version !== void 0 && (this.version = args.version);
50
+ }
51
+ }
52
+
53
+ class SpawnContractsInvalidError extends WorkflowError {
54
+ issues;
55
+ constructor(args) {
56
+ super("spawn-contracts-invalid", args.message), this.name = "SpawnContractsInvalidError",
57
+ this.issues = args.issues;
58
+ }
59
+ }
60
+
61
+ class DefinitionInUseError extends WorkflowError {
62
+ definition;
63
+ blockedBy;
64
+ constructor(args) {
65
+ super("definition-in-use", definitionInUseMessage(args.definition, args.blockedBy)),
66
+ this.name = "DefinitionInUseError", this.definition = args.definition, this.blockedBy = args.blockedBy;
67
+ }
68
+ }
69
+
70
+ function definitionInUseMessage(definition, blockedBy) {
71
+ if (blockedBy.reason === "non-terminal-instances") {
72
+ const head = blockedBy.instanceIds.slice(0, 3).join(", "), preview = blockedBy.instanceIds.length > 3 ? `${head}, …` : head;
73
+ return `Cannot delete ${definition}: ${blockedBy.instanceIds.length} non-terminal instance(s) exist (${preview}). Pass cascade to abort them first — instances are aborted in place, never deleted.`;
74
+ }
75
+ const names = blockedBy.referrers.map(r => `${r.definition} v${r.version}`).join(", ");
76
+ return `Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`;
77
+ }
78
+
79
+ class EffectNotFoundError extends WorkflowError {
80
+ instanceId;
81
+ effectKey;
82
+ settled;
83
+ constructor(args) {
84
+ super("effect-not-found", effectNotFoundMessage(args)), this.name = "EffectNotFoundError",
85
+ this.instanceId = args.instanceId, this.effectKey = args.effectKey, args.settled !== void 0 && (this.settled = args.settled);
86
+ }
87
+ }
88
+
89
+ function effectNotFoundMessage(args) {
90
+ const base = `Pending effect "${args.effectKey}" not found on instance ${args.instanceId}`;
91
+ if (args.settled === void 0) return base;
92
+ const cause = args.settled.detail !== void 0 ? ` (${args.settled.detail})` : "";
93
+ return args.settled.status === "cancelled" ? `${base} — it was cancelled at ${args.settled.ranAt}${cause}` : `${base} — it already settled "${args.settled.status}" at ${args.settled.ranAt}${cause}`;
94
+ }
95
+
96
+ function errorMessage(err) {
97
+ return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
98
+ }
99
+
100
+ function rethrowWithContext(err, context) {
101
+ throw new Error(`${context}: ${errorMessage(err)}`, {
102
+ cause: err
103
+ });
104
+ }
105
+
106
+ const WORKFLOW_INSTANCE_TYPE = "sanity.workflow.instance";
107
+
108
+ function terminalState(instance) {
109
+ return instance.abortedAt !== void 0 ? "aborted" : instance.completedAt !== void 0 ? "completed" : "in-flight";
110
+ }
111
+
112
+ function isUnprimed(instance) {
113
+ return instance.stages.length === 0 && terminalState(instance) === "in-flight";
114
+ }
115
+
116
+ function parseDefinitionSnapshotValue(instance) {
117
+ try {
118
+ return JSON.parse(instance.definitionSnapshot);
119
+ } catch (err) {
120
+ rethrowWithContext(err, `Failed to parse definitionSnapshot on instance "${instance._id}"`);
121
+ }
122
+ }
123
+
124
+ function parseDefinitionSnapshot(instance) {
125
+ return parseDefinitionSnapshotValue(instance);
126
+ }
127
+
128
+ function parentRef(instance) {
129
+ return instance.ancestors.at(-1);
130
+ }
131
+
132
+ const DATA_MODEL_VERSION = 2, DATA_MODEL_MIN_READER = 2, READER_MODEL_ROLLOUT_URL = "https://github.com/sanity-io/workflows/blob/main/docs/reader-model-rollout.md";
133
+
134
+ class ReaderModelAcknowledgementError extends WorkflowError {
135
+ code="WORKFLOW_READER_MODEL_ACKNOWLEDGEMENT_MISMATCH";
136
+ expectedMinReaderModel;
137
+ engineMinReaderModel=DATA_MODEL_MIN_READER;
138
+ engineModelVersion=DATA_MODEL_VERSION;
139
+ documentationUrl=READER_MODEL_ROLLOUT_URL;
140
+ constructor(expectedMinReaderModel, context = "Deployment") {
141
+ const expected = expectedMinReaderModel === void 0 ? "missing" : String(expectedMinReaderModel);
142
+ super("reader-model-acknowledgement", `${context} expected reader floor ${expected}; the installed engine requires acknowledgement ${DATA_MODEL_MIN_READER}. Dependency upgrades can change writer compatibility. Upgrade all readers and Functions before accepting a higher floor; after verifying the rollout, change the literal in deployment configuration. ${READER_MODEL_ROLLOUT_URL}`),
143
+ this.name = "ReaderModelAcknowledgementError", this.expectedMinReaderModel = expectedMinReaderModel;
144
+ }
145
+ }
146
+
147
+ function assertReaderModelAcknowledgement(expectedMinReaderModel, context) {
148
+ if (typeof expectedMinReaderModel != "number" || !Number.isFinite(expectedMinReaderModel) || !Number.isInteger(expectedMinReaderModel) || expectedMinReaderModel < 0 || expectedMinReaderModel !== DATA_MODEL_MIN_READER) throw new ReaderModelAcknowledgementError(expectedMinReaderModel, context);
149
+ }
150
+
151
+ const DATA_MODEL_CHANGES = Object.freeze([ Object.freeze({
152
+ id: "governed-model-stamps",
153
+ introducedInModel: 1,
154
+ minReaderModel: 0,
155
+ documentTypes: Object.freeze([ "definition", "instance" ]),
156
+ compatibility: "additive",
157
+ applicability: "unconditional",
158
+ summary: "Definition and instance documents carry model provenance and reader-floor stamps."
159
+ }), Object.freeze({
160
+ id: "subject-field-kind",
161
+ introducedInModel: 2,
162
+ minReaderModel: 0,
163
+ documentTypes: Object.freeze([ "definition", "instance" ]),
164
+ compatibility: "additive",
165
+ applicability: "detectable",
166
+ summary: "A workflow-level subject field identifies the document a workflow is about."
167
+ }), Object.freeze({
168
+ id: "typed-scalar-choice-lists",
169
+ introducedInModel: 2,
170
+ minReaderModel: 2,
171
+ documentTypes: Object.freeze([ "definition", "instance" ]),
172
+ compatibility: "reader-floor",
173
+ applicability: "detectable",
174
+ summary: "Scalar fields may constrain writes to a persisted typed choice list."
175
+ }), Object.freeze({
176
+ id: "action-semantics",
177
+ introducedInModel: 2,
178
+ minReaderModel: 0,
179
+ documentTypes: Object.freeze([ "definition" ]),
180
+ compatibility: "additive",
181
+ applicability: "detectable",
182
+ summary: "Ordinary actions may carry a closed bag of advisory workflow semantics."
183
+ }), Object.freeze({
184
+ id: "inclusive-scalar-bounds",
185
+ introducedInModel: 2,
186
+ minReaderModel: 2,
187
+ documentTypes: Object.freeze([ "definition", "instance" ]),
188
+ compatibility: "reader-floor",
189
+ applicability: "detectable",
190
+ summary: "String, text, and number values may carry persisted inclusive bounds."
191
+ }) ]);
192
+
193
+ function recordOf(value) {
194
+ return value !== null && typeof value == "object" && !Array.isArray(value) ? value : void 0;
195
+ }
196
+
197
+ function recordsAt(record, key) {
198
+ const value = record[key];
199
+ return Array.isArray(value) ? value.map(recordOf).filter(item => item !== void 0) : [];
200
+ }
201
+
202
+ function nestedFieldEntries(entries) {
203
+ return entries.flatMap(entry => [ entry, ...nestedFieldEntries(recordsAt(entry, "fields")), ...nestedFieldEntries(recordsAt(entry, "of")) ]);
204
+ }
205
+
206
+ function parsedDefinitionSnapshot(root) {
207
+ if (typeof root.definitionSnapshot == "string") return recordOf(parseDefinitionSnapshotValue({
208
+ _id: typeof root._id == "string" ? root._id : "<unknown instance>",
209
+ definitionSnapshot: root.definitionSnapshot
210
+ }));
211
+ }
212
+
213
+ function persistedFieldEntries(document) {
214
+ const root = recordOf(document);
215
+ if (root === void 0) return [];
216
+ const snapshot = parsedDefinitionSnapshot(root), roots = snapshot === void 0 ? [ root ] : [ root, snapshot ], stages = roots.flatMap(candidate => recordsAt(candidate, "stages")), activities = stages.flatMap(stage => recordsAt(stage, "activities")), actions = activities.flatMap(activity => recordsAt(activity, "actions")), effects = actions.flatMap(action => recordsAt(action, "effects"));
217
+ return nestedFieldEntries([ ...roots.flatMap(candidate => recordsAt(candidate, "fields")), ...stages.flatMap(stage => recordsAt(stage, "fields")), ...activities.flatMap(activity => recordsAt(activity, "fields")), ...actions.flatMap(action => recordsAt(action, "params")), ...effects.flatMap(effect => recordsAt(effect, "outputs")) ]);
218
+ }
219
+
220
+ function hasChoiceList(document) {
221
+ return persistedFieldEntries(document).some(entry => {
222
+ const options = recordOf(entry.options);
223
+ return options !== void 0 && Array.isArray(options.list);
224
+ });
225
+ }
226
+
227
+ function hasSubjectField(document) {
228
+ return persistedFieldEntries(document).some(entry => entry.type === "subject" || entry._type === "subject");
229
+ }
230
+
231
+ function hasActionSemantics(document) {
232
+ const root = recordOf(document);
233
+ return root === void 0 ? !1 : recordsAt(root, "stages").flatMap(stage => recordsAt(stage, "activities")).flatMap(activity => recordsAt(activity, "actions")).some(action => Array.isArray(action.semantics));
234
+ }
235
+
236
+ function hasScalarValidation(document) {
237
+ return persistedFieldEntries(document).some(entry => {
238
+ const validation = recordOf(entry.validation);
239
+ return typeof validation?.min == "number" || typeof validation?.max == "number";
240
+ });
241
+ }
242
+
243
+ const featureDetectors = {
244
+ "governed-model-stamps": () => !0,
245
+ "subject-field-kind": hasSubjectField,
246
+ "typed-scalar-choice-lists": hasChoiceList,
247
+ "action-semantics": hasActionSemantics,
248
+ "inclusive-scalar-bounds": hasScalarValidation
249
+ };
250
+
251
+ function requiredModelFeatures(documentType, document) {
252
+ return DATA_MODEL_CHANGES.filter(change => change.documentTypes.some(candidate => candidate === documentType) && featureDetectors[change.id](document));
253
+ }
254
+
255
+ function requiredReaderModel(documentType, document) {
256
+ return Math.max(0, ...requiredModelFeatures(documentType, document).map(change => change.minReaderModel));
257
+ }
258
+
259
+ function modelStampFor(args) {
260
+ return {
261
+ modelVersion: DATA_MODEL_VERSION,
262
+ minReaderModel: Math.max(args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
263
+ };
264
+ }
265
+
266
+ function fieldTreeShape(value) {
267
+ if (Array.isArray(value)) return value.map(fieldTreeShape);
268
+ if (value === null) return "null";
269
+ if (typeof value == "object") {
270
+ const record = value;
271
+ return Object.fromEntries(Object.keys(record).toSorted().map(key => [ key, fieldTreeShape(record[key]) ]));
272
+ }
273
+ return typeof value;
274
+ }
275
+
276
+ function modelVersionOf(doc) {
277
+ const stamp = doc.modelVersion;
278
+ return typeof stamp == "number" ? stamp : 0;
279
+ }
280
+
281
+ function minReaderModelOf(doc) {
282
+ const floor = doc.minReaderModel;
283
+ return typeof floor == "number" ? floor : modelVersionOf(doc);
284
+ }
285
+
286
+ class ModelVersionAheadError extends WorkflowError {
287
+ documentId;
288
+ documentModelVersion;
289
+ requiredReaderModel;
290
+ engineModelVersion;
291
+ constructor(args) {
292
+ super("model-version-ahead", `Document "${args.documentId}" was written by engine data model ${args.documentModelVersion} and requires a reader at model ${args.requiredReaderModel} or newer; this engine reads up to model ${DATA_MODEL_VERSION}. Upgrade @sanity/workflow-engine to a version that understands it.`),
293
+ this.name = "ModelVersionAheadError", this.documentId = args.documentId, this.documentModelVersion = args.documentModelVersion,
294
+ this.requiredReaderModel = args.requiredReaderModel, this.engineModelVersion = DATA_MODEL_VERSION;
295
+ }
296
+ }
297
+
298
+ function assertReadableModel(doc) {
299
+ const documentReaderModel = minReaderModelOf(doc);
300
+ if (documentReaderModel > DATA_MODEL_VERSION) throw new ModelVersionAheadError({
301
+ documentId: doc._id,
302
+ documentModelVersion: modelVersionOf(doc),
303
+ requiredReaderModel: documentReaderModel
304
+ });
305
+ return doc;
306
+ }
307
+
23
308
  function isCascadeFired(action) {
24
309
  return action.when !== void 0;
25
310
  }
@@ -40,16 +325,6 @@ function driverKind(actor) {
40
325
  return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
41
326
  }
42
327
 
43
- function errorMessage(err) {
44
- return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
45
- }
46
-
47
- function rethrowWithContext(err, context) {
48
- throw new Error(`${context}: ${errorMessage(err)}`, {
49
- cause: err
50
- });
51
- }
52
-
53
328
  function andConditions(parts) {
54
329
  const present = parts.filter(p => p !== void 0);
55
330
  if (present.length !== 0) return present.length === 1 ? present[0] : present.map(p => `(${p})`).join(" && ");
@@ -250,71 +525,6 @@ function isGdr(value) {
250
525
  return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
251
526
  }
252
527
 
253
- class WorkflowError extends Error {
254
- kind;
255
- constructor(kind, message, options) {
256
- super(message, options), this.kind = kind;
257
- }
258
- }
259
-
260
- class ContractViolationError extends WorkflowError {
261
- constructor(message) {
262
- super("contract-violation", message), this.name = "ContractViolationError";
263
- }
264
- }
265
-
266
- class InstanceNotFoundError extends WorkflowError {
267
- instanceId;
268
- constructor(args) {
269
- super("instance-not-found", `Workflow instance ${args.instanceId} not found${args.detail ? ` (${args.detail})` : ""}`),
270
- this.name = "InstanceNotFoundError", this.instanceId = args.instanceId;
271
- }
272
- }
273
-
274
- class DefinitionNotFoundError extends WorkflowError {
275
- definition;
276
- version;
277
- constructor(args) {
278
- super("definition-not-found", args.version !== void 0 ? `Workflow definition ${args.definition} v${args.version} not deployed` : `Workflow definition ${args.definition} has no deployed versions`),
279
- this.name = "DefinitionNotFoundError", this.definition = args.definition, args.version !== void 0 && (this.version = args.version);
280
- }
281
- }
282
-
283
- class DefinitionInUseError extends WorkflowError {
284
- definition;
285
- blockedBy;
286
- constructor(args) {
287
- super("definition-in-use", definitionInUseMessage(args.definition, args.blockedBy)),
288
- this.name = "DefinitionInUseError", this.definition = args.definition, this.blockedBy = args.blockedBy;
289
- }
290
- }
291
-
292
- function definitionInUseMessage(definition, blockedBy) {
293
- if (blockedBy.reason === "non-terminal-instances") {
294
- const head = blockedBy.instanceIds.slice(0, 3).join(", "), preview = blockedBy.instanceIds.length > 3 ? `${head}, …` : head;
295
- return `Cannot delete ${definition}: ${blockedBy.instanceIds.length} non-terminal instance(s) exist (${preview}). Pass cascade to abort them first — instances are aborted in place, never deleted.`;
296
- }
297
- const names = blockedBy.referrers.map(r => `${r.definition} v${r.version}`).join(", ");
298
- return `Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`;
299
- }
300
-
301
- class EffectNotFoundError extends WorkflowError {
302
- instanceId;
303
- effectKey;
304
- settled;
305
- constructor(args) {
306
- super("effect-not-found", effectNotFoundMessage(args)), this.name = "EffectNotFoundError",
307
- this.instanceId = args.instanceId, this.effectKey = args.effectKey, args.settled !== void 0 && (this.settled = args.settled);
308
- }
309
- }
310
-
311
- function effectNotFoundMessage(args) {
312
- const base = `Pending effect "${args.effectKey}" not found on instance ${args.instanceId}`;
313
- if (args.settled === void 0) return base;
314
- const cause = args.settled.detail !== void 0 ? ` (${args.settled.detail})` : "";
315
- return args.settled.status === "cancelled" ? `${base} — it was cancelled at ${args.settled.ranAt}${cause}` : `${base} — it already settled "${args.settled.status}" at ${args.settled.ranAt}${cause}`;
316
- }
317
-
318
528
  const LAKE_ID_SEGMENT_RE = /^[a-z0-9][a-z0-9-]*$/, LAKE_ID_SEGMENT_GLOSS = "ASCII lowercase + digits + dashes, no leading dash, no dots";
319
529
 
320
530
  function validateTag(tag) {
@@ -354,6 +564,7 @@ const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(vali
354
564
  resource: WorkflowResourceSchema
355
565
  }), DefinitionSchema = v__namespace.custom(input => typeof input == "object" && input !== null && typeof input.name == "string", "expected a workflow definition (an object with a string `name`)"), DeploymentSchema = v__namespace.object({
356
566
  name: NonEmptyString$1,
567
+ expectedMinReaderModel: v__namespace.optional(v__namespace.custom(() => !0), void 0),
357
568
  tag: v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty(), v__namespace.check(isValidTag, "invalid tag — lowercase letters, digits and dashes only, no leading dash, no dots")),
358
569
  workflowResource: WorkflowResourceSchema,
359
570
  resourceAliases: v__namespace.optional(v__namespace.pipe(v__namespace.array(ResourceBindingSchema), v__namespace.check(bindings => new Set(bindings.map(binding => binding.name)).size === bindings.length, "duplicate resource handle name — each binding name must be unique within a deployment"))),
@@ -596,6 +807,8 @@ function desugarFieldEntry({entry: entry, path: path, ctx: ctx}) {
596
807
  required: entry.required,
597
808
  initialValue: entry.initialValue,
598
809
  editable: editable,
810
+ options: entry.options,
811
+ validation: entry.validation,
599
812
  types: entry.types,
600
813
  fields: entry.fields,
601
814
  of: entry.of
@@ -738,7 +951,7 @@ function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ct
738
951
  };
739
952
  }
740
953
 
741
- const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "release.ref" ];
954
+ const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref" ];
742
955
 
743
956
  function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
744
957
  if (target === void 0 || target.type === "url") return target;
@@ -759,18 +972,15 @@ function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
759
972
  };
760
973
  }
761
974
 
762
- function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
763
- if ("type" in action) return desugarClaimAction({
764
- action: action,
765
- path: path,
766
- env: env,
767
- ctx: ctx
768
- });
769
- action.roles !== void 0 && action.roles.length === 0 && ctx.issues.push({
975
+ function reportEmptyActionRoles({action: action, path: path, ctx: ctx}) {
976
+ action.roles === void 0 || action.roles.length > 0 || ctx.issues.push({
770
977
  path: [ ...path, "roles" ],
771
978
  message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
772
979
  });
773
- const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarOps({
980
+ }
981
+
982
+ function desugarActionOps(args) {
983
+ const {action: action, path: path, env: env, activityName: activityName, ctx: ctx} = args, ops = desugarOps({
774
984
  ops: action.ops,
775
985
  path: [ ...path, "ops" ],
776
986
  env: env,
@@ -781,9 +991,32 @@ function desugarAction({action: action, path: path, env: env, activityName: acti
781
991
  type: "status.set",
782
992
  activity: activityName,
783
993
  status: action.status
784
- }), {
994
+ }), ops;
995
+ }
996
+
997
+ function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
998
+ if ("type" in action) return desugarClaimAction({
999
+ action: action,
1000
+ path: path,
1001
+ env: env,
1002
+ ctx: ctx
1003
+ });
1004
+ reportEmptyActionRoles({
1005
+ action: action,
1006
+ path: path,
1007
+ ctx: ctx
1008
+ });
1009
+ const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarActionOps({
1010
+ action: action,
1011
+ path: path,
1012
+ env: env,
1013
+ activityName: activityName,
1014
+ ctx: ctx
1015
+ });
1016
+ return {
785
1017
  ...stripUndefined({
786
1018
  name: action.name,
1019
+ semantics: action.semantics,
787
1020
  title: action.title,
788
1021
  description: action.description,
789
1022
  group: normalizeGroup(action.group),
@@ -1170,7 +1403,7 @@ function isTerminalActivityStatus(status) {
1170
1403
  return TERMINAL_ACTIVITY_STATUSES.includes(status);
1171
1404
  }
1172
1405
 
1173
- const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISSIONS = [ "create", "read", "update" ], MUTATION_GUARD_ACTIONS = [ "create", "update", "delete", "publish", "unpublish" ], ACTIVITY_KINDS = [ "user", "service", "script", "manual", "receive" ], EXECUTOR_CLASSIFICATIONS = [ "autonomous", "interactive", "off-system", "hybrid" ], GROUP_KINDS = [ "core", "details" ], DRIVER_KINDS = [ "person", "agent", "service", "engine" ], CONDITION_VARS = [ {
1406
+ const ACTION_SEMANTICS = [ "decision.accept", "decision.decline" ], FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISSIONS = [ "create", "read", "update" ], MUTATION_GUARD_ACTIONS = [ "create", "update", "delete", "publish", "unpublish" ], ACTIVITY_KINDS = [ "user", "service", "script", "manual", "receive" ], EXECUTOR_CLASSIFICATIONS = [ "autonomous", "interactive", "off-system", "hybrid" ], GROUP_KINDS = [ "core", "details" ], DRIVER_KINDS = [ "person", "agent", "service", "engine" ], CONDITION_VARS = [ {
1174
1407
  name: "self",
1175
1408
  binding: "always",
1176
1409
  label: "this workflow instance",
@@ -1179,7 +1412,7 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
1179
1412
  name: "fields",
1180
1413
  binding: "always",
1181
1414
  label: "the workflow's fields",
1182
- description: "Declared field entries rendered by name (`$fields.<name>` is the value, no wrapper). Stage/activity scopes overlay lexically. What a read puts in your hand follows the declared kind: a singular `doc.ref` DEREFERENCES into the hydrated document (lake `_id`/`_type` plus content fields — which may themselves be named `id`/`type`), while `doc.refs` elements and `release.ref` stay REFERENCES — `{id, type[, releaseName]}` with `id` a GDR URI — because identity reads (membership, counting, joins) must stay total without hydrating every target, and targets may live in resources the evaluation cannot fetch from. Conditions evaluate against the in-memory snapshot only, so a dereferencing plural kind would silently hole wherever hydration lagged."
1415
+ description: "Declared field entries rendered by name (`$fields.<name>` is the value, no wrapper). Stage/activity scopes overlay lexically. What a read puts in your hand follows the declared kind: a singular `doc.ref` (or `subject`) DEREFERENCES into the hydrated document (lake `_id`/`_type` plus content fields — which may themselves be named `id`/`type`), while `doc.refs` elements and `release.ref` stay REFERENCES — `{id, type[, releaseName]}` with `id` a GDR URI — because identity reads (membership, counting, joins) must stay total without hydrating every target, and targets may live in resources the evaluation cannot fetch from. Conditions evaluate against the in-memory snapshot only, so a dereferencing plural kind would silently hole wherever hydration lagged."
1183
1416
  }, {
1184
1417
  name: "parent",
1185
1418
  binding: "always",
@@ -1260,18 +1493,26 @@ const FIELD_SCOPES = [ "workflow", "stage", "activity" ], DOCUMENT_VALUE_PERMISS
1260
1493
  binding: "always",
1261
1494
  label: "the spawned subworkflows",
1262
1495
  description: "Every row of the instance's subworkflow registry, faceted by `activity`/`action`/`definition`/`rowKey`/`status` (`'active'|'done'|'aborted'`) with `current` marking the open stage entry's cohort and `stage` the child's current stage. Usable anywhere — transition `when`s, requirements, any stage's gates; the settled gate is `count($subworkflows[activity == <name> && current && status == 'active']) == 0`."
1263
- } ], RESERVED_CONDITION_VARS = CONDITION_VARS.map(v2 => v2.name), FILTER_SCOPE_VARS = CONDITION_VARS.filter(v2 => v2.binding === "always").map(v2 => v2.name), CALLER_BOUND_VARS = CONDITION_VARS.filter(v2 => v2.binding === "caller").map(v2 => v2.name), START_FILTER_VARS = [ {
1496
+ } ], SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR = "subjectHasInFlightInstance", RESERVED_CONDITION_VARS = [ ...CONDITION_VARS.map(v2 => v2.name), SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR ], FILTER_SCOPE_VARS = CONDITION_VARS.filter(v2 => v2.binding === "always").map(v2 => v2.name), CALLER_BOUND_VARS = CONDITION_VARS.filter(v2 => v2.binding === "caller").map(v2 => v2.name), START_FILTER_VARS = [ {
1264
1497
  name: "tag",
1498
+ label: "this engine tag",
1265
1499
  description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
1266
1500
  }, {
1267
1501
  name: "definition",
1502
+ label: "this workflow definition",
1268
1503
  description: "The `name` of the definition under evaluation (its own start block binds it)."
1269
1504
  }, {
1270
1505
  name: "now",
1506
+ label: "the current time",
1271
1507
  description: "The ISO clock reading of the evaluating engine."
1508
+ }, {
1509
+ name: SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR,
1510
+ label: "the subject already has an in-flight workflow",
1511
+ description: "Whether any instance in this engine tag, across all definitions, has the same resource-qualified subject and no `completedAt`. Advisory under concurrent starts."
1272
1512
  } ], START_ALLOWED_VARS = [ ...START_FILTER_VARS, {
1273
1513
  name: "fields",
1274
- 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."
1514
+ label: "the start's input fields",
1515
+ description: "The caller's input entries by name (`initialFields` — at `startInstance`, the values the start would seed; at a pre-flight, the values gathered so far, so a read of a not-yet-supplied entry is GROQ null). Document references bind as GDR envelopes — `$fields.<entry>.id` is the GDR URI, never a string authors assemble — a singular `doc.ref` or `subject` included (nothing hydrates at the gate or the pre-flight). Pathed reads are deploy-checked against these envelope shapes."
1275
1516
  } ], GUARD_PREDICATE_VARS = [ {
1276
1517
  name: "guard",
1277
1518
  description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
@@ -1421,6 +1662,18 @@ function releaseRef({res: res, releaseName: releaseName}) {
1421
1662
  };
1422
1663
  }
1423
1664
 
1665
+ function isSingleDocRefKind(kind) {
1666
+ return kind === "doc.ref" || kind === "subject";
1667
+ }
1668
+
1669
+ function refKindAcceptsTypes(kind) {
1670
+ return isSingleDocRefKind(kind) || kind === "doc.refs";
1671
+ }
1672
+
1673
+ function isSingleDocRefEntry(entry) {
1674
+ return isSingleDocRefKind(entry._type);
1675
+ }
1676
+
1424
1677
  function isTodoListItem(row) {
1425
1678
  if (typeof row != "object" || row === null) return !1;
1426
1679
  const candidate = row, status = candidate.status;
@@ -1471,9 +1724,34 @@ const GdrUriSchema = v__namespace.custom(s => typeof s == "string" && isGdrUri(s
1471
1724
  }), tolerantObject()({
1472
1725
  type: v__namespace.literal("role"),
1473
1726
  role: NonEmptyString
1474
- }) ]), NullableString = v__namespace.union([ v__namespace.null(), v__namespace.string() ]), NullableNumber = v__namespace.union([ v__namespace.null(), v__namespace.number() ]), NullableBoolean = v__namespace.union([ v__namespace.null(), v__namespace.boolean() ]), NullableDateTime = v__namespace.union([ v__namespace.null(), IsoTimestamp ]), NullableDate = v__namespace.union([ v__namespace.null(), v__namespace.pipe(v__namespace.string(), v__namespace.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date")) ]), NullableUrl = NullableString, fieldValueSchemas = {
1727
+ }) ]), NullableString = v__namespace.union([ v__namespace.null(), v__namespace.string() ]), NullableNumber = v__namespace.union([ v__namespace.null(), v__namespace.number() ]), NullableBoolean = v__namespace.union([ v__namespace.null(), v__namespace.boolean() ]), NullableDateTime = v__namespace.union([ v__namespace.null(), IsoTimestamp ]), NullableDate = v__namespace.union([ v__namespace.null(), v__namespace.pipe(v__namespace.string(), v__namespace.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date")) ]), NullableUrl = NullableString, CHOICE_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "url", "date", "datetime", "dateTime" ]);
1728
+
1729
+ function normalizedChoiceKind(kind) {
1730
+ return kind === "dateTime" ? "datetime" : kind;
1731
+ }
1732
+
1733
+ function checkChoiceList(args) {
1734
+ const {entryType: entryType, options: options, validation: validation} = args;
1735
+ if (options === void 0) return;
1736
+ if (!CHOICE_KINDS.has(entryType)) return [ `\`options\` is not valid on "${entryType}" values` ];
1737
+ const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => checkValueAgainst({
1738
+ entryType: kind,
1739
+ value: option.value,
1740
+ validation: validation
1741
+ }, valueSchemas)?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
1742
+ for (const option of options.list) seen.has(option.value) && issues.push(`duplicate option value ${JSON.stringify(option.value)}`),
1743
+ seen.add(option.value);
1744
+ return issues.length === 0 ? void 0 : issues;
1745
+ }
1746
+
1747
+ function choiceValueIssues(options, value) {
1748
+ if (!(options === void 0 || value === null || value === void 0)) return options.list.some(option => Object.is(option.value, value)) ? void 0 : [ `value ${JSON.stringify(value)} is not declared in \`options.list\`; expected one of ${options.list.map(option => JSON.stringify(option.value)).join(", ")}` ];
1749
+ }
1750
+
1751
+ const fieldValueSchemas = {
1475
1752
  "doc.ref": v__namespace.union([ v__namespace.null(), GdrShape ]),
1476
1753
  "doc.refs": v__namespace.array(GdrShape),
1754
+ subject: v__namespace.union([ v__namespace.null(), GdrShape ]),
1477
1755
  "release.ref": v__namespace.union([ v__namespace.null(), ReleaseRefShape ]),
1478
1756
  string: NullableString,
1479
1757
  text: NullableString,
@@ -1491,7 +1769,56 @@ const GdrUriSchema = v__namespace.custom(s => typeof s == "string" && isGdrUri(s
1491
1769
  };
1492
1770
 
1493
1771
  function shapeValueSchema(shape, leaf) {
1494
- return shape.type === "object" ? objectSchema(shape.fields ?? [], leaf) : shape.type === "array" ? v__namespace.array(objectSchema(shape.of ?? [], leaf)) : leaf[shape.type] ?? v__namespace.any();
1772
+ if (shape.type === "object") return objectSchema(shape.fields ?? [], leaf);
1773
+ if (shape.type === "array") return v__namespace.array(objectSchema(shape.of ?? [], leaf));
1774
+ const schema = leaf[shape.type] ?? v__namespace.any();
1775
+ return constrainedScalarSchema({
1776
+ schema: schema,
1777
+ entryType: shape.type,
1778
+ ...shape
1779
+ });
1780
+ }
1781
+
1782
+ function scalarMeasurement(entryType, value) {
1783
+ if (entryType === "number" && typeof value == "number") return value;
1784
+ if ((entryType === "string" || entryType === "text") && typeof value == "string") return value.length;
1785
+ }
1786
+
1787
+ function scalarBoundIssue(args) {
1788
+ const {entryType: entryType, measured: measured, bound: bound, limit: limit} = args;
1789
+ return bound === void 0 || limit === "min" && measured >= bound || limit === "max" && measured <= bound ? void 0 : `${entryType === "number" ? "" : "length "}must be ${limit === "min" ? "greater than or equal to" : "less than or equal to"} ${bound}`;
1790
+ }
1791
+
1792
+ function scalarValidationIssues(args) {
1793
+ const {entryType: entryType, validation: validation, value: value} = args;
1794
+ if (validation === void 0 || value === null || value === void 0) return;
1795
+ const measured = scalarMeasurement(entryType, value);
1796
+ if (measured === void 0) return;
1797
+ const issues = [ scalarBoundIssue({
1798
+ entryType: entryType,
1799
+ measured: measured,
1800
+ bound: validation.min,
1801
+ limit: "min"
1802
+ }), scalarBoundIssue({
1803
+ entryType: entryType,
1804
+ measured: measured,
1805
+ bound: validation.max,
1806
+ limit: "max"
1807
+ }) ].filter(issue => issue !== void 0);
1808
+ return issues.length === 0 ? void 0 : issues;
1809
+ }
1810
+
1811
+ function constrainedScalarSchema(args) {
1812
+ const {schema: schema, entryType: entryType, options: options, validation: validation} = args;
1813
+ return options === void 0 && validation === void 0 ? schema : v__namespace.pipe(schema, v__namespace.check(value => choiceValueIssues(options, value) === void 0 && scalarValidationIssues({
1814
+ entryType: entryType,
1815
+ validation: validation,
1816
+ value: value
1817
+ }) === void 0, issue => [ ...choiceValueIssues(options, issue.input) ?? [], ...scalarValidationIssues({
1818
+ entryType: entryType,
1819
+ validation: validation,
1820
+ value: issue.input
1821
+ }) ?? [] ].join("; ")));
1495
1822
  }
1496
1823
 
1497
1824
  function objectSchema(fields, leaf) {
@@ -1514,7 +1841,7 @@ function appendItemSchema(entryType, shape) {
1514
1841
  function rejectedRefTypes(args) {
1515
1842
  const {entryType: entryType, types: types, value: value} = args;
1516
1843
  if (types === void 0 || value === null || value === void 0) return [];
1517
- if (entryType !== "doc.ref" && entryType !== "doc.refs") return [];
1844
+ if (!refKindAcceptsTypes(entryType)) return [];
1518
1845
  let items = [ value ];
1519
1846
  return entryType === "doc.refs" && (items = Array.isArray(value) ? value : []),
1520
1847
  [ ...new Set(items.map(gdrTypeOf).filter(t => t !== void 0 && !types.includes(t))) ];
@@ -1549,6 +1876,10 @@ function checkValueAgainst(args, leaf) {
1549
1876
  entryType: args.entryType,
1550
1877
  types: args.types,
1551
1878
  value: args.value
1879
+ }) ?? choiceValueIssues(args.options, args.value) ?? scalarValidationIssues({
1880
+ entryType: args.entryType,
1881
+ validation: args.validation,
1882
+ value: args.value
1552
1883
  }) : formatIssues(result.issues);
1553
1884
  }
1554
1885
 
@@ -1579,6 +1910,7 @@ const AuthoringRefId = v__namespace.pipe(v__namespace.string(), v__namespace.che
1579
1910
  ...valueSchemas,
1580
1911
  "doc.ref": v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
1581
1912
  "doc.refs": v__namespace.array(AuthoringGdrShape),
1913
+ subject: v__namespace.union([ v__namespace.null(), AuthoringGdrShape ]),
1582
1914
  "release.ref": v__namespace.union([ v__namespace.null(), v__namespace.looseObject({
1583
1915
  id: AuthoringRefId,
1584
1916
  type: v__namespace.literal("system.release"),
@@ -1618,10 +1950,10 @@ function validateFieldAppendItem(args) {
1618
1950
  });
1619
1951
  }
1620
1952
 
1621
- function formatIssues(issues) {
1953
+ function formatIssues(issues, formatMessage = issue => issue.message) {
1622
1954
  return issues.map(i => {
1623
1955
  const keys = i.path?.map(p => p.key) ?? [];
1624
- return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
1956
+ return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(i)}`;
1625
1957
  });
1626
1958
  }
1627
1959
 
@@ -1747,7 +2079,15 @@ function groupMembershipNames(group) {
1747
2079
  return group === void 0 ? [] : typeof group == "string" ? [ group ] : [ ...group ];
1748
2080
  }
1749
2081
 
1750
- const FIELD_VALUE_KINDS = [ "doc.ref", "doc.refs", "release.ref", "string", "text", "number", "boolean", "date", "datetime", "url", "actor", "assignee", "assignees", "object", "array" ], FieldValueKindSchema = picklist(FIELD_VALUE_KINDS), FieldKindSchema = picklist(FIELD_VALUE_KINDS), FieldEntryName = groqIdentifier("`$fields.<name>`");
2082
+ const FIELD_VALUE_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref", "string", "text", "number", "boolean", "date", "datetime", "url", "actor", "assignee", "assignees", "object", "array" ], FieldValueKindSchema = picklist(FIELD_VALUE_KINDS), FieldKindSchema = picklist(FIELD_VALUE_KINDS), FieldEntryName = groqIdentifier("`$fields.<name>`"), FiniteNumber = v__namespace.pipe(v__namespace.number(), v__namespace.finite("must be finite")), ScalarValidationSchema = v__namespace.pipe(v__namespace.strictObject({
2083
+ min: v__namespace.optional(FiniteNumber),
2084
+ max: v__namespace.optional(FiniteNumber)
2085
+ }), v__namespace.check(validation => validation.min !== void 0 || validation.max !== void 0, "declare at least one bound, or omit `validation`"), v__namespace.check(validation => validation.min === void 0 || validation.max === void 0 || validation.min <= validation.max, "`min` must be less than or equal to `max`")), ChoiceOptionsSchema = v__namespace.strictObject({
2086
+ list: v__namespace.pipe(v__namespace.array(v__namespace.strictObject({
2087
+ title: NonEmpty,
2088
+ value: v__namespace.union([ v__namespace.string(), v__namespace.number() ])
2089
+ })), v__namespace.minLength(1, "declare at least one choice, or omit `options`"))
2090
+ });
1751
2091
 
1752
2092
  function asShape(input) {
1753
2093
  return typeof input == "object" && input !== null ? input : {};
@@ -1781,14 +2121,16 @@ function compositeChecked(entries) {
1781
2121
  return v__namespace.pipe(v__namespace.strictObject(entries), v__namespace.check(input => compositeShapeOk(input), issue => compositeShapeMessage(issue.input)), v__namespace.check(input => duplicateSubfieldName(input) === void 0, issue => `duplicate sub-field name "${duplicateSubfieldName(issue.input)}" — sub-field names must be unique within \`fields\` / \`of\``));
1782
2122
  }
1783
2123
 
1784
- const FieldShapeSchema = v__namespace.lazy(() => compositeChecked({
2124
+ const FieldShapeSchema = v__namespace.lazy(() => v__namespace.pipe(compositeChecked({
1785
2125
  type: FieldValueKindSchema,
1786
2126
  name: FieldEntryName,
1787
2127
  title: v__namespace.optional(v__namespace.string()),
1788
2128
  description: v__namespace.optional(v__namespace.string()),
2129
+ options: v__namespace.optional(ChoiceOptionsSchema),
2130
+ validation: v__namespace.optional(ScalarValidationSchema),
1789
2131
  fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
1790
2132
  of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
1791
- })), StoredEditableSchema = v__namespace.union([ v__namespace.literal(!0), NonEmpty ]), AuthoringEditableSchema = v__namespace.union([ v__namespace.literal(!0), v__namespace.array(NonEmpty), NonEmpty ]);
2133
+ }), choiceOptionsCheck(), scalarValidationCheck())), StoredEditableSchema = v__namespace.union([ v__namespace.literal(!0), NonEmpty ]), AuthoringEditableSchema = v__namespace.union([ v__namespace.literal(!0), v__namespace.array(NonEmpty), NonEmpty ]);
1792
2134
 
1793
2135
  function fieldBase(editable, group) {
1794
2136
  return {
@@ -1806,6 +2148,8 @@ function fieldEntryFields(editable, group) {
1806
2148
  return {
1807
2149
  type: FieldKindSchema,
1808
2150
  ...fieldBase(editable, group),
2151
+ options: v__namespace.optional(ChoiceOptionsSchema),
2152
+ validation: v__namespace.optional(ScalarValidationSchema),
1809
2153
  types: v__namespace.optional(v__namespace.pipe(v__namespace.array(NonEmpty), v__namespace.minLength(1, "declare at least one accepted type, or omit `types` to accept any"))),
1810
2154
  fields: v__namespace.optional(v__namespace.array(FieldShapeSchema)),
1811
2155
  of: v__namespace.optional(v__namespace.array(FieldShapeSchema))
@@ -1818,7 +2162,9 @@ function literalSeedIssues(entry) {
1818
2162
  value: entry.initialValue.value,
1819
2163
  types: entry.types,
1820
2164
  fields: entry.fields,
1821
- of: entry.of
2165
+ of: entry.of,
2166
+ options: entry.options,
2167
+ validation: entry.validation
1822
2168
  });
1823
2169
  }
1824
2170
 
@@ -1827,10 +2173,37 @@ function literalSeedCheck() {
1827
2173
  }
1828
2174
 
1829
2175
  function refTypesCheck() {
1830
- return v__namespace.check(entry => entry.types === void 0 || entry.type === "doc.ref" || entry.type === "doc.refs", issue => `\`types\` is only valid on \`doc.ref\` / \`doc.refs\` entries, not "${issue.input.type}"`);
2176
+ return v__namespace.check(entry => entry.types === void 0 || refKindAcceptsTypes(entry.type), issue => `\`types\` is only valid on \`doc.ref\` / \`doc.refs\` / \`subject\` entries, not "${issue.input.type}"`);
1831
2177
  }
1832
2178
 
1833
- const FieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields(StoredEditableSchema, StoredGroupMembershipSchema)), refTypesCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields(AuthoringEditableSchema, AuthoringGroupMembershipSchema)), refTypesCheck(), literalSeedCheck())), ClaimFieldSchema = pinned()(v__namespace.strictObject({
2179
+ function choiceOptionsCheck() {
2180
+ return v__namespace.check(entry => checkChoiceList({
2181
+ entryType: entry.type,
2182
+ options: entry.options,
2183
+ validation: entry.validation
2184
+ }) === void 0, issue => (checkChoiceList({
2185
+ entryType: issue.input.type,
2186
+ options: issue.input.options,
2187
+ validation: issue.input.validation
2188
+ }) ?? []).join("; "));
2189
+ }
2190
+
2191
+ const SCALAR_VALIDATION_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number" ]);
2192
+
2193
+ function scalarValidationCheck() {
2194
+ return v__namespace.check(entry => scalarValidationDeclarationIssues(entry) === void 0, issue => (scalarValidationDeclarationIssues(issue.input) ?? []).join("; "));
2195
+ }
2196
+
2197
+ function scalarValidationDeclarationIssues(entry) {
2198
+ const {type: type, validation: validation} = entry;
2199
+ if (validation === void 0) return;
2200
+ if (!SCALAR_VALIDATION_KINDS.has(type)) return [ `\`validation\` is only valid on \`string\` / \`text\` / \`number\` values, not "${type}"` ];
2201
+ if (type === "number") return;
2202
+ const issues = Object.entries(validation).flatMap(([bound, value]) => Number.isInteger(value) && value >= 0 ? [] : [ `\`validation.${bound}\` must be a non-negative integer for ${type} length` ]);
2203
+ return issues.length === 0 ? void 0 : issues;
2204
+ }
2205
+
2206
+ const FieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields(StoredEditableSchema, StoredGroupMembershipSchema)), refTypesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields(AuthoringEditableSchema, AuthoringGroupMembershipSchema)), refTypesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), ClaimFieldSchema = pinned()(v__namespace.strictObject({
1834
2207
  type: v__namespace.literal("claim"),
1835
2208
  name: FieldEntryName,
1836
2209
  title: v__namespace.optional(v__namespace.string()),
@@ -1861,17 +2234,25 @@ const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("
1861
2234
  with: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
1862
2235
  context: v__namespace.optional(v__namespace.record(NonEmpty, ConditionSchema)),
1863
2236
  onExit: v__namespace.optional(picklist([ "detach", "abort" ]))
1864
- }), ActionParamSchema = v__namespace.strictObject({
2237
+ }), ActionParamSchema = v__namespace.pipe(v__namespace.strictObject({
1865
2238
  type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
1866
2239
  name: NonEmpty,
1867
2240
  title: v__namespace.optional(v__namespace.string()),
1868
2241
  description: v__namespace.optional(v__namespace.string()),
1869
- required: v__namespace.optional(v__namespace.boolean())
1870
- });
2242
+ required: v__namespace.optional(v__namespace.boolean()),
2243
+ options: v__namespace.optional(ChoiceOptionsSchema),
2244
+ validation: v__namespace.optional(ScalarValidationSchema)
2245
+ }), choiceOptionsCheck(), scalarValidationCheck());
2246
+
2247
+ function hasUniqueSemanticNamespaces(semantics) {
2248
+ const namespaces = semantics.map(semantic => semantic.split(".", 1)[0]);
2249
+ return new Set(namespaces).size === namespaces.length;
2250
+ }
1871
2251
 
1872
2252
  function actionFields(op, group) {
1873
2253
  return {
1874
2254
  name: NonEmpty,
2255
+ semantics: v__namespace.optional(v__namespace.pipe(v__namespace.array(picklist(ACTION_SEMANTICS)), v__namespace.minLength(1, "declare at least one semantic, or omit `semantics`"), v__namespace.check(hasUniqueSemanticNamespaces, "declare at most one semantic from each namespace"))),
1875
2256
  title: v__namespace.optional(v__namespace.string()),
1876
2257
  description: v__namespace.optional(v__namespace.string()),
1877
2258
  group: v__namespace.optional(group),
@@ -2060,7 +2441,7 @@ function startKindOf(definition) {
2060
2441
  }
2061
2442
 
2062
2443
  function isSubjectEntry(entry) {
2063
- return entry.required === !0 && (entry.type === "doc.ref" || entry.type === "doc.refs");
2444
+ return entry.type === "subject";
2064
2445
  }
2065
2446
 
2066
2447
  function isInputSourced(entry) {
@@ -2562,6 +2943,52 @@ function checkAssigneesEntries(def, issues) {
2562
2943
  });
2563
2944
  }
2564
2945
 
2946
+ function nestedSubjectPaths(shapes, base) {
2947
+ return (shapes ?? []).flatMap((shape, i) => isSubjectEntry(shape) ? [ [ ...base, i, "type" ] ] : [ ...nestedSubjectPaths(shape.fields, [ ...base, i, "fields" ]), ...nestedSubjectPaths(shape.of, [ ...base, i, "of" ]) ]);
2948
+ }
2949
+
2950
+ function checkSubjectEffectOutputs(def, issues) {
2951
+ for (const [i, stage] of def.stages.entries()) for (const [j, activity] of (stage.activities ?? []).entries()) for (const [a, action] of (activity.actions ?? []).entries()) pushSubjectOutputIssues({
2952
+ action: action,
2953
+ path: [ "stages", i, "activities", j, "actions", a ],
2954
+ issues: issues
2955
+ });
2956
+ }
2957
+
2958
+ function pushSubjectOutputIssues(args) {
2959
+ const {action: action, path: path, issues: issues} = args;
2960
+ for (const [e, effect] of (action.effects ?? []).entries()) {
2961
+ const base = [ ...path, "effects", e, "outputs" ];
2962
+ for (const nestedPath of nestedSubjectPaths(effect.outputs, base)) issues.push({
2963
+ path: nestedPath,
2964
+ message: `effect "${effect.name}" declares a \`subject\` output shape — no subject reader ever looks at effect outputs. Declare \`doc.ref\` for a document-valued output`
2965
+ });
2966
+ }
2967
+ }
2968
+
2969
+ function checkSubjectEntries(def, issues) {
2970
+ for (const {entries: entries, scope: scope, path: path, label: label} of fieldScopes(def)) {
2971
+ const subjects = (entries ?? []).flatMap((entry, n) => isSubjectEntry(entry) ? [ {
2972
+ entry: entry,
2973
+ n: n
2974
+ } ] : []);
2975
+ if (scope !== "workflow") for (const {entry: entry, n: n} of subjects) issues.push({
2976
+ path: [ ...path, n, "type" ],
2977
+ message: `${label} entry "${entry.name}" is a \`subject\` — the subject names the document the WORKFLOW is about, so it is valid only on a workflow-scope entry. Declare \`doc.ref\` for a plain reference here`
2978
+ }); else for (const {n: n} of subjects.slice(1)) issues.push({
2979
+ path: [ ...path, n ],
2980
+ message: "at most one subject-kind field entry per workflow — the runtime identifies THE subject by kind, and a second one makes it ambiguous"
2981
+ });
2982
+ for (const [n, entry] of (entries ?? []).entries()) {
2983
+ const nested = [ ...nestedSubjectPaths(entry.fields, [ ...path, n, "fields" ]), ...nestedSubjectPaths(entry.of, [ ...path, n, "of" ]) ];
2984
+ for (const nestedPath of nested) issues.push({
2985
+ path: nestedPath,
2986
+ message: `${label} entry "${entry.name}" declares a \`subject\` sub-field — a nested subject can never be read as the workflow's subject. Declare \`doc.ref\` for a nested reference`
2987
+ });
2988
+ }
2989
+ }
2990
+ }
2991
+
2565
2992
  function checkActivityTerminalPaths(def, issues) {
2566
2993
  for (const [i, stage] of def.stages.entries()) {
2567
2994
  const resolvable = terminallyResolvableActivities(stage);
@@ -2612,12 +3039,30 @@ function checkStart(def, issues) {
2612
3039
  if (def.start !== void 0 && (def.lifecycle === "child" && issues.push({
2613
3040
  path: [ "start" ],
2614
3041
  message: "a spawn-only (lifecycle 'child') definition declares `start` — children are instantiated by a parent's `spawn`, never started standalone, so the block would never apply. Remove `start`, or drop `lifecycle: 'child'`"
2615
- }), 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({
3042
+ }), checkStartFilterReads(def, issues), checkStartAllowedReads(def, issues), checkStartSubjectVariable(def, issues),
3043
+ def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
2616
3044
  path: [ "fields", n, "required" ],
2617
- message: `start.kind 'autonomous' means runs are initiated by a system reacting to a document, so every required input must be derivable from that triggering document — required entry "${entry.name}" (kind "${entry.type}") is not a document reference. Make it optional, seed it another way (query/literal), or declare it doc.ref / doc.refs`
3045
+ message: `start.kind 'autonomous' means runs are initiated by a system reacting to a document, so every required input must be derivable from that triggering document — required entry "${entry.name}" (kind "${entry.type}") is not the workflow's subject. Make it optional, seed it another way (query/literal), or declare it the \`subject\` entry (the document the run is about)`
2618
3046
  });
2619
3047
  }
2620
3048
 
3049
+ function checkStartSubjectVariable(def, issues) {
3050
+ const subject = (def.fields ?? []).find(isSubjectEntry);
3051
+ for (const [key, condition] of [ [ "filter", def.start?.filter ], [ "allowed", def.start?.allowed ] ]) if (condition !== void 0 && conditionParameterNames(condition).has(SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR)) {
3052
+ if (subject === void 0) {
3053
+ issues.push({
3054
+ path: [ "start", key ],
3055
+ message: `start.${key} reads $${SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR}, but the definition declares no \`subject\` entry — the engine has no prospective subject identity to match. Declare a \`subject\` entry (the document the workflow is about), or remove the variable`
3056
+ });
3057
+ continue;
3058
+ }
3059
+ key === "allowed" && !isInputSourced(subject) && issues.push({
3060
+ path: [ "start", key ],
3061
+ message: `start.allowed reads $${SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR}, but the definition's subject entry "${subject.name}" is not \`input\`-sourced — the start gate cannot bind its prospective subject. Make the subject input-sourced, or remove the variable`
3062
+ });
3063
+ }
3064
+ }
3065
+
2621
3066
  function checkStartFilterReads(def, issues) {
2622
3067
  const filter = def.start?.filter;
2623
3068
  filter !== void 0 && (conditionParameterNames(filter).has("fields") && issues.push({
@@ -2625,7 +3070,7 @@ function checkStartFilterReads(def, issues) {
2625
3070
  message: "start.filter reads $fields, but the filter is browse-time-pure — a start surface evaluates it per document, before any inputs exist, so $fields cannot be bound. Move the input-dependent rule to start.allowed, the start-time permission predicate that binds $fields and is enforced by startInstance"
2626
3071
  }), readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
2627
3072
  path: [ "start", "filter" ],
2628
- 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"
3073
+ message: "start.filter reads the candidate document (its root), but the definition declares no `subject` entry — a read surface would never have a document to bind as root, so every root read is GROQ null and the filter silently misevaluates. Declare a `subject` entry (the document the workflow is about), or gate on the dataset instead"
2629
3074
  }));
2630
3075
  }
2631
3076
 
@@ -2942,7 +3387,7 @@ function checkFieldReadOpValues(def, issues) {
2942
3387
  ops: site.ops,
2943
3388
  path: site.path,
2944
3389
  label: site.label,
2945
- activityEntries: entryMap(site.activity.fields)
3390
+ activityEntries: site.activity.fields ?? []
2946
3391
  });
2947
3392
  }
2948
3393
 
@@ -2964,6 +3409,9 @@ function fieldReadsIn(value, path) {
2964
3409
 
2965
3410
  function checkOpFieldRead({read: read, where: where, path: path, workflowEntries: workflowEntries, stageEntries: stageEntries, activityEntries: activityEntries, issues: issues}) {
2966
3411
  const hosts = [ {
3412
+ scope: "activity",
3413
+ entries: activityEntries
3414
+ }, {
2967
3415
  scope: "stage",
2968
3416
  entries: stageEntries
2969
3417
  }, {
@@ -2976,8 +3424,7 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
2976
3424
  message: opFieldReadMissMessage({
2977
3425
  read: read,
2978
3426
  where: where,
2979
- hosts: hosts,
2980
- activityEntries: activityEntries
3427
+ hosts: hosts
2981
3428
  })
2982
3429
  });
2983
3430
  return;
@@ -2991,9 +3438,9 @@ function checkOpFieldRead({read: read, where: where, path: path, workflowEntries
2991
3438
  });
2992
3439
  }
2993
3440
 
2994
- function opFieldReadMissMessage({read: read, where: where, hosts: hosts, activityEntries: activityEntries}) {
2995
- 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}`));
2996
- return `${where} reads field "${read.field}", which is not declared at ${searched} — the read resolves to undefined at op time, so the write silently lands empty.${activityHint} Known: ${known.join(", ") || "(none)"}`;
3441
+ function opFieldReadMissMessage({read: read, where: where, hosts: hosts}) {
3442
+ const searched = read.scope === void 0 ? "activity, stage, or workflow scope" : `${read.scope} scope`, known = hosts.flatMap(host => host.entries.map(entry => `${host.scope}:${entry.name}`));
3443
+ return `${where} reads field "${read.field}", which is not declared at ${searched} — the read resolves to undefined at op time, so the write silently lands empty. Known: ${known.join(", ") || "(none)"}`;
2997
3444
  }
2998
3445
 
2999
3446
  function checkUpdateWhereOps(def, issues) {
@@ -3156,6 +3603,7 @@ const SCALAR = {
3156
3603
  kind: "list",
3157
3604
  item: GDR_VALUE
3158
3605
  }),
3606
+ subject: () => DOC_CONTENT,
3159
3607
  "release.ref": () => RELEASE_VALUE,
3160
3608
  string: () => SCALAR,
3161
3609
  text: () => SCALAR,
@@ -3180,7 +3628,8 @@ const SCALAR = {
3180
3628
  })
3181
3629
  }, START_ALLOWED_VALUE_NODES = {
3182
3630
  ...VALUE_NODES,
3183
- "doc.ref": () => GDR_VALUE
3631
+ "doc.ref": () => GDR_VALUE,
3632
+ subject: () => GDR_VALUE
3184
3633
  };
3185
3634
 
3186
3635
  function valueNodeFor(shape, nodes) {
@@ -3252,11 +3701,14 @@ function checkWorkflowInvariants(def) {
3252
3701
  checkRequiredField(def, issues), checkStart(def, issues), checkPredicates(def, issues),
3253
3702
  checkUnboundConditionVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
3254
3703
  checkFieldReadOpValues(def, issues), checkUpdateWhereOps(def, issues), checkGuardFieldReads(def, issues),
3255
- checkAssigneesEntries(def, issues), checkActivityTerminalPaths(def, issues), checkTerminalStageActivities(def, issues),
3704
+ checkAssigneesEntries(def, issues), checkSubjectEntries(def, issues), checkSubjectEffectOutputs(def, issues),
3705
+ checkActivityTerminalPaths(def, issues), checkTerminalStageActivities(def, issues),
3256
3706
  checkTriggeredActionParams(def, issues), checkStoredRolesPlacement(def, issues),
3257
3707
  checkGroups(def, issues), issues;
3258
3708
  }
3259
3709
 
3710
+ exports.ACTION_SEMANTICS = ACTION_SEMANTICS;
3711
+
3260
3712
  exports.ACTIVITY_KINDS = ACTIVITY_KINDS;
3261
3713
 
3262
3714
  exports.ACTIVITY_STATUSES = ACTIVITY_STATUSES;
@@ -3287,6 +3739,12 @@ exports.CONDITION_VARS = CONDITION_VARS;
3287
3739
 
3288
3740
  exports.ContractViolationError = ContractViolationError;
3289
3741
 
3742
+ exports.DATA_MODEL_CHANGES = DATA_MODEL_CHANGES;
3743
+
3744
+ exports.DATA_MODEL_MIN_READER = DATA_MODEL_MIN_READER;
3745
+
3746
+ exports.DATA_MODEL_VERSION = DATA_MODEL_VERSION;
3747
+
3290
3748
  exports.DEFAULT_TRANSITION_WHEN = DEFAULT_TRANSITION_WHEN;
3291
3749
 
3292
3750
  exports.DOCUMENT_VALUE_PERMISSIONS = DOCUMENT_VALUE_PERMISSIONS;
@@ -3329,22 +3787,34 @@ exports.IsoTimestamp = IsoTimestamp;
3329
3787
 
3330
3788
  exports.MUTATION_GUARD_ACTIONS = MUTATION_GUARD_ACTIONS;
3331
3789
 
3790
+ exports.ModelVersionAheadError = ModelVersionAheadError;
3791
+
3332
3792
  exports.NonEmptyString = NonEmptyString;
3333
3793
 
3334
3794
  exports.PersistedDocShapeError = PersistedDocShapeError;
3335
3795
 
3796
+ exports.READER_MODEL_ROLLOUT_URL = READER_MODEL_ROLLOUT_URL;
3797
+
3336
3798
  exports.RESERVED_CONDITION_VARS = RESERVED_CONDITION_VARS;
3337
3799
 
3338
3800
  exports.RESOURCE_ALIAS_NAME_SOURCE = RESOURCE_ALIAS_NAME_SOURCE;
3339
3801
 
3802
+ exports.ReaderModelAcknowledgementError = ReaderModelAcknowledgementError;
3803
+
3340
3804
  exports.START_ALLOWED_VARS = START_ALLOWED_VARS;
3341
3805
 
3342
3806
  exports.START_FILTER_VARS = START_FILTER_VARS;
3343
3807
 
3808
+ exports.SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR = SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR;
3809
+
3810
+ exports.SpawnContractsInvalidError = SpawnContractsInvalidError;
3811
+
3344
3812
  exports.StoredFieldOpSchema = StoredFieldOpSchema;
3345
3813
 
3346
3814
  exports.WORKFLOW_DEFINITION_TYPE = WORKFLOW_DEFINITION_TYPE;
3347
3815
 
3816
+ exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;
3817
+
3348
3818
  exports.WorkflowConfigSchema = WorkflowConfigSchema;
3349
3819
 
3350
3820
  exports.WorkflowError = WorkflowError;
@@ -3353,10 +3823,16 @@ exports.actorFulfillsRole = actorFulfillsRole;
3353
3823
 
3354
3824
  exports.andConditions = andConditions;
3355
3825
 
3826
+ exports.assertReadableModel = assertReadableModel;
3827
+
3828
+ exports.assertReaderModelAcknowledgement = assertReaderModelAcknowledgement;
3829
+
3356
3830
  exports.checkFieldValue = checkFieldValue;
3357
3831
 
3358
3832
  exports.checkWorkflowInvariants = checkWorkflowInvariants;
3359
3833
 
3834
+ exports.choiceValueIssues = choiceValueIssues;
3835
+
3360
3836
  exports.clientConfigFromResource = clientConfigFromResource;
3361
3837
 
3362
3838
  exports.conditionEffectReads = conditionEffectReads;
@@ -3389,6 +3865,8 @@ exports.evaluatePredicates = evaluatePredicates;
3389
3865
 
3390
3866
  exports.extractDocumentId = extractDocumentId;
3391
3867
 
3868
+ exports.fieldTreeShape = fieldTreeShape;
3869
+
3392
3870
  exports.fieldValueSchemas = fieldValueSchemas;
3393
3871
 
3394
3872
  exports.formatIssuePath = formatIssuePath;
@@ -3425,6 +3903,10 @@ exports.isNotesEntry = isNotesEntry;
3425
3903
 
3426
3904
  exports.isParseableInstant = isParseableInstant;
3427
3905
 
3906
+ exports.isSingleDocRefEntry = isSingleDocRefEntry;
3907
+
3908
+ exports.isSingleDocRefKind = isSingleDocRefKind;
3909
+
3428
3910
  exports.isStartableDefinition = isStartableDefinition;
3429
3911
 
3430
3912
  exports.isSubjectEntry = isSubjectEntry;
@@ -3437,8 +3919,20 @@ exports.isTodoListItem = isTodoListItem;
3437
3919
 
3438
3920
  exports.isUnevaluable = isUnevaluable;
3439
3921
 
3922
+ exports.isUnprimed = isUnprimed;
3923
+
3440
3924
  exports.labelFor = labelFor;
3441
3925
 
3926
+ exports.minReaderModelOf = minReaderModelOf;
3927
+
3928
+ exports.modelStampFor = modelStampFor;
3929
+
3930
+ exports.modelVersionOf = modelVersionOf;
3931
+
3932
+ exports.parentRef = parentRef;
3933
+
3934
+ exports.parseDefinitionSnapshot = parseDefinitionSnapshot;
3935
+
3442
3936
  exports.parseGdr = parseGdr;
3443
3937
 
3444
3938
  exports.parseOrThrow = parseOrThrow;
@@ -3457,6 +3951,8 @@ exports.refDashboard = refDashboard;
3457
3951
 
3458
3952
  exports.refDataset = refDataset;
3459
3953
 
3954
+ exports.refKindAcceptsTypes = refKindAcceptsTypes;
3955
+
3460
3956
  exports.refMediaLibrary = refMediaLibrary;
3461
3957
 
3462
3958
  exports.refTypeIssues = refTypeIssues;
@@ -3467,6 +3963,10 @@ exports.releaseDocId = releaseDocId;
3467
3963
 
3468
3964
  exports.releaseRef = releaseRef;
3469
3965
 
3966
+ exports.requiredModelFeatures = requiredModelFeatures;
3967
+
3968
+ exports.requiredReaderModel = requiredReaderModel;
3969
+
3470
3970
  exports.resourceAliasesToMap = resourceAliasesToMap;
3471
3971
 
3472
3972
  exports.resourceFromGdrUri = resourceFromGdrUri;
@@ -3481,6 +3981,8 @@ exports.runGroq = runGroq;
3481
3981
 
3482
3982
  exports.sameResource = sameResource;
3483
3983
 
3984
+ exports.scalarValidationIssues = scalarValidationIssues;
3985
+
3484
3986
  exports.schemaTreeShape = schemaTreeShape;
3485
3987
 
3486
3988
  exports.selfGdr = selfGdr;
@@ -3489,6 +3991,8 @@ exports.startKindOf = startKindOf;
3489
3991
 
3490
3992
  exports.tagScopeFilter = tagScopeFilter;
3491
3993
 
3994
+ exports.terminalState = terminalState;
3995
+
3492
3996
  exports.toBareId = toBareId;
3493
3997
 
3494
3998
  exports.toPhysicalGdr = toPhysicalGdr;