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,691 @@
1
+ 'use strict';
2
+
3
+ const { schemaError } = require('./errors');
4
+ const { createDefaultRegistry } = require('./resource-registry');
5
+ const { resourceId } = require('./dependency-graph');
6
+ const { isSha256 } = require('./hash');
7
+ const { compareCodePoints, sortStrings } = require('./sort');
8
+ const {
9
+ createSha256,
10
+ getResourceState,
11
+ hashStable,
12
+ } = require('./state-store');
13
+ const { stableStringify } = require('./normalize-manifest');
14
+
15
+ const PLAN_KIND = 'openyida_schema_plan';
16
+ const PLAN_CONTRACT_VERSION = 1;
17
+
18
+ const OPERATIONS = Object.freeze({
19
+ CREATE: 'create',
20
+ UPDATE: 'update',
21
+ NOOP: 'noop',
22
+ CONFLICT: 'conflict',
23
+ UNMANAGED: 'unmanaged',
24
+ ORPHAN: 'orphan',
25
+ });
26
+
27
+ const REASON_CODES = Object.freeze({
28
+ NO_STATE_BINDING: 'NO_STATE_BINDING',
29
+ DESIRED_CHANGED: 'DESIRED_CHANGED',
30
+ ALREADY_MATCHES_DESIRED: 'ALREADY_MATCHES_DESIRED',
31
+ STATE_REPAIR_REQUIRED: 'STATE_REPAIR_REQUIRED',
32
+ REMOTE_DRIFT: 'REMOTE_DRIFT',
33
+ DESIRED_AND_OBSERVED_CHANGED: 'DESIRED_AND_OBSERVED_CHANGED',
34
+ FORM_MODE_CONVERSION: 'FORM_MODE_CONVERSION',
35
+ MANAGED_FIELD_MISSING: 'MANAGED_FIELD_MISSING',
36
+ FORM_MANAGED_FIELD_REMOVAL_UNSUPPORTED: 'FORM_MANAGED_FIELD_REMOVAL_UNSUPPORTED',
37
+ FORM_FIELD_TYPE_CHANGE_UNSUPPORTED: 'FORM_FIELD_TYPE_CHANGE_UNSUPPORTED',
38
+ FORM_OPTION_VALUE_CHANGE_UNSUPPORTED: 'FORM_OPTION_VALUE_CHANGE_UNSUPPORTED',
39
+ FORM_ASSOCIATION_TARGET_CHANGE_UNSUPPORTED: 'FORM_ASSOCIATION_TARGET_CHANGE_UNSUPPORTED',
40
+ REMOTE_DRAFT_EXISTS: 'REMOTE_DRAFT_EXISTS',
41
+ REMOTE_IDENTITY_DRIFT: 'REMOTE_IDENTITY_DRIFT',
42
+ STATE_LAST_APPLIED_MISSING: 'STATE_LAST_APPLIED_MISSING',
43
+ MANIFEST_RESOURCE_REMOVED: 'MANIFEST_RESOURCE_REMOVED',
44
+ REMOTE_RESOURCE_MISSING: 'REMOTE_RESOURCE_MISSING',
45
+ });
46
+
47
+ const COUNT_KEYS = [
48
+ OPERATIONS.CREATE,
49
+ OPERATIONS.UPDATE,
50
+ OPERATIONS.NOOP,
51
+ OPERATIONS.CONFLICT,
52
+ OPERATIONS.UNMANAGED,
53
+ OPERATIONS.ORPHAN,
54
+ ];
55
+
56
+ function createPlan(input, options = {}) {
57
+ const registry = options.registry || createDefaultRegistry();
58
+ const desiredResources = normalizeResourceList(input && input.desiredResources, { registry });
59
+ const observedResources = normalizeObservedList(input && input.observedResources, { registry });
60
+ const state = input && input.state || {};
61
+ const manifestHash = input && input.manifestHash;
62
+ const stateRevision = normalizeStateRevision(state.revision);
63
+ const environmentKey = state.environment && state.environment.environmentKey || '';
64
+
65
+ const desiredById = indexResources(desiredResources);
66
+ const observedById = indexResources(observedResources);
67
+ const stateById = indexStateResources(state);
68
+ const allIds = sortStrings(new Set([
69
+ ...desiredById.keys(),
70
+ ...observedById.keys(),
71
+ ...stateById.keys(),
72
+ ]));
73
+
74
+ validatePlannerState({
75
+ desiredResources,
76
+ registry,
77
+ state,
78
+ });
79
+
80
+ const changesById = new Map();
81
+ for (const id of allIds) {
82
+ const desiredResource = desiredById.get(id);
83
+ const stateResource = stateById.get(id);
84
+ const observedResource = observedById.get(id);
85
+ const change = classifyResource({
86
+ desiredResource,
87
+ id,
88
+ observedResource,
89
+ registry,
90
+ stateResource,
91
+ });
92
+ if (change) {
93
+ changesById.set(id, change);
94
+ }
95
+ }
96
+
97
+ const sortedIds = topologicalSortIds(allIds, desiredById);
98
+ const changes = sortedIds
99
+ .map(id => changesById.get(id))
100
+ .filter(Boolean);
101
+ const counts = countChanges(changes);
102
+ const planId = computePlanId({
103
+ changes,
104
+ desiredResources,
105
+ environmentKey,
106
+ manifestHash,
107
+ observedResources,
108
+ registry,
109
+ state,
110
+ stateRevision,
111
+ });
112
+
113
+ return {
114
+ kind: PLAN_KIND,
115
+ contractVersion: PLAN_CONTRACT_VERSION,
116
+ success: true,
117
+ planId,
118
+ manifestHash,
119
+ stateRevision,
120
+ counts,
121
+ changes,
122
+ };
123
+ }
124
+
125
+ function selectObservableResources(desiredResources, state, options = {}) {
126
+ const registry = options.registry || createDefaultRegistry();
127
+ validatePlannerState({
128
+ desiredResources,
129
+ registry,
130
+ state,
131
+ });
132
+ return normalizeResourceList(desiredResources, { registry }).filter((resource) => {
133
+ const stateResource = getResourceState(state, resource.resourceType, resource.key);
134
+ return !!(stateResource && hasTrustedLastApplied(stateResource));
135
+ });
136
+ }
137
+
138
+ function validatePlannerState(input) {
139
+ const registry = input.registry || createDefaultRegistry();
140
+ const desiredResources = normalizeResourceList(input.desiredResources, { registry });
141
+ const state = input.state || {};
142
+ const desiredById = indexResources(desiredResources);
143
+ const stateById = indexStateResources(state);
144
+
145
+ for (const [id, entry] of stateById.entries()) {
146
+ assertAdapterVersion(entry.resourceType, entry.key, entry.stateResource, registry);
147
+ if (entry.stateResource.lastApplied !== undefined && entry.stateResource.lastAppliedHash !== undefined) {
148
+ assertLastAppliedHash(entry.resourceType, entry.key, entry.stateResource);
149
+ }
150
+ const desiredResource = desiredById.get(id);
151
+ if (desiredResource) {
152
+ assertAdapterVersion(desiredResource.resourceType, desiredResource.key, entry.stateResource, registry);
153
+ }
154
+ }
155
+ }
156
+
157
+ function classifyResource(input) {
158
+ const desiredResource = input.desiredResource;
159
+ const stateEntry = input.stateResource;
160
+ const observedResource = input.observedResource;
161
+
162
+ if (!desiredResource && stateEntry) {
163
+ return createChange({
164
+ id: input.id,
165
+ operation: OPERATIONS.ORPHAN,
166
+ reasonCode: REASON_CODES.MANIFEST_RESOURCE_REMOVED,
167
+ risk: 'medium',
168
+ });
169
+ }
170
+
171
+ if (!desiredResource) {
172
+ return null;
173
+ }
174
+
175
+ if (!stateEntry) {
176
+ return createChange({
177
+ id: input.id,
178
+ operation: OPERATIONS.CREATE,
179
+ reasonCode: REASON_CODES.NO_STATE_BINDING,
180
+ risk: 'medium',
181
+ });
182
+ }
183
+
184
+ assertAdapterVersion(desiredResource.resourceType, desiredResource.key, stateEntry.stateResource, input.registry);
185
+ const trustedLastApplied = getTrustedLastApplied(desiredResource, stateEntry.stateResource);
186
+ if (!trustedLastApplied) {
187
+ return createChange({
188
+ id: input.id,
189
+ operation: OPERATIONS.UNMANAGED,
190
+ reasonCode: REASON_CODES.STATE_LAST_APPLIED_MISSING,
191
+ risk: 'high',
192
+ });
193
+ }
194
+
195
+ if (!observedResource) {
196
+ return createChange({
197
+ id: input.id,
198
+ operation: OPERATIONS.CONFLICT,
199
+ reasonCode: REASON_CODES.REMOTE_RESOURCE_MISSING,
200
+ risk: 'high',
201
+ });
202
+ }
203
+
204
+ const desiredHash = hashManagedIdentity({
205
+ adapterVersion: desiredResource.adapterVersion,
206
+ managed: desiredResource.desired || {},
207
+ resourceType: desiredResource.resourceType,
208
+ key: desiredResource.key,
209
+ });
210
+ const lastAppliedHash = trustedLastApplied.identityHash;
211
+ const observedHash = hashManagedIdentity({
212
+ adapterVersion: observedResource.adapterVersion,
213
+ managed: observedResource.managed || {},
214
+ resourceType: observedResource.resourceType,
215
+ key: observedResource.key,
216
+ });
217
+ const adapter = input.registry.get(desiredResource.resourceType);
218
+ const comparisonLastApplied = prepareComparisonLastApplied(adapter, {
219
+ desired: desiredResource.desired || {},
220
+ lastApplied: trustedLastApplied.managed,
221
+ observed: observedResource.managed || {},
222
+ });
223
+ const comparisonLastAppliedHash = hashManagedIdentity({
224
+ adapterVersion: desiredResource.adapterVersion,
225
+ managed: comparisonLastApplied,
226
+ resourceType: desiredResource.resourceType,
227
+ key: desiredResource.key,
228
+ });
229
+ const observedConflictReason = classifyObservedConflict(adapter, {
230
+ desired: desiredResource.desired || {},
231
+ desiredChanged: desiredHash !== comparisonLastAppliedHash,
232
+ lastApplied: trustedLastApplied.managed,
233
+ observed: observedResource.managed || {},
234
+ observedMatchesDesired: observedHash === desiredHash,
235
+ observedResource,
236
+ });
237
+
238
+ if (observedConflictReason) {
239
+ return createChange({
240
+ id: input.id,
241
+ operation: OPERATIONS.CONFLICT,
242
+ reasonCode: observedConflictReason,
243
+ risk: 'high',
244
+ });
245
+ }
246
+
247
+ if (observedHash === desiredHash) {
248
+ const stateRepair = lastAppliedHash !== desiredHash;
249
+ return createChange({
250
+ id: input.id,
251
+ operation: OPERATIONS.NOOP,
252
+ reasonCode: stateRepair
253
+ ? REASON_CODES.STATE_REPAIR_REQUIRED
254
+ : REASON_CODES.ALREADY_MATCHES_DESIRED,
255
+ risk: 'low',
256
+ stateRepair,
257
+ });
258
+ }
259
+
260
+ if (observedHash === comparisonLastAppliedHash && desiredHash !== comparisonLastAppliedHash) {
261
+ const reasonCode = classifyUpdateReason(adapter, {
262
+ desired: desiredResource.desired || {},
263
+ lastApplied: trustedLastApplied.managed,
264
+ observed: observedResource.managed || {},
265
+ });
266
+ return createChange({
267
+ id: input.id,
268
+ operation: OPERATIONS.UPDATE,
269
+ reasonCode,
270
+ risk: 'low',
271
+ });
272
+ }
273
+
274
+ if (desiredHash === comparisonLastAppliedHash && observedHash !== comparisonLastAppliedHash) {
275
+ return createChange({
276
+ id: input.id,
277
+ operation: OPERATIONS.CONFLICT,
278
+ reasonCode: REASON_CODES.REMOTE_DRIFT,
279
+ risk: 'high',
280
+ });
281
+ }
282
+
283
+ return createChange({
284
+ id: input.id,
285
+ operation: OPERATIONS.CONFLICT,
286
+ reasonCode: REASON_CODES.DESIRED_AND_OBSERVED_CHANGED,
287
+ risk: 'high',
288
+ });
289
+ }
290
+
291
+ function classifyObservedConflict(adapter, input) {
292
+ if (typeof adapter.classifyObservedConflict !== 'function') {
293
+ return undefined;
294
+ }
295
+ const reasonCode = adapter.classifyObservedConflict(input);
296
+ if (reasonCode === undefined) {
297
+ return undefined;
298
+ }
299
+ if (typeof reasonCode !== 'string' || !/^[A-Z][A-Z0-9_]*$/.test(reasonCode)) {
300
+ throw schemaError('SCHEMA_INTERNAL_ERROR', 'Resource adapter returned an invalid observed conflict reasonCode.');
301
+ }
302
+ return reasonCode;
303
+ }
304
+
305
+ function prepareComparisonLastApplied(adapter, input) {
306
+ if (typeof adapter.prepareComparison !== 'function') {
307
+ return input.lastApplied;
308
+ }
309
+ const result = adapter.prepareComparison(input);
310
+ if (!result || typeof result !== 'object' || Array.isArray(result) || !result.lastApplied) {
311
+ throw schemaError('SCHEMA_INTERNAL_ERROR', 'Resource adapter returned an invalid planner comparison.');
312
+ }
313
+ return result.lastApplied;
314
+ }
315
+
316
+ function classifyUpdateReason(adapter, input) {
317
+ if (typeof adapter.classifyUpdate !== 'function') {
318
+ return REASON_CODES.DESIRED_CHANGED;
319
+ }
320
+ const reasonCode = adapter.classifyUpdate(input);
321
+ if (reasonCode === undefined) {
322
+ return REASON_CODES.DESIRED_CHANGED;
323
+ }
324
+ if (typeof reasonCode !== 'string' || !/^[A-Z][A-Z0-9_]*$/.test(reasonCode)) {
325
+ throw schemaError('SCHEMA_INTERNAL_ERROR', 'Resource adapter returned an invalid update reasonCode.');
326
+ }
327
+ return reasonCode;
328
+ }
329
+
330
+ function createChange(input) {
331
+ const parsed = parseResourceId(input.id);
332
+ const change = {
333
+ operation: input.operation,
334
+ resourceType: parsed.resourceType,
335
+ key: parsed.key,
336
+ risk: input.risk,
337
+ reasonCode: input.reasonCode,
338
+ };
339
+ if (input.stateRepair === true) {
340
+ change.stateRepair = true;
341
+ }
342
+ return change;
343
+ }
344
+
345
+ function assertAdapterVersion(resourceType, key, stateResource, registry) {
346
+ const adapter = registry.get(resourceType);
347
+ if (stateResource.adapterVersion !== adapter.adapterVersion) {
348
+ throw schemaError('SCHEMA_STATE_ADAPTER_VERSION_UNSUPPORTED', 'State resource adapterVersion is not supported by the current adapter.', {
349
+ details: {
350
+ resourceType,
351
+ key,
352
+ stateAdapterVersion: stateResource.adapterVersion,
353
+ adapterVersion: adapter.adapterVersion,
354
+ },
355
+ });
356
+ }
357
+ }
358
+
359
+ function getTrustedLastApplied(resource, stateResource) {
360
+ if (!hasTrustedLastApplied(stateResource)) {
361
+ return null;
362
+ }
363
+ assertLastAppliedHash(resource.resourceType, resource.key, stateResource);
364
+ return {
365
+ identityHash: hashManagedIdentity({
366
+ adapterVersion: stateResource.adapterVersion,
367
+ managed: stateResource.lastApplied,
368
+ resourceType: resource.resourceType,
369
+ key: resource.key,
370
+ }),
371
+ legacyHash: stateResource.lastAppliedHash,
372
+ managed: stateResource.lastApplied,
373
+ };
374
+ }
375
+
376
+ function hasTrustedLastApplied(stateResource) {
377
+ return !!(
378
+ stateResource &&
379
+ stateResource.lastApplied &&
380
+ stateResource.lastAppliedHash
381
+ );
382
+ }
383
+
384
+ function assertLastAppliedHash(resourceType, key, stateResource) {
385
+ const computed = hashStable(stateResource.lastApplied || {});
386
+ if (computed !== stateResource.lastAppliedHash) {
387
+ throw schemaError('SCHEMA_PLAN_STATE_INTEGRITY_FAILED', 'State lastAppliedHash does not match lastApplied content.', {
388
+ details: {
389
+ resourceType,
390
+ key,
391
+ },
392
+ });
393
+ }
394
+ }
395
+
396
+ function topologicalSortIds(ids, desiredById) {
397
+ const idSet = new Set(ids);
398
+ const indegree = new Map(ids.map(id => [id, 0]));
399
+ const outgoing = new Map(ids.map(id => [id, []]));
400
+
401
+ for (const [id, resource] of desiredById.entries()) {
402
+ if (!idSet.has(id)) {
403
+ continue;
404
+ }
405
+ for (const dependency of resource.dependsOn || []) {
406
+ if (!idSet.has(dependency)) {
407
+ continue;
408
+ }
409
+ indegree.set(id, (indegree.get(id) || 0) + 1);
410
+ outgoing.get(dependency).push(id);
411
+ }
412
+ }
413
+
414
+ for (const dependents of outgoing.values()) {
415
+ dependents.sort(compareCodePoints);
416
+ }
417
+
418
+ const ready = sortStrings([...indegree.entries()]
419
+ .filter(([, count]) => count === 0)
420
+ .map(([id]) => id));
421
+ const sorted = [];
422
+
423
+ while (ready.length > 0) {
424
+ const id = ready.shift();
425
+ sorted.push(id);
426
+ for (const dependent of outgoing.get(id) || []) {
427
+ const nextCount = (indegree.get(dependent) || 0) - 1;
428
+ indegree.set(dependent, nextCount);
429
+ if (nextCount === 0) {
430
+ insertSorted(ready, dependent);
431
+ }
432
+ }
433
+ }
434
+
435
+ if (sorted.length !== ids.length) {
436
+ throw schemaError('SCHEMA_DEPENDENCY_CYCLE', 'Manifest resource dependencies must not contain cycles.', {
437
+ details: {
438
+ cycle: findRemainingCycle(ids, sorted, outgoing),
439
+ },
440
+ });
441
+ }
442
+ return sorted;
443
+ }
444
+
445
+ function findRemainingCycle(ids, sorted, outgoing) {
446
+ const sortedSet = new Set(sorted);
447
+ const remaining = new Set(ids.filter(id => !sortedSet.has(id)));
448
+ const visiting = new Set();
449
+ const visited = new Set();
450
+ const stack = [];
451
+
452
+ function visit(id) {
453
+ if (!remaining.has(id) || visited.has(id)) {
454
+ return null;
455
+ }
456
+ if (visiting.has(id)) {
457
+ const start = stack.indexOf(id);
458
+ return stack.slice(start).concat(id);
459
+ }
460
+
461
+ visiting.add(id);
462
+ stack.push(id);
463
+ for (const dependent of outgoing.get(id) || []) {
464
+ const cycle = visit(dependent);
465
+ if (cycle) {
466
+ return cycle;
467
+ }
468
+ }
469
+ stack.pop();
470
+ visiting.delete(id);
471
+ visited.add(id);
472
+ return null;
473
+ }
474
+
475
+ for (const id of sortStrings(remaining)) {
476
+ const cycle = visit(id);
477
+ if (cycle) {
478
+ return cycle;
479
+ }
480
+ }
481
+ return sortStrings(remaining);
482
+ }
483
+
484
+ function insertSorted(values, nextValue) {
485
+ const index = values.findIndex(value => compareCodePoints(nextValue, value) < 0);
486
+ if (index === -1) {
487
+ values.push(nextValue);
488
+ } else {
489
+ values.splice(index, 0, nextValue);
490
+ }
491
+ }
492
+
493
+ function countChanges(changes) {
494
+ const counts = {};
495
+ COUNT_KEYS.forEach((key) => {
496
+ counts[key] = 0;
497
+ });
498
+ changes.forEach((change) => {
499
+ counts[change.operation] += 1;
500
+ });
501
+ return counts;
502
+ }
503
+
504
+ function computePlanId(input) {
505
+ const desiredBasis = normalizeResourceList(input.desiredResources, { registry: input.registry }).map(resource => ({
506
+ resourceType: resource.resourceType,
507
+ key: resource.key,
508
+ adapterVersion: resource.adapterVersion,
509
+ desiredManagedHash: hashManagedIdentity({
510
+ adapterVersion: resource.adapterVersion,
511
+ managed: resource.desired || {},
512
+ resourceType: resource.resourceType,
513
+ key: resource.key,
514
+ }),
515
+ })).sort(compareResourceObjects);
516
+ const stateBasis = normalizeStateBasis(input.state, input.registry);
517
+ const observedBasis = normalizeObservedList(input.observedResources, { registry: input.registry }).map(resource => {
518
+ const basis = {
519
+ resourceType: resource.resourceType,
520
+ key: resource.key,
521
+ adapterVersion: resource.adapterVersion,
522
+ observedManagedHash: hashManagedIdentity({
523
+ adapterVersion: resource.adapterVersion,
524
+ managed: resource.managed || {},
525
+ resourceType: resource.resourceType,
526
+ key: resource.key,
527
+ }),
528
+ };
529
+ for (const property of [
530
+ 'observedDraftCount',
531
+ 'observedDraftHash',
532
+ 'observedIdentityHash',
533
+ 'observedIdentityMatchesBindings',
534
+ ]) {
535
+ if (resource[property] !== undefined) {
536
+ basis[property] = resource[property];
537
+ }
538
+ }
539
+ if (resource.remoteSchemaHash !== undefined) {
540
+ basis.remoteSchemaHash = resource.remoteSchemaHash;
541
+ }
542
+ return basis;
543
+ }).sort(compareResourceObjects);
544
+
545
+ return createSha256(stableStringify({
546
+ contractVersion: PLAN_CONTRACT_VERSION,
547
+ changes: input.changes,
548
+ desired: desiredBasis,
549
+ environmentKey: input.environmentKey,
550
+ manifestHash: input.manifestHash,
551
+ observed: observedBasis,
552
+ state: stateBasis,
553
+ stateRevision: input.stateRevision,
554
+ }));
555
+ }
556
+
557
+ function normalizeResourceList(resources, options = {}) {
558
+ const registry = options.registry;
559
+ return (Array.isArray(resources) ? resources : []).map(resource => ({
560
+ resourceType: resource.resourceType,
561
+ key: resource.key,
562
+ adapterVersion: resource.adapterVersion || getAdapterVersion(registry, resource.resourceType),
563
+ desired: resource.desired || {},
564
+ dependsOn: Array.isArray(resource.dependsOn) ? sortStrings(resource.dependsOn) : [],
565
+ }));
566
+ }
567
+
568
+ function normalizeObservedList(resources, options = {}) {
569
+ const registry = options.registry;
570
+ return (Array.isArray(resources) ? resources : []).map(resource => {
571
+ const normalized = {
572
+ resourceType: resource.resourceType,
573
+ key: resource.key,
574
+ adapterVersion: resource.adapterVersion || getAdapterVersion(registry, resource.resourceType),
575
+ managed: resource.managed || {},
576
+ observedManagedHash: resource.observedManagedHash,
577
+ };
578
+ if (resource.remoteSchemaHash !== undefined) {
579
+ if (!isSha256(resource.remoteSchemaHash)) {
580
+ throw schemaError('SCHEMA_INTERNAL_ERROR', 'Observed resource remoteSchemaHash must be a SHA-256 hash.');
581
+ }
582
+ normalized.remoteSchemaHash = resource.remoteSchemaHash;
583
+ }
584
+ for (const property of [
585
+ 'observedDraftCount',
586
+ 'observedDraftHash',
587
+ 'observedIdentityHash',
588
+ 'observedIdentityMatchesBindings',
589
+ ]) {
590
+ if (resource[property] !== undefined) {
591
+ normalized[property] = resource[property];
592
+ }
593
+ }
594
+ return normalized;
595
+ });
596
+ }
597
+
598
+ function normalizeStateBasis(state, registry) {
599
+ const entries = [];
600
+ const resources = state && state.resources || {};
601
+ for (const resourceType of sortStrings(Object.keys(resources))) {
602
+ for (const key of sortStrings(Object.keys(resources[resourceType] || {}))) {
603
+ const entry = resources[resourceType][key] || {};
604
+ const basis = {
605
+ resourceType,
606
+ key,
607
+ adapterVersion: entry.adapterVersion,
608
+ hasBindings: !!entry.bindings,
609
+ hasTrustedLastApplied: hasTrustedLastApplied(entry),
610
+ };
611
+ if (hasTrustedLastApplied(entry)) {
612
+ basis.lastAppliedManagedHash = hashManagedIdentity({
613
+ adapterVersion: entry.adapterVersion || getAdapterVersion(registry, resourceType),
614
+ managed: entry.lastApplied || {},
615
+ resourceType,
616
+ key,
617
+ });
618
+ }
619
+ entries.push(basis);
620
+ }
621
+ }
622
+ return entries.sort(compareResourceObjects);
623
+ }
624
+
625
+ function hashManagedIdentity(input) {
626
+ return hashStable({
627
+ adapterVersion: input.adapterVersion,
628
+ contractVersion: PLAN_CONTRACT_VERSION,
629
+ key: input.key,
630
+ managed: input.managed || {},
631
+ resourceType: input.resourceType,
632
+ });
633
+ }
634
+
635
+ function getAdapterVersion(registry, resourceType) {
636
+ if (!registry) {
637
+ return 1;
638
+ }
639
+ return registry.get(resourceType).adapterVersion;
640
+ }
641
+
642
+ function indexResources(resources) {
643
+ const result = new Map();
644
+ for (const resource of resources) {
645
+ result.set(resourceId(resource), resource);
646
+ }
647
+ return result;
648
+ }
649
+
650
+ function indexStateResources(state) {
651
+ const result = new Map();
652
+ const resources = state && state.resources || {};
653
+ for (const resourceType of sortStrings(Object.keys(resources))) {
654
+ const resourceMap = resources[resourceType] || {};
655
+ for (const key of sortStrings(Object.keys(resourceMap))) {
656
+ result.set(`${resourceType}:${key}`, {
657
+ resourceType,
658
+ key,
659
+ stateResource: resourceMap[key],
660
+ });
661
+ }
662
+ }
663
+ return result;
664
+ }
665
+
666
+ function compareResourceObjects(left, right) {
667
+ return compareCodePoints(`${left.resourceType}:${left.key}`, `${right.resourceType}:${right.key}`);
668
+ }
669
+
670
+ function normalizeStateRevision(value) {
671
+ return Number.isInteger(value) && value >= 0 ? value : 0;
672
+ }
673
+
674
+ function parseResourceId(id) {
675
+ const separator = String(id).indexOf(':');
676
+ return {
677
+ resourceType: String(id).slice(0, separator),
678
+ key: String(id).slice(separator + 1),
679
+ };
680
+ }
681
+
682
+ module.exports = {
683
+ OPERATIONS,
684
+ PLAN_CONTRACT_VERSION,
685
+ PLAN_KIND,
686
+ REASON_CODES,
687
+ createPlan,
688
+ hashManagedIdentity,
689
+ selectObservableResources,
690
+ validatePlannerState,
691
+ };