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.
- package/dist/ai.d.ts +1 -1
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +4 -3
- package/dist/ai.js.map +1 -1
- package/dist/browser.d.ts +13 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +13 -0
- package/dist/browser.js.map +1 -1
- package/dist/concurrency.d.ts +3 -3
- package/dist/concurrency.d.ts.map +1 -1
- package/dist/data-field-validation.d.ts.map +1 -1
- package/dist/data-field-validation.js +17 -2
- package/dist/data-field-validation.js.map +1 -1
- package/dist/field-value-schemas.d.ts +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/native-compiler/actor-authority.d.ts +11 -0
- package/dist/native-compiler/actor-authority.d.ts.map +1 -0
- package/dist/native-compiler/actor-authority.js +56 -0
- package/dist/native-compiler/actor-authority.js.map +1 -0
- package/dist/native-compiler/artifact-capacity.d.ts +12 -0
- package/dist/native-compiler/artifact-capacity.d.ts.map +1 -0
- package/dist/native-compiler/artifact-capacity.js +34 -0
- package/dist/native-compiler/artifact-capacity.js.map +1 -0
- package/dist/native-compiler/authenticated-public-read.d.ts +18 -0
- package/dist/native-compiler/authenticated-public-read.d.ts.map +1 -0
- package/dist/native-compiler/authenticated-public-read.js +79 -0
- package/dist/native-compiler/authenticated-public-read.js.map +1 -0
- package/dist/native-compiler/compiler.d.ts +3 -22
- package/dist/native-compiler/compiler.d.ts.map +1 -1
- package/dist/native-compiler/compiler.js +441 -59
- package/dist/native-compiler/compiler.js.map +1 -1
- package/dist/native-compiler/data-business-command.d.ts +44 -0
- package/dist/native-compiler/data-business-command.d.ts.map +1 -0
- package/dist/native-compiler/data-business-command.js +88 -0
- package/dist/native-compiler/data-business-command.js.map +1 -0
- package/dist/native-compiler/data-capacity.d.ts +14 -3
- package/dist/native-compiler/data-capacity.d.ts.map +1 -1
- package/dist/native-compiler/data-capacity.js +11 -3
- package/dist/native-compiler/data-capacity.js.map +1 -1
- package/dist/native-compiler/data-field.d.ts +24 -0
- package/dist/native-compiler/data-field.d.ts.map +1 -1
- package/dist/native-compiler/data-field.js +23 -2
- package/dist/native-compiler/data-field.js.map +1 -1
- package/dist/native-compiler/data-surface.d.ts +1 -1
- package/dist/native-compiler/data-surface.d.ts.map +1 -1
- package/dist/native-compiler/data-surface.js +2 -0
- package/dist/native-compiler/data-surface.js.map +1 -1
- package/dist/native-compiler/date-event-projection.d.ts +13 -0
- package/dist/native-compiler/date-event-projection.d.ts.map +1 -0
- package/dist/native-compiler/date-event-projection.js +71 -0
- package/dist/native-compiler/date-event-projection.js.map +1 -0
- package/dist/native-compiler/declaration-capacity.d.ts +60 -0
- package/dist/native-compiler/declaration-capacity.d.ts.map +1 -0
- package/dist/native-compiler/declaration-capacity.js +32 -0
- package/dist/native-compiler/declaration-capacity.js.map +1 -0
- package/dist/native-compiler/development-backend.d.ts +84 -0
- package/dist/native-compiler/development-backend.d.ts.map +1 -0
- package/dist/native-compiler/development-backend.js +77 -0
- package/dist/native-compiler/development-backend.js.map +1 -0
- package/dist/native-compiler/development-configuration.d.ts +65 -0
- package/dist/native-compiler/development-configuration.d.ts.map +1 -0
- package/dist/native-compiler/development-configuration.js +143 -0
- package/dist/native-compiler/development-configuration.js.map +1 -0
- package/dist/native-compiler/index.cjs +6 -6
- package/dist/native-compiler/index.d.ts +22 -0
- package/dist/native-compiler/index.d.ts.map +1 -1
- package/dist/native-compiler/index.js +6 -6
- package/dist/native-compiler/managed-concurrency.js +2 -2
- package/dist/native-compiler/managed-concurrency.js.map +1 -1
- package/dist/native-compiler/record-set-guard.d.ts +5 -0
- package/dist/native-compiler/record-set-guard.d.ts.map +1 -0
- package/dist/native-compiler/record-set-guard.js +27 -0
- package/dist/native-compiler/record-set-guard.js.map +1 -0
- package/dist/native-compiler/resource-view.d.ts +4 -0
- package/dist/native-compiler/resource-view.d.ts.map +1 -0
- package/dist/native-compiler/resource-view.js +20 -0
- package/dist/native-compiler/resource-view.js.map +1 -0
- package/dist/native-compiler/unique-keys.d.ts +21 -0
- package/dist/native-compiler/unique-keys.d.ts.map +1 -1
- package/dist/native-compiler/unique-keys.js +17 -3
- package/dist/native-compiler/unique-keys.js.map +1 -1
- package/dist/native-compiler/user-candidates.d.ts +80 -0
- package/dist/native-compiler/user-candidates.d.ts.map +1 -0
- package/dist/native-compiler/user-candidates.js +197 -0
- package/dist/native-compiler/user-candidates.js.map +1 -0
- package/dist/native-compiler/workflow-approval-empty.d.ts +28 -0
- package/dist/native-compiler/workflow-approval-empty.d.ts.map +1 -0
- package/dist/native-compiler/workflow-approval-empty.js +34 -0
- package/dist/native-compiler/workflow-approval-empty.js.map +1 -0
- package/dist/native-compiler/workflow-approved-delegation.d.ts +39 -0
- package/dist/native-compiler/workflow-approved-delegation.d.ts.map +1 -0
- package/dist/native-compiler/workflow-approved-delegation.js +94 -0
- package/dist/native-compiler/workflow-approved-delegation.js.map +1 -0
- package/dist/native-compiler/workflow-business-command.d.ts +108 -0
- package/dist/native-compiler/workflow-business-command.d.ts.map +1 -0
- package/dist/native-compiler/workflow-business-command.js +62 -0
- package/dist/native-compiler/workflow-business-command.js.map +1 -0
- package/dist/native-compiler/workflow-completion-deadline.d.ts +41 -0
- package/dist/native-compiler/workflow-completion-deadline.d.ts.map +1 -0
- package/dist/native-compiler/workflow-completion-deadline.js +40 -0
- package/dist/native-compiler/workflow-completion-deadline.js.map +1 -0
- package/dist/native-compiler/workflow-correction.d.ts +16 -0
- package/dist/native-compiler/workflow-correction.d.ts.map +1 -0
- package/dist/native-compiler/workflow-correction.js +42 -0
- package/dist/native-compiler/workflow-correction.js.map +1 -0
- package/dist/native-compiler/workflow-delegation-administration.d.ts +73 -0
- package/dist/native-compiler/workflow-delegation-administration.d.ts.map +1 -1
- package/dist/native-compiler/workflow-delegation-administration.js +6 -4
- package/dist/native-compiler/workflow-delegation-administration.js.map +1 -1
- package/dist/native-compiler/workflow-graph.d.ts +21 -2
- package/dist/native-compiler/workflow-graph.d.ts.map +1 -1
- package/dist/native-compiler/workflow-graph.js +20 -3
- package/dist/native-compiler/workflow-graph.js.map +1 -1
- package/dist/native-compiler/workflow-initiator-approval.d.ts +32 -0
- package/dist/native-compiler/workflow-initiator-approval.d.ts.map +1 -0
- package/dist/native-compiler/workflow-initiator-approval.js +45 -0
- package/dist/native-compiler/workflow-initiator-approval.js.map +1 -0
- package/dist/native-compiler/workflow-instance-policy.d.ts +2 -0
- package/dist/native-compiler/workflow-instance-policy.d.ts.map +1 -1
- package/dist/native-compiler/workflow-instance-policy.js +9 -4
- package/dist/native-compiler/workflow-instance-policy.js.map +1 -1
- package/dist/native-compiler/workflow-launch-preflight.d.ts +22 -0
- package/dist/native-compiler/workflow-launch-preflight.d.ts.map +1 -0
- package/dist/native-compiler/workflow-launch-preflight.js +30 -0
- package/dist/native-compiler/workflow-launch-preflight.js.map +1 -0
- package/dist/native-compiler/workflow-native-stage.d.ts +69 -0
- package/dist/native-compiler/workflow-native-stage.d.ts.map +1 -0
- package/dist/native-compiler/workflow-native-stage.js +42 -0
- package/dist/native-compiler/workflow-native-stage.js.map +1 -0
- package/dist/native-compiler/workflow-node-administration.d.ts +11 -1
- package/dist/native-compiler/workflow-node-administration.d.ts.map +1 -1
- package/dist/native-compiler/workflow-node-administration.js +25 -5
- package/dist/native-compiler/workflow-node-administration.js.map +1 -1
- package/dist/native-compiler/workflow-owned-subject.d.ts +60 -0
- package/dist/native-compiler/workflow-owned-subject.d.ts.map +1 -0
- package/dist/native-compiler/workflow-owned-subject.js +69 -0
- package/dist/native-compiler/workflow-owned-subject.js.map +1 -0
- package/dist/native-compiler/workflow-rejection-notification.d.ts +31 -0
- package/dist/native-compiler/workflow-rejection-notification.d.ts.map +1 -0
- package/dist/native-compiler/workflow-rejection-notification.js +47 -0
- package/dist/native-compiler/workflow-rejection-notification.js.map +1 -0
- package/dist/native-compiler/workflow-role-union.d.ts +31 -0
- package/dist/native-compiler/workflow-role-union.d.ts.map +1 -0
- package/dist/native-compiler/workflow-role-union.js +38 -0
- package/dist/native-compiler/workflow-role-union.js.map +1 -0
- package/dist/native-compiler/workflow-task-page.d.ts +10 -3
- package/dist/native-compiler/workflow-task-page.d.ts.map +1 -1
- package/dist/native-compiler/workflow-task-page.js +5 -3
- package/dist/native-compiler/workflow-task-page.js.map +1 -1
- package/dist/native-version.d.ts +36 -1
- package/dist/native-version.d.ts.map +1 -1
- package/dist/native-version.js +36 -1
- package/dist/native-version.js.map +1 -1
- package/dist/native.d.ts +24 -2
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/references.d.ts +2 -0
- package/dist/references.d.ts.map +1 -1
- package/dist/schemas.d.ts +10118 -5656
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +379 -17
- package/dist/schemas.js.map +1 -1
- package/dist/surface.d.ts +4 -2
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +1 -19
- package/dist/surface.js.map +1 -1
- package/dist/types.d.ts +371 -13
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -2
- package/dist/types.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +61 -2
- package/dist/validation.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
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 =
|
|
90
|
-
const MAX_CONTRACT_BYTES =
|
|
91
|
-
const MAX_DEPTH =
|
|
92
|
-
const
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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 =
|
|
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:
|
|
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',
|
|
793
|
-
boundedArray(authz.roles, '/config/authz/roles',
|
|
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',
|
|
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',
|
|
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',
|
|
833
|
-
boundedArray(workflows.bindings, '/config/workflows/bindings',
|
|
834
|
-
boundedArray(workflows.activations, '/config/workflows/activations',
|
|
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',
|
|
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
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
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`,
|
|
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
|
-
|
|
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
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
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',
|
|
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',
|
|
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',
|
|
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 >
|
|
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);
|