openyida 2026.7.18 → 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 (200) hide show
  1. package/README.md +37 -2
  2. package/bin/yida.js +113 -18
  3. package/docs/capabilities.md +202 -0
  4. package/lib/agent-center/agent-center.js +2 -3
  5. package/lib/app/app-list.js +3 -4
  6. package/lib/app/build-page.js +13 -6
  7. package/lib/app/check-page.js +16 -6
  8. package/lib/app/compile.js +17 -7
  9. package/lib/app/create-app.js +14 -2
  10. package/lib/app/create-form/api-path.js +25 -0
  11. package/lib/app/create-form/args.js +13 -0
  12. package/lib/app/create-form/commands.js +4 -0
  13. package/lib/app/create-form/definition-reader.js +23 -23
  14. package/lib/app/create-form/field-normalizers.js +26 -7
  15. package/lib/app/create-form/schema-patch.js +4 -1
  16. package/lib/app/create-form.js +155 -68
  17. package/lib/app/create-page.js +47 -10
  18. package/lib/app/generate-page.js +20 -1
  19. package/lib/app/get-form-config.js +2 -1
  20. package/lib/app/get-schema.js +67 -15
  21. package/lib/app/import-app.js +94 -42
  22. package/lib/app/list-forms.js +4 -8
  23. package/lib/app/page-ir.js +140 -0
  24. package/lib/app/publish.js +289 -556
  25. package/lib/app/schema-field-resolution.js +370 -0
  26. package/lib/app/services/app-reader.js +85 -0
  27. package/lib/app/services/app-service.js +235 -0
  28. package/lib/app/services/canvas-page-compiler.js +91 -0
  29. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  30. package/lib/app/services/field-bindings.js +68 -0
  31. package/lib/app/services/form-compiler.js +1922 -0
  32. package/lib/app/services/form-mode-service.js +182 -0
  33. package/lib/app/services/form-schema-patcher.js +333 -0
  34. package/lib/app/services/form-schema-reader.js +59 -0
  35. package/lib/app/services/form-service.js +379 -0
  36. package/lib/app/services/form-validation.js +562 -0
  37. package/lib/app/services/native-page-compiler.js +92 -0
  38. package/lib/app/services/native-page-schema-builder.js +428 -0
  39. package/lib/app/services/page-resource-service.js +185 -0
  40. package/lib/app/update-app.js +22 -9
  41. package/lib/app/update-form-config.js +4 -2
  42. package/lib/asset/asset-cmd.js +19 -6
  43. package/lib/auth/oauth-loopback.js +376 -15
  44. package/lib/auth/token-auth.js +34 -3
  45. package/lib/auth/token-store.js +22 -4
  46. package/lib/basic-info/basic-info.js +2 -1
  47. package/lib/bridge/bridge.js +43 -18
  48. package/lib/core/agent-capabilities.js +5 -3
  49. package/lib/core/command-errors.js +38 -0
  50. package/lib/core/command-manifest.js +59 -1
  51. package/lib/core/copy.js +12 -0
  52. package/lib/core/doctor.js +15 -4
  53. package/lib/core/env-cmd.js +1 -1
  54. package/lib/core/env.js +7 -6
  55. package/lib/core/i18n.js +66 -8
  56. package/lib/core/legacy-schema-guard.js +408 -0
  57. package/lib/core/locales/en.js +38 -46
  58. package/lib/core/locales/zh.js +30 -46
  59. package/lib/core/sample.js +12 -2
  60. package/lib/core/utils.js +334 -66
  61. package/lib/core/yida-client.js +108 -5
  62. package/lib/corp-efficiency/corp-efficiency.js +2 -2
  63. package/lib/corp-manager/corp-manager.js +3 -3
  64. package/lib/formula/evaluate.js +11 -68
  65. package/lib/formula/field-refs.js +83 -0
  66. package/lib/i18n-management/i18n-management.js +2 -3
  67. package/lib/process/configure-process.js +44 -1992
  68. package/lib/process/create-process.js +54 -87
  69. package/lib/process/preview-process.js +20 -8
  70. package/lib/process/services/process-compiler.js +2056 -0
  71. package/lib/process/services/process-reader.js +232 -0
  72. package/lib/process/services/process-resource-service.js +571 -0
  73. package/lib/process/services/process-service.js +122 -0
  74. package/lib/process/services/process-stage-checkpoint.js +246 -0
  75. package/lib/report/append.js +7 -26
  76. package/lib/report/http.js +31 -5
  77. package/lib/report/index.js +10 -2
  78. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  79. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  80. package/lib/samples/yida-canvas-custom-page/dashboard-starter.canvas.jsx +5 -2
  81. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  82. package/lib/samples/yida-canvas-custom-page/portal-shell-home.canvas.jsx +2 -2
  83. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +206 -10
  84. package/lib/samples/yida-custom-page/data-management.oyd.jsx +14 -3
  85. package/lib/samples/yida-custom-page/design-tokens.js +2 -2
  86. package/lib/samples/yida-custom-page/detail-profile.oyd.jsx +1 -1
  87. package/lib/samples/yida-custom-page/portal-shell-home.oyd.jsx +7 -2
  88. package/lib/samples/yida-custom-page/split-pane-detail.oyd.jsx +2 -1
  89. package/lib/schema/adapters/app-adapter.js +210 -0
  90. package/lib/schema/adapters/form-adapter.js +1362 -0
  91. package/lib/schema/adapters/keys.js +19 -0
  92. package/lib/schema/adapters/page-adapter.js +566 -0
  93. package/lib/schema/adapters/process-adapter.js +519 -0
  94. package/lib/schema/applier.js +1878 -0
  95. package/lib/schema/apply-store.js +1083 -0
  96. package/lib/schema/command.js +645 -0
  97. package/lib/schema/dependency-graph.js +83 -0
  98. package/lib/schema/errors.js +481 -0
  99. package/lib/schema/hash.js +9 -0
  100. package/lib/schema/manifest-limits.js +177 -0
  101. package/lib/schema/manifest-loader.js +309 -0
  102. package/lib/schema/manifest-schema-v1.json +193 -0
  103. package/lib/schema/normalize-manifest.js +281 -0
  104. package/lib/schema/page-canvas-foundation.js +447 -0
  105. package/lib/schema/page-data-source-builder.js +561 -0
  106. package/lib/schema/page-foundation.js +556 -0
  107. package/lib/schema/page-source-loader.js +314 -0
  108. package/lib/schema/planner.js +691 -0
  109. package/lib/schema/remote-dispatch-boundary.js +39 -0
  110. package/lib/schema/remote-missing.js +21 -0
  111. package/lib/schema/remote-reader.js +189 -0
  112. package/lib/schema/resource-registry.js +157 -0
  113. package/lib/schema/server-revision.js +71 -0
  114. package/lib/schema/sort.js +28 -0
  115. package/lib/schema/state-store.js +589 -0
  116. package/package.json +10 -8
  117. package/scripts/check-release-risks.js +254 -0
  118. package/scripts/generate-command-docs.js +6 -1
  119. package/scripts/i18n-baseline.json +11 -11
  120. package/scripts/postinstall.js +8 -12
  121. package/scripts/validate-ci.sh +15 -11
  122. package/scripts/validate-i18n.js +30 -9
  123. package/scripts/validate-package-size.js +5 -2
  124. package/scripts/validate-skills.js +4 -0
  125. package/scripts/validate-structure.js +15 -0
  126. package/yida-skills/SKILL.md +117 -30
  127. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  128. package/yida-skills/references/setup-and-env.md +59 -59
  129. package/yida-skills/references/task-retrospective.md +6 -6
  130. package/yida-skills/skills/yida-app/SKILL.md +78 -17
  131. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  132. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  133. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +15 -0
  134. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  135. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  136. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  137. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  138. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  139. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  140. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  141. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  142. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  143. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  144. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  145. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  146. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  147. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  148. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  149. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  150. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  151. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  152. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  153. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  154. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  155. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  156. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  157. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  158. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  159. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  160. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  161. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  162. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  163. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  164. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  165. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  166. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  167. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  168. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  169. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  170. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  171. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  172. package/yida-skills/skills/yida-report/SKILL.md +9 -235
  173. package/yida-skills/skills/yida-report/references/schema-builder-details.md +239 -0
  174. package/yida-skills/skills/yida-skill-evaluator/SKILL.md +170 -0
  175. package/yida-skills/skills-index.json +238 -54
  176. package/lib/core/locales/ar.js +0 -834
  177. package/lib/core/locales/de.js +0 -834
  178. package/lib/core/locales/es.js +0 -834
  179. package/lib/core/locales/fr.js +0 -834
  180. package/lib/core/locales/hi.js +0 -834
  181. package/lib/core/locales/ja.js +0 -1227
  182. package/lib/core/locales/ko.js +0 -836
  183. package/lib/core/locales/pt.js +0 -834
  184. package/lib/core/locales/vi.js +0 -834
  185. package/lib/core/locales/zh-HK.js +0 -1263
  186. package/project/pages/src/demo-birthday-game.oyd.jsx +0 -832
  187. package/project/pages/src/demo-chip-insight.oyd.jsx +0 -983
  188. package/project/pages/src/demo-compat-smoke.oyd.jsx +0 -58
  189. package/project/pages/src/demo-crm-batch-entry.oyd.jsx +0 -805
  190. package/project/pages/src/demo-crm-dashboard.oyd.jsx +0 -677
  191. package/project/pages/src/demo-future-vision-2026.oyd.jsx +0 -1102
  192. package/project/pages/src/demo-ppt.oyd.jsx +0 -1192
  193. package/project/pages/src/demo-salary-calculator.oyd.jsx +0 -904
  194. package/project/pages/src/openyida-knowledge-doc.oyd.jsx +0 -1714
  195. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  196. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  197. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  198. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  199. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  200. 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
+ };