openyida 2026.7.19 → 2026.7.21
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/README.md +14 -0
- package/bin/yida.js +107 -18
- package/lib/app/create-app.js +12 -1
- package/lib/app/create-form/args.js +13 -0
- package/lib/app/create-form.js +154 -67
- package/lib/app/create-page.js +45 -9
- package/lib/app/get-schema.js +65 -14
- package/lib/app/import-app.js +92 -41
- package/lib/app/publish.js +289 -556
- package/lib/app/schema-field-resolution.js +370 -0
- package/lib/app/services/app-reader.js +85 -0
- package/lib/app/services/app-service.js +235 -0
- package/lib/app/services/canvas-page-compiler.js +91 -0
- package/lib/app/services/canvas-page-schema-builder.js +149 -0
- package/lib/app/services/field-bindings.js +68 -0
- package/lib/app/services/form-compiler.js +1922 -0
- package/lib/app/services/form-mode-service.js +182 -0
- package/lib/app/services/form-schema-patcher.js +333 -0
- package/lib/app/services/form-schema-reader.js +59 -0
- package/lib/app/services/form-service.js +379 -0
- package/lib/app/services/form-validation.js +562 -0
- package/lib/app/services/native-page-compiler.js +92 -0
- package/lib/app/services/native-page-schema-builder.js +428 -0
- package/lib/app/services/page-resource-service.js +185 -0
- package/lib/app/update-app.js +18 -6
- package/lib/auth/token-auth.js +34 -3
- package/lib/auth/token-store.js +22 -4
- package/lib/bridge/bridge.js +37 -5
- package/lib/core/agent-capabilities.js +5 -3
- package/lib/core/command-manifest.js +40 -0
- package/lib/core/doctor.js +15 -4
- package/lib/core/env-cmd.js +1 -1
- package/lib/core/env.js +7 -6
- package/lib/core/legacy-schema-guard.js +408 -0
- package/lib/core/locales/en.js +8 -46
- package/lib/core/locales/zh.js +8 -46
- package/lib/core/sample.js +1 -1
- package/lib/core/utils.js +334 -66
- package/lib/core/yida-client.js +104 -3
- package/lib/formula/evaluate.js +8 -66
- package/lib/formula/field-refs.js +83 -0
- package/lib/process/configure-process.js +44 -1992
- package/lib/process/create-process.js +54 -87
- package/lib/process/services/process-compiler.js +2056 -0
- package/lib/process/services/process-reader.js +232 -0
- package/lib/process/services/process-resource-service.js +571 -0
- package/lib/process/services/process-service.js +122 -0
- package/lib/process/services/process-stage-checkpoint.js +246 -0
- package/lib/report/append.js +7 -26
- package/lib/report/http.js +31 -5
- package/lib/report/index.js +10 -2
- package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
- package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
- package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
- package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
- package/lib/schema/adapters/app-adapter.js +210 -0
- package/lib/schema/adapters/form-adapter.js +1362 -0
- package/lib/schema/adapters/keys.js +19 -0
- package/lib/schema/adapters/page-adapter.js +566 -0
- package/lib/schema/adapters/process-adapter.js +519 -0
- package/lib/schema/applier.js +1878 -0
- package/lib/schema/apply-store.js +1083 -0
- package/lib/schema/command.js +645 -0
- package/lib/schema/dependency-graph.js +83 -0
- package/lib/schema/errors.js +481 -0
- package/lib/schema/hash.js +9 -0
- package/lib/schema/manifest-limits.js +177 -0
- package/lib/schema/manifest-loader.js +309 -0
- package/lib/schema/manifest-schema-v1.json +193 -0
- package/lib/schema/normalize-manifest.js +281 -0
- package/lib/schema/page-canvas-foundation.js +447 -0
- package/lib/schema/page-data-source-builder.js +561 -0
- package/lib/schema/page-foundation.js +556 -0
- package/lib/schema/page-source-loader.js +314 -0
- package/lib/schema/planner.js +691 -0
- package/lib/schema/remote-dispatch-boundary.js +39 -0
- package/lib/schema/remote-missing.js +21 -0
- package/lib/schema/remote-reader.js +189 -0
- package/lib/schema/resource-registry.js +157 -0
- package/lib/schema/server-revision.js +71 -0
- package/lib/schema/sort.js +28 -0
- package/lib/schema/state-store.js +589 -0
- package/package.json +2 -2
- package/scripts/postinstall.js +8 -12
- package/scripts/validate-package-size.js +1 -1
- package/yida-skills/SKILL.md +114 -29
- package/yida-skills/references/schema-as-code-phase1.md +99 -0
- package/yida-skills/references/setup-and-env.md +59 -59
- package/yida-skills/references/task-retrospective.md +6 -6
- package/yida-skills/skills/yida-app/SKILL.md +60 -14
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
- package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
- package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
- package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
- package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
- package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
- package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
- package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
- package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
- package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
- package/yida-skills/skills/yida-integration/SKILL.md +7 -1
- package/yida-skills/skills/yida-login/SKILL.md +50 -99
- package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
- package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
- package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
- package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
- package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
- package/yida-skills/skills/yida-report/SKILL.md +7 -1
- package/yida-skills/skills-index.json +14 -46
- package/yida-skills/skills/large-file-write/SKILL.md +0 -93
- package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
- package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
- package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
- package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
- package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isDeepStrictEqual } = require('util');
|
|
4
|
+
const { schemaError } = require('../errors');
|
|
5
|
+
const { validateSemanticKey } = require('./keys');
|
|
6
|
+
const { normalizeFormReference } = require('./form-adapter');
|
|
7
|
+
const { projectProcessManaged, readProcessDefinition } = require('../../process/services/process-reader');
|
|
8
|
+
const { validateManagedNodeBindings } = require('../../process/services/process-compiler');
|
|
9
|
+
const {
|
|
10
|
+
applyProcessResource,
|
|
11
|
+
prepareProcessResource,
|
|
12
|
+
readProcessVersionSnapshot,
|
|
13
|
+
reconcileProcessResource,
|
|
14
|
+
} = require('../../process/services/process-resource-service');
|
|
15
|
+
const {
|
|
16
|
+
advanceProcessStagedCheckpoint,
|
|
17
|
+
isProcessStageCheckpointRemoteComplete,
|
|
18
|
+
validateProcessStageCheckpoint,
|
|
19
|
+
} = require('../../process/services/process-stage-checkpoint');
|
|
20
|
+
const { hashStable } = require('../state-store');
|
|
21
|
+
const { sortStrings } = require('../sort');
|
|
22
|
+
|
|
23
|
+
const PROCESS_NODE_TYPE = 'approval';
|
|
24
|
+
const PROCESS_APPROVER = 'originator';
|
|
25
|
+
const PROCESS_ADAPTER_VERSION = 1;
|
|
26
|
+
|
|
27
|
+
const processAdapter = {
|
|
28
|
+
resourceType: 'process',
|
|
29
|
+
adapterVersion: PROCESS_ADAPTER_VERSION,
|
|
30
|
+
validate(entry, context = {}) {
|
|
31
|
+
const processKey = entry.key;
|
|
32
|
+
const definition = entry.definition;
|
|
33
|
+
const pointer = `/processes/${escapePointer(processKey)}`;
|
|
34
|
+
validateSemanticKey(processKey, pointer);
|
|
35
|
+
|
|
36
|
+
const formReference = normalizeFormReference(definition.form, context);
|
|
37
|
+
const formKey = formReference.slice('form:'.length);
|
|
38
|
+
const form = context.manifest && context.manifest.forms && context.manifest.forms[formKey];
|
|
39
|
+
if (form && form.mode !== 'process') {
|
|
40
|
+
throw schemaError('SCHEMA_PROCESS_FORM_MODE_REQUIRED', 'Process resources require their form to declare mode "process".', {
|
|
41
|
+
path: `${pointer}/form`,
|
|
42
|
+
details: {
|
|
43
|
+
key: processKey,
|
|
44
|
+
resourceType: 'process',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const nodeKeys = new Set();
|
|
50
|
+
definition.nodes.forEach((node, index) => {
|
|
51
|
+
const nodePointer = `${pointer}/nodes/${index}`;
|
|
52
|
+
validateSemanticKey(node.key, `${nodePointer}/key`);
|
|
53
|
+
if (nodeKeys.has(node.key)) {
|
|
54
|
+
throw schemaError('SCHEMA_DUPLICATE_KEY', 'Process node semantic keys must be unique within a process.', {
|
|
55
|
+
path: `${nodePointer}/key`,
|
|
56
|
+
details: { key: node.key },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
nodeKeys.add(node.key);
|
|
60
|
+
if (node.type !== PROCESS_NODE_TYPE) {
|
|
61
|
+
throw schemaError('SCHEMA_PROCESS_NODE_TYPE_UNSUPPORTED', 'Process node type is not supported by Manifest v1.', {
|
|
62
|
+
path: `${nodePointer}/type`,
|
|
63
|
+
details: { nodeType: node.type },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (node.approver !== PROCESS_APPROVER) {
|
|
67
|
+
throw schemaError('SCHEMA_PROCESS_NODE_PROPERTY_INVALID', 'Process approval nodes currently require approver "originator".', {
|
|
68
|
+
path: `${nodePointer}/approver`,
|
|
69
|
+
details: { property: 'approver' },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
normalize(entry, context = {}) {
|
|
76
|
+
this.validate(entry, context);
|
|
77
|
+
const processKey = entry.key;
|
|
78
|
+
const definition = entry.definition;
|
|
79
|
+
const pointer = `/processes/${escapePointer(processKey)}`;
|
|
80
|
+
const formReference = normalizeFormReference(definition.form, context);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
resourceType: 'process',
|
|
84
|
+
key: processKey,
|
|
85
|
+
desired: {
|
|
86
|
+
form: formReference,
|
|
87
|
+
nodes: definition.nodes.map(node => ({
|
|
88
|
+
key: node.key,
|
|
89
|
+
type: PROCESS_NODE_TYPE,
|
|
90
|
+
name: node.name,
|
|
91
|
+
approver: PROCESS_APPROVER,
|
|
92
|
+
})),
|
|
93
|
+
},
|
|
94
|
+
dependsOn: [formReference],
|
|
95
|
+
dependencySources: {
|
|
96
|
+
[formReference]: [{
|
|
97
|
+
kind: 'reference',
|
|
98
|
+
path: `${pointer}/form`,
|
|
99
|
+
}],
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
async readObserved(binding, context = {}) {
|
|
105
|
+
const bindings = normalizeProcessBindings(binding);
|
|
106
|
+
const versionSnapshot = await readProcessVersionSnapshot(context, bindings);
|
|
107
|
+
if (!matchesProcessIdentity(versionSnapshot.active, bindings)) {
|
|
108
|
+
return { versionSnapshot };
|
|
109
|
+
}
|
|
110
|
+
const reader = context.services && context.services.readProcessDefinition || readProcessDefinition;
|
|
111
|
+
assertAdapterDispatchBoundary(context, 'before');
|
|
112
|
+
const definitionResult = await reader(context, bindings);
|
|
113
|
+
assertAdapterDispatchBoundary(context, 'after');
|
|
114
|
+
return {
|
|
115
|
+
definitionResult,
|
|
116
|
+
versionSnapshot,
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
projectObserved(observed, binding, context = {}) {
|
|
121
|
+
const bindings = normalizeProcessBindings(binding);
|
|
122
|
+
const desired = context.resource && context.resource.desired || context.desired;
|
|
123
|
+
const versionObservation = projectVersionObservation(observed && observed.versionSnapshot, bindings);
|
|
124
|
+
const managed = versionObservation.observedIdentityMatchesBindings
|
|
125
|
+
? projectProcessManaged(observed.definitionResult, {
|
|
126
|
+
desired,
|
|
127
|
+
formUuid: bindings.formUuid,
|
|
128
|
+
nodeBindings: bindings.nodeBindings,
|
|
129
|
+
})
|
|
130
|
+
: clonePlain(binding && binding.lastApplied || {});
|
|
131
|
+
return {
|
|
132
|
+
bindings,
|
|
133
|
+
managed,
|
|
134
|
+
...versionObservation,
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
classifyObservedConflict(input) {
|
|
139
|
+
const observed = input.observedResource || {};
|
|
140
|
+
if (observed.observedIdentityMatchesBindings === false) {
|
|
141
|
+
return 'REMOTE_IDENTITY_DRIFT';
|
|
142
|
+
}
|
|
143
|
+
if (
|
|
144
|
+
observed.observedDraftCount > 0 &&
|
|
145
|
+
input.desiredChanged === true &&
|
|
146
|
+
input.observedMatchesDesired !== true
|
|
147
|
+
) {
|
|
148
|
+
return 'REMOTE_DRAFT_EXISTS';
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
async prepareOperation(input, context = {}) {
|
|
154
|
+
const operationInput = buildProcessOperationInput(
|
|
155
|
+
input.resource,
|
|
156
|
+
input.stateResource,
|
|
157
|
+
context,
|
|
158
|
+
input.recovery
|
|
159
|
+
);
|
|
160
|
+
const prepare = context.services && context.services.prepareProcessResource || prepareProcessResource;
|
|
161
|
+
try {
|
|
162
|
+
return await prepare(context, operationInput);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error && error.code === 'PROCESS_RESOURCE_RECONCILIATION_REQUIRED') {
|
|
165
|
+
throw schemaError('SCHEMA_RECONCILIATION_REQUIRED', 'Process checkpoint evidence is incomplete.', {
|
|
166
|
+
details: {
|
|
167
|
+
operation: input.operation,
|
|
168
|
+
resourceType: 'process',
|
|
169
|
+
key: input.resource && input.resource.key,
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (error && (
|
|
174
|
+
error.code === 'PROCESS_RESOURCE_VERSION_CONFLICT' ||
|
|
175
|
+
error.code === 'PROCESS_RESOURCE_DRAFT_CONFLICT'
|
|
176
|
+
)) {
|
|
177
|
+
throw schemaError('SCHEMA_APPLY_JIT_CONFLICT', 'Current process version state no longer permits the reviewed write.', {
|
|
178
|
+
details: {
|
|
179
|
+
operation: input.operation,
|
|
180
|
+
resourceType: 'process',
|
|
181
|
+
key: input.resource && input.resource.key,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (error && error.code === 'PROCESS_RESOURCE_COMPILE_FAILED') {
|
|
186
|
+
throw schemaError('SCHEMA_PROCESS_COMPILE_FAILED', 'Process desired state could not be compiled.', {
|
|
187
|
+
details: {
|
|
188
|
+
resourceType: 'process',
|
|
189
|
+
key: input.resource && input.resource.key,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
throw schemaError('SCHEMA_REMOTE_READ_FAILED', 'Process version state could not be read.', {
|
|
194
|
+
details: {
|
|
195
|
+
resourceType: 'process',
|
|
196
|
+
key: input.resource && input.resource.key,
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
async create(desired, context = {}) {
|
|
203
|
+
return runProcessWrite(context);
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
async update(desired, observed, binding, context = {}) {
|
|
207
|
+
return runProcessWrite(context);
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
buildBindings(result, context = {}) {
|
|
211
|
+
const checkpoint = context.stageCheckpoint || {};
|
|
212
|
+
return requireCompleteProcessBindings({
|
|
213
|
+
appType: result && result.appType,
|
|
214
|
+
formUuid: result && result.formUuid,
|
|
215
|
+
nodeBindings: result && result.nodeBindings || checkpoint.nodeBindings,
|
|
216
|
+
processCode: result && result.processCode || checkpoint.processCode,
|
|
217
|
+
processId: result && result.processId || checkpoint.processId,
|
|
218
|
+
processVersion: result && result.processVersion !== undefined
|
|
219
|
+
? result.processVersion
|
|
220
|
+
: checkpoint.processVersion,
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
verify(projection, context = {}) {
|
|
225
|
+
if (
|
|
226
|
+
!projection ||
|
|
227
|
+
projection.observedIdentityMatchesBindings !== true ||
|
|
228
|
+
!isDeepStrictEqual(projection.managed, context.resource && context.resource.desired)
|
|
229
|
+
) {
|
|
230
|
+
throw schemaError('SCHEMA_APPLY_VERIFY_FAILED', 'Process observed projection does not match desired managed state.', {
|
|
231
|
+
details: {
|
|
232
|
+
resourceType: 'process',
|
|
233
|
+
key: context.resource && context.resource.key,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return projection;
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
async reconcileStaged(input, context = {}) {
|
|
241
|
+
const operationInput = buildProcessOperationInput(
|
|
242
|
+
input.resource,
|
|
243
|
+
input.stateResource,
|
|
244
|
+
context,
|
|
245
|
+
{ stageCheckpoint: input.stageCheckpoint }
|
|
246
|
+
);
|
|
247
|
+
const reconcile = context.services && context.services.reconcileProcessResource || reconcileProcessResource;
|
|
248
|
+
return reconcile(context, {
|
|
249
|
+
...operationInput,
|
|
250
|
+
operation: input.operation,
|
|
251
|
+
resource: input.resource,
|
|
252
|
+
stageCheckpoint: input.stageCheckpoint,
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
advanceStagedCheckpoint(previous, event, input) {
|
|
257
|
+
return advanceProcessStagedCheckpoint(previous, event, input);
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
isStagedCheckpointRemoteComplete(checkpoint, context = {}) {
|
|
261
|
+
return isProcessStageCheckpointRemoteComplete(checkpoint, {
|
|
262
|
+
desiredManagedHash: context.desiredManagedHash,
|
|
263
|
+
});
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
validateStageCheckpoint(checkpoint, context = {}) {
|
|
267
|
+
return validateProcessStageCheckpoint(checkpoint, context);
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
validateStateResource(entry) {
|
|
271
|
+
validateProcessStateResource(entry);
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
function assertAdapterDispatchBoundary(context, phase) {
|
|
276
|
+
if (typeof context.assertRemoteDispatchBoundary === 'function') {
|
|
277
|
+
context.assertRemoteDispatchBoundary(phase);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function runProcessWrite(context) {
|
|
282
|
+
const writer = context.services && context.services.applyProcessResource || applyProcessResource;
|
|
283
|
+
return writer({
|
|
284
|
+
...context,
|
|
285
|
+
checkpointStage: context.checkpointStage,
|
|
286
|
+
}, {
|
|
287
|
+
prepared: context.prepared,
|
|
288
|
+
recovery: context.recovery,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function projectVersionObservation(snapshot, bindings) {
|
|
293
|
+
if (!snapshot || !snapshot.active || !Array.isArray(snapshot.drafts)) {
|
|
294
|
+
throw schemaError('SCHEMA_REMOTE_PROJECT_FAILED', 'Process version observation is invalid.', {
|
|
295
|
+
details: { resourceType: 'process' },
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
const activeIdentity = {
|
|
299
|
+
processId: snapshot.active.processId,
|
|
300
|
+
processVersion: snapshot.active.processVersion,
|
|
301
|
+
};
|
|
302
|
+
const draftIdentities = snapshot.drafts.map(row => ({
|
|
303
|
+
processId: row.processId,
|
|
304
|
+
processVersion: row.processVersion,
|
|
305
|
+
}));
|
|
306
|
+
return {
|
|
307
|
+
observedDraftCount: draftIdentities.length,
|
|
308
|
+
observedDraftHash: hashStable({
|
|
309
|
+
adapterVersion: PROCESS_ADAPTER_VERSION,
|
|
310
|
+
identities: draftIdentities,
|
|
311
|
+
resourceType: 'process',
|
|
312
|
+
}),
|
|
313
|
+
observedIdentityHash: hashStable({
|
|
314
|
+
adapterVersion: PROCESS_ADAPTER_VERSION,
|
|
315
|
+
identity: activeIdentity,
|
|
316
|
+
resourceType: 'process',
|
|
317
|
+
}),
|
|
318
|
+
observedIdentityMatchesBindings: matchesProcessIdentity(snapshot.active, bindings),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function matchesProcessIdentity(identity, bindings) {
|
|
323
|
+
return !!(
|
|
324
|
+
identity &&
|
|
325
|
+
identity.processId === bindings.processId &&
|
|
326
|
+
identity.processVersion === bindings.processVersion
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function buildProcessOperationInput(resource, stateResource, context, recovery) {
|
|
331
|
+
const formKey = resource && resource.desired && String(resource.desired.form).slice('form:'.length);
|
|
332
|
+
const formState = context.state && context.state.resources && context.state.resources.form && context.state.resources.form[formKey];
|
|
333
|
+
const formBindings = formState && formState.bindings || {};
|
|
334
|
+
if (!isNonEmptyString(formBindings.appType) || !isNonEmptyString(formBindings.formUuid) || !isNonEmptyString(formBindings.processCode)) {
|
|
335
|
+
throw schemaError('SCHEMA_STATE_RESOURCE_BINDING_MISSING', 'Process form dependency bindings are incomplete.', {
|
|
336
|
+
details: { resourceType: 'form', key: formKey },
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
const stageCheckpoint = recovery && recovery.stageCheckpoint;
|
|
340
|
+
if (stageCheckpoint && stageCheckpoint.processCode !== formBindings.processCode) {
|
|
341
|
+
throw schemaError('SCHEMA_RECONCILIATION_REQUIRED', 'Process checkpoint no longer matches its form dependency.', {
|
|
342
|
+
details: {
|
|
343
|
+
operation: 'reconcile',
|
|
344
|
+
resourceType: 'process',
|
|
345
|
+
key: resource && resource.key,
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
const stateBindings = stateResource ? normalizeProcessBindings(stateResource) : null;
|
|
350
|
+
if (stateBindings && (
|
|
351
|
+
stateBindings.appType !== formBindings.appType ||
|
|
352
|
+
stateBindings.formUuid !== formBindings.formUuid ||
|
|
353
|
+
stateBindings.processCode !== formBindings.processCode
|
|
354
|
+
)) {
|
|
355
|
+
throw schemaError('SCHEMA_APPLY_JIT_CONFLICT', 'Process bindings no longer match the form dependency.', {
|
|
356
|
+
details: {
|
|
357
|
+
operation: 'update',
|
|
358
|
+
resourceType: 'process',
|
|
359
|
+
key: resource && resource.key,
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
appType: formBindings.appType,
|
|
365
|
+
baseUrl: context.authRef && context.authRef.baseUrl || context.environment && context.environment.endpoint,
|
|
366
|
+
desired: resource.desired,
|
|
367
|
+
existingBindings: stateBindings && stateBindings.nodeBindings || {},
|
|
368
|
+
formUuid: formBindings.formUuid,
|
|
369
|
+
processCode: formBindings.processCode,
|
|
370
|
+
recovery,
|
|
371
|
+
stateBindings,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function normalizeProcessBindings(binding) {
|
|
376
|
+
const source = binding && binding.bindings ? binding.bindings : binding || {};
|
|
377
|
+
return {
|
|
378
|
+
appType: source.appType,
|
|
379
|
+
formUuid: source.formUuid,
|
|
380
|
+
nodeBindings: cloneNodeBindings(source.nodeBindings || {}),
|
|
381
|
+
processCode: source.processCode,
|
|
382
|
+
processId: source.processId,
|
|
383
|
+
processVersion: source.processVersion,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function requireCompleteProcessBindings(value) {
|
|
388
|
+
const normalized = normalizeProcessBindings(value);
|
|
389
|
+
if (
|
|
390
|
+
!isNonEmptyString(normalized.appType) ||
|
|
391
|
+
!isNonEmptyString(normalized.formUuid) ||
|
|
392
|
+
!isNonEmptyString(normalized.processCode) ||
|
|
393
|
+
!isNonEmptyString(normalized.processId) ||
|
|
394
|
+
!Number.isInteger(normalized.processVersion) ||
|
|
395
|
+
normalized.processVersion < 0 ||
|
|
396
|
+
Object.keys(normalized.nodeBindings).length === 0
|
|
397
|
+
) {
|
|
398
|
+
throw schemaError('SCHEMA_APPLY_VERIFY_FAILED', 'Process operation did not produce complete bindings.', {
|
|
399
|
+
details: { resourceType: 'process' },
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return normalized;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function validateProcessStateResource(entry) {
|
|
406
|
+
const state = entry.state || {};
|
|
407
|
+
validateProcessBindings(state.bindings, `${entry.path}/bindings`);
|
|
408
|
+
if (state.lastApplied !== undefined) {
|
|
409
|
+
validateManagedProcess(state.lastApplied, `${entry.path}/lastApplied`);
|
|
410
|
+
if (!validateManagedNodeBindings(state.lastApplied.nodes, state.bindings.nodeBindings)) {
|
|
411
|
+
stateInvalid(`${entry.path}/bindings/nodeBindings`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function validateProcessBindings(bindings, pointer) {
|
|
417
|
+
assertObject(bindings, pointer);
|
|
418
|
+
assertAllowedKeys(bindings, [
|
|
419
|
+
'appType',
|
|
420
|
+
'formUuid',
|
|
421
|
+
'nodeBindings',
|
|
422
|
+
'processCode',
|
|
423
|
+
'processId',
|
|
424
|
+
'processVersion',
|
|
425
|
+
], pointer);
|
|
426
|
+
for (const property of ['appType', 'formUuid', 'processCode', 'processId']) {
|
|
427
|
+
if (!isNonEmptyString(bindings[property])) {
|
|
428
|
+
stateInvalid(`${pointer}/${property}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (!Number.isInteger(bindings.processVersion) || bindings.processVersion < 0) {
|
|
432
|
+
stateInvalid(`${pointer}/processVersion`);
|
|
433
|
+
}
|
|
434
|
+
assertObject(bindings.nodeBindings, `${pointer}/nodeBindings`);
|
|
435
|
+
if (Object.keys(bindings.nodeBindings).length === 0) {
|
|
436
|
+
stateInvalid(`${pointer}/nodeBindings`);
|
|
437
|
+
}
|
|
438
|
+
const bindingKeys = sortStrings(Object.keys(bindings.nodeBindings));
|
|
439
|
+
for (const key of bindingKeys) {
|
|
440
|
+
validateSemanticKey(key, `${pointer}/nodeBindings/${escapePointer(key)}`);
|
|
441
|
+
}
|
|
442
|
+
const stateNodes = bindingKeys.map(key => ({ key, type: PROCESS_NODE_TYPE }));
|
|
443
|
+
if (!validateManagedNodeBindings(stateNodes, bindings.nodeBindings)) {
|
|
444
|
+
stateInvalid(`${pointer}/nodeBindings`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function validateManagedProcess(value, pointer) {
|
|
449
|
+
assertObject(value, pointer);
|
|
450
|
+
assertAllowedKeys(value, ['form', 'nodes'], pointer);
|
|
451
|
+
if (!isNonEmptyString(value.form) || !value.form.startsWith('form:')) {
|
|
452
|
+
stateInvalid(`${pointer}/form`);
|
|
453
|
+
}
|
|
454
|
+
if (!Array.isArray(value.nodes) || value.nodes.length === 0) {
|
|
455
|
+
stateInvalid(`${pointer}/nodes`);
|
|
456
|
+
}
|
|
457
|
+
value.nodes.forEach((node, index) => {
|
|
458
|
+
const nodePointer = `${pointer}/nodes/${index}`;
|
|
459
|
+
assertObject(node, nodePointer);
|
|
460
|
+
assertAllowedKeys(node, ['key', 'type', 'name', 'approver'], nodePointer);
|
|
461
|
+
validateSemanticKey(node.key, `${nodePointer}/key`);
|
|
462
|
+
if (
|
|
463
|
+
node.type !== PROCESS_NODE_TYPE ||
|
|
464
|
+
node.approver !== PROCESS_APPROVER ||
|
|
465
|
+
!isNonEmptyString(node.name)
|
|
466
|
+
) {
|
|
467
|
+
stateInvalid(nodePointer);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function cloneNodeBindings(value) {
|
|
473
|
+
const result = Object.create(null);
|
|
474
|
+
for (const key of sortStrings(Object.keys(value || {}))) {
|
|
475
|
+
const node = value[key] || {};
|
|
476
|
+
result[key] = {
|
|
477
|
+
nodeId: node.nodeId,
|
|
478
|
+
componentName: node.componentName,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function assertObject(value, pointer) {
|
|
485
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
486
|
+
stateInvalid(pointer);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function assertAllowedKeys(value, allowedKeys, pointer) {
|
|
491
|
+
const allowed = new Set(allowedKeys);
|
|
492
|
+
for (const key of Object.keys(value || {})) {
|
|
493
|
+
if (!allowed.has(key)) {
|
|
494
|
+
stateInvalid(`${pointer}/${escapePointer(key)}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function stateInvalid(path) {
|
|
500
|
+
throw schemaError('SCHEMA_STATE_INVALID', 'Process state resource is invalid.', { path });
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function isNonEmptyString(value) {
|
|
504
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function clonePlain(value) {
|
|
508
|
+
return JSON.parse(JSON.stringify(value));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function escapePointer(value) {
|
|
512
|
+
return String(value).replace(/~/g, '~0').replace(/\//g, '~1');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
module.exports = {
|
|
516
|
+
PROCESS_APPROVER,
|
|
517
|
+
PROCESS_NODE_TYPE,
|
|
518
|
+
processAdapter,
|
|
519
|
+
};
|