@sanity/workflow-engine 0.20.0 → 0.22.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 +99 -0
- package/DATAMODEL.md +168 -0
- package/dist/_chunks-cjs/invariants.cjs +223 -82
- package/dist/_chunks-es/invariants.js +213 -78
- package/dist/define.d.cts +42 -23
- package/dist/define.d.ts +42 -23
- package/dist/index.cjs +1717 -864
- package/dist/index.d.cts +450 -176
- package/dist/index.d.ts +450 -176
- package/dist/index.js +1698 -863
- package/package.json +2 -2
|
@@ -115,12 +115,33 @@ function isUnprimed(instance) {
|
|
|
115
115
|
|
|
116
116
|
function parseDefinitionSnapshotValue(instance) {
|
|
117
117
|
try {
|
|
118
|
-
return JSON.parse(instance.definitionSnapshot);
|
|
118
|
+
return normalizeLegacyActivityRequirements(JSON.parse(instance.definitionSnapshot));
|
|
119
119
|
} catch (err) {
|
|
120
120
|
rethrowWithContext(err, `Failed to parse definitionSnapshot on instance "${instance._id}"`);
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
function normalizeLegacyActivityRequirements(value) {
|
|
125
|
+
for (const stage of arrayMember(value, "stages")) for (const activity of arrayMember(stage, "activities")) normalizeLegacyRequirementMap(activity);
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function arrayMember(value, key) {
|
|
130
|
+
if (typeof value != "object" || value === null) return [];
|
|
131
|
+
const member = value[key];
|
|
132
|
+
return Array.isArray(member) ? member : [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function normalizeLegacyRequirementMap(value) {
|
|
136
|
+
if (typeof value != "object" || value === null) return;
|
|
137
|
+
const activity = value, requirements = activity.requirements;
|
|
138
|
+
typeof requirements != "object" || requirements === null || Array.isArray(requirements) || (activity.requirements = Object.entries(requirements).map(([name, query]) => ({
|
|
139
|
+
type: "groq",
|
|
140
|
+
name: name,
|
|
141
|
+
query: query
|
|
142
|
+
})));
|
|
143
|
+
}
|
|
144
|
+
|
|
124
145
|
function parseDefinitionSnapshot(instance) {
|
|
125
146
|
return parseDefinitionSnapshotValue(instance);
|
|
126
147
|
}
|
|
@@ -129,7 +150,7 @@ function parentRef(instance) {
|
|
|
129
150
|
return instance.ancestors.at(-1);
|
|
130
151
|
}
|
|
131
152
|
|
|
132
|
-
const DATA_MODEL_VERSION =
|
|
153
|
+
const DATA_MODEL_VERSION = 4, DATA_MODEL_MIN_READER = 4, READER_MODEL_ROLLOUT_URL = "https://www.sanity.io/docs/editorial-workflows/prerelease";
|
|
133
154
|
|
|
134
155
|
class ReaderModelAcknowledgementError extends WorkflowError {
|
|
135
156
|
code="WORKFLOW_READER_MODEL_ACKNOWLEDGEMENT_MISMATCH";
|
|
@@ -139,7 +160,7 @@ class ReaderModelAcknowledgementError extends WorkflowError {
|
|
|
139
160
|
documentationUrl=READER_MODEL_ROLLOUT_URL;
|
|
140
161
|
constructor(expectedMinReaderModel, context = "Deployment") {
|
|
141
162
|
const expected = expectedMinReaderModel === void 0 ? "missing" : String(expectedMinReaderModel);
|
|
142
|
-
super("reader-model-acknowledgement", `${context} expected reader floor ${expected}; the installed engine requires acknowledgement ${DATA_MODEL_MIN_READER}.
|
|
163
|
+
super("reader-model-acknowledgement", `${context} expected reader floor ${expected}; the installed engine requires acknowledgement ${DATA_MODEL_MIN_READER}. Do not change the acknowledgement yet: accepting ${DATA_MODEL_MIN_READER} authorizes this engine to write documents that older readers will refuse. Upgrade every Studio, CLI, MCP server, Function, and other runtime that reads engine-owned documents; verify that rollout in every environment sharing the workflow resource; then change the literal in deployment configuration and deploy the writer. Rollout guide: ${READER_MODEL_ROLLOUT_URL}`),
|
|
143
164
|
this.name = "ReaderModelAcknowledgementError", this.expectedMinReaderModel = expectedMinReaderModel;
|
|
144
165
|
}
|
|
145
166
|
}
|
|
@@ -204,6 +225,22 @@ const DATA_MODEL_CHANGES = Object.freeze([ Object.freeze({
|
|
|
204
225
|
compatibility: "additive",
|
|
205
226
|
applicability: "detectable",
|
|
206
227
|
summary: "Pending-effect claims carry an exact-claim token gating mid-dispatch state reports."
|
|
228
|
+
}), Object.freeze({
|
|
229
|
+
id: "classified-principal-ids",
|
|
230
|
+
introducedInModel: 4,
|
|
231
|
+
minReaderModel: 4,
|
|
232
|
+
documentTypes: Object.freeze([ "instance" ]),
|
|
233
|
+
compatibility: "reader-floor",
|
|
234
|
+
applicability: "unconditional",
|
|
235
|
+
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."
|
|
236
|
+
}), Object.freeze({
|
|
237
|
+
id: "readiness-requirements",
|
|
238
|
+
introducedInModel: 4,
|
|
239
|
+
minReaderModel: 4,
|
|
240
|
+
documentTypes: Object.freeze([ "definition" ]),
|
|
241
|
+
compatibility: "reader-floor",
|
|
242
|
+
applicability: "detectable",
|
|
243
|
+
summary: "Start and activity readiness use named polymorphic requirement arrays."
|
|
207
244
|
}) ]);
|
|
208
245
|
|
|
209
246
|
function recordOf(value) {
|
|
@@ -264,6 +301,11 @@ function hasClaimTokens(document) {
|
|
|
264
301
|
});
|
|
265
302
|
}
|
|
266
303
|
|
|
304
|
+
function hasReadinessRequirements(document) {
|
|
305
|
+
const root = recordOf(document);
|
|
306
|
+
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)));
|
|
307
|
+
}
|
|
308
|
+
|
|
267
309
|
const featureDetectors = {
|
|
268
310
|
"governed-model-stamps": () => !0,
|
|
269
311
|
"subject-field-kind": document => hasFieldKind(document, "subject"),
|
|
@@ -271,7 +313,9 @@ const featureDetectors = {
|
|
|
271
313
|
"action-semantics": hasActionSemantics,
|
|
272
314
|
"inclusive-scalar-bounds": hasScalarValidation,
|
|
273
315
|
"progress-field-kind": document => hasFieldKind(document, "progress"),
|
|
274
|
-
"effect-claim-tokens": hasClaimTokens
|
|
316
|
+
"effect-claim-tokens": hasClaimTokens,
|
|
317
|
+
"classified-principal-ids": () => !0,
|
|
318
|
+
"readiness-requirements": hasReadinessRequirements
|
|
275
319
|
};
|
|
276
320
|
|
|
277
321
|
function requiredModelFeatures(documentType, document) {
|
|
@@ -285,7 +329,7 @@ function requiredReaderModel(documentType, document) {
|
|
|
285
329
|
function modelStampFor(args) {
|
|
286
330
|
return {
|
|
287
331
|
modelVersion: DATA_MODEL_VERSION,
|
|
288
|
-
minReaderModel: Math.max(args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
|
|
332
|
+
minReaderModel: Math.max(DATA_MODEL_MIN_READER, args.storedMinReaderModel ?? 0, requiredReaderModel(args.documentType, args.document))
|
|
289
333
|
};
|
|
290
334
|
}
|
|
291
335
|
|
|
@@ -814,8 +858,8 @@ function desugarStart(start) {
|
|
|
814
858
|
...start.filter !== void 0 ? {
|
|
815
859
|
filter: start.filter
|
|
816
860
|
} : {},
|
|
817
|
-
...start.
|
|
818
|
-
|
|
861
|
+
...start.requirements !== void 0 ? {
|
|
862
|
+
requirements: start.requirements
|
|
819
863
|
} : {}
|
|
820
864
|
};
|
|
821
865
|
}
|
|
@@ -1577,7 +1621,7 @@ const ACTION_SEMANTICS = [ "decision.accept", "decision.decline" ], FIELD_SCOPES
|
|
|
1577
1621
|
binding: "always",
|
|
1578
1622
|
label: "the spawned subworkflows",
|
|
1579
1623
|
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`."
|
|
1580
|
-
} ],
|
|
1624
|
+
} ], 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 = [ {
|
|
1581
1625
|
name: "tag",
|
|
1582
1626
|
label: "this engine tag",
|
|
1583
1627
|
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
@@ -1589,11 +1633,7 @@ const ACTION_SEMANTICS = [ "decision.accept", "decision.decline" ], FIELD_SCOPES
|
|
|
1589
1633
|
name: "now",
|
|
1590
1634
|
label: "the current time",
|
|
1591
1635
|
description: "The ISO clock reading of the evaluating engine."
|
|
1592
|
-
}, {
|
|
1593
|
-
name: SUBJECT_HAS_IN_FLIGHT_INSTANCE_VAR,
|
|
1594
|
-
label: "the subject already has an in-flight workflow",
|
|
1595
|
-
description: "Whether any instance in this engine tag, across all definitions, has the same resource-qualified subject and no `completedAt`. Advisory under concurrent starts."
|
|
1596
|
-
} ], START_ALLOWED_VARS = [ ...START_FILTER_VARS, {
|
|
1636
|
+
} ], START_REQUIREMENT_VARS = [ ...START_FILTER_VARS, {
|
|
1597
1637
|
name: "fields",
|
|
1598
1638
|
label: "the start's input fields",
|
|
1599
1639
|
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."
|
|
@@ -1778,6 +1818,37 @@ function isNotesEntry(entry) {
|
|
|
1778
1818
|
return columns !== void 0 && columns.has("body") && columns.has("actor") && columns.has("at");
|
|
1779
1819
|
}
|
|
1780
1820
|
|
|
1821
|
+
const ANONYMOUS_IDENTITY = "<anonymous>", SYSTEM_IDENTITY = "<system>", E_PREFIXED_PROJECT_ID = /^e-(.+)$/;
|
|
1822
|
+
|
|
1823
|
+
function classifyPrincipalId(id) {
|
|
1824
|
+
if (id === ANONYMOUS_IDENTITY || id === SYSTEM_IDENTITY) return {
|
|
1825
|
+
namespace: "sentinel"
|
|
1826
|
+
};
|
|
1827
|
+
if (id.startsWith("g")) return {
|
|
1828
|
+
namespace: "global",
|
|
1829
|
+
globalId: id
|
|
1830
|
+
};
|
|
1831
|
+
if (id.startsWith("p-")) return {
|
|
1832
|
+
namespace: "robot",
|
|
1833
|
+
globalId: id
|
|
1834
|
+
};
|
|
1835
|
+
const embeddedGlobal = E_PREFIXED_PROJECT_ID.exec(id)?.[1];
|
|
1836
|
+
return embeddedGlobal !== void 0 ? embeddedGlobal.startsWith("g") ? {
|
|
1837
|
+
namespace: "project",
|
|
1838
|
+
globalId: embeddedGlobal
|
|
1839
|
+
} : {
|
|
1840
|
+
namespace: "unknown"
|
|
1841
|
+
} : id.startsWith("p") ? {
|
|
1842
|
+
namespace: "project"
|
|
1843
|
+
} : {
|
|
1844
|
+
namespace: "unknown"
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
function lakePrincipalId(args) {
|
|
1849
|
+
return args.localPrincipalId ?? args.actor.id;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1781
1852
|
class FieldValueShapeError extends WorkflowError {
|
|
1782
1853
|
entryType;
|
|
1783
1854
|
entryName;
|
|
@@ -1818,11 +1889,11 @@ function checkChoiceList(args) {
|
|
|
1818
1889
|
const {entryType: entryType, options: options, validation: validation} = args;
|
|
1819
1890
|
if (options === void 0) return;
|
|
1820
1891
|
if (!CHOICE_KINDS.has(entryType)) return [ `\`options\` is not valid on "${entryType}" values` ];
|
|
1821
|
-
const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => checkValueAgainst({
|
|
1892
|
+
const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => issuesOf(checkValueAgainst({
|
|
1822
1893
|
entryType: kind,
|
|
1823
1894
|
value: option.value,
|
|
1824
1895
|
validation: validation
|
|
1825
|
-
}, valueSchemas)?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
|
|
1896
|
+
}, valueSchemas))?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
|
|
1826
1897
|
for (const option of options.list) seen.has(option.value) && issues.push(`duplicate option value ${JSON.stringify(option.value)}`),
|
|
1827
1898
|
seen.add(option.value);
|
|
1828
1899
|
return issues.length === 0 ? void 0 : issues;
|
|
@@ -1848,8 +1919,27 @@ const fieldValueSchemas = {
|
|
|
1848
1919
|
actor: v__namespace.union([ v__namespace.null(), ActorShape ]),
|
|
1849
1920
|
assignee: v__namespace.union([ v__namespace.null(), AssigneeShape ]),
|
|
1850
1921
|
assignees: v__namespace.array(AssigneeShape)
|
|
1851
|
-
},
|
|
1922
|
+
}, WritePrincipalId = v__namespace.pipe(NonEmptyString, v__namespace.rawTransform(({dataset: dataset, addIssue: addIssue}) => {
|
|
1923
|
+
const classified = classifyPrincipalId(dataset.value);
|
|
1924
|
+
return classified.namespace === "global" || classified.namespace === "robot" ? dataset.value : classified.namespace === "project" && classified.globalId !== void 0 ? classified.globalId : (addIssue({
|
|
1925
|
+
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.`
|
|
1926
|
+
}), dataset.value);
|
|
1927
|
+
})), ActorWriteShape = tolerantObject()({
|
|
1928
|
+
kind: v__namespace.picklist(ACTOR_KINDS),
|
|
1929
|
+
id: WritePrincipalId,
|
|
1930
|
+
roles: v__namespace.exactOptional(v__namespace.array(v__namespace.string())),
|
|
1931
|
+
onBehalfOf: v__namespace.exactOptional(v__namespace.string())
|
|
1932
|
+
}), AssigneeWriteShape = v__namespace.union([ tolerantObject()({
|
|
1933
|
+
type: v__namespace.literal("user"),
|
|
1934
|
+
id: WritePrincipalId
|
|
1935
|
+
}), tolerantObject()({
|
|
1936
|
+
type: v__namespace.literal("role"),
|
|
1937
|
+
role: NonEmptyString
|
|
1938
|
+
}) ]), valueSchemas = {
|
|
1852
1939
|
...fieldValueSchemas,
|
|
1940
|
+
actor: v__namespace.nullable(ActorWriteShape),
|
|
1941
|
+
assignee: v__namespace.nullable(AssigneeWriteShape),
|
|
1942
|
+
assignees: v__namespace.array(AssigneeWriteShape),
|
|
1853
1943
|
query: v__namespace.any()
|
|
1854
1944
|
};
|
|
1855
1945
|
|
|
@@ -1920,7 +2010,7 @@ function wholeValueSchema(args) {
|
|
|
1920
2010
|
function appendItemSchema(entryType, shape) {
|
|
1921
2011
|
if (entryType === "array") return objectSchema(shape.of ?? [], valueSchemas);
|
|
1922
2012
|
if (entryType === "doc.refs") return GdrShape;
|
|
1923
|
-
if (entryType === "assignees") return
|
|
2013
|
+
if (entryType === "assignees") return AssigneeWriteShape;
|
|
1924
2014
|
}
|
|
1925
2015
|
|
|
1926
2016
|
function rejectedRefTypes(args) {
|
|
@@ -1945,7 +2035,7 @@ function gdrTypeOf(item) {
|
|
|
1945
2035
|
return typeof t == "string" ? t : void 0;
|
|
1946
2036
|
}
|
|
1947
2037
|
|
|
1948
|
-
function
|
|
2038
|
+
function parseFieldValue(args) {
|
|
1949
2039
|
return checkValueAgainst(args, valueSchemas);
|
|
1950
2040
|
}
|
|
1951
2041
|
|
|
@@ -1955,9 +2045,14 @@ function checkValueAgainst(args, leaf) {
|
|
|
1955
2045
|
shape: args,
|
|
1956
2046
|
leaf: leaf
|
|
1957
2047
|
});
|
|
1958
|
-
if (schema === void 0) return
|
|
2048
|
+
if (schema === void 0) return {
|
|
2049
|
+
issues: [ `unknown field entry type ${args.entryType}` ]
|
|
2050
|
+
};
|
|
1959
2051
|
const result = v__namespace.safeParse(schema, args.value);
|
|
1960
|
-
|
|
2052
|
+
if (!result.success) return {
|
|
2053
|
+
issues: formatIssues(result.issues)
|
|
2054
|
+
};
|
|
2055
|
+
const postIssues = refTypeIssues({
|
|
1961
2056
|
entryType: args.entryType,
|
|
1962
2057
|
types: args.types,
|
|
1963
2058
|
value: args.value
|
|
@@ -1965,17 +2060,27 @@ function checkValueAgainst(args, leaf) {
|
|
|
1965
2060
|
entryType: args.entryType,
|
|
1966
2061
|
validation: args.validation,
|
|
1967
2062
|
value: args.value
|
|
1968
|
-
})
|
|
2063
|
+
});
|
|
2064
|
+
return postIssues !== void 0 ? {
|
|
2065
|
+
issues: postIssues
|
|
2066
|
+
} : {
|
|
2067
|
+
output: result.output
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
function issuesOf(check) {
|
|
2072
|
+
return "issues" in check ? check.issues : void 0;
|
|
1969
2073
|
}
|
|
1970
2074
|
|
|
1971
2075
|
function validateFieldValue(args) {
|
|
1972
|
-
const
|
|
1973
|
-
if (issues
|
|
2076
|
+
const check = checkValueAgainst(args, valueSchemas);
|
|
2077
|
+
if ("issues" in check) throw new FieldValueShapeError({
|
|
1974
2078
|
entryType: args.entryType,
|
|
1975
2079
|
entryName: args.entryName,
|
|
1976
|
-
issues: issues,
|
|
2080
|
+
issues: check.issues,
|
|
1977
2081
|
mode: "value"
|
|
1978
2082
|
});
|
|
2083
|
+
return check.output;
|
|
1979
2084
|
}
|
|
1980
2085
|
|
|
1981
2086
|
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}$`);
|
|
@@ -2004,7 +2109,7 @@ const AuthoringRefId = v__namespace.pipe(v__namespace.string(), v__namespace.che
|
|
|
2004
2109
|
};
|
|
2005
2110
|
|
|
2006
2111
|
function checkLiteralSeed(args) {
|
|
2007
|
-
return args.value === null ? [ "a literal seed cannot be null — omit `initialValue` to start the field empty" ] : checkValueAgainst(args, seedValueSchemas);
|
|
2112
|
+
return args.value === null ? [ "a literal seed cannot be null — omit `initialValue` to start the field empty" ] : issuesOf(checkValueAgainst(args, seedValueSchemas));
|
|
2008
2113
|
}
|
|
2009
2114
|
|
|
2010
2115
|
function validateFieldAppendItem(args) {
|
|
@@ -2033,13 +2138,15 @@ function validateFieldAppendItem(args) {
|
|
|
2033
2138
|
issues: typeIssues,
|
|
2034
2139
|
mode: "item"
|
|
2035
2140
|
});
|
|
2141
|
+
return result.output;
|
|
2036
2142
|
}
|
|
2037
2143
|
|
|
2038
2144
|
function formatIssues(issues, formatMessage = issue => issue.message) {
|
|
2039
|
-
|
|
2040
|
-
const keys =
|
|
2041
|
-
return `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(
|
|
2042
|
-
}
|
|
2145
|
+
const formatOne = (issue, prefix) => {
|
|
2146
|
+
const keys = [ ...prefix, ...issue.path?.map(p => p.key) ?? [] ], sub = issue.issues?.filter(candidate => candidate.expected !== "null");
|
|
2147
|
+
return sub !== void 0 && sub.length > 0 ? sub.flatMap(candidate => formatOne(candidate, keys)) : [ `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(issue)}` ];
|
|
2148
|
+
};
|
|
2149
|
+
return issues.flatMap(issue => formatOne(issue, []));
|
|
2043
2150
|
}
|
|
2044
2151
|
|
|
2045
2152
|
const NonEmpty = NonEmptyString, PositiveInt = v__namespace.pipe(v__namespace.number(), v__namespace.integer(), v__namespace.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -2390,7 +2497,18 @@ const StoredActionSchema = pinned()(v__namespace.strictObject({
|
|
|
2390
2497
|
filter: v__namespace.optional(ConditionSchema),
|
|
2391
2498
|
params: v__namespace.optional(v__namespace.array(ActionParamSchema)),
|
|
2392
2499
|
effects: v__namespace.optional(v__namespace.array(EffectSchema))
|
|
2393
|
-
})), AuthoringActionSchema = pinned()(v__namespace.lazy(input => typeof input == "object" && input !== null && "type" in input ? ClaimActionSchema : RawAuthoringActionSchema))
|
|
2500
|
+
})), AuthoringActionSchema = pinned()(v__namespace.lazy(input => typeof input == "object" && input !== null && "type" in input ? ClaimActionSchema : RawAuthoringActionSchema)), requirementBase = {
|
|
2501
|
+
name: NonEmpty,
|
|
2502
|
+
title: v__namespace.optional(v__namespace.string()),
|
|
2503
|
+
description: v__namespace.optional(v__namespace.string())
|
|
2504
|
+
}, GroqRequirementSchemaRaw = v__namespace.strictObject({
|
|
2505
|
+
...requirementBase,
|
|
2506
|
+
type: v__namespace.literal("groq"),
|
|
2507
|
+
query: ConditionSchema
|
|
2508
|
+
}), SingleSubjectRequirementSchemaRaw = v__namespace.strictObject({
|
|
2509
|
+
...requirementBase,
|
|
2510
|
+
type: v__namespace.literal("singleSubject")
|
|
2511
|
+
}), GroqRequirementSchema = pinned()(GroqRequirementSchemaRaw), StartRequirementSchema = pinned()(v__namespace.variant("type", [ GroqRequirementSchemaRaw, SingleSubjectRequirementSchemaRaw ]));
|
|
2394
2512
|
|
|
2395
2513
|
function activityFields({field: field, action: action, target: target, group: group}) {
|
|
2396
2514
|
return {
|
|
@@ -2401,7 +2519,7 @@ function activityFields({field: field, action: action, target: target, group: gr
|
|
|
2401
2519
|
group: v__namespace.optional(group),
|
|
2402
2520
|
target: v__namespace.optional(target),
|
|
2403
2521
|
filter: v__namespace.optional(ConditionSchema),
|
|
2404
|
-
requirements: v__namespace.optional(v__namespace.
|
|
2522
|
+
requirements: v__namespace.optional(v__namespace.array(GroqRequirementSchema)),
|
|
2405
2523
|
actions: v__namespace.optional(v__namespace.array(action)),
|
|
2406
2524
|
fields: v__namespace.optional(v__namespace.array(field))
|
|
2407
2525
|
};
|
|
@@ -2497,7 +2615,7 @@ function startFields(kind) {
|
|
|
2497
2615
|
return {
|
|
2498
2616
|
kind: kind,
|
|
2499
2617
|
filter: v__namespace.optional(ConditionSchema),
|
|
2500
|
-
|
|
2618
|
+
requirements: v__namespace.optional(v__namespace.array(StartRequirementSchema))
|
|
2501
2619
|
};
|
|
2502
2620
|
}
|
|
2503
2621
|
|
|
@@ -2636,6 +2754,13 @@ function checkActivities({def: def, i: i, activityNames: activityNames, issues:
|
|
|
2636
2754
|
})),
|
|
2637
2755
|
what: `action name in activity "${activity.name}"`,
|
|
2638
2756
|
issues: issues
|
|
2757
|
+
}), checkDuplicates({
|
|
2758
|
+
names: (activity.requirements ?? []).map((requirement, r) => ({
|
|
2759
|
+
name: requirement.name,
|
|
2760
|
+
path: [ ...path, "requirements", r, "name" ]
|
|
2761
|
+
})),
|
|
2762
|
+
what: `requirement name in activity "${activity.name}"`,
|
|
2763
|
+
issues: issues
|
|
2639
2764
|
}), checkStatusSetTargets({
|
|
2640
2765
|
activity: activity,
|
|
2641
2766
|
activityNames: activityNames,
|
|
@@ -2944,10 +3069,10 @@ function collectActivityConditionSites({activity: activity, path: path, stageFie
|
|
|
2944
3069
|
policy: "cascade",
|
|
2945
3070
|
fields: fields
|
|
2946
3071
|
});
|
|
2947
|
-
for (const [
|
|
2948
|
-
groq:
|
|
2949
|
-
path: [ ...path, "requirements",
|
|
2950
|
-
label: `activity "${activity.name}" requirement "${name}"`,
|
|
3072
|
+
for (const [index, requirement] of (activity.requirements ?? []).entries()) sites.push({
|
|
3073
|
+
groq: requirement.query,
|
|
3074
|
+
path: [ ...path, "requirements", index, "query" ],
|
|
3075
|
+
label: `activity "${activity.name}" requirement "${requirement.name}"`,
|
|
2951
3076
|
policy: "caller-bound",
|
|
2952
3077
|
fields: fields
|
|
2953
3078
|
});
|
|
@@ -3143,73 +3268,83 @@ function checkTriggeredActionParams(def, issues) {
|
|
|
3143
3268
|
}
|
|
3144
3269
|
|
|
3145
3270
|
function checkStart(def, issues) {
|
|
3146
|
-
if (def.start !== void 0 && (
|
|
3271
|
+
if (def.start !== void 0 && (checkDuplicates({
|
|
3272
|
+
names: (def.start.requirements ?? []).map((requirement, index) => ({
|
|
3273
|
+
name: requirement.name,
|
|
3274
|
+
path: [ "start", "requirements", index, "name" ]
|
|
3275
|
+
})),
|
|
3276
|
+
what: "start requirement name",
|
|
3277
|
+
issues: issues
|
|
3278
|
+
}), def.lifecycle === "child" && issues.push({
|
|
3147
3279
|
path: [ "start" ],
|
|
3148
3280
|
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'`"
|
|
3149
|
-
}), checkStartFilterReads(def, issues),
|
|
3150
|
-
def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
|
|
3281
|
+
}), checkStartFilterReads(def, issues), checkStartRequirementReads(def, issues),
|
|
3282
|
+
checkSingleSubjectRequirements(def, issues), def.start.kind === "autonomous")) for (const [n, entry] of (def.fields ?? []).entries()) entry.required !== !0 || isSubjectEntry(entry) || issues.push({
|
|
3151
3283
|
path: [ "fields", n, "required" ],
|
|
3152
3284
|
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)`
|
|
3153
3285
|
});
|
|
3154
3286
|
}
|
|
3155
3287
|
|
|
3156
|
-
function
|
|
3288
|
+
function checkSingleSubjectRequirements(def, issues) {
|
|
3289
|
+
const indexes = (def.start?.requirements ?? []).map((requirement, index) => ({
|
|
3290
|
+
requirement: requirement,
|
|
3291
|
+
index: index
|
|
3292
|
+
})).filter(({requirement: requirement}) => requirement.type === "singleSubject"), [first] = indexes;
|
|
3293
|
+
if (first === void 0) return;
|
|
3157
3294
|
const subject = (def.fields ?? []).find(isSubjectEntry);
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
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`
|
|
3163
|
-
});
|
|
3164
|
-
continue;
|
|
3165
|
-
}
|
|
3166
|
-
key === "allowed" && !isInputSourced(subject) && issues.push({
|
|
3167
|
-
path: [ "start", key ],
|
|
3168
|
-
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`
|
|
3295
|
+
if (subject === void 0) {
|
|
3296
|
+
issues.push({
|
|
3297
|
+
path: [ "start", "requirements", first.index ],
|
|
3298
|
+
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"
|
|
3169
3299
|
});
|
|
3300
|
+
return;
|
|
3170
3301
|
}
|
|
3302
|
+
isInputSourced(subject) || issues.push({
|
|
3303
|
+
path: [ "start", "requirements", first.index ],
|
|
3304
|
+
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`
|
|
3305
|
+
});
|
|
3171
3306
|
}
|
|
3172
3307
|
|
|
3173
3308
|
function checkStartFilterReads(def, issues) {
|
|
3174
3309
|
const filter = def.start?.filter;
|
|
3175
3310
|
filter !== void 0 && (conditionParameterNames(filter).has("fields") && issues.push({
|
|
3176
3311
|
path: [ "start", "filter" ],
|
|
3177
|
-
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
|
|
3312
|
+
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"
|
|
3178
3313
|
}), readsRootDocument(filter) && !(def.fields ?? []).some(isSubjectEntry) && issues.push({
|
|
3179
3314
|
path: [ "start", "filter" ],
|
|
3180
3315
|
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"
|
|
3181
3316
|
}));
|
|
3182
3317
|
}
|
|
3183
3318
|
|
|
3184
|
-
function
|
|
3185
|
-
const allowed = def.start?.allowed;
|
|
3186
|
-
if (allowed === void 0) return;
|
|
3319
|
+
function checkStartRequirementReads(def, issues) {
|
|
3187
3320
|
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())}`;
|
|
3188
|
-
for (const
|
|
3189
|
-
const
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3321
|
+
for (const [index, requirement] of (def.start?.requirements ?? []).entries()) if (requirement.type === "groq") {
|
|
3322
|
+
for (const read of conditionFieldReads(requirement.query)) {
|
|
3323
|
+
const entry = bindable.get(read.name);
|
|
3324
|
+
if (entry === void 0) {
|
|
3325
|
+
issues.push({
|
|
3326
|
+
path: [ "start", "requirements", index, "query" ],
|
|
3327
|
+
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}`
|
|
3328
|
+
});
|
|
3329
|
+
continue;
|
|
3330
|
+
}
|
|
3331
|
+
pushFieldReadPathIssue({
|
|
3332
|
+
where: "start requirement",
|
|
3333
|
+
read: {
|
|
3334
|
+
field: read.name,
|
|
3335
|
+
path: read.path
|
|
3336
|
+
},
|
|
3337
|
+
target: entry,
|
|
3338
|
+
path: [ "start", "requirements", index, "query" ],
|
|
3339
|
+
issues: issues,
|
|
3340
|
+
nodes: START_REQUIREMENT_VALUE_NODES
|
|
3194
3341
|
});
|
|
3195
|
-
continue;
|
|
3196
3342
|
}
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
field: read.name,
|
|
3201
|
-
path: read.path
|
|
3202
|
-
},
|
|
3203
|
-
target: entry,
|
|
3204
|
-
path: [ "start", "allowed" ],
|
|
3205
|
-
issues: issues,
|
|
3206
|
-
nodes: START_ALLOWED_VALUE_NODES
|
|
3343
|
+
readsRootDocument(requirement.query) && issues.push({
|
|
3344
|
+
path: [ "start", "requirements", index, "query" ],
|
|
3345
|
+
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"
|
|
3207
3346
|
});
|
|
3208
3347
|
}
|
|
3209
|
-
readsRootDocument(allowed) && issues.push({
|
|
3210
|
-
path: [ "start", "allowed" ],
|
|
3211
|
-
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"
|
|
3212
|
-
});
|
|
3213
3348
|
}
|
|
3214
3349
|
|
|
3215
3350
|
function checkGroups(def, issues) {
|
|
@@ -3734,7 +3869,7 @@ const SCALAR = {
|
|
|
3734
3869
|
kind: "rows",
|
|
3735
3870
|
of: shape.of ?? []
|
|
3736
3871
|
})
|
|
3737
|
-
},
|
|
3872
|
+
}, START_REQUIREMENT_VALUE_NODES = {
|
|
3738
3873
|
...VALUE_NODES,
|
|
3739
3874
|
"doc.ref": () => GDR_VALUE,
|
|
3740
3875
|
subject: () => GDR_VALUE
|
|
@@ -3823,6 +3958,8 @@ exports.ACTIVITY_STATUSES = ACTIVITY_STATUSES;
|
|
|
3823
3958
|
|
|
3824
3959
|
exports.ACTOR_KINDS = ACTOR_KINDS;
|
|
3825
3960
|
|
|
3961
|
+
exports.ANONYMOUS_IDENTITY = ANONYMOUS_IDENTITY;
|
|
3962
|
+
|
|
3826
3963
|
exports.ActorShape = ActorShape;
|
|
3827
3964
|
|
|
3828
3965
|
exports.AuthoringActionSchema = AuthoringActionSchema;
|
|
@@ -3909,11 +4046,11 @@ exports.RESOURCE_ALIAS_NAME_SOURCE = RESOURCE_ALIAS_NAME_SOURCE;
|
|
|
3909
4046
|
|
|
3910
4047
|
exports.ReaderModelAcknowledgementError = ReaderModelAcknowledgementError;
|
|
3911
4048
|
|
|
3912
|
-
exports.START_ALLOWED_VARS = START_ALLOWED_VARS;
|
|
3913
|
-
|
|
3914
4049
|
exports.START_FILTER_VARS = START_FILTER_VARS;
|
|
3915
4050
|
|
|
3916
|
-
exports.
|
|
4051
|
+
exports.START_REQUIREMENT_VARS = START_REQUIREMENT_VARS;
|
|
4052
|
+
|
|
4053
|
+
exports.SYSTEM_IDENTITY = SYSTEM_IDENTITY;
|
|
3917
4054
|
|
|
3918
4055
|
exports.SpawnContractsInvalidError = SpawnContractsInvalidError;
|
|
3919
4056
|
|
|
@@ -3937,12 +4074,12 @@ exports.assertReadableModel = assertReadableModel;
|
|
|
3937
4074
|
|
|
3938
4075
|
exports.assertReaderModelAcknowledgement = assertReaderModelAcknowledgement;
|
|
3939
4076
|
|
|
3940
|
-
exports.checkFieldValue = checkFieldValue;
|
|
3941
|
-
|
|
3942
4077
|
exports.checkWorkflowInvariants = checkWorkflowInvariants;
|
|
3943
4078
|
|
|
3944
4079
|
exports.choiceValueIssues = choiceValueIssues;
|
|
3945
4080
|
|
|
4081
|
+
exports.classifyPrincipalId = classifyPrincipalId;
|
|
4082
|
+
|
|
3946
4083
|
exports.clientConfigFromResource = clientConfigFromResource;
|
|
3947
4084
|
|
|
3948
4085
|
exports.conditionEffectReads = conditionEffectReads;
|
|
@@ -4033,6 +4170,8 @@ exports.isUnprimed = isUnprimed;
|
|
|
4033
4170
|
|
|
4034
4171
|
exports.labelFor = labelFor;
|
|
4035
4172
|
|
|
4173
|
+
exports.lakePrincipalId = lakePrincipalId;
|
|
4174
|
+
|
|
4036
4175
|
exports.minReaderModelOf = minReaderModelOf;
|
|
4037
4176
|
|
|
4038
4177
|
exports.modelStampFor = modelStampFor;
|
|
@@ -4043,6 +4182,8 @@ exports.parentRef = parentRef;
|
|
|
4043
4182
|
|
|
4044
4183
|
exports.parseDefinitionSnapshot = parseDefinitionSnapshot;
|
|
4045
4184
|
|
|
4185
|
+
exports.parseFieldValue = parseFieldValue;
|
|
4186
|
+
|
|
4046
4187
|
exports.parseGdr = parseGdr;
|
|
4047
4188
|
|
|
4048
4189
|
exports.parseOrThrow = parseOrThrow;
|