openxiangda-contracts 2.43.0 → 2.63.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.
Files changed (114) hide show
  1. package/dist/browser.d.ts +8 -0
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +8 -0
  4. package/dist/browser.js.map +1 -1
  5. package/dist/concurrency.d.ts +2 -2
  6. package/dist/concurrency.d.ts.map +1 -1
  7. package/dist/data-field-validation.d.ts.map +1 -1
  8. package/dist/data-field-validation.js +13 -2
  9. package/dist/data-field-validation.js.map +1 -1
  10. package/dist/field-value-schemas.d.ts +1 -1
  11. package/dist/field-value-schemas.d.ts.map +1 -1
  12. package/dist/field-value-schemas.js +2 -1
  13. package/dist/field-value-schemas.js.map +1 -1
  14. package/dist/index.d.ts +8 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +8 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/native-compiler/compiler.d.ts +1 -1
  19. package/dist/native-compiler/compiler.d.ts.map +1 -1
  20. package/dist/native-compiler/compiler.js +266 -22
  21. package/dist/native-compiler/compiler.js.map +1 -1
  22. package/dist/native-compiler/data-business-command.d.ts +42 -0
  23. package/dist/native-compiler/data-business-command.d.ts.map +1 -0
  24. package/dist/native-compiler/data-business-command.js +88 -0
  25. package/dist/native-compiler/data-business-command.js.map +1 -0
  26. package/dist/native-compiler/data-capacity.d.ts +8 -0
  27. package/dist/native-compiler/data-capacity.d.ts.map +1 -0
  28. package/dist/native-compiler/data-capacity.js +15 -0
  29. package/dist/native-compiler/data-capacity.js.map +1 -0
  30. package/dist/native-compiler/data-field.d.ts +2 -0
  31. package/dist/native-compiler/data-field.d.ts.map +1 -1
  32. package/dist/native-compiler/data-field.js +7 -2
  33. package/dist/native-compiler/data-field.js.map +1 -1
  34. package/dist/native-compiler/data-surface.d.ts +1 -1
  35. package/dist/native-compiler/data-surface.d.ts.map +1 -1
  36. package/dist/native-compiler/data-surface.js +2 -0
  37. package/dist/native-compiler/data-surface.js.map +1 -1
  38. package/dist/native-compiler/index.cjs +5 -5
  39. package/dist/native-compiler/index.d.ts +10 -0
  40. package/dist/native-compiler/index.d.ts.map +1 -1
  41. package/dist/native-compiler/index.js +5 -5
  42. package/dist/native-compiler/managed-concurrency.js +2 -2
  43. package/dist/native-compiler/managed-concurrency.js.map +1 -1
  44. package/dist/native-compiler/resource-view.d.ts +4 -0
  45. package/dist/native-compiler/resource-view.d.ts.map +1 -0
  46. package/dist/native-compiler/resource-view.js +20 -0
  47. package/dist/native-compiler/resource-view.js.map +1 -0
  48. package/dist/native-compiler/unique-keys.d.ts +21 -0
  49. package/dist/native-compiler/unique-keys.d.ts.map +1 -1
  50. package/dist/native-compiler/unique-keys.js +17 -3
  51. package/dist/native-compiler/unique-keys.js.map +1 -1
  52. package/dist/native-compiler/user-candidates.d.ts +72 -0
  53. package/dist/native-compiler/user-candidates.d.ts.map +1 -0
  54. package/dist/native-compiler/user-candidates.js +184 -0
  55. package/dist/native-compiler/user-candidates.js.map +1 -0
  56. package/dist/native-compiler/workflow-approval-empty.d.ts +28 -0
  57. package/dist/native-compiler/workflow-approval-empty.d.ts.map +1 -0
  58. package/dist/native-compiler/workflow-approval-empty.js +34 -0
  59. package/dist/native-compiler/workflow-approval-empty.js.map +1 -0
  60. package/dist/native-compiler/workflow-business-command.d.ts +108 -0
  61. package/dist/native-compiler/workflow-business-command.d.ts.map +1 -0
  62. package/dist/native-compiler/workflow-business-command.js +58 -0
  63. package/dist/native-compiler/workflow-business-command.js.map +1 -0
  64. package/dist/native-compiler/workflow-graph.d.ts +8 -0
  65. package/dist/native-compiler/workflow-graph.d.ts.map +1 -1
  66. package/dist/native-compiler/workflow-graph.js +1 -0
  67. package/dist/native-compiler/workflow-graph.js.map +1 -1
  68. package/dist/native-compiler/workflow-initiator-approval.d.ts +28 -0
  69. package/dist/native-compiler/workflow-initiator-approval.d.ts.map +1 -0
  70. package/dist/native-compiler/workflow-initiator-approval.js +30 -0
  71. package/dist/native-compiler/workflow-initiator-approval.js.map +1 -0
  72. package/dist/native-compiler/workflow-instance-policy.d.ts +2 -0
  73. package/dist/native-compiler/workflow-instance-policy.d.ts.map +1 -1
  74. package/dist/native-compiler/workflow-instance-policy.js +9 -4
  75. package/dist/native-compiler/workflow-instance-policy.js.map +1 -1
  76. package/dist/native-compiler/workflow-launch-preflight.d.ts +22 -0
  77. package/dist/native-compiler/workflow-launch-preflight.d.ts.map +1 -0
  78. package/dist/native-compiler/workflow-launch-preflight.js +30 -0
  79. package/dist/native-compiler/workflow-launch-preflight.js.map +1 -0
  80. package/dist/native-compiler/workflow-node-administration.d.ts +6 -0
  81. package/dist/native-compiler/workflow-node-administration.d.ts.map +1 -1
  82. package/dist/native-compiler/workflow-node-administration.js +9 -2
  83. package/dist/native-compiler/workflow-node-administration.js.map +1 -1
  84. package/dist/native-compiler/workflow-rejection-notification.d.ts +31 -0
  85. package/dist/native-compiler/workflow-rejection-notification.d.ts.map +1 -0
  86. package/dist/native-compiler/workflow-rejection-notification.js +47 -0
  87. package/dist/native-compiler/workflow-rejection-notification.js.map +1 -0
  88. package/dist/native-compiler/workflow-task-page.d.ts +44 -3
  89. package/dist/native-compiler/workflow-task-page.d.ts.map +1 -1
  90. package/dist/native-compiler/workflow-task-page.js +108 -9
  91. package/dist/native-compiler/workflow-task-page.js.map +1 -1
  92. package/dist/native-version.d.ts +19 -1
  93. package/dist/native-version.d.ts.map +1 -1
  94. package/dist/native-version.js +19 -1
  95. package/dist/native-version.js.map +1 -1
  96. package/dist/native.d.ts +15 -2
  97. package/dist/native.d.ts.map +1 -1
  98. package/dist/native.js.map +1 -1
  99. package/dist/schemas.d.ts +3048 -212
  100. package/dist/schemas.d.ts.map +1 -1
  101. package/dist/schemas.js +253 -9
  102. package/dist/schemas.js.map +1 -1
  103. package/dist/surface.d.ts +3 -2
  104. package/dist/surface.d.ts.map +1 -1
  105. package/dist/surface.js +1 -19
  106. package/dist/surface.js.map +1 -1
  107. package/dist/types.d.ts +286 -8
  108. package/dist/types.d.ts.map +1 -1
  109. package/dist/types.js +4 -0
  110. package/dist/types.js.map +1 -1
  111. package/dist/validation.d.ts.map +1 -1
  112. package/dist/validation.js +42 -5
  113. package/dist/validation.js.map +1 -1
  114. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
- import { NativeUniqueKeyContractError, parseNativeUniqueKeys } from './unique-keys.js';
1
+ import { validateUserCandidateReferences, validateWorkflowUserCandidateBindings, UserCandidateContractError } from './user-candidates.js';
2
+ import { NativeUniqueKeyContractError, parseNativeUniqueKeys, nativeUniqueKeysContractVersion } from './unique-keys.js';
2
3
  import { validateManagedConcurrency, ManagedConcurrencyContractError } from './managed-concurrency.js';
3
4
  import { normalizeDecimalReservationEventDeclaration, decimalReservationEventContext } from './decimal-reservation.js';
4
5
  import { NativeDecimalLifecycleContractError, parseDecimalReservationLifecycle, validateDecimalReservationLifecycles } from './decimal-lifecycle.js';
@@ -9,6 +10,11 @@ import { validateWorkflowInstanceCommandPolicies } from './workflow-instance-pol
9
10
  import { validateWorkflowReadability } from './workflow-graph.js';
10
11
  import { validateWorkflowAdministration } from './workflow-node-administration.js';
11
12
  import { validateWorkflowAutomaticCc } from './workflow-automatic-cc.js';
13
+ import { validateWorkflowApprovalEmptyPolicy, workflowBindingAllowsEmptyUsers } from './workflow-approval-empty.js';
14
+ import { validateWorkflowInitiatorApprovalPolicy } from './workflow-initiator-approval.js';
15
+ import { validateWorkflowRejectionNotification } from './workflow-rejection-notification.js';
16
+ import { validateWorkflowCommandHandlers } from './workflow-business-command.js';
17
+ import { validateWorkflowLaunchPreflight } from './workflow-launch-preflight.js';
12
18
  import { validateWorkflowTaskPages } from './workflow-task-page.js';
13
19
  import { validateWorkflowBusinessSteps, compileWorkflowBusinessStepHandlers, validateWorkflowBusinessStepSubscriptions, WORKFLOW_BUSINESS_STEP_EVENT, WORKFLOW_BUSINESS_STEP_EVENTS } from './workflow-business-step.js';
14
20
  import { validateWorkflowAssignmentRoutingBindings, validateWorkflowAssignmentRoutingPolicy } from './workflow-assignment-routing.js';
@@ -124,6 +130,7 @@ export const DATA_EVENT_TYPES_V2 = [
124
130
  'openxiangda.data.record.deleted.v2',
125
131
  ];
126
132
  export const WORKFLOW_EVENT_TYPES_V2 = [
133
+ 'openxiangda.workflow.node.skipped.v2',
127
134
  WORKFLOW_BUSINESS_STEP_EVENT,
128
135
  'openxiangda.workflow.step.result_received.v2',
129
136
  'openxiangda.workflow.step.completed.v2',
@@ -284,6 +291,10 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
284
291
  const roles = config.authz.roles;
285
292
  const resources = config.data.resources;
286
293
  const operations = config.backend.operations;
294
+ const initiatorPhoneOperations = operations.filter(operation => operation.platformAccess?.directory?.mode === 'current-initiator' &&
295
+ operation.platformAccess.directory.fields.includes('phone'));
296
+ const initiatorPhoneCommands = (config.data.concurrency?.commands || []).filter((command) => command.execution?.directory?.mode === 'current-initiator' &&
297
+ command.execution.directory.fields.includes('phone'));
287
298
  const requiresDirectoryV2 = roles.some(role => role.capabilities.some((capability) => capability.endsWith(':directory:read'))) ||
288
299
  resources.some(resource => resource.schema.fields.some((field) => [
289
300
  'user.single',
@@ -299,12 +310,26 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
299
310
  const usesManagedFiles = operations.some(operation => operation.platformAccess?.managedFiles ||
300
311
  operation.platformAccess?.managedFileCopies) ||
301
312
  config.events.subscriptions.some((subscription) => subscription.platformAccess?.managedFileCopies);
302
- const usesNotification = operations.some(operation => operation.platformAccess?.notification) ||
313
+ const usesNotification = config.workflows.definitions.some((item) => item.definition.rejectionNotification !== undefined) ||
314
+ operations.some(operation => operation.platformAccess?.notification) ||
303
315
  config.events.subscriptions.some((subscription) => subscription.platformAccess?.notification);
304
- const taskManagedFileDefinitions = config.workflows.definitions.filter((item) => {
316
+ const hasTaskFile = (item, types, nestedOnly = false) => {
305
317
  const fields = resources.find(resource => resource.code === item.definition.subject?.resourceCode)?.schema.fields || [];
306
- return Object.values(item.definition.taskPages || {}).some((page) => page.fields.some((field) => !field.readonly && fields.some((metadata) => metadata.code === field.code && ['file', 'image'].includes(metadata.type))));
307
- });
318
+ return Object.values(item.definition.taskPages || {}).some((page) => page.fields.some((field) => {
319
+ if (field.readonly)
320
+ return false;
321
+ const metadata = fields.find((value) => value.code === field.code);
322
+ if (!nestedOnly && metadata && types.includes(metadata.type))
323
+ return true;
324
+ if (!field.subtable || !metadata?.subtable)
325
+ return false;
326
+ const children = resources.find(resource => resource.code === metadata.subtable.resourceCode)?.schema.fields || [];
327
+ return field.subtable.fields.some((child) => !child.readonly && children.some((value) => value.code === child.code && types.includes(value.type)));
328
+ }));
329
+ };
330
+ const taskManagedFileDefinitions = config.workflows.definitions.filter((item) => hasTaskFile(item, ['file', 'image', 'signature', 'text.rich']));
331
+ const taskRichFieldDefinitions = config.workflows.definitions.filter((item) => hasTaskFile(item, ['signature', 'text.rich']));
332
+ const taskOwnedFileDefinitions = config.workflows.definitions.filter((item) => hasTaskFile(item, ['file', 'image', 'signature', 'text.rich'], true));
308
333
  const standardProcessDefinitions = config.workflows.definitions.filter((declaration) => declaration.launch.mode === 'standalone' ||
309
334
  declaration.launch.mode === 'hidden-handoff');
310
335
  const usesBusinessProcess = operations.some(operation => operation.platformAccess?.workflow) ||
@@ -359,6 +384,12 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
359
384
  declaration: resources.filter(resource => resource.decimalReservationLifecycle)
360
385
  .map(resource => ({ code: resource.code, decimalReservationLifecycle: resource.decimalReservationLifecycle })) }]
361
386
  : []),
387
+ ...(operations.some(operation => operation.platformAccess?.dataCommands)
388
+ ? [{ code: 'data.business-commands', declaration: operations.filter(operation => operation.platformAccess?.dataCommands) }]
389
+ : []),
390
+ ...(operations.some(operation => operation.platformAccess?.recordEdit)
391
+ ? [{ code: 'data.record-edit', declaration: operations.filter(operation => operation.platformAccess?.recordEdit) }]
392
+ : []),
362
393
  ...(operations.some(operation => operation.platformAccess?.decimalReservation) ||
363
394
  config.events.subscriptions.some((subscription) => subscription.platformAccess?.decimalReservation)
364
395
  ? [{
@@ -403,6 +434,14 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
403
434
  },
404
435
  ]
405
436
  : []),
437
+ ...(resources.some(resource => resource.recordComments !== undefined)
438
+ ? [{ code: 'data.record-comments', declaration: dataUsage }] : []),
439
+ ...(resources.some(resource => resource.recordDeletion !== undefined)
440
+ ? [{ code: 'data.workflow-record-deletion', declaration: dataUsage }] : []),
441
+ ...(resources.some(resource => resource.recordPrint !== undefined)
442
+ ? [{ code: 'data.record-print', declaration: dataUsage }] : []),
443
+ ...(resources.some(resource => resource.workflowHistory !== undefined)
444
+ ? [{ code: 'workflow.record-history-read', declaration: dataUsage }] : []),
406
445
  ...(resources.some(hasDataAuditReadPolicy)
407
446
  ? [{ code: 'data.audit-read-access', declaration: dataUsage }]
408
447
  : []),
@@ -435,6 +474,13 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
435
474
  ]
436
475
  : []),
437
476
  ...(operations.some(operation => operation.platformAccess?.directory?.mode === 'selected-user') ? [{ code: 'directory.selected-user', declaration: operations.filter(operation => operation.platformAccess?.directory?.mode === 'selected-user').map(operation => ({ code: operation.code, directory: operation.platformAccess.directory })) }] : []),
477
+ ...(initiatorPhoneOperations.length || initiatorPhoneCommands.length ? [{
478
+ code: 'directory.current-initiator-phone',
479
+ declaration: {
480
+ operations: initiatorPhoneOperations.map(operation => ({ code: operation.code, directory: operation.platformAccess.directory })),
481
+ commands: initiatorPhoneCommands.map((command) => ({ code: command.code, directory: command.execution.directory })),
482
+ },
483
+ }] : []),
438
484
  ...(usesManagedFiles
439
485
  ? [
440
486
  {
@@ -477,6 +523,9 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
477
523
  },
478
524
  ]
479
525
  : []),
526
+ ...(config.data.resources.some((resource) => resource.schema.fields.some((field) => field.userCandidates !== undefined))
527
+ ? [{ code: 'data.user-candidates', declaration: config.data.resources.filter((resource) => resource.schema.fields.some((field) => field.userCandidates !== undefined)) }]
528
+ : []),
480
529
  ...(config.workflows.activations.length
481
530
  ? [
482
531
  {
@@ -495,12 +544,37 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
495
544
  declaration: config.workflows.definitions.filter((item) => item.definition.instanceCommands !== undefined),
496
545
  }]
497
546
  : []),
547
+ ...(config.workflows.definitions.some((item) => item.definition.instanceCommands?.withdraw?.reasonRequired === false)
548
+ ? [{ code: 'workflow.optional-withdrawal-reason',
549
+ declaration: config.workflows.definitions.filter((item) => item.definition.instanceCommands?.withdraw?.reasonRequired === false) }]
550
+ : []),
551
+ ...(config.workflows.definitions.some((item) => item.definition.rejectionNotification !== undefined)
552
+ ? [{
553
+ code: 'workflow.rejection-notification',
554
+ declaration: config.workflows.definitions.filter((item) => item.definition.rejectionNotification !== undefined),
555
+ }]
556
+ : []),
557
+ ...(config.workflows.definitions.some((item) => item.definition.commandHandlers !== undefined) || operations.some(operation => operation.platformAccess?.workflow?.businessCommands)
558
+ ? [{ code: 'workflow.business-data-command', declaration: {
559
+ definitions: config.workflows.definitions.filter((item) => item.definition.commandHandlers !== undefined),
560
+ operations: operations.filter(operation => operation.platformAccess?.workflow?.businessCommands),
561
+ } }] : []),
562
+ ...(config.workflows.definitions.some((item) => item.definition.launchPreflight !== undefined)
563
+ ? [{ code: 'workflow.launch-preflight', declaration: config.workflows.definitions.filter((item) => item.definition.launchPreflight !== undefined) }] : []),
564
+ ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.operationPolicy?.reject?.commentRequired === false))
565
+ ? [{ code: 'workflow.optional-rejection-comment', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.operationPolicy?.reject?.commentRequired === false)) }] : []),
498
566
  ...(namedInputSourceDefinitions.length
499
567
  ? [{ code: 'workflow.named-input-sources', declaration: namedInputSourceDefinitions }]
500
568
  : []),
501
569
  ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.administration !== undefined || node.operationPolicy !== undefined))
502
570
  ? [{ code: 'workflow.node-administration', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.administration !== undefined || node.operationPolicy !== undefined)) }]
503
571
  : []),
572
+ ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'approval' && node.emptyPolicy !== undefined))
573
+ ? [{ code: 'workflow.approval-empty-policy', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'approval' && node.emptyPolicy !== undefined)) }]
574
+ : []),
575
+ ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.initiatorApprovalPolicy !== undefined))
576
+ ? [{ code: 'workflow.initiator-approval-policy', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.initiatorApprovalPolicy !== undefined)) }]
577
+ : []),
504
578
  ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'cc'))
505
579
  ? [{ code: 'workflow.automatic-cc', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'cc')) }]
506
580
  : []),
@@ -509,6 +583,12 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
509
583
  : []),
510
584
  ...(taskManagedFileDefinitions.length
511
585
  ? [{ code: 'workflow.task-managed-files', declaration: taskManagedFileDefinitions }] : []),
586
+ ...(taskOwnedFileDefinitions.length
587
+ ? [{ code: 'workflow.task-owned-files', declaration: taskOwnedFileDefinitions }] : []),
588
+ ...(taskRichFieldDefinitions.length
589
+ ? [{ code: 'workflow.task-rich-fields', declaration: taskRichFieldDefinitions }] : []),
590
+ ...(config.workflows.definitions.some((item) => Object.values(item.definition.taskPages || {}).some((page) => page.fields.some((field) => field.subtable)))
591
+ ? [{ code: 'workflow.task-owned-subtables', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.taskPages || {}).some((page) => page.fields.some((field) => field.subtable))) }] : []),
512
592
  ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'action'))
513
593
  ? [{ code: 'workflow.durable-business-step', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'action')) }]
514
594
  : []),
@@ -537,6 +617,8 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
537
617
  {
538
618
  code: 'notification-hub-v2',
539
619
  declaration: {
620
+ ...(config.workflows.definitions.some((item) => item.definition.rejectionNotification !== undefined)
621
+ ? { rejectionNotifications: config.workflows.definitions.filter((item) => item.definition.rejectionNotification !== undefined) } : {}),
540
622
  operations: operations
541
623
  .filter(operation => operation.platformAccess?.notification)
542
624
  .map(operation => ({
@@ -573,7 +655,9 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
573
655
  return usages
574
656
  .map(({ code, declaration }) => ({
575
657
  code,
576
- contractVersion: OPENXIANGDA_V2_PLATFORM_CAPABILITY_CONTRACT_VERSIONS[code],
658
+ contractVersion: code === 'data.unique-keys'
659
+ ? (resources.some(resource => nativeUniqueKeysContractVersion(resource.schema.fields, resource.uniqueKeys) === '1.1.0') ? '1.1.0' : '1.0.0')
660
+ : OPENXIANGDA_V2_PLATFORM_CAPABILITY_CONTRACT_VERSIONS[code],
577
661
  usageDigest: `sha256:${sha256Canonical(declaration)}`,
578
662
  }))
579
663
  .sort((left, right) => left.code === right.code ? 0 : left.code < right.code ? -1 : 1);
@@ -988,7 +1072,7 @@ function compileExpectedContract(config, configDigest, generatorVersion) {
988
1072
  field: trigger.field,
989
1073
  })),
990
1074
  ...(config.workflows.activations.length
991
- ? WORKFLOW_EVENT_TYPES_V2.filter(eventType => !WORKFLOW_BUSINESS_STEP_EVENTS.includes(eventType) || config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'action'))).map(eventType => ({
1075
+ ? WORKFLOW_EVENT_TYPES_V2.filter(eventType => !WORKFLOW_BUSINESS_STEP_EVENTS.includes(eventType) || config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'action'))).filter(eventType => eventType !== 'openxiangda.workflow.node.skipped.v2' || config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'approval' && node.emptyPolicy === 'skip'))).map(eventType => ({
992
1076
  code: `workflow:${eventType}`,
993
1077
  source: 'workflow',
994
1078
  eventType,
@@ -1193,9 +1277,12 @@ function compileCapabilities(config) {
1193
1277
  };
1194
1278
  catalog.set(code, capability);
1195
1279
  }
1196
- for (const [resourceIndex, raw] of config.data.resources.entries()) {
1197
- const pointer = `/config/data/resources/${resourceIndex}`;
1198
- const resource = validateResource(raw, pointer, config.appCode);
1280
+ const resources = config.data.resources.map((raw, resourceIndex) => ({
1281
+ pointer: `/config/data/resources/${resourceIndex}`,
1282
+ resource: validateResource(raw, `/config/data/resources/${resourceIndex}`, config.appCode),
1283
+ }));
1284
+ // CRUD definitions precede all field references, regardless of source order.
1285
+ for (const { pointer, resource } of resources) {
1199
1286
  const operationNames = {
1200
1287
  read: `读取${resource.name}`,
1201
1288
  create: `新建${resource.name}`,
@@ -1214,6 +1301,10 @@ function compileCapabilities(config) {
1214
1301
  source: 'data',
1215
1302
  });
1216
1303
  }
1304
+ }
1305
+ const definedCapabilities = new Set(catalog.keys());
1306
+ const fieldOwners = new Map();
1307
+ for (const { pointer, resource } of resources) {
1217
1308
  for (const [fieldCode, rawPolicy] of Object.entries(resource.fieldPolicies)) {
1218
1309
  if (isDataAuditMetadataField(fieldCode))
1219
1310
  continue;
@@ -1223,6 +1314,13 @@ function compileCapabilities(config) {
1223
1314
  continue;
1224
1315
  for (const code of uniqueStrings(policy[operation], `${pointer}/fieldPolicies/${fieldCode}/${operation}`, 2000)) {
1225
1316
  capabilityCode(code, `${pointer}/fieldPolicies/${fieldCode}/${operation}`, config.appCode);
1317
+ if (definedCapabilities.has(code))
1318
+ continue;
1319
+ const existingOwner = fieldOwners.get(code);
1320
+ if (existingOwner && existingOwner !== resource.code) {
1321
+ fail('NATIVE_CAPABILITY_DUPLICATE', `${pointer}/fieldPolicies/${fieldCode}/${operation}`);
1322
+ }
1323
+ fieldOwners.set(code, resource.code);
1226
1324
  if (catalog.has(code))
1227
1325
  continue;
1228
1326
  catalog.set(code, {
@@ -1511,6 +1609,15 @@ function compileOperations(config) {
1511
1609
  const code = stableCode(operation.code, `${pointer}/code`);
1512
1610
  validateBackendOperationAi(operation.ai, pointer, code, method, declaredResourceCodes);
1513
1611
  const platformAccess = validateOperationPlatformAccess(operation.platformAccess, `${pointer}/platformAccess`, declaredResources, declaredResourceFields, declaredWorkflowCodes, new Set(config.authz.roles.map((role) => String(role.code))));
1612
+ if (platformAccess?.recordEdit) {
1613
+ const owner = config.data.resources.find((item) => item.code === platformAccess.recordEdit.resourceCode);
1614
+ if (owner?.surface?.mutationOwner !== 'action')
1615
+ fail('NATIVE_RECORD_EDIT_ACTION_OWNER_REQUIRED', `${pointer}/platformAccess/recordEdit`);
1616
+ for (const fieldCode of platformAccess.recordEdit.fieldCodes) {
1617
+ if (!owner.fieldPolicies?.[fieldCode]?.update?.length || owner.surface?.fields?.[fieldCode]?.system === true)
1618
+ fail('NATIVE_RECORD_EDIT_FIELD_INVALID', `${pointer}/platformAccess/recordEdit/fieldCodes`);
1619
+ }
1620
+ }
1514
1621
  if (platformAccess?.decimalReservation) {
1515
1622
  const reservation = platformAccess.decimalReservation;
1516
1623
  const resourceCode = String(reservation.resourceCode);
@@ -1707,11 +1814,33 @@ function validateOperationPlatformAccess(value, pointer, declaredResources, decl
1707
1814
  if (value === undefined)
1708
1815
  return null;
1709
1816
  const access = object(value, pointer);
1710
- exactKeys(access, ['directory', 'managedFiles', 'managedFileCopies', 'notification', 'workflow', 'roleAssertions', 'decimalReservation'], pointer, true);
1817
+ exactKeys(access, ['directory', 'managedFiles', 'managedFileCopies', 'notification', 'workflow', 'roleAssertions', 'decimalReservation', 'dataCommands', 'recordEdit'], pointer, true);
1711
1818
  if (Object.keys(access).length === 0) {
1712
1819
  fail('NATIVE_OPERATION_PLATFORM_ACCESS_EMPTY', pointer);
1713
1820
  }
1714
1821
  const result = {};
1822
+ if (access.dataCommands !== undefined) {
1823
+ const commands = object(access.dataCommands, `${pointer}/dataCommands`);
1824
+ exactKeys(commands, ['mode'], `${pointer}/dataCommands`);
1825
+ equal(commands.mode, 'recoverable-native', `${pointer}/dataCommands/mode`);
1826
+ result.dataCommands = { mode: 'recoverable-native' };
1827
+ }
1828
+ if (access.recordEdit !== undefined) {
1829
+ const entryPointer = `${pointer}/recordEdit`;
1830
+ const entry = object(access.recordEdit, entryPointer);
1831
+ exactKeys(entry, ['resourceCode', 'fieldCodes'], entryPointer);
1832
+ const code = resourceCode(entry.resourceCode, `${entryPointer}/resourceCode`);
1833
+ const fields = declaredResourceFields.get(code);
1834
+ const fieldCodes = uniqueStrings(entry.fieldCodes, `${entryPointer}/fieldCodes`, 200);
1835
+ if (!fields || !fieldCodes.length || !result.dataCommands)
1836
+ fail('NATIVE_RECORD_EDIT_DECLARATION_INVALID', entryPointer);
1837
+ for (const fieldCode of fieldCodes) {
1838
+ const field = fields.get(fieldCode);
1839
+ if (!field || field.type === 'serial-number' || field.type === 'subtable')
1840
+ fail('NATIVE_RECORD_EDIT_FIELD_INVALID', `${entryPointer}/fieldCodes`);
1841
+ }
1842
+ result.recordEdit = { resourceCode: code, fieldCodes: uniqueSorted(fieldCodes) };
1843
+ }
1715
1844
  if (access.roleAssertions !== undefined) {
1716
1845
  const assertions = object(access.roleAssertions, `${pointer}/roleAssertions`);
1717
1846
  exactKeys(assertions, ['roleCodes'], `${pointer}/roleAssertions`);
@@ -1735,8 +1864,9 @@ function validateOperationPlatformAccess(value, pointer, declaredResources, decl
1735
1864
  'employeeNumber',
1736
1865
  'primaryDepartment',
1737
1866
  'departments',
1867
+ ...(directory.mode === 'current-initiator' ? ['phone'] : []),
1738
1868
  ]);
1739
- const fields = uniqueStrings(directory.fields, `${pointer}/directory/fields`, 4);
1869
+ const fields = uniqueStrings(directory.fields, `${pointer}/directory/fields`, allowed.size);
1740
1870
  if (!fields.length || fields.some(field => !allowed.has(field))) {
1741
1871
  fail('NATIVE_OPERATION_DIRECTORY_FIELDS_INVALID', `${pointer}/directory/fields`);
1742
1872
  }
@@ -1829,12 +1959,16 @@ function validateOperationPlatformAccess(value, pointer, declaredResources, decl
1829
1959
  }
1830
1960
  if (access.workflow !== undefined) {
1831
1961
  const workflow = object(access.workflow, `${pointer}/workflow`);
1832
- exactKeys(workflow, ['codes'], `${pointer}/workflow`);
1962
+ exactKeys(workflow, ['codes'], `${pointer}/workflow`, ['businessCommands']);
1833
1963
  const codes = uniqueStrings(workflow.codes, `${pointer}/workflow/codes`, 16);
1834
1964
  if (!codes.length || codes.some(code => !declaredWorkflowCodes.has(code))) {
1835
1965
  fail('NATIVE_OPERATION_WORKFLOW_CODE_INVALID', `${pointer}/workflow/codes`);
1836
1966
  }
1837
- result.workflow = { codes: uniqueSorted(codes) };
1967
+ const businessCommands = workflow.businessCommands === undefined ? undefined : uniqueStrings(workflow.businessCommands, `${pointer}/workflow/businessCommands`, 3);
1968
+ if (businessCommands && (!businessCommands.length || businessCommands.some(command => !['approve', 'reject', 'withdraw'].includes(command)))) {
1969
+ fail('NATIVE_OPERATION_WORKFLOW_BUSINESS_COMMAND_INVALID', `${pointer}/workflow/businessCommands`);
1970
+ }
1971
+ result.workflow = { codes: uniqueSorted(codes), ...(businessCommands ? { businessCommands: uniqueSorted(businessCommands) } : {}) };
1838
1972
  }
1839
1973
  if (access.decimalReservation !== undefined) {
1840
1974
  const reservationPointer = `${pointer}/decimalReservation`;
@@ -3596,6 +3730,40 @@ function validateAuthorizationReferences(config, capabilities) {
3596
3730
  config.data.resources.forEach((rawResource, index) => {
3597
3731
  const resourcePointer = `/config/data/resources/${index}`;
3598
3732
  const resource = object(rawResource, resourcePointer);
3733
+ if (resource.recordDeletion !== undefined) {
3734
+ const pointer = `${resourcePointer}/recordDeletion/delete`;
3735
+ for (const capability of resource.recordDeletion.delete) {
3736
+ capabilityCode(capability, pointer, config.appCode);
3737
+ if (!capabilityCodes.has(capability))
3738
+ fail('NATIVE_CAPABILITY_REFERENCE_MISSING', pointer);
3739
+ }
3740
+ }
3741
+ if (resource.recordComments !== undefined) {
3742
+ for (const mode of ['read', 'create']) {
3743
+ const pointer = `${resourcePointer}/recordComments/${mode}`;
3744
+ for (const capability of resource.recordComments[mode]) {
3745
+ capabilityCode(capability, pointer, config.appCode);
3746
+ if (!capabilityCodes.has(capability))
3747
+ fail('NATIVE_CAPABILITY_REFERENCE_MISSING', pointer);
3748
+ }
3749
+ }
3750
+ }
3751
+ if (resource.recordPrint !== undefined) {
3752
+ const pointer = `${resourcePointer}/recordPrint/read`;
3753
+ for (const capability of resource.recordPrint.read) {
3754
+ capabilityCode(capability, pointer, config.appCode);
3755
+ if (!capabilityCodes.has(capability))
3756
+ fail('NATIVE_CAPABILITY_REFERENCE_MISSING', pointer);
3757
+ }
3758
+ }
3759
+ if (resource.workflowHistory !== undefined) {
3760
+ const pointer = `${resourcePointer}/workflowHistory/read`;
3761
+ for (const capability of resource.workflowHistory.read) {
3762
+ capabilityCode(capability, pointer, config.appCode);
3763
+ if (!capabilityCodes.has(capability))
3764
+ fail('NATIVE_CAPABILITY_REFERENCE_MISSING', pointer);
3765
+ }
3766
+ }
3599
3767
  for (const [fieldCode, rawPolicy] of Object.entries(resource.fieldPolicies || {})) {
3600
3768
  if (!isDataAuditMetadataField(fieldCode))
3601
3769
  continue;
@@ -3614,6 +3782,9 @@ function validateAuthorizationReferences(config, capabilities) {
3614
3782
  return;
3615
3783
  const resourceCapabilities = object(resource.capabilities, `${resourcePointer}/capabilities`);
3616
3784
  for (const operation of ['create', 'update', 'delete']) {
3785
+ if (operation === 'delete' && Array.isArray(resource.recordDeletion?.delete) &&
3786
+ resource.recordDeletion.delete.length > 0)
3787
+ continue;
3617
3788
  forbiddenNativeMutationCapabilities.set(String(resourceCapabilities[operation]), operation);
3618
3789
  }
3619
3790
  });
@@ -4082,9 +4253,10 @@ function validateDataPolicyReferences(config) {
4082
4253
  function validateDataFieldReferences(config) {
4083
4254
  try {
4084
4255
  validateNativeDataResourceReferencesV2(config.data.resources, '/config/data/resources');
4256
+ config.data.resources.forEach((resource, index) => validateUserCandidateReferences(resource.schema.fields, new Set(config.authz.roles.map((role) => role.code)), new Set(config.authz.scopeDimensions.map((dimension) => dimension.code)), `/config/data/resources/${index}/schema/fields`));
4085
4257
  }
4086
4258
  catch (error) {
4087
- if (error instanceof NativeDataFieldContractV2Error) {
4259
+ if (error instanceof NativeDataFieldContractV2Error || error instanceof UserCandidateContractError) {
4088
4260
  fail(error.code, error.pointer);
4089
4261
  }
4090
4262
  throw error;
@@ -4307,9 +4479,15 @@ function validateWorkflowReferences(config) {
4307
4479
  }
4308
4480
  definitions.add(key);
4309
4481
  validateWorkflowDefinition(definition, `${pointer}/definition`);
4482
+ const preflightErrors = validateWorkflowLaunchPreflight(definition);
4483
+ if (preflightErrors.length)
4484
+ fail(preflightErrors[0], `${pointer}/definition/launchPreflight`);
4485
+ const businessCommandErrors = validateWorkflowCommandHandlers(definition, config.backend.operations);
4486
+ if (businessCommandErrors.length)
4487
+ fail(businessCommandErrors[0], `${pointer}/definition/commandHandlers`);
4310
4488
  validateWorkflowSubject(definition.subject, resources, `${pointer}/definition/subject`);
4311
4489
  const taskPageErrors = validateWorkflowTaskPages(definition, new Map((resources.get(definition.subject.resourceCode)?.schema.fields || [])
4312
- .map((field) => [field.code, { ...resources.get(definition.subject.resourceCode)?.surface?.fields?.[field.code], ...field, system: ['id', 'revision', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'].includes(field.code) }])));
4490
+ .map((field) => [field.code, { ...resources.get(definition.subject.resourceCode)?.surface?.fields?.[field.code], ...field, system: ['id', 'revision', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'].includes(field.code) }])), new Map([...resources].map(([code, resource]) => [code, new Map(resource.schema.fields.map((field) => [field.code, { ...resource.surface?.fields?.[field.code], ...field, system: ['id', 'revision', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'].includes(field.code) }]))])));
4313
4491
  if (taskPageErrors.length)
4314
4492
  fail(taskPageErrors[0], `${pointer}/definition/taskPages`);
4315
4493
  const policyErrors = validateWorkflowInstanceCommandPolicies(definition, {
@@ -4366,6 +4544,9 @@ function validateWorkflowReferences(config) {
4366
4544
  }
4367
4545
  const definition = definitionValues.get(definitionKey);
4368
4546
  const binding = bindingValues.get(bindingKey);
4547
+ const preflightErrors = validateWorkflowLaunchPreflight(definition, binding);
4548
+ if (preflightErrors.length)
4549
+ fail(preflightErrors[0], pointer);
4369
4550
  const administrationErrors = validateWorkflowAdministration(definition, binding);
4370
4551
  if (administrationErrors.length)
4371
4552
  fail(administrationErrors[0], pointer);
@@ -4375,6 +4556,15 @@ function validateWorkflowReferences(config) {
4375
4556
  fail('NATIVE_WORKFLOW_COMMAND_DEACTIVATION_POLICY_MISMATCH', `${pointer}/acceptedCommandDeactivationPolicy`);
4376
4557
  }
4377
4558
  validateWorkflowDefinitionBinding(definition, binding, `/config/workflows/activations/${index}`);
4559
+ try {
4560
+ const fields = resources.get(definition.subject.resourceCode)?.schema.fields || [];
4561
+ validateWorkflowUserCandidateBindings(definition, binding, new Map(fields.map((field) => [field.code, field])), `${pointer}/bindings`, new Map([...resources].map(([code, resource]) => [code, new Map(resource.schema.fields.map((field) => [field.code, field]))])));
4562
+ }
4563
+ catch (error) {
4564
+ if (error instanceof UserCandidateContractError)
4565
+ fail(error.code, error.pointer);
4566
+ throw error;
4567
+ }
4378
4568
  activeBindings.set(code, binding);
4379
4569
  }
4380
4570
  const routingErrors = validateWorkflowAssignmentRoutingBindings([...activeBindings.values()]);
@@ -4533,7 +4723,7 @@ function validateWorkflowDefinition(definition, pointer) {
4533
4723
  'startAt',
4534
4724
  'inputSchema',
4535
4725
  'nodes',
4536
- ], pointer, ['organizationContext', 'instanceCommands', 'readability', 'taskPages']);
4726
+ ], pointer, ['organizationContext', 'instanceCommands', 'readability', 'taskPages', 'rejectionNotification', 'commandHandlers', 'launchPreflight']);
4537
4727
  equal(definition.schemaVersion, 'openxiangda.workflow-definition/v2', `${pointer}/schemaVersion`);
4538
4728
  workflowCodeValue(definition.code, `${pointer}/code`);
4539
4729
  requiredString(definition.title, `${pointer}/title`, 255);
@@ -4552,6 +4742,15 @@ function validateWorkflowDefinition(definition, pointer) {
4552
4742
  const administrationErrors = validateWorkflowAdministration(definition);
4553
4743
  if (administrationErrors.length)
4554
4744
  fail(administrationErrors[0], `${pointer}/nodes`);
4745
+ const emptyPolicyErrors = validateWorkflowApprovalEmptyPolicy(definition);
4746
+ if (emptyPolicyErrors.length)
4747
+ fail(emptyPolicyErrors[0], `${pointer}/nodes`);
4748
+ const initiatorPolicyErrors = validateWorkflowInitiatorApprovalPolicy(definition);
4749
+ if (initiatorPolicyErrors.length)
4750
+ fail(initiatorPolicyErrors[0], `${pointer}/nodes`);
4751
+ const rejectionNotificationErrors = validateWorkflowRejectionNotification(definition);
4752
+ if (rejectionNotificationErrors.length)
4753
+ fail(rejectionNotificationErrors[0], `${pointer}/rejectionNotification`);
4555
4754
  const ccErrors = validateWorkflowAutomaticCc(definition);
4556
4755
  if (ccErrors.length)
4557
4756
  fail(ccErrors[0], `${pointer}/nodes`);
@@ -4696,10 +4895,10 @@ function validateWorkflowBinding(binding, pointer) {
4696
4895
  ].includes(provider)) {
4697
4896
  fail('NATIVE_WORKFLOW_BINDING_PROVIDER_INVALID', `${pointer}/bindings/${code}/provider`);
4698
4897
  }
4699
- if (provider === 'fixed_users' &&
4700
- boundedArray(entry.users, `${pointer}/bindings/${code}/users`, 200)
4701
- .length === 0) {
4702
- fail('NATIVE_WORKFLOW_BINDING_USERS_REQUIRED', `${pointer}/bindings/${code}/users`);
4898
+ if (provider === 'fixed_users') {
4899
+ if (!Array.isArray(entry.users))
4900
+ fail('NATIVE_WORKFLOW_BINDING_USERS_REQUIRED', `${pointer}/bindings/${code}/users`);
4901
+ boundedArray(entry.users, `${pointer}/bindings/${code}/users`, 200);
4703
4902
  }
4704
4903
  if (['input_users', 'form_field_users'].includes(provider)) {
4705
4904
  requiredString(entry.inputPath, `${pointer}/bindings/${code}/inputPath`, 255);
@@ -4723,6 +4922,13 @@ function validateWorkflowBinding(binding, pointer) {
4723
4922
  function validateWorkflowDefinitionBinding(definition, binding, pointer) {
4724
4923
  equal(binding.workflowCode, definition.code, `${pointer}/workflowCode`);
4725
4924
  const entries = object(binding.bindings, `${pointer}/bindings`);
4925
+ for (const [code, entry] of Object.entries(entries)) {
4926
+ if (entry.provider === 'fixed_users' && !entry.users.length && !workflowBindingAllowsEmptyUsers(definition, code))
4927
+ fail('NATIVE_WORKFLOW_BINDING_USERS_REQUIRED', `${pointer}/bindings/${code}/users`);
4928
+ }
4929
+ const emptyPolicyErrors = validateWorkflowApprovalEmptyPolicy(definition, binding);
4930
+ if (emptyPolicyErrors.length)
4931
+ fail(emptyPolicyErrors[0], pointer);
4726
4932
  const ccErrors = validateWorkflowAutomaticCc(definition, binding);
4727
4933
  if (ccErrors.length)
4728
4934
  fail(ccErrors[0], pointer);
@@ -4817,6 +5023,10 @@ function validateResource(raw, pointer, appCode) {
4817
5023
  'invariants',
4818
5024
  'decimalReservationLifecycle',
4819
5025
  'uniqueKeys',
5026
+ 'workflowHistory',
5027
+ 'recordPrint',
5028
+ 'recordComments',
5029
+ 'recordDeletion',
4820
5030
  'capabilities',
4821
5031
  'dataPolicyCode',
4822
5032
  'fieldPolicies',
@@ -4839,11 +5049,45 @@ function validateResource(raw, pointer, appCode) {
4839
5049
  validateNativeDataResourceSurfaceV2(resource.surface, fields, `${pointer}/surface`);
4840
5050
  }
4841
5051
  catch (error) {
4842
- if (error instanceof NativeDataFieldContractV2Error || error instanceof NativeDecimalLifecycleContractError || error instanceof NativeUniqueKeyContractError) {
5052
+ if (error instanceof NativeDataFieldContractV2Error || error instanceof UserCandidateContractError || error instanceof NativeDecimalLifecycleContractError || error instanceof NativeUniqueKeyContractError) {
4843
5053
  fail(error.code, error.pointer);
4844
5054
  }
4845
5055
  throw error;
4846
5056
  }
5057
+ if (resource.recordComments !== undefined) {
5058
+ const policy = object(resource.recordComments, `${pointer}/recordComments`);
5059
+ exactKeys(policy, ['read', 'create'], `${pointer}/recordComments`);
5060
+ for (const mode of ['read', 'create']) {
5061
+ const list = policy[mode];
5062
+ if (!Array.isArray(list) || list.length > 20 ||
5063
+ list.some(value => typeof value !== 'string' || !value.trim()) || new Set(list).size !== list.length)
5064
+ fail('NATIVE_RECORD_COMMENTS_POLICY_INVALID', `${pointer}/recordComments/${mode}`);
5065
+ }
5066
+ }
5067
+ if (resource.recordDeletion !== undefined) {
5068
+ const policy = object(resource.recordDeletion, `${pointer}/recordDeletion`);
5069
+ exactKeys(policy, ['delete'], `${pointer}/recordDeletion`);
5070
+ const remove = policy.delete;
5071
+ if (!Array.isArray(remove) || remove.length > 20 ||
5072
+ remove.some(value => typeof value !== 'string' || !value.trim()) || new Set(remove).size !== remove.length)
5073
+ fail('NATIVE_RECORD_DELETION_DELETE_INVALID', `${pointer}/recordDeletion/delete`);
5074
+ }
5075
+ if (resource.recordPrint !== undefined) {
5076
+ const policy = object(resource.recordPrint, `${pointer}/recordPrint`);
5077
+ exactKeys(policy, ['read'], `${pointer}/recordPrint`);
5078
+ const read = policy.read;
5079
+ if (!Array.isArray(read) || read.length > 20 ||
5080
+ read.some(value => typeof value !== 'string' || !value.trim()) || new Set(read).size !== read.length)
5081
+ fail('NATIVE_RECORD_PRINT_READ_INVALID', `${pointer}/recordPrint/read`);
5082
+ }
5083
+ if (resource.workflowHistory !== undefined) {
5084
+ const policy = object(resource.workflowHistory, `${pointer}/workflowHistory`);
5085
+ exactKeys(policy, ['read'], `${pointer}/workflowHistory`);
5086
+ const read = policy.read;
5087
+ if (!Array.isArray(read) || read.length > 20 ||
5088
+ read.some(value => typeof value !== 'string' || !value.trim()) || new Set(read).size !== read.length)
5089
+ fail('NATIVE_WORKFLOW_HISTORY_READ_INVALID', `${pointer}/workflowHistory/read`);
5090
+ }
4847
5091
  const fieldCodes = new Set(fields.map(field => field.code));
4848
5092
  const capabilities = object(resource.capabilities, `${pointer}/capabilities`);
4849
5093
  exactKeys(capabilities, ['read', 'create', 'update', 'delete'], `${pointer}/capabilities`);