@spottoai/types-package 1.0.2-dev1138.2 → 1.0.2-dev1138.5

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 (261) hide show
  1. package/README.md +9 -0
  2. package/dist/accounts/accounts.d.ts +77 -4
  3. package/dist/accounts/accounts.d.ts.map +1 -1
  4. package/dist/accounts/accounts.js +2 -1
  5. package/dist/accounts/accounts.js.map +1 -1
  6. package/dist/accounts/azureManualOnboarding.d.ts +114 -0
  7. package/dist/accounts/azureManualOnboarding.d.ts.map +1 -0
  8. package/dist/accounts/azureManualOnboarding.js +12 -0
  9. package/dist/accounts/azureManualOnboarding.js.map +1 -0
  10. package/dist/accounts/azureSpSetup.d.ts +128 -31
  11. package/dist/accounts/azureSpSetup.d.ts.map +1 -1
  12. package/dist/accounts/azureSpSetup.js +15 -0
  13. package/dist/accounts/azureSpSetup.js.map +1 -1
  14. package/dist/accounts/azureSpSetupDurable.d.ts +8 -0
  15. package/dist/accounts/azureSpSetupDurable.d.ts.map +1 -1
  16. package/dist/accounts/azureSpSetupDurable.js +28 -0
  17. package/dist/accounts/azureSpSetupDurable.js.map +1 -1
  18. package/dist/accounts/index.d.ts +1 -0
  19. package/dist/accounts/index.d.ts.map +1 -1
  20. package/dist/accounts/index.js +1 -0
  21. package/dist/accounts/index.js.map +1 -1
  22. package/dist/accounts/readPermissions.d.ts +1 -1
  23. package/dist/accounts/readPermissions.d.ts.map +1 -1
  24. package/dist/accounts/readPermissions.js.map +1 -1
  25. package/dist/accounts/validation.d.ts +24 -0
  26. package/dist/accounts/validation.d.ts.map +1 -1
  27. package/dist/ai/index.d.ts +14 -1
  28. package/dist/ai/index.d.ts.map +1 -1
  29. package/dist/ai/index.js +12 -0
  30. package/dist/ai/index.js.map +1 -1
  31. package/dist/aws/commitments-planning.d.ts +5 -0
  32. package/dist/aws/commitments-planning.d.ts.map +1 -1
  33. package/dist/aws/commitments-planning.js +19 -0
  34. package/dist/aws/commitments-planning.js.map +1 -1
  35. package/dist/aws/estates.d.ts +1 -1
  36. package/dist/aws/estates.d.ts.map +1 -1
  37. package/dist/aws/estates.js +7 -1
  38. package/dist/aws/estates.js.map +1 -1
  39. package/dist/aws/index.d.ts +5 -0
  40. package/dist/aws/index.d.ts.map +1 -1
  41. package/dist/aws/index.js +5 -0
  42. package/dist/aws/index.js.map +1 -1
  43. package/dist/aws/onboarding.d.ts +3 -0
  44. package/dist/aws/onboarding.d.ts.map +1 -1
  45. package/dist/aws/organizationCommitments.d.ts +260 -0
  46. package/dist/aws/organizationCommitments.d.ts.map +1 -0
  47. package/dist/aws/organizationCommitments.js +53 -0
  48. package/dist/aws/organizationCommitments.js.map +1 -0
  49. package/dist/aws/organizationCommitmentsApiValidation.d.ts +21 -0
  50. package/dist/aws/organizationCommitmentsApiValidation.d.ts.map +1 -0
  51. package/dist/aws/organizationCommitmentsApiValidation.js +226 -0
  52. package/dist/aws/organizationCommitmentsApiValidation.js.map +1 -0
  53. package/dist/aws/organizationCommitmentsValidation.d.ts +12 -0
  54. package/dist/aws/organizationCommitmentsValidation.d.ts.map +1 -0
  55. package/dist/aws/organizationCommitmentsValidation.js +228 -0
  56. package/dist/aws/organizationCommitmentsValidation.js.map +1 -0
  57. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.d.ts +9 -0
  58. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.d.ts.map +1 -0
  59. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js +407 -0
  60. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js.map +1 -0
  61. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.d.ts +17 -0
  62. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.d.ts.map +1 -0
  63. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js +6 -0
  64. package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js.map +1 -0
  65. package/dist/aws/requests.d.ts +13 -2
  66. package/dist/aws/requests.d.ts.map +1 -1
  67. package/dist/aws/requests.js +1 -1
  68. package/dist/aws/requests.js.map +1 -1
  69. package/dist/azure/commitmentsPlanning.d.ts +2 -2
  70. package/dist/azure/commitmentsPlanning.d.ts.map +1 -1
  71. package/dist/azure/financialAnalytics.d.ts +68 -2
  72. package/dist/azure/financialAnalytics.d.ts.map +1 -1
  73. package/dist/azure/financialAnalyticsDelivery.d.ts +2 -0
  74. package/dist/azure/financialAnalyticsDelivery.d.ts.map +1 -1
  75. package/dist/azure/financialAnalyticsDelivery.js +4 -1
  76. package/dist/azure/financialAnalyticsDelivery.js.map +1 -1
  77. package/dist/azure/financialAnalyticsDisplayKernel.d.ts +12 -0
  78. package/dist/azure/financialAnalyticsDisplayKernel.d.ts.map +1 -0
  79. package/dist/azure/financialAnalyticsDisplayKernel.js +144 -0
  80. package/dist/azure/financialAnalyticsDisplayKernel.js.map +1 -0
  81. package/dist/azure/financialAnalyticsValidation.d.ts.map +1 -1
  82. package/dist/azure/financialAnalyticsValidation.js +61 -7
  83. package/dist/azure/financialAnalyticsValidation.js.map +1 -1
  84. package/dist/azure/financialAuthorityProjectionKernel.d.ts +7 -0
  85. package/dist/azure/financialAuthorityProjectionKernel.d.ts.map +1 -0
  86. package/dist/azure/financialAuthorityProjectionKernel.js +165 -0
  87. package/dist/azure/financialAuthorityProjectionKernel.js.map +1 -0
  88. package/dist/azure/financialAuthorityView.d.ts +4 -0
  89. package/dist/azure/financialAuthorityView.d.ts.map +1 -1
  90. package/dist/azure/financialAuthorityView.js.map +1 -1
  91. package/dist/azure/financialAuthorityViewValidation.d.ts +3 -1
  92. package/dist/azure/financialAuthorityViewValidation.d.ts.map +1 -1
  93. package/dist/azure/financialAuthorityViewValidation.js +206 -2
  94. package/dist/azure/financialAuthorityViewValidation.js.map +1 -1
  95. package/dist/azure/financialChargeComposition.d.ts +81 -0
  96. package/dist/azure/financialChargeComposition.d.ts.map +1 -0
  97. package/dist/azure/financialChargeComposition.js +41 -0
  98. package/dist/azure/financialChargeComposition.js.map +1 -0
  99. package/dist/azure/financialChargeCompositionKernel.d.ts +3 -0
  100. package/dist/azure/financialChargeCompositionKernel.d.ts.map +1 -0
  101. package/dist/azure/financialChargeCompositionKernel.js +53 -0
  102. package/dist/azure/financialChargeCompositionKernel.js.map +1 -0
  103. package/dist/azure/financialChargeCompositionValidation.d.ts +8 -0
  104. package/dist/azure/financialChargeCompositionValidation.d.ts.map +1 -0
  105. package/dist/azure/financialChargeCompositionValidation.js +164 -0
  106. package/dist/azure/financialChargeCompositionValidation.js.map +1 -0
  107. package/dist/azure/financialDataflow.d.ts +8 -1
  108. package/dist/azure/financialDataflow.d.ts.map +1 -1
  109. package/dist/azure/financialDataflow.js.map +1 -1
  110. package/dist/azure/financialDataflowKernel.d.ts +2 -0
  111. package/dist/azure/financialDataflowKernel.d.ts.map +1 -1
  112. package/dist/azure/financialDataflowKernel.js +60 -7
  113. package/dist/azure/financialDataflowKernel.js.map +1 -1
  114. package/dist/azure/financialDataflowValidation.d.ts +1 -1
  115. package/dist/azure/financialDataflowValidation.d.ts.map +1 -1
  116. package/dist/azure/financialDataflowValidation.js +146 -28
  117. package/dist/azure/financialDataflowValidation.js.map +1 -1
  118. package/dist/azure/financialPolicy.d.ts +3 -0
  119. package/dist/azure/financialPolicy.d.ts.map +1 -1
  120. package/dist/azure/financialPolicy.js.map +1 -1
  121. package/dist/azure/financialPolicyValidation.d.ts.map +1 -1
  122. package/dist/azure/financialPolicyValidation.js +5 -0
  123. package/dist/azure/financialPolicyValidation.js.map +1 -1
  124. package/dist/azure/financialProjection.d.ts +11 -1
  125. package/dist/azure/financialProjection.d.ts.map +1 -1
  126. package/dist/azure/financialProjection.js.map +1 -1
  127. package/dist/azure/financialProjectionValidation.d.ts.map +1 -1
  128. package/dist/azure/financialProjectionValidation.js +43 -3
  129. package/dist/azure/financialProjectionValidation.js.map +1 -1
  130. package/dist/azure/financialSavingsAuthority.d.ts +25 -4
  131. package/dist/azure/financialSavingsAuthority.d.ts.map +1 -1
  132. package/dist/azure/financialSavingsAuthorityValidation.d.ts +6 -2
  133. package/dist/azure/financialSavingsAuthorityValidation.d.ts.map +1 -1
  134. package/dist/azure/financialSavingsAuthorityValidation.js +126 -2
  135. package/dist/azure/financialSavingsAuthorityValidation.js.map +1 -1
  136. package/dist/azure/financialSavingsCoordinateValidation.d.ts.map +1 -1
  137. package/dist/azure/financialSavingsCoordinateValidation.js +62 -19
  138. package/dist/azure/financialSavingsCoordinateValidation.js.map +1 -1
  139. package/dist/azure/financialSavingsSurfaceProjection.d.ts +58 -3
  140. package/dist/azure/financialSavingsSurfaceProjection.d.ts.map +1 -1
  141. package/dist/azure/financialSavingsSurfaceProjection.js +3 -1
  142. package/dist/azure/financialSavingsSurfaceProjection.js.map +1 -1
  143. package/dist/azure/financialSavingsSurfaceProjectionKernel.d.ts +14 -0
  144. package/dist/azure/financialSavingsSurfaceProjectionKernel.d.ts.map +1 -0
  145. package/dist/azure/financialSavingsSurfaceProjectionKernel.js +326 -0
  146. package/dist/azure/financialSavingsSurfaceProjectionKernel.js.map +1 -0
  147. package/dist/azure/financialSavingsSurfaceProjectionValidation.d.ts.map +1 -1
  148. package/dist/azure/financialSavingsSurfaceProjectionValidation.js +126 -12
  149. package/dist/azure/financialSavingsSurfaceProjectionValidation.js.map +1 -1
  150. package/dist/azure/financialScope.d.ts +6 -0
  151. package/dist/azure/financialScope.d.ts.map +1 -1
  152. package/dist/azure/financialScope.js +6 -0
  153. package/dist/azure/financialScope.js.map +1 -1
  154. package/dist/azure/governance.d.ts +145 -0
  155. package/dist/azure/governance.d.ts.map +1 -1
  156. package/dist/azure/governance.js +2 -1
  157. package/dist/azure/governance.js.map +1 -1
  158. package/dist/azure/metrics.d.ts +2 -2
  159. package/dist/azure/metrics.d.ts.map +1 -1
  160. package/dist/azure/payloads.d.ts +19 -0
  161. package/dist/azure/payloads.d.ts.map +1 -1
  162. package/dist/azure/prices.d.ts +16 -3
  163. package/dist/azure/prices.d.ts.map +1 -1
  164. package/dist/azure/recommendationState.d.ts +1 -1
  165. package/dist/azure/recommendationState.d.ts.map +1 -1
  166. package/dist/azure/recommendations.d.ts +15 -5
  167. package/dist/azure/recommendations.d.ts.map +1 -1
  168. package/dist/azure/reports.d.ts +3 -2
  169. package/dist/azure/reports.d.ts.map +1 -1
  170. package/dist/azure/reports.js.map +1 -1
  171. package/dist/azure/resources.d.ts +12 -0
  172. package/dist/azure/resources.d.ts.map +1 -1
  173. package/dist/azure/serviceRetirement.d.ts +33 -2
  174. package/dist/azure/serviceRetirement.d.ts.map +1 -1
  175. package/dist/azure/subscriptions.d.ts +13 -2
  176. package/dist/azure/subscriptions.d.ts.map +1 -1
  177. package/dist/azure/views.d.ts +26 -3
  178. package/dist/azure/views.d.ts.map +1 -1
  179. package/dist/azure/views.js.map +1 -1
  180. package/dist/azure/vmPatchManagement.d.ts +109 -0
  181. package/dist/azure/vmPatchManagement.d.ts.map +1 -0
  182. package/dist/azure/vmPatchManagement.js +7 -0
  183. package/dist/azure/vmPatchManagement.js.map +1 -0
  184. package/dist/common/provider.d.ts +53 -0
  185. package/dist/common/provider.d.ts.map +1 -1
  186. package/dist/common/provider.js.map +1 -1
  187. package/dist/environment/canonicalization.d.ts +12 -0
  188. package/dist/environment/canonicalization.d.ts.map +1 -0
  189. package/dist/environment/canonicalization.js +40 -0
  190. package/dist/environment/canonicalization.js.map +1 -0
  191. package/dist/environment/contracts.d.ts +196 -0
  192. package/dist/environment/contracts.d.ts.map +1 -0
  193. package/dist/environment/contracts.js +21 -0
  194. package/dist/environment/contracts.js.map +1 -0
  195. package/dist/environment/index.d.ts +5 -0
  196. package/dist/environment/index.d.ts.map +1 -0
  197. package/dist/environment/index.js +21 -0
  198. package/dist/environment/index.js.map +1 -0
  199. package/dist/environment/internal.d.ts +21 -0
  200. package/dist/environment/internal.d.ts.map +1 -0
  201. package/dist/environment/internal.js +113 -0
  202. package/dist/environment/internal.js.map +1 -0
  203. package/dist/environment/references.d.ts +18 -0
  204. package/dist/environment/references.d.ts.map +1 -0
  205. package/dist/environment/references.js +206 -0
  206. package/dist/environment/references.js.map +1 -0
  207. package/dist/environment/validation.d.ts +28 -0
  208. package/dist/environment/validation.d.ts.map +1 -0
  209. package/dist/environment/validation.js +320 -0
  210. package/dist/environment/validation.js.map +1 -0
  211. package/dist/esm/accounts/accounts.js +1 -0
  212. package/dist/esm/accounts/azureManualOnboarding.js +8 -0
  213. package/dist/esm/accounts/azureSpSetup.js +14 -1
  214. package/dist/esm/accounts/azureSpSetupDurable.js +27 -1
  215. package/dist/esm/accounts/index.js +1 -0
  216. package/dist/esm/ai/index.js +10 -1
  217. package/dist/esm/aws/commitments-planning.js +5 -0
  218. package/dist/esm/aws/estates.js +7 -1
  219. package/dist/esm/aws/index.js +5 -0
  220. package/dist/esm/aws/organizationCommitments.js +48 -0
  221. package/dist/esm/aws/organizationCommitmentsApiValidation.js +220 -0
  222. package/dist/esm/aws/organizationCommitmentsValidation.js +224 -0
  223. package/dist/esm/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js +402 -0
  224. package/dist/esm/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js +2 -0
  225. package/dist/esm/aws/requests.js +1 -1
  226. package/dist/esm/azure/financialAnalyticsDelivery.js +2 -0
  227. package/dist/esm/azure/financialAnalyticsDisplayKernel.js +139 -0
  228. package/dist/esm/azure/financialAnalyticsValidation.js +61 -7
  229. package/dist/esm/azure/financialAuthorityProjectionKernel.js +159 -0
  230. package/dist/esm/azure/financialAuthorityViewValidation.js +204 -1
  231. package/dist/esm/azure/financialChargeComposition.js +36 -0
  232. package/dist/esm/azure/financialChargeCompositionKernel.js +48 -0
  233. package/dist/esm/azure/financialChargeCompositionValidation.js +155 -0
  234. package/dist/esm/azure/financialDataflowKernel.js +60 -7
  235. package/dist/esm/azure/financialDataflowValidation.js +146 -28
  236. package/dist/esm/azure/financialPolicyValidation.js +5 -0
  237. package/dist/esm/azure/financialProjectionValidation.js +43 -3
  238. package/dist/esm/azure/financialSavingsAuthorityValidation.js +124 -2
  239. package/dist/esm/azure/financialSavingsCoordinateValidation.js +62 -19
  240. package/dist/esm/azure/financialSavingsSurfaceProjection.js +2 -0
  241. package/dist/esm/azure/financialSavingsSurfaceProjectionKernel.js +318 -0
  242. package/dist/esm/azure/financialSavingsSurfaceProjectionValidation.js +126 -12
  243. package/dist/esm/azure/financialScope.js +6 -0
  244. package/dist/esm/azure/governance.js +1 -0
  245. package/dist/esm/azure/vmPatchManagement.js +3 -0
  246. package/dist/esm/entries/commitments-planning.js +2 -2
  247. package/dist/esm/entries/environment.js +3 -0
  248. package/dist/esm/environment/canonicalization.js +34 -0
  249. package/dist/esm/environment/contracts.js +17 -0
  250. package/dist/esm/environment/index.js +4 -0
  251. package/dist/esm/environment/internal.js +97 -0
  252. package/dist/esm/environment/references.js +194 -0
  253. package/dist/esm/environment/validation.js +303 -0
  254. package/dist/esm/index.js +2 -0
  255. package/dist/index.d.ts +2 -0
  256. package/dist/index.d.ts.map +1 -1
  257. package/dist/index.js +2 -0
  258. package/dist/index.js.map +1 -1
  259. package/dist/portfolio/portfolioOperations.d.ts +1 -1
  260. package/dist/portfolio/portfolioOperations.d.ts.map +1 -1
  261. package/package.json +15 -3
@@ -0,0 +1,318 @@
1
+ import { AZURE_BILLED_ALL_CHARGES_POLICY_V1 } from './financialChargeComposition.js';
2
+ import { FINANCIAL_SAVINGS_SURFACE_PROJECTION_CONTRACT_VERSION_V1, FINANCIAL_SAVINGS_SURFACE_PROJECTION_SCHEMA_VERSION_V1, } from './financialSavingsSurfaceProjection.js';
3
+ import { createFinancialSavingsSurfaceProjectionIdV1, isFinancialSavingsSurfaceProjectionV1, } from './financialSavingsSurfaceProjectionValidation.js';
4
+ export class FinancialSavingsSurfaceProjectionError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'FinancialSavingsSurfaceProjectionError';
8
+ }
9
+ }
10
+ const sameGeneration = (left, right) => Boolean(left && right && left.runId === right.runId && left.generatedAt === right.generatedAt);
11
+ const exactMinorUnitSum = (values) => {
12
+ const sum = values.reduce((total, value) => total + BigInt(value), 0n);
13
+ if (sum > BigInt(Number.MAX_SAFE_INTEGER) || sum < BigInt(Number.MIN_SAFE_INTEGER)) {
14
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings surface contribution exceeds the safe integer boundary');
15
+ }
16
+ return Number(sum);
17
+ };
18
+ const projectLifecycleBindings = (allocations) => {
19
+ const byLifecycleIdentity = new Map();
20
+ for (const allocation of allocations) {
21
+ const key = `${allocation.ownerScopeId}\u0000${allocation.recommendationId}`;
22
+ const existing = byLifecycleIdentity.get(key);
23
+ if (existing) {
24
+ existing.allocationIds.push(allocation.allocationId);
25
+ continue;
26
+ }
27
+ byLifecycleIdentity.set(key, {
28
+ resourceId: allocation.ownerScopeId,
29
+ recommendationId: allocation.recommendationId,
30
+ allocationIds: [allocation.allocationId],
31
+ });
32
+ }
33
+ return [...byLifecycleIdentity.values()]
34
+ .map(binding => ({ ...binding, allocationIds: [...binding.allocationIds].sort() }))
35
+ .sort((left, right) => left.resourceId.localeCompare(right.resourceId) || left.recommendationId.localeCompare(right.recommendationId));
36
+ };
37
+ export const projectFinancialSavingsSurfaceQueryV1 = (source, recommendationIds, filterFingerprint) => {
38
+ if (!isFinancialSavingsSurfaceProjectionV1(source) || source.surface !== 'recommendations') {
39
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings source projection is invalid for recommendation filtering');
40
+ }
41
+ const uniqueRecommendationIds = [...new Set(recommendationIds)];
42
+ if (uniqueRecommendationIds.length !== recommendationIds.length ||
43
+ uniqueRecommendationIds.some(id => !id.trim()) ||
44
+ !filterFingerprint.trim()) {
45
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings query identity is invalid');
46
+ }
47
+ const scope = {
48
+ kind: 'recommendation-query',
49
+ filterFingerprint,
50
+ recommendationIds: uniqueRecommendationIds,
51
+ };
52
+ const includedRecommendationIds = new Set(uniqueRecommendationIds);
53
+ const lifecycleBindings = source.lifecycleBindings
54
+ ?.filter(binding => includedRecommendationIds.has(binding.recommendationId))
55
+ .map(binding => ({ ...binding, allocationIds: [...binding.allocationIds] }));
56
+ const coordinates = source.coordinates.map((coordinate) => {
57
+ if (coordinate.status === 'unavailable')
58
+ return coordinate;
59
+ const recommendationContributions = coordinate.recommendationContributions.filter(contribution => includedRecommendationIds.has(contribution.recommendationId));
60
+ const filteredAmounts = {
61
+ recommendationContributions,
62
+ aggregate: {
63
+ allocationIds: recommendationContributions.flatMap(contribution => contribution.allocationIds),
64
+ savingsMinorUnits: exactMinorUnitSum(recommendationContributions.map(contribution => contribution.savingsMinorUnits)),
65
+ },
66
+ };
67
+ if (coordinate.status === 'available')
68
+ return { ...coordinate, ...filteredAmounts };
69
+ const { unavailableRecommendationIds: _sourceUnavailableRecommendationIds, ...common } = coordinate;
70
+ void _sourceUnavailableRecommendationIds;
71
+ const unavailableRecommendationIds = coordinate.unavailableRecommendationIds.filter(recommendationId => includedRecommendationIds.has(recommendationId));
72
+ return unavailableRecommendationIds.length > 0
73
+ ? {
74
+ ...common,
75
+ ...filteredAmounts,
76
+ status: 'partial',
77
+ unavailableRecommendationIds: unavailableRecommendationIds,
78
+ }
79
+ : { ...common, ...filteredAmounts, status: 'available' };
80
+ });
81
+ const identity = {
82
+ schemaVersion: source.schemaVersion,
83
+ contractVersion: source.contractVersion,
84
+ surface: source.surface,
85
+ scope,
86
+ provider: source.provider,
87
+ providerAccountRefs: source.providerAccountRefs,
88
+ artifactGeneration: source.artifactGeneration,
89
+ financialAuthorityId: source.financialAuthorityId,
90
+ savingsAuthorityId: source.savingsAuthorityId,
91
+ ...(lifecycleBindings?.length ? { lifecycleBindings: lifecycleBindings } : {}),
92
+ coordinates,
93
+ };
94
+ const projection = {
95
+ ...identity,
96
+ projectionId: createFinancialSavingsSurfaceProjectionIdV1(identity),
97
+ };
98
+ if (!isFinancialSavingsSurfaceProjectionV1(projection)) {
99
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings query projection failed validation');
100
+ }
101
+ return projection;
102
+ };
103
+ const projectRecommendationContributions = (allocations) => {
104
+ const byRecommendation = new Map();
105
+ for (const allocation of allocations) {
106
+ const existing = byRecommendation.get(allocation.recommendationId) ?? {
107
+ allocationIds: [],
108
+ savingsMinorUnits: [],
109
+ allocations: [],
110
+ };
111
+ existing.allocationIds.push(allocation.allocationId);
112
+ existing.savingsMinorUnits.push(allocation.savingsMinorUnits);
113
+ existing.allocations.push({ allocationId: allocation.allocationId, savingsMinorUnits: allocation.savingsMinorUnits });
114
+ byRecommendation.set(allocation.recommendationId, existing);
115
+ }
116
+ return Array.from(byRecommendation.entries())
117
+ .sort(([left], [right]) => left.localeCompare(right))
118
+ .map(([recommendationId, contribution]) => ({
119
+ recommendationId,
120
+ allocationIds: [...contribution.allocationIds].sort(),
121
+ savingsMinorUnits: exactMinorUnitSum(contribution.savingsMinorUnits),
122
+ allocations: contribution.allocations
123
+ .sort((left, right) => left.allocationId.localeCompare(right.allocationId)),
124
+ }));
125
+ };
126
+ export const projectFinancialSavingsSurfaceResourceQueryV1 = (source, allocationIds, recommendationIds, filterFingerprint) => {
127
+ if (!isFinancialSavingsSurfaceProjectionV1(source) || source.surface !== 'resources') {
128
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings source projection is invalid for resource filtering');
129
+ }
130
+ const uniqueAllocationIds = [...new Set(allocationIds)];
131
+ const uniqueRecommendationIds = [...new Set(recommendationIds)];
132
+ if (uniqueAllocationIds.length !== allocationIds.length ||
133
+ uniqueRecommendationIds.length !== recommendationIds.length ||
134
+ uniqueAllocationIds.some(id => !id.trim()) ||
135
+ uniqueRecommendationIds.some(id => !id.trim()) ||
136
+ !filterFingerprint.trim()) {
137
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings resource query identity is invalid');
138
+ }
139
+ const includedAllocationIds = new Set(uniqueAllocationIds);
140
+ const includedRecommendationIds = new Set(uniqueRecommendationIds);
141
+ const lifecycleBindings = source.lifecycleBindings
142
+ ?.map(binding => {
143
+ const filteredAllocationIds = binding.allocationIds.filter(allocationId => includedAllocationIds.has(allocationId));
144
+ return filteredAllocationIds.length
145
+ ? {
146
+ ...binding,
147
+ allocationIds: filteredAllocationIds,
148
+ }
149
+ : undefined;
150
+ })
151
+ .filter((binding) => binding !== undefined);
152
+ const scope = {
153
+ kind: 'resource-query',
154
+ filterFingerprint,
155
+ allocationIds: uniqueAllocationIds,
156
+ recommendationIds: uniqueRecommendationIds,
157
+ };
158
+ const coordinates = source.coordinates.map((coordinate) => {
159
+ if (coordinate.status === 'unavailable')
160
+ return coordinate;
161
+ const recommendationContributions = coordinate.recommendationContributions
162
+ .map(contribution => {
163
+ if (contribution.allocations === undefined) {
164
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings resource query requires exact allocation contribution evidence');
165
+ }
166
+ const allocations = contribution.allocations.filter(allocation => includedAllocationIds.has(allocation.allocationId));
167
+ if (allocations.length === 0)
168
+ return undefined;
169
+ return {
170
+ recommendationId: contribution.recommendationId,
171
+ allocationIds: allocations.map(allocation => allocation.allocationId),
172
+ allocations: allocations,
173
+ savingsMinorUnits: exactMinorUnitSum(allocations.map(allocation => allocation.savingsMinorUnits)),
174
+ };
175
+ })
176
+ .filter((contribution) => contribution !== undefined);
177
+ const filteredAmounts = {
178
+ recommendationContributions,
179
+ aggregate: {
180
+ allocationIds: recommendationContributions.flatMap(contribution => contribution.allocationIds),
181
+ savingsMinorUnits: exactMinorUnitSum(recommendationContributions.map(contribution => contribution.savingsMinorUnits)),
182
+ },
183
+ };
184
+ if (coordinate.status === 'available')
185
+ return { ...coordinate, ...filteredAmounts };
186
+ const { unavailableRecommendationIds: _sourceUnavailableRecommendationIds, ...common } = coordinate;
187
+ void _sourceUnavailableRecommendationIds;
188
+ const unavailableRecommendationIds = coordinate.unavailableRecommendationIds.filter(id => includedRecommendationIds.has(id));
189
+ return unavailableRecommendationIds.length > 0
190
+ ? {
191
+ ...common,
192
+ ...filteredAmounts,
193
+ status: 'partial',
194
+ unavailableRecommendationIds: unavailableRecommendationIds,
195
+ }
196
+ : { ...common, ...filteredAmounts, status: 'available' };
197
+ });
198
+ const identity = {
199
+ schemaVersion: source.schemaVersion,
200
+ contractVersion: source.contractVersion,
201
+ surface: source.surface,
202
+ scope,
203
+ provider: source.provider,
204
+ providerAccountRefs: source.providerAccountRefs,
205
+ artifactGeneration: source.artifactGeneration,
206
+ financialAuthorityId: source.financialAuthorityId,
207
+ savingsAuthorityId: source.savingsAuthorityId,
208
+ ...(lifecycleBindings?.length ? { lifecycleBindings: lifecycleBindings } : {}),
209
+ coordinates,
210
+ };
211
+ const projection = { ...identity, projectionId: createFinancialSavingsSurfaceProjectionIdV1(identity) };
212
+ if (!isFinancialSavingsSurfaceProjectionV1(projection)) {
213
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings resource query projection failed validation');
214
+ }
215
+ return projection;
216
+ };
217
+ /**
218
+ * Projects a validated Resources authority into a compact, immutable surface.
219
+ * It partitions canonical allocation amounts for display and never recalculates
220
+ * scenario economics from legacy recommendation or resource fields.
221
+ */
222
+ export const buildFinancialSavingsSurfaceProjectionV1 = (resourcesView, surface) => {
223
+ const authority = resourcesView.financialAuthority;
224
+ const savingsAuthority = resourcesView.financialSavingsAuthority;
225
+ if (!authority || !savingsAuthority)
226
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings authority is unavailable');
227
+ if (authority.authorityId !== savingsAuthority.financialAuthorityId ||
228
+ !sameGeneration(resourcesView.artifactGeneration, authority.artifactGeneration) ||
229
+ !sameGeneration(authority.artifactGeneration, savingsAuthority.artifactGeneration)) {
230
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings authority generation or identity binding is invalid');
231
+ }
232
+ const financialCoordinateById = new Map(authority.coordinates.map(coordinate => [coordinate.coordinateId, coordinate]));
233
+ const savingsCoordinateById = new Map(savingsAuthority.coordinates.map(coordinate => [coordinate.coordinateId, coordinate]));
234
+ if (financialCoordinateById.size !== authority.coordinates.length ||
235
+ savingsCoordinateById.size !== savingsAuthority.coordinates.length ||
236
+ financialCoordinateById.size !== savingsCoordinateById.size ||
237
+ Array.from(financialCoordinateById.keys()).some(coordinateId => !savingsCoordinateById.has(coordinateId))) {
238
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings coordinates are not one-to-one with financial authority coordinates');
239
+ }
240
+ const coordinates = authority.coordinates.map((financialCoordinate) => {
241
+ const savingsCoordinate = savingsCoordinateById.get(financialCoordinate.coordinateId);
242
+ if (!savingsCoordinate) {
243
+ throw new FinancialSavingsSurfaceProjectionError(`Financial savings coordinate ${financialCoordinate.coordinateId} is missing`);
244
+ }
245
+ const common = {
246
+ coordinateId: financialCoordinate.coordinateId,
247
+ periodRole: financialCoordinate.periodRole,
248
+ period: financialCoordinate.period,
249
+ costBasis: financialCoordinate.costBasis,
250
+ estimateLens: financialCoordinate.estimateLens,
251
+ chargeInclusionPolicyRef: AZURE_BILLED_ALL_CHARGES_POLICY_V1.policyRef,
252
+ ...(financialCoordinate.requestedCurrencyCode === undefined
253
+ ? {}
254
+ : { requestedCurrencyCode: financialCoordinate.requestedCurrencyCode }),
255
+ ...(savingsCoordinate.currentAggregateBaselineId === undefined
256
+ ? {}
257
+ : { currentAggregateBaselineId: savingsCoordinate.currentAggregateBaselineId }),
258
+ };
259
+ if (savingsCoordinate.status === 'unavailable') {
260
+ return { ...common, status: 'unavailable', unavailableReason: savingsCoordinate.unavailableReason };
261
+ }
262
+ const currentAggregate = financialCoordinate.aggregateBaseline;
263
+ if (currentAggregate.status !== 'available' ||
264
+ currentAggregate.baselineId !== savingsCoordinate.currentAggregateBaselineId ||
265
+ currentAggregate.total.currencyCode !== savingsCoordinate.accountingCurrencyCode) {
266
+ throw new FinancialSavingsSurfaceProjectionError(`Financial savings coordinate ${financialCoordinate.coordinateId} has no matching available current aggregate baseline`);
267
+ }
268
+ const unavailableRecommendationIds = savingsCoordinate.status === 'partial'
269
+ ? Array.from(new Set(savingsCoordinate.activations
270
+ .filter(activation => activation.result === 'unavailable')
271
+ .map(activation => activation.recommendationId))).sort()
272
+ : [];
273
+ const composed = {
274
+ ...common,
275
+ currentAggregateBaselineId: savingsCoordinate.currentAggregateBaselineId,
276
+ currentAggregate: { ...currentAggregate.total },
277
+ accountingCurrencyCode: savingsCoordinate.accountingCurrencyCode,
278
+ minorUnitScale: savingsCoordinate.minorUnitScale,
279
+ roundingMode: savingsCoordinate.roundingMode,
280
+ recommendationContributions: projectRecommendationContributions(savingsCoordinate.allocations),
281
+ aggregate: {
282
+ allocationIds: [...savingsCoordinate.aggregate.allocationIds],
283
+ savingsMinorUnits: savingsCoordinate.aggregate.savingsMinorUnits,
284
+ },
285
+ };
286
+ return savingsCoordinate.status === 'partial'
287
+ ? {
288
+ ...composed,
289
+ status: 'partial',
290
+ unavailableRecommendationIds: unavailableRecommendationIds,
291
+ }
292
+ : { ...composed, status: 'available' };
293
+ });
294
+ const lifecycleBindings = projectLifecycleBindings(savingsAuthority.coordinates.flatMap(coordinate => (coordinate.status === 'unavailable' ? [] : coordinate.allocations)));
295
+ const identity = {
296
+ schemaVersion: FINANCIAL_SAVINGS_SURFACE_PROJECTION_SCHEMA_VERSION_V1,
297
+ contractVersion: FINANCIAL_SAVINGS_SURFACE_PROJECTION_CONTRACT_VERSION_V1,
298
+ surface,
299
+ scope: { kind: 'subscription-full' },
300
+ provider: 'azure',
301
+ providerAccountRefs: [...authority.providerAccountRefs],
302
+ artifactGeneration: { ...authority.artifactGeneration },
303
+ financialAuthorityId: authority.authorityId,
304
+ savingsAuthorityId: savingsAuthority.savingsAuthorityId,
305
+ ...(lifecycleBindings.length
306
+ ? { lifecycleBindings: lifecycleBindings }
307
+ : {}),
308
+ coordinates,
309
+ };
310
+ const projection = {
311
+ ...identity,
312
+ projectionId: createFinancialSavingsSurfaceProjectionIdV1(identity),
313
+ };
314
+ if (!isFinancialSavingsSurfaceProjectionV1(projection)) {
315
+ throw new FinancialSavingsSurfaceProjectionError('Financial savings surface projection failed validation');
316
+ }
317
+ return projection;
318
+ };
@@ -2,6 +2,7 @@ import { sha256Utf8 } from '../common/sha256.js';
2
2
  import { canonicalizeFinancialSavingsJsonValue, hasExactFinancialSavingsFields, haveSameFinancialSavingsSet, isFinancialSavingsHash, isFinancialSavingsIdentity, isFinancialSavingsIsoInstant, isFinancialSavingsMinorUnits, isFinancialSavingsRecord, sumFinancialSavingsMinorUnits, } from './financialSavingsAuthorityValidationPrimitives.js';
3
3
  import { FINANCIAL_SAVINGS_SURFACE_PROJECTION_CONTRACT_VERSION_V1, FINANCIAL_SAVINGS_SURFACE_PROJECTION_SCHEMA_VERSION_V1, } from './financialSavingsSurfaceProjection.js';
4
4
  import { isFinancialBaselinePeriodV2 } from './financialScopeBaselineValidation.js';
5
+ import { isFinancialChargeInclusionPolicyRefV1 } from './financialChargeCompositionValidation.js';
5
6
  import { isCanonicalExactMoney } from './financialValidationPrimitives.js';
6
7
  const COST_BASES = new Set(['billed', 'amortized']);
7
8
  const ESTIMATE_LENSES = new Set(['actual-only', 'actual-plus-estimated', 'estimates-only']);
@@ -18,7 +19,22 @@ const MAX_ALLOCATIONS = 20000;
18
19
  export const canonicalizeFinancialSavingsSurfaceProjectionIdentityV1 = (value) => JSON.stringify(canonicalizeFinancialSavingsJsonValue({
19
20
  ...value,
20
21
  providerAccountRefs: [...value.providerAccountRefs].sort(),
21
- scope: value.scope.kind === 'subscription-full' ? value.scope : { ...value.scope, recommendationIds: [...value.scope.recommendationIds].sort() },
22
+ ...(value.lifecycleBindings === undefined
23
+ ? {}
24
+ : {
25
+ lifecycleBindings: [...value.lifecycleBindings]
26
+ .sort((left, right) => left.resourceId.localeCompare(right.resourceId) || left.recommendationId.localeCompare(right.recommendationId))
27
+ .map(binding => ({ ...binding, allocationIds: [...binding.allocationIds].sort() })),
28
+ }),
29
+ scope: value.scope.kind === 'subscription-full'
30
+ ? value.scope
31
+ : value.scope.kind === 'recommendation-query'
32
+ ? { ...value.scope, recommendationIds: [...value.scope.recommendationIds].sort() }
33
+ : {
34
+ ...value.scope,
35
+ allocationIds: [...value.scope.allocationIds].sort(),
36
+ recommendationIds: [...value.scope.recommendationIds].sort(),
37
+ },
22
38
  coordinates: [...value.coordinates]
23
39
  .sort((left, right) => left.coordinateId.localeCompare(right.coordinateId))
24
40
  .map(coordinate => coordinate.status === 'unavailable'
@@ -27,7 +43,15 @@ export const canonicalizeFinancialSavingsSurfaceProjectionIdentityV1 = (value) =
27
43
  ...coordinate,
28
44
  recommendationContributions: [...coordinate.recommendationContributions]
29
45
  .sort((left, right) => left.recommendationId.localeCompare(right.recommendationId))
30
- .map(contribution => ({ ...contribution, allocationIds: [...contribution.allocationIds].sort() })),
46
+ .map(contribution => ({
47
+ ...contribution,
48
+ allocationIds: [...contribution.allocationIds].sort(),
49
+ ...(contribution.allocations === undefined
50
+ ? {}
51
+ : {
52
+ allocations: [...contribution.allocations].sort((left, right) => left.allocationId.localeCompare(right.allocationId)),
53
+ }),
54
+ })),
31
55
  aggregate: { ...coordinate.aggregate, allocationIds: [...coordinate.aggregate.allocationIds].sort() },
32
56
  }),
33
57
  }));
@@ -38,12 +62,24 @@ const isScope = (value) => {
38
62
  return false;
39
63
  if (value.kind === 'subscription-full')
40
64
  return hasExactFinancialSavingsFields(value, ['kind']);
41
- return (value.kind === 'recommendation-query' &&
65
+ if (value.kind === 'recommendation-query' &&
42
66
  hasExactFinancialSavingsFields(value, ['kind', 'filterFingerprint', 'recommendationIds']) &&
43
67
  isFinancialSavingsHash(value.filterFingerprint) &&
44
68
  Array.isArray(value.recommendationIds) &&
45
69
  value.recommendationIds.length <= MAX_ALLOCATIONS &&
46
70
  value.recommendationIds.every(isFinancialSavingsIdentity) &&
71
+ new Set(value.recommendationIds).size === value.recommendationIds.length)
72
+ return true;
73
+ return (value.kind === 'resource-query' &&
74
+ hasExactFinancialSavingsFields(value, ['kind', 'filterFingerprint', 'allocationIds', 'recommendationIds']) &&
75
+ isFinancialSavingsHash(value.filterFingerprint) &&
76
+ Array.isArray(value.allocationIds) &&
77
+ value.allocationIds.length <= MAX_ALLOCATIONS &&
78
+ value.allocationIds.every(isFinancialSavingsHash) &&
79
+ new Set(value.allocationIds).size === value.allocationIds.length &&
80
+ Array.isArray(value.recommendationIds) &&
81
+ value.recommendationIds.length <= MAX_ALLOCATIONS &&
82
+ value.recommendationIds.every(isFinancialSavingsIdentity) &&
47
83
  new Set(value.recommendationIds).size === value.recommendationIds.length);
48
84
  };
49
85
  const isCoordinateCommon = (value) => isFinancialSavingsHash(value.coordinateId) &&
@@ -53,9 +89,20 @@ const isCoordinateCommon = (value) => isFinancialSavingsHash(value.coordinateId)
53
89
  COST_BASES.has(value.costBasis) &&
54
90
  typeof value.estimateLens === 'string' &&
55
91
  ESTIMATE_LENSES.has(value.estimateLens) &&
92
+ isFinancialChargeInclusionPolicyRefV1(value.chargeInclusionPolicyRef) &&
56
93
  (value.requestedCurrencyCode === undefined || isCurrency(value.requestedCurrencyCode)) &&
57
94
  (value.currentAggregateBaselineId === undefined || isFinancialSavingsHash(value.currentAggregateBaselineId));
58
- const isAvailableCoordinate = (value, queryRecommendationIds) => {
95
+ const isLifecycleBinding = (value) => isFinancialSavingsRecord(value) &&
96
+ hasExactFinancialSavingsFields(value, ['resourceId', 'recommendationId', 'allocationIds']) &&
97
+ isFinancialSavingsIdentity(value.resourceId) &&
98
+ isFinancialSavingsIdentity(value.recommendationId) &&
99
+ Array.isArray(value.allocationIds) &&
100
+ value.allocationIds.length > 0 &&
101
+ value.allocationIds.length <= MAX_ALLOCATIONS &&
102
+ value.allocationIds.every(isFinancialSavingsHash) &&
103
+ new Set(value.allocationIds).size === value.allocationIds.length;
104
+ const isComposedCoordinate = (value, queryRecommendationIds, queryAllocationIds) => {
105
+ const partial = value.status === 'partial';
59
106
  if (!hasExactFinancialSavingsFields(value, [
60
107
  'status',
61
108
  'coordinateId',
@@ -63,6 +110,7 @@ const isAvailableCoordinate = (value, queryRecommendationIds) => {
63
110
  'period',
64
111
  'costBasis',
65
112
  'estimateLens',
113
+ 'chargeInclusionPolicyRef',
66
114
  'currentAggregateBaselineId',
67
115
  'currentAggregate',
68
116
  'accountingCurrencyCode',
@@ -70,6 +118,7 @@ const isAvailableCoordinate = (value, queryRecommendationIds) => {
70
118
  'roundingMode',
71
119
  'recommendationContributions',
72
120
  'aggregate',
121
+ ...(partial ? ['unavailableRecommendationIds'] : []),
73
122
  ], ['requestedCurrencyCode']) ||
74
123
  !isCoordinateCommon(value) ||
75
124
  !isFinancialSavingsHash(value.currentAggregateBaselineId) ||
@@ -90,12 +139,20 @@ const isAvailableCoordinate = (value, queryRecommendationIds) => {
90
139
  new Set(value.aggregate.allocationIds).size !== value.aggregate.allocationIds.length ||
91
140
  !isFinancialSavingsMinorUnits(value.aggregate.savingsMinorUnits))
92
141
  return false;
142
+ if (partial &&
143
+ (!Array.isArray(value.unavailableRecommendationIds) ||
144
+ value.unavailableRecommendationIds.length === 0 ||
145
+ value.unavailableRecommendationIds.length > MAX_ALLOCATIONS ||
146
+ !value.unavailableRecommendationIds.every(isFinancialSavingsIdentity) ||
147
+ new Set(value.unavailableRecommendationIds).size !== value.unavailableRecommendationIds.length ||
148
+ (queryRecommendationIds !== undefined && value.unavailableRecommendationIds.some(id => !queryRecommendationIds.has(id)))))
149
+ return false;
93
150
  const recommendationIds = new Set();
94
151
  const allocationIds = [];
95
152
  const contributionAmounts = [];
96
153
  for (const contribution of value.recommendationContributions) {
97
154
  if (!isFinancialSavingsRecord(contribution) ||
98
- !hasExactFinancialSavingsFields(contribution, ['recommendationId', 'allocationIds', 'savingsMinorUnits']) ||
155
+ !hasExactFinancialSavingsFields(contribution, ['recommendationId', 'allocationIds', 'savingsMinorUnits'], ['allocations']) ||
99
156
  !isFinancialSavingsIdentity(contribution.recommendationId) ||
100
157
  recommendationIds.has(contribution.recommendationId) ||
101
158
  (queryRecommendationIds !== undefined && !queryRecommendationIds.has(contribution.recommendationId)) ||
@@ -104,7 +161,33 @@ const isAvailableCoordinate = (value, queryRecommendationIds) => {
104
161
  contribution.allocationIds.length > MAX_ALLOCATIONS ||
105
162
  !contribution.allocationIds.every(isFinancialSavingsHash) ||
106
163
  new Set(contribution.allocationIds).size !== contribution.allocationIds.length ||
107
- !isFinancialSavingsMinorUnits(contribution.savingsMinorUnits))
164
+ !isFinancialSavingsMinorUnits(contribution.savingsMinorUnits) ||
165
+ (queryAllocationIds !== undefined && !Array.isArray(contribution.allocations)) ||
166
+ (contribution.allocations !== undefined &&
167
+ (!Array.isArray(contribution.allocations) || contribution.allocations.length !== contribution.allocationIds.length)))
168
+ return false;
169
+ const exactAllocations = contribution.allocations;
170
+ if (exactAllocations === undefined) {
171
+ recommendationIds.add(contribution.recommendationId);
172
+ allocationIds.push(...contribution.allocationIds);
173
+ contributionAmounts.push(contribution.savingsMinorUnits);
174
+ continue;
175
+ }
176
+ const exactAllocationIds = [];
177
+ const exactAllocationAmounts = [];
178
+ for (const allocation of exactAllocations) {
179
+ if (!isFinancialSavingsRecord(allocation) ||
180
+ !hasExactFinancialSavingsFields(allocation, ['allocationId', 'savingsMinorUnits']) ||
181
+ !isFinancialSavingsHash(allocation.allocationId) ||
182
+ (queryAllocationIds !== undefined && !queryAllocationIds.has(allocation.allocationId)) ||
183
+ !isFinancialSavingsMinorUnits(allocation.savingsMinorUnits))
184
+ return false;
185
+ exactAllocationIds.push(allocation.allocationId);
186
+ exactAllocationAmounts.push(allocation.savingsMinorUnits);
187
+ }
188
+ if (new Set(exactAllocationIds).size !== exactAllocationIds.length ||
189
+ !haveSameFinancialSavingsSet(exactAllocationIds, contribution.allocationIds) ||
190
+ sumFinancialSavingsMinorUnits(exactAllocationAmounts) !== contribution.savingsMinorUnits)
108
191
  return false;
109
192
  recommendationIds.add(contribution.recommendationId);
110
193
  allocationIds.push(...contribution.allocationIds);
@@ -114,7 +197,7 @@ const isAvailableCoordinate = (value, queryRecommendationIds) => {
114
197
  haveSameFinancialSavingsSet(allocationIds, value.aggregate.allocationIds) &&
115
198
  sumFinancialSavingsMinorUnits(contributionAmounts) === value.aggregate.savingsMinorUnits);
116
199
  };
117
- const isUnavailableCoordinate = (value) => hasExactFinancialSavingsFields(value, ['status', 'coordinateId', 'periodRole', 'period', 'costBasis', 'estimateLens', 'unavailableReason'], ['requestedCurrencyCode', 'currentAggregateBaselineId']) &&
200
+ const isUnavailableCoordinate = (value) => hasExactFinancialSavingsFields(value, ['status', 'coordinateId', 'periodRole', 'period', 'costBasis', 'estimateLens', 'chargeInclusionPolicyRef', 'unavailableReason'], ['requestedCurrencyCode', 'currentAggregateBaselineId']) &&
118
201
  isCoordinateCommon(value) &&
119
202
  typeof value.unavailableReason === 'string' &&
120
203
  UNAVAILABLE_REASONS.has(value.unavailableReason);
@@ -132,13 +215,15 @@ export const isFinancialSavingsSurfaceProjectionV1 = (value) => {
132
215
  'financialAuthorityId',
133
216
  'savingsAuthorityId',
134
217
  'coordinates',
135
- ]) ||
218
+ ], ['lifecycleBindings']) ||
136
219
  value.schemaVersion !== FINANCIAL_SAVINGS_SURFACE_PROJECTION_SCHEMA_VERSION_V1 ||
137
220
  value.contractVersion !== FINANCIAL_SAVINGS_SURFACE_PROJECTION_CONTRACT_VERSION_V1 ||
138
221
  !isFinancialSavingsHash(value.projectionId) ||
139
- (value.surface !== 'recommendations' && value.surface !== 'dashboard') ||
222
+ (value.surface !== 'recommendations' && value.surface !== 'resources' && value.surface !== 'dashboard') ||
140
223
  !isScope(value.scope) ||
141
224
  (value.surface === 'dashboard' && value.scope.kind !== 'subscription-full') ||
225
+ (value.surface === 'recommendations' && value.scope.kind === 'resource-query') ||
226
+ (value.surface === 'resources' && value.scope.kind === 'recommendation-query') ||
142
227
  value.provider !== 'azure' ||
143
228
  !Array.isArray(value.providerAccountRefs) ||
144
229
  value.providerAccountRefs.length === 0 ||
@@ -151,21 +236,50 @@ export const isFinancialSavingsSurfaceProjectionV1 = (value) => {
151
236
  !isFinancialSavingsIsoInstant(value.artifactGeneration.generatedAt) ||
152
237
  !isFinancialSavingsHash(value.financialAuthorityId) ||
153
238
  !isFinancialSavingsHash(value.savingsAuthorityId) ||
239
+ (value.lifecycleBindings !== undefined &&
240
+ (!Array.isArray(value.lifecycleBindings) ||
241
+ value.lifecycleBindings.length === 0 ||
242
+ value.lifecycleBindings.length > MAX_ALLOCATIONS ||
243
+ !value.lifecycleBindings.every(isLifecycleBinding))) ||
154
244
  !Array.isArray(value.coordinates) ||
155
245
  value.coordinates.length === 0 ||
156
246
  value.coordinates.length > MAX_COORDINATES)
157
247
  return false;
158
248
  const projection = value;
159
- const queryRecommendationIds = projection.scope.kind === 'recommendation-query' ? new Set(projection.scope.recommendationIds) : undefined;
249
+ const queryRecommendationIds = projection.scope.kind === 'recommendation-query' || projection.scope.kind === 'resource-query'
250
+ ? new Set(projection.scope.recommendationIds)
251
+ : undefined;
252
+ const queryAllocationIds = projection.scope.kind === 'resource-query' ? new Set(projection.scope.allocationIds) : undefined;
160
253
  if (!projection.coordinates.every(coordinate => {
161
254
  if (!isFinancialSavingsRecord(coordinate))
162
255
  return false;
163
- return coordinate.status === 'available'
164
- ? isAvailableCoordinate(coordinate, queryRecommendationIds)
256
+ return coordinate.status === 'available' || coordinate.status === 'partial'
257
+ ? isComposedCoordinate(coordinate, queryRecommendationIds, queryAllocationIds)
165
258
  : coordinate.status === 'unavailable' && isUnavailableCoordinate(coordinate);
166
259
  }) ||
167
260
  new Set(projection.coordinates.map(coordinate => coordinate.coordinateId)).size !== projection.coordinates.length)
168
261
  return false;
262
+ if (projection.lifecycleBindings !== undefined) {
263
+ const lifecycleKeys = projection.lifecycleBindings.map(binding => `${binding.resourceId}\u0000${binding.recommendationId}`);
264
+ const boundAllocationIds = projection.lifecycleBindings.flatMap(binding => binding.allocationIds);
265
+ const projectedAllocationOwners = new Map();
266
+ for (const coordinate of projection.coordinates) {
267
+ if (coordinate.status === 'unavailable')
268
+ continue;
269
+ for (const contribution of coordinate.recommendationContributions) {
270
+ for (const allocationId of contribution.allocationIds) {
271
+ if (projectedAllocationOwners.has(allocationId))
272
+ return false;
273
+ projectedAllocationOwners.set(allocationId, contribution.recommendationId);
274
+ }
275
+ }
276
+ }
277
+ if (new Set(lifecycleKeys).size !== lifecycleKeys.length ||
278
+ new Set(boundAllocationIds).size !== boundAllocationIds.length ||
279
+ boundAllocationIds.length !== projectedAllocationOwners.size ||
280
+ projection.lifecycleBindings.some(binding => binding.allocationIds.some(allocationId => projectedAllocationOwners.get(allocationId) !== binding.recommendationId)))
281
+ return false;
282
+ }
169
283
  const { projectionId: _projectionId, ...identity } = projection;
170
284
  return projection.projectionId === createFinancialSavingsSurfaceProjectionIdV1(identity);
171
285
  };
@@ -2,6 +2,9 @@ export * from './financialScopeEvidence.js';
2
2
  export * from './financialScopeBaseline.js';
3
3
  export * from './financialScopeBaselineIdentity.js';
4
4
  export * from './financialScopeBaselineValidation.js';
5
+ export * from './financialChargeComposition.js';
6
+ export * from './financialChargeCompositionValidation.js';
7
+ export * from './financialChargeCompositionKernel.js';
5
8
  export * from './financialEvidenceAssessmentValidation.js';
6
9
  export * from './financialProjection.js';
7
10
  export * from './financialProjectionValidation.js';
@@ -9,15 +12,18 @@ export * from './financialDisplayRollup.js';
9
12
  export * from './financialDisplayRollupValidation.js';
10
13
  export * from './financialAuthorityView.js';
11
14
  export * from './financialAuthorityViewValidation.js';
15
+ export * from './financialAuthorityProjectionKernel.js';
12
16
  export * from './financialSavingsAuthority.js';
13
17
  export * from './financialSavingsAuthorityValidation.js';
14
18
  export * from './financialSavingsSurfaceProjection.js';
15
19
  export * from './financialSavingsSurfaceProjectionValidation.js';
20
+ export * from './financialSavingsSurfaceProjectionKernel.js';
16
21
  export * from './financialDataflow.js';
17
22
  export * from './financialDataflowValidation.js';
18
23
  export * from './financialDataflowKernel.js';
19
24
  export * from './financialAnalytics.js';
20
25
  export * from './financialAnalyticsValidation.js';
26
+ export * from './financialAnalyticsDisplayKernel.js';
21
27
  export * from './financialAnalyticsDelivery.js';
22
28
  export * from './financialAnalyticsDeliveryValidation.js';
23
29
  export * from './financialPolicy.js';
@@ -6,6 +6,7 @@ export const TENANT_GOVERNANCE_GRAPH_SCHEMA_VERSION = '2026-05-02.tenant-graph-v
6
6
  export const GLOBAL_ADMIN_RAW_SCHEMA_VERSION = '2026-06-24.global-admin-raw-v1';
7
7
  export const PRIVILEGED_ROLE_SUMMARY_SCHEMA_VERSION = '2026-06-24.privileged-role-summary-v1';
8
8
  export const TENANT_GOVERNANCE_ACCESS_SCHEMA_VERSION = '2026-06-24.tenant-governance-access-v1';
9
+ export const TENANT_MFA_POSTURE_SCHEMA_VERSION = '2026-08-25.tenant-mfa-posture-v1';
9
10
  export const GOVERNANCE_REPORT_PORTAL_FILE = 'governance.json.gz';
10
11
  export const GOVERNANCE_GRAPH_PORTAL_FILE = 'governance-graph.json.gz';
11
12
  export const GOVERNANCE_ACCESS_PORTAL_FILE = 'governance-access.json.gz';
@@ -0,0 +1,3 @@
1
+ export const VM_PATCH_MANAGEMENT_SCHEMA_VERSION = 1;
2
+ export const PATCH_MANAGEMENT_PORTAL_FILE_NAME = 'patch-management.json.gz';
3
+ export const AZURE_VM_PATCH_MANAGEMENT_RESOURCE_TYPE = 'microsoft.compute/virtualmachines';
@@ -1,3 +1,3 @@
1
- export * from '../aws/commitmentsPlanningValidation.js';
2
- import * as namespace from '../aws/commitmentsPlanningValidation.js';
1
+ export * from '../aws/commitments-planning.js';
2
+ import * as namespace from '../aws/commitments-planning.js';
3
3
  export default namespace;
@@ -0,0 +1,3 @@
1
+ export * from '../environment/index.js';
2
+ import * as namespace from '../environment/index.js';
3
+ export default namespace;
@@ -0,0 +1,34 @@
1
+ import { isEnvironmentDocumentDescriptorSetV1, isEnvironmentScopeV1 } from './validation.js';
2
+ const compareUnicodeCodePoints = (left, right) => {
3
+ const leftPoints = Array.from(left, character => character.codePointAt(0));
4
+ const rightPoints = Array.from(right, character => character.codePointAt(0));
5
+ const length = Math.min(leftPoints.length, rightPoints.length);
6
+ for (let index = 0; index < length; index += 1) {
7
+ const difference = leftPoints[index] - rightPoints[index];
8
+ if (difference !== 0)
9
+ return difference;
10
+ }
11
+ return leftPoints.length - rightPoints.length;
12
+ };
13
+ /**
14
+ * Builds the canonical scope-qualified artifact subject used inside opaque
15
+ * logical references. The JSON tuple is an identifier, never a storage path.
16
+ */
17
+ export const buildEnvironmentScopeQualifiedSubjectV1 = (scope) => {
18
+ if (!isEnvironmentScopeV1(scope))
19
+ throw new TypeError('Invalid V1 environment scope.');
20
+ return JSON.stringify([scope.kind, scope.tenantId, scope.companyId, scope.subscriptionId]);
21
+ };
22
+ /**
23
+ * Produces the exact compact UTF-8 tree-digest preimage. Hashing remains the
24
+ * caller's responsibility so this helper stays browser-safe.
25
+ */
26
+ export const buildEnvironmentTreeDigestPreimageV1 = (descriptors) => {
27
+ if (!isEnvironmentDocumentDescriptorSetV1(descriptors)) {
28
+ throw new TypeError('V1 environment tree digests require each allowlisted document exactly once.');
29
+ }
30
+ const pairs = descriptors
31
+ .map(descriptor => [descriptor.name, descriptor.contentSha256])
32
+ .sort((left, right) => compareUnicodeCodePoints(left[0], right[0]));
33
+ return JSON.stringify(pairs);
34
+ };