openxiangda-contracts 2.47.0 → 2.69.1

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 (177) hide show
  1. package/dist/ai.d.ts +1 -1
  2. package/dist/ai.d.ts.map +1 -1
  3. package/dist/ai.js +4 -3
  4. package/dist/ai.js.map +1 -1
  5. package/dist/browser.d.ts +13 -0
  6. package/dist/browser.d.ts.map +1 -1
  7. package/dist/browser.js +13 -0
  8. package/dist/browser.js.map +1 -1
  9. package/dist/concurrency.d.ts +3 -3
  10. package/dist/concurrency.d.ts.map +1 -1
  11. package/dist/data-field-validation.d.ts.map +1 -1
  12. package/dist/data-field-validation.js +17 -2
  13. package/dist/data-field-validation.js.map +1 -1
  14. package/dist/field-value-schemas.d.ts +1 -1
  15. package/dist/index.d.ts +17 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +17 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/native-compiler/actor-authority.d.ts +11 -0
  20. package/dist/native-compiler/actor-authority.d.ts.map +1 -0
  21. package/dist/native-compiler/actor-authority.js +56 -0
  22. package/dist/native-compiler/actor-authority.js.map +1 -0
  23. package/dist/native-compiler/artifact-capacity.d.ts +12 -0
  24. package/dist/native-compiler/artifact-capacity.d.ts.map +1 -0
  25. package/dist/native-compiler/artifact-capacity.js +34 -0
  26. package/dist/native-compiler/artifact-capacity.js.map +1 -0
  27. package/dist/native-compiler/authenticated-public-read.d.ts +18 -0
  28. package/dist/native-compiler/authenticated-public-read.d.ts.map +1 -0
  29. package/dist/native-compiler/authenticated-public-read.js +79 -0
  30. package/dist/native-compiler/authenticated-public-read.js.map +1 -0
  31. package/dist/native-compiler/compiler.d.ts +3 -22
  32. package/dist/native-compiler/compiler.d.ts.map +1 -1
  33. package/dist/native-compiler/compiler.js +441 -59
  34. package/dist/native-compiler/compiler.js.map +1 -1
  35. package/dist/native-compiler/data-business-command.d.ts +44 -0
  36. package/dist/native-compiler/data-business-command.d.ts.map +1 -0
  37. package/dist/native-compiler/data-business-command.js +88 -0
  38. package/dist/native-compiler/data-business-command.js.map +1 -0
  39. package/dist/native-compiler/data-capacity.d.ts +14 -3
  40. package/dist/native-compiler/data-capacity.d.ts.map +1 -1
  41. package/dist/native-compiler/data-capacity.js +11 -3
  42. package/dist/native-compiler/data-capacity.js.map +1 -1
  43. package/dist/native-compiler/data-field.d.ts +24 -0
  44. package/dist/native-compiler/data-field.d.ts.map +1 -1
  45. package/dist/native-compiler/data-field.js +23 -2
  46. package/dist/native-compiler/data-field.js.map +1 -1
  47. package/dist/native-compiler/data-surface.d.ts +1 -1
  48. package/dist/native-compiler/data-surface.d.ts.map +1 -1
  49. package/dist/native-compiler/data-surface.js +2 -0
  50. package/dist/native-compiler/data-surface.js.map +1 -1
  51. package/dist/native-compiler/date-event-projection.d.ts +13 -0
  52. package/dist/native-compiler/date-event-projection.d.ts.map +1 -0
  53. package/dist/native-compiler/date-event-projection.js +71 -0
  54. package/dist/native-compiler/date-event-projection.js.map +1 -0
  55. package/dist/native-compiler/declaration-capacity.d.ts +60 -0
  56. package/dist/native-compiler/declaration-capacity.d.ts.map +1 -0
  57. package/dist/native-compiler/declaration-capacity.js +32 -0
  58. package/dist/native-compiler/declaration-capacity.js.map +1 -0
  59. package/dist/native-compiler/development-backend.d.ts +84 -0
  60. package/dist/native-compiler/development-backend.d.ts.map +1 -0
  61. package/dist/native-compiler/development-backend.js +77 -0
  62. package/dist/native-compiler/development-backend.js.map +1 -0
  63. package/dist/native-compiler/development-configuration.d.ts +65 -0
  64. package/dist/native-compiler/development-configuration.d.ts.map +1 -0
  65. package/dist/native-compiler/development-configuration.js +143 -0
  66. package/dist/native-compiler/development-configuration.js.map +1 -0
  67. package/dist/native-compiler/index.cjs +6 -6
  68. package/dist/native-compiler/index.d.ts +22 -0
  69. package/dist/native-compiler/index.d.ts.map +1 -1
  70. package/dist/native-compiler/index.js +6 -6
  71. package/dist/native-compiler/managed-concurrency.js +2 -2
  72. package/dist/native-compiler/managed-concurrency.js.map +1 -1
  73. package/dist/native-compiler/record-set-guard.d.ts +5 -0
  74. package/dist/native-compiler/record-set-guard.d.ts.map +1 -0
  75. package/dist/native-compiler/record-set-guard.js +27 -0
  76. package/dist/native-compiler/record-set-guard.js.map +1 -0
  77. package/dist/native-compiler/resource-view.d.ts +4 -0
  78. package/dist/native-compiler/resource-view.d.ts.map +1 -0
  79. package/dist/native-compiler/resource-view.js +20 -0
  80. package/dist/native-compiler/resource-view.js.map +1 -0
  81. package/dist/native-compiler/unique-keys.d.ts +21 -0
  82. package/dist/native-compiler/unique-keys.d.ts.map +1 -1
  83. package/dist/native-compiler/unique-keys.js +17 -3
  84. package/dist/native-compiler/unique-keys.js.map +1 -1
  85. package/dist/native-compiler/user-candidates.d.ts +80 -0
  86. package/dist/native-compiler/user-candidates.d.ts.map +1 -0
  87. package/dist/native-compiler/user-candidates.js +197 -0
  88. package/dist/native-compiler/user-candidates.js.map +1 -0
  89. package/dist/native-compiler/workflow-approval-empty.d.ts +28 -0
  90. package/dist/native-compiler/workflow-approval-empty.d.ts.map +1 -0
  91. package/dist/native-compiler/workflow-approval-empty.js +34 -0
  92. package/dist/native-compiler/workflow-approval-empty.js.map +1 -0
  93. package/dist/native-compiler/workflow-approved-delegation.d.ts +39 -0
  94. package/dist/native-compiler/workflow-approved-delegation.d.ts.map +1 -0
  95. package/dist/native-compiler/workflow-approved-delegation.js +94 -0
  96. package/dist/native-compiler/workflow-approved-delegation.js.map +1 -0
  97. package/dist/native-compiler/workflow-business-command.d.ts +108 -0
  98. package/dist/native-compiler/workflow-business-command.d.ts.map +1 -0
  99. package/dist/native-compiler/workflow-business-command.js +62 -0
  100. package/dist/native-compiler/workflow-business-command.js.map +1 -0
  101. package/dist/native-compiler/workflow-completion-deadline.d.ts +41 -0
  102. package/dist/native-compiler/workflow-completion-deadline.d.ts.map +1 -0
  103. package/dist/native-compiler/workflow-completion-deadline.js +40 -0
  104. package/dist/native-compiler/workflow-completion-deadline.js.map +1 -0
  105. package/dist/native-compiler/workflow-correction.d.ts +16 -0
  106. package/dist/native-compiler/workflow-correction.d.ts.map +1 -0
  107. package/dist/native-compiler/workflow-correction.js +42 -0
  108. package/dist/native-compiler/workflow-correction.js.map +1 -0
  109. package/dist/native-compiler/workflow-delegation-administration.d.ts +73 -0
  110. package/dist/native-compiler/workflow-delegation-administration.d.ts.map +1 -1
  111. package/dist/native-compiler/workflow-delegation-administration.js +6 -4
  112. package/dist/native-compiler/workflow-delegation-administration.js.map +1 -1
  113. package/dist/native-compiler/workflow-graph.d.ts +21 -2
  114. package/dist/native-compiler/workflow-graph.d.ts.map +1 -1
  115. package/dist/native-compiler/workflow-graph.js +20 -3
  116. package/dist/native-compiler/workflow-graph.js.map +1 -1
  117. package/dist/native-compiler/workflow-initiator-approval.d.ts +32 -0
  118. package/dist/native-compiler/workflow-initiator-approval.d.ts.map +1 -0
  119. package/dist/native-compiler/workflow-initiator-approval.js +45 -0
  120. package/dist/native-compiler/workflow-initiator-approval.js.map +1 -0
  121. package/dist/native-compiler/workflow-instance-policy.d.ts +2 -0
  122. package/dist/native-compiler/workflow-instance-policy.d.ts.map +1 -1
  123. package/dist/native-compiler/workflow-instance-policy.js +9 -4
  124. package/dist/native-compiler/workflow-instance-policy.js.map +1 -1
  125. package/dist/native-compiler/workflow-launch-preflight.d.ts +22 -0
  126. package/dist/native-compiler/workflow-launch-preflight.d.ts.map +1 -0
  127. package/dist/native-compiler/workflow-launch-preflight.js +30 -0
  128. package/dist/native-compiler/workflow-launch-preflight.js.map +1 -0
  129. package/dist/native-compiler/workflow-native-stage.d.ts +69 -0
  130. package/dist/native-compiler/workflow-native-stage.d.ts.map +1 -0
  131. package/dist/native-compiler/workflow-native-stage.js +42 -0
  132. package/dist/native-compiler/workflow-native-stage.js.map +1 -0
  133. package/dist/native-compiler/workflow-node-administration.d.ts +11 -1
  134. package/dist/native-compiler/workflow-node-administration.d.ts.map +1 -1
  135. package/dist/native-compiler/workflow-node-administration.js +25 -5
  136. package/dist/native-compiler/workflow-node-administration.js.map +1 -1
  137. package/dist/native-compiler/workflow-owned-subject.d.ts +60 -0
  138. package/dist/native-compiler/workflow-owned-subject.d.ts.map +1 -0
  139. package/dist/native-compiler/workflow-owned-subject.js +69 -0
  140. package/dist/native-compiler/workflow-owned-subject.js.map +1 -0
  141. package/dist/native-compiler/workflow-rejection-notification.d.ts +31 -0
  142. package/dist/native-compiler/workflow-rejection-notification.d.ts.map +1 -0
  143. package/dist/native-compiler/workflow-rejection-notification.js +47 -0
  144. package/dist/native-compiler/workflow-rejection-notification.js.map +1 -0
  145. package/dist/native-compiler/workflow-role-union.d.ts +31 -0
  146. package/dist/native-compiler/workflow-role-union.d.ts.map +1 -0
  147. package/dist/native-compiler/workflow-role-union.js +38 -0
  148. package/dist/native-compiler/workflow-role-union.js.map +1 -0
  149. package/dist/native-compiler/workflow-task-page.d.ts +10 -3
  150. package/dist/native-compiler/workflow-task-page.d.ts.map +1 -1
  151. package/dist/native-compiler/workflow-task-page.js +5 -3
  152. package/dist/native-compiler/workflow-task-page.js.map +1 -1
  153. package/dist/native-version.d.ts +36 -1
  154. package/dist/native-version.d.ts.map +1 -1
  155. package/dist/native-version.js +36 -1
  156. package/dist/native-version.js.map +1 -1
  157. package/dist/native.d.ts +24 -2
  158. package/dist/native.d.ts.map +1 -1
  159. package/dist/native.js.map +1 -1
  160. package/dist/references.d.ts +2 -0
  161. package/dist/references.d.ts.map +1 -1
  162. package/dist/schemas.d.ts +10118 -5656
  163. package/dist/schemas.d.ts.map +1 -1
  164. package/dist/schemas.js +379 -17
  165. package/dist/schemas.js.map +1 -1
  166. package/dist/surface.d.ts +4 -2
  167. package/dist/surface.d.ts.map +1 -1
  168. package/dist/surface.js +1 -19
  169. package/dist/surface.js.map +1 -1
  170. package/dist/types.d.ts +371 -13
  171. package/dist/types.d.ts.map +1 -1
  172. package/dist/types.js +6 -2
  173. package/dist/types.js.map +1 -1
  174. package/dist/validation.d.ts.map +1 -1
  175. package/dist/validation.js +61 -2
  176. package/dist/validation.js.map +1 -1
  177. package/package.json +1 -1
@@ -1,4 +1,7 @@
1
- import { NativeUniqueKeyContractError, parseNativeUniqueKeys } from './unique-keys.js';
1
+ import { assertWorkflowNativeStagePolicy } from './workflow-native-stage.js';
2
+ import { validateAuthenticatedPublicRead, AuthenticatedPublicReadContractError } from './authenticated-public-read.js';
3
+ import { validateUserCandidateReferences, validateWorkflowUserCandidateBindings, UserCandidateContractError, requiresUserCandidateLaunchScope } from './user-candidates.js';
4
+ import { NativeUniqueKeyContractError, parseNativeUniqueKeys, nativeUniqueKeysContractVersion } from './unique-keys.js';
2
5
  import { validateManagedConcurrency, ManagedConcurrencyContractError } from './managed-concurrency.js';
3
6
  import { normalizeDecimalReservationEventDeclaration, decimalReservationEventContext } from './decimal-reservation.js';
4
7
  import { NativeDecimalLifecycleContractError, parseDecimalReservationLifecycle, validateDecimalReservationLifecycles } from './decimal-lifecycle.js';
@@ -9,13 +12,25 @@ import { validateWorkflowInstanceCommandPolicies } from './workflow-instance-pol
9
12
  import { validateWorkflowReadability } from './workflow-graph.js';
10
13
  import { validateWorkflowAdministration } from './workflow-node-administration.js';
11
14
  import { validateWorkflowAutomaticCc } from './workflow-automatic-cc.js';
15
+ import { validateWorkflowApprovalEmptyPolicy, workflowBindingAllowsEmptyUsers } from './workflow-approval-empty.js';
16
+ import { validateWorkflowInitiatorApprovalPolicy } from './workflow-initiator-approval.js';
17
+ import { validateWorkflowCompletionDeadlines } from './workflow-completion-deadline.js';
18
+ import { validateWorkflowRejectionNotification } from './workflow-rejection-notification.js';
19
+ import { validateWorkflowCommandHandlers } from './workflow-business-command.js';
20
+ import { validateWorkflowLaunchPreflight } from './workflow-launch-preflight.js';
21
+ import { validateWorkflowApprovedDelegation } from './workflow-approved-delegation.js';
22
+ import { validateWorkflowCorrections } from './workflow-correction.js';
23
+ import { dateEventProjectionFieldsV2 } from './date-event-projection.js';
12
24
  import { validateWorkflowTaskPages } from './workflow-task-page.js';
25
+ import { requiresExtendedOwnedSubtableCapacity } from './data-capacity.js';
13
26
  import { validateWorkflowBusinessSteps, compileWorkflowBusinessStepHandlers, validateWorkflowBusinessStepSubscriptions, WORKFLOW_BUSINESS_STEP_EVENT, WORKFLOW_BUSINESS_STEP_EVENTS } from './workflow-business-step.js';
14
27
  import { validateWorkflowAssignmentRoutingBindings, validateWorkflowAssignmentRoutingPolicy } from './workflow-assignment-routing.js';
28
+ import { requiresWorkflowRoleUnion, validateWorkflowRoleUnion } from './workflow-role-union.js';
29
+ import { assertWorkflowOwnedSubjectCreate, assertWorkflowOwnedSubjectCreateResources } from './workflow-owned-subject.js';
15
30
  import * as crypto from 'crypto';
16
31
  import { OPENXIANGDA_COMPILER_CONTRACT_VERSION as OPENXIANGDA_V2_COMPILER_CONTRACT_VERSION, PLATFORM_CAPABILITY_CONTRACT_VERSIONS as OPENXIANGDA_V2_PLATFORM_CAPABILITY_CONTRACT_VERSIONS, } from '../native-version.js';
17
32
  import { canonicalJson, sha256Digest as sha256Canonical } from '../canonical.js';
18
- import { NativeDataFieldContractV2Error, nativeFieldRequiresCreateInputV2, parseNativeDataFieldsV2, parseNativeDataResourceInvariantsV2, validateNativeDataResourceReferencesV2, } from './data-field.js';
33
+ import { NativeDataFieldContractV2Error, nativeFieldRequiresCreateInputV2, requiresDateTimeMinutePrecision, requiresOwnedSubtableMinimumRows, parseNativeDataFieldsV2, parseNativeDataResourceInvariantsV2, validateNativeDataResourceReferencesV2, } from './data-field.js';
19
34
  import { validateNativeDataResourceSurfaceV2 } from './data-surface.js';
20
35
  import { nativeFieldSupportsSortV2 } from './field-query-plan.js';
21
36
  import { nativeScopeSourceGrantPathSupportedV2, nativeScopeSourceSubjectPathSupportedV2, resolveNativeScopeSourceFieldPathV2, } from './scope-source-field-path.js';
@@ -86,11 +101,10 @@ const WORKFLOW_OPERATIONS = new Set([
86
101
  'admin_override',
87
102
  'retry_resolution',
88
103
  ]);
89
- const MAX_CONFIG_BYTES = 4 * 1024 * 1024;
90
- const MAX_CONTRACT_BYTES = 8 * 1024 * 1024;
91
- const MAX_DEPTH = 40;
92
- const MAX_NODES = 100000;
93
- const MAX_STRING_BYTES = 1024 * 1024;
104
+ const MAX_CONFIG_BYTES = NATIVE_ARTIFACT_CAPACITY_V2.configBytes;
105
+ const MAX_CONTRACT_BYTES = NATIVE_ARTIFACT_CAPACITY_V2.contractBytes;
106
+ const MAX_DEPTH = NATIVE_ARTIFACT_CAPACITY_V2.depth;
107
+ const MAX_STRING_BYTES = NATIVE_ARTIFACT_CAPACITY_V2.stringBytes;
94
108
  const EVENT_HANDLER_MANIFEST_SCHEMA = 'openxiangda.event-handler-manifest/v2';
95
109
  const ROUTE_MANIFEST_SCHEMA = 'openxiangda.application-route-manifest/v3';
96
110
  const EVENT_CAPTURE_FIELD_LIMIT = 64;
@@ -102,28 +116,17 @@ const EVENT_AUTHORIZATION_SYSTEM_FIELDS = new Set([
102
116
  'created_at',
103
117
  'updated_at',
104
118
  ]);
105
- export const NATIVE_CONTRACT_CAPACITY_V2 = Object.freeze({
106
- perspectives: 100,
107
- resources: 100,
108
- subjectReadSurfaces: 50,
109
- capabilities: 2000,
110
- operations: 500,
111
- eventConsumers: 100,
112
- eventProducers: 1000,
113
- eventSchemas: 100,
114
- eventTypes: 500,
115
- workflows: 100,
116
- routes: 500,
117
- adminPages: 500,
118
- adminNavigationGroups: 100,
119
- adminNavigationItems: 500,
120
- });
119
+ export { NATIVE_CONTRACT_CAPACITY_V2, requiresExtendedDeclarationCapacity, generatedCrudCapabilityOperations } from './declaration-capacity.js';
120
+ import { NATIVE_CONTRACT_CAPACITY_V2, requiresExtendedDeclarationCapacity } from './declaration-capacity.js';
121
+ export { NATIVE_ARTIFACT_CAPACITY_V2, requiresExtendedArtifactCapacity } from './artifact-capacity.js';
122
+ import { NATIVE_ARTIFACT_CAPACITY_V2, requiresExtendedArtifactCapacity } from './artifact-capacity.js';
121
123
  export const DATA_EVENT_TYPES_V2 = [
122
124
  'openxiangda.data.record.created.v2',
123
125
  'openxiangda.data.record.updated.v2',
124
126
  'openxiangda.data.record.deleted.v2',
125
127
  ];
126
128
  export const WORKFLOW_EVENT_TYPES_V2 = [
129
+ 'openxiangda.workflow.node.skipped.v2',
127
130
  WORKFLOW_BUSINESS_STEP_EVENT,
128
131
  'openxiangda.workflow.step.result_received.v2',
129
132
  'openxiangda.workflow.step.completed.v2',
@@ -190,8 +193,13 @@ export function compileNativeApplicationConfiguration(input) {
190
193
  verifyArtifactDigest(contractSource, expectedContractDigest, '/contractBytes', 'NATIVE_CONTRACT_DIGEST_MISMATCH');
191
194
  const parsedConfig = parseCanonicalArtifact(configSource, '/configBytes', 'NATIVE_CONFIG_JSON_INVALID', 'NATIVE_CONFIG_NOT_CANONICAL');
192
195
  const parsedContract = parseCanonicalArtifact(contractSource, '/contractBytes', 'NATIVE_CONTRACT_JSON_INVALID', 'NATIVE_CONTRACT_NOT_CANONICAL');
193
- inspectJsonBudget(parsedConfig, '/config');
194
- inspectJsonBudget(parsedContract, '/contracts');
196
+ // First enforce all global safety bounds before deriving an extension from
197
+ // untrusted input. The sealed configuration alone selects the larger budget.
198
+ inspectJsonBudget(parsedConfig, '/config', NATIVE_ARTIFACT_CAPACITY_V2.extendedNodes);
199
+ inspectJsonBudget(parsedContract, '/contracts', NATIVE_ARTIFACT_CAPACITY_V2.extendedNodes);
200
+ if (!requiresExtendedArtifactCapacity(parsedConfig)) {
201
+ inspectJsonBudget(parsedContract, '/contracts', NATIVE_ARTIFACT_CAPACITY_V2.legacyNodes);
202
+ }
195
203
  const config = parsedConfig;
196
204
  const contract = parsedContract;
197
205
  validateConfigurationEnvelope(config, appCode);
@@ -280,10 +288,20 @@ export function compileNativeApplicationConfiguration(input) {
280
288
  * Derives the platform capability usage closure from the normalized, sealed
281
289
  * ConfigurationBundleV3. Manifest declarations are deliberately not an input.
282
290
  */
291
+ function workflowUsesOwnedInitialFacts(definition, resources) {
292
+ const subject = definition.subject;
293
+ const resource = resources.find(value => value.code === subject?.resourceCode);
294
+ const projected = new Set(Object.values(subject?.factProjection || {}));
295
+ return Boolean(resource?.schema?.fields?.some((field) => field.type === 'subtable' && projected.has(field.code)));
296
+ }
283
297
  export function compileRequiredPlatformCapabilitiesV3(config) {
284
298
  const roles = config.authz.roles;
285
299
  const resources = config.data.resources;
286
300
  const operations = config.backend.operations;
301
+ const initiatorPhoneOperations = operations.filter(operation => operation.platformAccess?.directory?.mode === 'current-initiator' &&
302
+ operation.platformAccess.directory.fields.includes('phone'));
303
+ const initiatorPhoneCommands = (config.data.concurrency?.commands || []).filter((command) => command.execution?.directory?.mode === 'current-initiator' &&
304
+ command.execution.directory.fields.includes('phone'));
287
305
  const requiresDirectoryV2 = roles.some(role => role.capabilities.some((capability) => capability.endsWith(':directory:read'))) ||
288
306
  resources.some(resource => resource.schema.fields.some((field) => [
289
307
  'user.single',
@@ -299,7 +317,8 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
299
317
  const usesManagedFiles = operations.some(operation => operation.platformAccess?.managedFiles ||
300
318
  operation.platformAccess?.managedFileCopies) ||
301
319
  config.events.subscriptions.some((subscription) => subscription.platformAccess?.managedFileCopies);
302
- const usesNotification = operations.some(operation => operation.platformAccess?.notification) ||
320
+ const usesNotification = config.workflows.definitions.some((item) => item.definition.rejectionNotification !== undefined) ||
321
+ operations.some(operation => operation.platformAccess?.notification) ||
303
322
  config.events.subscriptions.some((subscription) => subscription.platformAccess?.notification);
304
323
  const hasTaskFile = (item, types, nestedOnly = false) => {
305
324
  const fields = resources.find(resource => resource.code === item.definition.subject?.resourceCode)?.schema.fields || [];
@@ -362,6 +381,17 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
362
381
  },
363
382
  };
364
383
  const usages = [
384
+ ...(requiresExtendedArtifactCapacity(config)
385
+ ? [{ code: 'application.extended-artifact-capacity',
386
+ declaration: NATIVE_ARTIFACT_CAPACITY_V2 }] : []),
387
+ ...(requiresExtendedDeclarationCapacity(config)
388
+ ? [{ code: 'application.extended-declaration-capacity', declaration: {
389
+ resources: config.data.resources.length, policies: config.authz.dataPolicies.length,
390
+ roles: config.authz.roles.length, capabilities: config.authz.capabilities.length,
391
+ definitions: config.workflows.definitions.length, bindings: config.workflows.bindings.length,
392
+ activations: config.workflows.activations.length, routes: config.frontend.routes.length,
393
+ navigationItems: config.frontend.admin.navigation.reduce((count, group) => count + group.items.length, 0),
394
+ } }] : []),
365
395
  ...(resources.some(resource => resource.uniqueKeys?.length)
366
396
  ? [{ code: 'data.unique-keys', declaration: resources.filter(resource => resource.uniqueKeys?.length)
367
397
  .map(resource => ({ code: resource.code, uniqueKeys: resource.uniqueKeys })) }] : []),
@@ -372,6 +402,23 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
372
402
  declaration: resources.filter(resource => resource.decimalReservationLifecycle)
373
403
  .map(resource => ({ code: resource.code, decimalReservationLifecycle: resource.decimalReservationLifecycle })) }]
374
404
  : []),
405
+ ...(operations.some(operation => operation.platformAccess?.dataCommands)
406
+ ? [{ code: 'data.business-commands', declaration: operations.filter(operation => operation.platformAccess?.dataCommands) }]
407
+ : []),
408
+ ...(config.authz.dataPolicies.some((policy) => policy.publicRead)
409
+ ? [{ code: 'data.authenticated-public-projection',
410
+ declaration: config.authz.dataPolicies.filter((policy) => policy.publicRead) }]
411
+ : []),
412
+ ...(operations.some(operation => operation.platformAccess?.roleAssertions?.actorAuthority)
413
+ ? [{ code: 'data.transaction-actor-authority',
414
+ declaration: operations.filter(operation => operation.platformAccess?.roleAssertions?.actorAuthority) }]
415
+ : []),
416
+ ...(operations.some(operation => operation.platformAccess?.workflowStage)
417
+ ? [{ code: 'workflow.native-stage-guard', declaration: operations.filter(operation => operation.platformAccess?.workflowStage) }]
418
+ : []),
419
+ ...(operations.some(operation => operation.platformAccess?.recordEdit)
420
+ ? [{ code: 'data.record-edit', declaration: operations.filter(operation => operation.platformAccess?.recordEdit) }]
421
+ : []),
375
422
  ...(operations.some(operation => operation.platformAccess?.decimalReservation) ||
376
423
  config.events.subscriptions.some((subscription) => subscription.platformAccess?.decimalReservation)
377
424
  ? [{
@@ -416,6 +463,14 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
416
463
  },
417
464
  ]
418
465
  : []),
466
+ ...(resources.some(resource => resource.recordComments !== undefined)
467
+ ? [{ code: 'data.record-comments', declaration: dataUsage }] : []),
468
+ ...(resources.some(resource => resource.recordDeletion !== undefined)
469
+ ? [{ code: 'data.workflow-record-deletion', declaration: dataUsage }] : []),
470
+ ...(resources.some(resource => resource.recordPrint !== undefined)
471
+ ? [{ code: 'data.record-print', declaration: dataUsage }] : []),
472
+ ...(resources.some(resource => resource.workflowHistory !== undefined)
473
+ ? [{ code: 'workflow.record-history-read', declaration: dataUsage }] : []),
419
474
  ...(resources.some(hasDataAuditReadPolicy)
420
475
  ? [{ code: 'data.audit-read-access', declaration: dataUsage }]
421
476
  : []),
@@ -448,6 +503,13 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
448
503
  ]
449
504
  : []),
450
505
  ...(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 })) }] : []),
506
+ ...(initiatorPhoneOperations.length || initiatorPhoneCommands.length ? [{
507
+ code: 'directory.current-initiator-phone',
508
+ declaration: {
509
+ operations: initiatorPhoneOperations.map(operation => ({ code: operation.code, directory: operation.platformAccess.directory })),
510
+ commands: initiatorPhoneCommands.map((command) => ({ code: command.code, directory: command.execution.directory })),
511
+ },
512
+ }] : []),
451
513
  ...(usesManagedFiles
452
514
  ? [
453
515
  {
@@ -481,6 +543,9 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
481
543
  ...(config.events.capturePolicies?.length
482
544
  ? [{ code: 'events.capture-policy', declaration: config.events.capturePolicies }]
483
545
  : []),
546
+ ...(config.events.dateTriggers.some((trigger) => dateEventProjectionFieldsV2(trigger.resourceCode, config.events.subscriptions.filter((subscription) => subscription.eventTypes.includes(trigger.eventType)), config.data.resources.find((resource) => resource.code === trigger.resourceCode)).length > 0)
547
+ ? [{ code: 'events.date-source-projection', declaration: { triggers: config.events.dateTriggers, subscriptions: config.events.subscriptions } }]
548
+ : []),
484
549
  ...(usesEvents
485
550
  ? [
486
551
  { code: 'events-v2', declaration: config.events },
@@ -490,6 +555,23 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
490
555
  },
491
556
  ]
492
557
  : []),
558
+ ...(requiresExtendedOwnedSubtableCapacity(config.data.resources)
559
+ ? [{ code: 'data.extended-owned-subtable-capacity', declaration: config.data.resources.filter((resource) => requiresExtendedOwnedSubtableCapacity([resource])) }] : []),
560
+ ...(config.data.resources.some((resource) => resource.schema.fields.some((field) => field.userCandidates !== undefined))
561
+ ? [{ code: 'data.user-candidates', declaration: config.data.resources.filter((resource) => resource.schema.fields.some((field) => field.userCandidates !== undefined)) }]
562
+ : []),
563
+ ...(requiresWorkflowRoleUnion(config.workflows.bindings)
564
+ ? [{ code: 'workflow.role-union', declaration: config.workflows.bindings.filter((item) => Object.values(item.binding.bindings).some((entry) => entry.roleCodes !== undefined)) }] : []),
565
+ ...(config.backend.operations.some((item) => item.platformAccess?.ownedSubject)
566
+ ? [{ code: 'workflow.named-owned-create', declaration: config.backend.operations.filter((item) => item.platformAccess?.ownedSubject) }] : []),
567
+ ...(requiresUserCandidateLaunchScope(config.data.resources)
568
+ ? [{ code: 'data.user-candidate-launch-scope', declaration: config.data.resources.filter((resource) => requiresUserCandidateLaunchScope([resource])) }]
569
+ : []),
570
+ ...(requiresOwnedSubtableMinimumRows(config.data.resources)
571
+ ? [{ code: 'data.subtable-minimum-rows', declaration: config.data.resources.filter((resource) => requiresOwnedSubtableMinimumRows([resource])) }] : []),
572
+ ...(requiresDateTimeMinutePrecision(config.data.resources)
573
+ ? [{ code: 'data.datetime-minute-precision', declaration: config.data.resources.filter((resource) => requiresDateTimeMinutePrecision([resource])) }]
574
+ : []),
493
575
  ...(config.workflows.activations.length
494
576
  ? [
495
577
  {
@@ -508,12 +590,53 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
508
590
  declaration: config.workflows.definitions.filter((item) => item.definition.instanceCommands !== undefined),
509
591
  }]
510
592
  : []),
593
+ ...(config.workflows.definitions.some((item) => item.definition.instanceCommands?.withdraw?.reasonRequired === false)
594
+ ? [{ code: 'workflow.optional-withdrawal-reason',
595
+ declaration: config.workflows.definitions.filter((item) => item.definition.instanceCommands?.withdraw?.reasonRequired === false) }]
596
+ : []),
597
+ ...(config.workflows.definitions.some((item) => item.definition.rejectionNotification !== undefined)
598
+ ? [{
599
+ code: 'workflow.rejection-notification',
600
+ declaration: config.workflows.definitions.filter((item) => item.definition.rejectionNotification !== undefined),
601
+ }]
602
+ : []),
603
+ ...(config.workflows.definitions.some((item) => item.definition.commandHandlers !== undefined) || operations.some(operation => operation.platformAccess?.workflow?.businessCommands)
604
+ ? [{ code: 'workflow.business-data-command', declaration: {
605
+ definitions: config.workflows.definitions.filter((item) => item.definition.commandHandlers !== undefined),
606
+ operations: operations.filter(operation => operation.platformAccess?.workflow?.businessCommands),
607
+ } }] : []),
608
+ ...(config.workflows.definitions.some((item) => item.definition.commandHandlers?.resubmit) || operations.some(operation => operation.platformAccess?.workflow?.businessCommands?.includes('resubmit'))
609
+ ? [{ code: 'workflow.correction-business-command', declaration: {
610
+ definitions: config.workflows.definitions.filter((item) => item.definition.commandHandlers?.resubmit),
611
+ operations: operations.filter(operation => operation.platformAccess?.workflow?.businessCommands?.includes('resubmit')),
612
+ } }] : []),
613
+ ...(config.workflows.definitions.some((item) => item.definition.launchPreflight !== undefined)
614
+ ? [{ code: 'workflow.launch-preflight', declaration: config.workflows.definitions.filter((item) => item.definition.launchPreflight !== undefined) }] : []),
615
+ ...(config.workflows.definitions.some((item) => item.definition.approvedDelegation !== undefined)
616
+ ? [{ code: 'workflow.approved-delegation', declaration: config.workflows.definitions.filter((item) => item.definition.approvedDelegation !== undefined) }] : []),
617
+ ...(config.workflows.definitions.some((item) => workflowUsesOwnedInitialFacts(item.definition, config.data.resources))
618
+ ? [{ code: 'workflow.owned-initial-facts', declaration: config.workflows.definitions.filter((item) => workflowUsesOwnedInitialFacts(item.definition, config.data.resources)) }] : []),
619
+ ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'correction'))
620
+ ? [{ code: 'workflow.initiator-correction', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'correction')) }] : []),
621
+ ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.operationPolicy?.reject?.commentRequired === false))
622
+ ? [{ code: 'workflow.optional-rejection-comment', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.operationPolicy?.reject?.commentRequired === false)) }] : []),
623
+ ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => Object.values(node.operationPolicy || {}).some((policy) => policy.reasonRequired === false)))
624
+ ? [{ code: 'workflow.optional-operation-reason', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => Object.values(node.operationPolicy || {}).some((policy) => policy.reasonRequired === false))) }] : []),
511
625
  ...(namedInputSourceDefinitions.length
512
626
  ? [{ code: 'workflow.named-input-sources', declaration: namedInputSourceDefinitions }]
513
627
  : []),
514
628
  ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.administration !== undefined || node.operationPolicy !== undefined))
515
629
  ? [{ code: 'workflow.node-administration', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.administration !== undefined || node.operationPolicy !== undefined)) }]
516
630
  : []),
631
+ ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'approval' && node.emptyPolicy !== undefined))
632
+ ? [{ code: 'workflow.approval-empty-policy', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'approval' && node.emptyPolicy !== undefined)) }]
633
+ : []),
634
+ ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.completionDeadline !== undefined))
635
+ ? [{ code: 'workflow.completion-deadline', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.completionDeadline !== undefined)) }]
636
+ : []),
637
+ ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.initiatorApprovalPolicy !== undefined))
638
+ ? [{ code: 'workflow.initiator-approval-policy', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.initiatorApprovalPolicy !== undefined)) }]
639
+ : []),
517
640
  ...(config.workflows.definitions.some((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'cc'))
518
641
  ? [{ code: 'workflow.automatic-cc', declaration: config.workflows.definitions.filter((item) => Object.values(item.definition.nodes).some((node) => node.kind === 'cc')) }]
519
642
  : []),
@@ -556,6 +679,8 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
556
679
  {
557
680
  code: 'notification-hub-v2',
558
681
  declaration: {
682
+ ...(config.workflows.definitions.some((item) => item.definition.rejectionNotification !== undefined)
683
+ ? { rejectionNotifications: config.workflows.definitions.filter((item) => item.definition.rejectionNotification !== undefined) } : {}),
559
684
  operations: operations
560
685
  .filter(operation => operation.platformAccess?.notification)
561
686
  .map(operation => ({
@@ -592,7 +717,9 @@ export function compileRequiredPlatformCapabilitiesV3(config) {
592
717
  return usages
593
718
  .map(({ code, declaration }) => ({
594
719
  code,
595
- contractVersion: OPENXIANGDA_V2_PLATFORM_CAPABILITY_CONTRACT_VERSIONS[code],
720
+ contractVersion: code === 'data.unique-keys'
721
+ ? (resources.some(resource => nativeUniqueKeysContractVersion(resource.schema.fields, resource.uniqueKeys) === '1.1.0') ? '1.1.0' : '1.0.0')
722
+ : OPENXIANGDA_V2_PLATFORM_CAPABILITY_CONTRACT_VERSIONS[code],
596
723
  usageDigest: `sha256:${sha256Canonical(declaration)}`,
597
724
  }))
598
725
  .sort((left, right) => left.code === right.code ? 0 : left.code < right.code ? -1 : 1);
@@ -789,13 +916,13 @@ function validateConfigurationEnvelope(config, appCode) {
789
916
  'dataPolicies',
790
917
  'authorizationTransitions',
791
918
  ], '/config/authz', ['authenticatedUserRoleCode']);
792
- boundedArray(authz.capabilities, '/config/authz/capabilities', 2000);
793
- boundedArray(authz.roles, '/config/authz/roles', 100);
919
+ boundedArray(authz.capabilities, '/config/authz/capabilities', NATIVE_CONTRACT_CAPACITY_V2.capabilities);
920
+ boundedArray(authz.roles, '/config/authz/roles', NATIVE_CONTRACT_CAPACITY_V2.roles);
794
921
  boundedArray(authz.scopeDimensions, '/config/authz/scopeDimensions', 100);
795
922
  boundedArray(authz.scopeSources, '/config/authz/scopeSources', 100);
796
923
  boundedArray(authz.roleMembershipSources, '/config/authz/roleMembershipSources', 100);
797
924
  boundedArray(authz.relationshipGrantSources, '/config/authz/relationshipGrantSources', 100);
798
- boundedArray(authz.dataPolicies, '/config/authz/dataPolicies', 100);
925
+ boundedArray(authz.dataPolicies, '/config/authz/dataPolicies', NATIVE_CONTRACT_CAPACITY_V2.dataPolicies);
799
926
  boundedArray(authz.authorizationTransitions, '/config/authz/authorizationTransitions', 100);
800
927
  const backend = object(config.backend, '/config/backend');
801
928
  exactKeys(backend, ['secrets', 'operations'], '/config/backend');
@@ -808,7 +935,7 @@ function validateConfigurationEnvelope(config, appCode) {
808
935
  'subjectReadSurfaces',
809
936
  'concurrency',
810
937
  ]);
811
- boundedArray(data.resources, '/config/data/resources', 100);
938
+ boundedArray(data.resources, '/config/data/resources', NATIVE_CONTRACT_CAPACITY_V2.resources);
812
939
  if (data.subjectReadSurfaces !== undefined) {
813
940
  boundedArray(data.subjectReadSurfaces, '/config/data/subjectReadSurfaces', NATIVE_CONTRACT_CAPACITY_V2.subjectReadSurfaces);
814
941
  }
@@ -829,14 +956,14 @@ function validateConfigurationEnvelope(config, appCode) {
829
956
  'providers',
830
957
  'editableParameters',
831
958
  ], '/config/workflows');
832
- boundedArray(workflows.definitions, '/config/workflows/definitions', 100);
833
- boundedArray(workflows.bindings, '/config/workflows/bindings', 100);
834
- boundedArray(workflows.activations, '/config/workflows/activations', 100);
959
+ boundedArray(workflows.definitions, '/config/workflows/definitions', NATIVE_CONTRACT_CAPACITY_V2.workflows);
960
+ boundedArray(workflows.bindings, '/config/workflows/bindings', NATIVE_CONTRACT_CAPACITY_V2.workflows);
961
+ boundedArray(workflows.activations, '/config/workflows/activations', NATIVE_CONTRACT_CAPACITY_V2.workflows);
835
962
  boundedArray(workflows.providers, '/config/workflows/providers', 100);
836
963
  boundedArray(workflows.editableParameters, '/config/workflows/editableParameters', 100);
837
964
  const frontend = object(config.frontend, '/config/frontend');
838
965
  exactKeys(frontend, ['routes', 'user', 'admin', 'devicePolicy'], '/config/frontend', ['authentication', 'publicAccess']);
839
- boundedArray(frontend.routes, '/config/frontend/routes', 500);
966
+ boundedArray(frontend.routes, '/config/frontend/routes', NATIVE_CONTRACT_CAPACITY_V2.routes);
840
967
  const user = object(frontend.user, '/config/frontend/user');
841
968
  exactKeys(user, ['applicationTodoCenter'], '/config/frontend/user');
842
969
  boolean(user.applicationTodoCenter, '/config/frontend/user/applicationTodoCenter');
@@ -1007,7 +1134,7 @@ function compileExpectedContract(config, configDigest, generatorVersion) {
1007
1134
  field: trigger.field,
1008
1135
  })),
1009
1136
  ...(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 => ({
1137
+ ? 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
1138
  code: `workflow:${eventType}`,
1012
1139
  source: 'workflow',
1013
1140
  eventType,
@@ -1212,9 +1339,12 @@ function compileCapabilities(config) {
1212
1339
  };
1213
1340
  catalog.set(code, capability);
1214
1341
  }
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);
1342
+ const resources = config.data.resources.map((raw, resourceIndex) => ({
1343
+ pointer: `/config/data/resources/${resourceIndex}`,
1344
+ resource: validateResource(raw, `/config/data/resources/${resourceIndex}`, config.appCode),
1345
+ }));
1346
+ // CRUD definitions precede all field references, regardless of source order.
1347
+ for (const { pointer, resource } of resources) {
1218
1348
  const operationNames = {
1219
1349
  read: `读取${resource.name}`,
1220
1350
  create: `新建${resource.name}`,
@@ -1233,6 +1363,10 @@ function compileCapabilities(config) {
1233
1363
  source: 'data',
1234
1364
  });
1235
1365
  }
1366
+ }
1367
+ const definedCapabilities = new Set(catalog.keys());
1368
+ const fieldOwners = new Map();
1369
+ for (const { pointer, resource } of resources) {
1236
1370
  for (const [fieldCode, rawPolicy] of Object.entries(resource.fieldPolicies)) {
1237
1371
  if (isDataAuditMetadataField(fieldCode))
1238
1372
  continue;
@@ -1242,6 +1376,13 @@ function compileCapabilities(config) {
1242
1376
  continue;
1243
1377
  for (const code of uniqueStrings(policy[operation], `${pointer}/fieldPolicies/${fieldCode}/${operation}`, 2000)) {
1244
1378
  capabilityCode(code, `${pointer}/fieldPolicies/${fieldCode}/${operation}`, config.appCode);
1379
+ if (definedCapabilities.has(code))
1380
+ continue;
1381
+ const existingOwner = fieldOwners.get(code);
1382
+ if (existingOwner && existingOwner !== resource.code) {
1383
+ fail('NATIVE_CAPABILITY_DUPLICATE', `${pointer}/fieldPolicies/${fieldCode}/${operation}`);
1384
+ }
1385
+ fieldOwners.set(code, resource.code);
1245
1386
  if (catalog.has(code))
1246
1387
  continue;
1247
1388
  catalog.set(code, {
@@ -1530,6 +1671,21 @@ function compileOperations(config) {
1530
1671
  const code = stableCode(operation.code, `${pointer}/code`);
1531
1672
  validateBackendOperationAi(operation.ai, pointer, code, method, declaredResourceCodes);
1532
1673
  const platformAccess = validateOperationPlatformAccess(operation.platformAccess, `${pointer}/platformAccess`, declaredResources, declaredResourceFields, declaredWorkflowCodes, new Set(config.authz.roles.map((role) => String(role.code))));
1674
+ if (platformAccess?.workflowStage) {
1675
+ const stage = platformAccess.workflowStage;
1676
+ const workflow = config.workflows.definitions.find((entry) => entry.definition.code === stage.workflowCode);
1677
+ if (!workflow || workflow.definition.subject?.resourceCode !== stage.resourceCode || stage.runningNodeIds.some((id) => !workflow.definition.nodes[id]))
1678
+ fail('NATIVE_WORKFLOW_STAGE_NODE_INVALID', `${pointer}/platformAccess/workflowStage/runningNodeIds`);
1679
+ }
1680
+ if (platformAccess?.recordEdit) {
1681
+ const owner = config.data.resources.find((item) => item.code === platformAccess.recordEdit.resourceCode);
1682
+ if (owner?.surface?.mutationOwner !== 'action')
1683
+ fail('NATIVE_RECORD_EDIT_ACTION_OWNER_REQUIRED', `${pointer}/platformAccess/recordEdit`);
1684
+ for (const fieldCode of platformAccess.recordEdit.fieldCodes) {
1685
+ if (!owner.fieldPolicies?.[fieldCode]?.update?.length || owner.surface?.fields?.[fieldCode]?.system === true)
1686
+ fail('NATIVE_RECORD_EDIT_FIELD_INVALID', `${pointer}/platformAccess/recordEdit/fieldCodes`);
1687
+ }
1688
+ }
1533
1689
  if (platformAccess?.decimalReservation) {
1534
1690
  const reservation = platformAccess.decimalReservation;
1535
1691
  const resourceCode = String(reservation.resourceCode);
@@ -1726,14 +1882,51 @@ function validateOperationPlatformAccess(value, pointer, declaredResources, decl
1726
1882
  if (value === undefined)
1727
1883
  return null;
1728
1884
  const access = object(value, pointer);
1729
- exactKeys(access, ['directory', 'managedFiles', 'managedFileCopies', 'notification', 'workflow', 'roleAssertions', 'decimalReservation'], pointer, true);
1885
+ exactKeys(access, ['directory', 'managedFiles', 'managedFileCopies', 'notification', 'workflow', 'roleAssertions', 'decimalReservation', 'dataCommands', 'recordEdit', 'ownedSubject', 'workflowStage'], pointer, true);
1730
1886
  if (Object.keys(access).length === 0) {
1731
1887
  fail('NATIVE_OPERATION_PLATFORM_ACCESS_EMPTY', pointer);
1732
1888
  }
1733
1889
  const result = {};
1890
+ if (access.dataCommands !== undefined) {
1891
+ const commands = object(access.dataCommands, `${pointer}/dataCommands`);
1892
+ exactKeys(commands, ['mode'], `${pointer}/dataCommands`);
1893
+ equal(commands.mode, 'recoverable-native', `${pointer}/dataCommands/mode`);
1894
+ result.dataCommands = { mode: 'recoverable-native' };
1895
+ }
1896
+ if (access.workflowStage !== undefined) {
1897
+ try {
1898
+ assertWorkflowNativeStagePolicy(access.workflowStage);
1899
+ }
1900
+ catch {
1901
+ fail('NATIVE_WORKFLOW_STAGE_DECLARATION_INVALID', `${pointer}/workflowStage`);
1902
+ }
1903
+ const stage = access.workflowStage;
1904
+ if (!result.dataCommands || access.decimalReservation !== undefined || !declaredResources.has(stage.resourceCode) ||
1905
+ !declaredWorkflowCodes.has(stage.workflowCode) || !access.workflow?.codes?.includes(stage.workflowCode))
1906
+ fail('NATIVE_WORKFLOW_STAGE_DECLARATION_INVALID', `${pointer}/workflowStage`);
1907
+ result.workflowStage = { ...stage, runningNodeIds: uniqueSorted([...stage.runningNodeIds]), allowedStatuses: uniqueSorted([...stage.allowedStatuses]) };
1908
+ }
1909
+ if (access.recordEdit !== undefined) {
1910
+ const entryPointer = `${pointer}/recordEdit`;
1911
+ const entry = object(access.recordEdit, entryPointer);
1912
+ exactKeys(entry, ['resourceCode', 'fieldCodes'], entryPointer);
1913
+ const code = resourceCode(entry.resourceCode, `${entryPointer}/resourceCode`);
1914
+ const fields = declaredResourceFields.get(code);
1915
+ const fieldCodes = uniqueStrings(entry.fieldCodes, `${entryPointer}/fieldCodes`, 200);
1916
+ if (!fields || !fieldCodes.length || !result.dataCommands)
1917
+ fail('NATIVE_RECORD_EDIT_DECLARATION_INVALID', entryPointer);
1918
+ for (const fieldCode of fieldCodes) {
1919
+ const field = fields.get(fieldCode);
1920
+ if (!field || field.type === 'serial-number' || field.type === 'subtable')
1921
+ fail('NATIVE_RECORD_EDIT_FIELD_INVALID', `${entryPointer}/fieldCodes`);
1922
+ }
1923
+ result.recordEdit = { resourceCode: code, fieldCodes: uniqueSorted(fieldCodes) };
1924
+ }
1734
1925
  if (access.roleAssertions !== undefined) {
1735
1926
  const assertions = object(access.roleAssertions, `${pointer}/roleAssertions`);
1736
- exactKeys(assertions, ['roleCodes'], `${pointer}/roleAssertions`);
1927
+ exactKeys(assertions, ['roleCodes', 'actorAuthority'], `${pointer}/roleAssertions`, true);
1928
+ if (assertions.actorAuthority !== undefined)
1929
+ equal(assertions.actorAuthority, true, `${pointer}/roleAssertions/actorAuthority`);
1737
1930
  const codes = uniqueStrings(assertions.roleCodes, `${pointer}/roleAssertions/roleCodes`, 20);
1738
1931
  if (!codes.length)
1739
1932
  fail('NATIVE_OPERATION_ROLE_ASSERTIONS_REQUIRED', `${pointer}/roleAssertions/roleCodes`);
@@ -1741,7 +1934,8 @@ function validateOperationPlatformAccess(value, pointer, declaredResources, decl
1741
1934
  if (!declaredRoleCodes.has(code))
1742
1935
  fail('NATIVE_ROLE_REFERENCE_MISSING', `${pointer}/roleAssertions/roleCodes/${index}`);
1743
1936
  });
1744
- result.roleAssertions = { roleCodes: uniqueSorted(codes) };
1937
+ result.roleAssertions = { roleCodes: uniqueSorted(codes),
1938
+ ...(assertions.actorAuthority === true ? { actorAuthority: true } : {}) };
1745
1939
  }
1746
1940
  if (access.directory !== undefined) {
1747
1941
  const directory = object(access.directory, `${pointer}/directory`);
@@ -1754,8 +1948,9 @@ function validateOperationPlatformAccess(value, pointer, declaredResources, decl
1754
1948
  'employeeNumber',
1755
1949
  'primaryDepartment',
1756
1950
  'departments',
1951
+ ...(directory.mode === 'current-initiator' ? ['phone'] : []),
1757
1952
  ]);
1758
- const fields = uniqueStrings(directory.fields, `${pointer}/directory/fields`, 4);
1953
+ const fields = uniqueStrings(directory.fields, `${pointer}/directory/fields`, allowed.size);
1759
1954
  if (!fields.length || fields.some(field => !allowed.has(field))) {
1760
1955
  fail('NATIVE_OPERATION_DIRECTORY_FIELDS_INVALID', `${pointer}/directory/fields`);
1761
1956
  }
@@ -1848,12 +2043,37 @@ function validateOperationPlatformAccess(value, pointer, declaredResources, decl
1848
2043
  }
1849
2044
  if (access.workflow !== undefined) {
1850
2045
  const workflow = object(access.workflow, `${pointer}/workflow`);
1851
- exactKeys(workflow, ['codes'], `${pointer}/workflow`);
2046
+ exactKeys(workflow, ['codes'], `${pointer}/workflow`, ['businessCommands']);
1852
2047
  const codes = uniqueStrings(workflow.codes, `${pointer}/workflow/codes`, 16);
1853
2048
  if (!codes.length || codes.some(code => !declaredWorkflowCodes.has(code))) {
1854
2049
  fail('NATIVE_OPERATION_WORKFLOW_CODE_INVALID', `${pointer}/workflow/codes`);
1855
2050
  }
1856
- result.workflow = { codes: uniqueSorted(codes) };
2051
+ const businessCommands = workflow.businessCommands === undefined ? undefined : uniqueStrings(workflow.businessCommands, `${pointer}/workflow/businessCommands`, 4);
2052
+ if (businessCommands && (!businessCommands.length || businessCommands.some(command => !['approve', 'reject', 'withdraw', 'resubmit'].includes(command)))) {
2053
+ fail('NATIVE_OPERATION_WORKFLOW_BUSINESS_COMMAND_INVALID', `${pointer}/workflow/businessCommands`);
2054
+ }
2055
+ result.workflow = { codes: uniqueSorted(codes), ...(businessCommands ? { businessCommands: uniqueSorted(businessCommands) } : {}) };
2056
+ }
2057
+ if (access.ownedSubject !== undefined) {
2058
+ const entryPointer = `${pointer}/ownedSubject`;
2059
+ const grant = access.ownedSubject;
2060
+ try {
2061
+ assertWorkflowOwnedSubjectCreate(grant);
2062
+ }
2063
+ catch {
2064
+ fail('NATIVE_WORKFLOW_OWNED_SUBJECT_INVALID', entryPointer);
2065
+ }
2066
+ if (!result.workflow || access.decimalReservation !== undefined)
2067
+ fail('NATIVE_WORKFLOW_OWNED_SUBJECT_INVALID', entryPointer);
2068
+ try {
2069
+ assertWorkflowOwnedSubjectCreateResources(grant, declaredResourceFields, result.managedFiles || []);
2070
+ }
2071
+ catch (error) {
2072
+ fail(`NATIVE_${error.message}`, entryPointer);
2073
+ }
2074
+ result.ownedSubject = { mode: 'create', resourceCode: grant.resourceCode, subtables: sorted(grant.subtables.map(table => ({
2075
+ fieldCode: table.fieldCode, fieldCodes: uniqueSorted([...table.fieldCodes]),
2076
+ })), table => table.fieldCode) };
1857
2077
  }
1858
2078
  if (access.decimalReservation !== undefined) {
1859
2079
  const reservationPointer = `${pointer}/decimalReservation`;
@@ -2818,6 +3038,8 @@ function compileAdminNavigation(config) {
2818
3038
  }
2819
3039
  function validateAdminNavigationDeclaration(value, pointer) {
2820
3040
  const groups = boundedArray(value, pointer, NATIVE_CONTRACT_CAPACITY_V2.adminNavigationGroups);
3041
+ if (groups.reduce((count, group) => count + (Array.isArray(group?.items) ? group.items.length : 0), 0) > NATIVE_CONTRACT_CAPACITY_V2.adminNavigationItems)
3042
+ fail('NATIVE_ARRAY_LIMIT_EXCEEDED', pointer);
2821
3043
  let itemCount = 0;
2822
3044
  groups.forEach((rawGroup, groupIndex) => {
2823
3045
  const groupPointer = `${pointer}/${groupIndex}`;
@@ -2958,6 +3180,8 @@ function validateAdminPageContracts(value, pointer) {
2958
3180
  }
2959
3181
  function validateAdminNavigationContracts(value, pointer) {
2960
3182
  const groups = boundedArray(value, pointer, NATIVE_CONTRACT_CAPACITY_V2.adminNavigationGroups);
3183
+ if (groups.reduce((count, group) => count + (Array.isArray(group?.items) ? group.items.length : 0), 0) > NATIVE_CONTRACT_CAPACITY_V2.adminNavigationItems)
3184
+ fail('NATIVE_ARRAY_LIMIT_EXCEEDED', pointer);
2961
3185
  let itemCount = 0;
2962
3186
  groups.forEach((rawGroup, groupIndex) => {
2963
3187
  const groupPointer = `${pointer}/${groupIndex}`;
@@ -3479,6 +3703,9 @@ function compileWorkflowLaunchContract(config, definition, value, pointer, opera
3479
3703
  const fieldCodes = new Set();
3480
3704
  const sourceCounts = new Map();
3481
3705
  const normalizedInputs = {};
3706
+ const ownedSubject = operationContract.platformAccess?.ownedSubject;
3707
+ if (ownedSubject && (intentKind !== 'create' || ownedSubject.resourceCode !== subjectResourceCode))
3708
+ fail('NATIVE_WORKFLOW_OWNED_SUBJECT_LAUNCH_MISMATCH', intentPointer);
3482
3709
  for (const [inputCode, rawBinding] of inputEntries) {
3483
3710
  if (!PROPERTY_CODE_PATTERN.test(inputCode) ||
3484
3711
  !Object.prototype.hasOwnProperty.call(requestProperties, inputCode)) {
@@ -3505,6 +3732,9 @@ function compileWorkflowLaunchContract(config, definition, value, pointer, opera
3505
3732
  fail('NATIVE_WORKFLOW_NAMED_OPERATION_INPUT_INVALID', `${bindingPointer}/fieldCode`);
3506
3733
  }
3507
3734
  fieldCodes.add(fieldCode);
3735
+ const field = subjectResource.schema.fields.find((item) => item.code === fieldCode);
3736
+ if (field?.type === 'subtable' && (!ownedSubject || !ownedSubject.subtables.some((table) => table.fieldCode === fieldCode)))
3737
+ fail('NATIVE_WORKFLOW_OWNED_SUBJECT_LAUNCH_REQUIRED', bindingPointer);
3508
3738
  normalizedInputs[inputCode] = { source, fieldCode };
3509
3739
  }
3510
3740
  else {
@@ -3615,6 +3845,40 @@ function validateAuthorizationReferences(config, capabilities) {
3615
3845
  config.data.resources.forEach((rawResource, index) => {
3616
3846
  const resourcePointer = `/config/data/resources/${index}`;
3617
3847
  const resource = object(rawResource, resourcePointer);
3848
+ if (resource.recordDeletion !== undefined) {
3849
+ const pointer = `${resourcePointer}/recordDeletion/delete`;
3850
+ for (const capability of resource.recordDeletion.delete) {
3851
+ capabilityCode(capability, pointer, config.appCode);
3852
+ if (!capabilityCodes.has(capability))
3853
+ fail('NATIVE_CAPABILITY_REFERENCE_MISSING', pointer);
3854
+ }
3855
+ }
3856
+ if (resource.recordComments !== undefined) {
3857
+ for (const mode of ['read', 'create']) {
3858
+ const pointer = `${resourcePointer}/recordComments/${mode}`;
3859
+ for (const capability of resource.recordComments[mode]) {
3860
+ capabilityCode(capability, pointer, config.appCode);
3861
+ if (!capabilityCodes.has(capability))
3862
+ fail('NATIVE_CAPABILITY_REFERENCE_MISSING', pointer);
3863
+ }
3864
+ }
3865
+ }
3866
+ if (resource.recordPrint !== undefined) {
3867
+ const pointer = `${resourcePointer}/recordPrint/read`;
3868
+ for (const capability of resource.recordPrint.read) {
3869
+ capabilityCode(capability, pointer, config.appCode);
3870
+ if (!capabilityCodes.has(capability))
3871
+ fail('NATIVE_CAPABILITY_REFERENCE_MISSING', pointer);
3872
+ }
3873
+ }
3874
+ if (resource.workflowHistory !== undefined) {
3875
+ const pointer = `${resourcePointer}/workflowHistory/read`;
3876
+ for (const capability of resource.workflowHistory.read) {
3877
+ capabilityCode(capability, pointer, config.appCode);
3878
+ if (!capabilityCodes.has(capability))
3879
+ fail('NATIVE_CAPABILITY_REFERENCE_MISSING', pointer);
3880
+ }
3881
+ }
3618
3882
  for (const [fieldCode, rawPolicy] of Object.entries(resource.fieldPolicies || {})) {
3619
3883
  if (!isDataAuditMetadataField(fieldCode))
3620
3884
  continue;
@@ -3633,6 +3897,9 @@ function validateAuthorizationReferences(config, capabilities) {
3633
3897
  return;
3634
3898
  const resourceCapabilities = object(resource.capabilities, `${resourcePointer}/capabilities`);
3635
3899
  for (const operation of ['create', 'update', 'delete']) {
3900
+ if (operation === 'delete' && Array.isArray(resource.recordDeletion?.delete) &&
3901
+ resource.recordDeletion.delete.length > 0)
3902
+ continue;
3636
3903
  forbiddenNativeMutationCapabilities.set(String(resourceCapabilities[operation]), operation);
3637
3904
  }
3638
3905
  });
@@ -3923,6 +4190,7 @@ function validateDataPolicyReferences(config) {
3923
4190
  'name',
3924
4191
  'resourceCode',
3925
4192
  'unrestrictedRoleCodes',
4193
+ 'publicRead',
3926
4194
  'operations',
3927
4195
  'matchMode',
3928
4196
  'rules',
@@ -3954,6 +4222,26 @@ function validateDataPolicyReferences(config) {
3954
4222
  if (hasReadExpression && !policyFields) {
3955
4223
  fail('NATIVE_DATA_POLICY_RESOURCE_MISSING', `${pointer}/resourceCode`);
3956
4224
  }
4225
+ if (policy.publicRead !== undefined) {
4226
+ try {
4227
+ const baseline = config.authz.roles.find((role) => role.code === authenticatedUserRoleCode);
4228
+ validateAuthenticatedPublicRead({ policy,
4229
+ resource: config.data.resources.find((resource) => resource.code === policyResourceCode),
4230
+ baselineRoleCode: authenticatedUserRoleCode,
4231
+ baselineCapabilities: baseline?.capabilities || [],
4232
+ baselineDeniedCapabilities: baseline?.deniedCapabilities || [], pointer });
4233
+ }
4234
+ catch (error) {
4235
+ if (!(error instanceof AuthenticatedPublicReadContractError))
4236
+ throw error;
4237
+ fail(error.code, error.pointer);
4238
+ }
4239
+ if (!(policy.unrestrictedRoleCodes || []).includes(authenticatedUserRoleCode))
4240
+ fail('AUTHENTICATED_PUBLIC_READ_CANONICAL_BASELINE_MISSING', `${pointer}/unrestrictedRoleCodes`);
4241
+ }
4242
+ else if (authenticatedUserRoleCode && (policy.unrestrictedRoleCodes || []).includes(authenticatedUserRoleCode)) {
4243
+ fail('NATIVE_DATA_POLICY_BASELINE_UNRESTRICTED', `${pointer}/unrestrictedRoleCodes`);
4244
+ }
3957
4245
  const unrestrictedRoleCodes = uniqueStrings(policy.unrestrictedRoleCodes || [], `${pointer}/unrestrictedRoleCodes`, 100);
3958
4246
  if (policy.unrestrictedRoleCodes !== undefined &&
3959
4247
  unrestrictedRoleCodes.length === 0) {
@@ -4101,9 +4389,10 @@ function validateDataPolicyReferences(config) {
4101
4389
  function validateDataFieldReferences(config) {
4102
4390
  try {
4103
4391
  validateNativeDataResourceReferencesV2(config.data.resources, '/config/data/resources');
4392
+ 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
4393
  }
4105
4394
  catch (error) {
4106
- if (error instanceof NativeDataFieldContractV2Error) {
4395
+ if (error instanceof NativeDataFieldContractV2Error || error instanceof UserCandidateContractError) {
4107
4396
  fail(error.code, error.pointer);
4108
4397
  }
4109
4398
  throw error;
@@ -4326,6 +4615,15 @@ function validateWorkflowReferences(config) {
4326
4615
  }
4327
4616
  definitions.add(key);
4328
4617
  validateWorkflowDefinition(definition, `${pointer}/definition`);
4618
+ const preflightErrors = validateWorkflowLaunchPreflight(definition);
4619
+ if (preflightErrors.length)
4620
+ fail(preflightErrors[0], `${pointer}/definition/launchPreflight`);
4621
+ const delegationErrors = validateWorkflowApprovedDelegation(definition);
4622
+ if (delegationErrors.length)
4623
+ fail(delegationErrors[0], `${pointer}/definition/approvedDelegation`);
4624
+ const businessCommandErrors = validateWorkflowCommandHandlers(definition, config.backend.operations);
4625
+ if (businessCommandErrors.length)
4626
+ fail(businessCommandErrors[0], `${pointer}/definition/commandHandlers`);
4329
4627
  validateWorkflowSubject(definition.subject, resources, `${pointer}/definition/subject`);
4330
4628
  const taskPageErrors = validateWorkflowTaskPages(definition, new Map((resources.get(definition.subject.resourceCode)?.schema.fields || [])
4331
4629
  .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 +4683,9 @@ function validateWorkflowReferences(config) {
4385
4683
  }
4386
4684
  const definition = definitionValues.get(definitionKey);
4387
4685
  const binding = bindingValues.get(bindingKey);
4686
+ const preflightErrors = validateWorkflowLaunchPreflight(definition, binding);
4687
+ if (preflightErrors.length)
4688
+ fail(preflightErrors[0], pointer);
4388
4689
  const administrationErrors = validateWorkflowAdministration(definition, binding);
4389
4690
  if (administrationErrors.length)
4390
4691
  fail(administrationErrors[0], pointer);
@@ -4394,6 +4695,15 @@ function validateWorkflowReferences(config) {
4394
4695
  fail('NATIVE_WORKFLOW_COMMAND_DEACTIVATION_POLICY_MISMATCH', `${pointer}/acceptedCommandDeactivationPolicy`);
4395
4696
  }
4396
4697
  validateWorkflowDefinitionBinding(definition, binding, `/config/workflows/activations/${index}`);
4698
+ try {
4699
+ const fields = resources.get(definition.subject.resourceCode)?.schema.fields || [];
4700
+ 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]))])));
4701
+ }
4702
+ catch (error) {
4703
+ if (error instanceof UserCandidateContractError)
4704
+ fail(error.code, error.pointer);
4705
+ throw error;
4706
+ }
4397
4707
  activeBindings.set(code, binding);
4398
4708
  }
4399
4709
  const routingErrors = validateWorkflowAssignmentRoutingBindings([...activeBindings.values()]);
@@ -4552,7 +4862,7 @@ function validateWorkflowDefinition(definition, pointer) {
4552
4862
  'startAt',
4553
4863
  'inputSchema',
4554
4864
  'nodes',
4555
- ], pointer, ['organizationContext', 'instanceCommands', 'readability', 'taskPages']);
4865
+ ], pointer, ['organizationContext', 'instanceCommands', 'readability', 'taskPages', 'rejectionNotification', 'commandHandlers', 'launchPreflight', 'approvedDelegation']);
4556
4866
  equal(definition.schemaVersion, 'openxiangda.workflow-definition/v2', `${pointer}/schemaVersion`);
4557
4867
  workflowCodeValue(definition.code, `${pointer}/code`);
4558
4868
  requiredString(definition.title, `${pointer}/title`, 255);
@@ -4571,6 +4881,18 @@ function validateWorkflowDefinition(definition, pointer) {
4571
4881
  const administrationErrors = validateWorkflowAdministration(definition);
4572
4882
  if (administrationErrors.length)
4573
4883
  fail(administrationErrors[0], `${pointer}/nodes`);
4884
+ const emptyPolicyErrors = validateWorkflowApprovalEmptyPolicy(definition);
4885
+ if (emptyPolicyErrors.length)
4886
+ fail(emptyPolicyErrors[0], `${pointer}/nodes`);
4887
+ const initiatorPolicyErrors = validateWorkflowInitiatorApprovalPolicy(definition);
4888
+ if (initiatorPolicyErrors.length)
4889
+ fail(initiatorPolicyErrors[0], `${pointer}/nodes`);
4890
+ const deadlineErrors = validateWorkflowCompletionDeadlines(definition);
4891
+ if (deadlineErrors.length)
4892
+ fail(deadlineErrors[0], `${pointer}/nodes`);
4893
+ const rejectionNotificationErrors = validateWorkflowRejectionNotification(definition);
4894
+ if (rejectionNotificationErrors.length)
4895
+ fail(rejectionNotificationErrors[0], `${pointer}/rejectionNotification`);
4574
4896
  const ccErrors = validateWorkflowAutomaticCc(definition);
4575
4897
  if (ccErrors.length)
4576
4898
  fail(ccErrors[0], `${pointer}/nodes`);
@@ -4587,7 +4909,7 @@ function validateWorkflowDefinition(definition, pointer) {
4587
4909
  const nodePointer = `${pointer}/nodes/${nodeId}`;
4588
4910
  const node = object(rawNode, nodePointer);
4589
4911
  equal(node.id, nodeId, `${nodePointer}/id`);
4590
- if (!['approval', 'condition', 'end', 'cc', 'action'].includes(node.kind)) {
4912
+ if (!['approval', 'condition', 'end', 'cc', 'action', 'correction'].includes(node.kind)) {
4591
4913
  fail('NATIVE_WORKFLOW_NODE_KIND_INVALID', `${nodePointer}/kind`);
4592
4914
  }
4593
4915
  const targets = [];
@@ -4609,6 +4931,11 @@ function validateWorkflowDefinition(definition, pointer) {
4609
4931
  }
4610
4932
  }
4611
4933
  }
4934
+ else if (node.kind === 'correction') {
4935
+ const errors = validateWorkflowCorrections(definition);
4936
+ if (errors.length)
4937
+ fail(errors[0], nodePointer);
4938
+ }
4612
4939
  else if (node.kind === 'action') {
4613
4940
  targets.push(requiredString(node.next, `${nodePointer}/next`, 128));
4614
4941
  }
@@ -4653,6 +4980,13 @@ function validateWorkflowDefinition(definition, pointer) {
4653
4980
  visiting.delete(nodeId);
4654
4981
  };
4655
4982
  walk(startAt);
4983
+ for (const id of [...reachable]) {
4984
+ const current = nodes[id];
4985
+ if (current?.kind === 'approval')
4986
+ for (const target of current.returnTargets || [])
4987
+ if (nodes[target]?.kind === 'correction')
4988
+ reachable.add(target);
4989
+ }
4656
4990
  if (reachable.size !== nodeIds.length) {
4657
4991
  fail('NATIVE_WORKFLOW_NODE_UNREACHABLE', `${pointer}/nodes`);
4658
4992
  }
@@ -4699,6 +5033,9 @@ function validateWorkflowBinding(binding, pointer) {
4699
5033
  for (const [code, raw] of Object.entries(entries)) {
4700
5034
  workflowBindingCode(code, `${pointer}/bindings/${code}`);
4701
5035
  const entry = object(raw, `${pointer}/bindings/${code}`);
5036
+ const unionErrors = validateWorkflowRoleUnion(entry);
5037
+ if (unionErrors.length)
5038
+ fail(unionErrors[0], `${pointer}/bindings/${code}/roleCodes`);
4702
5039
  const provider = requiredString(entry.provider, `${pointer}/bindings/${code}/provider`, 64);
4703
5040
  if (![
4704
5041
  'fixed_users',
@@ -4715,15 +5052,15 @@ function validateWorkflowBinding(binding, pointer) {
4715
5052
  ].includes(provider)) {
4716
5053
  fail('NATIVE_WORKFLOW_BINDING_PROVIDER_INVALID', `${pointer}/bindings/${code}/provider`);
4717
5054
  }
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`);
5055
+ if (provider === 'fixed_users') {
5056
+ if (!Array.isArray(entry.users))
5057
+ fail('NATIVE_WORKFLOW_BINDING_USERS_REQUIRED', `${pointer}/bindings/${code}/users`);
5058
+ boundedArray(entry.users, `${pointer}/bindings/${code}/users`, 200);
4722
5059
  }
4723
5060
  if (['input_users', 'form_field_users'].includes(provider)) {
4724
5061
  requiredString(entry.inputPath, `${pointer}/bindings/${code}/inputPath`, 255);
4725
5062
  }
4726
- if (['app_role', 'app_role_in_scope', 'initiator_select'].includes(provider)) {
5063
+ if (['app_role', 'app_role_in_scope', 'initiator_select'].includes(provider) && entry.roleCodes === undefined) {
4727
5064
  stableCode(entry.roleCode, `${pointer}/bindings/${code}/roleCode`);
4728
5065
  }
4729
5066
  if (provider === 'department_supervisor') {
@@ -4742,6 +5079,13 @@ function validateWorkflowBinding(binding, pointer) {
4742
5079
  function validateWorkflowDefinitionBinding(definition, binding, pointer) {
4743
5080
  equal(binding.workflowCode, definition.code, `${pointer}/workflowCode`);
4744
5081
  const entries = object(binding.bindings, `${pointer}/bindings`);
5082
+ for (const [code, entry] of Object.entries(entries)) {
5083
+ if (entry.provider === 'fixed_users' && !entry.users.length && !workflowBindingAllowsEmptyUsers(definition, code))
5084
+ fail('NATIVE_WORKFLOW_BINDING_USERS_REQUIRED', `${pointer}/bindings/${code}/users`);
5085
+ }
5086
+ const emptyPolicyErrors = validateWorkflowApprovalEmptyPolicy(definition, binding);
5087
+ if (emptyPolicyErrors.length)
5088
+ fail(emptyPolicyErrors[0], pointer);
4745
5089
  const ccErrors = validateWorkflowAutomaticCc(definition, binding);
4746
5090
  if (ccErrors.length)
4747
5091
  fail(ccErrors[0], pointer);
@@ -4765,13 +5109,13 @@ function mergeUserSurfaceRoutes(expectedContract, contract, config) {
4765
5109
  const manifest = object(contract.routeManifest, '/contracts/routeManifest');
4766
5110
  const expectedManifest = object(expectedContract.routeManifest, '/contracts/routeManifest');
4767
5111
  const resources = new Map();
4768
- for (const raw of boundedArray(config.data?.resources, '/config/data/resources', 100)) {
5112
+ for (const raw of boundedArray(config.data?.resources, '/config/data/resources', NATIVE_CONTRACT_CAPACITY_V2.resources)) {
4769
5113
  const resource = object(raw, '/config/data/resources');
4770
5114
  resources.set(String(resource.code || ''), resource);
4771
5115
  }
4772
- const existingCodes = new Set(boundedArray(expectedManifest.routes, '/contracts/routeManifest/routes', 1000).map(entry => String(object(entry, '/contracts/routeManifest/routes').code || '')));
5116
+ const existingCodes = new Set(boundedArray(expectedManifest.routes, '/contracts/routeManifest/routes', NATIVE_CONTRACT_CAPACITY_V2.routes).map(entry => String(object(entry, '/contracts/routeManifest/routes').code || '')));
4773
5117
  const accepted = [];
4774
- for (const rawEntry of boundedArray(manifest.routes, '/contracts/routeManifest/routes', 1000)) {
5118
+ for (const rawEntry of boundedArray(manifest.routes, '/contracts/routeManifest/routes', NATIVE_CONTRACT_CAPACITY_V2.routes)) {
4775
5119
  const entry = object(rawEntry, '/contracts/routeManifest/routes');
4776
5120
  const kind = String(entry.kind || '');
4777
5121
  if (kind !== 'resource-records' && kind !== 'resource-submit')
@@ -4836,6 +5180,10 @@ function validateResource(raw, pointer, appCode) {
4836
5180
  'invariants',
4837
5181
  'decimalReservationLifecycle',
4838
5182
  'uniqueKeys',
5183
+ 'workflowHistory',
5184
+ 'recordPrint',
5185
+ 'recordComments',
5186
+ 'recordDeletion',
4839
5187
  'capabilities',
4840
5188
  'dataPolicyCode',
4841
5189
  'fieldPolicies',
@@ -4858,11 +5206,45 @@ function validateResource(raw, pointer, appCode) {
4858
5206
  validateNativeDataResourceSurfaceV2(resource.surface, fields, `${pointer}/surface`);
4859
5207
  }
4860
5208
  catch (error) {
4861
- if (error instanceof NativeDataFieldContractV2Error || error instanceof NativeDecimalLifecycleContractError || error instanceof NativeUniqueKeyContractError) {
5209
+ if (error instanceof NativeDataFieldContractV2Error || error instanceof UserCandidateContractError || error instanceof NativeDecimalLifecycleContractError || error instanceof NativeUniqueKeyContractError) {
4862
5210
  fail(error.code, error.pointer);
4863
5211
  }
4864
5212
  throw error;
4865
5213
  }
5214
+ if (resource.recordComments !== undefined) {
5215
+ const policy = object(resource.recordComments, `${pointer}/recordComments`);
5216
+ exactKeys(policy, ['read', 'create'], `${pointer}/recordComments`);
5217
+ for (const mode of ['read', 'create']) {
5218
+ const list = policy[mode];
5219
+ if (!Array.isArray(list) || list.length > 20 ||
5220
+ list.some(value => typeof value !== 'string' || !value.trim()) || new Set(list).size !== list.length)
5221
+ fail('NATIVE_RECORD_COMMENTS_POLICY_INVALID', `${pointer}/recordComments/${mode}`);
5222
+ }
5223
+ }
5224
+ if (resource.recordDeletion !== undefined) {
5225
+ const policy = object(resource.recordDeletion, `${pointer}/recordDeletion`);
5226
+ exactKeys(policy, ['delete'], `${pointer}/recordDeletion`);
5227
+ const remove = policy.delete;
5228
+ if (!Array.isArray(remove) || remove.length > 20 ||
5229
+ remove.some(value => typeof value !== 'string' || !value.trim()) || new Set(remove).size !== remove.length)
5230
+ fail('NATIVE_RECORD_DELETION_DELETE_INVALID', `${pointer}/recordDeletion/delete`);
5231
+ }
5232
+ if (resource.recordPrint !== undefined) {
5233
+ const policy = object(resource.recordPrint, `${pointer}/recordPrint`);
5234
+ exactKeys(policy, ['read'], `${pointer}/recordPrint`);
5235
+ const read = policy.read;
5236
+ if (!Array.isArray(read) || read.length > 20 ||
5237
+ read.some(value => typeof value !== 'string' || !value.trim()) || new Set(read).size !== read.length)
5238
+ fail('NATIVE_RECORD_PRINT_READ_INVALID', `${pointer}/recordPrint/read`);
5239
+ }
5240
+ if (resource.workflowHistory !== undefined) {
5241
+ const policy = object(resource.workflowHistory, `${pointer}/workflowHistory`);
5242
+ exactKeys(policy, ['read'], `${pointer}/workflowHistory`);
5243
+ const read = policy.read;
5244
+ if (!Array.isArray(read) || read.length > 20 ||
5245
+ read.some(value => typeof value !== 'string' || !value.trim()) || new Set(read).size !== read.length)
5246
+ fail('NATIVE_WORKFLOW_HISTORY_READ_INVALID', `${pointer}/workflowHistory/read`);
5247
+ }
4866
5248
  const fieldCodes = new Set(fields.map(field => field.code));
4867
5249
  const capabilities = object(resource.capabilities, `${pointer}/capabilities`);
4868
5250
  exactKeys(capabilities, ['read', 'create', 'update', 'delete'], `${pointer}/capabilities`);
@@ -4935,11 +5317,11 @@ function parseCanonicalArtifact(source, pointer, invalidCode, canonicalCode) {
4935
5317
  fail(canonicalCode, pointer);
4936
5318
  return result;
4937
5319
  }
4938
- function inspectJsonBudget(value, pointer) {
5320
+ function inspectJsonBudget(value, pointer, maxNodes) {
4939
5321
  let nodes = 0;
4940
5322
  const visit = (current, path, depth) => {
4941
5323
  nodes += 1;
4942
- if (nodes > MAX_NODES)
5324
+ if (nodes > maxNodes)
4943
5325
  fail('NATIVE_ARTIFACT_NODE_LIMIT_EXCEEDED', path);
4944
5326
  if (depth > MAX_DEPTH)
4945
5327
  fail('NATIVE_ARTIFACT_DEPTH_LIMIT_EXCEEDED', path);