openxiangda-contracts 2.47.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 (102) hide show
  1. package/dist/browser.d.ts +7 -0
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +7 -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 +10 -0
  9. package/dist/data-field-validation.js.map +1 -1
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +7 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/native-compiler/compiler.d.ts +1 -1
  15. package/dist/native-compiler/compiler.d.ts.map +1 -1
  16. package/dist/native-compiler/compiler.js +243 -18
  17. package/dist/native-compiler/compiler.js.map +1 -1
  18. package/dist/native-compiler/data-business-command.d.ts +42 -0
  19. package/dist/native-compiler/data-business-command.d.ts.map +1 -0
  20. package/dist/native-compiler/data-business-command.js +88 -0
  21. package/dist/native-compiler/data-business-command.js.map +1 -0
  22. package/dist/native-compiler/data-field.d.ts +2 -0
  23. package/dist/native-compiler/data-field.d.ts.map +1 -1
  24. package/dist/native-compiler/data-field.js +4 -0
  25. package/dist/native-compiler/data-field.js.map +1 -1
  26. package/dist/native-compiler/data-surface.d.ts +1 -1
  27. package/dist/native-compiler/data-surface.d.ts.map +1 -1
  28. package/dist/native-compiler/data-surface.js +2 -0
  29. package/dist/native-compiler/data-surface.js.map +1 -1
  30. package/dist/native-compiler/index.cjs +3 -3
  31. package/dist/native-compiler/index.d.ts +9 -0
  32. package/dist/native-compiler/index.d.ts.map +1 -1
  33. package/dist/native-compiler/index.js +3 -3
  34. package/dist/native-compiler/managed-concurrency.js +2 -2
  35. package/dist/native-compiler/managed-concurrency.js.map +1 -1
  36. package/dist/native-compiler/resource-view.d.ts +4 -0
  37. package/dist/native-compiler/resource-view.d.ts.map +1 -0
  38. package/dist/native-compiler/resource-view.js +20 -0
  39. package/dist/native-compiler/resource-view.js.map +1 -0
  40. package/dist/native-compiler/unique-keys.d.ts +21 -0
  41. package/dist/native-compiler/unique-keys.d.ts.map +1 -1
  42. package/dist/native-compiler/unique-keys.js +17 -3
  43. package/dist/native-compiler/unique-keys.js.map +1 -1
  44. package/dist/native-compiler/user-candidates.d.ts +72 -0
  45. package/dist/native-compiler/user-candidates.d.ts.map +1 -0
  46. package/dist/native-compiler/user-candidates.js +184 -0
  47. package/dist/native-compiler/user-candidates.js.map +1 -0
  48. package/dist/native-compiler/workflow-approval-empty.d.ts +28 -0
  49. package/dist/native-compiler/workflow-approval-empty.d.ts.map +1 -0
  50. package/dist/native-compiler/workflow-approval-empty.js +34 -0
  51. package/dist/native-compiler/workflow-approval-empty.js.map +1 -0
  52. package/dist/native-compiler/workflow-business-command.d.ts +108 -0
  53. package/dist/native-compiler/workflow-business-command.d.ts.map +1 -0
  54. package/dist/native-compiler/workflow-business-command.js +58 -0
  55. package/dist/native-compiler/workflow-business-command.js.map +1 -0
  56. package/dist/native-compiler/workflow-graph.d.ts +8 -0
  57. package/dist/native-compiler/workflow-graph.d.ts.map +1 -1
  58. package/dist/native-compiler/workflow-graph.js +1 -0
  59. package/dist/native-compiler/workflow-graph.js.map +1 -1
  60. package/dist/native-compiler/workflow-initiator-approval.d.ts +28 -0
  61. package/dist/native-compiler/workflow-initiator-approval.d.ts.map +1 -0
  62. package/dist/native-compiler/workflow-initiator-approval.js +30 -0
  63. package/dist/native-compiler/workflow-initiator-approval.js.map +1 -0
  64. package/dist/native-compiler/workflow-instance-policy.d.ts +2 -0
  65. package/dist/native-compiler/workflow-instance-policy.d.ts.map +1 -1
  66. package/dist/native-compiler/workflow-instance-policy.js +9 -4
  67. package/dist/native-compiler/workflow-instance-policy.js.map +1 -1
  68. package/dist/native-compiler/workflow-launch-preflight.d.ts +22 -0
  69. package/dist/native-compiler/workflow-launch-preflight.d.ts.map +1 -0
  70. package/dist/native-compiler/workflow-launch-preflight.js +30 -0
  71. package/dist/native-compiler/workflow-launch-preflight.js.map +1 -0
  72. package/dist/native-compiler/workflow-node-administration.d.ts +6 -0
  73. package/dist/native-compiler/workflow-node-administration.d.ts.map +1 -1
  74. package/dist/native-compiler/workflow-node-administration.js +9 -2
  75. package/dist/native-compiler/workflow-node-administration.js.map +1 -1
  76. package/dist/native-compiler/workflow-rejection-notification.d.ts +31 -0
  77. package/dist/native-compiler/workflow-rejection-notification.d.ts.map +1 -0
  78. package/dist/native-compiler/workflow-rejection-notification.js +47 -0
  79. package/dist/native-compiler/workflow-rejection-notification.js.map +1 -0
  80. package/dist/native-version.d.ts +16 -1
  81. package/dist/native-version.d.ts.map +1 -1
  82. package/dist/native-version.js +16 -1
  83. package/dist/native-version.js.map +1 -1
  84. package/dist/native.d.ts +15 -2
  85. package/dist/native.d.ts.map +1 -1
  86. package/dist/native.js.map +1 -1
  87. package/dist/schemas.d.ts +3028 -194
  88. package/dist/schemas.d.ts.map +1 -1
  89. package/dist/schemas.js +244 -2
  90. package/dist/schemas.js.map +1 -1
  91. package/dist/surface.d.ts +3 -2
  92. package/dist/surface.d.ts.map +1 -1
  93. package/dist/surface.js +1 -19
  94. package/dist/surface.js.map +1 -1
  95. package/dist/types.d.ts +286 -8
  96. package/dist/types.d.ts.map +1 -1
  97. package/dist/types.js +4 -0
  98. package/dist/types.js.map +1 -1
  99. package/dist/validation.d.ts.map +1 -1
  100. package/dist/validation.js +35 -2
  101. package/dist/validation.js.map +1 -1
  102. 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,7 +310,8 @@ 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
316
  const hasTaskFile = (item, types, nestedOnly = false) => {
305
317
  const fields = resources.find(resource => resource.code === item.definition.subject?.resourceCode)?.schema.fields || [];
@@ -372,6 +384,12 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
372
384
  declaration: resources.filter(resource => resource.decimalReservationLifecycle)
373
385
  .map(resource => ({ code: resource.code, decimalReservationLifecycle: resource.decimalReservationLifecycle })) }]
374
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
+ : []),
375
393
  ...(operations.some(operation => operation.platformAccess?.decimalReservation) ||
376
394
  config.events.subscriptions.some((subscription) => subscription.platformAccess?.decimalReservation)
377
395
  ? [{
@@ -416,6 +434,14 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
416
434
  },
417
435
  ]
418
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 }] : []),
419
445
  ...(resources.some(hasDataAuditReadPolicy)
420
446
  ? [{ code: 'data.audit-read-access', declaration: dataUsage }]
421
447
  : []),
@@ -448,6 +474,13 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
448
474
  ]
449
475
  : []),
450
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
+ }] : []),
451
484
  ...(usesManagedFiles
452
485
  ? [
453
486
  {
@@ -490,6 +523,9 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
490
523
  },
491
524
  ]
492
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
+ : []),
493
529
  ...(config.workflows.activations.length
494
530
  ? [
495
531
  {
@@ -508,12 +544,37 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
508
544
  declaration: config.workflows.definitions.filter((item) => item.definition.instanceCommands !== undefined),
509
545
  }]
510
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)) }] : []),
511
566
  ...(namedInputSourceDefinitions.length
512
567
  ? [{ code: 'workflow.named-input-sources', declaration: namedInputSourceDefinitions }]
513
568
  : []),
514
569
  ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.administration !== undefined || node.operationPolicy !== undefined))
515
570
  ? [{ code: 'workflow.node-administration', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.administration !== undefined || node.operationPolicy !== undefined)) }]
516
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
+ : []),
517
578
  ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'cc'))
518
579
  ? [{ code: 'workflow.automatic-cc', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'cc')) }]
519
580
  : []),
@@ -556,6 +617,8 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
556
617
  {
557
618
  code: 'notification-hub-v2',
558
619
  declaration: {
620
+ ...(config.workflows.definitions.some((item) => item.definition.rejectionNotification !== undefined)
621
+ ? { rejectionNotifications: config.workflows.definitions.filter((item) => item.definition.rejectionNotification !== undefined) } : {}),
559
622
  operations: operations
560
623
  .filter(operation => operation.platformAccess?.notification)
561
624
  .map(operation => ({
@@ -592,7 +655,9 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
592
655
  return usages
593
656
  .map(({ code, declaration }) => ({
594
657
  code,
595
- 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],
596
661
  usageDigest: `sha256:${sha256Canonical(declaration)}`,
597
662
  }))
598
663
  .sort((left, right) => left.code === right.code ? 0 : left.code < right.code ? -1 : 1);
@@ -1007,7 +1072,7 @@ function compileExpectedContract(config, configDigest, generatorVersion) {
1007
1072
  field: trigger.field,
1008
1073
  })),
1009
1074
  ...(config.workflows.activations.length
1010
- ? 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 => ({
1011
1076
  code: `workflow:${eventType}`,
1012
1077
  source: 'workflow',
1013
1078
  eventType,
@@ -1212,9 +1277,12 @@ function compileCapabilities(config) {
1212
1277
  };
1213
1278
  catalog.set(code, capability);
1214
1279
  }
1215
- for (const [resourceIndex, raw] of config.data.resources.entries()) {
1216
- const pointer = `/config/data/resources/${resourceIndex}`;
1217
- 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) {
1218
1286
  const operationNames = {
1219
1287
  read: `读取${resource.name}`,
1220
1288
  create: `新建${resource.name}`,
@@ -1233,6 +1301,10 @@ function compileCapabilities(config) {
1233
1301
  source: 'data',
1234
1302
  });
1235
1303
  }
1304
+ }
1305
+ const definedCapabilities = new Set(catalog.keys());
1306
+ const fieldOwners = new Map();
1307
+ for (const { pointer, resource } of resources) {
1236
1308
  for (const [fieldCode, rawPolicy] of Object.entries(resource.fieldPolicies)) {
1237
1309
  if (isDataAuditMetadataField(fieldCode))
1238
1310
  continue;
@@ -1242,6 +1314,13 @@ function compileCapabilities(config) {
1242
1314
  continue;
1243
1315
  for (const code of uniqueStrings(policy[operation], `${pointer}/fieldPolicies/${fieldCode}/${operation}`, 2000)) {
1244
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);
1245
1324
  if (catalog.has(code))
1246
1325
  continue;
1247
1326
  catalog.set(code, {
@@ -1530,6 +1609,15 @@ function compileOperations(config) {
1530
1609
  const code = stableCode(operation.code, `${pointer}/code`);
1531
1610
  validateBackendOperationAi(operation.ai, pointer, code, method, declaredResourceCodes);
1532
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
+ }
1533
1621
  if (platformAccess?.decimalReservation) {
1534
1622
  const reservation = platformAccess.decimalReservation;
1535
1623
  const resourceCode = String(reservation.resourceCode);
@@ -1726,11 +1814,33 @@ function validateOperationPlatformAccess(value, pointer, declaredResources, decl
1726
1814
  if (value === undefined)
1727
1815
  return null;
1728
1816
  const access = object(value, pointer);
1729
- 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);
1730
1818
  if (Object.keys(access).length === 0) {
1731
1819
  fail('NATIVE_OPERATION_PLATFORM_ACCESS_EMPTY', pointer);
1732
1820
  }
1733
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
+ }
1734
1844
  if (access.roleAssertions !== undefined) {
1735
1845
  const assertions = object(access.roleAssertions, `${pointer}/roleAssertions`);
1736
1846
  exactKeys(assertions, ['roleCodes'], `${pointer}/roleAssertions`);
@@ -1754,8 +1864,9 @@ function validateOperationPlatformAccess(value, pointer, declaredResources, decl
1754
1864
  'employeeNumber',
1755
1865
  'primaryDepartment',
1756
1866
  'departments',
1867
+ ...(directory.mode === 'current-initiator' ? ['phone'] : []),
1757
1868
  ]);
1758
- const fields = uniqueStrings(directory.fields, `${pointer}/directory/fields`, 4);
1869
+ const fields = uniqueStrings(directory.fields, `${pointer}/directory/fields`, allowed.size);
1759
1870
  if (!fields.length || fields.some(field => !allowed.has(field))) {
1760
1871
  fail('NATIVE_OPERATION_DIRECTORY_FIELDS_INVALID', `${pointer}/directory/fields`);
1761
1872
  }
@@ -1848,12 +1959,16 @@ function validateOperationPlatformAccess(value, pointer, declaredResources, decl
1848
1959
  }
1849
1960
  if (access.workflow !== undefined) {
1850
1961
  const workflow = object(access.workflow, `${pointer}/workflow`);
1851
- exactKeys(workflow, ['codes'], `${pointer}/workflow`);
1962
+ exactKeys(workflow, ['codes'], `${pointer}/workflow`, ['businessCommands']);
1852
1963
  const codes = uniqueStrings(workflow.codes, `${pointer}/workflow/codes`, 16);
1853
1964
  if (!codes.length || codes.some(code => !declaredWorkflowCodes.has(code))) {
1854
1965
  fail('NATIVE_OPERATION_WORKFLOW_CODE_INVALID', `${pointer}/workflow/codes`);
1855
1966
  }
1856
- 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) } : {}) };
1857
1972
  }
1858
1973
  if (access.decimalReservation !== undefined) {
1859
1974
  const reservationPointer = `${pointer}/decimalReservation`;
@@ -3615,6 +3730,40 @@ function validateAuthorizationReferences(config, capabilities) {
3615
3730
  config.data.resources.forEach((rawResource, index) => {
3616
3731
  const resourcePointer = `/config/data/resources/${index}`;
3617
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
+ }
3618
3767
  for (const [fieldCode, rawPolicy] of Object.entries(resource.fieldPolicies || {})) {
3619
3768
  if (!isDataAuditMetadataField(fieldCode))
3620
3769
  continue;
@@ -3633,6 +3782,9 @@ function validateAuthorizationReferences(config, capabilities) {
3633
3782
  return;
3634
3783
  const resourceCapabilities = object(resource.capabilities, `${resourcePointer}/capabilities`);
3635
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;
3636
3788
  forbiddenNativeMutationCapabilities.set(String(resourceCapabilities[operation]), operation);
3637
3789
  }
3638
3790
  });
@@ -4101,9 +4253,10 @@ function validateDataPolicyReferences(config) {
4101
4253
  function validateDataFieldReferences(config) {
4102
4254
  try {
4103
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`));
4104
4257
  }
4105
4258
  catch (error) {
4106
- if (error instanceof NativeDataFieldContractV2Error) {
4259
+ if (error instanceof NativeDataFieldContractV2Error || error instanceof UserCandidateContractError) {
4107
4260
  fail(error.code, error.pointer);
4108
4261
  }
4109
4262
  throw error;
@@ -4326,6 +4479,12 @@ function validateWorkflowReferences(config) {
4326
4479
  }
4327
4480
  definitions.add(key);
4328
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`);
4329
4488
  validateWorkflowSubject(definition.subject, resources, `${pointer}/definition/subject`);
4330
4489
  const taskPageErrors = validateWorkflowTaskPages(definition, new Map((resources.get(definition.subject.resourceCode)?.schema.fields || [])
4331
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) }]))])));
@@ -4385,6 +4544,9 @@ function validateWorkflowReferences(config) {
4385
4544
  }
4386
4545
  const definition = definitionValues.get(definitionKey);
4387
4546
  const binding = bindingValues.get(bindingKey);
4547
+ const preflightErrors = validateWorkflowLaunchPreflight(definition, binding);
4548
+ if (preflightErrors.length)
4549
+ fail(preflightErrors[0], pointer);
4388
4550
  const administrationErrors = validateWorkflowAdministration(definition, binding);
4389
4551
  if (administrationErrors.length)
4390
4552
  fail(administrationErrors[0], pointer);
@@ -4394,6 +4556,15 @@ function validateWorkflowReferences(config) {
4394
4556
  fail('NATIVE_WORKFLOW_COMMAND_DEACTIVATION_POLICY_MISMATCH', `${pointer}/acceptedCommandDeactivationPolicy`);
4395
4557
  }
4396
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
+ }
4397
4568
  activeBindings.set(code, binding);
4398
4569
  }
4399
4570
  const routingErrors = validateWorkflowAssignmentRoutingBindings([...activeBindings.values()]);
@@ -4552,7 +4723,7 @@ function validateWorkflowDefinition(definition, pointer) {
4552
4723
  'startAt',
4553
4724
  'inputSchema',
4554
4725
  'nodes',
4555
- ], pointer, ['organizationContext', 'instanceCommands', 'readability', 'taskPages']);
4726
+ ], pointer, ['organizationContext', 'instanceCommands', 'readability', 'taskPages', 'rejectionNotification', 'commandHandlers', 'launchPreflight']);
4556
4727
  equal(definition.schemaVersion, 'openxiangda.workflow-definition/v2', `${pointer}/schemaVersion`);
4557
4728
  workflowCodeValue(definition.code, `${pointer}/code`);
4558
4729
  requiredString(definition.title, `${pointer}/title`, 255);
@@ -4571,6 +4742,15 @@ function validateWorkflowDefinition(definition, pointer) {
4571
4742
  const administrationErrors = validateWorkflowAdministration(definition);
4572
4743
  if (administrationErrors.length)
4573
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`);
4574
4754
  const ccErrors = validateWorkflowAutomaticCc(definition);
4575
4755
  if (ccErrors.length)
4576
4756
  fail(ccErrors[0], `${pointer}/nodes`);
@@ -4715,10 +4895,10 @@ function validateWorkflowBinding(binding, pointer) {
4715
4895
  ].includes(provider)) {
4716
4896
  fail('NATIVE_WORKFLOW_BINDING_PROVIDER_INVALID', `${pointer}/bindings/${code}/provider`);
4717
4897
  }
4718
- if (provider === 'fixed_users' &&
4719
- boundedArray(entry.users, `${pointer}/bindings/${code}/users`, 200)
4720
- .length === 0) {
4721
- 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);
4722
4902
  }
4723
4903
  if (['input_users', 'form_field_users'].includes(provider)) {
4724
4904
  requiredString(entry.inputPath, `${pointer}/bindings/${code}/inputPath`, 255);
@@ -4742,6 +4922,13 @@ function validateWorkflowBinding(binding, pointer) {
4742
4922
  function validateWorkflowDefinitionBinding(definition, binding, pointer) {
4743
4923
  equal(binding.workflowCode, definition.code, `${pointer}/workflowCode`);
4744
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);
4745
4932
  const ccErrors = validateWorkflowAutomaticCc(definition, binding);
4746
4933
  if (ccErrors.length)
4747
4934
  fail(ccErrors[0], pointer);
@@ -4836,6 +5023,10 @@ function validateResource(raw, pointer, appCode) {
4836
5023
  'invariants',
4837
5024
  'decimalReservationLifecycle',
4838
5025
  'uniqueKeys',
5026
+ 'workflowHistory',
5027
+ 'recordPrint',
5028
+ 'recordComments',
5029
+ 'recordDeletion',
4839
5030
  'capabilities',
4840
5031
  'dataPolicyCode',
4841
5032
  'fieldPolicies',
@@ -4858,11 +5049,45 @@ function validateResource(raw, pointer, appCode) {
4858
5049
  validateNativeDataResourceSurfaceV2(resource.surface, fields, `${pointer}/surface`);
4859
5050
  }
4860
5051
  catch (error) {
4861
- if (error instanceof NativeDataFieldContractV2Error || error instanceof NativeDecimalLifecycleContractError || error instanceof NativeUniqueKeyContractError) {
5052
+ if (error instanceof NativeDataFieldContractV2Error || error instanceof UserCandidateContractError || error instanceof NativeDecimalLifecycleContractError || error instanceof NativeUniqueKeyContractError) {
4862
5053
  fail(error.code, error.pointer);
4863
5054
  }
4864
5055
  throw error;
4865
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
+ }
4866
5091
  const fieldCodes = new Set(fields.map(field => field.code));
4867
5092
  const capabilities = object(resource.capabilities, `${pointer}/capabilities`);
4868
5093
  exactKeys(capabilities, ['read', 'create', 'update', 'delete'], `${pointer}/capabilities`);