@sanity/workflow-engine 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,117 @@
1
1
  # @sanity/workflow-engine
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ab5e454: Export project-user response validation for host adapters that consume Sanity project-user APIs.
8
+ - e3122cc: New `instancesGuardQuery(instanceIds)` — the set-shaped guard filter (`sourceInstanceId in $instanceIds`, ordered by `_id`) reactive adapters subscribe as one shared live query per resource for every co-mounted session. `instanceGuardQuery(id)` delegates to it, so the per-instance and per-set reads share one filter definition; its compiled query string changes accordingly (same rows selected).
9
+ - 7c4dd86: Reduce guard lifecycle request volume and chunk orphan cleanup transactions.
10
+
11
+ ### Patch Changes
12
+
13
+ - bce09fc: Restore automatic effect request tags while preserving concrete handler client APIs, backed by native request-prefix support in the test client.
14
+ - efb4cd9: Discriminator validation now reports an unknown field kind, field source, or op value expression at its exact `type` path instead of collapsing to "Invalid type: Expected Object but received Object". The schemas route on `type` rather than trying indistinguishable object arms in a plain union, so newer syntax used with an older engine identifies the unsupported value and the installed engine's valid options.
15
+ - 46b0285: **BREAKING:** Replace manually declared document applicability with automatic
16
+ discovery from deployed first-class subject fields.
17
+
18
+ Update Studio configuration as follows:
19
+ - `mappings` is optional. Omit it when every applicable definition declares a
20
+ first-class subject. A mapping may customize an automatically discovered
21
+ `(docType, definition)` binding or explicitly register a definition modeled
22
+ with a plain `doc.ref` instead of a first-class subject.
23
+ - Multiple workflows may target one document type. Use one mapping row for each
24
+ distinct `(docType, definition)` pair. An exact duplicate pair is a
25
+ configuration error rather than a last-row-wins override.
26
+ - Remove the top-level `autoStart` map or function. Put `autoStart: true` on
27
+ each mapping row that should start automatically. Configure workspace-specific
28
+ behavior in that workspace's mapping rows. This also works for explicitly
29
+ registered definitions using the `doc.ref` field named `subject` convention.
30
+ - Replace `workflowDefaultDocumentNode({mappings})` with
31
+ `workflowDefaultDocumentNode()`.
32
+
33
+ Before:
34
+
35
+ ```ts
36
+ structureTool({defaultDocumentNode: workflowDefaultDocumentNode({mappings})})
37
+ workflowStudioPlugin({
38
+ tag: 'production',
39
+ mappings,
40
+ autoStart: {article: ['article-review', 'legal-review']},
41
+ })
42
+ ```
43
+
44
+ After:
45
+
46
+ ```ts
47
+ structureTool({defaultDocumentNode: workflowDefaultDocumentNode()})
48
+ workflowStudioPlugin({
49
+ tag: 'production',
50
+ mappings: [
51
+ {
52
+ docType: 'article',
53
+ definition: 'article-review',
54
+ label: 'Article review',
55
+ autoStart: true,
56
+ },
57
+ {
58
+ docType: 'article',
59
+ definition: 'legal-review',
60
+ label: 'Legal review',
61
+ autoStart: true,
62
+ },
63
+ ],
64
+ })
65
+ ```
66
+
67
+ Also remove all per-schema Editorial Workflows preview wiring:
68
+ - Remove `components: {preview: WorkflowStagePreview}`.
69
+ - Remove `_id` added only for Editorial Workflows from `preview.select` and
70
+ stop passing it through `preview.prepare`.
71
+ - Remove imports of `WorkflowStagePreview`; that component is no longer a
72
+ public package export.
73
+ - Remove imports of `mappingForDocType` and `workflowDocTypes`; effective
74
+ mappings are resolved inside the plugin and those host-side helpers are no
75
+ longer exported.
76
+ - Keep each schema's native inferred or custom preview unchanged. The plugin
77
+ now installs preview middleware itself, consumes the document identity Studio
78
+ already supplies, and delegates title, subtitle, media, and custom preview
79
+ composition through `renderDefault`.
80
+
81
+ Workflow stage pills appear on Studio surfaces that invoke preview middleware,
82
+ including reference and array-item previews. Custom preview components that
83
+ replace Studio's layout must render the `status` prop they receive. Studio's
84
+ Structure document-list rows bypass both plugin and schema preview middleware,
85
+ so they do not show workflow stage pills. Workflow status remains available in
86
+ the document form, footer badge, Workflows view, and Workflows tool.
87
+
88
+ Document-reference GDRs for dataset content now reject stored draft IDs
89
+ (`drafts.<id>`) and Content Release version IDs
90
+ (`versions.<release>.<id>`). Use the stable document ID in the GDR and select
91
+ the draft or release through workflow perspective instead. The validation error
92
+ includes the corresponding stable ID so CLI and API callers can correct the
93
+ input before an unresolvable workflow instance is created.
94
+
95
+ Previously persisted draft/version GDRs remain invalid workflow identities.
96
+ Read-side Studio displays now tolerate them by showing the raw URI instead of
97
+ crashing, and query-sourced occurrences are discarded through the existing
98
+ fail-soft field-resolution path. Correct existing data by starting a new
99
+ instance with the stable document ID; perspective selects the desired draft or
100
+ release content.
101
+
102
+ - 8a76c67: Add incremental reactive-session document feeds, coalesce watched-document emissions, and reuse discovery derivations and Studio layout output. Deleted watched documents now leave the held snapshot instead of remaining as stale evaluation input. The component test runner also has aggregate-suite timeout headroom.
103
+ - 7e8f459: Batch snapshot hydration to reduce watched-document read latency.
104
+ - 98e488e: Stabilize Editorial Workflows discovery subscriptions and limit full reactive sessions to surfaces that need live evaluation.
105
+
106
+ ## 0.19.0
107
+
108
+ ### Minor Changes
109
+
110
+ - 24b11dd: Effect handlers can report intermediate field state mid-dispatch. The handler context gains `ctx.commitOps({ops, idempotencyKey})` — field ops only, validated like completion ops, executed through the new `workflow.commitEffectOps` verb as a standard engine commit (gate, history, idempotency ledger, compare-and-swap persist, guard refresh, cascade) — and `ctx.setProgress(field, value)`, sugar that coalesces pending absolute sets per field. Calls enqueue synchronously into a bounded per-dispatch queue (depth 32, 200 commits per dispatch) that serializes commits in call order; overflow throws synchronously at the call site, and settlement drains the queue before completion so an un-awaited failed report still fails the effect. Pending-effect claims now carry a `claimToken` minted on every claim and takeover: a report from an expired, released, or superseded dispatch is rejected before writing (`StaleEffectClaimError`), and a successful report renews the claim's lease. Completion semantics are unchanged and remain claim-blind first-writer-wins. Each report emits the sampled `Editorial Workflows Effect State Reported` telemetry event (adoption signal, max one per minute). Persisted-model note: the claim token rides persisted model 3 additively (see `DATAMODEL.md`).
111
+ - 2005ab7: Action evaluations carry a `firing` consequence (`FiringConsequence` `{exitsStage, transition?}`): what firing the action would do to the flow right now, computed by replaying the fire in memory through the same machinery a real commit runs — ops, the triggered fixpoint, then transition selection — against the projection's state. The answer is total for the current state and conditional on the commit landing; it is omitted where the replay can't be faithful (caller params, a spawn's dataset-driven fan-out in the hop, a terminal instance, a stage visit whose entries have diverged from the pinned definition) and on cascade-fired actions. Advisory, like every derived verdict. Wrong-shape `field.updateWhere` aborts (a merge resolving to a non-object; a resolved merge carrying reserved row keys) now throw the typed `FieldValueShapeError` on the commit path, joining the other wrong-shape write aborts.
112
+ - 24b11dd: Add the `progress` field kind: an elevated `number` carrying application-defined 0–100 completion, so Studio and SDK surfaces can identify and elevate it without guessing which number field means "how far along". Every non-null value must be finite and within 0–100 inclusive (fractions allowed), enforced by one value schema at every write boundary — declaration seeds, start values, edits, ops, nested sub-fields, and effect outputs. Choice lists are rejected on the kind at authoring time. Mints persisted model 3 additively (see `DATAMODEL.md`).
113
+ - 59b40fd: **BREAKING:** a deployment's `name` is now its identity in `defineWorkflowConfig`: enforced unique across the config and constrained to the same grammar as `tag` (lowercase letters, digits, dashes — free text like "My cool workflow" is rejected). Tags may now repeat across deployments; the enforced storage invariant is instead the `(workflowResource, tag)` pair — two deployments sharing both are rejected with each entry named, since they would write into the same partition and fight over definition versions.
114
+
3
115
  ## 0.18.0
4
116
 
5
117
  ### Minor Changes
package/DATAMODEL.md CHANGED
@@ -445,6 +445,59 @@ Spread-based mutation copies preserve `validation` across every full persist;
445
445
  the derived floor prevents older writers from reaching constrained model-2
446
446
  instances.
447
447
 
448
+ ### Model 3 — the `progress` field kind + effect claim tokens (reader floor: 2)
449
+
450
+ Two additive growths ship together as model 3 — model 2 was released (npm
451
+ `@sanity/workflow-engine@0.18.0`) before either landed, so they mint the next
452
+ version rather than amending a shipped one. Neither raises the reader floor;
453
+ the writer maximum stays 2.
454
+
455
+ **The `progress` field kind.** An elevated `number` (same stored numeric
456
+ value) carrying application-defined 0–100 completion, so surfaces can
457
+ identify and elevate it without guessing which number field means "how far
458
+ along". Every non-null value is finite and within 0–100 inclusive (fractions
459
+ allowed), enforced at every write boundary by the kind's one value schema.
460
+ Model 2's `validation` tree composes with it as NARROWING only: each declared
461
+ bound must itself sit within 0–100, gated at authoring. Both stamped trees
462
+ grow the value:
463
+
464
+ - **Definition tree** — `fields[].type` (and nested `FieldShape.type`)
465
+ admits `'progress'`.
466
+ - **Instance tree** — resolved `fields[]` entries admit `_type: 'progress'`
467
+ (the same arm shape as `number`).
468
+
469
+ Manifest feature: `progress-field-kind` (definition + instance, additive,
470
+ detectable, floor 0).
471
+
472
+ Would an old reader misread the kind (rule 5)? No — the same loud refusal as
473
+ the `subject` kind: an older engine (model 2 and below) that loaded a
474
+ progress-carrying instance fails the strict `fields[]` variant with
475
+ `PersistedDocShapeError` before interpreting anything, and cannot deploy or
476
+ evaluate a progress-declaring definition. No older code path can write a
477
+ progress field while skipping its bound, so there is no silent bypass to
478
+ fence — the kind leaves the floor at 0.
479
+
480
+ **Effect claim tokens.** `pendingEffects[].claim` grows an optional
481
+ `claimToken` — an opaque identifier minted fresh on every claim and
482
+ takeover. It is the exact-claim identity a mid-dispatch state report
483
+ (`commitEffectOps`) must present: `effectKey` cannot serve because a lease
484
+ takeover keeps the same key. A successful report also renews the claim's
485
+ `leaseExpiresAt` in the same commit.
486
+
487
+ Manifest feature: `effect-claim-tokens` (instance, additive, detectable,
488
+ floor 0).
489
+
490
+ Tolerant read: an older engine's claim parse admits unknown keys, so the
491
+ token rides through untouched and means nothing to it — older claim
492
+ semantics (presence + lease) are unchanged. Round-trip survival: full
493
+ persists derive from the complete loaded post-state, so an older writer
494
+ carries the token along; the claim-REPLACING writes (takeover, sweep
495
+ release, completion drain) discard it deliberately, which is the token's
496
+ own lifecycle — a report presenting a discarded token is rejected before
497
+ writing, failing closed. A claim without a token (older writer's takeover)
498
+ simply cannot authorise mid-dispatch reports; dispatch and completion are
499
+ unaffected.
500
+
448
501
  ## Pending governed changes
449
502
 
450
503
  - **`temp.system.guard` → `system.guard`** — the guard doc type's `temp.`
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- var v = require("valibot"), groqConditionDescribe = require("@sanity/groq-condition-describe"), groqJs = require("groq-js");
3
+ var v = require("valibot"), groqConditionDescribe = require("@sanity/groq-condition-describe"), groqJs = require("groq-js"), idUtils = require("@sanity/id-utils");
4
4
 
5
5
  function _interopNamespaceCompat(e) {
6
6
  if (e && typeof e == "object" && "default" in e) return e;
@@ -129,7 +129,7 @@ function parentRef(instance) {
129
129
  return instance.ancestors.at(-1);
130
130
  }
131
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";
132
+ const DATA_MODEL_VERSION = 3, DATA_MODEL_MIN_READER = 2, READER_MODEL_ROLLOUT_URL = "https://github.com/sanity-io/workflows/blob/main/docs/reader-model-rollout.md";
133
133
 
134
134
  class ReaderModelAcknowledgementError extends WorkflowError {
135
135
  code="WORKFLOW_READER_MODEL_ACKNOWLEDGEMENT_MISMATCH";
@@ -188,6 +188,22 @@ const DATA_MODEL_CHANGES = Object.freeze([ Object.freeze({
188
188
  compatibility: "reader-floor",
189
189
  applicability: "detectable",
190
190
  summary: "String, text, and number values may carry persisted inclusive bounds."
191
+ }), Object.freeze({
192
+ id: "progress-field-kind",
193
+ introducedInModel: 3,
194
+ minReaderModel: 0,
195
+ documentTypes: Object.freeze([ "definition", "instance" ]),
196
+ compatibility: "additive",
197
+ applicability: "detectable",
198
+ summary: "A progress field kind carries application-defined 0–100 completion."
199
+ }), Object.freeze({
200
+ id: "effect-claim-tokens",
201
+ introducedInModel: 3,
202
+ minReaderModel: 0,
203
+ documentTypes: Object.freeze([ "instance" ]),
204
+ compatibility: "additive",
205
+ applicability: "detectable",
206
+ summary: "Pending-effect claims carry an exact-claim token gating mid-dispatch state reports."
191
207
  }) ]);
192
208
 
193
209
  function recordOf(value) {
@@ -224,8 +240,8 @@ function hasChoiceList(document) {
224
240
  });
225
241
  }
226
242
 
227
- function hasSubjectField(document) {
228
- return persistedFieldEntries(document).some(entry => entry.type === "subject" || entry._type === "subject");
243
+ function hasFieldKind(document, kind) {
244
+ return persistedFieldEntries(document).some(entry => entry.type === kind || entry._type === kind);
229
245
  }
230
246
 
231
247
  function hasActionSemantics(document) {
@@ -240,12 +256,22 @@ function hasScalarValidation(document) {
240
256
  });
241
257
  }
242
258
 
259
+ function hasClaimTokens(document) {
260
+ const root = recordOf(document);
261
+ return root === void 0 ? !1 : recordsAt(root, "pendingEffects").some(entry => {
262
+ const claim = recordOf(entry.claim);
263
+ return claim !== void 0 && typeof claim.claimToken == "string";
264
+ });
265
+ }
266
+
243
267
  const featureDetectors = {
244
268
  "governed-model-stamps": () => !0,
245
- "subject-field-kind": hasSubjectField,
269
+ "subject-field-kind": document => hasFieldKind(document, "subject"),
246
270
  "typed-scalar-choice-lists": hasChoiceList,
247
271
  "action-semantics": hasActionSemantics,
248
- "inclusive-scalar-bounds": hasScalarValidation
272
+ "inclusive-scalar-bounds": hasScalarValidation,
273
+ "progress-field-kind": document => hasFieldKind(document, "progress"),
274
+ "effect-claim-tokens": hasClaimTokens
249
275
  };
250
276
 
251
277
  function requiredModelFeatures(documentType, document) {
@@ -332,6 +358,21 @@ function andConditions(parts) {
332
358
 
333
359
  const KNOWN_SCHEMES = /* @__PURE__ */ new Set([ "dataset", "canvas", "media-library", "dashboard" ]), KNOWN_SCHEMES_TEXT = [ ...KNOWN_SCHEMES ].join(", ");
334
360
 
361
+ class VersionSpecificDatasetGdrError extends Error {
362
+ documentId;
363
+ stableDocumentId;
364
+ constructor(documentId, uri) {
365
+ const stableDocumentId = idUtils.getPublishedId(documentId);
366
+ super(`Invalid GDR "${uri}": dataset document ID "${documentId}" identifies a stored draft or release version. Use the stable document ID "${stableDocumentId}" and select drafts/releases through the workflow perspective.`),
367
+ this.name = "VersionSpecificDatasetGdrError", this.documentId = documentId, this.stableDocumentId = stableDocumentId;
368
+ }
369
+ }
370
+
371
+ function assertStableDatasetDocumentId(documentId, uri) {
372
+ const sanityId = documentId;
373
+ if (!(!idUtils.isDraftId(sanityId) && !idUtils.isVersionId(sanityId))) throw new VersionSpecificDatasetGdrError(documentId, uri);
374
+ }
375
+
335
376
  function parseGdr(uri) {
336
377
  const colon = uri.indexOf(":");
337
378
  if (colon < 0) throw new Error(`Invalid GDR "${uri}": must be a URI of form "<scheme>:<...id-parts>". Known schemes: ${KNOWN_SCHEMES_TEXT}.`);
@@ -341,11 +382,12 @@ function parseGdr(uri) {
341
382
  if (parts.some(part => part.length === 0)) throw new Error(`Invalid GDR "${uri}": id parts must be non-empty (no leading, trailing, or doubled ":").`);
342
383
  if (scheme === "dataset") {
343
384
  if (parts.length !== 3) throw new Error(`Invalid GDR "${uri}": dataset scheme requires <projectId>:<dataset>:<documentId> (3 parts after scheme); got ${parts.length}.`);
344
- return {
385
+ const documentId = parts[2];
386
+ return assertStableDatasetDocumentId(documentId, uri), {
345
387
  scheme: "dataset",
346
388
  projectId: parts[0],
347
389
  dataset: parts[1],
348
- documentId: parts[2]
390
+ documentId: documentId
349
391
  };
350
392
  }
351
393
  if (parts.length !== 2) throw new Error(`Invalid GDR "${uri}": ${scheme} scheme requires <resourceId>:<documentId> (2 parts after scheme); got ${parts.length}.`);
@@ -365,7 +407,11 @@ function tryParseGdr(uri) {
365
407
  }
366
408
 
367
409
  function gdrUri(parts) {
368
- return parts.scheme === "dataset" ? `dataset:${parts.projectId}:${parts.dataset}:${parts.documentId}` : `${parts.scheme}:${parts.resourceId}:${parts.documentId}`;
410
+ if (parts.scheme === "dataset") {
411
+ const uri = `dataset:${parts.projectId}:${parts.dataset}:${parts.documentId}`;
412
+ return assertStableDatasetDocumentId(parts.documentId, uri), uri;
413
+ }
414
+ return `${parts.scheme}:${parts.resourceId}:${parts.documentId}`;
369
415
  }
370
416
 
371
417
  function extractDocumentId(gdrUriString) {
@@ -547,7 +593,13 @@ function asPredicate(validate) {
547
593
  };
548
594
  }
549
595
 
550
- const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts), WorkflowResourceSchema = v__namespace.variant("type", [ v__namespace.object({
596
+ const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts);
597
+
598
+ function lakeSegment(label) {
599
+ return v__namespace.pipe(v__namespace.string(), v__namespace.nonEmpty(), v__namespace.check(isValidTag, `invalid ${label} — ${LAKE_ID_SEGMENT_GLOSS}`));
600
+ }
601
+
602
+ const WorkflowResourceSchema = v__namespace.variant("type", [ v__namespace.object({
551
603
  type: v__namespace.literal("dataset"),
552
604
  id: v__namespace.pipe(NonEmptyString$1, v__namespace.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
553
605
  }), v__namespace.object({
@@ -563,14 +615,46 @@ const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(vali
563
615
  name: v__namespace.pipe(NonEmptyString$1, v__namespace.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
564
616
  resource: WorkflowResourceSchema
565
617
  }), 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({
566
- name: NonEmptyString$1,
618
+ name: lakeSegment("name"),
567
619
  expectedMinReaderModel: v__namespace.optional(v__namespace.custom(() => !0), void 0),
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")),
620
+ tag: lakeSegment("tag"),
569
621
  workflowResource: WorkflowResourceSchema,
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"))),
622
+ resourceAliases: v__namespace.optional(v__namespace.pipe(v__namespace.array(ResourceBindingSchema), v__namespace.check(bindings => duplicateHandleMessage(bindings) === void 0, issue => duplicateHandleMessage(issue.input) ?? "duplicate resource handle name"))),
571
623
  definitions: v__namespace.pipe(v__namespace.array(DefinitionSchema), v__namespace.minLength(1, "a deployment needs at least one definition"))
572
- }), TelemetryLoggerSchema = v__namespace.custom(input => typeof input == "object" && input !== null && typeof input.log == "function", "expected a telemetry logger (an object with a `log` function)"), WorkflowConfigSchema = v__namespace.object({
573
- deployments: v__namespace.pipe(v__namespace.array(DeploymentSchema), v__namespace.minLength(1, "a config needs at least one deployment"), v__namespace.check(deployments => new Set(deployments.map(deployment => deployment.tag)).size === deployments.length, "duplicate deployment tag — each deployment must use a unique tag")),
624
+ });
625
+
626
+ function firstDuplicatePair(items, keyOf) {
627
+ const seen = /* @__PURE__ */ new Map;
628
+ for (const item of items) {
629
+ const key = keyOf(item), earlier = seen.get(key);
630
+ if (earlier !== void 0) return [ earlier, item ];
631
+ seen.set(key, item);
632
+ }
633
+ }
634
+
635
+ function duplicateHandleMessage(bindings) {
636
+ const pair = firstDuplicatePair(bindings, binding => binding.name);
637
+ if (pair !== void 0) return `duplicate resource handle name "${pair[1].name}" — each binding name must be unique within a deployment`;
638
+ }
639
+
640
+ function duplicateNameMessage(deployments) {
641
+ const pair = firstDuplicatePair(deployments, deployment => deployment.name);
642
+ if (pair !== void 0) return `duplicate deployment name "${pair[1].name}" — each deployment must use a unique name`;
643
+ }
644
+
645
+ function partitionKey(deployment) {
646
+ return `${resourceGdr(deployment.workflowResource)} ${deployment.tag}`;
647
+ }
648
+
649
+ function partitionCollisionMessage(deployments) {
650
+ const pair = firstDuplicatePair(deployments, partitionKey);
651
+ if (pair === void 0) return;
652
+ const [first, second] = pair;
653
+ return `deployments "${first.name}" and "${second.name}" share workflow resource + tag "${second.tag}" — both would write into the same partition; change one deployment’s tag or resource`;
654
+ }
655
+
656
+ const TelemetryLoggerSchema = v__namespace.custom(input => typeof input == "object" && input !== null && typeof input.log == "function", "expected a telemetry logger (an object with a `log` function)"), WorkflowConfigSchema = v__namespace.object({
657
+ deployments: v__namespace.pipe(v__namespace.array(DeploymentSchema), v__namespace.minLength(1, "a config needs at least one deployment"), v__namespace.check(deployments => duplicateNameMessage(deployments) === void 0, issue => duplicateNameMessage(issue.input) ?? "duplicate deployment name"), v__namespace.check(deployments => partitionCollisionMessage(deployments) === void 0, issue => partitionCollisionMessage(issue.input) ?? "duplicate deployment partition")),
574
658
  telemetry: v__namespace.optional(TelemetryLoggerSchema)
575
659
  });
576
660
 
@@ -1724,7 +1808,7 @@ const GdrUriSchema = v__namespace.custom(s => typeof s == "string" && isGdrUri(s
1724
1808
  }), tolerantObject()({
1725
1809
  type: v__namespace.literal("role"),
1726
1810
  role: NonEmptyString
1727
- }) ]), NullableString = v__namespace.union([ v__namespace.null(), v__namespace.string() ]), NullableNumber = v__namespace.union([ v__namespace.null(), v__namespace.number() ]), NullableBoolean = v__namespace.union([ v__namespace.null(), v__namespace.boolean() ]), NullableDateTime = v__namespace.union([ v__namespace.null(), IsoTimestamp ]), NullableDate = v__namespace.union([ v__namespace.null(), v__namespace.pipe(v__namespace.string(), v__namespace.regex(/^\d{4}-\d{2}-\d{2}$/, "must be a `YYYY-MM-DD` date")) ]), NullableUrl = NullableString, CHOICE_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "url", "date", "datetime", "dateTime" ]);
1811
+ }) ]), 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() ]), NullableProgress = v__namespace.union([ v__namespace.null(), v__namespace.pipe(v__namespace.number(), v__namespace.finite("progress must be a finite number"), v__namespace.minValue(0, "progress must be at least 0"), v__namespace.maxValue(100, "progress must be at most 100")) ]), 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
1812
 
1729
1813
  function normalizedChoiceKind(kind) {
1730
1814
  return kind === "dateTime" ? "datetime" : kind;
@@ -1756,6 +1840,7 @@ const fieldValueSchemas = {
1756
1840
  string: NullableString,
1757
1841
  text: NullableString,
1758
1842
  number: NullableNumber,
1843
+ progress: NullableProgress,
1759
1844
  boolean: NullableBoolean,
1760
1845
  date: NullableDate,
1761
1846
  datetime: NullableDateTime,
@@ -1780,13 +1865,13 @@ function shapeValueSchema(shape, leaf) {
1780
1865
  }
1781
1866
 
1782
1867
  function scalarMeasurement(entryType, value) {
1783
- if (entryType === "number" && typeof value == "number") return value;
1868
+ if ((entryType === "number" || entryType === "progress") && typeof value == "number") return value;
1784
1869
  if ((entryType === "string" || entryType === "text") && typeof value == "string") return value.length;
1785
1870
  }
1786
1871
 
1787
1872
  function scalarBoundIssue(args) {
1788
1873
  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}`;
1874
+ return bound === void 0 || limit === "min" && measured >= bound || limit === "max" && measured <= bound ? void 0 : `${entryType === "number" || entryType === "progress" ? "" : "length "}must be ${limit === "min" ? "greater than or equal to" : "less than or equal to"} ${bound}`;
1790
1875
  }
1791
1876
 
1792
1877
  function scalarValidationIssues(args) {
@@ -1964,7 +2049,15 @@ function groqIdentifier(referencedAs) {
1964
2049
  }
1965
2050
 
1966
2051
  function picklist(options) {
1967
- return v__namespace.picklist(options, `Invalid option: expected one of ${options.map(o => `"${o}"`).join("|")}`);
2052
+ return v__namespace.picklist(options, invalidOptionMessage(options));
2053
+ }
2054
+
2055
+ function invalidOptionMessage(options) {
2056
+ return `Invalid option: expected one of ${options.map(option => `"${option}"`).join("|")}`;
2057
+ }
2058
+
2059
+ function exhaustiveOptions() {
2060
+ return options => options;
1968
2061
  }
1969
2062
 
1970
2063
  function pinned() {
@@ -1979,12 +2072,12 @@ const LiteralSchema = v__namespace.strictObject({
1979
2072
  scope: v__namespace.optional(v__namespace.union([ v__namespace.literal("workflow"), v__namespace.literal("stage") ])),
1980
2073
  field: NonEmpty,
1981
2074
  path: v__namespace.optional(v__namespace.string())
1982
- }), FieldSourceSchema = v__namespace.union([ v__namespace.strictObject({
2075
+ }), FieldSourceSchema = v__namespace.variant("type", [ v__namespace.strictObject({
1983
2076
  type: v__namespace.literal("input")
1984
2077
  }), v__namespace.strictObject({
1985
2078
  type: v__namespace.literal("query"),
1986
2079
  query: NonEmpty
1987
- }), LiteralSchema, FieldReadSchema ]), ValueExprSchema = v__namespace.lazy(() => v__namespace.union([ LiteralSchema, FieldReadSchema, v__namespace.strictObject({
2080
+ }), LiteralSchema, FieldReadSchema ]), ValueExprSchema = v__namespace.lazy(() => v__namespace.variant("type", [ LiteralSchema, FieldReadSchema, v__namespace.strictObject({
1988
2081
  type: v__namespace.literal("param"),
1989
2082
  param: NonEmpty
1990
2083
  }), v__namespace.strictObject({
@@ -2079,7 +2172,7 @@ function groupMembershipNames(group) {
2079
2172
  return group === void 0 ? [] : typeof group == "string" ? [ group ] : [ ...group ];
2080
2173
  }
2081
2174
 
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({
2175
+ const FIELD_VALUE_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref", "string", "text", "number", "progress", "boolean", "date", "datetime", "url", "actor", "assignee", "assignees", "object", "array" ], FieldValueKindSchema = picklist(FIELD_VALUE_KINDS), FieldKindSchema = picklist(FIELD_VALUE_KINDS), AUTHORING_FIELD_SUGAR_KINDS = exhaustiveOptions()([ "claim", "todoList", "notes" ]), AUTHORING_FIELD_KINDS = [ ...FIELD_VALUE_KINDS, ...AUTHORING_FIELD_SUGAR_KINDS ], AuthoringRawFieldKindSchema = v__namespace.picklist(FIELD_VALUE_KINDS, issue => `${invalidOptionMessage(AUTHORING_FIELD_KINDS)} but received ${JSON.stringify(issue.input)}`), 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
2176
  min: v__namespace.optional(FiniteNumber),
2084
2177
  max: v__namespace.optional(FiniteNumber)
2085
2178
  }), 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({
@@ -2144,9 +2237,9 @@ function fieldBase(editable, group) {
2144
2237
  };
2145
2238
  }
2146
2239
 
2147
- function fieldEntryFields(editable, group) {
2240
+ function fieldEntryFields({editable: editable, group: group, kind: kind = FieldKindSchema}) {
2148
2241
  return {
2149
- type: FieldKindSchema,
2242
+ type: kind,
2150
2243
  ...fieldBase(editable, group),
2151
2244
  options: v__namespace.optional(ChoiceOptionsSchema),
2152
2245
  validation: v__namespace.optional(ScalarValidationSchema),
@@ -2188,7 +2281,7 @@ function choiceOptionsCheck() {
2188
2281
  }) ?? []).join("; "));
2189
2282
  }
2190
2283
 
2191
- const SCALAR_VALIDATION_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number" ]);
2284
+ const SCALAR_VALIDATION_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "progress" ]);
2192
2285
 
2193
2286
  function scalarValidationCheck() {
2194
2287
  return v__namespace.check(entry => scalarValidationDeclarationIssues(entry) === void 0, issue => (scalarValidationDeclarationIssues(issue.input) ?? []).join("; "));
@@ -2197,13 +2290,24 @@ function scalarValidationCheck() {
2197
2290
  function scalarValidationDeclarationIssues(entry) {
2198
2291
  const {type: type, validation: validation} = entry;
2199
2292
  if (validation === void 0) return;
2200
- if (!SCALAR_VALIDATION_KINDS.has(type)) return [ `\`validation\` is only valid on \`string\` / \`text\` / \`number\` values, not "${type}"` ];
2293
+ if (!SCALAR_VALIDATION_KINDS.has(type)) return [ `\`validation\` is only valid on \`string\` / \`text\` / \`number\` / \`progress\` values, not "${type}"` ];
2294
+ if (type === "progress") {
2295
+ const issues2 = Object.entries(validation).flatMap(([bound, value]) => typeof value == "number" && value >= 0 && value <= 100 ? [] : [ `\`validation.${bound}\` must stay within the progress kind's 0–100 contract` ]);
2296
+ return issues2.length === 0 ? void 0 : issues2;
2297
+ }
2201
2298
  if (type === "number") return;
2202
2299
  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
2300
  return issues.length === 0 ? void 0 : issues;
2204
2301
  }
2205
2302
 
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({
2303
+ const FieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields({
2304
+ editable: StoredEditableSchema,
2305
+ group: StoredGroupMembershipSchema
2306
+ })), refTypesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v__namespace.pipe(compositeChecked(fieldEntryFields({
2307
+ editable: AuthoringEditableSchema,
2308
+ group: AuthoringGroupMembershipSchema,
2309
+ kind: AuthoringRawFieldKindSchema
2310
+ })), refTypesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), ClaimFieldSchema = pinned()(v__namespace.strictObject({
2207
2311
  type: v__namespace.literal("claim"),
2208
2312
  name: FieldEntryName,
2209
2313
  title: v__namespace.optional(v__namespace.string()),
@@ -2218,7 +2322,10 @@ function listSugarFields(type) {
2218
2322
  };
2219
2323
  }
2220
2324
 
2221
- const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("todoList"))), NotesFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("notes"))), AuthoringFieldEntrySchema = pinned()(v__namespace.union([ RawAuthoringFieldEntrySchema, ClaimFieldSchema, TodoListFieldSchema, NotesFieldSchema ])), EffectSchema = v__namespace.strictObject({
2325
+ const TodoListFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("todoList"))), NotesFieldSchema = pinned()(v__namespace.strictObject(listSugarFields("notes"))), AuthoringFieldEntrySchema = pinned()(v__namespace.lazy(input => {
2326
+ const type = asShape(input).type;
2327
+ return type === "claim" ? ClaimFieldSchema : type === "todoList" ? TodoListFieldSchema : type === "notes" ? NotesFieldSchema : RawAuthoringFieldEntrySchema;
2328
+ })), EffectSchema = v__namespace.strictObject({
2222
2329
  name: NonEmpty,
2223
2330
  title: v__namespace.optional(v__namespace.string()),
2224
2331
  description: v__namespace.optional(v__namespace.string()),
@@ -3608,6 +3715,7 @@ const SCALAR = {
3608
3715
  string: () => SCALAR,
3609
3716
  text: () => SCALAR,
3610
3717
  number: () => SCALAR,
3718
+ progress: () => SCALAR,
3611
3719
  boolean: () => SCALAR,
3612
3720
  date: () => SCALAR,
3613
3721
  datetime: () => SCALAR,
@@ -3811,6 +3919,8 @@ exports.SpawnContractsInvalidError = SpawnContractsInvalidError;
3811
3919
 
3812
3920
  exports.StoredFieldOpSchema = StoredFieldOpSchema;
3813
3921
 
3922
+ exports.VersionSpecificDatasetGdrError = VersionSpecificDatasetGdrError;
3923
+
3814
3924
  exports.WORKFLOW_DEFINITION_TYPE = WORKFLOW_DEFINITION_TYPE;
3815
3925
 
3816
3926
  exports.WORKFLOW_INSTANCE_TYPE = WORKFLOW_INSTANCE_TYPE;