@sanity/workflow-engine 0.12.0 → 0.14.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.
@@ -1,9 +1,323 @@
1
1
  import * as v from "valibot";
2
+ const KNOWN_SCHEMES = /* @__PURE__ */ new Set(["dataset", "canvas", "media-library", "dashboard"]);
3
+ function parseGdr(uri) {
4
+ const colon = uri.indexOf(":");
5
+ if (colon < 0)
6
+ throw new Error(
7
+ `Invalid GDR "${uri}": must be a URI of form "<scheme>:<...id-parts>". Known schemes: dataset, canvas, media-library, dashboard.`
8
+ );
9
+ const scheme = uri.slice(0, colon), rest = uri.slice(colon + 1);
10
+ if (!KNOWN_SCHEMES.has(scheme))
11
+ throw new Error(
12
+ `Invalid GDR "${uri}": unknown scheme "${scheme}". Known: dataset, canvas, media-library, dashboard.`
13
+ );
14
+ const parts = rest.split(":");
15
+ if (parts.some((part) => part.length === 0))
16
+ throw new Error(
17
+ `Invalid GDR "${uri}": id parts must be non-empty (no leading, trailing, or doubled ":").`
18
+ );
19
+ if (scheme === "dataset") {
20
+ if (parts.length !== 3)
21
+ throw new Error(
22
+ `Invalid GDR "${uri}": dataset scheme requires <projectId>:<dataset>:<documentId> (3 parts after scheme); got ${parts.length}.`
23
+ );
24
+ return {
25
+ scheme: "dataset",
26
+ projectId: parts[0],
27
+ dataset: parts[1],
28
+ documentId: parts[2]
29
+ };
30
+ }
31
+ if (parts.length !== 2)
32
+ throw new Error(
33
+ `Invalid GDR "${uri}": ${scheme} scheme requires <resourceId>:<documentId> (2 parts after scheme); got ${parts.length}.`
34
+ );
35
+ return {
36
+ scheme,
37
+ resourceId: parts[0],
38
+ documentId: parts[1]
39
+ };
40
+ }
41
+ function tryParseGdr(uri) {
42
+ try {
43
+ return parseGdr(uri);
44
+ } catch {
45
+ return;
46
+ }
47
+ }
48
+ function gdrUri(parts) {
49
+ return parts.scheme === "dataset" ? `dataset:${parts.projectId}:${parts.dataset}:${parts.documentId}` : `${parts.scheme}:${parts.resourceId}:${parts.documentId}`;
50
+ }
51
+ function extractDocumentId(gdrUriString) {
52
+ return parseGdr(gdrUriString).documentId;
53
+ }
54
+ const RESOURCE_ALIAS_NAME_SOURCE = "[a-z0-9][a-z0-9-]*", RESOURCE_ALIAS_NAME_RE = new RegExp(`^${RESOURCE_ALIAS_NAME_SOURCE}$`);
55
+ function validateResourceAliasName(name) {
56
+ if (!RESOURCE_ALIAS_NAME_RE.test(name))
57
+ throw new Error(
58
+ `Invalid resource alias name "${name}": expected lowercase letters, digits, and dashes (no leading dash).`
59
+ );
60
+ }
61
+ function toPhysicalGdr(id, home) {
62
+ return isGdrUri(id) ? id : gdrFromResource(home, id);
63
+ }
64
+ function refDataset({
65
+ projectId,
66
+ dataset,
67
+ documentId,
68
+ type
69
+ }) {
70
+ return { id: gdrUri({ scheme: "dataset", projectId, dataset, documentId }), type };
71
+ }
72
+ function refCanvas({
73
+ resourceId,
74
+ documentId,
75
+ type
76
+ }) {
77
+ return { id: gdrUri({ scheme: "canvas", resourceId, documentId }), type };
78
+ }
79
+ function refMediaLibrary({
80
+ resourceId,
81
+ documentId,
82
+ type
83
+ }) {
84
+ return { id: gdrUri({ scheme: "media-library", resourceId, documentId }), type };
85
+ }
86
+ function refDashboard({
87
+ resourceId,
88
+ documentId,
89
+ type
90
+ }) {
91
+ return { id: gdrUri({ scheme: "dashboard", resourceId, documentId }), type };
92
+ }
93
+ function datasetResourceParts(id) {
94
+ const dot = id.indexOf(".");
95
+ if (dot <= 0 || dot === id.length - 1)
96
+ throw new Error(`Invalid dataset resource id "${id}": expected "<projectId>.<dataset>".`);
97
+ return { projectId: id.slice(0, dot), dataset: id.slice(dot + 1) };
98
+ }
99
+ function gdrFromResource(res, documentId) {
100
+ if (res.type === "dataset") {
101
+ const { projectId, dataset } = datasetResourceParts(res.id);
102
+ return gdrUri({ scheme: "dataset", projectId, dataset, documentId });
103
+ }
104
+ return gdrUri({ scheme: res.type, resourceId: res.id, documentId });
105
+ }
106
+ function gdrResourcePrefix(res) {
107
+ const full = gdrFromResource(res, "x");
108
+ return full.slice(0, full.length - 1);
109
+ }
110
+ function resourceFromParsed(parsed) {
111
+ return parsed.scheme === "dataset" ? { type: "dataset", id: `${parsed.projectId}.${parsed.dataset}` } : { type: parsed.scheme, id: parsed.resourceId ?? "" };
112
+ }
113
+ function resourceFromGdrUri(uri) {
114
+ const parsed = tryParseGdr(uri);
115
+ return parsed === void 0 ? void 0 : resourceFromParsed(parsed);
116
+ }
117
+ function sameResource(a, b) {
118
+ return a.type === b.type && a.id === b.id;
119
+ }
120
+ function selfGdr(doc) {
121
+ return gdrFromResource(doc.workflowResource, doc._id);
122
+ }
123
+ function definitionDocId({
124
+ tag,
125
+ definition,
126
+ version
127
+ }) {
128
+ return `${tag}.${definition}.v${version}`;
129
+ }
130
+ function isGdrUri(value) {
131
+ return typeof value == "string" && tryParseGdr(value) !== void 0;
132
+ }
133
+ function toBareId(id) {
134
+ return isGdrUri(id) ? extractDocumentId(id) : id;
135
+ }
136
+ function gdrRef({
137
+ res,
138
+ documentId,
139
+ type
140
+ }) {
141
+ return { id: gdrFromResource(res, documentId), type };
142
+ }
143
+ function isGdr(value) {
144
+ return typeof value == "object" && value !== null && typeof value.id == "string" && typeof value.type == "string";
145
+ }
146
+ class WorkflowError extends Error {
147
+ kind;
148
+ // oxlint-disable-next-line max-params -- the platform `Error(message, options)` signature with the discriminant prepended
149
+ constructor(kind, message, options) {
150
+ super(message, options), this.kind = kind;
151
+ }
152
+ }
153
+ class ContractViolationError extends WorkflowError {
154
+ constructor(message) {
155
+ super("contract-violation", message), this.name = "ContractViolationError";
156
+ }
157
+ }
158
+ class InstanceNotFoundError extends WorkflowError {
159
+ instanceId;
160
+ constructor(args) {
161
+ super(
162
+ "instance-not-found",
163
+ `Workflow instance ${args.instanceId} not found${args.detail ? ` (${args.detail})` : ""}`
164
+ ), this.name = "InstanceNotFoundError", this.instanceId = args.instanceId;
165
+ }
166
+ }
167
+ class DefinitionNotFoundError extends WorkflowError {
168
+ definition;
169
+ version;
170
+ constructor(args) {
171
+ super(
172
+ "definition-not-found",
173
+ args.version !== void 0 ? `Workflow definition ${args.definition} v${args.version} not deployed` : `Workflow definition ${args.definition} has no deployed versions`
174
+ ), this.name = "DefinitionNotFoundError", this.definition = args.definition, args.version !== void 0 && (this.version = args.version);
175
+ }
176
+ }
177
+ class DefinitionInUseError extends WorkflowError {
178
+ definition;
179
+ blockedBy;
180
+ constructor(args) {
181
+ super("definition-in-use", definitionInUseMessage(args.definition, args.blockedBy)), this.name = "DefinitionInUseError", this.definition = args.definition, this.blockedBy = args.blockedBy;
182
+ }
183
+ }
184
+ function definitionInUseMessage(definition, blockedBy) {
185
+ if (blockedBy.reason === "non-terminal-instances") {
186
+ const head = blockedBy.instanceIds.slice(0, 3).join(", "), preview = blockedBy.instanceIds.length > 3 ? `${head}, \u2026` : head;
187
+ return `Cannot delete ${definition}: ${blockedBy.instanceIds.length} non-terminal instance(s) exist (${preview}). Pass cascade to abort them first \u2014 instances are aborted in place, never deleted.`;
188
+ }
189
+ const names = blockedBy.referrers.map((r) => `${r.definition} v${r.version}`).join(", ");
190
+ return `Cannot delete ${definition}: still spawn-referenced by deployed definition(s) ${names}. Delete or redeploy the referrer(s) first.`;
191
+ }
192
+ class EffectNotFoundError extends WorkflowError {
193
+ instanceId;
194
+ effectKey;
195
+ constructor(args) {
196
+ super(
197
+ "effect-not-found",
198
+ `Pending effect "${args.effectKey}" not found on instance ${args.instanceId}`
199
+ ), this.name = "EffectNotFoundError", this.instanceId = args.instanceId, this.effectKey = args.effectKey;
200
+ }
201
+ }
202
+ const TAG_RE = /^[a-z0-9][a-z0-9-]*$/;
203
+ function validateTag(tag) {
204
+ if (!TAG_RE.test(tag))
205
+ throw new ContractViolationError(
206
+ `tag: invalid tag "${tag}" \u2014 must match ${TAG_RE.source} (ASCII lowercase + digits + dashes, no leading dash, no dots)`
207
+ );
208
+ }
209
+ function tagScopeFilter() {
210
+ return "tag == $tag";
211
+ }
212
+ const NonEmptyString = v.pipe(v.string(), v.nonEmpty("must not be empty"));
213
+ function asPredicate(validate) {
214
+ return (value) => {
215
+ try {
216
+ return validate(value), !0;
217
+ } catch {
218
+ return !1;
219
+ }
220
+ };
221
+ }
222
+ const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts), WorkflowResourceSchema = v.variant("type", [
223
+ v.object({
224
+ type: v.literal("dataset"),
225
+ id: v.pipe(
226
+ NonEmptyString,
227
+ v.check(isValidDatasetId, 'invalid dataset resource id \u2014 expected "<projectId>.<dataset>"')
228
+ )
229
+ }),
230
+ v.object({ type: v.literal("canvas"), id: NonEmptyString }),
231
+ v.object({ type: v.literal("media-library"), id: NonEmptyString }),
232
+ v.object({ type: v.literal("dashboard"), id: NonEmptyString })
233
+ ]), ResourceBindingSchema = v.object({
234
+ name: v.pipe(
235
+ NonEmptyString,
236
+ v.check(
237
+ isValidAliasName,
238
+ "invalid resource handle name \u2014 lowercase letters, digits and dashes only, no leading dash"
239
+ )
240
+ ),
241
+ resource: WorkflowResourceSchema
242
+ }), DefinitionSchema = v.custom(
243
+ (input) => typeof input == "object" && input !== null && typeof input.name == "string",
244
+ "expected a workflow definition (an object with a string `name`)"
245
+ ), DeploymentSchema = v.object({
246
+ name: NonEmptyString,
247
+ tag: v.pipe(
248
+ v.string(),
249
+ v.nonEmpty(),
250
+ v.check(
251
+ isValidTag,
252
+ "invalid tag \u2014 lowercase letters, digits and dashes only, no leading dash, no dots"
253
+ )
254
+ ),
255
+ workflowResource: WorkflowResourceSchema,
256
+ // Optional — omit for a workflow whose definitions reference no `@handle:`.
257
+ // No valibot default: a default makes the inferred output `resourceAliases`
258
+ // required (poor authoring ergonomics) and diverges input from output,
259
+ // which the shared `parseOrThrow` can't model. resourceAliasesToMap treats
260
+ // an absent map as none.
261
+ //
262
+ // Handle names must be unique: resourceAliasesToMap collapses bindings into a
263
+ // map, so a duplicate would silently overwrite — and the dup is gone before
264
+ // the engine sees it. Caught here, at the only layer that still can.
265
+ resourceAliases: v.optional(
266
+ v.pipe(
267
+ v.array(ResourceBindingSchema),
268
+ v.check(
269
+ (bindings) => new Set(bindings.map((binding) => binding.name)).size === bindings.length,
270
+ "duplicate resource handle name \u2014 each binding name must be unique within a deployment"
271
+ )
272
+ )
273
+ ),
274
+ definitions: v.pipe(
275
+ v.array(DefinitionSchema),
276
+ v.minLength(1, "a deployment needs at least one definition")
277
+ )
278
+ }), WorkflowConfigSchema = v.object({
279
+ // Tags must be unique: a tag is the environment key the CLI selects a
280
+ // deployment by, so two deployments sharing one make selection ambiguous.
281
+ // Enforced here so the ambiguity is a config error, not a deploy-time
282
+ // surprise — and so consumers can treat a tag as naming one deployment.
283
+ deployments: v.pipe(
284
+ v.array(DeploymentSchema),
285
+ v.minLength(1, "a config needs at least one deployment"),
286
+ v.check(
287
+ (deployments) => new Set(deployments.map((deployment) => deployment.tag)).size === deployments.length,
288
+ "duplicate deployment tag \u2014 each deployment must use a unique tag"
289
+ )
290
+ )
291
+ });
292
+ function resourceAliasesToMap(resourceAliases) {
293
+ return Object.fromEntries(
294
+ (resourceAliases ?? []).map((binding) => [
295
+ binding.name,
296
+ binding.resource
297
+ ])
298
+ );
299
+ }
2
300
  function andConditions(parts) {
3
301
  const present = parts.filter((p) => p !== void 0);
4
302
  if (present.length !== 0)
5
303
  return present.length === 1 ? present[0] : present.map((p) => `(${p})`).join(" && ");
6
304
  }
305
+ const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
306
+ function isGuardReadExpr(expr) {
307
+ return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
308
+ }
309
+ function printGuardRead(read) {
310
+ switch (read.type) {
311
+ case "self":
312
+ return "$self";
313
+ case "now":
314
+ return "$now";
315
+ case "fieldRead":
316
+ return read.path === void 0 ? `$fields.${read.field}` : `$fields.${read.field}.${read.path}`;
317
+ case "effectsRead":
318
+ return read.path === void 0 ? `$effects['${read.effect}']` : `$effects['${read.effect}'].${read.path}`;
319
+ }
320
+ }
7
321
  const UNIVERSAL_ROLE_ALIAS_KEY = "$all";
8
322
  function normalizeRoleAliases(aliases) {
9
323
  if (aliases === void 0 || !("*" in aliases)) return aliases;
@@ -20,11 +334,259 @@ function expandRequiredRoles(required, aliases) {
20
334
  for (const fulfiller of aliases[UNIVERSAL_ROLE_ALIAS_KEY] ?? []) out.add(fulfiller);
21
335
  return [...out];
22
336
  }
23
- function actorFulfillsRole(actorRoles, required, aliases) {
337
+ function actorFulfillsRole({
338
+ actorRoles,
339
+ required,
340
+ aliases
341
+ }) {
24
342
  if (actorRoles === void 0 || actorRoles.length === 0) return !1;
25
343
  const accepted = new Set(expandRequiredRoles([required], aliases));
26
344
  return actorRoles.some((role) => accepted.has(role));
27
345
  }
346
+ const CONDITION_VARS = [
347
+ {
348
+ name: "self",
349
+ binding: "always",
350
+ description: "GDR URI of the instance document itself \u2014 `*[_id == $self][0]` reads the instance in the snapshot."
351
+ },
352
+ {
353
+ name: "fields",
354
+ binding: "always",
355
+ description: "Declared field entries rendered by name (`$fields.<name>` is the value, no envelope). Stage/activity scopes overlay lexically; `doc.ref` values render the hydrated document when the snapshot holds it."
356
+ },
357
+ {
358
+ name: "parent",
359
+ binding: "always",
360
+ description: "The parent instance's GDR URI, `null` on a root instance."
361
+ },
362
+ {
363
+ name: "ancestors",
364
+ binding: "always",
365
+ description: "GDR URIs of the ancestor chain, root first."
366
+ },
367
+ { name: "stage", binding: "always", description: "The current stage's name." },
368
+ {
369
+ name: "now",
370
+ binding: "always",
371
+ description: "The ISO clock reading shared by every condition in one evaluation pass, so they agree on the time."
372
+ },
373
+ {
374
+ name: "effects",
375
+ binding: "always",
376
+ description: "The `effectsContext` map: stable handler params seeded at start plus completed effects' outputs namespaced by effect name (`$effects['<effect>'].<output>`). Handler fuel \u2014 transition filters should read instance/lake state (or $effectStatus) instead."
377
+ },
378
+ {
379
+ name: "effectStatus",
380
+ binding: "always",
381
+ description: "Effect name \u2192 `'done'` | `'failed'` of the latest completed run queued during the current stage entry; absent until the effect drains for this entry. Re-entry-safe: runs queued under a prior entry never count, so `$effectStatus['<effect>'] == 'done'` (or `defined($effectStatus['<effect>'])` for settled-either-way) waits for the fresh run."
382
+ },
383
+ {
384
+ name: "activities",
385
+ binding: "always",
386
+ description: "The current stage's activity rows, statuses included."
387
+ },
388
+ {
389
+ name: "allActivitiesDone",
390
+ binding: "always",
391
+ description: "Every current-stage activity is `done` or `skipped` \u2014 the default transition gate."
392
+ },
393
+ {
394
+ name: "anyActivityFailed",
395
+ binding: "always",
396
+ description: "Some current-stage activity is `failed`."
397
+ },
398
+ {
399
+ name: "actor",
400
+ binding: "caller",
401
+ description: "The acting identity (id + roles); `undefined` when no caller rides the evaluation."
402
+ },
403
+ {
404
+ name: "assigned",
405
+ binding: "caller",
406
+ description: "Whether the caller matches the activity's assignees-kind field entry (by user id, or by a role under the definition's `roleAliases`); `false` outside an activity context."
407
+ },
408
+ {
409
+ name: "can",
410
+ binding: "caller",
411
+ description: "Advisory per-permission booleans computed from the caller's grants; `undefined` without grants. Bound only while an action fires, so it may appear in action filters only (deploy-enforced)."
412
+ },
413
+ {
414
+ name: "row",
415
+ binding: "spawn",
416
+ description: "One `subworkflows.forEach` result row, bound while its `with` map evaluates \u2014 and the row under test while a where-op `where` evaluates (per row)."
417
+ },
418
+ {
419
+ name: "params",
420
+ binding: "caller",
421
+ description: "The firing action's args \u2014 bound while its effect bindings and where-op `where` conditions evaluate."
422
+ },
423
+ {
424
+ name: "subworkflows",
425
+ binding: "spawn",
426
+ description: "The spawned child rows (status included) of the spawning activity, bound for its `completeWhen`/`failWhen` \u2014 the implicit completion gate reads it."
427
+ }
428
+ ], RESERVED_CONDITION_VARS = CONDITION_VARS.map((v2) => v2.name), FILTER_SCOPE_VARS = CONDITION_VARS.filter(
429
+ (v2) => v2.binding === "always"
430
+ ).map((v2) => v2.name), GUARD_PREDICATE_VARS = [
431
+ {
432
+ name: "guard",
433
+ description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
434
+ },
435
+ {
436
+ name: "mutation",
437
+ description: "The attempted mutation \u2014 `mutation.action` is the write kind being gated."
438
+ }
439
+ ], ACTOR_KINDS = ["person", "agent", "system"];
440
+ class FieldValueShapeError extends WorkflowError {
441
+ entryType;
442
+ entryName;
443
+ issues;
444
+ constructor(args) {
445
+ const issueText = args.issues.join("; ");
446
+ super(
447
+ "field-value-shape",
448
+ `Field entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`
449
+ ), this.name = "FieldValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName, this.issues = args.issues;
450
+ }
451
+ }
452
+ const GdrShape = v.looseObject({
453
+ id: v.pipe(
454
+ v.string(),
455
+ v.check((s) => isGdrUri(s), "must be a GDR URI")
456
+ ),
457
+ type: v.pipe(v.string(), v.minLength(1))
458
+ }), ReleaseRefShape = v.looseObject({
459
+ id: v.pipe(
460
+ v.string(),
461
+ v.check((s) => isGdrUri(s), "must be a GDR URI")
462
+ ),
463
+ type: v.literal("system.release"),
464
+ releaseName: v.pipe(v.string(), v.minLength(1))
465
+ }), ActorShape = v.looseObject({
466
+ kind: v.picklist(ACTOR_KINDS),
467
+ id: v.pipe(v.string(), v.minLength(1)),
468
+ roles: v.optional(v.array(v.string())),
469
+ onBehalfOf: v.optional(v.string())
470
+ }), AssigneeShape = v.union([
471
+ v.looseObject({ type: v.literal("user"), id: v.pipe(v.string(), v.minLength(1)) }),
472
+ v.looseObject({ type: v.literal("role"), role: v.pipe(v.string(), v.minLength(1)) })
473
+ ]), NullableString = v.union([v.null(), v.string()]), NullableNumber = v.union([v.null(), v.number()]), NullableBoolean = v.union([v.null(), v.boolean()]), NullableDateTime = v.union([
474
+ v.null(),
475
+ v.pipe(
476
+ v.string(),
477
+ v.check((s) => !Number.isNaN(Date.parse(s)), "must be an ISO-8601 datetime string")
478
+ )
479
+ ]), NullableDate = v.union([
480
+ v.null(),
481
+ v.pipe(v.string(), v.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date"))
482
+ ]), NullableUrl = NullableString, valueSchemas = {
483
+ "doc.ref": v.union([v.null(), GdrShape]),
484
+ "doc.refs": v.array(GdrShape),
485
+ "release.ref": v.union([v.null(), ReleaseRefShape]),
486
+ query: v.any(),
487
+ string: NullableString,
488
+ text: NullableString,
489
+ number: NullableNumber,
490
+ boolean: NullableBoolean,
491
+ date: NullableDate,
492
+ datetime: NullableDateTime,
493
+ url: NullableUrl,
494
+ actor: v.union([v.null(), ActorShape]),
495
+ assignee: v.union([v.null(), AssigneeShape]),
496
+ assignees: v.array(AssigneeShape)
497
+ };
498
+ function shapeValueSchema(shape, leaf) {
499
+ return shape.type === "object" ? objectSchema(shape.fields ?? [], leaf) : shape.type === "array" ? v.array(objectSchema(shape.of ?? [], leaf)) : leaf[shape.type] ?? v.any();
500
+ }
501
+ function objectSchema(fields, leaf) {
502
+ const entries = /* @__PURE__ */ Object.create(null);
503
+ for (const f of fields) entries[f.name] = v.optional(shapeValueSchema(f, leaf));
504
+ return v.looseObject(entries);
505
+ }
506
+ function wholeValueSchema(args) {
507
+ const { entryType, shape, leaf } = args;
508
+ return entryType === "object" ? v.union([v.null(), objectSchema(shape.fields ?? [], leaf)]) : entryType === "array" ? v.array(objectSchema(shape.of ?? [], leaf)) : leaf[entryType];
509
+ }
510
+ function appendItemSchema(entryType, shape) {
511
+ if (entryType === "array") return objectSchema(shape.of ?? [], valueSchemas);
512
+ if (entryType === "doc.refs") return GdrShape;
513
+ if (entryType === "assignees") return AssigneeShape;
514
+ }
515
+ function checkFieldValue(args) {
516
+ return checkValueAgainst(args, valueSchemas);
517
+ }
518
+ function checkValueAgainst(args, leaf) {
519
+ const schema = wholeValueSchema({ entryType: args.entryType, shape: args, leaf });
520
+ if (schema === void 0) return [`unknown field entry type ${args.entryType}`];
521
+ const result = v.safeParse(schema, args.value);
522
+ return result.success ? void 0 : formatIssues(result.issues);
523
+ }
524
+ function validateFieldValue(args) {
525
+ const issues = checkFieldValue(args);
526
+ if (issues !== void 0)
527
+ throw new FieldValueShapeError({
528
+ entryType: args.entryType,
529
+ entryName: args.entryName,
530
+ issues,
531
+ mode: "value"
532
+ });
533
+ }
534
+ const BARE_ID_SOURCE = "[A-Za-z0-9_][A-Za-z0-9._-]*", BARE_ID_RE = new RegExp(`^${BARE_ID_SOURCE}$`), ALIAS_REF_RE = new RegExp(`^@${RESOURCE_ALIAS_NAME_SOURCE}:${BARE_ID_SOURCE}$`);
535
+ function isAuthoringRefId(id) {
536
+ return id.startsWith("@") ? ALIAS_REF_RE.test(id) : id.includes(":") ? isGdrUri(id) : BARE_ID_RE.test(id);
537
+ }
538
+ function isBareSeedId(id) {
539
+ return BARE_ID_RE.test(id);
540
+ }
541
+ const AuthoringRefId = v.pipe(
542
+ v.string(),
543
+ v.check(
544
+ isAuthoringRefId,
545
+ "must be a bare document id, a GDR URI, or a portable `@<alias>:<id>` reference"
546
+ )
547
+ ), AuthoringGdrShape = v.looseObject({
548
+ id: AuthoringRefId,
549
+ type: v.pipe(v.string(), v.minLength(1))
550
+ }), seedValueSchemas = {
551
+ ...valueSchemas,
552
+ "doc.ref": v.union([v.null(), AuthoringGdrShape]),
553
+ "doc.refs": v.array(AuthoringGdrShape),
554
+ "release.ref": v.union([
555
+ v.null(),
556
+ v.looseObject({
557
+ id: AuthoringRefId,
558
+ type: v.literal("system.release"),
559
+ releaseName: v.pipe(v.string(), v.minLength(1))
560
+ })
561
+ ])
562
+ };
563
+ function checkLiteralSeed(args) {
564
+ return args.value === null ? ["a literal seed cannot be null \u2014 omit `initialValue` to start the field empty"] : checkValueAgainst(args, seedValueSchemas);
565
+ }
566
+ function validateFieldAppendItem(args) {
567
+ const schema = appendItemSchema(args.entryType, args);
568
+ if (schema === void 0)
569
+ throw new FieldValueShapeError({
570
+ entryType: args.entryType,
571
+ entryName: args.entryName,
572
+ issues: [`field entry type ${args.entryType} does not support append`],
573
+ mode: "item"
574
+ });
575
+ const result = v.safeParse(schema, args.item);
576
+ if (!result.success)
577
+ throw new FieldValueShapeError({
578
+ entryType: args.entryType,
579
+ entryName: args.entryName,
580
+ issues: formatIssues(result.issues),
581
+ mode: "item"
582
+ });
583
+ }
584
+ function formatIssues(issues) {
585
+ return issues.map((i) => {
586
+ const keys = i.path?.map((p) => p.key) ?? [];
587
+ return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${i.message}`;
588
+ });
589
+ }
28
590
  const ACTIVITY_STATUSES = ["pending", "active", "done", "skipped", "failed"], TERMINAL_ACTIVITY_STATUSES = ["done", "skipped", "failed"];
29
591
  function isTerminalActivityStatus(status) {
30
592
  return TERMINAL_ACTIVITY_STATUSES.includes(status);
@@ -51,6 +613,9 @@ function picklist(options) {
51
613
  `Invalid option: expected one of ${options.map((o) => `"${o}"`).join("|")}`
52
614
  );
53
615
  }
616
+ function pinned() {
617
+ return (schema, ..._exact) => schema;
618
+ }
54
619
  const LiteralSchema = v.strictObject({ type: v.literal("literal"), value: v.unknown() }), FieldReadSchema = v.strictObject({
55
620
  type: v.literal("fieldRead"),
56
621
  scope: v.optional(v.union([v.literal("workflow"), v.literal("stage")])),
@@ -99,17 +664,7 @@ function manualTargetSchema(ref) {
99
664
  v.strictObject({ type: v.literal("field"), field: ref })
100
665
  ]);
101
666
  }
102
- const StoredManualTargetSchema = manualTargetSchema(StoredFieldRefSchema), AuthoringManualTargetSchema = manualTargetSchema(v.union([NonEmpty, AuthoringFieldRefSchema])), OpPredicateSchema = v.lazy(
103
- () => v.union([
104
- v.strictObject({
105
- type: v.literal("field"),
106
- field: NonEmpty,
107
- equals: ValueExprSchema
108
- }),
109
- v.strictObject({ type: v.literal("all"), of: v.array(OpPredicateSchema) }),
110
- v.strictObject({ type: v.literal("any"), of: v.array(OpPredicateSchema) })
111
- ])
112
- );
667
+ const StoredManualTargetSchema = manualTargetSchema(StoredFieldRefSchema), AuthoringManualTargetSchema = manualTargetSchema(v.union([NonEmpty, AuthoringFieldRefSchema])), ConditionSchema = NonEmpty;
113
668
  function opSchemas(targetSchema) {
114
669
  return [
115
670
  v.strictObject({
@@ -129,24 +684,24 @@ function opSchemas(targetSchema) {
129
684
  v.strictObject({
130
685
  type: v.literal("field.updateWhere"),
131
686
  target: targetSchema,
132
- where: OpPredicateSchema,
687
+ where: ConditionSchema,
133
688
  value: ValueExprSchema
134
689
  }),
135
690
  v.strictObject({
136
691
  type: v.literal("field.removeWhere"),
137
692
  target: targetSchema,
138
- where: OpPredicateSchema
693
+ where: ConditionSchema
139
694
  })
140
695
  ];
141
696
  }
142
- const StoredOpSchema = v.variant("type", [
697
+ const StoredFieldOpSchema = v.variant("type", [...opSchemas(StoredFieldRefSchema)]), StoredOpSchema = v.variant("type", [
143
698
  ...opSchemas(StoredFieldRefSchema),
144
699
  v.strictObject({
145
700
  type: v.literal("status.set"),
146
701
  activity: NonEmpty,
147
702
  status: picklist(ACTIVITY_STATUSES)
148
703
  })
149
- ]), StoredTransitionOpSchema = v.variant("type", [...opSchemas(StoredFieldRefSchema)]), AuditOpSchema = v.strictObject({
704
+ ]), StoredTransitionOpSchema = StoredFieldOpSchema, AuditOpSchema = v.strictObject({
150
705
  type: v.literal("audit"),
151
706
  target: AuthoringFieldRefSchema,
152
707
  value: ValueExprSchema,
@@ -235,7 +790,7 @@ const FieldShapeSchema = v.lazy(
235
790
  of: v.optional(v.array(FieldShapeSchema))
236
791
  })
237
792
  ), StoredEditableSchema = v.union([v.literal(!0), NonEmpty]), AuthoringEditableSchema = v.union([v.literal(!0), v.array(NonEmpty), NonEmpty]);
238
- function slotFields(editable) {
793
+ function fieldBase(editable) {
239
794
  return {
240
795
  name: FieldEntryName,
241
796
  title: v.optional(v.string()),
@@ -251,41 +806,62 @@ function slotFields(editable) {
251
806
  /**
252
807
  * How this field SEEDS its value at materialisation — see
253
808
  * {@link FieldSourceSchema}. Optional: an absent `initialValue` is working
254
- * memory (the slot starts empty and an op fills it later), the common case.
809
+ * memory (the field starts empty and an op fills it later), the common case.
255
810
  */
256
811
  initialValue: v.optional(FieldSourceSchema),
257
- /** Who-may-edit this slot via the edit seam — see {@link StoredEditableSchema}. */
812
+ /** Who-may-edit this field via the edit seam — see {@link StoredEditableSchema}. */
258
813
  editable: v.optional(editable)
259
814
  };
260
815
  }
261
816
  function fieldEntryFields(editable) {
262
817
  return {
263
818
  type: FieldKindSchema,
264
- ...slotFields(editable),
819
+ ...fieldBase(editable),
265
820
  /** Sub-field shapes for an `object` kind (see {@link compositeShapeOk}). */
266
821
  fields: v.optional(v.array(FieldShapeSchema)),
267
822
  /** Item sub-field shapes for an `array` kind (see {@link compositeShapeOk}). */
268
823
  of: v.optional(v.array(FieldShapeSchema))
269
824
  };
270
825
  }
271
- const FieldEntrySchema = compositeChecked(fieldEntryFields(StoredEditableSchema)), RawAuthoringFieldEntrySchema = compositeChecked(fieldEntryFields(AuthoringEditableSchema)), ClaimFieldSchema = v.strictObject({
272
- type: v.literal("claim"),
273
- name: FieldEntryName,
274
- title: v.optional(v.string()),
275
- description: v.optional(v.string())
276
- });
826
+ function literalSeedIssues(entry) {
827
+ if (entry.initialValue?.type === "literal")
828
+ return checkLiteralSeed({
829
+ entryType: entry.type,
830
+ value: entry.initialValue.value,
831
+ fields: entry.fields,
832
+ of: entry.of
833
+ });
834
+ }
835
+ function literalSeedCheck() {
836
+ return v.check(
837
+ (entry) => literalSeedIssues(entry) === void 0,
838
+ (issue) => `initialValue literal does not fit the declared kind: ${(literalSeedIssues(issue.input) ?? []).join("; ")}`
839
+ );
840
+ }
841
+ const FieldEntrySchema = pinned()(
842
+ v.pipe(compositeChecked(fieldEntryFields(StoredEditableSchema)), literalSeedCheck())
843
+ ), RawAuthoringFieldEntrySchema = pinned()(
844
+ v.pipe(
845
+ compositeChecked(fieldEntryFields(AuthoringEditableSchema)),
846
+ literalSeedCheck()
847
+ )
848
+ ), ClaimFieldSchema = pinned()(
849
+ v.strictObject({
850
+ type: v.literal("claim"),
851
+ name: FieldEntryName,
852
+ title: v.optional(v.string()),
853
+ description: v.optional(v.string())
854
+ })
855
+ );
277
856
  function listSugarFields(type) {
278
857
  return {
279
858
  type: v.literal(type),
280
- ...slotFields(AuthoringEditableSchema)
859
+ ...fieldBase(AuthoringEditableSchema)
281
860
  };
282
861
  }
283
- const TodoListFieldSchema = v.strictObject(listSugarFields("todoList")), NotesFieldSchema = v.strictObject(listSugarFields("notes")), AuthoringFieldEntrySchema = v.union([
284
- RawAuthoringFieldEntrySchema,
285
- ClaimFieldSchema,
286
- TodoListFieldSchema,
287
- NotesFieldSchema
288
- ]), ConditionSchema = NonEmpty, EffectSchema = v.strictObject({
862
+ const TodoListFieldSchema = pinned()(v.strictObject(listSugarFields("todoList"))), NotesFieldSchema = pinned()(v.strictObject(listSugarFields("notes"))), AuthoringFieldEntrySchema = pinned()(
863
+ v.union([RawAuthoringFieldEntrySchema, ClaimFieldSchema, TodoListFieldSchema, NotesFieldSchema])
864
+ ), EffectSchema = v.strictObject({
289
865
  name: NonEmpty,
290
866
  title: v.optional(v.string()),
291
867
  description: v.optional(v.string()),
@@ -317,8 +893,8 @@ function actionFields(op) {
317
893
  description: v.optional(v.string()),
318
894
  /**
319
895
  * The one gate mechanism: a condition over the rendered scope. Hard
320
- * enforcement lives outside the engine entirely (the lake ACL on the
321
- * documents, and guards) — every engine-evaluated gate is authoring/UX.
896
+ * enforcement lives outside the engine; every engine-evaluated gate is
897
+ * authoring/UX.
322
898
  */
323
899
  filter: v.optional(ConditionSchema),
324
900
  params: v.optional(v.array(ActionParamSchema)),
@@ -326,21 +902,27 @@ function actionFields(op) {
326
902
  effects: v.optional(v.array(EffectSchema))
327
903
  };
328
904
  }
329
- const StoredActionSchema = v.strictObject(actionFields(StoredOpSchema)), TerminalActivityStatus = picklist(TERMINAL_ACTIVITY_STATUSES), RawAuthoringActionSchema = v.strictObject({
330
- ...actionFields(AuthoringOpSchema),
331
- roles: v.optional(v.array(NonEmpty)),
332
- status: v.optional(TerminalActivityStatus)
333
- }), ClaimActionSchema = v.strictObject({
334
- type: v.literal("claim"),
335
- name: NonEmpty,
336
- title: v.optional(v.string()),
337
- description: v.optional(v.string()),
338
- field: v.union([NonEmpty, AuthoringFieldRefSchema]),
339
- roles: v.optional(v.array(NonEmpty)),
340
- filter: v.optional(ConditionSchema),
341
- params: v.optional(v.array(ActionParamSchema)),
342
- effects: v.optional(v.array(EffectSchema))
343
- }), AuthoringActionSchema = v.union([RawAuthoringActionSchema, ClaimActionSchema]), DefinitionRefSchema = v.strictObject({
905
+ const StoredActionSchema = pinned()(v.strictObject(actionFields(StoredOpSchema))), TerminalActivityStatus = picklist(TERMINAL_ACTIVITY_STATUSES), RawAuthoringActionSchema = pinned()(
906
+ v.strictObject({
907
+ ...actionFields(AuthoringOpSchema),
908
+ roles: v.optional(v.array(NonEmpty)),
909
+ status: v.optional(TerminalActivityStatus)
910
+ })
911
+ ), ClaimActionSchema = pinned()(
912
+ v.strictObject({
913
+ type: v.literal("claim"),
914
+ name: NonEmpty,
915
+ title: v.optional(v.string()),
916
+ description: v.optional(v.string()),
917
+ field: v.union([NonEmpty, AuthoringFieldRefSchema]),
918
+ roles: v.optional(v.array(NonEmpty)),
919
+ filter: v.optional(ConditionSchema),
920
+ params: v.optional(v.array(ActionParamSchema)),
921
+ effects: v.optional(v.array(EffectSchema))
922
+ })
923
+ ), AuthoringActionSchema = pinned()(
924
+ v.union([RawAuthoringActionSchema, ClaimActionSchema])
925
+ ), DefinitionRefSchema = v.strictObject({
344
926
  name: NonEmpty,
345
927
  version: v.optional(v.union([PositiveInt, v.literal("latest")]))
346
928
  }), SubworkflowsSchema = v.strictObject({
@@ -356,7 +938,7 @@ const StoredActionSchema = v.strictObject(actionFields(StoredOpSchema)), Termina
356
938
  */
357
939
  context: v.optional(v.record(NonEmpty, ConditionSchema))
358
940
  });
359
- function activityFields(field, action, op, target) {
941
+ function activityFields({ field, action, op, target }) {
360
942
  return {
361
943
  name: NonEmpty,
362
944
  title: v.optional(v.string()),
@@ -379,8 +961,8 @@ function activityFields(field, action, op, target) {
379
961
  * (visibility). An unmet requirement keeps the activity visible but disables
380
962
  * its actions with a `requirements-unmet` verdict naming the unmet keys;
381
963
  * all must hold (any-of lives inside one condition). Advisory like every
382
- * engine gate — the lake still enforces. Distinct from ACL (authorization)
383
- * and guards (content-write locks).
964
+ * engine gate. Distinct from ACL (authorization) and guards
965
+ * (content-write locks).
384
966
  */
385
967
  requirements: v.optional(v.record(NonEmpty, ConditionSchema)),
386
968
  /**
@@ -403,14 +985,23 @@ function activityFields(field, action, op, target) {
403
985
  fields: v.optional(v.array(field))
404
986
  };
405
987
  }
406
- const StoredActivitySchema = v.strictObject(
407
- activityFields(FieldEntrySchema, StoredActionSchema, StoredOpSchema, StoredManualTargetSchema)
408
- ), AuthoringActivitySchema = v.strictObject(
409
- activityFields(
410
- AuthoringFieldEntrySchema,
411
- AuthoringActionSchema,
412
- AuthoringOpSchema,
413
- AuthoringManualTargetSchema
988
+ const StoredActivitySchema = pinned()(
989
+ v.strictObject(
990
+ activityFields({
991
+ field: FieldEntrySchema,
992
+ action: StoredActionSchema,
993
+ op: StoredOpSchema,
994
+ target: StoredManualTargetSchema
995
+ })
996
+ )
997
+ ), AuthoringActivitySchema = pinned()(
998
+ v.strictObject(
999
+ activityFields({
1000
+ field: AuthoringFieldEntrySchema,
1001
+ action: AuthoringActionSchema,
1002
+ op: AuthoringOpSchema,
1003
+ target: AuthoringManualTargetSchema
1004
+ })
414
1005
  )
415
1006
  );
416
1007
  function transitionFields(op, filter) {
@@ -425,47 +1016,84 @@ function transitionFields(op, filter) {
425
1016
  effects: v.optional(v.array(EffectSchema))
426
1017
  };
427
1018
  }
428
- const StoredTransitionSchema = v.strictObject(
429
- transitionFields(StoredTransitionOpSchema, ConditionSchema)
1019
+ const StoredTransitionSchema = pinned()(
1020
+ v.strictObject(transitionFields(StoredTransitionOpSchema, ConditionSchema))
430
1021
  ), AuthoringTransitionOpSchema = v.variant("type", [
431
1022
  ...opSchemas(AuthoringFieldRefSchema),
432
1023
  AuditOpSchema
433
- ]), AuthoringTransitionSchema = v.strictObject(
434
- transitionFields(AuthoringTransitionOpSchema, v.optional(ConditionSchema))
435
- ), GuardActionSchema = picklist(MUTATION_GUARD_ACTIONS), GuardMatchSchema = v.strictObject({
436
- /** Subject `_type`(s); empty matches any type. */
437
- types: v.optional(v.array(NonEmpty)),
438
- /** Target docs as `$fields` reads (or `"$self"`), resolved at deploy to bare ids + the resource. */
439
- idRefs: v.optional(v.array(NonEmpty)),
440
- /** Glob id patterns (bare, resource-local). */
441
- idPatterns: v.optional(v.array(NonEmpty)),
442
- actions: v.pipe(
443
- v.array(GuardActionSchema),
444
- v.minLength(1, "a guard must match at least one action")
1024
+ ]), AuthoringTransitionSchema = pinned()(
1025
+ v.strictObject(transitionFields(AuthoringTransitionOpSchema, v.optional(ConditionSchema)))
1026
+ ), GuardActionSchema = picklist(MUTATION_GUARD_ACTIONS), GuardReadPath = v.pipe(
1027
+ NonEmpty,
1028
+ v.check(
1029
+ (path) => !/[\r\n\u2028\u2029]/.test(path),
1030
+ "a guard read path cannot contain a line break"
445
1031
  )
446
- }), GuardSchema = v.strictObject({
447
- name: NonEmpty,
448
- title: v.optional(v.string()),
449
- description: v.optional(v.string()),
450
- match: GuardMatchSchema,
451
- /**
452
- * Lake GROQ predicate — a distinct eval context reading
453
- * `document.before`/`document.after`, `mutation`, `guard`, and
454
- * `identity()`. Bare ids/fields only. Omitted or empty means
455
- * UNCONDITIONAL DENY.
456
- */
457
- predicate: v.optional(v.string()),
458
- /**
459
- * Projected workflow fields the predicate reads as `guard.metadata.*` —
460
- * the only bridge from the lake eval context (which cannot see `$fields`)
461
- * to workflow fields. Values are NOT GROQ: each is a deploy-time read in
462
- * the guard mini-language — `"$self"`, `"$now"`, or
463
- * `"$fields.<name>[.path]"` — resolved into a bare value at deploy and
464
- * re-synced by the post-field-op guard refresh.
465
- */
466
- metadata: v.optional(v.record(NonEmpty, NonEmpty))
467
- });
468
- function stageFields(field, activity, transition, editable) {
1032
+ ), GuardReadSchema = v.variant("type", [
1033
+ v.strictObject({ type: v.literal("self") }),
1034
+ v.strictObject({ type: v.literal("now") }),
1035
+ v.strictObject({
1036
+ type: v.literal("fieldRead"),
1037
+ field: FieldEntryName,
1038
+ path: v.optional(GuardReadPath)
1039
+ }),
1040
+ v.strictObject({
1041
+ type: v.literal("effectsRead"),
1042
+ // The stored spelling bracket-quotes the name (`$effects['<name>']`), so a
1043
+ // quote inside it would corrupt the printed read.
1044
+ effect: v.pipe(
1045
+ NonEmpty,
1046
+ v.check((name) => !name.includes("'"), "an effect name cannot contain `'`")
1047
+ ),
1048
+ path: v.optional(GuardReadPath)
1049
+ })
1050
+ ]);
1051
+ function guardMatchFields(read) {
1052
+ return {
1053
+ /** Subject `_type`(s); empty matches any type. */
1054
+ types: v.optional(v.array(NonEmpty)),
1055
+ /** Target docs as field reads (or the instance itself), resolved at deploy to bare ids + the resource. */
1056
+ idRefs: v.optional(v.array(read)),
1057
+ /** Glob id patterns (bare, resource-local). */
1058
+ idPatterns: v.optional(v.array(NonEmpty)),
1059
+ actions: v.pipe(
1060
+ v.array(GuardActionSchema),
1061
+ v.minLength(1, "a guard must match at least one action")
1062
+ )
1063
+ };
1064
+ }
1065
+ function guardFields(read) {
1066
+ return {
1067
+ name: NonEmpty,
1068
+ title: v.optional(v.string()),
1069
+ description: v.optional(v.string()),
1070
+ match: v.strictObject(guardMatchFields(read)),
1071
+ /**
1072
+ * Lake GROQ predicate — a distinct eval context reading
1073
+ * `document.before`/`document.after`, `mutation`, `guard`, and
1074
+ * `identity()`. Bare ids/fields only. Omitted or empty means
1075
+ * UNCONDITIONAL DENY.
1076
+ */
1077
+ predicate: v.optional(v.string()),
1078
+ /**
1079
+ * Projected workflow fields the predicate reads as `guard.metadata.*` —
1080
+ * the only bridge from the lake eval context (which cannot see `$fields`)
1081
+ * to workflow fields. Each value is a deploy-time read — a typed
1082
+ * {@link GuardRead} when authoring, the printed string spelling once
1083
+ * stored — resolved into a bare value at deploy and re-synced by the
1084
+ * post-field-op guard refresh.
1085
+ */
1086
+ metadata: v.optional(v.record(NonEmpty, read))
1087
+ };
1088
+ }
1089
+ const GuardSchema = v.strictObject(guardFields(NonEmpty)), AuthoringGuardSchema = v.strictObject(guardFields(GuardReadSchema));
1090
+ function stageFields({
1091
+ field,
1092
+ activity,
1093
+ transition,
1094
+ guard,
1095
+ editable
1096
+ }) {
469
1097
  return {
470
1098
  name: NonEmpty,
471
1099
  title: v.optional(v.string()),
@@ -474,49 +1102,60 @@ function stageFields(field, activity, transition, editable) {
474
1102
  transitions: v.optional(v.array(transition)),
475
1103
  /**
476
1104
  * Lake mutation guards active while this stage holds. Each compiles to a
477
- * `temp.system.guard` doc deployed on stage entry and retracted on exit.
1105
+ * persisted guard document deployed on stage entry and retracted on exit.
478
1106
  */
479
- guards: v.optional(v.array(GuardSchema)),
1107
+ guards: v.optional(v.array(guard)),
480
1108
  /** Stage-scoped field entries. Resolved at stage entry. */
481
1109
  fields: v.optional(v.array(field)),
482
1110
  /**
483
1111
  * Tighten-only editability overrides for the time this stage holds, keyed
484
- * by an in-scope slot name. The slot's own `editable` is the ceiling; a
1112
+ * by an in-scope field name. The field's own `editable` is the ceiling; a
485
1113
  * stage value is ANDed with it at runtime, so an override can only NARROW
486
- * (never open a slot the baseline left closed). An unlisted slot inherits
1114
+ * (never open a field the baseline left closed). An unlisted field inherits
487
1115
  * its baseline.
488
1116
  */
489
1117
  editable: v.optional(v.record(FieldEntryName, editable))
490
1118
  };
491
1119
  }
492
- const StoredStageSchema = v.strictObject(
493
- stageFields(FieldEntrySchema, StoredActivitySchema, StoredTransitionSchema, StoredEditableSchema)
494
- ), AuthoringStageSchema = v.strictObject(
495
- stageFields(
496
- AuthoringFieldEntrySchema,
497
- AuthoringActivitySchema,
498
- AuthoringTransitionSchema,
499
- AuthoringEditableSchema
1120
+ const StoredStageSchema = pinned()(
1121
+ v.strictObject(
1122
+ stageFields({
1123
+ field: FieldEntrySchema,
1124
+ activity: StoredActivitySchema,
1125
+ transition: StoredTransitionSchema,
1126
+ guard: GuardSchema,
1127
+ editable: StoredEditableSchema
1128
+ })
1129
+ )
1130
+ ), AuthoringStageSchema = pinned()(
1131
+ v.strictObject(
1132
+ stageFields({
1133
+ field: AuthoringFieldEntrySchema,
1134
+ activity: AuthoringActivitySchema,
1135
+ transition: AuthoringTransitionSchema,
1136
+ guard: AuthoringGuardSchema,
1137
+ editable: AuthoringEditableSchema
1138
+ })
500
1139
  )
501
1140
  ), RoleAliasesSchema = v.record(
502
1141
  NonEmpty,
503
1142
  v.pipe(v.array(NonEmpty), v.minLength(1, "a role alias must list at least one fulfilling role"))
504
- ), WORKFLOW_ROLES = ["workflow", "child"];
1143
+ ), WORKFLOW_LIFECYCLES = ["standalone", "child"];
505
1144
  function workflowFields(field, stage) {
506
1145
  return {
507
1146
  name: NonEmpty,
508
1147
  title: NonEmpty,
509
1148
  description: v.optional(v.string()),
510
1149
  /**
511
- * Whether a human may start this workflow standalone. `'child'` marks a
1150
+ * How instances of this workflow come to exist. `'child'` marks a
512
1151
  * spawn-only definition — instantiated by a parent via `activity.subworkflows`,
513
- * never started cold from a picker. Omitted ⇒ `'workflow'` (startable).
1152
+ * never started cold from a picker. Omitted ⇒ `'standalone'` (startable).
514
1153
  * Advisory: consumers filter their start pickers on it (see
515
1154
  * {@link isStartableDefinition}); the engine does NOT refuse a
516
1155
  * `startInstance` on a `'child'` def — load-bearing `required` field is the
517
1156
  * runtime backstop.
518
1157
  */
519
- role: v.optional(picklist(WORKFLOW_ROLES)),
1158
+ lifecycle: v.optional(picklist(WORKFLOW_LIFECYCLES)),
520
1159
  /** Reference field: named for the target, holds the stage's `name`. */
521
1160
  initialStage: NonEmpty,
522
1161
  /** Workflow-scope field entries. Persist for the instance lifetime. */
@@ -532,12 +1171,14 @@ function workflowFields(field, stage) {
532
1171
  roleAliases: v.optional(RoleAliasesSchema)
533
1172
  };
534
1173
  }
535
- v.strictObject(workflowFields(FieldEntrySchema, StoredStageSchema));
536
- const AuthoringWorkflowSchema = v.strictObject(
537
- workflowFields(AuthoringFieldEntrySchema, AuthoringStageSchema)
1174
+ pinned()(
1175
+ v.strictObject(workflowFields(FieldEntrySchema, StoredStageSchema))
1176
+ );
1177
+ const AuthoringWorkflowSchema = pinned()(
1178
+ v.strictObject(workflowFields(AuthoringFieldEntrySchema, AuthoringStageSchema))
538
1179
  ), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
539
1180
  function isStartableDefinition(definition) {
540
- return definition.role !== "child";
1181
+ return definition.lifecycle !== "child";
541
1182
  }
542
1183
  function formatValidationError(label, issues) {
543
1184
  const lines = issues.map((issue) => ` - ${issue.path.length === 0 ? "(root)" : formatPath(issue.path)}: ${issue.message}`);
@@ -559,26 +1200,75 @@ function formatPath(path) {
559
1200
  }
560
1201
  export {
561
1202
  ACTIVITY_KINDS,
1203
+ ACTOR_KINDS,
562
1204
  AuthoringActionSchema,
563
1205
  AuthoringActivitySchema,
564
1206
  AuthoringFieldEntrySchema,
1207
+ AuthoringGuardSchema,
565
1208
  AuthoringOpSchema,
566
1209
  AuthoringStageSchema,
567
1210
  AuthoringTransitionSchema,
568
1211
  AuthoringWorkflowSchema,
1212
+ CONDITION_VARS,
1213
+ ContractViolationError,
569
1214
  DOCUMENT_VALUE_PERMISSIONS,
570
1215
  DRIVER_KINDS,
1216
+ DefinitionInUseError,
1217
+ DefinitionNotFoundError,
1218
+ EFFECTS_READ,
1219
+ EffectNotFoundError,
571
1220
  EffectSchema,
1221
+ FIELD_READ,
1222
+ FILTER_SCOPE_VARS,
1223
+ FieldValueShapeError,
572
1224
  GROQ_IDENTIFIER,
573
- GuardSchema,
1225
+ GUARD_PREDICATE_VARS,
1226
+ InstanceNotFoundError,
1227
+ RESERVED_CONDITION_VARS,
1228
+ RESOURCE_ALIAS_NAME_SOURCE,
1229
+ StoredFieldOpSchema,
574
1230
  WORKFLOW_DEFINITION_TYPE,
1231
+ WorkflowConfigSchema,
1232
+ WorkflowError,
575
1233
  actorFulfillsRole,
576
1234
  andConditions,
1235
+ checkFieldValue,
1236
+ datasetResourceParts,
1237
+ definitionDocId,
577
1238
  expandRequiredRoles,
1239
+ extractDocumentId,
1240
+ formatIssues,
578
1241
  formatValidationError,
1242
+ gdrFromResource,
1243
+ gdrRef,
1244
+ gdrResourcePrefix,
1245
+ gdrUri,
1246
+ isBareSeedId,
1247
+ isGdr,
1248
+ isGdrUri,
1249
+ isGuardReadExpr,
579
1250
  isStartableDefinition,
580
1251
  isTerminalActivityStatus,
581
1252
  issuesFromValibot,
582
- normalizeRoleAliases
1253
+ normalizeRoleAliases,
1254
+ parseGdr,
1255
+ printGuardRead,
1256
+ refCanvas,
1257
+ refDashboard,
1258
+ refDataset,
1259
+ refMediaLibrary,
1260
+ resourceAliasesToMap,
1261
+ resourceFromGdrUri,
1262
+ resourceFromParsed,
1263
+ sameResource,
1264
+ selfGdr,
1265
+ tagScopeFilter,
1266
+ toBareId,
1267
+ toPhysicalGdr,
1268
+ tryParseGdr,
1269
+ validateFieldAppendItem,
1270
+ validateFieldValue,
1271
+ validateResourceAliasName,
1272
+ validateTag
583
1273
  };
584
1274
  //# sourceMappingURL=schema.js.map