@sanity/workflow-engine 0.19.0 → 0.21.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 +196 -0
- package/DATAMODEL.md +168 -0
- package/dist/_chunks-cjs/invariants.cjs +275 -94
- package/dist/_chunks-es/invariants.js +264 -89
- package/dist/define.d.cts +58 -40
- package/dist/define.d.ts +58 -40
- package/dist/index.cjs +1940 -901
- package/dist/index.d.cts +524 -236
- package/dist/index.d.ts +524 -236
- package/dist/index.js +1928 -911
- package/package.json +3 -2
|
@@ -4,6 +4,8 @@ import { conditionOutcome, runGroq as runGroq$1, evaluateConditionOutcome as eva
|
|
|
4
4
|
|
|
5
5
|
import { parse } from "groq-js";
|
|
6
6
|
|
|
7
|
+
import { isDraftId, isVersionId, getPublishedId } from "@sanity/id-utils";
|
|
8
|
+
|
|
7
9
|
class WorkflowError extends Error {
|
|
8
10
|
kind;
|
|
9
11
|
constructor(kind, message, options) {
|
|
@@ -99,12 +101,33 @@ function isUnprimed(instance) {
|
|
|
99
101
|
|
|
100
102
|
function parseDefinitionSnapshotValue(instance) {
|
|
101
103
|
try {
|
|
102
|
-
return JSON.parse(instance.definitionSnapshot);
|
|
104
|
+
return normalizeLegacyActivityRequirements(JSON.parse(instance.definitionSnapshot));
|
|
103
105
|
} catch (err) {
|
|
104
106
|
rethrowWithContext(err, `Failed to parse definitionSnapshot on instance "${instance._id}"`);
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
function normalizeLegacyActivityRequirements(value) {
|
|
111
|
+
for (const stage of arrayMember(value, "stages")) for (const activity of arrayMember(stage, "activities")) normalizeLegacyRequirementMap(activity);
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function arrayMember(value, key) {
|
|
116
|
+
if (typeof value != "object" || value === null) return [];
|
|
117
|
+
const member = value[key];
|
|
118
|
+
return Array.isArray(member) ? member : [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeLegacyRequirementMap(value) {
|
|
122
|
+
if (typeof value != "object" || value === null) return;
|
|
123
|
+
const activity = value, requirements = activity.requirements;
|
|
124
|
+
typeof requirements != "object" || requirements === null || Array.isArray(requirements) || (activity.requirements = Object.entries(requirements).map(([name, query]) => ({
|
|
125
|
+
type: "groq",
|
|
126
|
+
name: name,
|
|
127
|
+
query: query
|
|
128
|
+
})));
|
|
129
|
+
}
|
|
130
|
+
|
|
108
131
|
function parseDefinitionSnapshot(instance) {
|
|
109
132
|
return parseDefinitionSnapshotValue(instance);
|
|
110
133
|
}
|
|
@@ -113,7 +136,7 @@ function parentRef(instance) {
|
|
|
113
136
|
return instance.ancestors.at(-1);
|
|
114
137
|
}
|
|
115
138
|
|
|
116
|
-
const DATA_MODEL_VERSION =
|
|
139
|
+
const DATA_MODEL_VERSION = 4, DATA_MODEL_MIN_READER = 4, READER_MODEL_ROLLOUT_URL = "https://github.com/sanity-io/workflows/blob/main/docs/reader-model-rollout.md";
|
|
117
140
|
|
|
118
141
|
class ReaderModelAcknowledgementError extends WorkflowError {
|
|
119
142
|
code="WORKFLOW_READER_MODEL_ACKNOWLEDGEMENT_MISMATCH";
|
|
@@ -123,7 +146,7 @@ class ReaderModelAcknowledgementError extends WorkflowError {
|
|
|
123
146
|
documentationUrl=READER_MODEL_ROLLOUT_URL;
|
|
124
147
|
constructor(expectedMinReaderModel, context = "Deployment") {
|
|
125
148
|
const expected = expectedMinReaderModel === void 0 ? "missing" : String(expectedMinReaderModel);
|
|
126
|
-
super("reader-model-acknowledgement", `${context} expected reader floor ${expected}; the installed engine requires acknowledgement ${DATA_MODEL_MIN_READER}. Dependency upgrades can change writer compatibility. Upgrade all readers and Functions before accepting a higher floor; after verifying the rollout, change the literal in deployment configuration. ${READER_MODEL_ROLLOUT_URL}`),
|
|
149
|
+
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. Instances written before the new floor may stop matching user-identity queries, and older readers refuse documents this engine touches — a prerelease deployment with no data worth migrating can reset every engine-owned document with \`sanity-workflows nuke\` and start fresh. ${READER_MODEL_ROLLOUT_URL}`),
|
|
127
150
|
this.name = "ReaderModelAcknowledgementError", this.expectedMinReaderModel = expectedMinReaderModel;
|
|
128
151
|
}
|
|
129
152
|
}
|
|
@@ -188,6 +211,22 @@ const DATA_MODEL_CHANGES = Object.freeze([ Object.freeze({
|
|
|
188
211
|
compatibility: "additive",
|
|
189
212
|
applicability: "detectable",
|
|
190
213
|
summary: "Pending-effect claims carry an exact-claim token gating mid-dispatch state reports."
|
|
214
|
+
}), Object.freeze({
|
|
215
|
+
id: "classified-principal-ids",
|
|
216
|
+
introducedInModel: 4,
|
|
217
|
+
minReaderModel: 4,
|
|
218
|
+
documentTypes: Object.freeze([ "instance" ]),
|
|
219
|
+
compatibility: "reader-floor",
|
|
220
|
+
applicability: "unconditional",
|
|
221
|
+
summary: "Principal ids are namespace-classified: actor and assignee writes carry the account-global user id only, and readers resolve legacy project-scoped ids through the prefix classifier at the instance read funnel."
|
|
222
|
+
}), Object.freeze({
|
|
223
|
+
id: "readiness-requirements",
|
|
224
|
+
introducedInModel: 4,
|
|
225
|
+
minReaderModel: 4,
|
|
226
|
+
documentTypes: Object.freeze([ "definition" ]),
|
|
227
|
+
compatibility: "reader-floor",
|
|
228
|
+
applicability: "detectable",
|
|
229
|
+
summary: "Start and activity readiness use named polymorphic requirement arrays."
|
|
191
230
|
}) ]);
|
|
192
231
|
|
|
193
232
|
function recordOf(value) {
|
|
@@ -248,6 +287,11 @@ function hasClaimTokens(document) {
|
|
|
248
287
|
});
|
|
249
288
|
}
|
|
250
289
|
|
|
290
|
+
function hasReadinessRequirements(document) {
|
|
291
|
+
const root = recordOf(document);
|
|
292
|
+
return root === void 0 ? !1 : Array.isArray(recordOf(root.start)?.requirements) ? !0 : recordsAt(root, "stages").some(stage => recordsAt(stage, "activities").some(activity => Array.isArray(activity.requirements)));
|
|
293
|
+
}
|
|
294
|
+
|
|
251
295
|
const featureDetectors = {
|
|
252
296
|
"governed-model-stamps": () => !0,
|
|
253
297
|
"subject-field-kind": document => hasFieldKind(document, "subject"),
|
|
@@ -255,7 +299,9 @@ const featureDetectors = {
|
|
|
255
299
|
"action-semantics": hasActionSemantics,
|
|
256
300
|
"inclusive-scalar-bounds": hasScalarValidation,
|
|
257
301
|
"progress-field-kind": document => hasFieldKind(document, "progress"),
|
|
258
|
-
"effect-claim-tokens": hasClaimTokens
|
|
302
|
+
"effect-claim-tokens": hasClaimTokens,
|
|
303
|
+
"classified-principal-ids": () => !0,
|
|
304
|
+
"readiness-requirements": hasReadinessRequirements
|
|
259
305
|
};
|
|
260
306
|
|
|
261
307
|
function requiredModelFeatures(documentType, document) {
|
|
@@ -269,7 +315,7 @@ function requiredReaderModel(documentType, document) {
|
|
|
269
315
|
function modelStampFor(args) {
|
|
270
316
|
return {
|
|
271
317
|
modelVersion: DATA_MODEL_VERSION,
|
|
272
|
-
minReaderModel: Math.max(args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
|
|
318
|
+
minReaderModel: Math.max(DATA_MODEL_MIN_READER, args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
|
|
273
319
|
};
|
|
274
320
|
}
|
|
275
321
|
|
|
@@ -342,6 +388,21 @@ function andConditions(parts) {
|
|
|
342
388
|
|
|
343
389
|
const KNOWN_SCHEMES = /* @__PURE__ */ new Set([ "dataset", "canvas", "media-library", "dashboard" ]), KNOWN_SCHEMES_TEXT = [ ...KNOWN_SCHEMES ].join(", ");
|
|
344
390
|
|
|
391
|
+
class VersionSpecificDatasetGdrError extends Error {
|
|
392
|
+
documentId;
|
|
393
|
+
stableDocumentId;
|
|
394
|
+
constructor(documentId, uri) {
|
|
395
|
+
const stableDocumentId = getPublishedId(documentId);
|
|
396
|
+
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.`),
|
|
397
|
+
this.name = "VersionSpecificDatasetGdrError", this.documentId = documentId, this.stableDocumentId = stableDocumentId;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function assertStableDatasetDocumentId(documentId, uri) {
|
|
402
|
+
const sanityId = documentId;
|
|
403
|
+
if (!(!isDraftId(sanityId) && !isVersionId(sanityId))) throw new VersionSpecificDatasetGdrError(documentId, uri);
|
|
404
|
+
}
|
|
405
|
+
|
|
345
406
|
function parseGdr(uri) {
|
|
346
407
|
const colon = uri.indexOf(":");
|
|
347
408
|
if (colon < 0) throw new Error(`Invalid GDR "${uri}": must be a URI of form "<scheme>:<...id-parts>". Known schemes: ${KNOWN_SCHEMES_TEXT}.`);
|
|
@@ -351,11 +412,12 @@ function parseGdr(uri) {
|
|
|
351
412
|
if (parts.some(part => part.length === 0)) throw new Error(`Invalid GDR "${uri}": id parts must be non-empty (no leading, trailing, or doubled ":").`);
|
|
352
413
|
if (scheme === "dataset") {
|
|
353
414
|
if (parts.length !== 3) throw new Error(`Invalid GDR "${uri}": dataset scheme requires <projectId>:<dataset>:<documentId> (3 parts after scheme); got ${parts.length}.`);
|
|
354
|
-
|
|
415
|
+
const documentId = parts[2];
|
|
416
|
+
return assertStableDatasetDocumentId(documentId, uri), {
|
|
355
417
|
scheme: "dataset",
|
|
356
418
|
projectId: parts[0],
|
|
357
419
|
dataset: parts[1],
|
|
358
|
-
documentId:
|
|
420
|
+
documentId: documentId
|
|
359
421
|
};
|
|
360
422
|
}
|
|
361
423
|
if (parts.length !== 2) throw new Error(`Invalid GDR "${uri}": ${scheme} scheme requires <resourceId>:<documentId> (2 parts after scheme); got ${parts.length}.`);
|
|
@@ -375,7 +437,11 @@ function tryParseGdr(uri) {
|
|
|
375
437
|
}
|
|
376
438
|
|
|
377
439
|
function gdrUri(parts) {
|
|
378
|
-
|
|
440
|
+
if (parts.scheme === "dataset") {
|
|
441
|
+
const uri = `dataset:${parts.projectId}:${parts.dataset}:${parts.documentId}`;
|
|
442
|
+
return assertStableDatasetDocumentId(parts.documentId, uri), uri;
|
|
443
|
+
}
|
|
444
|
+
return `${parts.scheme}:${parts.resourceId}:${parts.documentId}`;
|
|
379
445
|
}
|
|
380
446
|
|
|
381
447
|
function extractDocumentId(gdrUriString) {
|
|
@@ -778,8 +844,8 @@ function desugarStart(start) {
|
|
|
778
844
|
...start.filter !== void 0 ? {
|
|
779
845
|
filter: start.filter
|
|
780
846
|
} : {},
|
|
781
|
-
...start.
|
|
782
|
-
|
|
847
|
+
...start.requirements !== void 0 ? {
|
|
848
|
+
requirements: start.requirements
|
|
783
849
|
} : {}
|
|
784
850
|
};
|
|
785
851
|
}
|
|
@@ -1541,7 +1607,7 @@ const ACTION_SEMANTICS = [ "decision.accept", "decision.decline" ], FIELD_SCOPES
|
|
|
1541
1607
|
binding: "always",
|
|
1542
1608
|
label: "the spawned subworkflows",
|
|
1543
1609
|
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`."
|
|
1544
|
-
} ],
|
|
1610
|
+
} ], 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 = [ {
|
|
1545
1611
|
name: "tag",
|
|
1546
1612
|
label: "this engine tag",
|
|
1547
1613
|
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
@@ -1553,11 +1619,7 @@ const ACTION_SEMANTICS = [ "decision.accept", "decision.decline" ], FIELD_SCOPES
|
|
|
1553
1619
|
name: "now",
|
|
1554
1620
|
label: "the current time",
|
|
1555
1621
|
description: "The ISO clock reading of the evaluating engine."
|
|
1556
|
-
}, {
|
|
1557
|
-
name: SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR,
|
|
1558
|
-
label: "the subject already has an in-flight workflow",
|
|
1559
|
-
description: "Whether any instance in this engine tag, across all definitions, has the same resource-qualified subject and no `completedAt`. Advisory under concurrent starts."
|
|
1560
|
-
} ], START_ALLOWED_VARS = [ ...START_FILTER_VARS, {
|
|
1622
|
+
} ], START_REQUIREMENT_VARS = [ ...START_FILTER_VARS, {
|
|
1561
1623
|
name: "fields",
|
|
1562
1624
|
label: "the start's input fields",
|
|
1563
1625
|
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."
|
|
@@ -1742,6 +1804,37 @@ function isNotesEntry(entry) {
|
|
|
1742
1804
|
return columns !== void 0 && columns.has("body") && columns.has("actor") && columns.has("at");
|
|
1743
1805
|
}
|
|
1744
1806
|
|
|
1807
|
+
const ANONYMOUS_IDENTITY = "<anonymous>", SYSTEM_IDENTITY = "<system>", E_PREFIXED_PROJECT_ID = /^e-(.+)$/;
|
|
1808
|
+
|
|
1809
|
+
function classifyPrincipalId(id) {
|
|
1810
|
+
if (id === ANONYMOUS_IDENTITY || id === SYSTEM_IDENTITY) return {
|
|
1811
|
+
namespace: "sentinel"
|
|
1812
|
+
};
|
|
1813
|
+
if (id.startsWith("g")) return {
|
|
1814
|
+
namespace: "global",
|
|
1815
|
+
globalId: id
|
|
1816
|
+
};
|
|
1817
|
+
if (id.startsWith("p-")) return {
|
|
1818
|
+
namespace: "robot",
|
|
1819
|
+
globalId: id
|
|
1820
|
+
};
|
|
1821
|
+
const embeddedGlobal = E_PREFIXED_PROJECT_ID.exec(id)?.[1];
|
|
1822
|
+
return embeddedGlobal !== void 0 ? embeddedGlobal.startsWith("g") ? {
|
|
1823
|
+
namespace: "project",
|
|
1824
|
+
globalId: embeddedGlobal
|
|
1825
|
+
} : {
|
|
1826
|
+
namespace: "unknown"
|
|
1827
|
+
} : id.startsWith("p") ? {
|
|
1828
|
+
namespace: "project"
|
|
1829
|
+
} : {
|
|
1830
|
+
namespace: "unknown"
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
function lakePrincipalId(args) {
|
|
1835
|
+
return args.localPrincipalId ?? args.actor.id;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1745
1838
|
class FieldValueShapeError extends WorkflowError {
|
|
1746
1839
|
entryType;
|
|
1747
1840
|
entryName;
|
|
@@ -1782,11 +1875,11 @@ function checkChoiceList(args) {
|
|
|
1782
1875
|
const {entryType: entryType, options: options, validation: validation} = args;
|
|
1783
1876
|
if (options === void 0) return;
|
|
1784
1877
|
if (!CHOICE_KINDS.has(entryType)) return [ `\`options\` is not valid on "${entryType}" values` ];
|
|
1785
|
-
const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => checkValueAgainst({
|
|
1878
|
+
const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => issuesOf(checkValueAgainst({
|
|
1786
1879
|
entryType: kind,
|
|
1787
1880
|
value: option.value,
|
|
1788
1881
|
validation: validation
|
|
1789
|
-
}, valueSchemas)?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
|
|
1882
|
+
}, valueSchemas))?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
|
|
1790
1883
|
for (const option of options.list) seen.has(option.value) && issues.push(`duplicate option value ${JSON.stringify(option.value)}`),
|
|
1791
1884
|
seen.add(option.value);
|
|
1792
1885
|
return issues.length === 0 ? void 0 : issues;
|
|
@@ -1812,8 +1905,27 @@ const fieldValueSchemas = {
|
|
|
1812
1905
|
actor: v.union([ v.null(), ActorShape ]),
|
|
1813
1906
|
assignee: v.union([ v.null(), AssigneeShape ]),
|
|
1814
1907
|
assignees: v.array(AssigneeShape)
|
|
1815
|
-
},
|
|
1908
|
+
}, WritePrincipalId = v.pipe(NonEmptyString, v.rawTransform(({dataset: dataset, addIssue: addIssue}) => {
|
|
1909
|
+
const classified = classifyPrincipalId(dataset.value);
|
|
1910
|
+
return classified.namespace === "global" || classified.namespace === "robot" ? dataset.value : classified.namespace === "project" && classified.globalId !== void 0 ? classified.globalId : (addIssue({
|
|
1911
|
+
message: `principal id "${dataset.value}" is not an account-global user id. Workflow user ids are the global \`sanityUserId\` (or a robot token's id). Resolve project members through your surface's member hook, or /projects/<projectId>/users/<id> → sanityUserId.`
|
|
1912
|
+
}), dataset.value);
|
|
1913
|
+
})), ActorWriteShape = tolerantObject()({
|
|
1914
|
+
kind: v.picklist(ACTOR_KINDS),
|
|
1915
|
+
id: WritePrincipalId,
|
|
1916
|
+
roles: v.exactOptional(v.array(v.string())),
|
|
1917
|
+
onBehalfOf: v.exactOptional(v.string())
|
|
1918
|
+
}), AssigneeWriteShape = v.union([ tolerantObject()({
|
|
1919
|
+
type: v.literal("user"),
|
|
1920
|
+
id: WritePrincipalId
|
|
1921
|
+
}), tolerantObject()({
|
|
1922
|
+
type: v.literal("role"),
|
|
1923
|
+
role: NonEmptyString
|
|
1924
|
+
}) ]), valueSchemas = {
|
|
1816
1925
|
...fieldValueSchemas,
|
|
1926
|
+
actor: v.nullable(ActorWriteShape),
|
|
1927
|
+
assignee: v.nullable(AssigneeWriteShape),
|
|
1928
|
+
assignees: v.array(AssigneeWriteShape),
|
|
1817
1929
|
query: v.any()
|
|
1818
1930
|
};
|
|
1819
1931
|
|
|
@@ -1884,7 +1996,7 @@ function wholeValueSchema(args) {
|
|
|
1884
1996
|
function appendItemSchema(entryType, shape) {
|
|
1885
1997
|
if (entryType === "array") return objectSchema(shape.of ?? [], valueSchemas);
|
|
1886
1998
|
if (entryType === "doc.refs") return GdrShape;
|
|
1887
|
-
if (entryType === "assignees") return
|
|
1999
|
+
if (entryType === "assignees") return AssigneeWriteShape;
|
|
1888
2000
|
}
|
|
1889
2001
|
|
|
1890
2002
|
function rejectedRefTypes(args) {
|
|
@@ -1909,7 +2021,7 @@ function gdrTypeOf(item) {
|
|
|
1909
2021
|
return typeof t == "string" ? t : void 0;
|
|
1910
2022
|
}
|
|
1911
2023
|
|
|
1912
|
-
function
|
|
2024
|
+
function parseFieldValue(args) {
|
|
1913
2025
|
return checkValueAgainst(args, valueSchemas);
|
|
1914
2026
|
}
|
|
1915
2027
|
|
|
@@ -1919,9 +2031,14 @@ function checkValueAgainst(args, leaf) {
|
|
|
1919
2031
|
shape: args,
|
|
1920
2032
|
leaf: leaf
|
|
1921
2033
|
});
|
|
1922
|
-
if (schema === void 0) return
|
|
2034
|
+
if (schema === void 0) return {
|
|
2035
|
+
issues: [ `unknown field entry type ${args.entryType}` ]
|
|
2036
|
+
};
|
|
1923
2037
|
const result = v.safeParse(schema, args.value);
|
|
1924
|
-
|
|
2038
|
+
if (!result.success) return {
|
|
2039
|
+
issues: formatIssues(result.issues)
|
|
2040
|
+
};
|
|
2041
|
+
const postIssues = refTypeIssues({
|
|
1925
2042
|
entryType: args.entryType,
|
|
1926
2043
|
types: args.types,
|
|
1927
2044
|
value: args.value
|
|
@@ -1929,17 +2046,27 @@ function checkValueAgainst(args, leaf) {
|
|
|
1929
2046
|
entryType: args.entryType,
|
|
1930
2047
|
validation: args.validation,
|
|
1931
2048
|
value: args.value
|
|
1932
|
-
})
|
|
2049
|
+
});
|
|
2050
|
+
return postIssues !== void 0 ? {
|
|
2051
|
+
issues: postIssues
|
|
2052
|
+
} : {
|
|
2053
|
+
output: result.output
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
function issuesOf(check) {
|
|
2058
|
+
return "issues" in check ? check.issues : void 0;
|
|
1933
2059
|
}
|
|
1934
2060
|
|
|
1935
2061
|
function validateFieldValue(args) {
|
|
1936
|
-
const
|
|
1937
|
-
if (issues
|
|
2062
|
+
const check = checkValueAgainst(args, valueSchemas);
|
|
2063
|
+
if ("issues" in check) throw new FieldValueShapeError({
|
|
1938
2064
|
entryType: args.entryType,
|
|
1939
2065
|
entryName: args.entryName,
|
|
1940
|
-
issues: issues,
|
|
2066
|
+
issues: check.issues,
|
|
1941
2067
|
mode: "value"
|
|
1942
2068
|
});
|
|
2069
|
+
return check.output;
|
|
1943
2070
|
}
|
|
1944
2071
|
|
|
1945
2072
|
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}$`);
|
|
@@ -1968,7 +2095,7 @@ const AuthoringRefId = v.pipe(v.string(), v.check(isAuthoringRefId, "must be a b
|
|
|
1968
2095
|
};
|
|
1969
2096
|
|
|
1970
2097
|
function checkLiteralSeed(args) {
|
|
1971
|
-
return args.value === null ? [ "a literal seed cannot be null — omit `initialValue` to start the field empty" ] : checkValueAgainst(args, seedValueSchemas);
|
|
2098
|
+
return args.value === null ? [ "a literal seed cannot be null — omit `initialValue` to start the field empty" ] : issuesOf(checkValueAgainst(args, seedValueSchemas));
|
|
1972
2099
|
}
|
|
1973
2100
|
|
|
1974
2101
|
function validateFieldAppendItem(args) {
|
|
@@ -1997,13 +2124,15 @@ function validateFieldAppendItem(args) {
|
|
|
1997
2124
|
issues: typeIssues,
|
|
1998
2125
|
mode: "item"
|
|
1999
2126
|
});
|
|
2127
|
+
return result.output;
|
|
2000
2128
|
}
|
|
2001
2129
|
|
|
2002
2130
|
function formatIssues(issues, formatMessage = issue => issue.message) {
|
|
2003
|
-
|
|
2004
|
-
const keys =
|
|
2005
|
-
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(
|
|
2006
|
-
}
|
|
2131
|
+
const formatOne = (issue, prefix) => {
|
|
2132
|
+
const keys = [ ...prefix, ...issue.path?.map(p => p.key) ?? [] ], sub = issue.issues?.filter(candidate => candidate.expected !== "null");
|
|
2133
|
+
return sub !== void 0 && sub.length > 0 ? sub.flatMap(candidate => formatOne(candidate, keys)) : [ `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(issue)}` ];
|
|
2134
|
+
};
|
|
2135
|
+
return issues.flatMap(issue => formatOne(issue, []));
|
|
2007
2136
|
}
|
|
2008
2137
|
|
|
2009
2138
|
const NonEmpty = NonEmptyString, PositiveInt = v.pipe(v.number(), v.integer(), v.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -2013,7 +2142,15 @@ function groqIdentifier(referencedAs) {
|
|
|
2013
2142
|
}
|
|
2014
2143
|
|
|
2015
2144
|
function picklist(options) {
|
|
2016
|
-
return v.picklist(options,
|
|
2145
|
+
return v.picklist(options, invalidOptionMessage(options));
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
function invalidOptionMessage(options) {
|
|
2149
|
+
return `Invalid option: expected one of ${options.map(option => `"${option}"`).join("|")}`;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
function exhaustiveOptions() {
|
|
2153
|
+
return options => options;
|
|
2017
2154
|
}
|
|
2018
2155
|
|
|
2019
2156
|
function pinned() {
|
|
@@ -2028,12 +2165,12 @@ const LiteralSchema = v.strictObject({
|
|
|
2028
2165
|
scope: v.optional(v.union([ v.literal("workflow"), v.literal("stage") ])),
|
|
2029
2166
|
field: NonEmpty,
|
|
2030
2167
|
path: v.optional(v.string())
|
|
2031
|
-
}), FieldSourceSchema = v.
|
|
2168
|
+
}), FieldSourceSchema = v.variant("type", [ v.strictObject({
|
|
2032
2169
|
type: v.literal("input")
|
|
2033
2170
|
}), v.strictObject({
|
|
2034
2171
|
type: v.literal("query"),
|
|
2035
2172
|
query: NonEmpty
|
|
2036
|
-
}), LiteralSchema, FieldReadSchema ]), ValueExprSchema = v.lazy(() => v.
|
|
2173
|
+
}), LiteralSchema, FieldReadSchema ]), ValueExprSchema = v.lazy(() => v.variant("type", [ LiteralSchema, FieldReadSchema, v.strictObject({
|
|
2037
2174
|
type: v.literal("param"),
|
|
2038
2175
|
param: NonEmpty
|
|
2039
2176
|
}), v.strictObject({
|
|
@@ -2128,7 +2265,7 @@ function groupMembershipNames(group) {
|
|
|
2128
2265
|
return group === void 0 ? [] : typeof group == "string" ? [ group ] : [ ...group ];
|
|
2129
2266
|
}
|
|
2130
2267
|
|
|
2131
|
-
const FIELD_VALUE_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref", "string", "text", "number", "progress", "boolean", "date", "datetime", "url", "actor", "assignee", "assignees", "object", "array" ], FieldValueKindSchema = picklist(FIELD_VALUE_KINDS), FieldKindSchema = picklist(FIELD_VALUE_KINDS), FieldEntryName = groqIdentifier("`$fields.<name>`"), FiniteNumber = v.pipe(v.number(), v.finite("must be finite")), ScalarValidationSchema = v.pipe(v.strictObject({
|
|
2268
|
+
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.picklist(FIELD_VALUE_KINDS, issue => `${invalidOptionMessage(AUTHORING_FIELD_KINDS)} but received ${JSON.stringify(issue.input)}`), FieldEntryName = groqIdentifier("`$fields.<name>`"), FiniteNumber = v.pipe(v.number(), v.finite("must be finite")), ScalarValidationSchema = v.pipe(v.strictObject({
|
|
2132
2269
|
min: v.optional(FiniteNumber),
|
|
2133
2270
|
max: v.optional(FiniteNumber)
|
|
2134
2271
|
}), v.check(validation => validation.min !== void 0 || validation.max !== void 0, "declare at least one bound, or omit `validation`"), v.check(validation => validation.min === void 0 || validation.max === void 0 || validation.min <= validation.max, "`min` must be less than or equal to `max`")), ChoiceOptionsSchema = v.strictObject({
|
|
@@ -2193,9 +2330,9 @@ function fieldBase(editable, group) {
|
|
|
2193
2330
|
};
|
|
2194
2331
|
}
|
|
2195
2332
|
|
|
2196
|
-
function fieldEntryFields(editable, group) {
|
|
2333
|
+
function fieldEntryFields({editable: editable, group: group, kind: kind = FieldKindSchema}) {
|
|
2197
2334
|
return {
|
|
2198
|
-
type:
|
|
2335
|
+
type: kind,
|
|
2199
2336
|
...fieldBase(editable, group),
|
|
2200
2337
|
options: v.optional(ChoiceOptionsSchema),
|
|
2201
2338
|
validation: v.optional(ScalarValidationSchema),
|
|
@@ -2256,7 +2393,14 @@ function scalarValidationDeclarationIssues(entry) {
|
|
|
2256
2393
|
return issues.length === 0 ? void 0 : issues;
|
|
2257
2394
|
}
|
|
2258
2395
|
|
|
2259
|
-
const FieldEntrySchema = pinned()(v.pipe(compositeChecked(fieldEntryFields(
|
|
2396
|
+
const FieldEntrySchema = pinned()(v.pipe(compositeChecked(fieldEntryFields({
|
|
2397
|
+
editable: StoredEditableSchema,
|
|
2398
|
+
group: StoredGroupMembershipSchema
|
|
2399
|
+
})), refTypesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v.pipe(compositeChecked(fieldEntryFields({
|
|
2400
|
+
editable: AuthoringEditableSchema,
|
|
2401
|
+
group: AuthoringGroupMembershipSchema,
|
|
2402
|
+
kind: AuthoringRawFieldKindSchema
|
|
2403
|
+
})), refTypesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), ClaimFieldSchema = pinned()(v.strictObject({
|
|
2260
2404
|
type: v.literal("claim"),
|
|
2261
2405
|
name: FieldEntryName,
|
|
2262
2406
|
title: v.optional(v.string()),
|
|
@@ -2271,7 +2415,10 @@ function listSugarFields(type) {
|
|
|
2271
2415
|
};
|
|
2272
2416
|
}
|
|
2273
2417
|
|
|
2274
|
-
const TodoListFieldSchema = pinned()(v.strictObject(listSugarFields("todoList"))), NotesFieldSchema = pinned()(v.strictObject(listSugarFields("notes"))), AuthoringFieldEntrySchema = pinned()(v.
|
|
2418
|
+
const TodoListFieldSchema = pinned()(v.strictObject(listSugarFields("todoList"))), NotesFieldSchema = pinned()(v.strictObject(listSugarFields("notes"))), AuthoringFieldEntrySchema = pinned()(v.lazy(input => {
|
|
2419
|
+
const type = asShape(input).type;
|
|
2420
|
+
return type === "claim" ? ClaimFieldSchema : type === "todoList" ? TodoListFieldSchema : type === "notes" ? NotesFieldSchema : RawAuthoringFieldEntrySchema;
|
|
2421
|
+
})), EffectSchema = v.strictObject({
|
|
2275
2422
|
name: NonEmpty,
|
|
2276
2423
|
title: v.optional(v.string()),
|
|
2277
2424
|
description: v.optional(v.string()),
|
|
@@ -2336,7 +2483,18 @@ const StoredActionSchema = pinned()(v.strictObject({
|
|
|
2336
2483
|
filter: v.optional(ConditionSchema),
|
|
2337
2484
|
params: v.optional(v.array(ActionParamSchema)),
|
|
2338
2485
|
effects: v.optional(v.array(EffectSchema))
|
|
2339
|
-
})), AuthoringActionSchema = pinned()(v.lazy(input => typeof input == "object" && input !== null && "type" in input ? ClaimActionSchema : RawAuthoringActionSchema))
|
|
2486
|
+
})), AuthoringActionSchema = pinned()(v.lazy(input => typeof input == "object" && input !== null && "type" in input ? ClaimActionSchema : RawAuthoringActionSchema)), requirementBase = {
|
|
2487
|
+
name: NonEmpty,
|
|
2488
|
+
title: v.optional(v.string()),
|
|
2489
|
+
description: v.optional(v.string())
|
|
2490
|
+
}, GroqRequirementSchemaRaw = v.strictObject({
|
|
2491
|
+
...requirementBase,
|
|
2492
|
+
type: v.literal("groq"),
|
|
2493
|
+
query: ConditionSchema
|
|
2494
|
+
}), SingleSubjectRequirementSchemaRaw = v.strictObject({
|
|
2495
|
+
...requirementBase,
|
|
2496
|
+
type: v.literal("singleSubject")
|
|
2497
|
+
}), GroqRequirementSchema = pinned()(GroqRequirementSchemaRaw), StartRequirementSchema = pinned()(v.variant("type", [ GroqRequirementSchemaRaw, SingleSubjectRequirementSchemaRaw ]));
|
|
2340
2498
|
|
|
2341
2499
|
function activityFields({field: field, action: action, target: target, group: group}) {
|
|
2342
2500
|
return {
|
|
@@ -2347,7 +2505,7 @@ function activityFields({field: field, action: action, target: target, group: gr
|
|
|
2347
2505
|
group: v.optional(group),
|
|
2348
2506
|
target: v.optional(target),
|
|
2349
2507
|
filter: v.optional(ConditionSchema),
|
|
2350
|
-
requirements: v.optional(v.
|
|
2508
|
+
requirements: v.optional(v.array(GroqRequirementSchema)),
|
|
2351
2509
|
actions: v.optional(v.array(action)),
|
|
2352
2510
|
fields: v.optional(v.array(field))
|
|
2353
2511
|
};
|
|
@@ -2443,7 +2601,7 @@ function startFields(kind) {
|
|
|
2443
2601
|
return {
|
|
2444
2602
|
kind: kind,
|
|
2445
2603
|
filter: v.optional(ConditionSchema),
|
|
2446
|
-
|
|
2604
|
+
requirements: v.optional(v.array(StartRequirementSchema))
|
|
2447
2605
|
};
|
|
2448
2606
|
}
|
|
2449
2607
|
|
|
@@ -2582,6 +2740,13 @@ function checkActivities({def: def, i: i, activityNames: activityNames, issues:
|
|
|
2582
2740
|
})),
|
|
2583
2741
|
what: `action name in activity "${activity.name}"`,
|
|
2584
2742
|
issues: issues
|
|
2743
|
+
}), checkDuplicates({
|
|
2744
|
+
names: (activity.requirements ?? []).map((requirement, r) => ({
|
|
2745
|
+
name: requirement.name,
|
|
2746
|
+
path: [ ...path, "requirements", r, "name" ]
|
|
2747
|
+
})),
|
|
2748
|
+
what: `requirement name in activity "${activity.name}"`,
|
|
2749
|
+
issues: issues
|
|
2585
2750
|
}), checkStatusSetTargets({
|
|
2586
2751
|
activity: activity,
|
|
2587
2752
|
activityNames: activityNames,
|
|
@@ -2890,10 +3055,10 @@ function collectActivityConditionSites({activity: activity, path: path, stageFie
|
|
|
2890
3055
|
policy: "cascade",
|
|
2891
3056
|
fields: fields
|
|
2892
3057
|
});
|
|
2893
|
-
for (const [
|
|
2894
|
-
groq:
|
|
2895
|
-
path: [ ...path, "requirements",
|
|
2896
|
-
label: `activity "${activity.name}" requirement "${name}"`,
|
|
3058
|
+
for (const [index, requirement] of (activity.requirements ?? []).entries()) sites.push({
|
|
3059
|
+
groq: requirement.query,
|
|
3060
|
+
path: [ ...path, "requirements", index, "query" ],
|
|
3061
|
+
label: `activity "${activity.name}" requirement "${requirement.name}"`,
|
|
2897
3062
|
policy: "caller-bound",
|
|
2898
3063
|
fields: fields
|
|
2899
3064
|
});
|
|
@@ -3089,73 +3254,83 @@ function checkTriggeredActionParams(def, issues) {
|
|
|
3089
3254
|
}
|
|
3090
3255
|
|
|
3091
3256
|
function checkStart(def, issues) {
|
|
3092
|
-
if (def.start !== void 0 && (
|
|
3257
|
+
if (def.start !== void 0 && (checkDuplicates({
|
|
3258
|
+
names: (def.start.requirements ?? []).map((requirement, index) => ({
|
|
3259
|
+
name: requirement.name,
|
|
3260
|
+
path: [ "start", "requirements", index, "name" ]
|
|
3261
|
+
})),
|
|
3262
|
+
what: "start requirement name",
|
|
3263
|
+
issues: issues
|
|
3264
|
+
}), def.lifecycle === "child" && issues.push({
|
|
3093
3265
|
path: [ "start" ],
|
|
3094
3266
|
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'`"
|
|
3095
|
-
}), checkStartFilterReads(def, issues),
|
|
3096
|
-
def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
|
|
3267
|
+
}), checkStartFilterReads(def, issues), checkStartRequirementReads(def, issues),
|
|
3268
|
+
checkSingleSubjectRequirements(def, issues), def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
|
|
3097
3269
|
path: [ "fields", n, "required" ],
|
|
3098
3270
|
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)`
|
|
3099
3271
|
});
|
|
3100
3272
|
}
|
|
3101
3273
|
|
|
3102
|
-
function
|
|
3274
|
+
function checkSingleSubjectRequirements(def, issues) {
|
|
3275
|
+
const indexes = (def.start?.requirements ?? []).map((requirement, index) => ({
|
|
3276
|
+
requirement: requirement,
|
|
3277
|
+
index: index
|
|
3278
|
+
})).filter(({requirement: requirement}) => requirement.type === "singleSubject"), [first] = indexes;
|
|
3279
|
+
if (first === void 0) return;
|
|
3103
3280
|
const subject = (def.fields ?? []).find(isSubjectEntry);
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
message: `start.${key} reads $${SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR}, but the definition declares no \`subject\` entry — the engine has no prospective subject identity to match. Declare a \`subject\` entry (the document the workflow is about), or remove the variable`
|
|
3109
|
-
});
|
|
3110
|
-
continue;
|
|
3111
|
-
}
|
|
3112
|
-
key === "allowed" && !isInputSourced(subject) && issues.push({
|
|
3113
|
-
path: [ "start", key ],
|
|
3114
|
-
message: `start.allowed reads $${SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR}, but the definition's subject entry "${subject.name}" is not \`input\`-sourced — the start gate cannot bind its prospective subject. Make the subject input-sourced, or remove the variable`
|
|
3281
|
+
if (subject === void 0) {
|
|
3282
|
+
issues.push({
|
|
3283
|
+
path: [ "start", "requirements", first.index ],
|
|
3284
|
+
message: "a singleSubject start requirement caps the definition at one in-flight run per subject, but the definition declares no `subject` entry — there is no subject identity to dedupe on, so the rule could never apply. Declare a `subject` entry (the document the workflow is about), or remove the requirement"
|
|
3115
3285
|
});
|
|
3286
|
+
return;
|
|
3116
3287
|
}
|
|
3288
|
+
isInputSourced(subject) || issues.push({
|
|
3289
|
+
path: [ "start", "requirements", first.index ],
|
|
3290
|
+
message: `a singleSubject start requirement matches the prospective subject against in-flight runs, but the definition's subject entry "${subject.name}" is not \`input\`-sourced — the start gate cannot bind a prospective subject. Make the subject input-sourced, or remove the requirement`
|
|
3291
|
+
});
|
|
3117
3292
|
}
|
|
3118
3293
|
|
|
3119
3294
|
function checkStartFilterReads(def, issues) {
|
|
3120
3295
|
const filter = def.start?.filter;
|
|
3121
3296
|
filter !== void 0 && (conditionParameterNames(filter).has("fields") && issues.push({
|
|
3122
3297
|
path: [ "start", "filter" ],
|
|
3123
|
-
message: "start.filter reads $fields, but the filter is browse-time-pure — a start surface evaluates it per document, before any inputs exist, so $fields cannot be bound. Move the input-dependent rule to start
|
|
3298
|
+
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 a start requirement, the start-time readiness predicate that binds $fields and is enforced by startInstance"
|
|
3124
3299
|
}), readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
3125
3300
|
path: [ "start", "filter" ],
|
|
3126
3301
|
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"
|
|
3127
3302
|
}));
|
|
3128
3303
|
}
|
|
3129
3304
|
|
|
3130
|
-
function
|
|
3131
|
-
const allowed = def.start?.allowed;
|
|
3132
|
-
if (allowed === void 0) return;
|
|
3305
|
+
function checkStartRequirementReads(def, issues) {
|
|
3133
3306
|
const bindable = new Map((def.fields ?? []).filter(isInputSourced).map(entry => [ entry.name, entry ])), declared = new Set((def.fields ?? []).map(entry => entry.name)), nullVerdict = `so the read is GROQ null and null semantics decide the verdict (refuse-all or vacuously-allow, by the expression's shape), never the caller's real input. Bindable (input) fields: ${knownList(bindable.keys())}`;
|
|
3134
|
-
for (const
|
|
3135
|
-
const
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3307
|
+
for (const [index, requirement] of (def.start?.requirements ?? []).entries()) if (requirement.type === "groq") {
|
|
3308
|
+
for (const read of conditionFieldReads(requirement.query)) {
|
|
3309
|
+
const entry = bindable.get(read.name);
|
|
3310
|
+
if (entry === void 0) {
|
|
3311
|
+
issues.push({
|
|
3312
|
+
path: [ "start", "requirements", index, "query" ],
|
|
3313
|
+
message: declared.has(read.name) ? `start requirement reads $fields.${read.name}, but "${read.name}" is not an \`input\`-sourced entry — $fields binds only the caller's input entries (query/literal/fieldRead entries resolve at materialisation, after the start gate), ${nullVerdict}` : `start requirement reads $fields.${read.name}, but no workflow-scope field entry named "${read.name}" is declared — $fields binds only the caller's input entries, ${nullVerdict}`
|
|
3314
|
+
});
|
|
3315
|
+
continue;
|
|
3316
|
+
}
|
|
3317
|
+
pushFieldReadPathIssue({
|
|
3318
|
+
where: "start requirement",
|
|
3319
|
+
read: {
|
|
3320
|
+
field: read.name,
|
|
3321
|
+
path: read.path
|
|
3322
|
+
},
|
|
3323
|
+
target: entry,
|
|
3324
|
+
path: [ "start", "requirements", index, "query" ],
|
|
3325
|
+
issues: issues,
|
|
3326
|
+
nodes: START_REQUIREMENT_VALUE_NODES
|
|
3140
3327
|
});
|
|
3141
|
-
continue;
|
|
3142
3328
|
}
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
field: read.name,
|
|
3147
|
-
path: read.path
|
|
3148
|
-
},
|
|
3149
|
-
target: entry,
|
|
3150
|
-
path: [ "start", "allowed" ],
|
|
3151
|
-
issues: issues,
|
|
3152
|
-
nodes: START_ALLOWED_VALUE_NODES
|
|
3329
|
+
readsRootDocument(requirement.query) && issues.push({
|
|
3330
|
+
path: [ "start", "requirements", index, "query" ],
|
|
3331
|
+
message: "start requirement reads the candidate document as its root, but no root ever binds in the start-requirement context — startInstance holds inputs, not a loaded document, so the read is GROQ null and null semantics decide the verdict, never the workflow's real state. Read the subject as $fields.<entry> (its GDR URI is $fields.<entry>.id); a per-document visibility rule belongs in start.filter"
|
|
3153
3332
|
});
|
|
3154
3333
|
}
|
|
3155
|
-
readsRootDocument(allowed) && issues.push({
|
|
3156
|
-
path: [ "start", "allowed" ],
|
|
3157
|
-
message: "start.allowed reads the candidate document as its root, but no root ever binds in the start-allowed context — startInstance holds inputs, not a loaded document, so the read is GROQ null and null semantics decide the verdict, never the workflow's real state. Read the subject as $fields.<entry> (its GDR URI is $fields.<entry>.id); a per-document visibility rule belongs in start.filter"
|
|
3158
|
-
});
|
|
3159
3334
|
}
|
|
3160
3335
|
|
|
3161
3336
|
function checkGroups(def, issues) {
|
|
@@ -3680,7 +3855,7 @@ const SCALAR = {
|
|
|
3680
3855
|
kind: "rows",
|
|
3681
3856
|
of: shape.of ?? []
|
|
3682
3857
|
})
|
|
3683
|
-
},
|
|
3858
|
+
}, START_REQUIREMENT_VALUE_NODES = {
|
|
3684
3859
|
...VALUE_NODES,
|
|
3685
3860
|
"doc.ref": () => GDR_VALUE,
|
|
3686
3861
|
subject: () => GDR_VALUE
|
|
@@ -3761,4 +3936,4 @@ function checkWorkflowInvariants(def) {
|
|
|
3761
3936
|
checkGroups(def, issues), issues;
|
|
3762
3937
|
}
|
|
3763
3938
|
|
|
3764
|
-
export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_STATUSES, ACTOR_KINDS, ActorShape, AuthoringActionSchema, AuthoringActivitySchema, AuthoringFieldEntrySchema, AuthoringGuardSchema, AuthoringOpSchema, AuthoringStageSchema, AuthoringTransitionSchema, AuthoringWorkflowSchema, CALLER_BOUND_VARS, CONDITION_VARS, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_TRANSITION_WHEN, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DefinitionInUseError, DefinitionNotFoundError, EFFECTS_READ, EXECUTOR_CLASSIFICATIONS, EffectNotFoundError, EffectSchema, FIELD_READ, FIELD_SCOPES, FIELD_VALUE_KINDS, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GUARD_PREDICATE_VARS, GdrShape, GroupSchema, InstanceNotFoundError, IsoTimestamp, MUTATION_GUARD_ACTIONS, ModelVersionAheadError, NonEmptyString, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, RESOURCE_ALIAS_NAME_SOURCE, ReaderModelAcknowledgementError,
|
|
3939
|
+
export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_STATUSES, ACTOR_KINDS, ANONYMOUS_IDENTITY, ActorShape, AuthoringActionSchema, AuthoringActivitySchema, AuthoringFieldEntrySchema, AuthoringGuardSchema, AuthoringOpSchema, AuthoringStageSchema, AuthoringTransitionSchema, AuthoringWorkflowSchema, CALLER_BOUND_VARS, CONDITION_VARS, ContractViolationError, DATA_MODEL_CHANGES, DATA_MODEL_MIN_READER, DATA_MODEL_VERSION, DEFAULT_TRANSITION_WHEN, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DefinitionInUseError, DefinitionNotFoundError, EFFECTS_READ, EXECUTOR_CLASSIFICATIONS, EffectNotFoundError, EffectSchema, FIELD_READ, FIELD_SCOPES, FIELD_VALUE_KINDS, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GUARD_PREDICATE_VARS, GdrShape, GroupSchema, InstanceNotFoundError, IsoTimestamp, MUTATION_GUARD_ACTIONS, ModelVersionAheadError, NonEmptyString, PersistedDocShapeError, READER_MODEL_ROLLOUT_URL, RESERVED_CONDITION_VARS, RESOURCE_ALIAS_NAME_SOURCE, ReaderModelAcknowledgementError, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StoredFieldOpSchema, VersionSpecificDatasetGdrError, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, WorkflowConfigSchema, WorkflowError, actorFulfillsRole, andConditions, assertReadableModel, assertReaderModelAcknowledgement, checkWorkflowInvariants, choiceValueIssues, classifyPrincipalId, clientConfigFromResource, conditionEffectReads, conditionFieldReadNames, conditionParameterNames, conditionSyntaxIssues, datasetResourceParts, definitionDocId, deriveActivityKind, deriveExecutorClassification, desugarWorkflow, driverKind, errorMessage, evaluateCondition, evaluateConditionOutcome, evaluatePredicates, extractDocumentId, fieldTreeShape, fieldValueSchemas, formatIssuePath, formatIssues, formatValidationError, gdrFromResource, gdrRef, gdrResourcePrefix, gdrUri, groq, groupMembershipNames, isBareSeedId, isCascadeFired, isGdr, isGdrUri, isGuardReadExpr, isInputSourced, isNotesEntry, isParseableInstant, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTerminalActivityStatus, isTodoListEntry, isTodoListItem, isUnevaluable, isUnprimed, labelFor, lakePrincipalId, minReaderModelOf, modelStampFor, modelVersionOf, parentRef, parseDefinitionSnapshot, parseFieldValue, parseGdr, parseOrThrow, parsePersistedDoc, parseResourceGdr, parseStoredDefinition, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refTypeIssues, rejectedRefTypes, releaseDocId, releaseRef, requiredModelFeatures, requiredReaderModel, resourceAliasesToMap, resourceFromGdrUri, resourceFromParsed, resourceGdr, rethrowWithContext, runGroq, sameResource, scalarValidationIssues, schemaTreeShape, selfGdr, startKindOf, tagScopeFilter, terminalState, toBareId, toPhysicalGdr, tolerantEntries, tolerantObject, tryParseGdr, validateFieldAppendItem, validateFieldValue, validateResourceAliasName, validateTag };
|