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.
Files changed (139) hide show
  1. package/README.md +14 -0
  2. package/bin/yida.js +107 -18
  3. package/lib/app/create-app.js +12 -1
  4. package/lib/app/create-form/args.js +13 -0
  5. package/lib/app/create-form.js +154 -67
  6. package/lib/app/create-page.js +45 -9
  7. package/lib/app/get-schema.js +65 -14
  8. package/lib/app/import-app.js +92 -41
  9. package/lib/app/publish.js +289 -556
  10. package/lib/app/schema-field-resolution.js +370 -0
  11. package/lib/app/services/app-reader.js +85 -0
  12. package/lib/app/services/app-service.js +235 -0
  13. package/lib/app/services/canvas-page-compiler.js +91 -0
  14. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  15. package/lib/app/services/field-bindings.js +68 -0
  16. package/lib/app/services/form-compiler.js +1922 -0
  17. package/lib/app/services/form-mode-service.js +182 -0
  18. package/lib/app/services/form-schema-patcher.js +333 -0
  19. package/lib/app/services/form-schema-reader.js +59 -0
  20. package/lib/app/services/form-service.js +379 -0
  21. package/lib/app/services/form-validation.js +562 -0
  22. package/lib/app/services/native-page-compiler.js +92 -0
  23. package/lib/app/services/native-page-schema-builder.js +428 -0
  24. package/lib/app/services/page-resource-service.js +185 -0
  25. package/lib/app/update-app.js +18 -6
  26. package/lib/auth/token-auth.js +34 -3
  27. package/lib/auth/token-store.js +22 -4
  28. package/lib/bridge/bridge.js +37 -5
  29. package/lib/core/agent-capabilities.js +5 -3
  30. package/lib/core/command-manifest.js +40 -0
  31. package/lib/core/doctor.js +15 -4
  32. package/lib/core/env-cmd.js +1 -1
  33. package/lib/core/env.js +7 -6
  34. package/lib/core/legacy-schema-guard.js +408 -0
  35. package/lib/core/locales/en.js +8 -46
  36. package/lib/core/locales/zh.js +8 -46
  37. package/lib/core/sample.js +1 -1
  38. package/lib/core/utils.js +334 -66
  39. package/lib/core/yida-client.js +104 -3
  40. package/lib/formula/evaluate.js +8 -66
  41. package/lib/formula/field-refs.js +83 -0
  42. package/lib/process/configure-process.js +44 -1992
  43. package/lib/process/create-process.js +54 -87
  44. package/lib/process/services/process-compiler.js +2056 -0
  45. package/lib/process/services/process-reader.js +232 -0
  46. package/lib/process/services/process-resource-service.js +571 -0
  47. package/lib/process/services/process-service.js +122 -0
  48. package/lib/process/services/process-stage-checkpoint.js +246 -0
  49. package/lib/report/append.js +7 -26
  50. package/lib/report/http.js +31 -5
  51. package/lib/report/index.js +10 -2
  52. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  53. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  54. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  55. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
  56. package/lib/schema/adapters/app-adapter.js +210 -0
  57. package/lib/schema/adapters/form-adapter.js +1362 -0
  58. package/lib/schema/adapters/keys.js +19 -0
  59. package/lib/schema/adapters/page-adapter.js +566 -0
  60. package/lib/schema/adapters/process-adapter.js +519 -0
  61. package/lib/schema/applier.js +1878 -0
  62. package/lib/schema/apply-store.js +1083 -0
  63. package/lib/schema/command.js +645 -0
  64. package/lib/schema/dependency-graph.js +83 -0
  65. package/lib/schema/errors.js +481 -0
  66. package/lib/schema/hash.js +9 -0
  67. package/lib/schema/manifest-limits.js +177 -0
  68. package/lib/schema/manifest-loader.js +309 -0
  69. package/lib/schema/manifest-schema-v1.json +193 -0
  70. package/lib/schema/normalize-manifest.js +281 -0
  71. package/lib/schema/page-canvas-foundation.js +447 -0
  72. package/lib/schema/page-data-source-builder.js +561 -0
  73. package/lib/schema/page-foundation.js +556 -0
  74. package/lib/schema/page-source-loader.js +314 -0
  75. package/lib/schema/planner.js +691 -0
  76. package/lib/schema/remote-dispatch-boundary.js +39 -0
  77. package/lib/schema/remote-missing.js +21 -0
  78. package/lib/schema/remote-reader.js +189 -0
  79. package/lib/schema/resource-registry.js +157 -0
  80. package/lib/schema/server-revision.js +71 -0
  81. package/lib/schema/sort.js +28 -0
  82. package/lib/schema/state-store.js +589 -0
  83. package/package.json +2 -2
  84. package/scripts/postinstall.js +8 -12
  85. package/scripts/validate-package-size.js +1 -1
  86. package/yida-skills/SKILL.md +114 -29
  87. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  88. package/yida-skills/references/setup-and-env.md +59 -59
  89. package/yida-skills/references/task-retrospective.md +6 -6
  90. package/yida-skills/skills/yida-app/SKILL.md +60 -14
  91. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  92. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  93. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
  94. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  95. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  96. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  97. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  98. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  99. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  100. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  101. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  102. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  103. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  104. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  105. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  106. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  107. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  108. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  109. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  110. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  111. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  112. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  113. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  114. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  115. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  116. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  117. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  118. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  119. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  120. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  121. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  122. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  123. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  124. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  125. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  126. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  127. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  128. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  129. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  130. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  131. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  132. package/yida-skills/skills/yida-report/SKILL.md +7 -1
  133. package/yida-skills/skills-index.json +14 -46
  134. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  135. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  136. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  137. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  138. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  139. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
@@ -0,0 +1,1362 @@
1
+ 'use strict';
2
+
3
+ const { schemaError } = require('../errors');
4
+ const { createSchemaHash, normalizeLabel, normalizeSchemaFields } = require('../../app/schema-field-resolution');
5
+ const {
6
+ ASSOCIATION_FORM_FIELD_TYPE,
7
+ TABLE_FIELD_TYPE,
8
+ isOptionFieldType,
9
+ isSupportedFieldType,
10
+ normalizeFieldType,
11
+ } = require('../../app/services/form-compiler');
12
+ const { readFormSchema } = require('../../app/services/form-schema-reader');
13
+ const {
14
+ convertFormToProcess,
15
+ readFormMode,
16
+ } = require('../../app/services/form-mode-service');
17
+ const {
18
+ createFormResource,
19
+ prepareFormResourceUpdate,
20
+ resumeFormResource,
21
+ updateFormResource,
22
+ } = require('../../app/services/form-service');
23
+ const { requireSchemaServerRevision } = require('../server-revision');
24
+ const { sortStrings } = require('../sort');
25
+ const { validateSemanticKey } = require('./keys');
26
+ const { isDeepStrictEqual } = require('util');
27
+
28
+ const FIELD_COPY_KEYS = [
29
+ 'label',
30
+ 'required',
31
+ ];
32
+
33
+ const formAdapter = {
34
+ resourceType: 'form',
35
+ adapterVersion: 1,
36
+ validate(entry) {
37
+ const formKey = entry.key;
38
+ const form = entry.definition;
39
+ const pointer = `/forms/${escapePointer(formKey)}`;
40
+ validateSemanticKey(formKey, pointer);
41
+ if (!form.fields || typeof form.fields !== 'object' || Array.isArray(form.fields) || Object.keys(form.fields).length === 0) {
42
+ throw schemaError('SCHEMA_FORM_FIELDS_REQUIRED', 'Manifest forms must contain at least one field.', {
43
+ path: `${pointer}/fields`,
44
+ });
45
+ }
46
+ validateFields(form.fields, `${pointer}/fields`);
47
+ },
48
+
49
+ normalize(entry, context) {
50
+ this.validate(entry, context);
51
+ const formKey = entry.key;
52
+ const form = entry.definition;
53
+ const pointer = `/forms/${escapePointer(formKey)}`;
54
+
55
+ const dependencySources = {};
56
+ const dependsOn = new Set([`app:${context.appKey}`]);
57
+ dependencySources[`app:${context.appKey}`] = [{ kind: 'dependsOn', path: pointer }];
58
+
59
+ for (let index = 0; index < (form.dependsOn || []).length; index++) {
60
+ const dependency = form.dependsOn[index];
61
+ const target = normalizeDependency(dependency, context);
62
+ dependsOn.add(target);
63
+ addDependencySource(dependencySources, target, {
64
+ kind: 'dependsOn',
65
+ path: `${pointer}/dependsOn/${index}`,
66
+ });
67
+ }
68
+
69
+ const fieldPaths = new Set();
70
+ const fields = normalizeFields(form.fields || {}, {
71
+ formKey,
72
+ pointerPath: `${pointer}/fields`,
73
+ semanticPrefix: '',
74
+ fieldPaths,
75
+ context,
76
+ dependsOn,
77
+ dependencySources,
78
+ });
79
+
80
+ const desired = {
81
+ title: form.title,
82
+ fields,
83
+ };
84
+ if (form.mode !== undefined) {
85
+ desired.mode = form.mode;
86
+ }
87
+
88
+ return {
89
+ resourceType: 'form',
90
+ key: formKey,
91
+ desired,
92
+ dependsOn: sortStrings(dependsOn),
93
+ dependencySources,
94
+ };
95
+ },
96
+
97
+ async readObserved(binding, context = {}) {
98
+ const normalized = normalizeFormBindings(binding);
99
+ const appType = normalized.appType || context.appType;
100
+ if (!appType || !normalized.formUuid) {
101
+ throw schemaError('SCHEMA_OBSERVED_BINDING_MISSING', 'Form observed read requires appType and formUuid bindings.', {
102
+ details: { resourceType: 'form' },
103
+ });
104
+ }
105
+ const input = {
106
+ appType,
107
+ formUuid: normalized.formUuid,
108
+ };
109
+ const reader = context.services && context.services.readFormSchema || readFormSchema;
110
+ assertAdapterDispatchBoundary(context, 'before');
111
+ const schemaResult = await reader(context, input);
112
+ assertAdapterDispatchBoundary(context, 'after');
113
+ if (!shouldObserveMode(binding, context.resource)) {
114
+ return schemaResult;
115
+ }
116
+ const modeReader = context.services && context.services.readFormMode || readFormMode;
117
+ assertAdapterDispatchBoundary(context, 'before');
118
+ const modeObservation = await modeReader(context, input);
119
+ assertAdapterDispatchBoundary(context, 'after');
120
+ return { modeObservation, schemaResult };
121
+ },
122
+
123
+ projectObserved(observed, binding, context = {}) {
124
+ const normalized = normalizeFormBindings(binding);
125
+ const observedParts = splitFormObserved(observed);
126
+ const schemaResult = normalizeSchemaResult(observedParts.schemaResult);
127
+ assertReadableFormSchema(schemaResult);
128
+ const desired = context.resource && context.resource.desired || context.desired || {};
129
+ const projectionBasis = binding && binding.lastApplied || desired;
130
+ const schemaFieldsById = buildSchemaFieldsById(normalizeSchemaFields(schemaResult));
131
+ const componentsByFieldId = buildComponentsByFieldId(schemaResult);
132
+ const usedFieldBindings = {};
133
+ const managed = {};
134
+ const title = extractObservedTitle(schemaResult);
135
+ if (title) {
136
+ managed.title = title;
137
+ }
138
+ if (shouldProjectMode(projectionBasis, desired)) {
139
+ const modeObservation = observedParts.modeObservation;
140
+ if (!modeObservation || !['receipt', 'process'].includes(modeObservation.mode)) {
141
+ throw schemaError('SCHEMA_OBSERVED_STRUCTURE_MISMATCH', 'Observed form mode is missing or unsupported.', {
142
+ details: { resourceType: 'form' },
143
+ });
144
+ }
145
+ managed.mode = modeObservation.mode;
146
+ }
147
+ const formBindingIndex = buildFormBindingIndex(context.state);
148
+ managed.fields = projectObservedFields(projectionBasis.fields || [], {
149
+ componentsByFieldId,
150
+ fieldBindings: normalized.fieldBindings,
151
+ formBindingIndex,
152
+ schemaFieldsById,
153
+ usedFieldBindings,
154
+ expectedParentFieldId: null,
155
+ });
156
+
157
+ const bindings = {
158
+ appType: normalized.appType || context.appType || '',
159
+ formUuid: normalized.formUuid,
160
+ fieldBindings: usedFieldBindings,
161
+ };
162
+ const observedProcessCode = observedParts.modeObservation && observedParts.modeObservation.processCode;
163
+ if (observedProcessCode || normalized.processCode) {
164
+ bindings.processCode = observedProcessCode || normalized.processCode;
165
+ }
166
+ return {
167
+ managed,
168
+ remoteSchemaHash: createSchemaHash(schemaResult),
169
+ bindings,
170
+ };
171
+ },
172
+
173
+ prepareComparison(input) {
174
+ const desired = input && input.desired || {};
175
+ const lastApplied = input && input.lastApplied || {};
176
+ const observed = input && input.observed || {};
177
+ if (
178
+ Object.prototype.hasOwnProperty.call(desired, 'mode') &&
179
+ !Object.prototype.hasOwnProperty.call(lastApplied, 'mode') &&
180
+ ['receipt', 'process'].includes(observed.mode)
181
+ ) {
182
+ return {
183
+ lastApplied: Object.assign({}, clonePlain(lastApplied), { mode: observed.mode }),
184
+ };
185
+ }
186
+ return { lastApplied };
187
+ },
188
+
189
+ classifyObservedConflict(input) {
190
+ const lastAppliedFields = indexManagedFields(input && input.lastApplied && input.lastApplied.fields);
191
+ const desiredFields = indexManagedFields(input && input.desired && input.desired.fields);
192
+ const observedFields = indexManagedFields(input && input.observed && input.observed.fields);
193
+
194
+ for (const semanticPath of sortStrings(lastAppliedFields.keys())) {
195
+ const previous = lastAppliedFields.get(semanticPath);
196
+ const desired = desiredFields.get(semanticPath);
197
+ const observed = observedFields.get(semanticPath);
198
+ if (!observed) {
199
+ return 'MANAGED_FIELD_MISSING';
200
+ }
201
+ if (!desired) {
202
+ return 'FORM_MANAGED_FIELD_REMOVAL_UNSUPPORTED';
203
+ }
204
+ if (desired.type !== previous.type || observed.type !== previous.type) {
205
+ return 'FORM_FIELD_TYPE_CHANGE_UNSUPPORTED';
206
+ }
207
+ if (
208
+ hasRemovedOptionValues(previous.options, desired.options) ||
209
+ hasRemovedOptionValues(previous.options, observed.options)
210
+ ) {
211
+ return 'FORM_OPTION_VALUE_CHANGE_UNSUPPORTED';
212
+ }
213
+ if (
214
+ previous.form !== undefined &&
215
+ (desired.form !== previous.form || observed.form !== previous.form)
216
+ ) {
217
+ return 'FORM_ASSOCIATION_TARGET_CHANGE_UNSUPPORTED';
218
+ }
219
+ }
220
+ return undefined;
221
+ },
222
+
223
+ classifyUpdate(input) {
224
+ const desired = input && input.desired || {};
225
+ const observed = input && input.observed || {};
226
+ return desired.mode === 'process' && observed.mode === 'receipt'
227
+ ? 'FORM_MODE_CONVERSION'
228
+ : undefined;
229
+ },
230
+
231
+ prepareOperation(input, context = {}) {
232
+ if (!input || input.operation !== 'update') {
233
+ return undefined;
234
+ }
235
+ const service = context.services && context.services.prepareFormResourceUpdate || prepareFormResourceUpdate;
236
+ try {
237
+ const operationInput = buildFormOperationInput(input.resource, input.stateResource, input.observed, context);
238
+ const currentMode = operationInput.currentMode;
239
+ const desiredMode = operationInput.targetMode;
240
+ if (currentMode === 'process' && desiredMode === 'receipt') {
241
+ throw schemaError('SCHEMA_APPLY_DESTRUCTIVE_CHANGE_UNSUPPORTED', 'Process forms cannot be converted back to receipt forms.', {
242
+ details: {
243
+ resourceType: 'form',
244
+ key: input.resource && input.resource.key,
245
+ operation: 'remove',
246
+ },
247
+ });
248
+ }
249
+ const prepared = service(operationInput);
250
+ prepared.modeConversion = currentMode === 'receipt' && desiredMode === 'process';
251
+ return prepared;
252
+ } catch (error) {
253
+ if (
254
+ error &&
255
+ (
256
+ error.code === 'FORM_SCHEMA_DESTRUCTIVE_CHANGE_UNSUPPORTED' ||
257
+ error.code === 'FORM_COMPILER_FIELD_BINDING_TYPE_MISMATCH'
258
+ )
259
+ ) {
260
+ throw schemaError('SCHEMA_APPLY_DESTRUCTIVE_CHANGE_UNSUPPORTED', 'Managed field removal or type replacement is not supported.', {
261
+ details: {
262
+ resourceType: 'form',
263
+ key: input.resource && input.resource.key,
264
+ semanticPath: error.details && error.details.semanticPath,
265
+ operation: error.details && error.details.operation || 'replace_type',
266
+ },
267
+ });
268
+ }
269
+ throw error;
270
+ }
271
+ },
272
+
273
+ async create(desired, context = {}) {
274
+ const creator = context.services && context.services.createFormResource || createFormResource;
275
+ const input = buildFormOperationInput(context.resource, null, null, context);
276
+ return creator(context, input);
277
+ },
278
+
279
+ async resumeCreate(desired, createIdentity, context = {}) {
280
+ const bindings = normalizeFormBindings(createIdentity);
281
+ const observed = await readResumeCreateObservedForResume(this, desired, bindings, {
282
+ ...context,
283
+ resource: context.resource,
284
+ });
285
+ try {
286
+ const projection = this.projectObserved(observed, { bindings, lastApplied: desired }, {
287
+ ...context,
288
+ desired,
289
+ resource: context.resource,
290
+ });
291
+ if (!isDeepStrictEqual(projection.managed, desired)) {
292
+ throw schemaError('SCHEMA_APPLY_JIT_CONFLICT', 'Bound form already contains different managed content.', {
293
+ details: { resourceType: 'form' },
294
+ });
295
+ }
296
+ return formIdentityResult(bindings, observed);
297
+ } catch (error) {
298
+ if (!error || error.code !== 'SCHEMA_OBSERVED_STRUCTURE_MISMATCH') {
299
+ throw error;
300
+ }
301
+ }
302
+ const resume = context.services && context.services.resumeFormResource || resumeFormResource;
303
+ const input = buildFormOperationInput(context.resource, bindings, observed, context);
304
+ return resume(context, input);
305
+ },
306
+
307
+ projectOperationResult(result, stateResource, context = {}) {
308
+ if (
309
+ context.recovery !== 'createIdentity' ||
310
+ !result ||
311
+ !Object.prototype.hasOwnProperty.call(result, 'schemaResult') ||
312
+ !operationResultHasRequiredModeEvidence(result.schemaResult, context)
313
+ ) {
314
+ return null;
315
+ }
316
+ return this.projectObserved(result.schemaResult, {
317
+ bindings: stateResource && stateResource.bindings,
318
+ lastApplied: context.resource && context.resource.desired,
319
+ }, context);
320
+ },
321
+
322
+ async update(desired, observed, binding, context = {}) {
323
+ const updater = context.services && context.services.updateFormResource || updateFormResource;
324
+ const input = buildFormOperationInput(context.resource, context.stateResource || binding, observed, context);
325
+ input.prepared = context.prepared;
326
+ const result = await updater(context, input);
327
+ if (context.prepared && context.prepared.modeConversion) {
328
+ const converter = context.services && context.services.convertFormToProcess || convertFormToProcess;
329
+ assertAdapterDispatchBoundary(context, 'before');
330
+ await converter(context, {
331
+ appType: input.appType,
332
+ formUuid: input.formUuid,
333
+ });
334
+ assertAdapterDispatchBoundary(context, 'after');
335
+ }
336
+ return result;
337
+ },
338
+
339
+ buildBindings(result, context = {}) {
340
+ const existing = normalizeFormBindings(context.stateResource);
341
+ const fieldBindings = {};
342
+ const usedFieldIds = new Set();
343
+ const resultBindings = result && result.fieldBindings || {};
344
+ const componentTypes = result && result.fieldBindingComponents || {};
345
+ for (const semanticPath of sortStrings(Object.keys(resultBindings))) {
346
+ const fieldId = resultBindings[semanticPath];
347
+ if (usedFieldIds.has(fieldId)) {
348
+ throw schemaError('SCHEMA_APPLY_VERIFY_FAILED', 'Form operation produced ambiguous field bindings.', {
349
+ details: {
350
+ resourceType: 'form',
351
+ key: context.resource && context.resource.key,
352
+ semanticPath,
353
+ },
354
+ });
355
+ }
356
+ usedFieldIds.add(fieldId);
357
+ fieldBindings[semanticPath] = {
358
+ fieldId,
359
+ componentType: componentTypes[semanticPath] || '',
360
+ };
361
+ }
362
+ const bindings = {
363
+ appType: result && result.appType || existing.appType || resolveAppType(context.resource, context.state),
364
+ formUuid: result && result.formUuid || existing.formUuid,
365
+ fieldBindings,
366
+ };
367
+ if (context.identityCheckpoint === true) {
368
+ if (!bindings.appType || !bindings.formUuid) {
369
+ throw schemaError('SCHEMA_APPLY_VERIFY_FAILED', 'Form create identity is incomplete.', {
370
+ details: { resourceType: 'form' },
371
+ });
372
+ }
373
+ return bindings;
374
+ }
375
+ const processCode = result && result.processCode || existing.processCode;
376
+ if (processCode) {
377
+ bindings.processCode = processCode;
378
+ }
379
+ if (!bindings.appType || !bindings.formUuid || Object.keys(bindings.fieldBindings).length === 0) {
380
+ throw schemaError('SCHEMA_APPLY_VERIFY_FAILED', 'Form operation did not produce complete bindings.', {
381
+ details: {
382
+ resourceType: 'form',
383
+ key: context.resource && context.resource.key,
384
+ },
385
+ });
386
+ }
387
+ return bindings;
388
+ },
389
+
390
+ verify(projection, context = {}) {
391
+ if (context.recovery === 'pending' && context.operation === 'update') {
392
+ throw schemaError('SCHEMA_RECONCILIATION_REQUIRED', 'Pending form update lacks durable pre-write Schema evidence.', {
393
+ details: {
394
+ resourceType: 'form',
395
+ key: context.resource && context.resource.key,
396
+ },
397
+ });
398
+ }
399
+ if (!projection || !isDeepStrictEqual(projection.managed, context.resource && context.resource.desired)) {
400
+ throw schemaError('SCHEMA_APPLY_VERIFY_FAILED', 'Form observed projection does not match desired managed state.', {
401
+ details: {
402
+ resourceType: 'form',
403
+ key: context.resource && context.resource.key,
404
+ },
405
+ });
406
+ }
407
+ if (
408
+ context.operation === 'update' &&
409
+ context.recovery !== 'pending' &&
410
+ !context.prepared &&
411
+ !context.expectedRemoteSchemaHash
412
+ ) {
413
+ throw schemaError('SCHEMA_RECONCILIATION_REQUIRED', 'Form update verification lacks full-Schema evidence.', {
414
+ details: {
415
+ resourceType: 'form',
416
+ key: context.resource && context.resource.key,
417
+ },
418
+ });
419
+ }
420
+ const expectedRemoteSchemaHash = context.prepared && context.prepared.schema
421
+ ? createSchemaHash({ content: context.prepared.schema })
422
+ : context.expectedRemoteSchemaHash;
423
+ if (
424
+ expectedRemoteSchemaHash &&
425
+ projection.remoteSchemaHash !== expectedRemoteSchemaHash
426
+ ) {
427
+ throw schemaError('SCHEMA_APPLY_VERIFY_FAILED', 'Form post-write Schema contains an unexpected remote delta.', {
428
+ details: {
429
+ resourceType: 'form',
430
+ key: context.resource && context.resource.key,
431
+ property: 'remoteSchema',
432
+ },
433
+ });
434
+ }
435
+ return projection;
436
+ },
437
+
438
+ validateStateResource(entry) {
439
+ validateFormStateResource(entry);
440
+ },
441
+ };
442
+
443
+ function assertAdapterDispatchBoundary(context, phase) {
444
+ if (typeof context.assertRemoteDispatchBoundary === 'function') {
445
+ context.assertRemoteDispatchBoundary(phase);
446
+ }
447
+ }
448
+
449
+ async function readResumeCreateObserved(adapter, bindings, context) {
450
+ const retry = normalizeFormResumeReadbackRetry(context && context.formPostWriteReadbackRetry);
451
+ let lastError;
452
+ for (let attempt = 1; attempt <= retry.maxAttempts; attempt++) {
453
+ try {
454
+ return await adapter.readObserved({ bindings }, context);
455
+ } catch (error) {
456
+ if (!isRetryableFormResumeReadbackError(error) || attempt === retry.maxAttempts) {
457
+ throw error;
458
+ }
459
+ lastError = error;
460
+ await waitForFormResumeReadback(retry.delayMs);
461
+ }
462
+ }
463
+ throw lastError;
464
+ }
465
+
466
+ async function readResumeCreateObservedForResume(adapter, desired, bindings, context) {
467
+ try {
468
+ return await readResumeCreateObserved(adapter, bindings, context);
469
+ } catch (error) {
470
+ if (!shouldFallbackReceiptResumeModeRead(error, desired)) {
471
+ throw error;
472
+ }
473
+ const schemaOnlyObserved = await readResumeCreateObserved(adapter, bindings, {
474
+ ...context,
475
+ resource: omitDesiredMode(context && context.resource),
476
+ });
477
+ return {
478
+ schemaResult: schemaOnlyObserved,
479
+ modeObservation: { mode: 'receipt' },
480
+ };
481
+ }
482
+ }
483
+
484
+ function shouldFallbackReceiptResumeModeRead(error, desired) {
485
+ return !!(
486
+ error &&
487
+ error.code === 'FORM_MODE_READ_FAILED' &&
488
+ desired &&
489
+ desired.mode === 'receipt' &&
490
+ !isAuthCsrfOrPermissionReadFailure(error)
491
+ );
492
+ }
493
+
494
+ function operationResultHasRequiredModeEvidence(observed, context = {}) {
495
+ const desired = context.resource && context.resource.desired || {};
496
+ if (!Object.prototype.hasOwnProperty.call(desired, 'mode')) {
497
+ return true;
498
+ }
499
+ const modeObservation = splitFormObserved(observed).modeObservation;
500
+ if (!modeObservation || modeObservation.mode !== desired.mode) {
501
+ return false;
502
+ }
503
+ return desired.mode !== 'process' ||
504
+ (typeof modeObservation.processCode === 'string' && modeObservation.processCode.length > 0);
505
+ }
506
+
507
+ function omitDesiredMode(resource) {
508
+ if (!resource || !resource.desired || !Object.prototype.hasOwnProperty.call(resource.desired, 'mode')) {
509
+ return resource;
510
+ }
511
+ const desired = { ...resource.desired };
512
+ delete desired.mode;
513
+ return { ...resource, desired };
514
+ }
515
+
516
+ function normalizeFormResumeReadbackRetry(config = {}) {
517
+ return {
518
+ maxAttempts: Number.isInteger(config.maxAttempts) && config.maxAttempts > 0
519
+ ? config.maxAttempts
520
+ : 1,
521
+ delayMs: Number.isInteger(config.delayMs) && config.delayMs >= 0
522
+ ? config.delayMs
523
+ : 0,
524
+ };
525
+ }
526
+
527
+ function isRetryableFormResumeReadbackError(error) {
528
+ if (!error || isAuthCsrfOrPermissionReadFailure(error)) {
529
+ return false;
530
+ }
531
+ return error.code === 'FORM_SCHEMA_READ_FAILED' ||
532
+ error.code === 'FORM_MODE_READ_FAILED' ||
533
+ error.code === 'SCHEMA_REMOTE_READ_FAILED' ||
534
+ error.code === 'SCHEMA_REMOTE_RESOURCE_MISSING';
535
+ }
536
+
537
+ function isAuthCsrfOrPermissionReadFailure(error) {
538
+ const result = error &&
539
+ error.details &&
540
+ error.details.result;
541
+ if (!result) {
542
+ return false;
543
+ }
544
+ if (result.__needLogin || result.__csrfExpired) {
545
+ return true;
546
+ }
547
+ if (result.__httpStatus === 401 || result.__httpStatus === 403) {
548
+ return true;
549
+ }
550
+ const code = String(result.errorCode || result.code || '').toUpperCase();
551
+ if (code === '401' || code === '403' || code.includes('FORBIDDEN') || code.includes('PERMISSION')) {
552
+ return true;
553
+ }
554
+ const message = String(result.errorMsg || result.message || error.message || '').toLowerCase();
555
+ return /forbidden|permission|unauthori[sz]ed|access denied|无权限|权限不足|未授权/.test(message);
556
+ }
557
+
558
+ function waitForFormResumeReadback(delayMs) {
559
+ if (delayMs === 0) {
560
+ return Promise.resolve();
561
+ }
562
+ return new Promise(resolve => setTimeout(resolve, delayMs));
563
+ }
564
+
565
+ function validateFields(fields, pointerPath) {
566
+ for (const fieldKey of Object.keys(fields)) {
567
+ const pointer = `${pointerPath}/${escapePointer(fieldKey)}`;
568
+ validateSemanticKey(fieldKey, pointer);
569
+ const field = fields[fieldKey];
570
+ const fieldType = normalizeFieldType(field && field.type);
571
+ if (!isSupportedFieldType(fieldType)) {
572
+ throw schemaError('SCHEMA_FIELD_TYPE_UNSUPPORTED', 'Field type is not supported by the SAC-01 form compiler.', {
573
+ path: `${pointer}/type`,
574
+ details: { fieldType: field && field.type },
575
+ });
576
+ }
577
+
578
+ if (field.children !== undefined && fieldType !== TABLE_FIELD_TYPE) {
579
+ throw schemaError('SCHEMA_FIELD_PROPERTY_INVALID', 'children is only supported on TableField.', {
580
+ path: `${pointer}/children`,
581
+ details: { property: 'children', fieldType },
582
+ });
583
+ }
584
+ if (field.form !== undefined && fieldType !== ASSOCIATION_FORM_FIELD_TYPE) {
585
+ throw schemaError('SCHEMA_FIELD_PROPERTY_INVALID', 'form is only supported on AssociationFormField.', {
586
+ path: `${pointer}/form`,
587
+ details: { property: 'form', fieldType },
588
+ });
589
+ }
590
+ if (field.options !== undefined && !isOptionFieldType(fieldType)) {
591
+ throw schemaError('SCHEMA_FIELD_PROPERTY_INVALID', 'options is only supported on option fields.', {
592
+ path: `${pointer}/options`,
593
+ details: { property: 'options', fieldType },
594
+ });
595
+ }
596
+ if (field.options !== undefined && isOptionFieldType(fieldType)) {
597
+ validateOptionValues(field.options, `${pointer}/options`);
598
+ }
599
+ if (field.children !== undefined) {
600
+ validateFields(field.children, `${pointer}/children`);
601
+ }
602
+ }
603
+ }
604
+
605
+ function validateOptionValues(options, pointer) {
606
+ if (!Array.isArray(options)) {
607
+ return;
608
+ }
609
+ options.forEach((option, index) => {
610
+ const optionPointer = `${pointer}/${index}`;
611
+ if (option && typeof option === 'object' && !Array.isArray(option)) {
612
+ assertNonBlankOptionValue(option.label, `${optionPointer}/label`);
613
+ assertNonBlankOptionValue(option.value, `${optionPointer}/value`);
614
+ return;
615
+ }
616
+ assertNonBlankOptionValue(option, optionPointer);
617
+ });
618
+ }
619
+
620
+ function assertNonBlankOptionValue(value, pointer) {
621
+ if (String(value).trim() === '') {
622
+ throw schemaError('SCHEMA_FIELD_PROPERTY_INVALID', 'Option label and value must not be blank.', {
623
+ path: pointer,
624
+ details: { property: 'options' },
625
+ });
626
+ }
627
+ }
628
+
629
+ function normalizeFields(fields, context) {
630
+ return sortStrings(Object.keys(fields)).map(fieldKey => {
631
+ const pointer = `${context.pointerPath}/${escapePointer(fieldKey)}`;
632
+ validateSemanticKey(fieldKey, pointer);
633
+
634
+ const semanticPath = context.semanticPrefix ? `${context.semanticPrefix}.${fieldKey}` : fieldKey;
635
+ if (context.fieldPaths.has(semanticPath)) {
636
+ throw schemaError('SCHEMA_DUPLICATE_KEY', 'Field semantic paths must be unique within a form.', {
637
+ path: pointer,
638
+ details: { semanticPath },
639
+ });
640
+ }
641
+ context.fieldPaths.add(semanticPath);
642
+
643
+ const field = fields[fieldKey];
644
+ const fieldType = normalizeFieldType(field.type);
645
+ const desired = {
646
+ key: fieldKey,
647
+ semanticPath,
648
+ type: fieldType,
649
+ };
650
+ for (const copyKey of FIELD_COPY_KEYS) {
651
+ if (field[copyKey] !== undefined) {
652
+ desired[copyKey] = clonePlain(field[copyKey]);
653
+ }
654
+ }
655
+ if (field.options !== undefined) {
656
+ desired.options = normalizeManagedOptions(field.options);
657
+ }
658
+
659
+ if (field.form) {
660
+ const target = normalizeFormReference(field.form, context.context);
661
+ desired.form = target;
662
+ context.dependsOn.add(target);
663
+ addDependencySource(context.dependencySources, target, {
664
+ kind: 'reference',
665
+ path: `${pointer}/form`,
666
+ });
667
+ }
668
+
669
+ for (let index = 0; index < (field.dependsOn || []).length; index++) {
670
+ const dependency = field.dependsOn[index];
671
+ const target = normalizeDependency(dependency, context.context);
672
+ context.dependsOn.add(target);
673
+ addDependencySource(context.dependencySources, target, {
674
+ kind: 'dependsOn',
675
+ path: `${pointer}/dependsOn/${index}`,
676
+ });
677
+ }
678
+
679
+ if (field.children) {
680
+ desired.children = normalizeFields(field.children, {
681
+ ...context,
682
+ pointerPath: `${pointer}/children`,
683
+ semanticPrefix: semanticPath,
684
+ });
685
+ }
686
+
687
+ return desired;
688
+ });
689
+ }
690
+
691
+ function normalizeDependency(value, context) {
692
+ if (String(value).includes(':')) {
693
+ const [resourceType, key, extra] = String(value).split(':');
694
+ if (!resourceType || !key || extra !== undefined) {
695
+ throw schemaError('SCHEMA_INVALID_REFERENCE', 'Resource references must use "resourceType:key" or a form key.', {
696
+ details: { resourceType },
697
+ });
698
+ }
699
+ assertRegisteredResourceType(resourceType, context);
700
+ validateSemanticKey(resourceType, undefined);
701
+ validateSemanticKey(key, undefined);
702
+ return `${resourceType}:${key}`;
703
+ }
704
+ validateSemanticKey(value, undefined);
705
+ return `form:${value}`;
706
+ }
707
+
708
+ function normalizeFormReference(value, context) {
709
+ if (String(value).includes(':')) {
710
+ const [resourceType, key, extra] = String(value).split(':');
711
+ if (!resourceType || !key || extra !== undefined) {
712
+ throw schemaError('SCHEMA_INVALID_REFERENCE', 'Association form references must use a form key or "form:key".', {
713
+ details: { resourceType },
714
+ });
715
+ }
716
+ assertRegisteredResourceType(resourceType, context);
717
+ if (resourceType !== 'form') {
718
+ throw schemaError('SCHEMA_INVALID_REFERENCE', 'Association form references must target a form resource.', {
719
+ details: { resourceType },
720
+ });
721
+ }
722
+ validateSemanticKey(key, undefined);
723
+ return `form:${key}`;
724
+ }
725
+ validateSemanticKey(value, undefined);
726
+ return `form:${value}`;
727
+ }
728
+
729
+ function assertRegisteredResourceType(resourceType, context) {
730
+ if (!context || !context.registry) {
731
+ return;
732
+ }
733
+ context.registry.get(resourceType);
734
+ }
735
+
736
+ function addDependencySource(sources, target, source) {
737
+ if (!sources[target]) {
738
+ sources[target] = [];
739
+ }
740
+ sources[target].push(source);
741
+ }
742
+
743
+ function clonePlain(value) {
744
+ if (value === undefined || value === null || typeof value !== 'object') {
745
+ return value;
746
+ }
747
+ return JSON.parse(JSON.stringify(value));
748
+ }
749
+
750
+ function normalizeSchemaResult(schemaResult) {
751
+ let content = schemaResult && Object.prototype.hasOwnProperty.call(schemaResult, 'content')
752
+ ? schemaResult.content
753
+ : schemaResult;
754
+ if (typeof content === 'string') {
755
+ try {
756
+ content = JSON.parse(content);
757
+ } catch (error) {
758
+ throw schemaError('SCHEMA_OBSERVED_STRUCTURE_MISMATCH', 'Observed form Schema content is invalid.', {
759
+ details: { resourceType: 'form' },
760
+ });
761
+ }
762
+ }
763
+ return { content };
764
+ }
765
+
766
+ function assertReadableFormSchema(schemaResult) {
767
+ const content = schemaResult && schemaResult.content;
768
+ if (
769
+ !content ||
770
+ typeof content !== 'object' ||
771
+ Array.isArray(content) ||
772
+ !Array.isArray(content.pages) ||
773
+ !findFirstComponent(content, 'FormContainer')
774
+ ) {
775
+ throw schemaError('SCHEMA_OBSERVED_STRUCTURE_MISMATCH', 'Observed form Schema is incomplete or unsupported.', {
776
+ details: { resourceType: 'form' },
777
+ });
778
+ }
779
+ }
780
+
781
+ function normalizeFormBindings(binding) {
782
+ const source = binding && binding.bindings ? binding.bindings : binding || {};
783
+ return {
784
+ appType: source.appType || '',
785
+ formUuid: source.formUuid || '',
786
+ fieldBindings: normalizeObservedFieldBindings(source.fieldBindings || source.fields || {}),
787
+ processCode: source.processCode || '',
788
+ };
789
+ }
790
+
791
+ function shouldObserveMode(binding, resource) {
792
+ const desired = resource && resource.desired || {};
793
+ const lastApplied = binding && binding.lastApplied || {};
794
+ return Object.prototype.hasOwnProperty.call(desired, 'mode') ||
795
+ Object.prototype.hasOwnProperty.call(lastApplied, 'mode');
796
+ }
797
+
798
+ function shouldProjectMode(projectionBasis, desired) {
799
+ return Object.prototype.hasOwnProperty.call(projectionBasis || {}, 'mode') ||
800
+ Object.prototype.hasOwnProperty.call(desired || {}, 'mode');
801
+ }
802
+
803
+ function splitFormObserved(observed) {
804
+ if (
805
+ observed &&
806
+ Object.prototype.hasOwnProperty.call(observed, 'schemaResult') &&
807
+ Object.prototype.hasOwnProperty.call(observed, 'modeObservation')
808
+ ) {
809
+ return observed;
810
+ }
811
+ return { schemaResult: observed, modeObservation: undefined };
812
+ }
813
+
814
+ function normalizeObservedFieldBindings(fieldBindings) {
815
+ const result = {};
816
+ const usedFieldIds = new Set();
817
+ sortStrings(Object.keys(fieldBindings || {})).forEach((semanticPath) => {
818
+ const value = fieldBindings[semanticPath];
819
+ const binding = typeof value === 'string'
820
+ ? { fieldId: value }
821
+ : Object.assign({}, value || {});
822
+ const fieldId = String(binding.fieldId || binding.id || binding.value || '');
823
+ if (fieldId && usedFieldIds.has(fieldId)) {
824
+ throw schemaError('SCHEMA_OBSERVED_BINDING_AMBIGUOUS', 'Form field bindings contain a duplicate fieldId.', {
825
+ details: { semanticPath },
826
+ });
827
+ }
828
+ if (fieldId) {
829
+ usedFieldIds.add(fieldId);
830
+ }
831
+ result[semanticPath] = { fieldId };
832
+ if (binding.componentType || binding.componentName) {
833
+ result[semanticPath].componentType = binding.componentType || binding.componentName;
834
+ }
835
+ });
836
+ return result;
837
+ }
838
+
839
+ function normalizeManagedOptions(options) {
840
+ return (Array.isArray(options) ? options : []).map((option) => {
841
+ if (option && typeof option === 'object') {
842
+ return {
843
+ label: String(option.label !== undefined ? option.label : option.value),
844
+ value: String(option.value !== undefined ? option.value : option.label),
845
+ };
846
+ }
847
+ const value = String(option);
848
+ return {
849
+ label: value,
850
+ value,
851
+ };
852
+ });
853
+ }
854
+
855
+ function projectObservedFields(fields, context) {
856
+ return (Array.isArray(fields) ? fields : []).map((field) => {
857
+ const semanticPath = field.semanticPath || field.key;
858
+ const binding = context.fieldBindings[semanticPath];
859
+ if (!binding || !binding.fieldId) {
860
+ throw schemaError('SCHEMA_OBSERVED_BINDING_MISSING', 'Observed form projection requires fieldId bindings.', {
861
+ details: { semanticPath },
862
+ });
863
+ }
864
+
865
+ const schemaField = context.schemaFieldsById.get(binding.fieldId);
866
+ if (!schemaField) {
867
+ return null;
868
+ }
869
+ validateObservedParent(semanticPath, schemaField, context.expectedParentFieldId);
870
+
871
+ const component = context.componentsByFieldId.get(binding.fieldId) || {};
872
+ const props = component.props || {};
873
+ const projected = {
874
+ key: field.key,
875
+ semanticPath,
876
+ type: schemaField.componentType,
877
+ };
878
+ if (field.label !== undefined) {
879
+ projected.label = normalizeLabel(props.label, schemaField.label);
880
+ }
881
+ if (field.required !== undefined) {
882
+ projected.required = hasRequiredValidation(props);
883
+ }
884
+ if (field.options !== undefined) {
885
+ projected.options = normalizeObservedOptions(props);
886
+ }
887
+ if (field.form !== undefined) {
888
+ projected.form = resolveObservedAssociationForm(semanticPath, props.associationForm, context.formBindingIndex);
889
+ }
890
+ context.usedFieldBindings[semanticPath] = {
891
+ fieldId: binding.fieldId,
892
+ componentType: schemaField.componentType,
893
+ };
894
+ if (Array.isArray(field.children)) {
895
+ projected.children = projectObservedFields(field.children, Object.assign({}, context, {
896
+ expectedParentFieldId: binding.fieldId,
897
+ }));
898
+ }
899
+ return projected;
900
+ }).filter(Boolean);
901
+ }
902
+
903
+ function validateObservedParent(semanticPath, schemaField, expectedParentFieldId) {
904
+ const actualParentFieldId = schemaField.parentFieldId || null;
905
+ const expected = expectedParentFieldId || null;
906
+ if (actualParentFieldId !== expected) {
907
+ throw schemaError('SCHEMA_OBSERVED_STRUCTURE_MISMATCH', 'Observed field parent does not match the managed semantic path.', {
908
+ details: {
909
+ semanticPath,
910
+ expectedParentBound: expected !== null,
911
+ actualParentBound: actualParentFieldId !== null,
912
+ },
913
+ });
914
+ }
915
+ }
916
+
917
+ function buildComponentsByFieldId(schemaResult) {
918
+ const components = new Map();
919
+ const pages = schemaResult && schemaResult.content && schemaResult.content.pages;
920
+
921
+ function visit(node) {
922
+ if (!node || typeof node !== 'object') {
923
+ return;
924
+ }
925
+ const fieldId = node.props && node.props.fieldId;
926
+ if (fieldId) {
927
+ if (components.has(fieldId)) {
928
+ throw schemaError('SCHEMA_OBSERVED_BINDING_AMBIGUOUS', 'Remote form Schema contains a duplicate fieldId.', {
929
+ details: { resourceType: 'form' },
930
+ });
931
+ }
932
+ components.set(fieldId, node);
933
+ }
934
+ if (Array.isArray(node.children)) {
935
+ node.children.forEach(visit);
936
+ }
937
+ }
938
+
939
+ if (Array.isArray(pages)) {
940
+ pages.forEach((page) => {
941
+ const roots = page && Array.isArray(page.componentsTree) ? page.componentsTree : [];
942
+ roots.forEach(visit);
943
+ });
944
+ }
945
+ return components;
946
+ }
947
+
948
+ function buildSchemaFieldsById(fields) {
949
+ const result = new Map();
950
+ for (const field of fields || []) {
951
+ if (!field.fieldId) {
952
+ continue;
953
+ }
954
+ if (result.has(field.fieldId)) {
955
+ throw schemaError('SCHEMA_OBSERVED_BINDING_AMBIGUOUS', 'Remote form Schema contains a duplicate fieldId.', {
956
+ details: { resourceType: 'form' },
957
+ });
958
+ }
959
+ result.set(field.fieldId, field);
960
+ }
961
+ return result;
962
+ }
963
+
964
+ function indexManagedFields(fields, result = new Map()) {
965
+ for (const field of Array.isArray(fields) ? fields : []) {
966
+ const semanticPath = field && (field.semanticPath || field.key);
967
+ if (semanticPath) {
968
+ result.set(semanticPath, field);
969
+ }
970
+ indexManagedFields(field && field.children, result);
971
+ }
972
+ return result;
973
+ }
974
+
975
+ function hasRemovedOptionValues(previousOptions, nextOptions) {
976
+ if (!Array.isArray(previousOptions)) {
977
+ return false;
978
+ }
979
+ const nextValues = new Set((Array.isArray(nextOptions) ? nextOptions : []).map(option => (
980
+ String(option && typeof option === 'object' ? option.value : option)
981
+ )));
982
+ return previousOptions.some(option => (
983
+ !nextValues.has(String(option && typeof option === 'object' ? option.value : option))
984
+ ));
985
+ }
986
+
987
+ function buildFormBindingIndex(state) {
988
+ const result = new Map();
989
+ const forms = state && state.resources && state.resources.form || {};
990
+ Object.keys(forms).forEach((formKey) => {
991
+ const bindings = forms[formKey] && forms[formKey].bindings || {};
992
+ const formUuid = bindings.formUuid || '';
993
+ if (!formUuid) {
994
+ return;
995
+ }
996
+ const appType = bindings.appType || '';
997
+ const keys = [
998
+ `${appType}:${formUuid}`,
999
+ `:${formUuid}`,
1000
+ ];
1001
+ keys.forEach((lookupKey) => {
1002
+ const existing = result.get(lookupKey) || [];
1003
+ existing.push(formKey);
1004
+ result.set(lookupKey, existing);
1005
+ });
1006
+ });
1007
+ return result;
1008
+ }
1009
+
1010
+ function resolveObservedAssociationForm(semanticPath, associationForm, formBindingIndex) {
1011
+ const formUuid = associationForm && associationForm.formUuid || '';
1012
+ const appType = associationForm && associationForm.appType || '';
1013
+ if (!formUuid) {
1014
+ throw schemaError('SCHEMA_OBSERVED_REFERENCE_MISSING', 'Association field observed formUuid is missing.', {
1015
+ details: { semanticPath },
1016
+ });
1017
+ }
1018
+ const candidates = formBindingIndex.get(`${appType}:${formUuid}`) || formBindingIndex.get(`:${formUuid}`) || [];
1019
+ const uniqueCandidates = sortStrings([...new Set(candidates)]);
1020
+ if (uniqueCandidates.length === 0) {
1021
+ throw schemaError('SCHEMA_OBSERVED_REFERENCE_MISSING', 'Association field target form is not bound in state.', {
1022
+ details: { semanticPath },
1023
+ });
1024
+ }
1025
+ if (uniqueCandidates.length > 1) {
1026
+ throw schemaError('SCHEMA_OBSERVED_REFERENCE_AMBIGUOUS', 'Association field target form binding is ambiguous.', {
1027
+ details: { semanticPath },
1028
+ });
1029
+ }
1030
+ return `form:${uniqueCandidates[0]}`;
1031
+ }
1032
+
1033
+ function extractObservedTitle(schemaResult) {
1034
+ const content = schemaResult && schemaResult.content || {};
1035
+ const direct = normalizeLocalizedText(content.title || content.name || content.formName);
1036
+ if (direct) {
1037
+ return direct;
1038
+ }
1039
+ const container = findFirstComponent(content, 'FormContainer');
1040
+ return normalizeLocalizedText(container && container.props && container.props.formLabel);
1041
+ }
1042
+
1043
+ function findFirstComponent(schema, componentName) {
1044
+ let found = null;
1045
+ function visit(node) {
1046
+ if (!node || typeof node !== 'object' || found) {
1047
+ return;
1048
+ }
1049
+ if (node.componentName === componentName) {
1050
+ found = node;
1051
+ return;
1052
+ }
1053
+ if (Array.isArray(node.children)) {
1054
+ node.children.forEach(visit);
1055
+ }
1056
+ }
1057
+ const pages = schema && schema.pages;
1058
+ if (Array.isArray(pages)) {
1059
+ pages.forEach((page) => {
1060
+ const roots = page && Array.isArray(page.componentsTree) ? page.componentsTree : [];
1061
+ roots.forEach(visit);
1062
+ });
1063
+ }
1064
+ return found;
1065
+ }
1066
+
1067
+ function buildFormOperationInput(resource, stateResource, observed, context) {
1068
+ const normalized = normalizeFormBindings(stateResource);
1069
+ const appType = normalized.appType || resolveAppType(resource, context.state);
1070
+ const observedParts = splitFormObserved(observed);
1071
+ const operationInput = {
1072
+ appType,
1073
+ currentMode: observedParts.modeObservation && observedParts.modeObservation.mode,
1074
+ formUuid: normalized.formUuid,
1075
+ formType: resource.desired.mode,
1076
+ definition: {
1077
+ title: resource.desired.title,
1078
+ fields: buildCompilerFields(resource.desired.fields || [], context.state),
1079
+ },
1080
+ existingBindings: normalized.fieldBindings,
1081
+ currentSchemaResult: observedParts.schemaResult,
1082
+ targetMode: resource.desired.mode,
1083
+ };
1084
+ if (observed !== undefined && observed !== null) {
1085
+ operationInput.serverRevision = requireSchemaServerRevision(
1086
+ observedParts.schemaResult,
1087
+ () => schemaError('SCHEMA_REMOTE_READ_FAILED', 'Observed form Schema revision is missing or invalid.', {
1088
+ details: { resourceType: 'form' },
1089
+ })
1090
+ );
1091
+ }
1092
+ return operationInput;
1093
+ }
1094
+
1095
+ function buildCompilerFields(fields, state) {
1096
+ return (Array.isArray(fields) ? fields : []).map((field) => {
1097
+ const result = {
1098
+ key: field.key,
1099
+ type: field.type,
1100
+ };
1101
+ for (const key of ['label', 'required', 'options']) {
1102
+ if (field[key] !== undefined) {
1103
+ result[key] = clonePlain(field[key]);
1104
+ }
1105
+ }
1106
+ if (Array.isArray(field.children)) {
1107
+ result.children = buildCompilerFields(field.children, state);
1108
+ }
1109
+ if (field.form) {
1110
+ const targetKey = String(field.form).slice('form:'.length);
1111
+ const target = state && state.resources && state.resources.form && state.resources.form[targetKey];
1112
+ const bindings = target && target.bindings || {};
1113
+ if (!bindings.appType || !bindings.formUuid) {
1114
+ throw schemaError('SCHEMA_STATE_RESOURCE_BINDING_MISSING', 'Association target form bindings are missing.', {
1115
+ details: { resourceType: 'form', key: targetKey },
1116
+ });
1117
+ }
1118
+ result.associationForm = {
1119
+ appType: bindings.appType,
1120
+ formUuid: bindings.formUuid,
1121
+ };
1122
+ }
1123
+ return result;
1124
+ });
1125
+ }
1126
+
1127
+ function formIdentityResult(bindings, schemaResult) {
1128
+ const fieldBindings = {};
1129
+ const fieldBindingComponents = {};
1130
+ for (const semanticPath of Object.keys(bindings.fieldBindings)) {
1131
+ const binding = bindings.fieldBindings[semanticPath];
1132
+ fieldBindings[semanticPath] = binding.fieldId;
1133
+ fieldBindingComponents[semanticPath] = binding.componentType || '';
1134
+ }
1135
+ return {
1136
+ appType: bindings.appType,
1137
+ formUuid: bindings.formUuid,
1138
+ fieldBindings,
1139
+ fieldBindingComponents,
1140
+ schemaResult,
1141
+ };
1142
+ }
1143
+
1144
+ function resolveAppType(resource, state) {
1145
+ const appDependency = (resource && resource.dependsOn || []).find(value => String(value).startsWith('app:'));
1146
+ const appKey = appDependency && appDependency.slice('app:'.length);
1147
+ const appState = appKey && state && state.resources && state.resources.app && state.resources.app[appKey];
1148
+ const appType = appState && appState.bindings && appState.bindings.appType;
1149
+ if (!appType) {
1150
+ throw schemaError('SCHEMA_STATE_RESOURCE_BINDING_MISSING', 'Form app dependency binding is missing.', {
1151
+ details: { resourceType: 'app', key: appKey },
1152
+ });
1153
+ }
1154
+ return appType;
1155
+ }
1156
+
1157
+ function normalizeLocalizedText(value) {
1158
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
1159
+ return value.zh_CN || value.en_US || sortStrings(Object.keys(value)).map(key => value[key]).find(Boolean) || '';
1160
+ }
1161
+ return value === undefined || value === null ? '' : String(value);
1162
+ }
1163
+
1164
+ function hasRequiredValidation(props) {
1165
+ if (props.required === true) {
1166
+ return true;
1167
+ }
1168
+ return Array.isArray(props.validation) && props.validation.some(rule => (
1169
+ rule && (rule.type === 'required' || rule.required === true)
1170
+ ));
1171
+ }
1172
+
1173
+ function normalizeObservedOptions(props) {
1174
+ const options = Array.isArray(props.dataSource)
1175
+ ? props.dataSource
1176
+ : props.defaultDataSource && Array.isArray(props.defaultDataSource.options)
1177
+ ? props.defaultDataSource.options
1178
+ : [];
1179
+ return options.map((option) => {
1180
+ if (option && typeof option === 'object') {
1181
+ const value = String(option.value !== undefined ? option.value : normalizeLocalizedText(option.text));
1182
+ return {
1183
+ label: normalizeLocalizedText(option.text || option.label || value),
1184
+ value,
1185
+ };
1186
+ }
1187
+ const value = String(option);
1188
+ return { label: value, value };
1189
+ });
1190
+ }
1191
+
1192
+ function validateFormStateResource(entry) {
1193
+ const state = entry.state || {};
1194
+ validateFormBindings(state.bindings, `${entry.path}/bindings`);
1195
+ if (state.lastApplied !== undefined) {
1196
+ validateManagedForm(state.lastApplied, `${entry.path}/lastApplied`);
1197
+ if (state.lastApplied.mode === 'process' && !state.bindings.processCode) {
1198
+ throw schemaError('SCHEMA_STATE_INVALID', 'Process-mode form state requires processCode bindings.', {
1199
+ path: `${entry.path}/bindings/processCode`,
1200
+ });
1201
+ }
1202
+ }
1203
+ }
1204
+
1205
+ function validateFormBindings(bindings, pointer) {
1206
+ assertAllowedKeys(bindings, ['appType', 'formUuid', 'fieldBindings', 'processCode'], pointer);
1207
+ if (!bindings.appType || typeof bindings.appType !== 'string') {
1208
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form state bindings must include appType.', {
1209
+ path: `${pointer}/appType`,
1210
+ });
1211
+ }
1212
+ if (!bindings.formUuid || typeof bindings.formUuid !== 'string') {
1213
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form state bindings must include formUuid.', {
1214
+ path: `${pointer}/formUuid`,
1215
+ });
1216
+ }
1217
+ if (!bindings.fieldBindings || typeof bindings.fieldBindings !== 'object' || Array.isArray(bindings.fieldBindings)) {
1218
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form state fieldBindings must be an object.', {
1219
+ path: `${pointer}/fieldBindings`,
1220
+ });
1221
+ }
1222
+ if (bindings.processCode !== undefined && (typeof bindings.processCode !== 'string' || !bindings.processCode)) {
1223
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form state processCode must be a non-empty string.', {
1224
+ path: `${pointer}/processCode`,
1225
+ });
1226
+ }
1227
+ const usedFieldIds = new Set();
1228
+ Object.keys(bindings.fieldBindings).forEach((semanticPath) => {
1229
+ const fieldBinding = bindings.fieldBindings[semanticPath];
1230
+ const fieldPointer = `${pointer}/fieldBindings/${escapePointer(semanticPath)}`;
1231
+ if (!fieldBinding || typeof fieldBinding !== 'object' || Array.isArray(fieldBinding)) {
1232
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form field binding must be an object.', {
1233
+ path: fieldPointer,
1234
+ });
1235
+ }
1236
+ assertAllowedKeys(fieldBinding, ['fieldId', 'componentType'], fieldPointer);
1237
+ if (!fieldBinding.fieldId || typeof fieldBinding.fieldId !== 'string') {
1238
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form field binding must include fieldId.', {
1239
+ path: `${fieldPointer}/fieldId`,
1240
+ });
1241
+ }
1242
+ if (usedFieldIds.has(fieldBinding.fieldId)) {
1243
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form state fieldBindings must map to unique fieldId values.', {
1244
+ path: `${fieldPointer}/fieldId`,
1245
+ });
1246
+ }
1247
+ usedFieldIds.add(fieldBinding.fieldId);
1248
+ if (fieldBinding.componentType !== undefined && typeof fieldBinding.componentType !== 'string') {
1249
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form field binding componentType must be a string.', {
1250
+ path: `${fieldPointer}/componentType`,
1251
+ });
1252
+ }
1253
+ });
1254
+ }
1255
+
1256
+ function validateManagedForm(value, pointer) {
1257
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1258
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form lastApplied must be a compact managed object.', {
1259
+ path: pointer,
1260
+ });
1261
+ }
1262
+ assertAllowedKeys(value, ['title', 'fields', 'mode'], pointer);
1263
+ if (typeof value.title !== 'string' || !value.title) {
1264
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form lastApplied must include title.', {
1265
+ path: `${pointer}/title`,
1266
+ });
1267
+ }
1268
+ if (!Array.isArray(value.fields)) {
1269
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form lastApplied fields must be an array.', {
1270
+ path: `${pointer}/fields`,
1271
+ });
1272
+ }
1273
+ if (value.mode !== undefined && !['receipt', 'process'].includes(value.mode)) {
1274
+ throw schemaError('SCHEMA_STATE_INVALID', 'Form lastApplied mode must be receipt or process.', {
1275
+ path: `${pointer}/mode`,
1276
+ });
1277
+ }
1278
+ value.fields.forEach((field, index) => validateManagedField(field, `${pointer}/fields/${index}`));
1279
+ }
1280
+
1281
+ function validateManagedField(field, pointer) {
1282
+ if (!field || typeof field !== 'object' || Array.isArray(field)) {
1283
+ throw schemaError('SCHEMA_STATE_INVALID', 'Managed field must be an object.', {
1284
+ path: pointer,
1285
+ });
1286
+ }
1287
+ assertAllowedKeys(field, ['key', 'semanticPath', 'type', 'label', 'required', 'options', 'form', 'children'], pointer);
1288
+ ['key', 'semanticPath', 'type', 'label'].forEach((property) => {
1289
+ if (typeof field[property] !== 'string' || !field[property]) {
1290
+ throw schemaError('SCHEMA_STATE_INVALID', 'Managed field is missing a required string property.', {
1291
+ path: `${pointer}/${property}`,
1292
+ details: { property },
1293
+ });
1294
+ }
1295
+ });
1296
+ if (field.required !== undefined && typeof field.required !== 'boolean') {
1297
+ throw schemaError('SCHEMA_STATE_INVALID', 'Managed field required must be boolean.', {
1298
+ path: `${pointer}/required`,
1299
+ });
1300
+ }
1301
+ if (field.form !== undefined && (typeof field.form !== 'string' || !field.form.startsWith('form:'))) {
1302
+ throw schemaError('SCHEMA_STATE_INVALID', 'Managed association form reference must be canonical.', {
1303
+ path: `${pointer}/form`,
1304
+ });
1305
+ }
1306
+ if (field.options !== undefined) {
1307
+ validateManagedOptions(field.options, `${pointer}/options`);
1308
+ }
1309
+ if (field.children !== undefined) {
1310
+ if (!Array.isArray(field.children)) {
1311
+ throw schemaError('SCHEMA_STATE_INVALID', 'Managed field children must be an array.', {
1312
+ path: `${pointer}/children`,
1313
+ });
1314
+ }
1315
+ field.children.forEach((child, index) => validateManagedField(child, `${pointer}/children/${index}`));
1316
+ }
1317
+ }
1318
+
1319
+ function validateManagedOptions(options, pointer) {
1320
+ if (!Array.isArray(options)) {
1321
+ throw schemaError('SCHEMA_STATE_INVALID', 'Managed field options must be an array.', {
1322
+ path: pointer,
1323
+ });
1324
+ }
1325
+ options.forEach((option, index) => {
1326
+ const optionPointer = `${pointer}/${index}`;
1327
+ if (!option || typeof option !== 'object' || Array.isArray(option)) {
1328
+ throw schemaError('SCHEMA_STATE_INVALID', 'Managed option must be an object.', {
1329
+ path: optionPointer,
1330
+ });
1331
+ }
1332
+ assertAllowedKeys(option, ['label', 'value'], optionPointer);
1333
+ if (typeof option.label !== 'string' || typeof option.value !== 'string') {
1334
+ throw schemaError('SCHEMA_STATE_INVALID', 'Managed option label and value must be strings.', {
1335
+ path: optionPointer,
1336
+ });
1337
+ }
1338
+ });
1339
+ }
1340
+
1341
+ function assertAllowedKeys(value, allowedKeys, pointer) {
1342
+ const allowed = new Set(allowedKeys);
1343
+ Object.keys(value || {}).forEach((key) => {
1344
+ if (!allowed.has(key)) {
1345
+ throw schemaError('SCHEMA_STATE_INVALID', 'State contains an unknown property.', {
1346
+ path: `${pointer}/${escapePointer(key)}`,
1347
+ details: { property: key },
1348
+ });
1349
+ }
1350
+ });
1351
+ }
1352
+
1353
+ function escapePointer(value) {
1354
+ return String(value).replace(/~/g, '~0').replace(/\//g, '~1');
1355
+ }
1356
+
1357
+ module.exports = {
1358
+ formAdapter,
1359
+ normalizeDependency,
1360
+ normalizeFormReference,
1361
+ normalizeManagedOptions,
1362
+ };