@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.
- package/README.md +9 -0
- package/dist/accounts/accounts.d.ts +77 -4
- package/dist/accounts/accounts.d.ts.map +1 -1
- package/dist/accounts/accounts.js +2 -1
- package/dist/accounts/accounts.js.map +1 -1
- package/dist/accounts/azureManualOnboarding.d.ts +114 -0
- package/dist/accounts/azureManualOnboarding.d.ts.map +1 -0
- package/dist/accounts/azureManualOnboarding.js +12 -0
- package/dist/accounts/azureManualOnboarding.js.map +1 -0
- package/dist/accounts/azureSpSetup.d.ts +128 -31
- package/dist/accounts/azureSpSetup.d.ts.map +1 -1
- package/dist/accounts/azureSpSetup.js +15 -0
- package/dist/accounts/azureSpSetup.js.map +1 -1
- package/dist/accounts/azureSpSetupDurable.d.ts +8 -0
- package/dist/accounts/azureSpSetupDurable.d.ts.map +1 -1
- package/dist/accounts/azureSpSetupDurable.js +28 -0
- package/dist/accounts/azureSpSetupDurable.js.map +1 -1
- package/dist/accounts/index.d.ts +1 -0
- package/dist/accounts/index.d.ts.map +1 -1
- package/dist/accounts/index.js +1 -0
- package/dist/accounts/index.js.map +1 -1
- package/dist/accounts/readPermissions.d.ts +1 -1
- package/dist/accounts/readPermissions.d.ts.map +1 -1
- package/dist/accounts/readPermissions.js.map +1 -1
- package/dist/accounts/validation.d.ts +24 -0
- package/dist/accounts/validation.d.ts.map +1 -1
- package/dist/ai/index.d.ts +14 -1
- package/dist/ai/index.d.ts.map +1 -1
- package/dist/ai/index.js +12 -0
- package/dist/ai/index.js.map +1 -1
- package/dist/aws/commitments-planning.d.ts +5 -0
- package/dist/aws/commitments-planning.d.ts.map +1 -1
- package/dist/aws/commitments-planning.js +19 -0
- package/dist/aws/commitments-planning.js.map +1 -1
- package/dist/aws/estates.d.ts +1 -1
- package/dist/aws/estates.d.ts.map +1 -1
- package/dist/aws/estates.js +7 -1
- package/dist/aws/estates.js.map +1 -1
- package/dist/aws/index.d.ts +5 -0
- package/dist/aws/index.d.ts.map +1 -1
- package/dist/aws/index.js +5 -0
- package/dist/aws/index.js.map +1 -1
- package/dist/aws/onboarding.d.ts +3 -0
- package/dist/aws/onboarding.d.ts.map +1 -1
- package/dist/aws/organizationCommitments.d.ts +260 -0
- package/dist/aws/organizationCommitments.d.ts.map +1 -0
- package/dist/aws/organizationCommitments.js +53 -0
- package/dist/aws/organizationCommitments.js.map +1 -0
- package/dist/aws/organizationCommitmentsApiValidation.d.ts +21 -0
- package/dist/aws/organizationCommitmentsApiValidation.d.ts.map +1 -0
- package/dist/aws/organizationCommitmentsApiValidation.js +226 -0
- package/dist/aws/organizationCommitmentsApiValidation.js.map +1 -0
- package/dist/aws/organizationCommitmentsValidation.d.ts +12 -0
- package/dist/aws/organizationCommitmentsValidation.d.ts.map +1 -0
- package/dist/aws/organizationCommitmentsValidation.js +228 -0
- package/dist/aws/organizationCommitmentsValidation.js.map +1 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.d.ts +9 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.d.ts.map +1 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js +407 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js.map +1 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.d.ts +17 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.d.ts.map +1 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js +6 -0
- package/dist/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js.map +1 -0
- package/dist/aws/requests.d.ts +13 -2
- package/dist/aws/requests.d.ts.map +1 -1
- package/dist/aws/requests.js +1 -1
- package/dist/aws/requests.js.map +1 -1
- package/dist/azure/commitmentsPlanning.d.ts +2 -2
- package/dist/azure/commitmentsPlanning.d.ts.map +1 -1
- package/dist/azure/financialAnalytics.d.ts +68 -2
- package/dist/azure/financialAnalytics.d.ts.map +1 -1
- package/dist/azure/financialAnalyticsDelivery.d.ts +2 -0
- package/dist/azure/financialAnalyticsDelivery.d.ts.map +1 -1
- package/dist/azure/financialAnalyticsDelivery.js +4 -1
- package/dist/azure/financialAnalyticsDelivery.js.map +1 -1
- package/dist/azure/financialAnalyticsDisplayKernel.d.ts +12 -0
- package/dist/azure/financialAnalyticsDisplayKernel.d.ts.map +1 -0
- package/dist/azure/financialAnalyticsDisplayKernel.js +144 -0
- package/dist/azure/financialAnalyticsDisplayKernel.js.map +1 -0
- package/dist/azure/financialAnalyticsValidation.d.ts.map +1 -1
- package/dist/azure/financialAnalyticsValidation.js +61 -7
- package/dist/azure/financialAnalyticsValidation.js.map +1 -1
- package/dist/azure/financialAuthorityProjectionKernel.d.ts +7 -0
- package/dist/azure/financialAuthorityProjectionKernel.d.ts.map +1 -0
- package/dist/azure/financialAuthorityProjectionKernel.js +165 -0
- package/dist/azure/financialAuthorityProjectionKernel.js.map +1 -0
- package/dist/azure/financialAuthorityView.d.ts +4 -0
- package/dist/azure/financialAuthorityView.d.ts.map +1 -1
- package/dist/azure/financialAuthorityView.js.map +1 -1
- package/dist/azure/financialAuthorityViewValidation.d.ts +3 -1
- package/dist/azure/financialAuthorityViewValidation.d.ts.map +1 -1
- package/dist/azure/financialAuthorityViewValidation.js +206 -2
- package/dist/azure/financialAuthorityViewValidation.js.map +1 -1
- package/dist/azure/financialChargeComposition.d.ts +81 -0
- package/dist/azure/financialChargeComposition.d.ts.map +1 -0
- package/dist/azure/financialChargeComposition.js +41 -0
- package/dist/azure/financialChargeComposition.js.map +1 -0
- package/dist/azure/financialChargeCompositionKernel.d.ts +3 -0
- package/dist/azure/financialChargeCompositionKernel.d.ts.map +1 -0
- package/dist/azure/financialChargeCompositionKernel.js +53 -0
- package/dist/azure/financialChargeCompositionKernel.js.map +1 -0
- package/dist/azure/financialChargeCompositionValidation.d.ts +8 -0
- package/dist/azure/financialChargeCompositionValidation.d.ts.map +1 -0
- package/dist/azure/financialChargeCompositionValidation.js +164 -0
- package/dist/azure/financialChargeCompositionValidation.js.map +1 -0
- package/dist/azure/financialDataflow.d.ts +8 -1
- package/dist/azure/financialDataflow.d.ts.map +1 -1
- package/dist/azure/financialDataflow.js.map +1 -1
- package/dist/azure/financialDataflowKernel.d.ts +2 -0
- package/dist/azure/financialDataflowKernel.d.ts.map +1 -1
- package/dist/azure/financialDataflowKernel.js +60 -7
- package/dist/azure/financialDataflowKernel.js.map +1 -1
- package/dist/azure/financialDataflowValidation.d.ts +1 -1
- package/dist/azure/financialDataflowValidation.d.ts.map +1 -1
- package/dist/azure/financialDataflowValidation.js +146 -28
- package/dist/azure/financialDataflowValidation.js.map +1 -1
- package/dist/azure/financialPolicy.d.ts +3 -0
- package/dist/azure/financialPolicy.d.ts.map +1 -1
- package/dist/azure/financialPolicy.js.map +1 -1
- package/dist/azure/financialPolicyValidation.d.ts.map +1 -1
- package/dist/azure/financialPolicyValidation.js +5 -0
- package/dist/azure/financialPolicyValidation.js.map +1 -1
- package/dist/azure/financialProjection.d.ts +11 -1
- package/dist/azure/financialProjection.d.ts.map +1 -1
- package/dist/azure/financialProjection.js.map +1 -1
- package/dist/azure/financialProjectionValidation.d.ts.map +1 -1
- package/dist/azure/financialProjectionValidation.js +43 -3
- package/dist/azure/financialProjectionValidation.js.map +1 -1
- package/dist/azure/financialSavingsAuthority.d.ts +25 -4
- package/dist/azure/financialSavingsAuthority.d.ts.map +1 -1
- package/dist/azure/financialSavingsAuthorityValidation.d.ts +6 -2
- package/dist/azure/financialSavingsAuthorityValidation.d.ts.map +1 -1
- package/dist/azure/financialSavingsAuthorityValidation.js +126 -2
- package/dist/azure/financialSavingsAuthorityValidation.js.map +1 -1
- package/dist/azure/financialSavingsCoordinateValidation.d.ts.map +1 -1
- package/dist/azure/financialSavingsCoordinateValidation.js +62 -19
- package/dist/azure/financialSavingsCoordinateValidation.js.map +1 -1
- package/dist/azure/financialSavingsSurfaceProjection.d.ts +58 -3
- package/dist/azure/financialSavingsSurfaceProjection.d.ts.map +1 -1
- package/dist/azure/financialSavingsSurfaceProjection.js +3 -1
- package/dist/azure/financialSavingsSurfaceProjection.js.map +1 -1
- package/dist/azure/financialSavingsSurfaceProjectionKernel.d.ts +14 -0
- package/dist/azure/financialSavingsSurfaceProjectionKernel.d.ts.map +1 -0
- package/dist/azure/financialSavingsSurfaceProjectionKernel.js +326 -0
- package/dist/azure/financialSavingsSurfaceProjectionKernel.js.map +1 -0
- package/dist/azure/financialSavingsSurfaceProjectionValidation.d.ts.map +1 -1
- package/dist/azure/financialSavingsSurfaceProjectionValidation.js +126 -12
- package/dist/azure/financialSavingsSurfaceProjectionValidation.js.map +1 -1
- package/dist/azure/financialScope.d.ts +6 -0
- package/dist/azure/financialScope.d.ts.map +1 -1
- package/dist/azure/financialScope.js +6 -0
- package/dist/azure/financialScope.js.map +1 -1
- package/dist/azure/governance.d.ts +145 -0
- package/dist/azure/governance.d.ts.map +1 -1
- package/dist/azure/governance.js +2 -1
- package/dist/azure/governance.js.map +1 -1
- package/dist/azure/metrics.d.ts +2 -2
- package/dist/azure/metrics.d.ts.map +1 -1
- package/dist/azure/payloads.d.ts +19 -0
- package/dist/azure/payloads.d.ts.map +1 -1
- package/dist/azure/prices.d.ts +16 -3
- package/dist/azure/prices.d.ts.map +1 -1
- package/dist/azure/recommendationState.d.ts +1 -1
- package/dist/azure/recommendationState.d.ts.map +1 -1
- package/dist/azure/recommendations.d.ts +15 -5
- package/dist/azure/recommendations.d.ts.map +1 -1
- package/dist/azure/reports.d.ts +3 -2
- package/dist/azure/reports.d.ts.map +1 -1
- package/dist/azure/reports.js.map +1 -1
- package/dist/azure/resources.d.ts +12 -0
- package/dist/azure/resources.d.ts.map +1 -1
- package/dist/azure/serviceRetirement.d.ts +33 -2
- package/dist/azure/serviceRetirement.d.ts.map +1 -1
- package/dist/azure/subscriptions.d.ts +13 -2
- package/dist/azure/subscriptions.d.ts.map +1 -1
- package/dist/azure/views.d.ts +26 -3
- package/dist/azure/views.d.ts.map +1 -1
- package/dist/azure/views.js.map +1 -1
- package/dist/azure/vmPatchManagement.d.ts +109 -0
- package/dist/azure/vmPatchManagement.d.ts.map +1 -0
- package/dist/azure/vmPatchManagement.js +7 -0
- package/dist/azure/vmPatchManagement.js.map +1 -0
- package/dist/common/provider.d.ts +53 -0
- package/dist/common/provider.d.ts.map +1 -1
- package/dist/common/provider.js.map +1 -1
- package/dist/environment/canonicalization.d.ts +12 -0
- package/dist/environment/canonicalization.d.ts.map +1 -0
- package/dist/environment/canonicalization.js +40 -0
- package/dist/environment/canonicalization.js.map +1 -0
- package/dist/environment/contracts.d.ts +196 -0
- package/dist/environment/contracts.d.ts.map +1 -0
- package/dist/environment/contracts.js +21 -0
- package/dist/environment/contracts.js.map +1 -0
- package/dist/environment/index.d.ts +5 -0
- package/dist/environment/index.d.ts.map +1 -0
- package/dist/environment/index.js +21 -0
- package/dist/environment/index.js.map +1 -0
- package/dist/environment/internal.d.ts +21 -0
- package/dist/environment/internal.d.ts.map +1 -0
- package/dist/environment/internal.js +113 -0
- package/dist/environment/internal.js.map +1 -0
- package/dist/environment/references.d.ts +18 -0
- package/dist/environment/references.d.ts.map +1 -0
- package/dist/environment/references.js +206 -0
- package/dist/environment/references.js.map +1 -0
- package/dist/environment/validation.d.ts +28 -0
- package/dist/environment/validation.d.ts.map +1 -0
- package/dist/environment/validation.js +320 -0
- package/dist/environment/validation.js.map +1 -0
- package/dist/esm/accounts/accounts.js +1 -0
- package/dist/esm/accounts/azureManualOnboarding.js +8 -0
- package/dist/esm/accounts/azureSpSetup.js +14 -1
- package/dist/esm/accounts/azureSpSetupDurable.js +27 -1
- package/dist/esm/accounts/index.js +1 -0
- package/dist/esm/ai/index.js +10 -1
- package/dist/esm/aws/commitments-planning.js +5 -0
- package/dist/esm/aws/estates.js +7 -1
- package/dist/esm/aws/index.js +5 -0
- package/dist/esm/aws/organizationCommitments.js +48 -0
- package/dist/esm/aws/organizationCommitmentsApiValidation.js +220 -0
- package/dist/esm/aws/organizationCommitmentsValidation.js +224 -0
- package/dist/esm/aws/portalOrganizationCommitmentsPlanningPublicArtifactValidation.js +402 -0
- package/dist/esm/aws/portalOrganizationCommitmentsPlanningPublicArtifacts.js +2 -0
- package/dist/esm/aws/requests.js +1 -1
- package/dist/esm/azure/financialAnalyticsDelivery.js +2 -0
- package/dist/esm/azure/financialAnalyticsDisplayKernel.js +139 -0
- package/dist/esm/azure/financialAnalyticsValidation.js +61 -7
- package/dist/esm/azure/financialAuthorityProjectionKernel.js +159 -0
- package/dist/esm/azure/financialAuthorityViewValidation.js +204 -1
- package/dist/esm/azure/financialChargeComposition.js +36 -0
- package/dist/esm/azure/financialChargeCompositionKernel.js +48 -0
- package/dist/esm/azure/financialChargeCompositionValidation.js +155 -0
- package/dist/esm/azure/financialDataflowKernel.js +60 -7
- package/dist/esm/azure/financialDataflowValidation.js +146 -28
- package/dist/esm/azure/financialPolicyValidation.js +5 -0
- package/dist/esm/azure/financialProjectionValidation.js +43 -3
- package/dist/esm/azure/financialSavingsAuthorityValidation.js +124 -2
- package/dist/esm/azure/financialSavingsCoordinateValidation.js +62 -19
- package/dist/esm/azure/financialSavingsSurfaceProjection.js +2 -0
- package/dist/esm/azure/financialSavingsSurfaceProjectionKernel.js +318 -0
- package/dist/esm/azure/financialSavingsSurfaceProjectionValidation.js +126 -12
- package/dist/esm/azure/financialScope.js +6 -0
- package/dist/esm/azure/governance.js +1 -0
- package/dist/esm/azure/vmPatchManagement.js +3 -0
- package/dist/esm/entries/commitments-planning.js +2 -2
- package/dist/esm/entries/environment.js +3 -0
- package/dist/esm/environment/canonicalization.js +34 -0
- package/dist/esm/environment/contracts.js +17 -0
- package/dist/esm/environment/index.js +4 -0
- package/dist/esm/environment/internal.js +97 -0
- package/dist/esm/environment/references.js +194 -0
- package/dist/esm/environment/validation.js +303 -0
- package/dist/esm/index.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/portfolio/portfolioOperations.d.ts +1 -1
- package/dist/portfolio/portfolioOperations.d.ts.map +1 -1
- 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
|
-
|
|
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 => ({
|
|
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
|
-
|
|
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
|
|
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'
|
|
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
|
-
?
|
|
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';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from '../aws/
|
|
2
|
-
import * as namespace from '../aws/
|
|
1
|
+
export * from '../aws/commitments-planning.js';
|
|
2
|
+
import * as namespace from '../aws/commitments-planning.js';
|
|
3
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
|
+
};
|