@sanity/workflow-engine 0.31.0 → 0.33.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 +323 -0
- package/DATAMODEL.md +223 -9
- package/README.md +2 -2
- package/dist/_chunks-cjs/invariants.cjs +2113 -1940
- package/dist/_chunks-es/invariants.js +2041 -1918
- package/dist/define.cjs +6 -3
- package/dist/define.d.cts +668 -1195
- package/dist/define.d.ts +668 -1195
- package/dist/define.js +7 -4
- package/dist/index.cjs +2513 -968
- package/dist/index.d.cts +1467 -1923
- package/dist/index.d.ts +1467 -1923
- package/dist/index.js +2432 -927
- package/package.json +6 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
2
|
|
|
3
|
-
import { conditionOutcome, runGroq as runGroq$1, evaluateConditionOutcome as evaluateConditionOutcome$1 } from "@sanity/groq-condition-describe";
|
|
3
|
+
import { _exhaustiveOptions, conditionOutcome, runGroq as runGroq$1, evaluateConditionOutcome as evaluateConditionOutcome$1 } from "@sanity/groq-condition-describe";
|
|
4
4
|
|
|
5
5
|
import { parse } from "groq-js";
|
|
6
6
|
|
|
@@ -26,6 +26,37 @@ function driverKind(actor) {
|
|
|
26
26
|
return actor.kind === "person" || actor.kind === "agent" ? actor.kind : "service";
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function isFilterScopedOut(entry) {
|
|
30
|
+
return entry.status === "skipped" && entry.startedAt === void 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function liveSubworkflows(host) {
|
|
34
|
+
return (host.subworkflows ?? []).filter(row => row.resolved === void 0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolvedChildStatus(child) {
|
|
38
|
+
if (!(child.completedAt === void 0 || child.completedAt === null)) return child.abortedAt !== void 0 && child.abortedAt !== null ? "aborted" : "done";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function condemnedSubworkflows(host) {
|
|
42
|
+
return liveSubworkflows(host).filter(row => row.abortPending !== void 0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function condemnSubworkflow(row, owed) {
|
|
46
|
+
row.abortPending !== void 0 || row.resolved !== void 0 || (row.abortPending = {
|
|
47
|
+
at: owed.at,
|
|
48
|
+
reason: owed.reason
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function findOpenStageEntry(host) {
|
|
53
|
+
return host.stages.find(s => s.name === host.currentStage && s.exitedAt === void 0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function findCurrentActivityEntry(host, activityName) {
|
|
57
|
+
return findOpenStageEntry(host)?.activities.find(a => a.name === activityName);
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
function errorMessage(err) {
|
|
30
61
|
return (err instanceof Error ? err.message : String(err)).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
31
62
|
}
|
|
@@ -339,107 +370,191 @@ function tagScopeFilter() {
|
|
|
339
370
|
return "tag == $tag";
|
|
340
371
|
}
|
|
341
372
|
|
|
342
|
-
const NonEmptyString$1 = v.pipe(v.string(), v.
|
|
373
|
+
const NonEmptyString$1 = v.pipe(v.string(), v.minLength(1, "must be a non-empty string"));
|
|
343
374
|
|
|
344
|
-
function
|
|
345
|
-
return value
|
|
346
|
-
try {
|
|
347
|
-
return validate(value), !0;
|
|
348
|
-
} catch {
|
|
349
|
-
return !1;
|
|
350
|
-
}
|
|
351
|
-
};
|
|
375
|
+
function isParseableInstant(value) {
|
|
376
|
+
return typeof value == "string" && !Number.isNaN(Date.parse(value));
|
|
352
377
|
}
|
|
353
378
|
|
|
354
|
-
const
|
|
379
|
+
const IsoTimestamp = v.pipe(v.string(), v.check(s => isParseableInstant(s), "must be an ISO-8601 datetime string"));
|
|
355
380
|
|
|
356
|
-
function
|
|
357
|
-
return
|
|
381
|
+
function tolerantObject() {
|
|
382
|
+
return (entries, ..._exact) => v.looseObject(entries);
|
|
358
383
|
}
|
|
359
384
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}), v.object({
|
|
364
|
-
type: v.literal("canvas"),
|
|
365
|
-
id: NonEmptyString$1
|
|
366
|
-
}), v.object({
|
|
367
|
-
type: v.literal("media-library"),
|
|
368
|
-
id: NonEmptyString$1
|
|
369
|
-
}), v.object({
|
|
370
|
-
type: v.literal("dashboard"),
|
|
371
|
-
id: NonEmptyString$1
|
|
372
|
-
}) ]), ResourceBindingSchema = v.object({
|
|
373
|
-
name: v.pipe(NonEmptyString$1, v.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
374
|
-
resource: WorkflowResourceSchema
|
|
375
|
-
}), DefinitionSchema = v.custom(input => typeof input == "object" && input !== null && typeof input.name == "string", "expected a workflow definition (an object with a string `name`)"), DeploymentSchema = v.object({
|
|
376
|
-
name: lakeSegment("name"),
|
|
377
|
-
expectedMinReaderModel: v.optional(v.custom(() => !0), void 0),
|
|
378
|
-
tag: lakeSegment("tag"),
|
|
379
|
-
workflowResource: WorkflowResourceSchema,
|
|
380
|
-
resourceAliases: v.optional(v.pipe(v.array(ResourceBindingSchema), v.check(bindings => duplicateHandleMessage(bindings) === void 0, issue => duplicateHandleMessage(issue.input) ?? "duplicate resource handle name"))),
|
|
381
|
-
definitions: v.pipe(v.array(DefinitionSchema), v.minLength(1, "a deployment needs at least one definition"))
|
|
382
|
-
});
|
|
385
|
+
function tolerantEntries() {
|
|
386
|
+
return (entries, ..._exact) => entries;
|
|
387
|
+
}
|
|
383
388
|
|
|
384
|
-
function
|
|
385
|
-
|
|
386
|
-
for (const item of items) {
|
|
387
|
-
const key = keyOf(item), earlier = seen.get(key);
|
|
388
|
-
if (earlier !== void 0) return [ earlier, item ];
|
|
389
|
-
seen.set(key, item);
|
|
390
|
-
}
|
|
389
|
+
function schemaTreeShape(schema) {
|
|
390
|
+
return walkSchemaShape(schema, /* @__PURE__ */ new Set);
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
function
|
|
394
|
-
|
|
395
|
-
if (
|
|
393
|
+
function walkSchemaShape(node, path) {
|
|
394
|
+
if (typeof node != "object" || node === null) return "unknown";
|
|
395
|
+
if (path.has(node)) return "(circular)";
|
|
396
|
+
path.add(node);
|
|
397
|
+
try {
|
|
398
|
+
const schema = node;
|
|
399
|
+
return containerShape(schema, path) ?? leafShape(schema);
|
|
400
|
+
} finally {
|
|
401
|
+
path.delete(node);
|
|
402
|
+
}
|
|
396
403
|
}
|
|
397
404
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
405
|
+
const objectWalker = (schema, path) => objectEntriesShape(schema.entries, path), unionWalker = (schema, path) => ({
|
|
406
|
+
union: schema.options.map(option => walkSchemaShape(option, path))
|
|
407
|
+
}), unwrapWalker = (schema, path) => walkSchemaShape(schema.wrapped, path), CONTAINER_WALKERS = {
|
|
408
|
+
strict_object: objectWalker,
|
|
409
|
+
loose_object: objectWalker,
|
|
410
|
+
object: objectWalker,
|
|
411
|
+
array: (schema, path) => ({
|
|
412
|
+
array: walkSchemaShape(schema.item, path)
|
|
413
|
+
}),
|
|
414
|
+
record: (schema, path) => ({
|
|
415
|
+
record: walkSchemaShape(schema.value, path)
|
|
416
|
+
}),
|
|
417
|
+
union: unionWalker,
|
|
418
|
+
variant: unionWalker,
|
|
419
|
+
lazy: (schema, path) => walkSchemaShape(schema.getter(void 0), path),
|
|
420
|
+
exact_optional: unwrapWalker,
|
|
421
|
+
optional: unwrapWalker,
|
|
422
|
+
nullable: unwrapWalker
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
function containerShape(schema, path) {
|
|
426
|
+
return CONTAINER_WALKERS[String(schema.type)]?.(schema, path);
|
|
401
427
|
}
|
|
402
428
|
|
|
403
|
-
function
|
|
404
|
-
|
|
429
|
+
function objectEntriesShape(entries, path) {
|
|
430
|
+
const out = {};
|
|
431
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
432
|
+
const optional = isOptionalEntry(entry), wrapped = optional ? entry.wrapped : entry;
|
|
433
|
+
out[optional ? `${key}?` : key] = walkSchemaShape(wrapped, path);
|
|
434
|
+
}
|
|
435
|
+
return Object.fromEntries(Object.entries(out).sort(([a], [b]) => a < b ? -1 : 1));
|
|
405
436
|
}
|
|
406
437
|
|
|
407
|
-
function
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
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`;
|
|
438
|
+
function isOptionalEntry(entry) {
|
|
439
|
+
if (typeof entry != "object" || entry === null) return !1;
|
|
440
|
+
const kind = entry.type;
|
|
441
|
+
return kind === "exact_optional" || kind === "optional";
|
|
412
442
|
}
|
|
413
443
|
|
|
414
|
-
const
|
|
415
|
-
deployments: v.pipe(v.array(DeploymentSchema), v.minLength(1, "a config needs at least one deployment"), v.check(deployments => duplicateNameMessage(deployments) === void 0, issue => duplicateNameMessage(issue.input) ?? "duplicate deployment name"), v.check(deployments => partitionCollisionMessage(deployments) === void 0, issue => partitionCollisionMessage(issue.input) ?? "duplicate deployment partition")),
|
|
416
|
-
telemetry: v.optional(TelemetryLoggerSchema)
|
|
417
|
-
});
|
|
444
|
+
const LEAF_KINDS = /* @__PURE__ */ new Set([ "string", "number", "boolean", "null", "undefined", "unknown", "any" ]);
|
|
418
445
|
|
|
419
|
-
function
|
|
420
|
-
|
|
446
|
+
function leafShape(schema) {
|
|
447
|
+
if (schema.type === "picklist") return schema.options.join(" | ");
|
|
448
|
+
if (schema.type === "literal") return `literal ${String(schema.literal)}`;
|
|
449
|
+
if (schema.type === "custom") return typeof schema.message == "string" ? `custom(${schema.message})` : "custom";
|
|
450
|
+
if (!LEAF_KINDS.has(String(schema.type))) throw new Error(`schemaTreeShape: unhandled schema kind "${String(schema.type)}" — extend the walker before regenerating the model ledger`);
|
|
451
|
+
return String(schema.type);
|
|
421
452
|
}
|
|
422
453
|
|
|
423
|
-
|
|
454
|
+
function formatValidationError(label, issues) {
|
|
455
|
+
const lines = issues.map(issue => ` - ${issue.path.length === 0 ? "(root)" : formatIssuePath(issue.path)}: ${issue.message}`);
|
|
456
|
+
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
457
|
+
}
|
|
424
458
|
|
|
425
|
-
function
|
|
426
|
-
return
|
|
459
|
+
function issuesFromValibot(issues) {
|
|
460
|
+
return issues.map(issue => ({
|
|
461
|
+
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
462
|
+
message: issue.message
|
|
463
|
+
}));
|
|
427
464
|
}
|
|
428
465
|
|
|
429
|
-
function
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
466
|
+
function formatIssuePath(path) {
|
|
467
|
+
let out = "";
|
|
468
|
+
for (const seg of path) typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
469
|
+
return out;
|
|
470
|
+
}
|
|
433
471
|
|
|
434
|
-
|
|
435
|
-
|
|
472
|
+
class PersistedDocShapeError extends WorkflowError {
|
|
473
|
+
documentId;
|
|
474
|
+
documentType;
|
|
475
|
+
issues;
|
|
476
|
+
constructor(args) {
|
|
477
|
+
super("persisted-doc-shape", formatValidationError(`Persisted ${args.documentType} document "${args.documentId}"`, args.issues)),
|
|
478
|
+
this.name = "PersistedDocShapeError", this.documentId = args.documentId, this.documentType = args.documentType,
|
|
479
|
+
this.issues = args.issues;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
436
482
|
|
|
437
|
-
|
|
438
|
-
|
|
483
|
+
function parsePersistedDoc(args) {
|
|
484
|
+
const result = v.safeParse(args.schema, args.doc);
|
|
485
|
+
if (!result.success) throw new PersistedDocShapeError({
|
|
486
|
+
documentId: documentIdOf(args.doc),
|
|
487
|
+
documentType: args.docType,
|
|
488
|
+
issues: issuesFromValibot(result.issues)
|
|
489
|
+
});
|
|
490
|
+
return result.output;
|
|
491
|
+
}
|
|
439
492
|
|
|
440
|
-
|
|
441
|
-
|
|
493
|
+
function documentIdOf(doc) {
|
|
494
|
+
if (typeof doc == "object" && doc !== null) {
|
|
495
|
+
const id = doc._id;
|
|
496
|
+
if (typeof id == "string") return id;
|
|
442
497
|
}
|
|
498
|
+
return "(unknown id)";
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const ACTOR_KINDS = [ "person", "agent", "system" ];
|
|
502
|
+
|
|
503
|
+
function releaseDocId(releaseName) {
|
|
504
|
+
return `_.releases.${releaseName}`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function releaseRef({res: res, releaseName: releaseName}) {
|
|
508
|
+
if (releaseName.length === 0) throw new ContractViolationError("releaseRef: releaseName must be a non-empty release name");
|
|
509
|
+
return {
|
|
510
|
+
id: gdrFromResource(res, releaseDocId(releaseName)),
|
|
511
|
+
type: "system.release",
|
|
512
|
+
releaseName: releaseName
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function isAlwaysArrayFieldKind(kind) {
|
|
517
|
+
return kind === "doc.refs" || kind === "assignee" || kind === "assignees" || kind === "array";
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function isSingleDocRefKind(kind) {
|
|
521
|
+
return kind === "doc.ref" || kind === "subject";
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function refKindAcceptsTypes(kind) {
|
|
525
|
+
return isSingleDocRefKind(kind) || kind === "doc.refs";
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function assignmentKindAcceptsRoles(kind) {
|
|
529
|
+
return kind === "assignee" || kind === "assignees";
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function isSingleDocRefEntry(entry) {
|
|
533
|
+
return isSingleDocRefKind(entry._type);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function isAssignmentFieldEntry(entry) {
|
|
537
|
+
return assignmentKindAcceptsRoles(entry._type);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function isTodoListItem(row) {
|
|
541
|
+
if (typeof row != "object" || row === null) return !1;
|
|
542
|
+
const candidate = row, status = candidate.status;
|
|
543
|
+
return typeof candidate._key == "string" && typeof candidate.label == "string" && (status == null || typeof status == "string");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function declaredRowColumns(entry) {
|
|
547
|
+
if (("_type" in entry ? entry._type : entry.type) === "array") return new Set((("of" in entry ? entry.of : void 0) ?? []).map(shape => shape.name));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function isTodoListEntry(entry) {
|
|
551
|
+
const columns = declaredRowColumns(entry);
|
|
552
|
+
return columns !== void 0 && columns.has("label") && columns.has("status");
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function isNotesEntry(entry) {
|
|
556
|
+
const columns = declaredRowColumns(entry);
|
|
557
|
+
return columns !== void 0 && columns.has("body") && columns.has("actor") && columns.has("at");
|
|
443
558
|
}
|
|
444
559
|
|
|
445
560
|
const UNIVERSAL_ROLE_ALIAS_KEY = "$all";
|
|
@@ -470,2183 +585,2157 @@ function actorFulfillsRole({actorRoles: actorRoles, required: required, aliases:
|
|
|
470
585
|
return actorRoles.some(role => accepted.has(role));
|
|
471
586
|
}
|
|
472
587
|
|
|
473
|
-
function
|
|
474
|
-
return
|
|
588
|
+
function normalizeAssignmentMembers(value) {
|
|
589
|
+
return value === null ? [] : Array.isArray(value) ? value : [ value ];
|
|
475
590
|
}
|
|
476
591
|
|
|
477
|
-
function
|
|
478
|
-
|
|
479
|
-
if (serialized === void 0) throw new Error(`groq tag cannot serialize ${typeof value} — interpolate JSON-representable values only`);
|
|
480
|
-
return serialized;
|
|
592
|
+
function assignmentMembers(entries) {
|
|
593
|
+
return entries.flatMap(entry => isAssignmentFieldEntry(entry) ? normalizeAssignmentMembers(entry.value) : []);
|
|
481
594
|
}
|
|
482
595
|
|
|
483
|
-
function
|
|
484
|
-
|
|
485
|
-
checkReservedRoleAliasKeys(authoring.roleAliases, issues);
|
|
486
|
-
const roleAliases = normalizeRoleAliases(authoring.roleAliases), ctx = {
|
|
487
|
-
issues: issues,
|
|
488
|
-
claimFields: /* @__PURE__ */ new Map,
|
|
489
|
-
claimedFields: /* @__PURE__ */ new Set,
|
|
490
|
-
roleAliases: roleAliases
|
|
491
|
-
}, workflowFields2 = desugarFieldEntries({
|
|
492
|
-
entries: authoring.fields,
|
|
493
|
-
path: [ "fields" ],
|
|
494
|
-
ctx: ctx
|
|
495
|
-
}), workflowLayer = layerOf(workflowFields2), stages = authoring.stages.map((stage, i) => {
|
|
496
|
-
const path = [ "stages", i ], stageFields2 = desugarFieldEntries({
|
|
497
|
-
entries: stage.fields,
|
|
498
|
-
path: [ ...path, "fields" ],
|
|
499
|
-
ctx: ctx
|
|
500
|
-
}), stageEnv = {
|
|
501
|
-
layers: [ {
|
|
502
|
-
scope: "stage",
|
|
503
|
-
entries: layerOf(stageFields2)
|
|
504
|
-
}, {
|
|
505
|
-
scope: "workflow",
|
|
506
|
-
entries: workflowLayer
|
|
507
|
-
} ]
|
|
508
|
-
}, activities = (stage.activities ?? []).map((activity, j) => desugarActivity({
|
|
509
|
-
activity: activity,
|
|
510
|
-
path: [ ...path, "activities", j ],
|
|
511
|
-
stageEnv: stageEnv,
|
|
512
|
-
ctx: ctx
|
|
513
|
-
})), transitions = (stage.transitions ?? []).map(transition => desugarTransition({
|
|
514
|
-
transition: transition
|
|
515
|
-
})), editable = desugarStageEditable({
|
|
516
|
-
overrides: stage.editable,
|
|
517
|
-
inScope: editableFieldNames({
|
|
518
|
-
workflowFields: workflowFields2,
|
|
519
|
-
stageFields: stageFields2,
|
|
520
|
-
activities: activities
|
|
521
|
-
}),
|
|
522
|
-
path: [ ...path, "editable" ],
|
|
523
|
-
ctx: ctx
|
|
524
|
-
});
|
|
525
|
-
return {
|
|
526
|
-
...stripUndefined({
|
|
527
|
-
name: stage.name,
|
|
528
|
-
semantics: stage.semantics,
|
|
529
|
-
title: stage.title,
|
|
530
|
-
description: stage.description,
|
|
531
|
-
groups: stage.groups,
|
|
532
|
-
guards: stage.guards?.map(desugarGuard)
|
|
533
|
-
}),
|
|
534
|
-
...stageFields2 ? {
|
|
535
|
-
fields: stageFields2
|
|
536
|
-
} : {},
|
|
537
|
-
...activities.length > 0 ? {
|
|
538
|
-
activities: activities
|
|
539
|
-
} : {},
|
|
540
|
-
...transitions.length > 0 ? {
|
|
541
|
-
transitions: transitions
|
|
542
|
-
} : {},
|
|
543
|
-
...editable ? {
|
|
544
|
-
editable: editable
|
|
545
|
-
} : {}
|
|
546
|
-
};
|
|
547
|
-
});
|
|
548
|
-
return checkUnclaimedClaimFields(ctx), {
|
|
549
|
-
definition: {
|
|
550
|
-
...stripUndefined({
|
|
551
|
-
name: authoring.name,
|
|
552
|
-
semantics: authoring.semantics,
|
|
553
|
-
title: authoring.title,
|
|
554
|
-
description: authoring.description,
|
|
555
|
-
groups: authoring.groups,
|
|
556
|
-
lifecycle: authoring.lifecycle,
|
|
557
|
-
start: desugarStart(authoring.start),
|
|
558
|
-
initialStage: authoring.initialStage,
|
|
559
|
-
predicates: authoring.predicates,
|
|
560
|
-
roleAliases: roleAliases
|
|
561
|
-
}),
|
|
562
|
-
...workflowFields2 ? {
|
|
563
|
-
fields: workflowFields2
|
|
564
|
-
} : {},
|
|
565
|
-
stages: stages
|
|
566
|
-
},
|
|
567
|
-
issues: ctx.issues
|
|
568
|
-
};
|
|
596
|
+
function assignmentState(members) {
|
|
597
|
+
return members.length === 0 ? "unrouted" : members.some(member => member.type === "user") ? "held" : "routed";
|
|
569
598
|
}
|
|
570
599
|
|
|
571
|
-
function
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
...start.filter !== void 0 ? {
|
|
575
|
-
filter: start.filter
|
|
576
|
-
} : {},
|
|
577
|
-
...start.requirements !== void 0 ? {
|
|
578
|
-
requirements: start.requirements
|
|
579
|
-
} : {}
|
|
580
|
-
};
|
|
600
|
+
function activeAssignmentMembers(members) {
|
|
601
|
+
const users = members.filter(member => member.type === "user");
|
|
602
|
+
return users.length > 0 ? users : members;
|
|
581
603
|
}
|
|
582
604
|
|
|
583
|
-
function
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
message: `role alias key "${key}" uses the reserved "$" prefix — that namespace is the engine's stored spelling for the universal fulfiller. Use "*" to mean "fulfills any gate", or rename the role.`
|
|
587
|
-
});
|
|
605
|
+
function assignmentMatch(members, identity) {
|
|
606
|
+
const active = activeAssignmentMembers(members);
|
|
607
|
+
return active.some(member => member.type === "user" && member.id === identity.userId) ? "user" : active.some(member => member.type === "role" && identity.roles.includes(member.role)) ? "role" : void 0;
|
|
588
608
|
}
|
|
589
609
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
name: "label",
|
|
593
|
-
title: "Label"
|
|
594
|
-
}, {
|
|
595
|
-
type: "string",
|
|
596
|
-
name: "status",
|
|
597
|
-
title: "Status"
|
|
598
|
-
}, {
|
|
599
|
-
type: "assignee",
|
|
600
|
-
name: "assignee",
|
|
601
|
-
title: "Assignee"
|
|
602
|
-
}, {
|
|
603
|
-
type: "date",
|
|
604
|
-
name: "dueDate",
|
|
605
|
-
title: "Due date"
|
|
606
|
-
} ], NOTES_OF = [ {
|
|
607
|
-
type: "text",
|
|
608
|
-
name: "body",
|
|
609
|
-
title: "Body"
|
|
610
|
-
}, {
|
|
611
|
-
type: "actor",
|
|
612
|
-
name: "actor",
|
|
613
|
-
title: "Actor"
|
|
614
|
-
}, {
|
|
615
|
-
type: "datetime",
|
|
616
|
-
name: "at",
|
|
617
|
-
title: "At"
|
|
618
|
-
} ];
|
|
619
|
-
|
|
620
|
-
function desugarFieldEntries({entries: entries, path: path, ctx: ctx}) {
|
|
621
|
-
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => desugarFieldEntry({
|
|
622
|
-
entry: entry,
|
|
623
|
-
path: [ ...path, i ],
|
|
624
|
-
ctx: ctx
|
|
625
|
-
}));
|
|
610
|
+
function identityMatchesAssignment(members, identity) {
|
|
611
|
+
return assignmentMatch(members, identity) !== void 0;
|
|
626
612
|
}
|
|
627
613
|
|
|
628
|
-
function
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
});
|
|
634
|
-
if (entry.type === "todoList" || entry.type === "notes") return desugarListField({
|
|
635
|
-
entry: entry,
|
|
636
|
-
path: path,
|
|
637
|
-
ctx: ctx
|
|
638
|
-
});
|
|
639
|
-
const editable = normalizeEditable({
|
|
640
|
-
editable: entry.editable,
|
|
641
|
-
path: [ ...path, "editable" ],
|
|
642
|
-
ctx: ctx
|
|
643
|
-
});
|
|
644
|
-
return {
|
|
645
|
-
...stripUndefined({
|
|
646
|
-
type: entry.type,
|
|
647
|
-
name: entry.name,
|
|
648
|
-
title: entry.title,
|
|
649
|
-
description: entry.description,
|
|
650
|
-
group: normalizeGroup(entry.group),
|
|
651
|
-
required: entry.required,
|
|
652
|
-
initialValue: entry.initialValue,
|
|
653
|
-
editable: editable,
|
|
654
|
-
options: entry.options,
|
|
655
|
-
validation: entry.validation,
|
|
656
|
-
types: entry.types,
|
|
657
|
-
roles: entry.roles,
|
|
658
|
-
fields: entry.fields,
|
|
659
|
-
of: entry.of
|
|
660
|
-
})
|
|
614
|
+
function assignmentStateCounts(assignments, identity) {
|
|
615
|
+
const counts = {
|
|
616
|
+
unrouted: 0,
|
|
617
|
+
routed: 0,
|
|
618
|
+
held: 0
|
|
661
619
|
};
|
|
620
|
+
for (const members of assignments) {
|
|
621
|
+
const state = assignmentState(members);
|
|
622
|
+
state === "unrouted" ? counts.unrouted += 1 : identityMatchesAssignment(members, identity) && (counts[state] += 1);
|
|
623
|
+
}
|
|
624
|
+
return counts;
|
|
662
625
|
}
|
|
663
626
|
|
|
664
|
-
function
|
|
665
|
-
|
|
666
|
-
...stripUndefined({
|
|
667
|
-
name: entry.name,
|
|
668
|
-
title: entry.title,
|
|
669
|
-
description: entry.description,
|
|
670
|
-
group: normalizeGroup(entry.group)
|
|
671
|
-
}),
|
|
672
|
-
type: "actor"
|
|
673
|
-
};
|
|
674
|
-
return ctx.claimFields.set(desugared, {
|
|
675
|
-
name: entry.name,
|
|
676
|
-
path: path
|
|
677
|
-
}), desugared;
|
|
627
|
+
function openActivityAssignments(instance) {
|
|
628
|
+
return (findOpenStageEntry(instance)?.activities ?? []).filter(activity => activity.status === "active").map(activity => assignmentMembers(activity.fields ?? []));
|
|
678
629
|
}
|
|
679
630
|
|
|
680
|
-
function
|
|
681
|
-
|
|
682
|
-
editable: entry.editable,
|
|
683
|
-
path: [ ...path, "editable" ],
|
|
684
|
-
ctx: ctx
|
|
685
|
-
});
|
|
686
|
-
return {
|
|
687
|
-
...stripUndefined({
|
|
688
|
-
name: entry.name,
|
|
689
|
-
title: entry.title,
|
|
690
|
-
description: entry.description,
|
|
691
|
-
group: normalizeGroup(entry.group),
|
|
692
|
-
required: entry.required,
|
|
693
|
-
initialValue: entry.initialValue,
|
|
694
|
-
editable: editable
|
|
695
|
-
}),
|
|
696
|
-
type: "array",
|
|
697
|
-
of: entry.type === "todoList" ? TODOLIST_OF : NOTES_OF
|
|
698
|
-
};
|
|
631
|
+
function instanceAssignmentStateCounts(instance, identity) {
|
|
632
|
+
return assignmentStateCounts(openActivityAssignments(instance), identity);
|
|
699
633
|
}
|
|
700
634
|
|
|
701
|
-
function
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
message: "editable: [] names no roles — use `true` to open the field to anyone in its scope, or list at least one role"
|
|
709
|
-
});
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
return condition;
|
|
713
|
-
}
|
|
714
|
-
return editable;
|
|
635
|
+
function actorMatchesAssignment(args) {
|
|
636
|
+
const actor = args.actor;
|
|
637
|
+
return actor === void 0 ? !1 : activeAssignmentMembers(args.members).some(member => member.type === "user" ? member.id === actor.id : actorFulfillsRole({
|
|
638
|
+
actorRoles: actor.roles,
|
|
639
|
+
required: member.role,
|
|
640
|
+
aliases: args.roleAliases
|
|
641
|
+
}));
|
|
715
642
|
}
|
|
716
643
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
644
|
+
const ANONYMOUS_IDENTITY = "<anonymous>", SYSTEM_IDENTITY = "<system>", E_PREFIXED_PROJECT_ID = /^e-(.+)$/;
|
|
645
|
+
|
|
646
|
+
function classifyPrincipalId(id) {
|
|
647
|
+
if (id === ANONYMOUS_IDENTITY || id === SYSTEM_IDENTITY) return {
|
|
648
|
+
namespace: "sentinel"
|
|
649
|
+
};
|
|
650
|
+
if (id.startsWith("g")) return {
|
|
651
|
+
namespace: "global",
|
|
652
|
+
globalId: id
|
|
653
|
+
};
|
|
654
|
+
if (id.startsWith("p-")) return {
|
|
655
|
+
namespace: "robot",
|
|
656
|
+
globalId: id
|
|
657
|
+
};
|
|
658
|
+
const embeddedGlobal = E_PREFIXED_PROJECT_ID.exec(id)?.[1];
|
|
659
|
+
return embeddedGlobal !== void 0 ? embeddedGlobal.startsWith("g") ? {
|
|
660
|
+
namespace: "project",
|
|
661
|
+
globalId: embeddedGlobal
|
|
662
|
+
} : {
|
|
663
|
+
namespace: "unknown"
|
|
664
|
+
} : id.startsWith("p") ? {
|
|
665
|
+
namespace: "project"
|
|
666
|
+
} : {
|
|
667
|
+
namespace: "unknown"
|
|
720
668
|
};
|
|
721
|
-
collect(workflowFields2), collect(stageFields2);
|
|
722
|
-
for (const activity of activities) collect(activity.fields);
|
|
723
|
-
return names;
|
|
724
669
|
}
|
|
725
670
|
|
|
726
|
-
function
|
|
727
|
-
if (
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
continue;
|
|
736
|
-
}
|
|
737
|
-
const normalized = normalizeEditable({
|
|
738
|
-
editable: value,
|
|
739
|
-
path: [ ...path, name ],
|
|
740
|
-
ctx: ctx
|
|
741
|
-
});
|
|
742
|
-
normalized !== void 0 && (out[name] = normalized);
|
|
671
|
+
function directoryBridgeId(sanityUserId) {
|
|
672
|
+
if (typeof sanityUserId == "string") return classifyPrincipalId(sanityUserId).namespace === "global" ? sanityUserId : void 0;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function firstCarriedGlobalId(candidates) {
|
|
676
|
+
for (const candidate of candidates) {
|
|
677
|
+
if (candidate === void 0) continue;
|
|
678
|
+
const {globalId: globalId} = classifyPrincipalId(candidate);
|
|
679
|
+
if (globalId !== void 0) return globalId;
|
|
743
680
|
}
|
|
744
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
745
681
|
}
|
|
746
682
|
|
|
747
|
-
function
|
|
748
|
-
return
|
|
683
|
+
function lakePrincipalId(args) {
|
|
684
|
+
return args.localPrincipalId ?? args.actor.id;
|
|
749
685
|
}
|
|
750
686
|
|
|
751
|
-
function
|
|
752
|
-
return
|
|
687
|
+
function isRecord(value) {
|
|
688
|
+
return typeof value == "object" && value !== null && !Array.isArray(value);
|
|
753
689
|
}
|
|
754
690
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}, actions = (activity.actions ?? []).map((action, a) => desugarAction({
|
|
766
|
-
action: action,
|
|
767
|
-
path: [ ...path, "actions", a ],
|
|
768
|
-
env: env,
|
|
769
|
-
activityName: activity.name,
|
|
770
|
-
ctx: ctx
|
|
771
|
-
})), target = desugarTarget({
|
|
772
|
-
target: activity.target,
|
|
773
|
-
env: env,
|
|
774
|
-
path: [ ...path, "target" ],
|
|
775
|
-
ctx: ctx
|
|
776
|
-
});
|
|
777
|
-
return {
|
|
778
|
-
...stripUndefined({
|
|
779
|
-
name: activity.name,
|
|
780
|
-
semantics: activity.semantics,
|
|
781
|
-
title: activity.title,
|
|
782
|
-
description: activity.description,
|
|
783
|
-
groups: activity.groups,
|
|
784
|
-
group: normalizeGroup(activity.group),
|
|
785
|
-
filter: activity.filter,
|
|
786
|
-
requirements: activity.requirements
|
|
787
|
-
}),
|
|
788
|
-
...target ? {
|
|
789
|
-
target: target
|
|
790
|
-
} : {},
|
|
791
|
-
...activityFields2 ? {
|
|
792
|
-
fields: activityFields2
|
|
793
|
-
} : {},
|
|
794
|
-
...actions.length > 0 ? {
|
|
795
|
-
actions: actions
|
|
796
|
-
} : {}
|
|
797
|
-
};
|
|
691
|
+
class FieldValueShapeError extends WorkflowError {
|
|
692
|
+
entryType;
|
|
693
|
+
entryName;
|
|
694
|
+
issues;
|
|
695
|
+
constructor(args) {
|
|
696
|
+
const issueText = args.issues.join("; ");
|
|
697
|
+
super("field-value-shape", `Field entry ${args.mode} shape invalid for "${args.entryName}" (${args.entryType}): ${issueText}`),
|
|
698
|
+
this.name = "FieldValueShapeError", this.entryType = args.entryType, this.entryName = args.entryName,
|
|
699
|
+
this.issues = args.issues;
|
|
700
|
+
}
|
|
798
701
|
}
|
|
799
702
|
|
|
800
|
-
const
|
|
703
|
+
const GdrUriSchema = v.custom(s => typeof s == "string" && isGdrUri(s), "must be a GDR URI"), GdrShape = tolerantObject()({
|
|
704
|
+
id: GdrUriSchema,
|
|
705
|
+
type: NonEmptyString$1
|
|
706
|
+
}), ReleaseRefShape = v.pipe(tolerantObject()({
|
|
707
|
+
id: GdrUriSchema,
|
|
708
|
+
type: v.literal("system.release"),
|
|
709
|
+
releaseName: NonEmptyString$1
|
|
710
|
+
}), v.check(ref => !isGdrUri(ref.id) || extractDocumentId(ref.id) === releaseDocId(ref.releaseName), "id must point at the `_.releases.<releaseName>` doc named by releaseName")), ActorShape = tolerantObject()({
|
|
711
|
+
kind: v.picklist(ACTOR_KINDS),
|
|
712
|
+
id: NonEmptyString$1,
|
|
713
|
+
roles: v.exactOptional(v.array(v.string())),
|
|
714
|
+
onBehalfOf: v.exactOptional(v.string())
|
|
715
|
+
}), AssigneeShape = v.union([ tolerantObject()({
|
|
716
|
+
type: v.literal("user"),
|
|
717
|
+
id: NonEmptyString$1
|
|
718
|
+
}), tolerantObject()({
|
|
719
|
+
type: v.literal("role"),
|
|
720
|
+
role: NonEmptyString$1
|
|
721
|
+
}) ]), AssigneeListShape = v.pipe(v.union([ v.null(), AssigneeShape, v.array(AssigneeShape) ]), v.transform(normalizeAssignmentMembers)), NullableString = v.union([ v.null(), v.string() ]), NullableNumber = v.union([ v.null(), v.number() ]), NullableBoolean = v.union([ v.null(), v.boolean() ]), NullableProgress = v.union([ v.null(), v.pipe(v.number(), v.finite("progress must be a finite number"), v.minValue(0, "progress must be at least 0"), v.maxValue(100, "progress must be at most 100")) ]), NullableDateTime = v.union([ v.null(), IsoTimestamp ]), NullableDate = v.union([ v.null(), v.pipe(v.string(), v.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", "dueDate", "datetime", "dueDatetime", "dateTime" ]);
|
|
801
722
|
|
|
802
|
-
function
|
|
803
|
-
|
|
804
|
-
const ref = typeof target.field == "string" ? {
|
|
805
|
-
field: target.field
|
|
806
|
-
} : target.field, field = resolveRef({
|
|
807
|
-
ref: ref,
|
|
808
|
-
env: env,
|
|
809
|
-
path: [ ...path, "field" ],
|
|
810
|
-
ctx: ctx
|
|
811
|
-
}) ?? fallbackRef(ref), entry = entryAt(env, field);
|
|
812
|
-
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
813
|
-
path: [ ...path, "field" ],
|
|
814
|
-
message: `manual activity target references "${field.field}" of kind "${entry.type}" — a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
|
|
815
|
-
}), {
|
|
816
|
-
type: "field",
|
|
817
|
-
field: field
|
|
818
|
-
};
|
|
723
|
+
function normalizedChoiceKind(kind) {
|
|
724
|
+
return kind === "dateTime" ? "datetime" : kind;
|
|
819
725
|
}
|
|
820
726
|
|
|
821
|
-
function
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
727
|
+
function checkChoiceList(args) {
|
|
728
|
+
const {entryType: entryType, options: options, validation: validation} = args;
|
|
729
|
+
if (options === void 0) return;
|
|
730
|
+
if (!CHOICE_KINDS.has(entryType)) return [ `\`options\` is not valid on "${entryType}" values` ];
|
|
731
|
+
const kind = normalizedChoiceKind(entryType), issues = options.list.flatMap((option, index) => issuesOf(checkValueAgainst({
|
|
732
|
+
entryType: kind,
|
|
733
|
+
value: option.value,
|
|
734
|
+
validation: validation
|
|
735
|
+
}, valueSchemas))?.map(issue => `at options.list.${index}.value: ${issue}`) ?? []), seen = /* @__PURE__ */ new Set;
|
|
736
|
+
for (const option of options.list) seen.has(option.value) && issues.push(`duplicate option value ${JSON.stringify(option.value)}`),
|
|
737
|
+
seen.add(option.value);
|
|
738
|
+
return issues.length === 0 ? void 0 : issues;
|
|
826
739
|
}
|
|
827
740
|
|
|
828
|
-
function
|
|
829
|
-
|
|
830
|
-
ops: action.ops,
|
|
831
|
-
path: [ ...path, "ops" ],
|
|
832
|
-
env: env,
|
|
833
|
-
firingActivity: activityName,
|
|
834
|
-
ctx: ctx
|
|
835
|
-
}) ?? [];
|
|
836
|
-
return action.status !== void 0 && ops.push({
|
|
837
|
-
type: "status.set",
|
|
838
|
-
activity: activityName,
|
|
839
|
-
status: action.status
|
|
840
|
-
}), ops;
|
|
741
|
+
function choiceValueIssues(options, value) {
|
|
742
|
+
if (!(options === void 0 || value === null || value === void 0)) return options.list.some(option => Object.is(option.value, value)) ? void 0 : [ `value ${JSON.stringify(value)} is not declared in \`options.list\`; expected one of ${options.list.map(option => JSON.stringify(option.value)).join(", ")}` ];
|
|
841
743
|
}
|
|
842
744
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
}
|
|
745
|
+
const fieldValueSchemas = {
|
|
746
|
+
"doc.ref": v.union([ v.null(), GdrShape ]),
|
|
747
|
+
"doc.refs": v.array(GdrShape),
|
|
748
|
+
subject: v.union([ v.null(), GdrShape ]),
|
|
749
|
+
"release.ref": v.union([ v.null(), ReleaseRefShape ]),
|
|
750
|
+
string: NullableString,
|
|
751
|
+
text: NullableString,
|
|
752
|
+
number: NullableNumber,
|
|
753
|
+
progress: NullableProgress,
|
|
754
|
+
boolean: NullableBoolean,
|
|
755
|
+
date: NullableDate,
|
|
756
|
+
dueDate: NullableDate,
|
|
757
|
+
datetime: NullableDateTime,
|
|
758
|
+
dueDatetime: NullableDateTime,
|
|
759
|
+
url: NullableUrl,
|
|
760
|
+
actor: v.union([ v.null(), ActorShape ]),
|
|
761
|
+
assignee: AssigneeListShape,
|
|
762
|
+
assignees: v.array(AssigneeShape)
|
|
763
|
+
}, WritePrincipalId = v.pipe(NonEmptyString$1, v.rawTransform(({dataset: dataset, addIssue: addIssue}) => {
|
|
764
|
+
const classified = classifyPrincipalId(dataset.value);
|
|
765
|
+
return classified.namespace === "global" || classified.namespace === "robot" ? dataset.value : classified.namespace === "project" && classified.globalId !== void 0 ? classified.globalId : (addIssue({
|
|
766
|
+
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.`
|
|
767
|
+
}), dataset.value);
|
|
768
|
+
})), ActorWriteShape = tolerantObject()({
|
|
769
|
+
kind: v.picklist(ACTOR_KINDS),
|
|
770
|
+
id: WritePrincipalId,
|
|
771
|
+
roles: v.exactOptional(v.array(v.string())),
|
|
772
|
+
onBehalfOf: v.exactOptional(v.string())
|
|
773
|
+
}), AssigneePrincipalId = v.pipe(WritePrincipalId, v.check(id => classifyPrincipalId(id).namespace !== "robot", "robot principals cannot be assignment members")), AssigneeWriteShape = v.union([ tolerantObject()({
|
|
774
|
+
type: v.literal("user"),
|
|
775
|
+
id: AssigneePrincipalId
|
|
776
|
+
}), tolerantObject()({
|
|
777
|
+
type: v.literal("role"),
|
|
778
|
+
role: NonEmptyString$1
|
|
779
|
+
}) ]), SingularAssigneeWriteShape = v.pipe(v.union([ AssigneeWriteShape, v.array(AssigneeWriteShape) ]), v.transform(normalizeAssignmentMembers), v.check(members => members.filter(member => member.type === "user").length <= 1, "must contain at most one user member")), valueSchemas = {
|
|
780
|
+
...fieldValueSchemas,
|
|
781
|
+
actor: v.nullable(ActorWriteShape),
|
|
782
|
+
assignee: SingularAssigneeWriteShape,
|
|
783
|
+
assignees: v.array(AssigneeWriteShape),
|
|
784
|
+
query: v.any()
|
|
785
|
+
};
|
|
885
786
|
|
|
886
|
-
function
|
|
887
|
-
const
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
env: env,
|
|
892
|
-
path: [ ...path, "field" ],
|
|
893
|
-
ctx: ctx
|
|
787
|
+
function shapeValueSchema(args) {
|
|
788
|
+
const {shape: shape, leaf: leaf} = args;
|
|
789
|
+
if (shape.type === "object") return objectSchema({
|
|
790
|
+
fields: shape.fields ?? [],
|
|
791
|
+
leaf: leaf
|
|
894
792
|
});
|
|
895
|
-
if (
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
env: env,
|
|
905
|
-
path: [ ...path, "field" ],
|
|
906
|
-
ctx: ctx
|
|
907
|
-
}), entry && ctx.claimedFields.add(entry);
|
|
908
|
-
}
|
|
909
|
-
const noSteal = `!defined($fields.${ref.field})`, filter = andConditions([ noSteal, rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = resolved ? [ {
|
|
910
|
-
type: "field.set",
|
|
911
|
-
target: resolved,
|
|
912
|
-
value: {
|
|
913
|
-
type: "actor"
|
|
914
|
-
}
|
|
915
|
-
} ] : [];
|
|
916
|
-
return {
|
|
917
|
-
...stripUndefined({
|
|
918
|
-
name: action.name,
|
|
919
|
-
title: action.title,
|
|
920
|
-
description: action.description,
|
|
921
|
-
group: normalizeGroup(action.group),
|
|
922
|
-
params: action.params,
|
|
923
|
-
effects: action.effects
|
|
924
|
-
}),
|
|
925
|
-
filter: filter,
|
|
926
|
-
...ops.length > 0 ? {
|
|
927
|
-
ops: ops
|
|
928
|
-
} : {}
|
|
929
|
-
};
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
function checkShadowedClaimTarget({actionName: actionName, field: field, resolved: resolved, env: env, path: path, ctx: ctx}) {
|
|
933
|
-
const nearest = env.layers.find(l => l.entries.has(field));
|
|
934
|
-
nearest === void 0 || nearest.scope === resolved.scope || ctx.issues.push({
|
|
935
|
-
path: path,
|
|
936
|
-
message: `claim action "${actionName}" targets "${field}" at scope "${resolved.scope}", but a nearer ${nearest.scope}-scope entry of the same name shadows it in $fields — the no-steal filter would read the shadowing entry while the claim writes the "${resolved.scope}" one. Rename one of the entries or claim the nearer one`
|
|
793
|
+
if (shape.type === "array") return v.array(objectSchema({
|
|
794
|
+
fields: shape.of ?? [],
|
|
795
|
+
leaf: leaf
|
|
796
|
+
}));
|
|
797
|
+
const schema = leaf[shape.type] ?? v.any();
|
|
798
|
+
return constrainedScalarSchema({
|
|
799
|
+
schema: schema,
|
|
800
|
+
entryType: shape.type,
|
|
801
|
+
...shape
|
|
937
802
|
});
|
|
938
803
|
}
|
|
939
804
|
|
|
940
|
-
function
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
message: `claim field "${name}" is never referenced by a claim action — you announced the pattern and wrote half of it. Declare a defineAction({ type: "claim", field: "${name}" }) or use a raw actor entry`
|
|
944
|
-
});
|
|
805
|
+
function scalarMeasurement(entryType, value) {
|
|
806
|
+
if ((entryType === "number" || entryType === "progress") && typeof value == "number") return value;
|
|
807
|
+
if ((entryType === "string" || entryType === "text") && typeof value == "string") return value.length;
|
|
945
808
|
}
|
|
946
809
|
|
|
947
|
-
|
|
810
|
+
function scalarBoundIssue(args) {
|
|
811
|
+
const {entryType: entryType, measured: measured, bound: bound, limit: limit} = args;
|
|
812
|
+
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}`;
|
|
813
|
+
}
|
|
948
814
|
|
|
949
|
-
function
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
815
|
+
function scalarValidationIssues(args) {
|
|
816
|
+
const {entryType: entryType, validation: validation, value: value} = args;
|
|
817
|
+
if (validation === void 0 || value === null || value === void 0) return;
|
|
818
|
+
const measured = scalarMeasurement(entryType, value);
|
|
819
|
+
if (measured === void 0) return;
|
|
820
|
+
const issues = [ scalarBoundIssue({
|
|
821
|
+
entryType: entryType,
|
|
822
|
+
measured: measured,
|
|
823
|
+
bound: validation.min,
|
|
824
|
+
limit: "min"
|
|
825
|
+
}), scalarBoundIssue({
|
|
826
|
+
entryType: entryType,
|
|
827
|
+
measured: measured,
|
|
828
|
+
bound: validation.max,
|
|
829
|
+
limit: "max"
|
|
830
|
+
}) ].filter(issue => issue !== void 0);
|
|
831
|
+
return issues.length === 0 ? void 0 : issues;
|
|
959
832
|
}
|
|
960
833
|
|
|
961
|
-
function
|
|
962
|
-
const {
|
|
963
|
-
return {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
metadata: Object.fromEntries(Object.entries(metadata).map(([key, read]) => [ key, printGuardRead(read) ]))
|
|
973
|
-
} : {}
|
|
974
|
-
};
|
|
834
|
+
function constrainedScalarSchema(args) {
|
|
835
|
+
const {schema: schema, entryType: entryType, options: options, validation: validation} = args;
|
|
836
|
+
return options === void 0 && validation === void 0 ? schema : v.pipe(schema, v.check(value => choiceValueIssues(options, value) === void 0 && scalarValidationIssues({
|
|
837
|
+
entryType: entryType,
|
|
838
|
+
validation: validation,
|
|
839
|
+
value: value
|
|
840
|
+
}) === void 0, issue => [ ...choiceValueIssues(options, issue.input) ?? [], ...scalarValidationIssues({
|
|
841
|
+
entryType: entryType,
|
|
842
|
+
validation: validation,
|
|
843
|
+
value: issue.input
|
|
844
|
+
}) ?? [] ].join("; ")));
|
|
975
845
|
}
|
|
976
846
|
|
|
977
|
-
function
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
firingActivity: firingActivity,
|
|
983
|
-
ctx: ctx
|
|
847
|
+
function objectSchema(args) {
|
|
848
|
+
const {fields: fields, leaf: leaf} = args, entries = /* @__PURE__ */ Object.create(null);
|
|
849
|
+
for (const f of fields) entries[f.name] = v.optional(shapeValueSchema({
|
|
850
|
+
shape: f,
|
|
851
|
+
leaf: leaf
|
|
984
852
|
}));
|
|
853
|
+
return v.looseObject(entries);
|
|
985
854
|
}
|
|
986
855
|
|
|
987
|
-
function
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
env: env,
|
|
997
|
-
ctx: ctx
|
|
998
|
-
});
|
|
999
|
-
const target = resolveRef({
|
|
1000
|
-
ref: op.target,
|
|
1001
|
-
env: env,
|
|
1002
|
-
path: [ ...path, "target" ],
|
|
1003
|
-
ctx: ctx
|
|
1004
|
-
}) ?? fallbackRef(op.target);
|
|
1005
|
-
return {
|
|
1006
|
-
...op,
|
|
1007
|
-
target: target
|
|
1008
|
-
};
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
const AUDIT_STAMPS = {
|
|
1012
|
-
actor: {
|
|
1013
|
-
type: "actor"
|
|
1014
|
-
},
|
|
1015
|
-
at: {
|
|
1016
|
-
type: "now"
|
|
1017
|
-
}
|
|
1018
|
-
};
|
|
1019
|
-
|
|
1020
|
-
function desugarAuditOp({op: op, path: path, env: env, ctx: ctx}) {
|
|
1021
|
-
const target = resolveRef({
|
|
1022
|
-
ref: op.target,
|
|
1023
|
-
env: env,
|
|
1024
|
-
path: [ ...path, "target" ],
|
|
1025
|
-
ctx: ctx
|
|
1026
|
-
}) ?? fallbackRef(op.target);
|
|
1027
|
-
if (op.value.type !== "object") return ctx.issues.push({
|
|
1028
|
-
path: [ ...path, "value" ],
|
|
1029
|
-
message: `audit value must be an object source carrying the domain fields (got "${op.value.type}")`
|
|
1030
|
-
}), {
|
|
1031
|
-
type: "field.append",
|
|
1032
|
-
target: target,
|
|
1033
|
-
value: op.value
|
|
1034
|
-
};
|
|
1035
|
-
const actorField = op.stampFields?.actor ?? "actor", atField = op.stampFields?.at ?? "at", fields = {
|
|
1036
|
-
...op.value.fields
|
|
1037
|
-
};
|
|
1038
|
-
for (const [stamp, source] of [ [ actorField, AUDIT_STAMPS.actor ], [ atField, AUDIT_STAMPS.at ] ]) {
|
|
1039
|
-
if (stamp in fields) {
|
|
1040
|
-
ctx.issues.push({
|
|
1041
|
-
path: [ ...path, "value", "fields", stamp ],
|
|
1042
|
-
message: `audit stamp field "${stamp}" collides with an authored domain field — rename yours or remap the stamp via stampFields`
|
|
1043
|
-
});
|
|
1044
|
-
continue;
|
|
1045
|
-
}
|
|
1046
|
-
fields[stamp] = source;
|
|
1047
|
-
}
|
|
1048
|
-
return {
|
|
1049
|
-
type: "field.append",
|
|
1050
|
-
target: target,
|
|
1051
|
-
value: {
|
|
1052
|
-
type: "object",
|
|
1053
|
-
fields: fields
|
|
1054
|
-
}
|
|
1055
|
-
};
|
|
856
|
+
function wholeValueSchema(args) {
|
|
857
|
+
const {entryType: entryType, shape: shape, leaf: leaf} = args;
|
|
858
|
+
return entryType === "object" ? v.union([ v.null(), objectSchema({
|
|
859
|
+
fields: shape.fields ?? [],
|
|
860
|
+
leaf: leaf
|
|
861
|
+
}) ]) : entryType === "array" ? v.array(objectSchema({
|
|
862
|
+
fields: shape.of ?? [],
|
|
863
|
+
leaf: leaf
|
|
864
|
+
})) : leaf[entryType];
|
|
1056
865
|
}
|
|
1057
866
|
|
|
1058
|
-
function
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
field: ref.field
|
|
1063
|
-
};
|
|
1064
|
-
const reachable = env.layers.flatMap(l => [ ...l.entries.keys() ].map(n => `${l.scope}:${n}`));
|
|
1065
|
-
ctx.issues.push({
|
|
1066
|
-
path: path,
|
|
1067
|
-
message: `field reference "${ref.field}"${ref.scope ? ` (scope "${ref.scope}")` : ""} does not resolve to a declared entry. Reachable: ${reachable.join(", ") || "(none)"}`
|
|
867
|
+
function appendItemSchema(entryType, shape) {
|
|
868
|
+
if (entryType === "array") return objectSchema({
|
|
869
|
+
fields: shape.of ?? [],
|
|
870
|
+
leaf: valueSchemas
|
|
1068
871
|
});
|
|
872
|
+
if (entryType === "doc.refs") return GdrShape;
|
|
873
|
+
if (entryType === "assignee" || entryType === "assignees") return AssigneeWriteShape;
|
|
1069
874
|
}
|
|
1070
875
|
|
|
1071
|
-
function
|
|
1072
|
-
|
|
876
|
+
function rejectedRefTypes(args) {
|
|
877
|
+
const {entryType: entryType, types: types, value: value} = args;
|
|
878
|
+
if (types === void 0 || value === null || value === void 0) return [];
|
|
879
|
+
if (!refKindAcceptsTypes(entryType)) return [];
|
|
880
|
+
let items = [ value ];
|
|
881
|
+
return entryType === "doc.refs" && (items = Array.isArray(value) ? value : []),
|
|
882
|
+
[ ...new Set(items.map(gdrTypeOf).filter(t => t !== void 0 && !types.includes(t))) ];
|
|
1073
883
|
}
|
|
1074
884
|
|
|
1075
|
-
function
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
};
|
|
885
|
+
function refTypeIssues(args) {
|
|
886
|
+
const rejected = rejectedRefTypes(args);
|
|
887
|
+
if (rejected.length === 0) return;
|
|
888
|
+
const accepts = (args.types ?? []).map(t => `"${t}"`).join(", ");
|
|
889
|
+
return rejected.map(t => `document type "${t}" is not accepted — this entry accepts ${accepts} (a GDR's \`type\` names the target document's schema type)`);
|
|
1080
890
|
}
|
|
1081
891
|
|
|
1082
|
-
function
|
|
1083
|
-
if (
|
|
892
|
+
function gdrTypeOf(item) {
|
|
893
|
+
if (typeof item != "object" || item === null) return;
|
|
894
|
+
const t = item.type;
|
|
895
|
+
return typeof t == "string" ? t : void 0;
|
|
1084
896
|
}
|
|
1085
897
|
|
|
1086
|
-
function
|
|
1087
|
-
|
|
1088
|
-
for (const [k, value] of Object.entries(obj)) value !== void 0 && (out[k] = value);
|
|
1089
|
-
return out;
|
|
898
|
+
function parseFieldValue(args) {
|
|
899
|
+
return checkValueAgainst(args, valueSchemas);
|
|
1090
900
|
}
|
|
1091
901
|
|
|
1092
|
-
function
|
|
1093
|
-
|
|
902
|
+
function checkValueAgainst(args, leaf) {
|
|
903
|
+
const schema = wholeValueSchema({
|
|
904
|
+
entryType: args.entryType,
|
|
905
|
+
shape: args,
|
|
906
|
+
leaf: leaf
|
|
907
|
+
});
|
|
908
|
+
if (schema === void 0) return {
|
|
909
|
+
issues: [ `unknown field entry type ${args.entryType}` ]
|
|
910
|
+
};
|
|
911
|
+
const result = v.safeParse(schema, args.value);
|
|
912
|
+
if (!result.success) return {
|
|
913
|
+
issues: formatIssues(result.issues)
|
|
914
|
+
};
|
|
915
|
+
const postIssues = refTypeIssues({
|
|
916
|
+
entryType: args.entryType,
|
|
917
|
+
types: args.types,
|
|
918
|
+
value: args.value
|
|
919
|
+
}) ?? choiceValueIssues(args.options, args.value) ?? scalarValidationIssues({
|
|
920
|
+
entryType: args.entryType,
|
|
921
|
+
validation: args.validation,
|
|
922
|
+
value: args.value
|
|
923
|
+
}) ?? assignmentValueIssues(args, result.output);
|
|
924
|
+
return postIssues !== void 0 ? {
|
|
925
|
+
issues: postIssues
|
|
926
|
+
} : {
|
|
927
|
+
output: result.output
|
|
928
|
+
};
|
|
1094
929
|
}
|
|
1095
930
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
return evaluateConditionOutcome$1({
|
|
1099
|
-
condition: condition,
|
|
1100
|
-
params: params,
|
|
1101
|
-
dataset: snapshot.docs
|
|
1102
|
-
});
|
|
931
|
+
function assignmentIdentity(assignee) {
|
|
932
|
+
return assignee.type === "user" ? `user:${assignee.id}` : `role:${assignee.role}`;
|
|
1103
933
|
}
|
|
1104
934
|
|
|
1105
|
-
|
|
1106
|
-
|
|
935
|
+
function assignmentCandidate(value) {
|
|
936
|
+
const parsed = v.safeParse(AssigneeShape, value);
|
|
937
|
+
return parsed.success ? parsed.output : void 0;
|
|
1107
938
|
}
|
|
1108
939
|
|
|
1109
|
-
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
}
|
|
1119
|
-
|
|
940
|
+
function assignmentValueIssues(args, value) {
|
|
941
|
+
const retained = previousAssignmentIdentities(args), issues = [];
|
|
942
|
+
return visitAssignmentEntries({
|
|
943
|
+
entryType: args.entryType,
|
|
944
|
+
value: value,
|
|
945
|
+
roles: args.roles,
|
|
946
|
+
fields: args.fields,
|
|
947
|
+
of: args.of,
|
|
948
|
+
path: "$"
|
|
949
|
+
}, ({assignee: assignee, roles: roles, path: path}) => {
|
|
950
|
+
consumeRetainedAssignment({
|
|
951
|
+
retained: retained,
|
|
952
|
+
path: path,
|
|
953
|
+
identity: assignmentIdentity(assignee)
|
|
954
|
+
}) || issues.push(...assignmentCandidateIssues({
|
|
955
|
+
roles: roles,
|
|
956
|
+
memberRoles: args.memberRoles,
|
|
957
|
+
roleAliases: args.roleAliases
|
|
958
|
+
}, assignee));
|
|
959
|
+
}), issues.length === 0 ? void 0 : issues;
|
|
1120
960
|
}
|
|
1121
961
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
dataset: snapshot.docs
|
|
1127
|
-
});
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
function conditionSyntaxIssues(groq2, boundVars) {
|
|
1131
|
-
const issues = [];
|
|
1132
|
-
let tree;
|
|
1133
|
-
try {
|
|
1134
|
-
tree = parse(groq2);
|
|
1135
|
-
} catch (err) {
|
|
1136
|
-
issues.push(errorMessage(err));
|
|
1137
|
-
}
|
|
1138
|
-
if (/\*\s*\[\s*_type\b/.test(groq2) && issues.push("condition scans by `_type` — that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors + field-declared docs). To bring extra docs in scope, declare a `doc.ref` (or `doc.refs`) field entry on the workflow or this stage. For lake scans like \"all articles in this release\", use a spawn action's `forEach` instead."),
|
|
1139
|
-
boundVars !== void 0 && tree !== void 0) for (const name of conditionParameterNames(groq2)) boundVars.includes(name) || issues.push(`reads $${name}, which this scope does not bind — an unbound variable evaluates to GROQ null, so the condition silently never matches. Bound here: ` + boundVars.map(n => `$${n}`).join(", "));
|
|
1140
|
-
return issues;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
function conditionParameterNames(groq2) {
|
|
1144
|
-
const read = /* @__PURE__ */ new Set;
|
|
1145
|
-
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
1146
|
-
node.type === "Parameter" && typeof node.name == "string" && read.add(node.name);
|
|
1147
|
-
}), read;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
function conditionFieldReadNames(groq2) {
|
|
1151
|
-
return new Set(conditionFieldReads(groq2).map(read => read.name));
|
|
962
|
+
function consumeRetainedAssignment(args) {
|
|
963
|
+
const identities = args.retained.get(args.path), remaining = identities?.get(args.identity) ?? 0;
|
|
964
|
+
return remaining === 0 || identities === void 0 ? !1 : (remaining === 1 ? identities.delete(args.identity) : identities.set(args.identity, remaining - 1),
|
|
965
|
+
!0);
|
|
1152
966
|
}
|
|
1153
967
|
|
|
1154
|
-
function
|
|
1155
|
-
const
|
|
1156
|
-
|
|
968
|
+
function previousAssignmentIdentities(args) {
|
|
969
|
+
const retained = /* @__PURE__ */ new Map;
|
|
970
|
+
if (args.previousValue === void 0) return retained;
|
|
971
|
+
const schema = wholeValueSchema({
|
|
972
|
+
entryType: args.entryType,
|
|
973
|
+
shape: args,
|
|
974
|
+
leaf: valueSchemas
|
|
975
|
+
});
|
|
976
|
+
if (schema === void 0) return retained;
|
|
977
|
+
const previous = v.safeParse(schema, args.previousValue);
|
|
978
|
+
return previous.success && visitAssignmentEntries({
|
|
979
|
+
entryType: args.entryType,
|
|
980
|
+
value: previous.output,
|
|
981
|
+
roles: args.roles,
|
|
982
|
+
fields: args.fields,
|
|
983
|
+
of: args.of,
|
|
984
|
+
path: "$"
|
|
985
|
+
}, ({assignee: assignee, path: path}) => {
|
|
986
|
+
const identities = retained.get(path) ?? /* @__PURE__ */ new Map, identity = assignmentIdentity(assignee);
|
|
987
|
+
identities.set(identity, (identities.get(identity) ?? 0) + 1), retained.set(path, identities);
|
|
988
|
+
}), retained;
|
|
1157
989
|
}
|
|
1158
990
|
|
|
1159
|
-
function
|
|
1160
|
-
if (
|
|
1161
|
-
|
|
991
|
+
function visitAssignmentEntries(args, visit) {
|
|
992
|
+
if (assignmentKindAcceptsRoles(args.entryType)) {
|
|
993
|
+
visitAssignmentCandidates(args, visit);
|
|
1162
994
|
return;
|
|
1163
995
|
}
|
|
1164
|
-
if (
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
path: path
|
|
1171
|
-
});
|
|
996
|
+
if (args.entryType === "object") {
|
|
997
|
+
visitAssignmentFields({
|
|
998
|
+
fields: args.fields,
|
|
999
|
+
value: args.value,
|
|
1000
|
+
path: args.path
|
|
1001
|
+
}, visit);
|
|
1172
1002
|
return;
|
|
1173
1003
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
let current = node;
|
|
1180
|
-
for (;typeof current == "object" && current !== null; ) {
|
|
1181
|
-
const typed = current;
|
|
1182
|
-
if (typed.type !== "AccessAttribute" || typeof typed.name != "string") return;
|
|
1183
|
-
names.unshift(typed.name);
|
|
1184
|
-
const base = typed.base;
|
|
1185
|
-
if (base?.type === "Parameter" && base.name === "fields") return names;
|
|
1186
|
-
current = base;
|
|
1187
|
-
}
|
|
1004
|
+
if (!(args.entryType !== "array" || !Array.isArray(args.value))) for (const row of args.value) visitAssignmentFields({
|
|
1005
|
+
fields: args.of,
|
|
1006
|
+
value: row,
|
|
1007
|
+
path: `${args.path}[]`
|
|
1008
|
+
}, visit);
|
|
1188
1009
|
}
|
|
1189
1010
|
|
|
1190
|
-
function
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1011
|
+
function visitAssignmentCandidates(args, visit) {
|
|
1012
|
+
if (args.roles === void 0) return;
|
|
1013
|
+
const indexedValues = Array.isArray(args.value) ? args.value.map(value => ({
|
|
1014
|
+
value: value,
|
|
1015
|
+
path: args.path
|
|
1016
|
+
})) : [ {
|
|
1017
|
+
value: args.value,
|
|
1018
|
+
path: args.path
|
|
1019
|
+
} ];
|
|
1020
|
+
for (const {value: value, path: path} of indexedValues) {
|
|
1021
|
+
const assignee = assignmentCandidate(value);
|
|
1022
|
+
assignee !== void 0 && visit({
|
|
1023
|
+
assignee: assignee,
|
|
1024
|
+
roles: args.roles,
|
|
1025
|
+
path: path
|
|
1198
1026
|
});
|
|
1199
|
-
}), reads;
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
function readsRootDocument(groq2) {
|
|
1203
|
-
return nodeReadsRoot(tryParseGroq(groq2), 0);
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
const SCOPED_CHILD = {
|
|
1207
|
-
Filter: "expr",
|
|
1208
|
-
Projection: "expr",
|
|
1209
|
-
Map: "expr",
|
|
1210
|
-
FlatMap: "expr",
|
|
1211
|
-
PipeFuncCall: "args"
|
|
1212
|
-
};
|
|
1213
|
-
|
|
1214
|
-
function nodeReadsRoot(node, depth) {
|
|
1215
|
-
if (Array.isArray(node)) return node.some(item => nodeReadsRoot(item, depth));
|
|
1216
|
-
if (typeof node != "object" || node === null) return !1;
|
|
1217
|
-
const typed = node;
|
|
1218
|
-
if (depth === 0 && typed.type === "This" || depth === 0 && typed.type === "AccessAttribute" && typed.base === void 0) return !0;
|
|
1219
|
-
if (typed.type === "Parent") {
|
|
1220
|
-
const climb = typeof typed.n == "number" ? typed.n : 1;
|
|
1221
|
-
return depth - climb <= 0;
|
|
1222
|
-
}
|
|
1223
|
-
const scopedChild = typeof typed.type == "string" ? SCOPED_CHILD[typed.type] : void 0;
|
|
1224
|
-
return scopedChild !== void 0 ? Object.entries(node).some(([key, value]) => nodeReadsRoot(value, key === scopedChild ? depth + 1 : depth)) : Object.values(node).some(value => nodeReadsRoot(value, depth));
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
function tryParseGroq(groq2) {
|
|
1228
|
-
try {
|
|
1229
|
-
return parse(groq2);
|
|
1230
|
-
} catch {
|
|
1231
|
-
return;
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
function walkAstNodes(node, visit) {
|
|
1236
|
-
if (Array.isArray(node)) {
|
|
1237
|
-
for (const item of node) walkAstNodes(item, visit);
|
|
1238
|
-
return;
|
|
1239
|
-
}
|
|
1240
|
-
if (!(typeof node != "object" || node === null)) {
|
|
1241
|
-
visit(node);
|
|
1242
|
-
for (const value of Object.values(node)) walkAstNodes(value, visit);
|
|
1243
1027
|
}
|
|
1244
1028
|
}
|
|
1245
1029
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1030
|
+
function visitAssignmentFields(args, visit) {
|
|
1031
|
+
if (!(args.fields === void 0 || !isRecord(args.value))) for (const field of args.fields) visitAssignmentShape({
|
|
1032
|
+
shape: field,
|
|
1033
|
+
value: args.value[field.name],
|
|
1034
|
+
path: `${args.path}.${field.name}`
|
|
1035
|
+
}, visit);
|
|
1250
1036
|
}
|
|
1251
1037
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1038
|
+
function visitAssignmentShape(args, visit) {
|
|
1039
|
+
visitAssignmentEntries({
|
|
1040
|
+
entryType: args.shape.type,
|
|
1041
|
+
value: args.value,
|
|
1042
|
+
roles: args.shape.roles,
|
|
1043
|
+
fields: args.shape.fields,
|
|
1044
|
+
of: args.shape.of,
|
|
1045
|
+
path: args.path
|
|
1046
|
+
}, visit);
|
|
1256
1047
|
}
|
|
1257
1048
|
|
|
1258
|
-
function
|
|
1259
|
-
if (
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1049
|
+
function assignmentCandidateIssues(args, assignee) {
|
|
1050
|
+
if (assignee.type === "role") return collectiveRoleIssues(args, assignee.role);
|
|
1051
|
+
const held = args.memberRoles?.[assignee.id];
|
|
1052
|
+
return held === void 0 ? args.memberRoles === void 0 ? [] : [ `user "${assignee.id}" has no available project membership for role eligibility` ] : args.roles.some(required => actorFulfillsRole({
|
|
1053
|
+
actorRoles: held,
|
|
1054
|
+
required: required,
|
|
1055
|
+
aliases: args.roleAliases
|
|
1056
|
+
})) ? [] : [ `user "${assignee.id}" does not fulfill any eligible role: ${formatEligibleRoles(args.roles)}` ];
|
|
1265
1057
|
}
|
|
1266
1058
|
|
|
1267
|
-
function
|
|
1268
|
-
return
|
|
1059
|
+
function collectiveRoleIssues(args, role) {
|
|
1060
|
+
return args.roles.includes(role) ? [] : [ `collective role "${role}" is not eligible — this entry accepts ${formatEligibleRoles(args.roles)}` ];
|
|
1269
1061
|
}
|
|
1270
1062
|
|
|
1271
|
-
function
|
|
1272
|
-
return
|
|
1063
|
+
function formatEligibleRoles(roles) {
|
|
1064
|
+
return roles.map(role => `"${role}"`).join(", ");
|
|
1273
1065
|
}
|
|
1274
1066
|
|
|
1275
|
-
function
|
|
1276
|
-
return
|
|
1067
|
+
function issuesOf(check) {
|
|
1068
|
+
return "issues" in check ? check.issues : void 0;
|
|
1277
1069
|
}
|
|
1278
1070
|
|
|
1279
|
-
function
|
|
1280
|
-
|
|
1071
|
+
function validateFieldValue(args) {
|
|
1072
|
+
const check = checkValueAgainst(args, valueSchemas);
|
|
1073
|
+
if ("issues" in check) throw new FieldValueShapeError({
|
|
1074
|
+
entryType: args.entryType,
|
|
1075
|
+
entryName: args.entryName,
|
|
1076
|
+
issues: check.issues,
|
|
1077
|
+
mode: "value"
|
|
1078
|
+
});
|
|
1079
|
+
return check.output;
|
|
1281
1080
|
}
|
|
1282
1081
|
|
|
1283
|
-
|
|
1284
|
-
return isSingleDocRefKind(entry._type);
|
|
1285
|
-
}
|
|
1082
|
+
const BARE_ID_SOURCE = "[A-Za-z0-9_][A-Za-z0-9._-]{0,127}", BARE_ID_RE = new RegExp(`^${BARE_ID_SOURCE}$`), ALIAS_REF_RE = new RegExp(`^@${RESOURCE_ALIAS_NAME_SOURCE}:${BARE_ID_SOURCE}$`);
|
|
1286
1083
|
|
|
1287
|
-
function
|
|
1288
|
-
return
|
|
1084
|
+
function isBareDocumentId(id) {
|
|
1085
|
+
return BARE_ID_RE.test(id) && !id.includes("..");
|
|
1289
1086
|
}
|
|
1290
1087
|
|
|
1291
|
-
function
|
|
1292
|
-
if (
|
|
1293
|
-
|
|
1294
|
-
|
|
1088
|
+
function isAuthoringRefId(id) {
|
|
1089
|
+
if (id.startsWith("@")) {
|
|
1090
|
+
const separator = id.indexOf(":");
|
|
1091
|
+
return ALIAS_REF_RE.test(id) && isBareDocumentId(id.slice(separator + 1));
|
|
1092
|
+
}
|
|
1093
|
+
return id.includes(":") ? isGdrUri(id) : isBareDocumentId(id);
|
|
1295
1094
|
}
|
|
1296
1095
|
|
|
1297
|
-
function
|
|
1298
|
-
|
|
1096
|
+
function isBareSeedId(id) {
|
|
1097
|
+
return isBareDocumentId(id);
|
|
1299
1098
|
}
|
|
1300
1099
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
}
|
|
1100
|
+
const AuthoringRefId = v.pipe(v.string(), v.check(isAuthoringRefId, "must be a bare document id, a GDR URI, or a portable `@<alias>:<id>` reference")), AuthoringGdrShape = v.looseObject({
|
|
1101
|
+
id: AuthoringRefId,
|
|
1102
|
+
type: NonEmptyString$1
|
|
1103
|
+
}), seedValueSchemas = {
|
|
1104
|
+
...valueSchemas,
|
|
1105
|
+
"doc.ref": v.union([ v.null(), AuthoringGdrShape ]),
|
|
1106
|
+
"doc.refs": v.array(AuthoringGdrShape),
|
|
1107
|
+
subject: v.union([ v.null(), AuthoringGdrShape ]),
|
|
1108
|
+
"release.ref": v.union([ v.null(), v.looseObject({
|
|
1109
|
+
id: AuthoringRefId,
|
|
1110
|
+
type: v.literal("system.release"),
|
|
1111
|
+
releaseName: NonEmptyString$1
|
|
1112
|
+
}) ])
|
|
1113
|
+
};
|
|
1305
1114
|
|
|
1306
|
-
function
|
|
1307
|
-
|
|
1308
|
-
return columns !== void 0 && columns.has("body") && columns.has("actor") && columns.has("at");
|
|
1115
|
+
function checkLiteralSeed(args) {
|
|
1116
|
+
return args.value === null ? [ "a literal seed cannot be null — omit `initialValue` to start the field empty" ] : issuesOf(checkValueAgainst(args, seedValueSchemas));
|
|
1309
1117
|
}
|
|
1310
1118
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
},
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
name: "effectStatus",
|
|
1353
|
-
binding: "always",
|
|
1354
|
-
label: "automation status",
|
|
1355
|
-
description: "Effect name → `'done'` | `'failed'` of the latest completed run queued during the current stage entry; absent until the effect drains for this entry. Re-entry-safe: runs queued under a prior entry never count, so `$effectStatus['<effect>'] == 'done'` (or `defined($effectStatus['<effect>'])` for settled-either-way) waits for the fresh run."
|
|
1356
|
-
}, {
|
|
1357
|
-
name: "activities",
|
|
1358
|
-
binding: "always",
|
|
1359
|
-
label: "this stage's activities",
|
|
1360
|
-
description: "The current stage's activity rows, statuses included."
|
|
1361
|
-
}, {
|
|
1362
|
-
name: "allActivitiesDone",
|
|
1363
|
-
binding: "always",
|
|
1364
|
-
label: "all activities in this stage are finished",
|
|
1365
|
-
description: "Every current-stage activity is `done` or `skipped` — the default transition gate."
|
|
1366
|
-
}, {
|
|
1367
|
-
name: "anyActivityFailed",
|
|
1368
|
-
binding: "always",
|
|
1369
|
-
label: "an activity in this stage has failed",
|
|
1370
|
-
description: "Some current-stage activity is `failed`."
|
|
1371
|
-
}, {
|
|
1372
|
-
name: "actor",
|
|
1373
|
-
binding: "caller",
|
|
1374
|
-
label: "you",
|
|
1375
|
-
description: "The acting identity (id + roles); `undefined` when no caller rides the evaluation. Never holds a value in the cascade gates (deploy-rejected there)."
|
|
1376
|
-
}, {
|
|
1377
|
-
name: "assigned",
|
|
1378
|
-
binding: "caller",
|
|
1379
|
-
label: "you are assigned to this activity",
|
|
1380
|
-
description: "Whether the caller matches the activity's assignees-kind field entry (by user id, or by a role under the definition's `roleAliases`); `false` outside an activity context. Constant `false` in the cascade gates (deploy-rejected there)."
|
|
1381
|
-
}, {
|
|
1382
|
-
name: "can",
|
|
1383
|
-
binding: "caller",
|
|
1384
|
-
label: "your permissions",
|
|
1385
|
-
description: "Advisory per-permission booleans computed from the caller's grants; `undefined` without grants. Bound wherever grants ride the evaluation: the projection's rendered scope (fireAction-action filters, activity requirements, editability predicates) and the fireAction/editField commit gates. Deploy rejects it at every site that evaluates without grants: transition `when`s, activity filters, cascade-fired actions' `when`/`filter`, effect bindings, where-op `where`s, and the spawn `forEach`/`with`/`context` sites."
|
|
1386
|
-
}, {
|
|
1387
|
-
name: "attributes",
|
|
1388
|
-
binding: "caller",
|
|
1389
|
-
label: "your attributes",
|
|
1390
|
-
description: "Advisory org-level User Attributes for the caller (Enterprise — same values as lake `user::attributes()`), keyed by attribute name with each active scalar or array value. `undefined` on expected HTTP absence; unexpected fetch failures throw; empty page binds `{}`. Bound wherever grants ride the evaluation (same sites as `$can`). Soft-gate paths fetch at most 100 attributes (no further pages) and warn when the envelope reports `hasMore: true` (partial bag still binds). Not a security boundary — the Content Lake remains the only enforcement point."
|
|
1391
|
-
}, {
|
|
1392
|
-
name: "row",
|
|
1393
|
-
binding: "spawn",
|
|
1394
|
-
label: "the spawned row",
|
|
1395
|
-
description: "One `spawn.forEach` result row, bound while its `with` map evaluates — and the row under test while a where-op `where` evaluates (per row). Deploy rejects a read at every other site (including `spawn.forEach` itself and `spawn.context`), where it is GROQ null."
|
|
1396
|
-
}, {
|
|
1397
|
-
name: "params",
|
|
1398
|
-
binding: "caller",
|
|
1399
|
-
label: "the action's arguments",
|
|
1400
|
-
description: "The firing action's args — they hold values only while a fireAction-fired action's effect bindings and where-op `where`s evaluate (spawn sites don't bind it at all, and a cascade-fired action has no caller to supply args). Deploy rejects a read in the cascade gates, in a cascade-fired action's payload, and in the caller-bound projection (action filters, requirements, editable predicates): args exist only once the caller fires the action, after those sites have evaluated."
|
|
1401
|
-
}, {
|
|
1402
|
-
name: "subworkflows",
|
|
1403
|
-
binding: "always",
|
|
1404
|
-
label: "the spawned subworkflows",
|
|
1405
|
-
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`."
|
|
1406
|
-
} ], 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 = [ {
|
|
1407
|
-
name: "tag",
|
|
1408
|
-
label: "this engine tag",
|
|
1409
|
-
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
1410
|
-
}, {
|
|
1411
|
-
name: "definition",
|
|
1412
|
-
label: "this workflow definition",
|
|
1413
|
-
description: "The `name` of the definition under evaluation (its own start block binds it)."
|
|
1414
|
-
}, {
|
|
1415
|
-
name: "now",
|
|
1416
|
-
label: "the current time",
|
|
1417
|
-
description: "The ISO clock reading of the evaluating engine."
|
|
1418
|
-
} ], START_REQUIREMENT_VARS = [ ...START_FILTER_VARS, {
|
|
1419
|
-
name: "fields",
|
|
1420
|
-
label: "the start's input fields",
|
|
1421
|
-
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."
|
|
1422
|
-
} ], GUARD_PREDICATE_VARS = [ {
|
|
1423
|
-
name: "guard",
|
|
1424
|
-
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
1425
|
-
}, {
|
|
1426
|
-
name: "mutation",
|
|
1427
|
-
description: "The attempted mutation — `mutation.action` is the write kind being gated."
|
|
1428
|
-
} ], NonEmptyString = v.pipe(v.string(), v.minLength(1, "must be a non-empty string"));
|
|
1429
|
-
|
|
1430
|
-
function isParseableInstant(value) {
|
|
1431
|
-
return typeof value == "string" && !Number.isNaN(Date.parse(value));
|
|
1119
|
+
function validateFieldAppendItem(args) {
|
|
1120
|
+
const schema = appendItemSchema(args.entryType, args);
|
|
1121
|
+
if (schema === void 0) throw new FieldValueShapeError({
|
|
1122
|
+
entryType: args.entryType,
|
|
1123
|
+
entryName: args.entryName,
|
|
1124
|
+
issues: [ `field entry type ${args.entryType} does not support append` ],
|
|
1125
|
+
mode: "item"
|
|
1126
|
+
});
|
|
1127
|
+
const result = v.safeParse(schema, args.item);
|
|
1128
|
+
if (!result.success) throw new FieldValueShapeError({
|
|
1129
|
+
entryType: args.entryType,
|
|
1130
|
+
entryName: args.entryName,
|
|
1131
|
+
issues: formatIssues(result.issues),
|
|
1132
|
+
mode: "item"
|
|
1133
|
+
});
|
|
1134
|
+
const assignmentArgs = {
|
|
1135
|
+
entryType: appendAssignmentEntryType(args.entryType),
|
|
1136
|
+
value: result.output,
|
|
1137
|
+
roles: args.roles,
|
|
1138
|
+
fields: args.entryType === "array" ? args.of : void 0,
|
|
1139
|
+
memberRoles: args.memberRoles,
|
|
1140
|
+
roleAliases: args.roleAliases
|
|
1141
|
+
}, assignmentIssues = assignmentValueIssues(assignmentArgs, result.output);
|
|
1142
|
+
if (assignmentIssues !== void 0) throw new FieldValueShapeError({
|
|
1143
|
+
entryType: args.entryType,
|
|
1144
|
+
entryName: args.entryName,
|
|
1145
|
+
issues: assignmentIssues,
|
|
1146
|
+
mode: "item"
|
|
1147
|
+
});
|
|
1148
|
+
const typeIssues = refTypeIssues({
|
|
1149
|
+
entryType: args.entryType,
|
|
1150
|
+
types: args.types,
|
|
1151
|
+
value: [ args.item ]
|
|
1152
|
+
});
|
|
1153
|
+
if (typeIssues !== void 0) throw new FieldValueShapeError({
|
|
1154
|
+
entryType: args.entryType,
|
|
1155
|
+
entryName: args.entryName,
|
|
1156
|
+
issues: typeIssues,
|
|
1157
|
+
mode: "item"
|
|
1158
|
+
});
|
|
1159
|
+
return result.output;
|
|
1432
1160
|
}
|
|
1433
1161
|
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
function tolerantObject() {
|
|
1437
|
-
return (entries, ..._exact) => v.looseObject(entries);
|
|
1162
|
+
function appendAssignmentEntryType(entryType) {
|
|
1163
|
+
return entryType === "assignee" || entryType === "assignees" ? "assignee" : entryType === "array" ? "object" : entryType;
|
|
1438
1164
|
}
|
|
1439
1165
|
|
|
1440
|
-
function
|
|
1441
|
-
|
|
1166
|
+
function formatIssues(issues, formatMessage = issue => issue.message) {
|
|
1167
|
+
const formatOne = (issue, prefix) => {
|
|
1168
|
+
const keys = [ ...prefix, ...issue.path?.map(p => p.key) ?? [] ], sub = issue.issues?.filter(candidate => candidate.expected !== "null");
|
|
1169
|
+
return sub !== void 0 && sub.length > 0 ? sub.flatMap(candidate => formatOne(candidate, keys)) : [ `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(issue)}` ];
|
|
1170
|
+
};
|
|
1171
|
+
return issues.flatMap(issue => formatOne(issue, []));
|
|
1442
1172
|
}
|
|
1443
1173
|
|
|
1444
|
-
|
|
1445
|
-
return walkSchemaShape(schema, /* @__PURE__ */ new Set);
|
|
1446
|
-
}
|
|
1174
|
+
const ACTIVITY_STATUSES = _exhaustiveOptions()([ "active", "done", "skipped", "failed" ]), TERMINAL_ACTIVITY_STATUSES = _exhaustiveOptions()([ "done", "skipped", "failed" ]);
|
|
1447
1175
|
|
|
1448
|
-
function
|
|
1449
|
-
|
|
1450
|
-
if (path.has(node)) return "(circular)";
|
|
1451
|
-
path.add(node);
|
|
1452
|
-
try {
|
|
1453
|
-
const schema = node;
|
|
1454
|
-
return containerShape(schema, path) ?? leafShape(schema);
|
|
1455
|
-
} finally {
|
|
1456
|
-
path.delete(node);
|
|
1457
|
-
}
|
|
1176
|
+
function isTerminalActivityStatus(status) {
|
|
1177
|
+
return TERMINAL_ACTIVITY_STATUSES.includes(status);
|
|
1458
1178
|
}
|
|
1459
1179
|
|
|
1460
|
-
const
|
|
1461
|
-
union: schema.options.map(option => walkSchemaShape(option, path))
|
|
1462
|
-
}), unwrapWalker = (schema, path) => walkSchemaShape(schema.wrapped, path), CONTAINER_WALKERS = {
|
|
1463
|
-
strict_object: objectWalker,
|
|
1464
|
-
loose_object: objectWalker,
|
|
1465
|
-
object: objectWalker,
|
|
1466
|
-
array: (schema, path) => ({
|
|
1467
|
-
array: walkSchemaShape(schema.item, path)
|
|
1468
|
-
}),
|
|
1469
|
-
record: (schema, path) => ({
|
|
1470
|
-
record: walkSchemaShape(schema.value, path)
|
|
1471
|
-
}),
|
|
1472
|
-
union: unionWalker,
|
|
1473
|
-
variant: unionWalker,
|
|
1474
|
-
lazy: (schema, path) => walkSchemaShape(schema.getter(void 0), path),
|
|
1475
|
-
exact_optional: unwrapWalker,
|
|
1476
|
-
optional: unwrapWalker,
|
|
1477
|
-
nullable: unwrapWalker
|
|
1478
|
-
};
|
|
1479
|
-
|
|
1480
|
-
function containerShape(schema, path) {
|
|
1481
|
-
return CONTAINER_WALKERS[String(schema.type)]?.(schema, path);
|
|
1482
|
-
}
|
|
1180
|
+
const SIGNAL_SEMANTICS = [ "signal.positive", "signal.caution", "signal.critical" ], DECISION_SEMANTICS = [ "decision.accept", "decision.decline" ], ACTION_SEMANTICS = [ ...DECISION_SEMANTICS, ...SIGNAL_SEMANTICS ], FIELD_SCOPES = _exhaustiveOptions()([ "workflow", "stage", "activity" ]), DOCUMENT_VALUE_PERMISSIONS = [ "create", "manage", "read", "update" ], LAKE_MUTATION_GUARD_ACTIONS = _exhaustiveOptions()([ "create", "update", "delete" ]), MUTATION_GUARD_ACTIONS = _exhaustiveOptions()([ ...LAKE_MUTATION_GUARD_ACTIONS, "publish", "unpublish" ]), GUARD_ACTIONS_REQUIRED_MESSAGE = "a guard must match at least one action", MUTATION_GUARD_ID_SPACES = [ "authored", "edit", "published" ];
|
|
1483
1181
|
|
|
1484
|
-
function
|
|
1485
|
-
|
|
1486
|
-
for (const [key, entry] of Object.entries(entries)) {
|
|
1487
|
-
const optional = isOptionalEntry(entry), wrapped = optional ? entry.wrapped : entry;
|
|
1488
|
-
out[optional ? `${key}?` : key] = walkSchemaShape(wrapped, path);
|
|
1489
|
-
}
|
|
1490
|
-
return Object.fromEntries(Object.entries(out).sort(([a], [b]) => a < b ? -1 : 1));
|
|
1182
|
+
function mutationGuardActionIdSpace(action) {
|
|
1183
|
+
return action === "update" ? "edit" : action === "publish" || action === "unpublish" ? "published" : "authored";
|
|
1491
1184
|
}
|
|
1492
1185
|
|
|
1493
|
-
function
|
|
1494
|
-
|
|
1495
|
-
const kind = entry.type;
|
|
1496
|
-
return kind === "exact_optional" || kind === "optional";
|
|
1186
|
+
function mutationGuardRequiresSplitEmission(actions) {
|
|
1187
|
+
return new Set(actions.map(mutationGuardActionIdSpace)).size > 1;
|
|
1497
1188
|
}
|
|
1498
1189
|
|
|
1499
|
-
const
|
|
1190
|
+
const ACTIVITY_KINDS = [ "user", "service", "script", "manual", "receive" ], EXECUTOR_CLASSIFICATIONS = [ "autonomous", "interactive", "off-system", "hybrid" ], GROUP_KINDS = [ "core", "details" ], DRIVER_KINDS = [ "person", "agent", "service", "engine" ], NonEmpty = NonEmptyString$1, PositiveInt = v.pipe(v.number(), v.integer(), v.minValue(1)), GROQ_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1500
1191
|
|
|
1501
|
-
function
|
|
1502
|
-
|
|
1503
|
-
if (schema.type === "literal") return `literal ${String(schema.literal)}`;
|
|
1504
|
-
if (schema.type === "custom") return typeof schema.message == "string" ? `custom(${schema.message})` : "custom";
|
|
1505
|
-
if (!LEAF_KINDS.has(String(schema.type))) throw new Error(`schemaTreeShape: unhandled schema kind "${String(schema.type)}" — extend the walker before regenerating the model ledger`);
|
|
1506
|
-
return String(schema.type);
|
|
1192
|
+
function groqIdentifier(referencedAs) {
|
|
1193
|
+
return v.pipe(v.string(), v.regex(GROQ_IDENTIFIER, `must be a GROQ-safe identifier (letters, digits, underscore; not starting with a digit) because it is referenced as ${referencedAs} in GROQ conditions`));
|
|
1507
1194
|
}
|
|
1508
1195
|
|
|
1509
|
-
function
|
|
1510
|
-
|
|
1511
|
-
return `${label} failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}):\n${lines.join(`\n`)}`;
|
|
1196
|
+
function picklist(options) {
|
|
1197
|
+
return v.picklist(options, invalidOptionMessage(options));
|
|
1512
1198
|
}
|
|
1513
1199
|
|
|
1514
|
-
function
|
|
1515
|
-
return
|
|
1516
|
-
path: issue.path ? issue.path.map(item => item.key) : [],
|
|
1517
|
-
message: issue.message
|
|
1518
|
-
}));
|
|
1200
|
+
function invalidOptionMessage(options) {
|
|
1201
|
+
return `Invalid option: expected one of ${options.map(option => `"${option}"`).join("|")}`;
|
|
1519
1202
|
}
|
|
1520
1203
|
|
|
1521
|
-
function
|
|
1522
|
-
|
|
1523
|
-
for (const seg of path) typeof seg == "number" ? out += `[${seg}]` : out += out.length === 0 ? String(seg) : `.${String(seg)}`;
|
|
1524
|
-
return out;
|
|
1204
|
+
function pinned() {
|
|
1205
|
+
return (schema, ..._exact) => schema;
|
|
1525
1206
|
}
|
|
1526
1207
|
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1208
|
+
const LiteralSchema = v.strictObject({
|
|
1209
|
+
type: v.literal("literal"),
|
|
1210
|
+
value: v.unknown()
|
|
1211
|
+
}), FieldReadSchema = v.strictObject({
|
|
1212
|
+
type: v.literal("fieldRead"),
|
|
1213
|
+
scope: v.optional(v.union([ v.literal("workflow"), v.literal("stage") ])),
|
|
1214
|
+
field: NonEmpty,
|
|
1215
|
+
path: v.optional(v.string())
|
|
1216
|
+
}), FieldSourceSchema = v.variant("type", [ v.strictObject({
|
|
1217
|
+
type: v.literal("input")
|
|
1218
|
+
}), v.strictObject({
|
|
1219
|
+
type: v.literal("query"),
|
|
1220
|
+
query: NonEmpty
|
|
1221
|
+
}), LiteralSchema, FieldReadSchema ]), ValueExprSchema = v.lazy(() => v.variant("type", [ LiteralSchema, FieldReadSchema, v.strictObject({
|
|
1222
|
+
type: v.literal("param"),
|
|
1223
|
+
param: NonEmpty
|
|
1224
|
+
}), v.strictObject({
|
|
1225
|
+
type: v.literal("actor")
|
|
1226
|
+
}), v.strictObject({
|
|
1227
|
+
type: v.literal("now")
|
|
1228
|
+
}), v.strictObject({
|
|
1229
|
+
type: v.literal("self")
|
|
1230
|
+
}), v.strictObject({
|
|
1231
|
+
type: v.literal("stage")
|
|
1232
|
+
}), v.strictObject({
|
|
1233
|
+
type: v.literal("object"),
|
|
1234
|
+
fields: v.record(NonEmpty, ValueExprSchema)
|
|
1235
|
+
}) ])), StoredFieldRefSchema = v.strictObject({
|
|
1236
|
+
scope: picklist(FIELD_SCOPES),
|
|
1237
|
+
field: NonEmpty
|
|
1238
|
+
}), AuthoringFieldRefSchema = v.strictObject({
|
|
1239
|
+
scope: v.optional(picklist(FIELD_SCOPES)),
|
|
1240
|
+
field: NonEmpty
|
|
1241
|
+
}), HREF_SCHEMES = [ "http:", "https:" ];
|
|
1242
|
+
|
|
1243
|
+
function isHttpUrl(value) {
|
|
1244
|
+
try {
|
|
1245
|
+
return HREF_SCHEMES.includes(new URL(value).protocol);
|
|
1246
|
+
} catch {
|
|
1247
|
+
return !1;
|
|
1535
1248
|
}
|
|
1536
1249
|
}
|
|
1537
1250
|
|
|
1538
|
-
|
|
1539
|
-
const result = v.safeParse(args.schema, args.doc);
|
|
1540
|
-
if (!result.success) throw new PersistedDocShapeError({
|
|
1541
|
-
documentId: documentIdOf(args.doc),
|
|
1542
|
-
documentType: args.docType,
|
|
1543
|
-
issues: issuesFromValibot(result.issues)
|
|
1544
|
-
});
|
|
1545
|
-
return result.output;
|
|
1546
|
-
}
|
|
1251
|
+
const UrlString = v.pipe(v.string(), v.url("must be a valid URL"), v.check(isHttpUrl, "must be an http(s) URL"));
|
|
1547
1252
|
|
|
1548
|
-
function
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1253
|
+
function manualTargetSchema(ref) {
|
|
1254
|
+
return v.variant("type", [ v.strictObject({
|
|
1255
|
+
type: v.literal("url"),
|
|
1256
|
+
url: UrlString
|
|
1257
|
+
}), v.strictObject({
|
|
1258
|
+
type: v.literal("field"),
|
|
1259
|
+
field: ref
|
|
1260
|
+
}) ]);
|
|
1554
1261
|
}
|
|
1555
1262
|
|
|
1556
|
-
const
|
|
1263
|
+
const StoredManualTargetSchema = pinned()(manualTargetSchema(StoredFieldRefSchema)), AuthoringManualTargetSchema = pinned()(manualTargetSchema(v.union([ NonEmpty, AuthoringFieldRefSchema ]))), ConditionSchema = NonEmpty;
|
|
1557
1264
|
|
|
1558
|
-
function
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1265
|
+
function opSchemas(targetSchema) {
|
|
1266
|
+
return [ v.strictObject({
|
|
1267
|
+
type: v.literal("field.set"),
|
|
1268
|
+
target: targetSchema,
|
|
1269
|
+
value: ValueExprSchema
|
|
1270
|
+
}), v.strictObject({
|
|
1271
|
+
type: v.literal("field.setIfMissing"),
|
|
1272
|
+
target: targetSchema,
|
|
1273
|
+
value: ValueExprSchema
|
|
1274
|
+
}), v.strictObject({
|
|
1275
|
+
type: v.literal("field.unset"),
|
|
1276
|
+
target: targetSchema
|
|
1277
|
+
}), v.strictObject({
|
|
1278
|
+
type: v.literal("field.append"),
|
|
1279
|
+
target: targetSchema,
|
|
1280
|
+
value: ValueExprSchema
|
|
1281
|
+
}), v.strictObject({
|
|
1282
|
+
type: v.literal("field.inc"),
|
|
1283
|
+
target: targetSchema,
|
|
1284
|
+
value: v.optional(ValueExprSchema)
|
|
1285
|
+
}), v.strictObject({
|
|
1286
|
+
type: v.literal("field.dec"),
|
|
1287
|
+
target: targetSchema,
|
|
1288
|
+
value: v.optional(ValueExprSchema)
|
|
1289
|
+
}), v.strictObject({
|
|
1290
|
+
type: v.literal("field.updateWhere"),
|
|
1291
|
+
target: targetSchema,
|
|
1292
|
+
where: ConditionSchema,
|
|
1293
|
+
value: ValueExprSchema
|
|
1294
|
+
}), v.strictObject({
|
|
1295
|
+
type: v.literal("field.removeWhere"),
|
|
1296
|
+
target: targetSchema,
|
|
1297
|
+
where: ConditionSchema
|
|
1298
|
+
}) ];
|
|
1581
1299
|
}
|
|
1582
1300
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1301
|
+
const StoredFieldOpSchema = pinned()(v.variant("type", [ ...opSchemas(StoredFieldRefSchema) ])), StoredOpSchema = pinned()(v.variant("type", [ ...opSchemas(StoredFieldRefSchema), v.strictObject({
|
|
1302
|
+
type: v.literal("status.set"),
|
|
1303
|
+
activity: NonEmpty,
|
|
1304
|
+
status: picklist(ACTIVITY_STATUSES)
|
|
1305
|
+
}) ])), AuditOpSchema = v.strictObject({
|
|
1306
|
+
type: v.literal("audit"),
|
|
1307
|
+
target: AuthoringFieldRefSchema,
|
|
1308
|
+
value: ValueExprSchema,
|
|
1309
|
+
stampFields: v.optional(v.strictObject({
|
|
1310
|
+
actor: v.optional(NonEmpty),
|
|
1311
|
+
at: v.optional(NonEmpty)
|
|
1312
|
+
}))
|
|
1313
|
+
}), AuthoringOpSchema = pinned()(v.variant("type", [ ...opSchemas(AuthoringFieldRefSchema), v.strictObject({
|
|
1314
|
+
type: v.literal("status.set"),
|
|
1315
|
+
activity: v.optional(NonEmpty),
|
|
1316
|
+
status: picklist(ACTIVITY_STATUSES)
|
|
1317
|
+
}), AuditOpSchema ])), GroupName = v.pipe(v.string(), v.regex(GROQ_IDENTIFIER, "must be an identifier (letters, digits, underscore; not starting with a digit)")), GroupSchema = pinned()(v.strictObject({
|
|
1318
|
+
name: GroupName,
|
|
1319
|
+
title: v.optional(v.string()),
|
|
1320
|
+
description: v.optional(v.string()),
|
|
1321
|
+
kind: v.optional(picklist(GROUP_KINDS))
|
|
1322
|
+
})), GroupNameList = v.pipe(v.array(GroupName), v.minLength(1, "name at least one group, or omit `group`"), v.check(names => new Set(names).size === names.length, "a group is listed more than once — list each group once")), StoredGroupMembershipSchema = GroupNameList, AuthoringGroupMembershipSchema = v.union([ GroupName, GroupNameList ]);
|
|
1323
|
+
|
|
1324
|
+
function groupMembershipNames(group) {
|
|
1325
|
+
return group === void 0 ? [] : typeof group == "string" ? [ group ] : [ ...group ];
|
|
1585
1326
|
}
|
|
1586
1327
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1328
|
+
const FIELD_VALUE_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref", "string", "text", "number", "progress", "boolean", "date", "dueDate", "datetime", "dueDatetime", "url", "actor", "assignee", "assignees", "object", "array" ], FieldValueKindSchema = picklist(FIELD_VALUE_KINDS), FieldKindSchema = picklist(FIELD_VALUE_KINDS), AUTHORING_FIELD_SUGAR_KINDS = _exhaustiveOptions()([ "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({
|
|
1329
|
+
min: v.optional(FiniteNumber),
|
|
1330
|
+
max: v.optional(FiniteNumber)
|
|
1331
|
+
}), 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({
|
|
1332
|
+
list: v.pipe(v.array(v.strictObject({
|
|
1333
|
+
title: NonEmpty,
|
|
1334
|
+
value: v.union([ v.string(), v.number() ])
|
|
1335
|
+
})), v.minLength(1, "declare at least one choice, or omit `options`"))
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
function asShape(input) {
|
|
1339
|
+
return typeof input == "object" && input !== null ? input : {};
|
|
1593
1340
|
}
|
|
1594
1341
|
|
|
1595
|
-
function
|
|
1596
|
-
|
|
1342
|
+
function compositeShapeOk(input) {
|
|
1343
|
+
const shape = asShape(input);
|
|
1344
|
+
return shape.type === "object" ? Array.isArray(shape.fields) && shape.fields.length > 0 && shape.of === void 0 : shape.type === "array" ? Array.isArray(shape.of) && shape.of.length > 0 && shape.fields === void 0 : shape.fields === void 0 && shape.of === void 0;
|
|
1597
1345
|
}
|
|
1598
1346
|
|
|
1599
|
-
function
|
|
1600
|
-
|
|
1347
|
+
function compositeShapeMessage(input) {
|
|
1348
|
+
const shape = asShape(input);
|
|
1349
|
+
return shape.type === "object" ? shape.of !== void 0 ? "an `object` kind declares its sub-fields with `fields`, not `of`" : "an `object` kind needs a non-empty `fields` list of sub-field shapes" : shape.type === "array" ? shape.fields !== void 0 ? "an `array` kind declares its item shape with `of`, not `fields`" : "an `array` kind needs a non-empty `of` list of sub-field shapes" : `\`fields\` / \`of\` are only valid on the \`object\` / \`array\` kinds, not "${String(shape.type)}"`;
|
|
1601
1350
|
}
|
|
1602
1351
|
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1352
|
+
function duplicateSubfieldName(input) {
|
|
1353
|
+
const shape = asShape(input);
|
|
1354
|
+
let list = [];
|
|
1355
|
+
Array.isArray(shape.fields) ? list = shape.fields : Array.isArray(shape.of) && (list = shape.of);
|
|
1356
|
+
const seen = /* @__PURE__ */ new Set;
|
|
1357
|
+
for (const item of list) {
|
|
1358
|
+
const name = asShape(item).name;
|
|
1359
|
+
if (typeof name == "string") {
|
|
1360
|
+
if (seen.has(name)) return name;
|
|
1361
|
+
seen.add(name);
|
|
1362
|
+
}
|
|
1612
1363
|
}
|
|
1613
1364
|
}
|
|
1614
1365
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
type: NonEmptyString
|
|
1618
|
-
}), ReleaseRefShape = v.pipe(tolerantObject()({
|
|
1619
|
-
id: GdrUriSchema,
|
|
1620
|
-
type: v.literal("system.release"),
|
|
1621
|
-
releaseName: NonEmptyString
|
|
1622
|
-
}), v.check(ref => !isGdrUri(ref.id) || extractDocumentId(ref.id) === releaseDocId(ref.releaseName), "id must point at the `_.releases.<releaseName>` doc named by releaseName")), ActorShape = tolerantObject()({
|
|
1623
|
-
kind: v.picklist(ACTOR_KINDS),
|
|
1624
|
-
id: NonEmptyString,
|
|
1625
|
-
roles: v.exactOptional(v.array(v.string())),
|
|
1626
|
-
onBehalfOf: v.exactOptional(v.string())
|
|
1627
|
-
}), AssigneeShape = v.union([ tolerantObject()({
|
|
1628
|
-
type: v.literal("user"),
|
|
1629
|
-
id: NonEmptyString
|
|
1630
|
-
}), tolerantObject()({
|
|
1631
|
-
type: v.literal("role"),
|
|
1632
|
-
role: NonEmptyString
|
|
1633
|
-
}) ]), NullableString = v.union([ v.null(), v.string() ]), NullableNumber = v.union([ v.null(), v.number() ]), NullableBoolean = v.union([ v.null(), v.boolean() ]), NullableProgress = v.union([ v.null(), v.pipe(v.number(), v.finite("progress must be a finite number"), v.minValue(0, "progress must be at least 0"), v.maxValue(100, "progress must be at most 100")) ]), NullableDateTime = v.union([ v.null(), IsoTimestamp ]), NullableDate = v.union([ v.null(), v.pipe(v.string(), v.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", "dueDate", "datetime", "dueDatetime", "dateTime" ]);
|
|
1634
|
-
|
|
1635
|
-
function normalizedChoiceKind(kind) {
|
|
1636
|
-
return kind === "dateTime" ? "datetime" : kind;
|
|
1366
|
+
function compositeChecked(entries) {
|
|
1367
|
+
return v.pipe(v.strictObject(entries), v.check(input => compositeShapeOk(input), issue => compositeShapeMessage(issue.input)), v.check(input => duplicateSubfieldName(input) === void 0, issue => `duplicate sub-field name "${duplicateSubfieldName(issue.input)}" — sub-field names must be unique within \`fields\` / \`of\``));
|
|
1637
1368
|
}
|
|
1638
1369
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1370
|
+
const AssignmentRolesSchema = v.pipe(v.array(NonEmpty), v.minLength(1, "declare at least one eligible role, or omit `roles`")), FieldShapeSchema = v.lazy(() => v.pipe(compositeChecked({
|
|
1371
|
+
type: FieldValueKindSchema,
|
|
1372
|
+
name: FieldEntryName,
|
|
1373
|
+
title: v.optional(v.string()),
|
|
1374
|
+
description: v.optional(v.string()),
|
|
1375
|
+
options: v.optional(ChoiceOptionsSchema),
|
|
1376
|
+
validation: v.optional(ScalarValidationSchema),
|
|
1377
|
+
roles: v.optional(AssignmentRolesSchema),
|
|
1378
|
+
fields: v.optional(v.array(FieldShapeSchema)),
|
|
1379
|
+
of: v.optional(v.array(FieldShapeSchema))
|
|
1380
|
+
}), choiceOptionsCheck(), scalarValidationCheck(), assignmentRolesCheck())), StoredEditableSchema = pinned()(v.union([ v.literal(!0), NonEmpty ])), AuthoringEditableSchema = pinned()(v.union([ v.literal(!0), v.array(NonEmpty), NonEmpty ]));
|
|
1381
|
+
|
|
1382
|
+
function fieldBase(editable, group) {
|
|
1383
|
+
return {
|
|
1384
|
+
name: FieldEntryName,
|
|
1385
|
+
title: v.optional(v.string()),
|
|
1386
|
+
description: v.optional(v.string()),
|
|
1387
|
+
group: v.optional(group),
|
|
1388
|
+
required: v.optional(v.boolean()),
|
|
1389
|
+
initialValue: v.optional(FieldSourceSchema),
|
|
1390
|
+
editable: v.optional(editable)
|
|
1391
|
+
};
|
|
1651
1392
|
}
|
|
1652
1393
|
|
|
1653
|
-
function
|
|
1654
|
-
|
|
1394
|
+
function fieldEntryFields({editable: editable, group: group, kind: kind = FieldKindSchema}) {
|
|
1395
|
+
return {
|
|
1396
|
+
type: kind,
|
|
1397
|
+
...fieldBase(editable, group),
|
|
1398
|
+
options: v.optional(ChoiceOptionsSchema),
|
|
1399
|
+
validation: v.optional(ScalarValidationSchema),
|
|
1400
|
+
types: v.optional(v.pipe(v.array(NonEmpty), v.minLength(1, "declare at least one accepted type, or omit `types` to accept any"))),
|
|
1401
|
+
roles: v.optional(AssignmentRolesSchema),
|
|
1402
|
+
fields: v.optional(v.array(FieldShapeSchema)),
|
|
1403
|
+
of: v.optional(v.array(FieldShapeSchema))
|
|
1404
|
+
};
|
|
1655
1405
|
}
|
|
1656
1406
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
date: NullableDate,
|
|
1668
|
-
dueDate: NullableDate,
|
|
1669
|
-
datetime: NullableDateTime,
|
|
1670
|
-
dueDatetime: NullableDateTime,
|
|
1671
|
-
url: NullableUrl,
|
|
1672
|
-
actor: v.union([ v.null(), ActorShape ]),
|
|
1673
|
-
assignee: v.union([ v.null(), AssigneeShape ]),
|
|
1674
|
-
assignees: v.array(AssigneeShape)
|
|
1675
|
-
}, WritePrincipalId = v.pipe(NonEmptyString, v.rawTransform(({dataset: dataset, addIssue: addIssue}) => {
|
|
1676
|
-
const classified = classifyPrincipalId(dataset.value);
|
|
1677
|
-
return classified.namespace === "global" || classified.namespace === "robot" ? dataset.value : classified.namespace === "project" && classified.globalId !== void 0 ? classified.globalId : (addIssue({
|
|
1678
|
-
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.`
|
|
1679
|
-
}), dataset.value);
|
|
1680
|
-
})), ActorWriteShape = tolerantObject()({
|
|
1681
|
-
kind: v.picklist(ACTOR_KINDS),
|
|
1682
|
-
id: WritePrincipalId,
|
|
1683
|
-
roles: v.exactOptional(v.array(v.string())),
|
|
1684
|
-
onBehalfOf: v.exactOptional(v.string())
|
|
1685
|
-
}), AssigneeWriteShape = v.union([ tolerantObject()({
|
|
1686
|
-
type: v.literal("user"),
|
|
1687
|
-
id: WritePrincipalId
|
|
1688
|
-
}), tolerantObject()({
|
|
1689
|
-
type: v.literal("role"),
|
|
1690
|
-
role: NonEmptyString
|
|
1691
|
-
}) ]), valueSchemas = {
|
|
1692
|
-
...fieldValueSchemas,
|
|
1693
|
-
actor: v.nullable(ActorWriteShape),
|
|
1694
|
-
assignee: v.nullable(AssigneeWriteShape),
|
|
1695
|
-
assignees: v.array(AssigneeWriteShape),
|
|
1696
|
-
query: v.any()
|
|
1697
|
-
};
|
|
1698
|
-
|
|
1699
|
-
function shapeValueSchema(args) {
|
|
1700
|
-
const {shape: shape, leaf: leaf} = args;
|
|
1701
|
-
if (shape.type === "object") return objectSchema({
|
|
1702
|
-
fields: shape.fields ?? [],
|
|
1703
|
-
leaf: leaf
|
|
1704
|
-
});
|
|
1705
|
-
if (shape.type === "array") return v.array(objectSchema({
|
|
1706
|
-
fields: shape.of ?? [],
|
|
1707
|
-
leaf: leaf
|
|
1708
|
-
}));
|
|
1709
|
-
const schema = leaf[shape.type] ?? v.any();
|
|
1710
|
-
return constrainedScalarSchema({
|
|
1711
|
-
schema: schema,
|
|
1712
|
-
entryType: shape.type,
|
|
1713
|
-
...shape
|
|
1407
|
+
function literalSeedIssues(entry) {
|
|
1408
|
+
if (entry.initialValue?.type === "literal") return checkLiteralSeed({
|
|
1409
|
+
entryType: entry.type,
|
|
1410
|
+
value: entry.initialValue.value,
|
|
1411
|
+
types: entry.types,
|
|
1412
|
+
fields: entry.fields,
|
|
1413
|
+
of: entry.of,
|
|
1414
|
+
options: entry.options,
|
|
1415
|
+
validation: entry.validation,
|
|
1416
|
+
roles: entry.roles
|
|
1714
1417
|
});
|
|
1715
1418
|
}
|
|
1716
1419
|
|
|
1717
|
-
function
|
|
1718
|
-
|
|
1719
|
-
if ((entryType === "string" || entryType === "text") && typeof value == "string") return value.length;
|
|
1420
|
+
function literalSeedCheck() {
|
|
1421
|
+
return v.check(entry => literalSeedIssues(entry) === void 0, issue => `initialValue literal does not fit the declared kind: ${(literalSeedIssues(issue.input) ?? []).join("; ")}`);
|
|
1720
1422
|
}
|
|
1721
1423
|
|
|
1722
|
-
function
|
|
1723
|
-
|
|
1724
|
-
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}`;
|
|
1424
|
+
function refTypesCheck() {
|
|
1425
|
+
return v.check(entry => entry.types === void 0 || refKindAcceptsTypes(entry.type), issue => `\`types\` is only valid on \`doc.ref\` / \`doc.refs\` / \`subject\` entries, not "${issue.input.type}"`);
|
|
1725
1426
|
}
|
|
1726
1427
|
|
|
1727
|
-
function
|
|
1728
|
-
|
|
1729
|
-
if (validation === void 0 || value === null || value === void 0) return;
|
|
1730
|
-
const measured = scalarMeasurement(entryType, value);
|
|
1731
|
-
if (measured === void 0) return;
|
|
1732
|
-
const issues = [ scalarBoundIssue({
|
|
1733
|
-
entryType: entryType,
|
|
1734
|
-
measured: measured,
|
|
1735
|
-
bound: validation.min,
|
|
1736
|
-
limit: "min"
|
|
1737
|
-
}), scalarBoundIssue({
|
|
1738
|
-
entryType: entryType,
|
|
1739
|
-
measured: measured,
|
|
1740
|
-
bound: validation.max,
|
|
1741
|
-
limit: "max"
|
|
1742
|
-
}) ].filter(issue => issue !== void 0);
|
|
1743
|
-
return issues.length === 0 ? void 0 : issues;
|
|
1428
|
+
function assignmentRolesCheck() {
|
|
1429
|
+
return v.check(entry => entry.roles === void 0 || assignmentKindAcceptsRoles(entry.type), issue => `\`roles\` is only valid on \`assignee\` / \`assignees\` entries, not "${issue.input.type}"`);
|
|
1744
1430
|
}
|
|
1745
1431
|
|
|
1746
|
-
function
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
validation: validation
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
validation: validation
|
|
1755
|
-
|
|
1756
|
-
}) ?? [] ].join("; ")));
|
|
1432
|
+
function choiceOptionsCheck() {
|
|
1433
|
+
return v.check(entry => checkChoiceList({
|
|
1434
|
+
entryType: entry.type,
|
|
1435
|
+
options: entry.options,
|
|
1436
|
+
validation: entry.validation
|
|
1437
|
+
}) === void 0, issue => (checkChoiceList({
|
|
1438
|
+
entryType: issue.input.type,
|
|
1439
|
+
options: issue.input.options,
|
|
1440
|
+
validation: issue.input.validation
|
|
1441
|
+
}) ?? []).join("; "));
|
|
1757
1442
|
}
|
|
1758
1443
|
|
|
1759
|
-
|
|
1760
|
-
const {fields: fields, leaf: leaf} = args, entries = /* @__PURE__ */ Object.create(null);
|
|
1761
|
-
for (const f of fields) entries[f.name] = v.optional(shapeValueSchema({
|
|
1762
|
-
shape: f,
|
|
1763
|
-
leaf: leaf
|
|
1764
|
-
}));
|
|
1765
|
-
return v.looseObject(entries);
|
|
1766
|
-
}
|
|
1444
|
+
const SCALAR_VALIDATION_KINDS = /* @__PURE__ */ new Set([ "string", "text", "number", "progress" ]);
|
|
1767
1445
|
|
|
1768
|
-
function
|
|
1769
|
-
|
|
1770
|
-
return entryType === "object" ? v.union([ v.null(), objectSchema({
|
|
1771
|
-
fields: shape.fields ?? [],
|
|
1772
|
-
leaf: leaf
|
|
1773
|
-
}) ]) : entryType === "array" ? v.array(objectSchema({
|
|
1774
|
-
fields: shape.of ?? [],
|
|
1775
|
-
leaf: leaf
|
|
1776
|
-
})) : leaf[entryType];
|
|
1446
|
+
function scalarValidationCheck() {
|
|
1447
|
+
return v.check(entry => scalarValidationDeclarationIssues(entry) === void 0, issue => (scalarValidationDeclarationIssues(issue.input) ?? []).join("; "));
|
|
1777
1448
|
}
|
|
1778
1449
|
|
|
1779
|
-
function
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1450
|
+
function scalarValidationDeclarationIssues(entry) {
|
|
1451
|
+
const {type: type, validation: validation} = entry;
|
|
1452
|
+
if (validation === void 0) return;
|
|
1453
|
+
if (!SCALAR_VALIDATION_KINDS.has(type)) return [ `\`validation\` is only valid on \`string\` / \`text\` / \`number\` / \`progress\` values, not "${type}"` ];
|
|
1454
|
+
if (type === "progress") {
|
|
1455
|
+
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` ]);
|
|
1456
|
+
return issues2.length === 0 ? void 0 : issues2;
|
|
1457
|
+
}
|
|
1458
|
+
if (type === "number") return;
|
|
1459
|
+
const issues = Object.entries(validation).flatMap(([bound, value]) => Number.isInteger(value) && value >= 0 ? [] : [ `\`validation.${bound}\` must be a non-negative integer for ${type} length` ]);
|
|
1460
|
+
return issues.length === 0 ? void 0 : issues;
|
|
1786
1461
|
}
|
|
1787
1462
|
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
}
|
|
1463
|
+
const FieldEntrySchema = pinned()(v.pipe(compositeChecked(fieldEntryFields({
|
|
1464
|
+
editable: StoredEditableSchema,
|
|
1465
|
+
group: StoredGroupMembershipSchema
|
|
1466
|
+
})), refTypesCheck(), assignmentRolesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck())), RawAuthoringFieldEntrySchema = pinned()(v.pipe(compositeChecked(fieldEntryFields({
|
|
1467
|
+
editable: AuthoringEditableSchema,
|
|
1468
|
+
group: AuthoringGroupMembershipSchema,
|
|
1469
|
+
kind: AuthoringRawFieldKindSchema
|
|
1470
|
+
})), refTypesCheck(), assignmentRolesCheck(), choiceOptionsCheck(), scalarValidationCheck(), literalSeedCheck()));
|
|
1796
1471
|
|
|
1797
|
-
function
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1472
|
+
function listSugarFields(type) {
|
|
1473
|
+
return {
|
|
1474
|
+
type: v.literal(type),
|
|
1475
|
+
...fieldBase(AuthoringEditableSchema, AuthoringGroupMembershipSchema)
|
|
1476
|
+
};
|
|
1802
1477
|
}
|
|
1803
1478
|
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1479
|
+
const TodoListFieldSchema = pinned()(v.strictObject(listSugarFields("todoList"))), NotesFieldSchema = pinned()(v.strictObject(listSugarFields("notes"))), AuthoringFieldEntrySchema = pinned()(v.lazy(input => {
|
|
1480
|
+
const type = asShape(input).type;
|
|
1481
|
+
return type === "todoList" ? TodoListFieldSchema : type === "notes" ? NotesFieldSchema : RawAuthoringFieldEntrySchema;
|
|
1482
|
+
})), RUNTIME_KINDS = _exhaustiveOptions()([ "function", "durableFunction", "selfHosted" ]), RuntimeBlockSchema = pinned()(v.strictObject({
|
|
1483
|
+
kind: picklist(RUNTIME_KINDS)
|
|
1484
|
+
})), EffectRuntimeBlockSchema = pinned()(v.variant("kind", [ v.strictObject({
|
|
1485
|
+
kind: v.literal("function"),
|
|
1486
|
+
timeout: v.optional(PositiveInt),
|
|
1487
|
+
memory: v.optional(PositiveInt)
|
|
1488
|
+
}), v.strictObject({
|
|
1489
|
+
kind: v.literal("durableFunction")
|
|
1490
|
+
}), v.strictObject({
|
|
1491
|
+
kind: v.literal("selfHosted")
|
|
1492
|
+
}) ], invalidOptionMessage(RUNTIME_KINDS))), EFFECT_RETRY_KINDS = _exhaustiveOptions()([ "engine" ]), EffectRetryBackoffSchema = pinned()(v.strictObject({
|
|
1493
|
+
kind: picklist([ "fixed", "exponential" ]),
|
|
1494
|
+
delayMs: PositiveInt
|
|
1495
|
+
}));
|
|
1809
1496
|
|
|
1810
|
-
function
|
|
1811
|
-
return
|
|
1497
|
+
function effectRetryFields(kind) {
|
|
1498
|
+
return {
|
|
1499
|
+
kind: kind,
|
|
1500
|
+
attempts: PositiveInt,
|
|
1501
|
+
backoff: v.optional(EffectRetryBackoffSchema),
|
|
1502
|
+
expiryMs: v.optional(PositiveInt)
|
|
1503
|
+
};
|
|
1812
1504
|
}
|
|
1813
1505
|
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
issues: formatIssues(result.issues)
|
|
1826
|
-
};
|
|
1827
|
-
const postIssues = refTypeIssues({
|
|
1828
|
-
entryType: args.entryType,
|
|
1829
|
-
types: args.types,
|
|
1830
|
-
value: args.value
|
|
1831
|
-
}) ?? choiceValueIssues(args.options, args.value) ?? scalarValidationIssues({
|
|
1832
|
-
entryType: args.entryType,
|
|
1833
|
-
validation: args.validation,
|
|
1834
|
-
value: args.value
|
|
1835
|
-
}) ?? assignmentValueIssues(args, result.output);
|
|
1836
|
-
return postIssues !== void 0 ? {
|
|
1837
|
-
issues: postIssues
|
|
1838
|
-
} : {
|
|
1839
|
-
output: result.output
|
|
1506
|
+
const StoredEffectRetrySchema = pinned()(v.strictObject(effectRetryFields(picklist(EFFECT_RETRY_KINDS)))), AuthoringEffectRetrySchema = pinned()(v.strictObject(effectRetryFields(v.optional(picklist(EFFECT_RETRY_KINDS)))));
|
|
1507
|
+
|
|
1508
|
+
function effectFields(retry) {
|
|
1509
|
+
return {
|
|
1510
|
+
name: NonEmpty,
|
|
1511
|
+
title: v.optional(v.string()),
|
|
1512
|
+
description: v.optional(v.string()),
|
|
1513
|
+
bindings: v.optional(v.record(v.string(), ConditionSchema)),
|
|
1514
|
+
input: v.optional(v.record(v.string(), v.unknown())),
|
|
1515
|
+
outputs: v.optional(v.array(FieldShapeSchema)),
|
|
1516
|
+
retry: v.optional(retry)
|
|
1840
1517
|
};
|
|
1841
1518
|
}
|
|
1842
1519
|
|
|
1843
|
-
|
|
1844
|
-
|
|
1520
|
+
const EffectSchema = pinned()(v.strictObject(effectFields(StoredEffectRetrySchema))), AuthoringEffectSchema = pinned()(v.strictObject({
|
|
1521
|
+
...effectFields(AuthoringEffectRetrySchema),
|
|
1522
|
+
runtime: v.optional(EffectRuntimeBlockSchema)
|
|
1523
|
+
})), DefinitionRefSchema = v.strictObject({
|
|
1524
|
+
name: NonEmpty,
|
|
1525
|
+
version: v.optional(v.union([ PositiveInt, v.literal("latest") ]))
|
|
1526
|
+
}), SubworkflowsSchema = v.strictObject({
|
|
1527
|
+
forEach: NonEmpty,
|
|
1528
|
+
definition: DefinitionRefSchema,
|
|
1529
|
+
with: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
1530
|
+
context: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
1531
|
+
onExit: v.optional(picklist([ "detach", "abort" ]))
|
|
1532
|
+
}), ActionParamSchema = v.pipe(v.strictObject({
|
|
1533
|
+
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
1534
|
+
name: NonEmpty,
|
|
1535
|
+
title: v.optional(v.string()),
|
|
1536
|
+
description: v.optional(v.string()),
|
|
1537
|
+
required: v.optional(v.boolean()),
|
|
1538
|
+
options: v.optional(ChoiceOptionsSchema),
|
|
1539
|
+
validation: v.optional(ScalarValidationSchema)
|
|
1540
|
+
}), choiceOptionsCheck(), scalarValidationCheck()), CUSTOM_SEMANTIC_HINT = "`custom.<camelCaseMeaning>`", CustomSemanticSchema = v.custom(input => typeof input == "string" && /^custom\.[a-z][a-zA-Z0-9]*$/.test(input)), SemanticSchema = v.union([ picklist(SIGNAL_SEMANTICS), CustomSemanticSchema ], `expected ${SIGNAL_SEMANTICS.join(", ")}, or ${CUSTOM_SEMANTIC_HINT}`), ActionSemanticSchema = v.union([ picklist(ACTION_SEMANTICS), CustomSemanticSchema ], `expected ${ACTION_SEMANTICS.join(", ")}, or ${CUSTOM_SEMANTIC_HINT}`);
|
|
1541
|
+
|
|
1542
|
+
function semanticNamespace(semantic) {
|
|
1543
|
+
return semantic.startsWith("custom.") ? semantic : semantic.split(".", 1)[0] ?? semantic;
|
|
1845
1544
|
}
|
|
1846
1545
|
|
|
1847
|
-
function
|
|
1848
|
-
const
|
|
1849
|
-
return
|
|
1546
|
+
function hasUniqueSemanticNamespaces(semantics) {
|
|
1547
|
+
const namespaces = semantics.map(semanticNamespace);
|
|
1548
|
+
return new Set(namespaces).size === namespaces.length;
|
|
1850
1549
|
}
|
|
1851
1550
|
|
|
1852
|
-
function
|
|
1853
|
-
|
|
1854
|
-
return visitAssignmentEntries({
|
|
1855
|
-
entryType: args.entryType,
|
|
1856
|
-
value: value,
|
|
1857
|
-
roles: args.roles,
|
|
1858
|
-
fields: args.fields,
|
|
1859
|
-
of: args.of,
|
|
1860
|
-
path: "$"
|
|
1861
|
-
}, ({assignee: assignee, roles: roles, path: path}) => {
|
|
1862
|
-
consumeRetainedAssignment({
|
|
1863
|
-
retained: retained,
|
|
1864
|
-
path: path,
|
|
1865
|
-
identity: assignmentIdentity(assignee)
|
|
1866
|
-
}) || issues.push(...assignmentCandidateIssues({
|
|
1867
|
-
roles: roles,
|
|
1868
|
-
memberRoles: args.memberRoles,
|
|
1869
|
-
roleAliases: args.roleAliases
|
|
1870
|
-
}, assignee));
|
|
1871
|
-
}), issues.length === 0 ? void 0 : issues;
|
|
1551
|
+
function semanticsFieldSchema(semantic) {
|
|
1552
|
+
return v.optional(v.pipe(v.array(semantic), v.minLength(1, "declare at least one semantic, or omit `semantics`"), v.check(hasUniqueSemanticNamespaces, "declare at most one semantic from each namespace")));
|
|
1872
1553
|
}
|
|
1873
1554
|
|
|
1874
|
-
|
|
1875
|
-
const identities = args.retained.get(args.path), remaining = identities?.get(args.identity) ?? 0;
|
|
1876
|
-
return remaining === 0 || identities === void 0 ? !1 : (remaining === 1 ? identities.delete(args.identity) : identities.set(args.identity, remaining - 1),
|
|
1877
|
-
!0);
|
|
1878
|
-
}
|
|
1555
|
+
const SemanticsFieldSchema = semanticsFieldSchema(SemanticSchema), ActionSemanticsFieldSchema = semanticsFieldSchema(ActionSemanticSchema);
|
|
1879
1556
|
|
|
1880
|
-
function
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
of: args.of,
|
|
1896
|
-
path: "$"
|
|
1897
|
-
}, ({assignee: assignee, path: path}) => {
|
|
1898
|
-
const identities = retained.get(path) ?? /* @__PURE__ */ new Map, identity = assignmentIdentity(assignee);
|
|
1899
|
-
identities.set(identity, (identities.get(identity) ?? 0) + 1), retained.set(path, identities);
|
|
1900
|
-
}), retained;
|
|
1557
|
+
function actionFields(schemas) {
|
|
1558
|
+
const {op: op, group: group, effect: effect} = schemas;
|
|
1559
|
+
return {
|
|
1560
|
+
name: NonEmpty,
|
|
1561
|
+
semantics: ActionSemanticsFieldSchema,
|
|
1562
|
+
title: v.optional(v.string()),
|
|
1563
|
+
description: v.optional(v.string()),
|
|
1564
|
+
group: v.optional(group),
|
|
1565
|
+
when: v.optional(ConditionSchema),
|
|
1566
|
+
filter: v.optional(ConditionSchema),
|
|
1567
|
+
params: v.optional(v.array(ActionParamSchema)),
|
|
1568
|
+
ops: v.optional(v.array(op)),
|
|
1569
|
+
effects: v.optional(v.array(effect)),
|
|
1570
|
+
spawn: v.optional(SubworkflowsSchema)
|
|
1571
|
+
};
|
|
1901
1572
|
}
|
|
1902
1573
|
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1574
|
+
const StoredActionSchema = pinned()(v.strictObject({
|
|
1575
|
+
...actionFields({
|
|
1576
|
+
op: StoredOpSchema,
|
|
1577
|
+
group: StoredGroupMembershipSchema,
|
|
1578
|
+
effect: EffectSchema
|
|
1579
|
+
}),
|
|
1580
|
+
roles: v.optional(v.array(NonEmpty))
|
|
1581
|
+
})), TerminalActivityStatus = picklist(TERMINAL_ACTIVITY_STATUSES), RawAuthoringActionSchema = pinned()(v.strictObject({
|
|
1582
|
+
...actionFields({
|
|
1583
|
+
op: AuthoringOpSchema,
|
|
1584
|
+
group: AuthoringGroupMembershipSchema,
|
|
1585
|
+
effect: AuthoringEffectSchema
|
|
1586
|
+
}),
|
|
1587
|
+
roles: v.optional(v.array(NonEmpty)),
|
|
1588
|
+
status: v.optional(TerminalActivityStatus)
|
|
1589
|
+
})), AuthoringActionSchema = pinned()(RawAuthoringActionSchema), requirementBase = {
|
|
1590
|
+
name: NonEmpty,
|
|
1591
|
+
title: v.optional(v.string()),
|
|
1592
|
+
description: v.optional(v.string())
|
|
1593
|
+
}, GroqRequirementSchemaRaw = v.strictObject({
|
|
1594
|
+
...requirementBase,
|
|
1595
|
+
type: v.literal("groq"),
|
|
1596
|
+
query: ConditionSchema
|
|
1597
|
+
}), SingleSubjectRequirementSchemaRaw = v.strictObject({
|
|
1598
|
+
...requirementBase,
|
|
1599
|
+
type: v.literal("singleSubject")
|
|
1600
|
+
}), GroqRequirementSchema = pinned()(GroqRequirementSchemaRaw), StartRequirementSchema = pinned()(v.variant("type", [ GroqRequirementSchemaRaw, SingleSubjectRequirementSchemaRaw ]));
|
|
1922
1601
|
|
|
1923
|
-
function
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
path: path
|
|
1938
|
-
});
|
|
1939
|
-
}
|
|
1602
|
+
function activityFields({field: field, action: action, target: target, group: group}) {
|
|
1603
|
+
return {
|
|
1604
|
+
name: NonEmpty,
|
|
1605
|
+
semantics: SemanticsFieldSchema,
|
|
1606
|
+
title: v.optional(v.string()),
|
|
1607
|
+
description: v.optional(v.string()),
|
|
1608
|
+
groups: v.optional(v.array(GroupSchema)),
|
|
1609
|
+
group: v.optional(group),
|
|
1610
|
+
target: v.optional(target),
|
|
1611
|
+
filter: v.optional(ConditionSchema),
|
|
1612
|
+
requirements: v.optional(v.array(GroqRequirementSchema)),
|
|
1613
|
+
actions: v.optional(v.array(action)),
|
|
1614
|
+
fields: v.optional(v.array(field))
|
|
1615
|
+
};
|
|
1940
1616
|
}
|
|
1941
1617
|
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1618
|
+
const StoredActivitySchema = pinned()(v.strictObject(activityFields({
|
|
1619
|
+
field: FieldEntrySchema,
|
|
1620
|
+
action: StoredActionSchema,
|
|
1621
|
+
target: StoredManualTargetSchema,
|
|
1622
|
+
group: StoredGroupMembershipSchema
|
|
1623
|
+
}))), AuthoringActivitySchema = pinned()(v.strictObject(activityFields({
|
|
1624
|
+
field: AuthoringFieldEntrySchema,
|
|
1625
|
+
action: AuthoringActionSchema,
|
|
1626
|
+
target: AuthoringManualTargetSchema,
|
|
1627
|
+
group: AuthoringGroupMembershipSchema
|
|
1628
|
+
})));
|
|
1949
1629
|
|
|
1950
|
-
function
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
}, visit);
|
|
1630
|
+
function transitionFields(when) {
|
|
1631
|
+
return {
|
|
1632
|
+
name: NonEmpty,
|
|
1633
|
+
title: v.optional(v.string()),
|
|
1634
|
+
description: v.optional(v.string()),
|
|
1635
|
+
to: NonEmpty,
|
|
1636
|
+
when: when
|
|
1637
|
+
};
|
|
1959
1638
|
}
|
|
1960
1639
|
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
}
|
|
1640
|
+
const StoredTransitionSchema = pinned()(v.strictObject(transitionFields(ConditionSchema))), AuthoringTransitionSchema = pinned()(v.strictObject(transitionFields(v.optional(ConditionSchema)))), GuardActionSchema = picklist(MUTATION_GUARD_ACTIONS), GuardActionsSchema = v.array(GuardActionSchema), NonEmptyGuardActionsSchema = v.pipe(GuardActionsSchema, v.minLength(1, GUARD_ACTIONS_REQUIRED_MESSAGE)), GuardReadPath = v.pipe(NonEmpty, v.check(path => !/[\r\n\u2028\u2029]/.test(path), "a guard read path cannot contain a line break")), GuardReadSchema = pinned()(v.variant("type", [ v.strictObject({
|
|
1641
|
+
type: v.literal("self")
|
|
1642
|
+
}), v.strictObject({
|
|
1643
|
+
type: v.literal("now")
|
|
1644
|
+
}), v.strictObject({
|
|
1645
|
+
type: v.literal("fieldRead"),
|
|
1646
|
+
field: FieldEntryName,
|
|
1647
|
+
path: v.optional(GuardReadPath)
|
|
1648
|
+
}), v.strictObject({
|
|
1649
|
+
type: v.literal("effectsRead"),
|
|
1650
|
+
effect: v.pipe(NonEmpty, v.check(name => !name.includes("'"), "an effect name cannot contain `'`")),
|
|
1651
|
+
path: v.optional(GuardReadPath)
|
|
1652
|
+
}) ]));
|
|
1970
1653
|
|
|
1971
|
-
function
|
|
1972
|
-
return
|
|
1654
|
+
function guardMatchFields(read, actions) {
|
|
1655
|
+
return {
|
|
1656
|
+
types: v.optional(v.array(NonEmpty)),
|
|
1657
|
+
idRefs: v.optional(v.array(read)),
|
|
1658
|
+
idPatterns: v.optional(v.array(NonEmpty)),
|
|
1659
|
+
actions: actions
|
|
1660
|
+
};
|
|
1973
1661
|
}
|
|
1974
1662
|
|
|
1975
|
-
function
|
|
1976
|
-
return
|
|
1663
|
+
function guardFields(read, actions) {
|
|
1664
|
+
return {
|
|
1665
|
+
name: NonEmpty,
|
|
1666
|
+
title: v.optional(v.string()),
|
|
1667
|
+
description: v.optional(v.string()),
|
|
1668
|
+
match: v.strictObject(guardMatchFields(read, actions)),
|
|
1669
|
+
predicate: v.optional(v.string()),
|
|
1670
|
+
metadata: v.optional(v.record(NonEmpty, read))
|
|
1671
|
+
};
|
|
1977
1672
|
}
|
|
1978
1673
|
|
|
1979
|
-
|
|
1980
|
-
return "issues" in check ? check.issues : void 0;
|
|
1981
|
-
}
|
|
1674
|
+
const GuardSchema = v.strictObject(guardFields(NonEmpty, GuardActionsSchema)), AuthoringGuardSchema = v.strictObject(guardFields(GuardReadSchema, NonEmptyGuardActionsSchema));
|
|
1982
1675
|
|
|
1983
|
-
function
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1676
|
+
function stageFields({field: field, activity: activity, transition: transition, guard: guard, editable: editable}) {
|
|
1677
|
+
return {
|
|
1678
|
+
name: NonEmpty,
|
|
1679
|
+
semantics: SemanticsFieldSchema,
|
|
1680
|
+
title: v.optional(v.string()),
|
|
1681
|
+
description: v.optional(v.string()),
|
|
1682
|
+
groups: v.optional(v.array(GroupSchema)),
|
|
1683
|
+
activities: v.optional(v.array(activity)),
|
|
1684
|
+
transitions: v.optional(v.array(transition)),
|
|
1685
|
+
guards: v.optional(v.array(guard)),
|
|
1686
|
+
fields: v.optional(v.array(field)),
|
|
1687
|
+
editable: v.optional(v.record(FieldEntryName, editable))
|
|
1688
|
+
};
|
|
1992
1689
|
}
|
|
1993
1690
|
|
|
1994
|
-
const
|
|
1691
|
+
const StoredStageSchema = pinned()(v.strictObject(stageFields({
|
|
1692
|
+
field: FieldEntrySchema,
|
|
1693
|
+
activity: StoredActivitySchema,
|
|
1694
|
+
transition: StoredTransitionSchema,
|
|
1695
|
+
guard: GuardSchema,
|
|
1696
|
+
editable: StoredEditableSchema
|
|
1697
|
+
}))), AuthoringStageSchema = pinned()(v.strictObject(stageFields({
|
|
1698
|
+
field: AuthoringFieldEntrySchema,
|
|
1699
|
+
activity: AuthoringActivitySchema,
|
|
1700
|
+
transition: AuthoringTransitionSchema,
|
|
1701
|
+
guard: AuthoringGuardSchema,
|
|
1702
|
+
editable: AuthoringEditableSchema
|
|
1703
|
+
}))), RoleAliasesSchema = pinned()(v.record(NonEmpty, v.pipe(v.array(NonEmpty), v.minLength(1, "a role alias must list at least one fulfilling role")))), WORKFLOW_LIFECYCLES = _exhaustiveOptions()([ "standalone", "child" ]), START_KINDS = _exhaustiveOptions()([ "interactive", "autonomous" ]);
|
|
1995
1704
|
|
|
1996
|
-
function
|
|
1997
|
-
return
|
|
1705
|
+
function startFields(kind) {
|
|
1706
|
+
return {
|
|
1707
|
+
kind: kind,
|
|
1708
|
+
filter: v.optional(ConditionSchema),
|
|
1709
|
+
requirements: v.optional(v.array(StartRequirementSchema))
|
|
1710
|
+
};
|
|
1998
1711
|
}
|
|
1999
1712
|
|
|
2000
|
-
|
|
2001
|
-
|
|
1713
|
+
const StoredStartSchema = pinned()(v.strictObject(startFields(picklist(START_KINDS)))), AuthoringStartSchema = pinned()(v.strictObject(startFields(v.optional(picklist(START_KINDS)))));
|
|
1714
|
+
|
|
1715
|
+
function workflowFields({field: field, stage: stage, start: start}) {
|
|
1716
|
+
return {
|
|
1717
|
+
name: NonEmpty,
|
|
1718
|
+
semantics: SemanticsFieldSchema,
|
|
1719
|
+
title: NonEmpty,
|
|
1720
|
+
description: v.optional(v.string()),
|
|
1721
|
+
groups: v.optional(v.array(GroupSchema)),
|
|
1722
|
+
lifecycle: v.optional(picklist(WORKFLOW_LIFECYCLES)),
|
|
1723
|
+
start: v.optional(start),
|
|
1724
|
+
initialStage: NonEmpty,
|
|
1725
|
+
fields: v.optional(v.array(field)),
|
|
1726
|
+
stages: v.pipe(v.array(stage), v.minLength(1, "must declare at least one stage")),
|
|
1727
|
+
predicates: v.optional(v.record(groqIdentifier("`$<name>`"), ConditionSchema)),
|
|
1728
|
+
roleAliases: v.optional(RoleAliasesSchema)
|
|
1729
|
+
};
|
|
2002
1730
|
}
|
|
2003
1731
|
|
|
2004
|
-
const
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
"doc.ref": v.union([ v.null(), AuthoringGdrShape ]),
|
|
2010
|
-
"doc.refs": v.array(AuthoringGdrShape),
|
|
2011
|
-
subject: v.union([ v.null(), AuthoringGdrShape ]),
|
|
2012
|
-
"release.ref": v.union([ v.null(), v.looseObject({
|
|
2013
|
-
id: AuthoringRefId,
|
|
2014
|
-
type: v.literal("system.release"),
|
|
2015
|
-
releaseName: NonEmptyString
|
|
2016
|
-
}) ])
|
|
2017
|
-
};
|
|
2018
|
-
|
|
2019
|
-
function checkLiteralSeed(args) {
|
|
2020
|
-
return args.value === null ? [ "a literal seed cannot be null — omit `initialValue` to start the field empty" ] : issuesOf(checkValueAgainst(args, seedValueSchemas));
|
|
2021
|
-
}
|
|
1732
|
+
const WorkflowDefinitionSchema = pinned()(v.strictObject(workflowFields({
|
|
1733
|
+
field: FieldEntrySchema,
|
|
1734
|
+
stage: StoredStageSchema,
|
|
1735
|
+
start: StoredStartSchema
|
|
1736
|
+
})));
|
|
2022
1737
|
|
|
2023
|
-
function
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
issues: [ `field entry type ${args.entryType} does not support append` ],
|
|
2029
|
-
mode: "item"
|
|
2030
|
-
});
|
|
2031
|
-
const result = v.safeParse(schema, args.item);
|
|
2032
|
-
if (!result.success) throw new FieldValueShapeError({
|
|
2033
|
-
entryType: args.entryType,
|
|
2034
|
-
entryName: args.entryName,
|
|
2035
|
-
issues: formatIssues(result.issues),
|
|
2036
|
-
mode: "item"
|
|
2037
|
-
});
|
|
2038
|
-
const assignmentArgs = {
|
|
2039
|
-
entryType: appendAssignmentEntryType(args.entryType),
|
|
2040
|
-
value: result.output,
|
|
2041
|
-
roles: args.roles,
|
|
2042
|
-
fields: args.entryType === "array" ? args.of : void 0,
|
|
2043
|
-
memberRoles: args.memberRoles,
|
|
2044
|
-
roleAliases: args.roleAliases
|
|
2045
|
-
}, assignmentIssues = assignmentValueIssues(assignmentArgs, result.output);
|
|
2046
|
-
if (assignmentIssues !== void 0) throw new FieldValueShapeError({
|
|
2047
|
-
entryType: args.entryType,
|
|
2048
|
-
entryName: args.entryName,
|
|
2049
|
-
issues: assignmentIssues,
|
|
2050
|
-
mode: "item"
|
|
2051
|
-
});
|
|
2052
|
-
const typeIssues = refTypeIssues({
|
|
2053
|
-
entryType: args.entryType,
|
|
2054
|
-
types: args.types,
|
|
2055
|
-
value: [ args.item ]
|
|
2056
|
-
});
|
|
2057
|
-
if (typeIssues !== void 0) throw new FieldValueShapeError({
|
|
2058
|
-
entryType: args.entryType,
|
|
2059
|
-
entryName: args.entryName,
|
|
2060
|
-
issues: typeIssues,
|
|
2061
|
-
mode: "item"
|
|
1738
|
+
function parseStoredDefinition(input, label) {
|
|
1739
|
+
return parseOrThrow({
|
|
1740
|
+
schema: WorkflowDefinitionSchema,
|
|
1741
|
+
input: input,
|
|
1742
|
+
label: label
|
|
2062
1743
|
});
|
|
2063
|
-
return result.output;
|
|
2064
1744
|
}
|
|
2065
1745
|
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
1746
|
+
const AuthoringWorkflowSchema = pinned()(v.strictObject({
|
|
1747
|
+
...workflowFields({
|
|
1748
|
+
field: AuthoringFieldEntrySchema,
|
|
1749
|
+
stage: AuthoringStageSchema,
|
|
1750
|
+
start: AuthoringStartSchema
|
|
1751
|
+
}),
|
|
1752
|
+
runtime: v.optional(RuntimeBlockSchema)
|
|
1753
|
+
})), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
|
|
2069
1754
|
|
|
2070
|
-
function
|
|
2071
|
-
|
|
2072
|
-
const keys = [ ...prefix, ...issue.path?.map(p => p.key) ?? [] ], sub = issue.issues?.filter(candidate => candidate.expected !== "null");
|
|
2073
|
-
return sub !== void 0 && sub.length > 0 ? sub.flatMap(candidate => formatOne(candidate, keys)) : [ `${keys.length > 0 ? `at ${keys.join(".")}: ` : ""}${formatMessage(issue)}` ];
|
|
2074
|
-
};
|
|
2075
|
-
return issues.flatMap(issue => formatOne(issue, []));
|
|
1755
|
+
function isStartableDefinition(definition) {
|
|
1756
|
+
return definition.lifecycle !== "child";
|
|
2076
1757
|
}
|
|
2077
1758
|
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
function groqIdentifier(referencedAs) {
|
|
2081
|
-
return v.pipe(v.string(), v.regex(GROQ_IDENTIFIER, `must be a GROQ-safe identifier (letters, digits, underscore; not starting with a digit) because it is referenced as ${referencedAs} in GROQ conditions`));
|
|
1759
|
+
function startKindOf(definition) {
|
|
1760
|
+
return definition.start?.kind ?? "interactive";
|
|
2082
1761
|
}
|
|
2083
1762
|
|
|
2084
|
-
function
|
|
2085
|
-
return
|
|
1763
|
+
function isSubjectEntry(entry) {
|
|
1764
|
+
return entry.type === "subject";
|
|
2086
1765
|
}
|
|
2087
1766
|
|
|
2088
|
-
function
|
|
2089
|
-
return
|
|
1767
|
+
function isDueDateEntry(entry) {
|
|
1768
|
+
return entry.type === "dueDate" || entry.type === "dueDatetime";
|
|
2090
1769
|
}
|
|
2091
1770
|
|
|
2092
|
-
function
|
|
2093
|
-
return
|
|
1771
|
+
function isInputSourced(entry) {
|
|
1772
|
+
return entry.initialValue?.type === "input";
|
|
2094
1773
|
}
|
|
2095
1774
|
|
|
2096
|
-
function
|
|
2097
|
-
|
|
1775
|
+
function parseOrThrow({schema: schema, input: input, label: label}) {
|
|
1776
|
+
const result = v.safeParse(schema, input);
|
|
1777
|
+
if (!result.success) throw new Error(formatValidationError(label, issuesFromValibot(result.issues)));
|
|
1778
|
+
return result.output;
|
|
2098
1779
|
}
|
|
2099
1780
|
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
type: v.literal("fieldRead"),
|
|
2105
|
-
scope: v.optional(v.union([ v.literal("workflow"), v.literal("stage") ])),
|
|
2106
|
-
field: NonEmpty,
|
|
2107
|
-
path: v.optional(v.string())
|
|
2108
|
-
}), FieldSourceSchema = v.variant("type", [ v.strictObject({
|
|
2109
|
-
type: v.literal("input")
|
|
2110
|
-
}), v.strictObject({
|
|
2111
|
-
type: v.literal("query"),
|
|
2112
|
-
query: NonEmpty
|
|
2113
|
-
}), LiteralSchema, FieldReadSchema ]), ValueExprSchema = v.lazy(() => v.variant("type", [ LiteralSchema, FieldReadSchema, v.strictObject({
|
|
2114
|
-
type: v.literal("param"),
|
|
2115
|
-
param: NonEmpty
|
|
2116
|
-
}), v.strictObject({
|
|
2117
|
-
type: v.literal("actor")
|
|
2118
|
-
}), v.strictObject({
|
|
2119
|
-
type: v.literal("now")
|
|
2120
|
-
}), v.strictObject({
|
|
2121
|
-
type: v.literal("self")
|
|
2122
|
-
}), v.strictObject({
|
|
2123
|
-
type: v.literal("stage")
|
|
2124
|
-
}), v.strictObject({
|
|
2125
|
-
type: v.literal("object"),
|
|
2126
|
-
fields: v.record(NonEmpty, ValueExprSchema)
|
|
2127
|
-
}) ])), StoredFieldRefSchema = v.strictObject({
|
|
2128
|
-
scope: picklist(FIELD_SCOPES),
|
|
2129
|
-
field: NonEmpty
|
|
2130
|
-
}), AuthoringFieldRefSchema = v.strictObject({
|
|
2131
|
-
scope: v.optional(picklist(FIELD_SCOPES)),
|
|
2132
|
-
field: NonEmpty
|
|
2133
|
-
}), HREF_SCHEMES = [ "http:", "https:" ];
|
|
2134
|
-
|
|
2135
|
-
function isHttpUrl(value) {
|
|
2136
|
-
try {
|
|
2137
|
-
return HREF_SCHEMES.includes(new URL(value).protocol);
|
|
2138
|
-
} catch {
|
|
2139
|
-
return !1;
|
|
1781
|
+
function labelFor(fn, value) {
|
|
1782
|
+
if (value && typeof value == "object" && "name" in value) {
|
|
1783
|
+
const raw = value.name;
|
|
1784
|
+
if (typeof raw == "string") return `${fn}("${raw}")`;
|
|
2140
1785
|
}
|
|
1786
|
+
return fn;
|
|
2141
1787
|
}
|
|
2142
1788
|
|
|
2143
|
-
const
|
|
2144
|
-
|
|
2145
|
-
function manualTargetSchema(ref) {
|
|
2146
|
-
return v.variant("type", [ v.strictObject({
|
|
2147
|
-
type: v.literal("url"),
|
|
2148
|
-
url: UrlString
|
|
2149
|
-
}), v.strictObject({
|
|
2150
|
-
type: v.literal("field"),
|
|
2151
|
-
field: ref
|
|
2152
|
-
}) ]);
|
|
2153
|
-
}
|
|
2154
|
-
|
|
2155
|
-
const StoredManualTargetSchema = manualTargetSchema(StoredFieldRefSchema), AuthoringManualTargetSchema = manualTargetSchema(v.union([ NonEmpty, AuthoringFieldRefSchema ])), ConditionSchema = NonEmpty;
|
|
1789
|
+
const NonEmptyString = v.pipe(v.string(), v.nonEmpty("must not be empty"));
|
|
2156
1790
|
|
|
2157
|
-
function
|
|
2158
|
-
return
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
value: ValueExprSchema
|
|
2166
|
-
}), v.strictObject({
|
|
2167
|
-
type: v.literal("field.unset"),
|
|
2168
|
-
target: targetSchema
|
|
2169
|
-
}), v.strictObject({
|
|
2170
|
-
type: v.literal("field.append"),
|
|
2171
|
-
target: targetSchema,
|
|
2172
|
-
value: ValueExprSchema
|
|
2173
|
-
}), v.strictObject({
|
|
2174
|
-
type: v.literal("field.inc"),
|
|
2175
|
-
target: targetSchema,
|
|
2176
|
-
value: v.optional(ValueExprSchema)
|
|
2177
|
-
}), v.strictObject({
|
|
2178
|
-
type: v.literal("field.dec"),
|
|
2179
|
-
target: targetSchema,
|
|
2180
|
-
value: v.optional(ValueExprSchema)
|
|
2181
|
-
}), v.strictObject({
|
|
2182
|
-
type: v.literal("field.updateWhere"),
|
|
2183
|
-
target: targetSchema,
|
|
2184
|
-
where: ConditionSchema,
|
|
2185
|
-
value: ValueExprSchema
|
|
2186
|
-
}), v.strictObject({
|
|
2187
|
-
type: v.literal("field.removeWhere"),
|
|
2188
|
-
target: targetSchema,
|
|
2189
|
-
where: ConditionSchema
|
|
2190
|
-
}) ];
|
|
1791
|
+
function asPredicate(validate) {
|
|
1792
|
+
return value => {
|
|
1793
|
+
try {
|
|
1794
|
+
return validate(value), !0;
|
|
1795
|
+
} catch {
|
|
1796
|
+
return !1;
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
2191
1799
|
}
|
|
2192
1800
|
|
|
2193
|
-
const
|
|
2194
|
-
type: v.literal("status.set"),
|
|
2195
|
-
activity: NonEmpty,
|
|
2196
|
-
status: picklist(ACTIVITY_STATUSES)
|
|
2197
|
-
}) ]), AuditOpSchema = v.strictObject({
|
|
2198
|
-
type: v.literal("audit"),
|
|
2199
|
-
target: AuthoringFieldRefSchema,
|
|
2200
|
-
value: ValueExprSchema,
|
|
2201
|
-
stampFields: v.optional(v.strictObject({
|
|
2202
|
-
actor: v.optional(NonEmpty),
|
|
2203
|
-
at: v.optional(NonEmpty)
|
|
2204
|
-
}))
|
|
2205
|
-
}), AuthoringOpSchema = v.variant("type", [ ...opSchemas(AuthoringFieldRefSchema), v.strictObject({
|
|
2206
|
-
type: v.literal("status.set"),
|
|
2207
|
-
activity: v.optional(NonEmpty),
|
|
2208
|
-
status: picklist(ACTIVITY_STATUSES)
|
|
2209
|
-
}), AuditOpSchema ]), GroupName = v.pipe(v.string(), v.regex(GROQ_IDENTIFIER, "must be an identifier (letters, digits, underscore; not starting with a digit)")), GroupSchema = pinned()(v.strictObject({
|
|
2210
|
-
name: GroupName,
|
|
2211
|
-
title: v.optional(v.string()),
|
|
2212
|
-
description: v.optional(v.string()),
|
|
2213
|
-
kind: v.optional(picklist(GROUP_KINDS))
|
|
2214
|
-
})), GroupNameList = v.pipe(v.array(GroupName), v.minLength(1, "name at least one group, or omit `group`"), v.check(names => new Set(names).size === names.length, "a group is listed more than once — list each group once")), StoredGroupMembershipSchema = GroupNameList, AuthoringGroupMembershipSchema = v.union([ GroupName, GroupNameList ]);
|
|
1801
|
+
const isValidTag = asPredicate(validateTag), isValidAliasName = asPredicate(validateResourceAliasName), isValidDatasetId = asPredicate(datasetResourceParts);
|
|
2215
1802
|
|
|
2216
|
-
function
|
|
2217
|
-
return
|
|
1803
|
+
function lakeSegment(label) {
|
|
1804
|
+
return v.pipe(v.string(), v.nonEmpty(), v.check(isValidTag, `invalid ${label} — ${LAKE_ID_SEGMENT_GLOSS}`));
|
|
2218
1805
|
}
|
|
2219
1806
|
|
|
2220
|
-
const
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
}), v.
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
1807
|
+
const WorkflowResourceSchema = v.variant("type", [ v.object({
|
|
1808
|
+
type: v.literal("dataset"),
|
|
1809
|
+
id: v.pipe(NonEmptyString, v.check(isValidDatasetId, 'invalid dataset resource id — expected "<projectId>.<dataset>"'))
|
|
1810
|
+
}), v.object({
|
|
1811
|
+
type: v.literal("canvas"),
|
|
1812
|
+
id: NonEmptyString
|
|
1813
|
+
}), v.object({
|
|
1814
|
+
type: v.literal("media-library"),
|
|
1815
|
+
id: NonEmptyString
|
|
1816
|
+
}), v.object({
|
|
1817
|
+
type: v.literal("dashboard"),
|
|
1818
|
+
id: NonEmptyString
|
|
1819
|
+
}) ]), ResourceBindingSchema = v.object({
|
|
1820
|
+
name: v.pipe(NonEmptyString, v.check(isValidAliasName, "invalid resource handle name — lowercase letters, digits and dashes only, no leading dash")),
|
|
1821
|
+
resource: WorkflowResourceSchema
|
|
1822
|
+
}), DefinitionSchema = v.custom(input => typeof input == "object" && input !== null && typeof input.name == "string", "expected a workflow definition (an object with a string `name`)"), DeploymentSchema = v.object({
|
|
1823
|
+
name: lakeSegment("name"),
|
|
1824
|
+
expectedMinReaderModel: v.optional(v.custom(() => !0), void 0),
|
|
1825
|
+
tag: lakeSegment("tag"),
|
|
1826
|
+
workflowResource: WorkflowResourceSchema,
|
|
1827
|
+
resourceAliases: v.optional(v.pipe(v.array(ResourceBindingSchema), v.check(bindings => duplicateHandleMessage(bindings) === void 0, issue => duplicateHandleMessage(issue.input) ?? "duplicate resource handle name"))),
|
|
1828
|
+
definitions: v.pipe(v.array(DefinitionSchema), v.minLength(1, "a deployment needs at least one definition")),
|
|
1829
|
+
runtime: v.optional(RuntimeBlockSchema)
|
|
2228
1830
|
});
|
|
2229
1831
|
|
|
2230
|
-
function
|
|
2231
|
-
|
|
1832
|
+
function firstDuplicatePair(items, keyOf) {
|
|
1833
|
+
const seen = /* @__PURE__ */ new Map;
|
|
1834
|
+
for (const item of items) {
|
|
1835
|
+
const key = keyOf(item), earlier = seen.get(key);
|
|
1836
|
+
if (earlier !== void 0) return [ earlier, item ];
|
|
1837
|
+
seen.set(key, item);
|
|
1838
|
+
}
|
|
2232
1839
|
}
|
|
2233
1840
|
|
|
2234
|
-
function
|
|
2235
|
-
const
|
|
2236
|
-
|
|
1841
|
+
function duplicateHandleMessage(bindings) {
|
|
1842
|
+
const pair = firstDuplicatePair(bindings, binding => binding.name);
|
|
1843
|
+
if (pair !== void 0) return `duplicate resource handle name "${pair[1].name}" — each binding name must be unique within a deployment`;
|
|
2237
1844
|
}
|
|
2238
1845
|
|
|
2239
|
-
function
|
|
2240
|
-
const
|
|
2241
|
-
|
|
1846
|
+
function duplicateNameMessage(deployments) {
|
|
1847
|
+
const pair = firstDuplicatePair(deployments, deployment => deployment.name);
|
|
1848
|
+
if (pair !== void 0) return `duplicate deployment name "${pair[1].name}" — each deployment must use a unique name`;
|
|
2242
1849
|
}
|
|
2243
1850
|
|
|
2244
|
-
function
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
1851
|
+
function partitionKey(deployment) {
|
|
1852
|
+
return `${resourceGdr(deployment.workflowResource)} ${deployment.tag}`;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
function partitionCollisionMessage(deployments) {
|
|
1856
|
+
const pair = firstDuplicatePair(deployments, partitionKey);
|
|
1857
|
+
if (pair === void 0) return;
|
|
1858
|
+
const [first, second] = pair;
|
|
1859
|
+
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`;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
const TelemetryLoggerSchema = v.custom(input => typeof input == "object" && input !== null && typeof input.log == "function", "expected a telemetry logger (an object with a `log` function)"), WorkflowConfigSchema = v.object({
|
|
1863
|
+
deployments: v.pipe(v.array(DeploymentSchema), v.minLength(1, "a config needs at least one deployment"), v.check(deployments => duplicateNameMessage(deployments) === void 0, issue => duplicateNameMessage(issue.input) ?? "duplicate deployment name"), v.check(deployments => partitionCollisionMessage(deployments) === void 0, issue => partitionCollisionMessage(issue.input) ?? "duplicate deployment partition")),
|
|
1864
|
+
telemetry: v.optional(TelemetryLoggerSchema)
|
|
1865
|
+
});
|
|
1866
|
+
|
|
1867
|
+
function resourceAliasesToMap(resourceAliases) {
|
|
1868
|
+
return Object.fromEntries((resourceAliases ?? []).map(binding => [ binding.name, binding.resource ]));
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
const FIELD_READ = /^\$fields\.(\w+)(?:\.(.+))?$/, EFFECTS_READ = /^\$effects\['([^']+)'\](?:\.(.+))?$/;
|
|
1872
|
+
|
|
1873
|
+
function isGuardReadExpr(expr) {
|
|
1874
|
+
return expr === "$self" || expr === "$now" || FIELD_READ.test(expr) || EFFECTS_READ.test(expr);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
function printGuardRead(read) {
|
|
1878
|
+
switch (read.type) {
|
|
1879
|
+
case "self":
|
|
1880
|
+
return "$self";
|
|
1881
|
+
|
|
1882
|
+
case "now":
|
|
1883
|
+
return "$now";
|
|
1884
|
+
|
|
1885
|
+
case "fieldRead":
|
|
1886
|
+
return read.path === void 0 ? `$fields.${read.field}` : `$fields.${read.field}.${read.path}`;
|
|
1887
|
+
|
|
1888
|
+
case "effectsRead":
|
|
1889
|
+
return read.path === void 0 ? `$effects['${read.effect}']` : `$effects['${read.effect}'].${read.path}`;
|
|
2255
1890
|
}
|
|
2256
1891
|
}
|
|
2257
1892
|
|
|
2258
|
-
function
|
|
2259
|
-
return
|
|
1893
|
+
function groq(strings, ...values) {
|
|
1894
|
+
return strings.reduce((out, part, i) => i === 0 ? part : `${out}${serializeGroqValue(values[i - 1])}${part}`, "");
|
|
2260
1895
|
}
|
|
2261
1896
|
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
options: v.optional(ChoiceOptionsSchema),
|
|
2268
|
-
validation: v.optional(ScalarValidationSchema),
|
|
2269
|
-
roles: v.optional(AssignmentRolesSchema),
|
|
2270
|
-
fields: v.optional(v.array(FieldShapeSchema)),
|
|
2271
|
-
of: v.optional(v.array(FieldShapeSchema))
|
|
2272
|
-
}), choiceOptionsCheck(), scalarValidationCheck(), assignmentRolesCheck())), StoredEditableSchema = v.union([ v.literal(!0), NonEmpty ]), AuthoringEditableSchema = v.union([ v.literal(!0), v.array(NonEmpty), NonEmpty ]);
|
|
1897
|
+
function serializeGroqValue(value) {
|
|
1898
|
+
const serialized = JSON.stringify(value);
|
|
1899
|
+
if (serialized === void 0) throw new Error(`groq tag cannot serialize ${typeof value} — interpolate JSON-representable values only`);
|
|
1900
|
+
return serialized;
|
|
1901
|
+
}
|
|
2273
1902
|
|
|
2274
|
-
function
|
|
1903
|
+
function desugarWorkflow(authoring) {
|
|
1904
|
+
const issues = [];
|
|
1905
|
+
checkReservedRoleAliasKeys(authoring.roleAliases, issues);
|
|
1906
|
+
const roleAliases = normalizeRoleAliases(authoring.roleAliases), ctx = {
|
|
1907
|
+
issues: issues,
|
|
1908
|
+
roleAliases: roleAliases,
|
|
1909
|
+
effectRuntime: /* @__PURE__ */ new Map
|
|
1910
|
+
}, workflowFields2 = desugarFieldEntries({
|
|
1911
|
+
entries: authoring.fields,
|
|
1912
|
+
path: [ "fields" ],
|
|
1913
|
+
ctx: ctx
|
|
1914
|
+
}), workflowLayer = layerOf(workflowFields2), stages = authoring.stages.map((stage, i) => {
|
|
1915
|
+
const path = [ "stages", i ], stageFields2 = desugarFieldEntries({
|
|
1916
|
+
entries: stage.fields,
|
|
1917
|
+
path: [ ...path, "fields" ],
|
|
1918
|
+
ctx: ctx
|
|
1919
|
+
}), stageEnv = {
|
|
1920
|
+
layers: [ {
|
|
1921
|
+
scope: "stage",
|
|
1922
|
+
entries: layerOf(stageFields2)
|
|
1923
|
+
}, {
|
|
1924
|
+
scope: "workflow",
|
|
1925
|
+
entries: workflowLayer
|
|
1926
|
+
} ]
|
|
1927
|
+
}, activities = (stage.activities ?? []).map((activity, j) => desugarActivity({
|
|
1928
|
+
activity: activity,
|
|
1929
|
+
path: [ ...path, "activities", j ],
|
|
1930
|
+
stageEnv: stageEnv,
|
|
1931
|
+
ctx: ctx
|
|
1932
|
+
})), transitions = (stage.transitions ?? []).map(transition => desugarTransition({
|
|
1933
|
+
transition: transition
|
|
1934
|
+
})), editable = desugarStageEditable({
|
|
1935
|
+
overrides: stage.editable,
|
|
1936
|
+
inScope: editableFieldNames({
|
|
1937
|
+
workflowFields: workflowFields2,
|
|
1938
|
+
stageFields: stageFields2,
|
|
1939
|
+
activities: activities
|
|
1940
|
+
}),
|
|
1941
|
+
path: [ ...path, "editable" ],
|
|
1942
|
+
ctx: ctx
|
|
1943
|
+
});
|
|
1944
|
+
return {
|
|
1945
|
+
...stripUndefined({
|
|
1946
|
+
name: stage.name,
|
|
1947
|
+
semantics: stage.semantics,
|
|
1948
|
+
title: stage.title,
|
|
1949
|
+
description: stage.description,
|
|
1950
|
+
groups: stage.groups,
|
|
1951
|
+
guards: stage.guards?.map(desugarGuard)
|
|
1952
|
+
}),
|
|
1953
|
+
...stageFields2 ? {
|
|
1954
|
+
fields: stageFields2
|
|
1955
|
+
} : {},
|
|
1956
|
+
...activities.length > 0 ? {
|
|
1957
|
+
activities: activities
|
|
1958
|
+
} : {},
|
|
1959
|
+
...transitions.length > 0 ? {
|
|
1960
|
+
transitions: transitions
|
|
1961
|
+
} : {},
|
|
1962
|
+
...editable ? {
|
|
1963
|
+
editable: editable
|
|
1964
|
+
} : {}
|
|
1965
|
+
};
|
|
1966
|
+
});
|
|
2275
1967
|
return {
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
1968
|
+
definition: {
|
|
1969
|
+
...stripUndefined({
|
|
1970
|
+
name: authoring.name,
|
|
1971
|
+
semantics: authoring.semantics,
|
|
1972
|
+
title: authoring.title,
|
|
1973
|
+
description: authoring.description,
|
|
1974
|
+
groups: authoring.groups,
|
|
1975
|
+
lifecycle: authoring.lifecycle,
|
|
1976
|
+
start: desugarStart(authoring.start),
|
|
1977
|
+
initialStage: authoring.initialStage,
|
|
1978
|
+
predicates: authoring.predicates,
|
|
1979
|
+
roleAliases: roleAliases
|
|
1980
|
+
}),
|
|
1981
|
+
...workflowFields2 ? {
|
|
1982
|
+
fields: workflowFields2
|
|
1983
|
+
} : {},
|
|
1984
|
+
stages: stages
|
|
1985
|
+
},
|
|
1986
|
+
runtime: collectRuntime({
|
|
1987
|
+
authoring: authoring,
|
|
1988
|
+
ctx: ctx
|
|
1989
|
+
}),
|
|
1990
|
+
issues: ctx.issues
|
|
2283
1991
|
};
|
|
2284
1992
|
}
|
|
2285
1993
|
|
|
2286
|
-
function
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
1994
|
+
function collectRuntime(args) {
|
|
1995
|
+
const {kind: kind} = args.authoring.runtime ?? {}, effects = Object.fromEntries(args.ctx.effectRuntime), declared = {
|
|
1996
|
+
...kind === void 0 ? {} : {
|
|
1997
|
+
kind: kind
|
|
1998
|
+
},
|
|
1999
|
+
...Object.keys(effects).length === 0 ? {} : {
|
|
2000
|
+
effects: effects
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
2003
|
+
return Object.keys(declared).length === 0 ? void 0 : declared;
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
function desugarEffect(args) {
|
|
2007
|
+
const {runtime: runtime, ...effect} = args.effect;
|
|
2008
|
+
runtime !== void 0 && args.ctx.effectRuntime.set(effect.name, runtime);
|
|
2009
|
+
const {retry: retry} = effect;
|
|
2010
|
+
return retry === void 0 ? effect : {
|
|
2011
|
+
...effect,
|
|
2012
|
+
retry: {
|
|
2013
|
+
...retry,
|
|
2014
|
+
kind: retry.kind ?? "engine"
|
|
2015
|
+
}
|
|
2296
2016
|
};
|
|
2297
2017
|
}
|
|
2298
2018
|
|
|
2299
|
-
function
|
|
2300
|
-
if (
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2019
|
+
function desugarStart(start) {
|
|
2020
|
+
if (start !== void 0) return {
|
|
2021
|
+
kind: start.kind ?? "interactive",
|
|
2022
|
+
...start.filter !== void 0 ? {
|
|
2023
|
+
filter: start.filter
|
|
2024
|
+
} : {},
|
|
2025
|
+
...start.requirements !== void 0 ? {
|
|
2026
|
+
requirements: start.requirements
|
|
2027
|
+
} : {}
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
function checkReservedRoleAliasKeys(aliases, issues) {
|
|
2032
|
+
for (const key of Object.keys(aliases ?? {})) key.startsWith("$") && issues.push({
|
|
2033
|
+
path: [ "roleAliases", key ],
|
|
2034
|
+
message: `role alias key "${key}" uses the reserved "$" prefix — that namespace is the engine's stored spelling for the universal fulfiller. Use "*" to mean "fulfills any gate", or rename the role.`
|
|
2309
2035
|
});
|
|
2310
2036
|
}
|
|
2311
2037
|
|
|
2312
|
-
|
|
2313
|
-
|
|
2038
|
+
const TODOLIST_OF = [ {
|
|
2039
|
+
type: "string",
|
|
2040
|
+
name: "label",
|
|
2041
|
+
title: "Label"
|
|
2042
|
+
}, {
|
|
2043
|
+
type: "string",
|
|
2044
|
+
name: "status",
|
|
2045
|
+
title: "Status"
|
|
2046
|
+
}, {
|
|
2047
|
+
type: "assignee",
|
|
2048
|
+
name: "assignee",
|
|
2049
|
+
title: "Assignee"
|
|
2050
|
+
}, {
|
|
2051
|
+
type: "date",
|
|
2052
|
+
name: "dueDate",
|
|
2053
|
+
title: "Due date"
|
|
2054
|
+
} ], NOTES_OF = [ {
|
|
2055
|
+
type: "text",
|
|
2056
|
+
name: "body",
|
|
2057
|
+
title: "Body"
|
|
2058
|
+
}, {
|
|
2059
|
+
type: "actor",
|
|
2060
|
+
name: "actor",
|
|
2061
|
+
title: "Actor"
|
|
2062
|
+
}, {
|
|
2063
|
+
type: "datetime",
|
|
2064
|
+
name: "at",
|
|
2065
|
+
title: "At"
|
|
2066
|
+
} ];
|
|
2067
|
+
|
|
2068
|
+
function desugarFieldEntries({entries: entries, path: path, ctx: ctx}) {
|
|
2069
|
+
return !entries || entries.length === 0 ? entries === void 0 ? void 0 : [] : entries.map((entry, i) => desugarFieldEntry({
|
|
2070
|
+
entry: entry,
|
|
2071
|
+
path: [ ...path, i ],
|
|
2072
|
+
ctx: ctx
|
|
2073
|
+
}));
|
|
2314
2074
|
}
|
|
2315
2075
|
|
|
2316
|
-
function
|
|
2317
|
-
|
|
2076
|
+
function desugarFieldEntry({entry: entry, path: path, ctx: ctx}) {
|
|
2077
|
+
if (entry.type === "todoList" || entry.type === "notes") return desugarListField({
|
|
2078
|
+
entry: entry,
|
|
2079
|
+
path: path,
|
|
2080
|
+
ctx: ctx
|
|
2081
|
+
});
|
|
2082
|
+
const editable = normalizeEditable({
|
|
2083
|
+
editable: entry.editable,
|
|
2084
|
+
path: [ ...path, "editable" ],
|
|
2085
|
+
ctx: ctx
|
|
2086
|
+
});
|
|
2087
|
+
return {
|
|
2088
|
+
...stripUndefined({
|
|
2089
|
+
type: entry.type,
|
|
2090
|
+
name: entry.name,
|
|
2091
|
+
title: entry.title,
|
|
2092
|
+
description: entry.description,
|
|
2093
|
+
group: normalizeGroup(entry.group),
|
|
2094
|
+
required: entry.required,
|
|
2095
|
+
initialValue: entry.initialValue,
|
|
2096
|
+
editable: editable,
|
|
2097
|
+
options: entry.options,
|
|
2098
|
+
validation: entry.validation,
|
|
2099
|
+
types: entry.types,
|
|
2100
|
+
roles: entry.roles,
|
|
2101
|
+
fields: entry.fields,
|
|
2102
|
+
of: entry.of
|
|
2103
|
+
})
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
function desugarListField({entry: entry, path: path, ctx: ctx}) {
|
|
2108
|
+
const editable = normalizeEditable({
|
|
2109
|
+
editable: entry.editable,
|
|
2110
|
+
path: [ ...path, "editable" ],
|
|
2111
|
+
ctx: ctx
|
|
2112
|
+
});
|
|
2113
|
+
return {
|
|
2114
|
+
...stripUndefined({
|
|
2115
|
+
name: entry.name,
|
|
2116
|
+
title: entry.title,
|
|
2117
|
+
description: entry.description,
|
|
2118
|
+
group: normalizeGroup(entry.group),
|
|
2119
|
+
required: entry.required,
|
|
2120
|
+
initialValue: entry.initialValue,
|
|
2121
|
+
editable: editable
|
|
2122
|
+
}),
|
|
2123
|
+
type: "array",
|
|
2124
|
+
of: entry.type === "todoList" ? TODOLIST_OF : NOTES_OF
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
function normalizeEditable({editable: editable, path: path, ctx: ctx}) {
|
|
2129
|
+
if (editable === void 0 || editable === !0) return editable;
|
|
2130
|
+
if (Array.isArray(editable)) {
|
|
2131
|
+
const condition = rolesCondition(editable, ctx.roleAliases);
|
|
2132
|
+
if (condition === void 0) {
|
|
2133
|
+
ctx.issues.push({
|
|
2134
|
+
path: path,
|
|
2135
|
+
message: "editable: [] names no roles — use `true` to open the field to anyone in its scope, or list at least one role"
|
|
2136
|
+
});
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
return condition;
|
|
2140
|
+
}
|
|
2141
|
+
return editable;
|
|
2318
2142
|
}
|
|
2319
2143
|
|
|
2320
|
-
function
|
|
2321
|
-
|
|
2144
|
+
function editableFieldNames({workflowFields: workflowFields2, stageFields: stageFields2, activities: activities}) {
|
|
2145
|
+
const names = /* @__PURE__ */ new Set, collect = entries => {
|
|
2146
|
+
for (const entry of entries ?? []) entry.editable !== void 0 && names.add(entry.name);
|
|
2147
|
+
};
|
|
2148
|
+
collect(workflowFields2), collect(stageFields2);
|
|
2149
|
+
for (const activity of activities) collect(activity.fields);
|
|
2150
|
+
return names;
|
|
2322
2151
|
}
|
|
2323
2152
|
|
|
2324
|
-
function
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2153
|
+
function desugarStageEditable({overrides: overrides, inScope: inScope, path: path, ctx: ctx}) {
|
|
2154
|
+
if (overrides === void 0) return;
|
|
2155
|
+
const out = {};
|
|
2156
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
2157
|
+
if (!inScope.has(name)) {
|
|
2158
|
+
ctx.issues.push({
|
|
2159
|
+
path: [ ...path, name ],
|
|
2160
|
+
message: `stage editable override "${name}" does not narrow an editable field in scope — name a workflow/stage/activity field of this stage that declares \`editable\``
|
|
2161
|
+
});
|
|
2162
|
+
continue;
|
|
2163
|
+
}
|
|
2164
|
+
const normalized = normalizeEditable({
|
|
2165
|
+
editable: value,
|
|
2166
|
+
path: [ ...path, name ],
|
|
2167
|
+
ctx: ctx
|
|
2168
|
+
});
|
|
2169
|
+
normalized !== void 0 && (out[name] = normalized);
|
|
2170
|
+
}
|
|
2171
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2334
2172
|
}
|
|
2335
2173
|
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
function scalarValidationCheck() {
|
|
2339
|
-
return v.check(entry => scalarValidationDeclarationIssues(entry) === void 0, issue => (scalarValidationDeclarationIssues(issue.input) ?? []).join("; "));
|
|
2174
|
+
function layerOf(entries) {
|
|
2175
|
+
return new Map((entries ?? []).map(entry => [ entry.name, entry ]));
|
|
2340
2176
|
}
|
|
2341
2177
|
|
|
2342
|
-
function
|
|
2343
|
-
|
|
2344
|
-
if (validation === void 0) return;
|
|
2345
|
-
if (!SCALAR_VALIDATION_KINDS.has(type)) return [ `\`validation\` is only valid on \`string\` / \`text\` / \`number\` / \`progress\` values, not "${type}"` ];
|
|
2346
|
-
if (type === "progress") {
|
|
2347
|
-
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` ]);
|
|
2348
|
-
return issues2.length === 0 ? void 0 : issues2;
|
|
2349
|
-
}
|
|
2350
|
-
if (type === "number") return;
|
|
2351
|
-
const issues = Object.entries(validation).flatMap(([bound, value]) => Number.isInteger(value) && value >= 0 ? [] : [ `\`validation.${bound}\` must be a non-negative integer for ${type} length` ]);
|
|
2352
|
-
return issues.length === 0 ? void 0 : issues;
|
|
2178
|
+
function normalizeGroup(group) {
|
|
2179
|
+
return group === void 0 || Array.isArray(group) ? group : [ group ];
|
|
2353
2180
|
}
|
|
2354
2181
|
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2182
|
+
function desugarActivity({activity: activity, path: path, stageEnv: stageEnv, ctx: ctx}) {
|
|
2183
|
+
const activityFields2 = desugarFieldEntries({
|
|
2184
|
+
entries: activity.fields,
|
|
2185
|
+
path: [ ...path, "fields" ],
|
|
2186
|
+
ctx: ctx
|
|
2187
|
+
}), env = {
|
|
2188
|
+
layers: [ {
|
|
2189
|
+
scope: "activity",
|
|
2190
|
+
entries: layerOf(activityFields2)
|
|
2191
|
+
}, ...stageEnv.layers ]
|
|
2192
|
+
}, actions = (activity.actions ?? []).map((action, a) => desugarAction({
|
|
2193
|
+
action: action,
|
|
2194
|
+
path: [ ...path, "actions", a ],
|
|
2195
|
+
env: env,
|
|
2196
|
+
activityName: activity.name,
|
|
2197
|
+
ctx: ctx
|
|
2198
|
+
})), target = desugarTarget({
|
|
2199
|
+
target: activity.target,
|
|
2200
|
+
env: env,
|
|
2201
|
+
path: [ ...path, "target" ],
|
|
2202
|
+
ctx: ctx
|
|
2203
|
+
});
|
|
2371
2204
|
return {
|
|
2372
|
-
|
|
2373
|
-
|
|
2205
|
+
...stripUndefined({
|
|
2206
|
+
name: activity.name,
|
|
2207
|
+
semantics: activity.semantics,
|
|
2208
|
+
title: activity.title,
|
|
2209
|
+
description: activity.description,
|
|
2210
|
+
groups: activity.groups,
|
|
2211
|
+
group: normalizeGroup(activity.group),
|
|
2212
|
+
filter: activity.filter,
|
|
2213
|
+
requirements: activity.requirements
|
|
2214
|
+
}),
|
|
2215
|
+
...target ? {
|
|
2216
|
+
target: target
|
|
2217
|
+
} : {},
|
|
2218
|
+
...activityFields2 ? {
|
|
2219
|
+
fields: activityFields2
|
|
2220
|
+
} : {},
|
|
2221
|
+
...actions.length > 0 ? {
|
|
2222
|
+
actions: actions
|
|
2223
|
+
} : {}
|
|
2374
2224
|
};
|
|
2375
2225
|
}
|
|
2376
2226
|
|
|
2377
|
-
const
|
|
2378
|
-
const type = asShape(input).type;
|
|
2379
|
-
return type === "claim" ? ClaimFieldSchema : type === "todoList" ? TodoListFieldSchema : type === "notes" ? NotesFieldSchema : RawAuthoringFieldEntrySchema;
|
|
2380
|
-
})), EffectSchema = v.strictObject({
|
|
2381
|
-
name: NonEmpty,
|
|
2382
|
-
title: v.optional(v.string()),
|
|
2383
|
-
description: v.optional(v.string()),
|
|
2384
|
-
bindings: v.optional(v.record(v.string(), ConditionSchema)),
|
|
2385
|
-
input: v.optional(v.record(v.string(), v.unknown())),
|
|
2386
|
-
outputs: v.optional(v.array(FieldShapeSchema))
|
|
2387
|
-
}), DefinitionRefSchema = v.strictObject({
|
|
2388
|
-
name: NonEmpty,
|
|
2389
|
-
version: v.optional(v.union([ PositiveInt, v.literal("latest") ]))
|
|
2390
|
-
}), SubworkflowsSchema = v.strictObject({
|
|
2391
|
-
forEach: NonEmpty,
|
|
2392
|
-
definition: DefinitionRefSchema,
|
|
2393
|
-
with: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
2394
|
-
context: v.optional(v.record(NonEmpty, ConditionSchema)),
|
|
2395
|
-
onExit: v.optional(picklist([ "detach", "abort" ]))
|
|
2396
|
-
}), ActionParamSchema = v.pipe(v.strictObject({
|
|
2397
|
-
type: picklist([ "string", "number", "boolean", "url", "dateTime", "actor", "doc.ref", "doc.refs", "json" ]),
|
|
2398
|
-
name: NonEmpty,
|
|
2399
|
-
title: v.optional(v.string()),
|
|
2400
|
-
description: v.optional(v.string()),
|
|
2401
|
-
required: v.optional(v.boolean()),
|
|
2402
|
-
options: v.optional(ChoiceOptionsSchema),
|
|
2403
|
-
validation: v.optional(ScalarValidationSchema)
|
|
2404
|
-
}), choiceOptionsCheck(), scalarValidationCheck()), CUSTOM_SEMANTIC_HINT = "`custom.<camelCaseMeaning>`", CustomSemanticSchema = v.custom(input => typeof input == "string" && /^custom\.[a-z][a-zA-Z0-9]*$/.test(input)), SemanticSchema = v.union([ picklist(SIGNAL_SEMANTICS), CustomSemanticSchema ], `expected ${SIGNAL_SEMANTICS.join(", ")}, or ${CUSTOM_SEMANTIC_HINT}`), ActionSemanticSchema = v.union([ picklist(ACTION_SEMANTICS), CustomSemanticSchema ], `expected ${ACTION_SEMANTICS.join(", ")}, or ${CUSTOM_SEMANTIC_HINT}`);
|
|
2405
|
-
|
|
2406
|
-
function semanticNamespace(semantic) {
|
|
2407
|
-
return semantic.startsWith("custom.") ? semantic : semantic.split(".", 1)[0] ?? semantic;
|
|
2408
|
-
}
|
|
2227
|
+
const TARGET_DOC_KINDS = [ "doc.ref", "doc.refs", "subject", "release.ref" ];
|
|
2409
2228
|
|
|
2410
|
-
function
|
|
2411
|
-
|
|
2412
|
-
|
|
2229
|
+
function desugarTarget({target: target, env: env, path: path, ctx: ctx}) {
|
|
2230
|
+
if (target === void 0 || target.type === "url") return target;
|
|
2231
|
+
const ref = typeof target.field == "string" ? {
|
|
2232
|
+
field: target.field
|
|
2233
|
+
} : target.field, field = resolveRef({
|
|
2234
|
+
ref: ref,
|
|
2235
|
+
env: env,
|
|
2236
|
+
path: [ ...path, "field" ],
|
|
2237
|
+
ctx: ctx
|
|
2238
|
+
}) ?? fallbackRef(ref), entry = entryAt(env, field);
|
|
2239
|
+
return entry && !TARGET_DOC_KINDS.includes(entry.type) && ctx.issues.push({
|
|
2240
|
+
path: [ ...path, "field" ],
|
|
2241
|
+
message: `manual activity target references "${field.field}" of kind "${entry.type}" — a deep-link target needs a document-valued entry (${TARGET_DOC_KINDS.join(", ")})`
|
|
2242
|
+
}), {
|
|
2243
|
+
type: "field",
|
|
2244
|
+
field: field
|
|
2245
|
+
};
|
|
2413
2246
|
}
|
|
2414
2247
|
|
|
2415
|
-
function
|
|
2416
|
-
|
|
2248
|
+
function reportEmptyActionRoles({action: action, path: path, ctx: ctx}) {
|
|
2249
|
+
action.roles === void 0 || action.roles.length > 0 || ctx.issues.push({
|
|
2250
|
+
path: [ ...path, "roles" ],
|
|
2251
|
+
message: "roles: [] names no roles — omit it to allow any identity, or list at least one role"
|
|
2252
|
+
});
|
|
2417
2253
|
}
|
|
2418
2254
|
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
effects: v.optional(v.array(EffectSchema)),
|
|
2433
|
-
spawn: v.optional(SubworkflowsSchema)
|
|
2434
|
-
};
|
|
2255
|
+
function desugarActionOps(args) {
|
|
2256
|
+
const {action: action, path: path, env: env, activityName: activityName, ctx: ctx} = args, ops = desugarOps({
|
|
2257
|
+
ops: action.ops,
|
|
2258
|
+
path: [ ...path, "ops" ],
|
|
2259
|
+
env: env,
|
|
2260
|
+
firingActivity: activityName,
|
|
2261
|
+
ctx: ctx
|
|
2262
|
+
}) ?? [];
|
|
2263
|
+
return action.status !== void 0 && ops.push({
|
|
2264
|
+
type: "status.set",
|
|
2265
|
+
activity: activityName,
|
|
2266
|
+
status: action.status
|
|
2267
|
+
}), ops;
|
|
2435
2268
|
}
|
|
2436
2269
|
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
field: v.union([ NonEmpty, AuthoringFieldRefSchema ]),
|
|
2451
|
-
roles: v.optional(v.array(NonEmpty)),
|
|
2452
|
-
filter: v.optional(ConditionSchema),
|
|
2453
|
-
params: v.optional(v.array(ActionParamSchema)),
|
|
2454
|
-
effects: v.optional(v.array(EffectSchema))
|
|
2455
|
-
})), AuthoringActionSchema = pinned()(v.lazy(input => typeof input == "object" && input !== null && "type" in input ? ClaimActionSchema : RawAuthoringActionSchema)), requirementBase = {
|
|
2456
|
-
name: NonEmpty,
|
|
2457
|
-
title: v.optional(v.string()),
|
|
2458
|
-
description: v.optional(v.string())
|
|
2459
|
-
}, GroqRequirementSchemaRaw = v.strictObject({
|
|
2460
|
-
...requirementBase,
|
|
2461
|
-
type: v.literal("groq"),
|
|
2462
|
-
query: ConditionSchema
|
|
2463
|
-
}), SingleSubjectRequirementSchemaRaw = v.strictObject({
|
|
2464
|
-
...requirementBase,
|
|
2465
|
-
type: v.literal("singleSubject")
|
|
2466
|
-
}), GroqRequirementSchema = pinned()(GroqRequirementSchemaRaw), StartRequirementSchema = pinned()(v.variant("type", [ GroqRequirementSchemaRaw, SingleSubjectRequirementSchemaRaw ]));
|
|
2467
|
-
|
|
2468
|
-
function activityFields({field: field, action: action, target: target, group: group}) {
|
|
2270
|
+
function desugarAction({action: action, path: path, env: env, activityName: activityName, ctx: ctx}) {
|
|
2271
|
+
reportEmptyActionRoles({
|
|
2272
|
+
action: action,
|
|
2273
|
+
path: path,
|
|
2274
|
+
ctx: ctx
|
|
2275
|
+
});
|
|
2276
|
+
const cascadeFired = isCascadeFired(action), filter = cascadeFired ? action.filter : andConditions([ rolesCondition(action.roles, ctx.roleAliases), action.filter ]), ops = desugarActionOps({
|
|
2277
|
+
action: action,
|
|
2278
|
+
path: path,
|
|
2279
|
+
env: env,
|
|
2280
|
+
activityName: activityName,
|
|
2281
|
+
ctx: ctx
|
|
2282
|
+
});
|
|
2469
2283
|
return {
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2284
|
+
...stripUndefined({
|
|
2285
|
+
name: action.name,
|
|
2286
|
+
semantics: action.semantics,
|
|
2287
|
+
title: action.title,
|
|
2288
|
+
description: action.description,
|
|
2289
|
+
group: normalizeGroup(action.group),
|
|
2290
|
+
when: action.when,
|
|
2291
|
+
params: action.params,
|
|
2292
|
+
effects: action.effects?.map(effect => desugarEffect({
|
|
2293
|
+
effect: effect,
|
|
2294
|
+
ctx: ctx
|
|
2295
|
+
})),
|
|
2296
|
+
spawn: action.spawn
|
|
2297
|
+
}),
|
|
2298
|
+
...cascadeFired && action.roles !== void 0 && action.roles.length > 0 ? {
|
|
2299
|
+
roles: action.roles
|
|
2300
|
+
} : {},
|
|
2301
|
+
...filter ? {
|
|
2302
|
+
filter: filter
|
|
2303
|
+
} : {},
|
|
2304
|
+
...ops.length > 0 ? {
|
|
2305
|
+
ops: ops
|
|
2306
|
+
} : {}
|
|
2481
2307
|
};
|
|
2482
2308
|
}
|
|
2483
2309
|
|
|
2484
|
-
const
|
|
2485
|
-
field: FieldEntrySchema,
|
|
2486
|
-
action: StoredActionSchema,
|
|
2487
|
-
target: StoredManualTargetSchema,
|
|
2488
|
-
group: StoredGroupMembershipSchema
|
|
2489
|
-
}))), AuthoringActivitySchema = pinned()(v.strictObject(activityFields({
|
|
2490
|
-
field: AuthoringFieldEntrySchema,
|
|
2491
|
-
action: AuthoringActionSchema,
|
|
2492
|
-
target: AuthoringManualTargetSchema,
|
|
2493
|
-
group: AuthoringGroupMembershipSchema
|
|
2494
|
-
})));
|
|
2310
|
+
const DEFAULT_TRANSITION_WHEN = "$allActivitiesDone";
|
|
2495
2311
|
|
|
2496
|
-
function
|
|
2312
|
+
function desugarTransition({transition: transition}) {
|
|
2497
2313
|
return {
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2314
|
+
...stripUndefined({
|
|
2315
|
+
name: transition.name,
|
|
2316
|
+
title: transition.title,
|
|
2317
|
+
description: transition.description,
|
|
2318
|
+
to: transition.to
|
|
2319
|
+
}),
|
|
2320
|
+
when: transition.when ?? DEFAULT_TRANSITION_WHEN
|
|
2503
2321
|
};
|
|
2504
2322
|
}
|
|
2505
2323
|
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
}), v.strictObject({
|
|
2509
|
-
type: v.literal("now")
|
|
2510
|
-
}), v.strictObject({
|
|
2511
|
-
type: v.literal("fieldRead"),
|
|
2512
|
-
field: FieldEntryName,
|
|
2513
|
-
path: v.optional(GuardReadPath)
|
|
2514
|
-
}), v.strictObject({
|
|
2515
|
-
type: v.literal("effectsRead"),
|
|
2516
|
-
effect: v.pipe(NonEmpty, v.check(name => !name.includes("'"), "an effect name cannot contain `'`")),
|
|
2517
|
-
path: v.optional(GuardReadPath)
|
|
2518
|
-
}) ]);
|
|
2519
|
-
|
|
2520
|
-
function guardMatchFields(read) {
|
|
2324
|
+
function desugarGuard(guard) {
|
|
2325
|
+
const {match: match, metadata: metadata, ...rest} = guard, {idRefs: idRefs, ...matchRest} = match;
|
|
2521
2326
|
return {
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2327
|
+
...rest,
|
|
2328
|
+
match: {
|
|
2329
|
+
...matchRest,
|
|
2330
|
+
...idRefs !== void 0 ? {
|
|
2331
|
+
idRefs: idRefs.map(printGuardRead)
|
|
2332
|
+
} : {}
|
|
2333
|
+
},
|
|
2334
|
+
...metadata !== void 0 ? {
|
|
2335
|
+
metadata: Object.fromEntries(Object.entries(metadata).map(([key, read]) => [ key, printGuardRead(read) ]))
|
|
2336
|
+
} : {}
|
|
2526
2337
|
};
|
|
2527
2338
|
}
|
|
2528
2339
|
|
|
2529
|
-
function
|
|
2340
|
+
function desugarOps({ops: ops, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
2341
|
+
if (ops) return ops.map((op, i) => desugarOp({
|
|
2342
|
+
op: op,
|
|
2343
|
+
path: [ ...path, i ],
|
|
2344
|
+
env: env,
|
|
2345
|
+
firingActivity: firingActivity,
|
|
2346
|
+
ctx: ctx
|
|
2347
|
+
}));
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
function desugarOp({op: op, path: path, env: env, firingActivity: firingActivity, ctx: ctx}) {
|
|
2351
|
+
if (op.type === "status.set") return {
|
|
2352
|
+
type: "status.set",
|
|
2353
|
+
activity: op.activity ?? firingActivity,
|
|
2354
|
+
status: op.status
|
|
2355
|
+
};
|
|
2356
|
+
if (op.type === "audit") return desugarAuditOp({
|
|
2357
|
+
op: op,
|
|
2358
|
+
path: path,
|
|
2359
|
+
env: env,
|
|
2360
|
+
ctx: ctx
|
|
2361
|
+
});
|
|
2362
|
+
const target = resolveRef({
|
|
2363
|
+
ref: op.target,
|
|
2364
|
+
env: env,
|
|
2365
|
+
path: [ ...path, "target" ],
|
|
2366
|
+
ctx: ctx
|
|
2367
|
+
}) ?? fallbackRef(op.target);
|
|
2530
2368
|
return {
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
description: v.optional(v.string()),
|
|
2534
|
-
match: v.strictObject(guardMatchFields(read)),
|
|
2535
|
-
predicate: v.optional(v.string()),
|
|
2536
|
-
metadata: v.optional(v.record(NonEmpty, read))
|
|
2369
|
+
...op,
|
|
2370
|
+
target: target
|
|
2537
2371
|
};
|
|
2538
2372
|
}
|
|
2539
2373
|
|
|
2540
|
-
const
|
|
2374
|
+
const AUDIT_STAMPS = {
|
|
2375
|
+
actor: {
|
|
2376
|
+
type: "actor"
|
|
2377
|
+
},
|
|
2378
|
+
at: {
|
|
2379
|
+
type: "now"
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2541
2382
|
|
|
2542
|
-
function
|
|
2383
|
+
function desugarAuditOp({op: op, path: path, env: env, ctx: ctx}) {
|
|
2384
|
+
const target = resolveRef({
|
|
2385
|
+
ref: op.target,
|
|
2386
|
+
env: env,
|
|
2387
|
+
path: [ ...path, "target" ],
|
|
2388
|
+
ctx: ctx
|
|
2389
|
+
}) ?? fallbackRef(op.target);
|
|
2390
|
+
if (op.value.type !== "object") return ctx.issues.push({
|
|
2391
|
+
path: [ ...path, "value" ],
|
|
2392
|
+
message: `audit value must be an object source carrying the domain fields (got "${op.value.type}")`
|
|
2393
|
+
}), {
|
|
2394
|
+
type: "field.append",
|
|
2395
|
+
target: target,
|
|
2396
|
+
value: op.value
|
|
2397
|
+
};
|
|
2398
|
+
const actorField = op.stampFields?.actor ?? "actor", atField = op.stampFields?.at ?? "at", fields = {
|
|
2399
|
+
...op.value.fields
|
|
2400
|
+
};
|
|
2401
|
+
for (const [stamp, source] of [ [ actorField, AUDIT_STAMPS.actor ], [ atField, AUDIT_STAMPS.at ] ]) {
|
|
2402
|
+
if (stamp in fields) {
|
|
2403
|
+
ctx.issues.push({
|
|
2404
|
+
path: [ ...path, "value", "fields", stamp ],
|
|
2405
|
+
message: `audit stamp field "${stamp}" collides with an authored domain field — rename yours or remap the stamp via stampFields`
|
|
2406
|
+
});
|
|
2407
|
+
continue;
|
|
2408
|
+
}
|
|
2409
|
+
fields[stamp] = source;
|
|
2410
|
+
}
|
|
2543
2411
|
return {
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
transitions: v.optional(v.array(transition)),
|
|
2551
|
-
guards: v.optional(v.array(guard)),
|
|
2552
|
-
fields: v.optional(v.array(field)),
|
|
2553
|
-
editable: v.optional(v.record(FieldEntryName, editable))
|
|
2412
|
+
type: "field.append",
|
|
2413
|
+
target: target,
|
|
2414
|
+
value: {
|
|
2415
|
+
type: "object",
|
|
2416
|
+
fields: fields
|
|
2417
|
+
}
|
|
2554
2418
|
};
|
|
2555
2419
|
}
|
|
2556
2420
|
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
editable: StoredEditableSchema
|
|
2563
|
-
}))), AuthoringStageSchema = pinned()(v.strictObject(stageFields({
|
|
2564
|
-
field: AuthoringFieldEntrySchema,
|
|
2565
|
-
activity: AuthoringActivitySchema,
|
|
2566
|
-
transition: AuthoringTransitionSchema,
|
|
2567
|
-
guard: AuthoringGuardSchema,
|
|
2568
|
-
editable: AuthoringEditableSchema
|
|
2569
|
-
}))), RoleAliasesSchema = v.record(NonEmpty, v.pipe(v.array(NonEmpty), v.minLength(1, "a role alias must list at least one fulfilling role"))), WORKFLOW_LIFECYCLES = [ "standalone", "child" ], START_KINDS = [ "interactive", "autonomous" ];
|
|
2570
|
-
|
|
2571
|
-
function startFields(kind) {
|
|
2572
|
-
return {
|
|
2573
|
-
kind: kind,
|
|
2574
|
-
filter: v.optional(ConditionSchema),
|
|
2575
|
-
requirements: v.optional(v.array(StartRequirementSchema))
|
|
2421
|
+
function resolveRef({ref: ref, env: env, path: path, ctx: ctx}) {
|
|
2422
|
+
const layers = ref.scope === void 0 ? env.layers : env.layers.filter(l => l.scope === ref.scope);
|
|
2423
|
+
for (const layer of layers) if (layer.entries.has(ref.field)) return {
|
|
2424
|
+
scope: layer.scope,
|
|
2425
|
+
field: ref.field
|
|
2576
2426
|
};
|
|
2427
|
+
const reachable = env.layers.flatMap(l => [ ...l.entries.keys() ].map(n => `${l.scope}:${n}`));
|
|
2428
|
+
ctx.issues.push({
|
|
2429
|
+
path: path,
|
|
2430
|
+
message: `field reference "${ref.field}"${ref.scope ? ` (scope "${ref.scope}")` : ""} does not resolve to a declared entry. Reachable: ${reachable.join(", ") || "(none)"}`
|
|
2431
|
+
});
|
|
2577
2432
|
}
|
|
2578
2433
|
|
|
2579
|
-
|
|
2434
|
+
function entryAt(env, ref) {
|
|
2435
|
+
return env.layers.find(l => l.scope === ref.scope)?.entries.get(ref.field);
|
|
2436
|
+
}
|
|
2580
2437
|
|
|
2581
|
-
function
|
|
2438
|
+
function fallbackRef(ref) {
|
|
2582
2439
|
return {
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
title: NonEmpty,
|
|
2586
|
-
description: v.optional(v.string()),
|
|
2587
|
-
groups: v.optional(v.array(GroupSchema)),
|
|
2588
|
-
lifecycle: v.optional(picklist(WORKFLOW_LIFECYCLES)),
|
|
2589
|
-
start: v.optional(start),
|
|
2590
|
-
initialStage: NonEmpty,
|
|
2591
|
-
fields: v.optional(v.array(field)),
|
|
2592
|
-
stages: v.pipe(v.array(stage), v.minLength(1, "must declare at least one stage")),
|
|
2593
|
-
predicates: v.optional(v.record(groqIdentifier("`$<name>`"), ConditionSchema)),
|
|
2594
|
-
roleAliases: v.optional(RoleAliasesSchema)
|
|
2440
|
+
scope: ref.scope ?? "workflow",
|
|
2441
|
+
field: ref.field
|
|
2595
2442
|
};
|
|
2596
2443
|
}
|
|
2597
2444
|
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2445
|
+
function rolesCondition(roles, aliases) {
|
|
2446
|
+
if (!(!roles || roles.length === 0)) return groq`count($actor.roles[@ in ${expandRequiredRoles(roles, aliases)}]) > 0`;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
function stripUndefined(obj) {
|
|
2450
|
+
const out = {};
|
|
2451
|
+
for (const [k, value] of Object.entries(obj)) value !== void 0 && (out[k] = value);
|
|
2452
|
+
return out;
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
function isUnevaluable(result) {
|
|
2456
|
+
return result == null;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
async function evaluateConditionOutcome(args) {
|
|
2460
|
+
const {condition: condition, snapshot: snapshot, params: params} = args;
|
|
2461
|
+
return evaluateConditionOutcome$1({
|
|
2462
|
+
condition: condition,
|
|
2463
|
+
params: params,
|
|
2464
|
+
dataset: snapshot.docs
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
async function evaluateCondition(args) {
|
|
2469
|
+
return await evaluateConditionOutcome(args) === "satisfied";
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
async function evaluatePredicates(args) {
|
|
2473
|
+
const out = {};
|
|
2474
|
+
for (const [name, groq2] of Object.entries(args.predicates ?? {})) {
|
|
2475
|
+
const result = await runGroq({
|
|
2476
|
+
groq: groq2,
|
|
2477
|
+
params: args.params,
|
|
2478
|
+
snapshot: args.snapshot
|
|
2479
|
+
}), outcome = conditionOutcome(result);
|
|
2480
|
+
out[name] = outcome === "unevaluable" ? null : outcome === "satisfied";
|
|
2481
|
+
}
|
|
2482
|
+
return out;
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
async function runGroq({groq: groq2, params: params, snapshot: snapshot}) {
|
|
2486
|
+
return runGroq$1({
|
|
2487
|
+
groq: groq2,
|
|
2488
|
+
params: params,
|
|
2489
|
+
dataset: snapshot.docs
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
function conditionSyntaxIssues(groq2, boundVars) {
|
|
2494
|
+
const issues = [];
|
|
2495
|
+
let tree;
|
|
2496
|
+
try {
|
|
2497
|
+
tree = parse(groq2);
|
|
2498
|
+
} catch (err) {
|
|
2499
|
+
issues.push(errorMessage(err));
|
|
2500
|
+
}
|
|
2501
|
+
if (/\*\s*\[\s*_type\b/.test(groq2) && issues.push("condition scans by `_type` — that's a discovery query, not a predicate. Conditions evaluate against the in-memory snapshot (instance + ancestors + field-declared docs). To bring extra docs in scope, declare a `doc.ref` (or `doc.refs`) field entry on the workflow or this stage. For lake scans like \"all articles in this release\", use a spawn action's `forEach` instead."),
|
|
2502
|
+
boundVars !== void 0 && tree !== void 0) for (const name of conditionParameterNames(groq2)) boundVars.includes(name) || issues.push(`reads $${name}, which this scope does not bind — an unbound variable evaluates to GROQ null, so the condition silently never matches. Bound here: ` + boundVars.map(n => `$${n}`).join(", "));
|
|
2503
|
+
return issues;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
function conditionParameterNames(groq2) {
|
|
2507
|
+
const read = /* @__PURE__ */ new Set;
|
|
2508
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
2509
|
+
node.type === "Parameter" && typeof node.name == "string" && read.add(node.name);
|
|
2510
|
+
}), read;
|
|
2511
|
+
}
|
|
2603
2512
|
|
|
2604
|
-
function
|
|
2605
|
-
return
|
|
2606
|
-
schema: WorkflowDefinitionSchema,
|
|
2607
|
-
input: input,
|
|
2608
|
-
label: label
|
|
2609
|
-
});
|
|
2513
|
+
function conditionFieldReadNames(groq2) {
|
|
2514
|
+
return new Set(conditionFieldReads(groq2).map(read => read.name));
|
|
2610
2515
|
}
|
|
2611
2516
|
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
}))), WORKFLOW_DEFINITION_TYPE = "sanity.workflow.definition";
|
|
2517
|
+
function conditionFieldReads(groq2) {
|
|
2518
|
+
const reads = /* @__PURE__ */ new Map;
|
|
2519
|
+
return collectFieldReads(tryParseGroq(groq2), reads), [ ...reads.values() ];
|
|
2520
|
+
}
|
|
2617
2521
|
|
|
2618
|
-
function
|
|
2619
|
-
|
|
2522
|
+
function collectFieldReads(node, reads) {
|
|
2523
|
+
if (Array.isArray(node)) {
|
|
2524
|
+
for (const item of node) collectFieldReads(item, reads);
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
if (typeof node != "object" || node === null) return;
|
|
2528
|
+
const chain = fieldsAttributeChain(node);
|
|
2529
|
+
if (chain !== void 0) {
|
|
2530
|
+
const [name, ...tail] = chain, path = tail.length > 0 ? tail.join(".") : void 0;
|
|
2531
|
+
reads.set(`${name}.${path ?? ""}`, {
|
|
2532
|
+
name: name,
|
|
2533
|
+
path: path
|
|
2534
|
+
});
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2537
|
+
for (const value of Object.values(node)) collectFieldReads(value, reads);
|
|
2620
2538
|
}
|
|
2621
2539
|
|
|
2622
|
-
function
|
|
2623
|
-
|
|
2540
|
+
function fieldsAttributeChain(node) {
|
|
2541
|
+
const names = [];
|
|
2542
|
+
let current = node;
|
|
2543
|
+
for (;typeof current == "object" && current !== null; ) {
|
|
2544
|
+
const typed = current;
|
|
2545
|
+
if (typed.type !== "AccessAttribute" || typeof typed.name != "string") return;
|
|
2546
|
+
names.unshift(typed.name);
|
|
2547
|
+
const base = typed.base;
|
|
2548
|
+
if (base?.type === "Parameter" && base.name === "fields") return names;
|
|
2549
|
+
current = base;
|
|
2550
|
+
}
|
|
2624
2551
|
}
|
|
2625
2552
|
|
|
2626
|
-
function
|
|
2627
|
-
|
|
2553
|
+
function conditionEffectReads(groq2) {
|
|
2554
|
+
const reads = [];
|
|
2555
|
+
return walkAstNodes(tryParseGroq(groq2), node => {
|
|
2556
|
+
if (node.type !== "AccessAttribute" || typeof node.name != "string") return;
|
|
2557
|
+
const base = node.base;
|
|
2558
|
+
base?.type !== "AccessAttribute" || typeof base.name != "string" || base.base?.type === "Parameter" && base.base.name === "effects" && reads.push({
|
|
2559
|
+
effect: base.name,
|
|
2560
|
+
key: node.name
|
|
2561
|
+
});
|
|
2562
|
+
}), reads;
|
|
2628
2563
|
}
|
|
2629
2564
|
|
|
2630
|
-
function
|
|
2631
|
-
return
|
|
2565
|
+
function readsRootDocument(groq2) {
|
|
2566
|
+
return nodeReadsRoot(tryParseGroq(groq2), 0);
|
|
2632
2567
|
}
|
|
2633
2568
|
|
|
2634
|
-
|
|
2635
|
-
|
|
2569
|
+
const SCOPED_CHILD = {
|
|
2570
|
+
Filter: "expr",
|
|
2571
|
+
Projection: "expr",
|
|
2572
|
+
Map: "expr",
|
|
2573
|
+
FlatMap: "expr",
|
|
2574
|
+
PipeFuncCall: "args"
|
|
2575
|
+
};
|
|
2576
|
+
|
|
2577
|
+
function nodeReadsRoot(node, depth) {
|
|
2578
|
+
if (Array.isArray(node)) return node.some(item => nodeReadsRoot(item, depth));
|
|
2579
|
+
if (typeof node != "object" || node === null) return !1;
|
|
2580
|
+
const typed = node;
|
|
2581
|
+
if (depth === 0 && typed.type === "This" || depth === 0 && typed.type === "AccessAttribute" && typed.base === void 0) return !0;
|
|
2582
|
+
if (typed.type === "Parent") {
|
|
2583
|
+
const climb = typeof typed.n == "number" ? typed.n : 1;
|
|
2584
|
+
return depth - climb <= 0;
|
|
2585
|
+
}
|
|
2586
|
+
const scopedChild = typeof typed.type == "string" ? SCOPED_CHILD[typed.type] : void 0;
|
|
2587
|
+
return scopedChild !== void 0 ? Object.entries(node).some(([key, value]) => nodeReadsRoot(value, key === scopedChild ? depth + 1 : depth)) : Object.values(node).some(value => nodeReadsRoot(value, depth));
|
|
2636
2588
|
}
|
|
2637
2589
|
|
|
2638
|
-
function
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2590
|
+
function tryParseGroq(groq2) {
|
|
2591
|
+
try {
|
|
2592
|
+
return parse(groq2);
|
|
2593
|
+
} catch {
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2642
2596
|
}
|
|
2643
2597
|
|
|
2644
|
-
function
|
|
2645
|
-
if (
|
|
2646
|
-
const
|
|
2647
|
-
|
|
2598
|
+
function walkAstNodes(node, visit) {
|
|
2599
|
+
if (Array.isArray(node)) {
|
|
2600
|
+
for (const item of node) walkAstNodes(item, visit);
|
|
2601
|
+
return;
|
|
2648
2602
|
}
|
|
2649
|
-
|
|
2603
|
+
if (!(typeof node != "object" || node === null)) {
|
|
2604
|
+
visit(node);
|
|
2605
|
+
for (const value of Object.values(node)) walkAstNodes(value, visit);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
const CONDITION_VARS = [ {
|
|
2610
|
+
name: "self",
|
|
2611
|
+
binding: "always",
|
|
2612
|
+
label: "this workflow instance",
|
|
2613
|
+
description: "GDR URI of the instance document itself — `*[_id == $self][0]` reads the instance in the snapshot."
|
|
2614
|
+
}, {
|
|
2615
|
+
name: "fields",
|
|
2616
|
+
binding: "always",
|
|
2617
|
+
label: "the workflow's fields",
|
|
2618
|
+
description: "Declared field entries rendered by name (`$fields.<name>` is the value, no wrapper). Stage/activity scopes overlay lexically. What a read puts in your hand follows the declared kind: a singular `doc.ref` (or `subject`) DEREFERENCES into the hydrated document (lake `_id`/`_type` plus content fields — which may themselves be named `id`/`type`), while `doc.refs` elements and `release.ref` stay REFERENCES — `{id, type[, releaseName]}` with `id` a GDR URI — because identity reads (membership, counting, joins) must stay total without hydrating every target, and targets may live in resources the evaluation cannot fetch from. Conditions evaluate against the in-memory snapshot only, so a dereferencing plural kind would silently hole wherever hydration lagged."
|
|
2619
|
+
}, {
|
|
2620
|
+
name: "parent",
|
|
2621
|
+
binding: "always",
|
|
2622
|
+
label: "the parent workflow",
|
|
2623
|
+
description: "The parent instance's GDR URI, `null` on a root instance."
|
|
2624
|
+
}, {
|
|
2625
|
+
name: "ancestors",
|
|
2626
|
+
binding: "always",
|
|
2627
|
+
label: "the workflow's ancestors",
|
|
2628
|
+
description: "GDR URIs of the ancestor chain, root first."
|
|
2629
|
+
}, {
|
|
2630
|
+
name: "stage",
|
|
2631
|
+
binding: "always",
|
|
2632
|
+
label: "the current stage",
|
|
2633
|
+
description: "The current stage's name."
|
|
2634
|
+
}, {
|
|
2635
|
+
name: "now",
|
|
2636
|
+
binding: "always",
|
|
2637
|
+
label: "the current time",
|
|
2638
|
+
description: "The ISO clock reading shared by every condition in one evaluation pass, so they agree on the time."
|
|
2639
|
+
}, {
|
|
2640
|
+
name: "context",
|
|
2641
|
+
binding: "always",
|
|
2642
|
+
label: "start-time context",
|
|
2643
|
+
description: "The instance's `context` bag: values seeded at `startInstance` plus a parent's `spawn.context` handoff, by entry name. Written only at start — never mutated afterwards."
|
|
2644
|
+
}, {
|
|
2645
|
+
name: "effects",
|
|
2646
|
+
binding: "always",
|
|
2647
|
+
label: "automation outputs",
|
|
2648
|
+
description: "Completed effects' outputs, namespaced by effect name (`$effects['<effect>'].<output>`) — each effect's latest completed run wins. Handler fuel — transition triggers should read instance/lake state (or $effectStatus) instead."
|
|
2649
|
+
}, {
|
|
2650
|
+
name: "effectStatus",
|
|
2651
|
+
binding: "always",
|
|
2652
|
+
label: "automation status",
|
|
2653
|
+
description: "Effect name → `'done'` | `'failed'` of the latest completed run queued during the current stage entry; absent until the effect drains for this entry. Re-entry-safe: runs queued under a prior entry never count, so `$effectStatus['<effect>'] == 'done'` (or `defined($effectStatus['<effect>'])` for settled-either-way) waits for the fresh run."
|
|
2654
|
+
}, {
|
|
2655
|
+
name: "activities",
|
|
2656
|
+
binding: "always",
|
|
2657
|
+
label: "this stage's activities",
|
|
2658
|
+
description: "The current stage's activity rows, statuses included."
|
|
2659
|
+
}, {
|
|
2660
|
+
name: "allActivitiesDone",
|
|
2661
|
+
binding: "always",
|
|
2662
|
+
label: "all activities in this stage are finished",
|
|
2663
|
+
description: "Every current-stage activity is `done` or `skipped` — the default transition gate."
|
|
2664
|
+
}, {
|
|
2665
|
+
name: "anyActivityFailed",
|
|
2666
|
+
binding: "always",
|
|
2667
|
+
label: "an activity in this stage has failed",
|
|
2668
|
+
description: "Some current-stage activity is `failed`."
|
|
2669
|
+
}, {
|
|
2670
|
+
name: "actor",
|
|
2671
|
+
binding: "caller",
|
|
2672
|
+
label: "you",
|
|
2673
|
+
description: "The acting identity (id + roles); `undefined` when no caller rides the evaluation. Never holds a value in the cascade gates (deploy-rejected there)."
|
|
2674
|
+
}, {
|
|
2675
|
+
name: "assigned",
|
|
2676
|
+
binding: "caller",
|
|
2677
|
+
label: "you are assigned to this activity",
|
|
2678
|
+
description: "Whether the caller matches the activity's assignees-kind field entry (by user id, or by a role under the definition's `roleAliases`); `false` outside an activity context. Constant `false` in the cascade gates (deploy-rejected there)."
|
|
2679
|
+
}, {
|
|
2680
|
+
name: "can",
|
|
2681
|
+
binding: "caller",
|
|
2682
|
+
label: "your permissions",
|
|
2683
|
+
description: "Advisory per-permission booleans computed from the caller's grants; `undefined` without grants. Bound wherever grants ride the evaluation: the projection's rendered scope (fireAction-action filters, activity requirements, editability predicates) and the fireAction/editField commit gates. Deploy rejects it at every site that evaluates without grants: transition `when`s, activity filters, cascade-fired actions' `when`/`filter`, effect bindings, where-op `where`s, and the spawn `forEach`/`with`/`context` sites."
|
|
2684
|
+
}, {
|
|
2685
|
+
name: "attributes",
|
|
2686
|
+
binding: "caller",
|
|
2687
|
+
label: "your attributes",
|
|
2688
|
+
description: "Advisory org-level User Attributes for the caller (Enterprise — same values as lake `user::attributes()`), keyed by attribute name with each active scalar or array value. `undefined` on expected HTTP absence; unexpected fetch failures throw; empty page binds `{}`. Bound wherever grants ride the evaluation (same sites as `$can`). Soft-gate paths fetch at most 100 attributes (no further pages) and warn when the envelope reports `hasMore: true` (partial bag still binds). Not a security boundary — the Content Lake remains the only enforcement point."
|
|
2689
|
+
}, {
|
|
2690
|
+
name: "row",
|
|
2691
|
+
binding: "spawn",
|
|
2692
|
+
label: "the spawned row",
|
|
2693
|
+
description: "One `spawn.forEach` result row, bound while its `with` map evaluates — and the row under test while a where-op `where` evaluates (per row). Deploy rejects a read at every other site (including `spawn.forEach` itself and `spawn.context`), where it is GROQ null."
|
|
2694
|
+
}, {
|
|
2695
|
+
name: "params",
|
|
2696
|
+
binding: "caller",
|
|
2697
|
+
label: "the action's arguments",
|
|
2698
|
+
description: "The firing action's args — they hold values only while a fireAction-fired action's effect bindings and where-op `where`s evaluate (spawn sites don't bind it at all, and a cascade-fired action has no caller to supply args). Deploy rejects a read in the cascade gates, in a cascade-fired action's payload, and in the caller-bound projection (action filters, requirements, editable predicates): args exist only once the caller fires the action, after those sites have evaluated."
|
|
2699
|
+
}, {
|
|
2700
|
+
name: "subworkflows",
|
|
2701
|
+
binding: "always",
|
|
2702
|
+
label: "the spawned subworkflows",
|
|
2703
|
+
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`."
|
|
2704
|
+
} ], 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 = [ {
|
|
2705
|
+
name: "tag",
|
|
2706
|
+
label: "this engine tag",
|
|
2707
|
+
description: "The engine's tag partition — scope `*[...]` instance scans with `tag == $tag`."
|
|
2708
|
+
}, {
|
|
2709
|
+
name: "definition",
|
|
2710
|
+
label: "this workflow definition",
|
|
2711
|
+
description: "The `name` of the definition under evaluation (its own start block binds it)."
|
|
2712
|
+
}, {
|
|
2713
|
+
name: "now",
|
|
2714
|
+
label: "the current time",
|
|
2715
|
+
description: "The ISO clock reading of the evaluating engine."
|
|
2716
|
+
} ], START_REQUIREMENT_VARS = [ ...START_FILTER_VARS, {
|
|
2717
|
+
name: "fields",
|
|
2718
|
+
label: "the start's input fields",
|
|
2719
|
+
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."
|
|
2720
|
+
} ], GUARD_PREDICATE_VARS = [ {
|
|
2721
|
+
name: "document",
|
|
2722
|
+
description: "The attempted mutation images, exposed as `document.before` and `document.after`."
|
|
2723
|
+
}, {
|
|
2724
|
+
name: "guard",
|
|
2725
|
+
description: "The guard document itself (its `metadata` carries deploy-time resolved values)."
|
|
2726
|
+
}, {
|
|
2727
|
+
name: "mutation",
|
|
2728
|
+
description: "The attempted mutation — `mutation.action` is the write kind being gated."
|
|
2729
|
+
} ];
|
|
2730
|
+
|
|
2731
|
+
function effectBackoffMs(retry, endedAttempts) {
|
|
2732
|
+
const {backoff: backoff} = retry;
|
|
2733
|
+
return backoff === void 0 || endedAttempts < 1 ? 0 : backoff.kind === "fixed" ? backoff.delayMs : backoff.delayMs * 2 ** (endedAttempts - 1);
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
function totalEffectBackoffMs(retry) {
|
|
2737
|
+
const waits = retry.attempts - 1;
|
|
2738
|
+
return retry.backoff === void 0 || waits < 1 ? 0 : retry.backoff.kind === "fixed" ? retry.backoff.delayMs * waits : retry.backoff.delayMs * (2 ** waits - 1);
|
|
2650
2739
|
}
|
|
2651
2740
|
|
|
2652
2741
|
function knownList(ids) {
|
|
@@ -3237,6 +3326,40 @@ function pushOutputIssues(args) {
|
|
|
3237
3326
|
}
|
|
3238
3327
|
}
|
|
3239
3328
|
|
|
3329
|
+
function checkEffectRetry(def, issues) {
|
|
3330
|
+
for (const {action: action, path: path} of actionSites(def)) for (const [e, effect] of (action.effects ?? []).entries()) effect.retry !== void 0 && pushRetryIssue({
|
|
3331
|
+
retry: effect.retry,
|
|
3332
|
+
name: effect.name,
|
|
3333
|
+
path: [ ...path, "effects", e, "retry" ],
|
|
3334
|
+
issues: issues
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
|
|
3338
|
+
function pushRetryIssue(args) {
|
|
3339
|
+
const {retry: retry, name: name, path: path, issues: issues} = args;
|
|
3340
|
+
if (retry.expiryMs !== void 0 && retry.expiryMs > MAX_RETRY_SPAN_MS) {
|
|
3341
|
+
issues.push({
|
|
3342
|
+
path: [ ...path, "expiryMs" ],
|
|
3343
|
+
message: `effect "${name}" declares a ${retry.expiryMs}ms \`expiryMs\`, past the ${MAX_RETRY_SPAN_MS}ms ceiling — a policy runs inside one \`drainEffects\` invocation, and no host offers a single execution longer than a year. Lower \`expiryMs\``
|
|
3344
|
+
});
|
|
3345
|
+
return;
|
|
3346
|
+
}
|
|
3347
|
+
const wait = totalEffectBackoffMs(retry);
|
|
3348
|
+
if (wait > MAX_RETRY_SPAN_MS) {
|
|
3349
|
+
issues.push({
|
|
3350
|
+
path: [ ...path, "attempts" ],
|
|
3351
|
+
message: `effect "${name}" accumulates ${spanned(wait)} of backoff before attempt ${retry.attempts}, past the ${MAX_RETRY_SPAN_MS}ms ceiling — a policy runs inside one \`drainEffects\` invocation, and no host offers a single execution longer than a year. Lower \`attempts\` or \`backoff.delayMs\``
|
|
3352
|
+
});
|
|
3353
|
+
return;
|
|
3354
|
+
}
|
|
3355
|
+
retry.expiryMs === void 0 || wait < retry.expiryMs || issues.push({
|
|
3356
|
+
path: [ ...path, "expiryMs" ],
|
|
3357
|
+
message: `effect "${name}" waits ${wait}ms of backoff before attempt ${retry.attempts}, which its ${retry.expiryMs}ms \`expiryMs\` closes first — the effect fails before the declared attempts run. Raise \`expiryMs\`, or lower \`attempts\`/\`backoff.delayMs\``
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
const spanned = ms => Number.isFinite(ms) ? `${ms}ms` : "more milliseconds than a number can count", MAX_RETRY_SPAN_MS = 316224e5;
|
|
3362
|
+
|
|
3240
3363
|
function checkSubjectEntries(def, issues) {
|
|
3241
3364
|
for (const {entries: entries, scope: scope, path: path, label: label} of fieldScopes(def)) {
|
|
3242
3365
|
const subjects = (entries ?? []).flatMap((entry, n) => isSubjectEntry(entry) ? [ {
|
|
@@ -4012,9 +4135,9 @@ function checkWorkflowInvariants(def) {
|
|
|
4012
4135
|
checkUnboundConditionVars(def, issues), checkConditionFieldReads(def, issues), checkFieldReadSeeds(def, issues),
|
|
4013
4136
|
checkFieldReadOpValues(def, issues), checkFieldTargetOps(def, issues), checkGuardFieldReads(def, issues),
|
|
4014
4137
|
checkAssigneesEntries(def, issues), checkDueDateEntries(def, issues), checkSubjectEntries(def, issues),
|
|
4015
|
-
checkLevelKindEffectOutputs(def, issues), checkActivityTerminalPaths(def, issues),
|
|
4138
|
+
checkLevelKindEffectOutputs(def, issues), checkEffectRetry(def, issues), checkActivityTerminalPaths(def, issues),
|
|
4016
4139
|
checkTerminalStageActivities(def, issues), checkTriggeredActionParams(def, issues),
|
|
4017
4140
|
checkStoredRolesPlacement(def, issues), checkGroups(def, issues), issues;
|
|
4018
4141
|
}
|
|
4019
4142
|
|
|
4020
|
-
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, DECISION_SEMANTICS, DEFAULT_TRANSITION_WHEN, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DefinitionInUseError, DefinitionNotFoundError, EFFECTS_READ, EXECUTOR_CLASSIFICATIONS, EffectNotFoundError,
|
|
4143
|
+
export { ACTION_SEMANTICS, ACTIVITY_KINDS, ACTIVITY_STATUSES, ACTOR_KINDS, ANONYMOUS_IDENTITY, ActorShape, AuthoringActionSchema, AuthoringActivitySchema, AuthoringEffectSchema, AuthoringFieldEntrySchema, AuthoringGuardSchema, AuthoringOpSchema, AuthoringStageSchema, AuthoringTransitionSchema, AuthoringWorkflowSchema, CALLER_BOUND_VARS, CONDITION_VARS, ContractViolationError, DECISION_SEMANTICS, DEFAULT_TRANSITION_WHEN, DOCUMENT_VALUE_PERMISSIONS, DRIVER_KINDS, DefinitionInUseError, DefinitionNotFoundError, EFFECTS_READ, EXECUTOR_CLASSIFICATIONS, EffectNotFoundError, FIELD_READ, FIELD_SCOPES, FIELD_VALUE_KINDS, FILTER_SCOPE_VARS, FieldValueShapeError, GROUP_KINDS, GUARD_ACTIONS_REQUIRED_MESSAGE, GUARD_PREDICATE_VARS, GdrShape, GroupSchema, InstanceNotFoundError, IsoTimestamp, LAKE_MUTATION_GUARD_ACTIONS, MUTATION_GUARD_ACTIONS, MUTATION_GUARD_ID_SPACES, NonEmptyString$1 as NonEmptyString, PersistedDocShapeError, RESERVED_CONDITION_VARS, RESOURCE_ALIAS_NAME_SOURCE, SIGNAL_SEMANTICS, START_FILTER_VARS, START_REQUIREMENT_VARS, SYSTEM_IDENTITY, SpawnContractsInvalidError, StoredFieldOpSchema, UNIVERSAL_ROLE_ALIAS_KEY, VersionSpecificDatasetGdrError, WORKFLOW_DEFINITION_TYPE, WorkflowConfigSchema, WorkflowError, activeAssignmentMembers, actorFulfillsRole, actorMatchesAssignment, andConditions, assignmentKindAcceptsRoles, assignmentMatch, assignmentMembers, assignmentState, assignmentStateCounts, checkWorkflowInvariants, choiceValueIssues, classifyPrincipalId, clientConfigFromResource, condemnSubworkflow, condemnedSubworkflows, conditionEffectReads, conditionFieldReadNames, conditionParameterNames, conditionSyntaxIssues, datasetResourceParts, definitionDocId, deriveActivityKind, deriveExecutorClassification, desugarWorkflow, directoryBridgeId, driverKind, effectBackoffMs, errorMessage, evaluateCondition, evaluateConditionOutcome, evaluatePredicates, extractDocumentId, fieldValueSchemas, findCurrentActivityEntry, findOpenStageEntry, firstCarriedGlobalId, formatIssuePath, formatIssues, formatValidationError, gdrFromResource, gdrRef, gdrResourcePrefix, gdrUri, groq, groupMembershipNames, identityMatchesAssignment, instanceAssignmentStateCounts, isAlwaysArrayFieldKind, isAssignmentFieldEntry, isBareDocumentId, isBareSeedId, isCascadeFired, isFilterScopedOut, isGdr, isGdrUri, isGuardReadExpr, isInputSourced, isNotesEntry, isParseableInstant, isRecord, isSingleDocRefEntry, isSingleDocRefKind, isStartableDefinition, isSubjectEntry, isTerminalActivityStatus, isTodoListEntry, isTodoListItem, isUnevaluable, labelFor, lakePrincipalId, liveSubworkflows, mutationGuardActionIdSpace, mutationGuardRequiresSplitEmission, normalizeAssignmentMembers, openActivityAssignments, parseFieldValue, parseGdr, parseOrThrow, parsePersistedDoc, parseResourceGdr, parseStoredDefinition, readsRootDocument, refCanvas, refDashboard, refDataset, refKindAcceptsTypes, refMediaLibrary, refTypeIssues, rejectedRefTypes, releaseDocId, releaseRef, resolvedChildStatus, resourceAliasesToMap, resourceFromGdrUri, resourceFromParsed, resourceGdr, rethrowWithContext, runGroq, sameResource, scalarValidationIssues, schemaTreeShape, selfGdr, startKindOf, tagScopeFilter, toBareId, toPhysicalGdr, tolerantEntries, tolerantObject, tryParseGdr, validateFieldAppendItem, validateFieldValue, validateResourceAliasName, validateTag };
|