@spottoai/types-package 1.0.2-beta.373 → 1.0.2-beta.374
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 +4 -2
- package/dist/accounts/accounts.d.ts.map +1 -1
- package/dist/accounts/accounts.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/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/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/entries/commitments-planning.js +2 -2
- package/package.json +4 -3
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { ProviderName } from '../common/provider.js';
|
|
2
|
+
import { asRecord, assertAccount, assertExactKeys, assertValue, requiredEnum, requiredString } from './pluginPublicArtifactValidationHelpers.js';
|
|
3
|
+
const ORGANIZATION_ID = /^o-[a-z0-9]{10,32}$/u;
|
|
4
|
+
const FORBIDDEN_ORGANIZATION_COMMITMENTS_KEYS = new Set([
|
|
5
|
+
'billingScopeId',
|
|
6
|
+
'blobPath',
|
|
7
|
+
'checkpoint',
|
|
8
|
+
'cloudAccountId',
|
|
9
|
+
'connectionString',
|
|
10
|
+
'credentialHealth',
|
|
11
|
+
'credentialId',
|
|
12
|
+
'credentialReference',
|
|
13
|
+
'externalId',
|
|
14
|
+
'managementGroupId',
|
|
15
|
+
'pricingQuote',
|
|
16
|
+
'quotePolicy',
|
|
17
|
+
'resourceGroupId',
|
|
18
|
+
'roleArn',
|
|
19
|
+
'saga',
|
|
20
|
+
'sasToken',
|
|
21
|
+
'secretArn',
|
|
22
|
+
'secretReference',
|
|
23
|
+
'storageCapacity',
|
|
24
|
+
'storagePath',
|
|
25
|
+
'subscription',
|
|
26
|
+
'subscriptionId',
|
|
27
|
+
'tenantId',
|
|
28
|
+
'unlockFinancialLedger',
|
|
29
|
+
]);
|
|
30
|
+
/** Validates the multi-account identity boundary of an AWS organization commitments view. */
|
|
31
|
+
export function validateAwsOrganizationCommitmentsPlanningViewIdentity(value, expected) {
|
|
32
|
+
const view = asRecord(value, 'organizationCommitmentsPlanning');
|
|
33
|
+
const providerScope = asRecord(view.providerScope, 'organizationCommitmentsPlanning.providerScope');
|
|
34
|
+
assertExactKeys(providerScope, ['providerName', 'providerScopeId', 'scopeType', 'companyId', 'estateId', 'organizationId', 'managementAccountId'], 'organizationCommitmentsPlanning.providerScope');
|
|
35
|
+
assertValue(providerScope.providerName, ProviderName.Aws, 'organizationCommitmentsPlanning.providerScope.providerName');
|
|
36
|
+
assertValue(providerScope.scopeType, 'organization', 'organizationCommitmentsPlanning.providerScope.scopeType');
|
|
37
|
+
const companyId = requiredString(providerScope.companyId, 'organizationCommitmentsPlanning.providerScope.companyId');
|
|
38
|
+
const estateId = requiredString(providerScope.estateId, 'organizationCommitmentsPlanning.providerScope.estateId');
|
|
39
|
+
const organizationId = requiredOrganizationId(providerScope.organizationId, 'organizationCommitmentsPlanning.providerScope.organizationId');
|
|
40
|
+
assertValue(providerScope.providerScopeId, organizationId, 'organizationCommitmentsPlanning.providerScope.providerScopeId');
|
|
41
|
+
const managementAccountId = requiredAccountId(providerScope.managementAccountId, 'organizationCommitmentsPlanning.providerScope.managementAccountId');
|
|
42
|
+
assertValue(expected.companyId, companyId, 'expected.companyId');
|
|
43
|
+
assertValue(expected.estateId, estateId, 'expected.estateId');
|
|
44
|
+
assertValue(expected.organizationId, organizationId, 'expected.organizationId');
|
|
45
|
+
assertValue(expected.managementAccountId, managementAccountId, 'expected.managementAccountId');
|
|
46
|
+
assertValue(expected.manifestRevision, requiredString(view.manifestRevision, 'organizationCommitmentsPlanning.manifestRevision'), 'expected.manifestRevision');
|
|
47
|
+
const accountMembership = validateAccounts(view.accounts, managementAccountId);
|
|
48
|
+
validateExpectedMembers(expected.memberAccountIds, accountMembership.accountIds);
|
|
49
|
+
validateInventory(view.inventory, accountMembership.members);
|
|
50
|
+
validatePayerAggregates(view.payerAggregates, accountMembership.members, managementAccountId);
|
|
51
|
+
validatePurchaseRecommendations(view.purchaseRecommendations, accountMembership.members, managementAccountId);
|
|
52
|
+
validateAttribution(view.allocation, accountMembership.members, false, 'organizationCommitmentsPlanning.allocation');
|
|
53
|
+
validateAttribution(view.resourceAttribution, accountMembership.members, true, 'organizationCommitmentsPlanning.resourceAttribution');
|
|
54
|
+
validateBoundIdentities(view, accountMembership.members, organizationId, companyId, estateId, managementAccountId, 'organizationCommitmentsPlanning');
|
|
55
|
+
}
|
|
56
|
+
function validateAccounts(value, managementAccountId) {
|
|
57
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
58
|
+
throw new Error('organizationCommitmentsPlanning.accounts must be a non-empty array.');
|
|
59
|
+
const accountIds = value.map((entry, index) => {
|
|
60
|
+
const field = `organizationCommitmentsPlanning.accounts[${index}]`;
|
|
61
|
+
const account = asRecord(entry, field);
|
|
62
|
+
assertExactKeys(account, ['accountId', 'displayName', 'role', 'inventoryStatus', 'lastSuccessfulRefreshAt'], field);
|
|
63
|
+
const accountId = requiredAccountId(account.accountId, `${field}.accountId`);
|
|
64
|
+
const role = requiredEnum(account.role, ['management', 'member'], `${field}.role`);
|
|
65
|
+
if ((accountId === managementAccountId) !== (role === 'management')) {
|
|
66
|
+
throw new Error(`${field}.role must identify the exact management account.`);
|
|
67
|
+
}
|
|
68
|
+
return accountId;
|
|
69
|
+
});
|
|
70
|
+
if (new Set(accountIds).size !== accountIds.length)
|
|
71
|
+
throw new Error('organizationCommitmentsPlanning.accounts must not contain duplicates.');
|
|
72
|
+
const sorted = [...accountIds].sort((left, right) => left.localeCompare(right));
|
|
73
|
+
if (JSON.stringify(sorted) !== JSON.stringify(accountIds))
|
|
74
|
+
throw new Error('organizationCommitmentsPlanning.accounts must be sorted by accountId.');
|
|
75
|
+
if (!accountIds.includes(managementAccountId))
|
|
76
|
+
throw new Error('organizationCommitmentsPlanning.accounts must contain the management account.');
|
|
77
|
+
return { accountIds, members: new Set(accountIds) };
|
|
78
|
+
}
|
|
79
|
+
function validateInventory(value, members) {
|
|
80
|
+
if (!Array.isArray(value))
|
|
81
|
+
throw new Error('organizationCommitmentsPlanning.inventory must be an array.');
|
|
82
|
+
value.forEach((entry, index) => {
|
|
83
|
+
const field = `organizationCommitmentsPlanning.inventory[${index}]`;
|
|
84
|
+
const item = asRecord(entry, field);
|
|
85
|
+
assertValue(item.sourceKind, 'aws-native', `${field}.sourceKind`);
|
|
86
|
+
assertValue(item.provider, ProviderName.Aws, `${field}.provider`);
|
|
87
|
+
requiredMember(item.ownerAccountId, members, `${field}.ownerAccountId`);
|
|
88
|
+
if (item.shape !== undefined)
|
|
89
|
+
validateAwsShape(item.shape, `${field}.shape`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function validatePayerAggregates(value, members, managementAccountId) {
|
|
93
|
+
if (!Array.isArray(value))
|
|
94
|
+
throw new Error('organizationCommitmentsPlanning.payerAggregates must be an array.');
|
|
95
|
+
value.forEach((entry, index) => {
|
|
96
|
+
const field = `organizationCommitmentsPlanning.payerAggregates[${index}]`;
|
|
97
|
+
const aggregate = asRecord(entry, field);
|
|
98
|
+
const payerAccountId = requiredMember(aggregate.payerAccountId, members, `${field}.payerAccountId`);
|
|
99
|
+
assertValue(payerAccountId, managementAccountId, `${field}.payerAccountId`);
|
|
100
|
+
validateAwsSource(aggregate.source, `${field}.source`);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function validatePurchaseRecommendations(value, members, managementAccountId) {
|
|
104
|
+
if (value === undefined)
|
|
105
|
+
return;
|
|
106
|
+
if (!Array.isArray(value))
|
|
107
|
+
throw new Error('organizationCommitmentsPlanning.purchaseRecommendations must be an array.');
|
|
108
|
+
value.forEach((entry, index) => {
|
|
109
|
+
const field = `organizationCommitmentsPlanning.purchaseRecommendations[${index}]`;
|
|
110
|
+
const recommendation = asRecord(entry, field);
|
|
111
|
+
assertValue(recommendation.purchaseScope, 'payer', `${field}.purchaseScope`);
|
|
112
|
+
const payerAccountId = requiredMember(recommendation.payerAccountId, members, `${field}.payerAccountId`);
|
|
113
|
+
assertValue(payerAccountId, managementAccountId, `${field}.payerAccountId`);
|
|
114
|
+
if (recommendation.recommendedAccountId !== undefined) {
|
|
115
|
+
requiredMember(recommendation.recommendedAccountId, members, `${field}.recommendedAccountId`);
|
|
116
|
+
}
|
|
117
|
+
validateAwsSource(recommendation.source, `${field}.source`);
|
|
118
|
+
validateAwsShape(recommendation.targetShape, `${field}.targetShape`);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function validateAttribution(value, members, resource, field) {
|
|
122
|
+
const evidence = asRecord(value, field);
|
|
123
|
+
if (evidence.status === 'unavailable') {
|
|
124
|
+
assertExactKeys(evidence, ['status', 'reason'], field);
|
|
125
|
+
requiredEnum(evidence.reason, ['not-collected', 'not-proved', 'source-unavailable'], `${field}.reason`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
assertValue(evidence.status, 'available', `${field}.status`);
|
|
129
|
+
assertExactKeys(evidence, ['status', 'source', 'records'], field);
|
|
130
|
+
validateAwsSource(evidence.source, `${field}.source`, true);
|
|
131
|
+
if (!Array.isArray(evidence.records) || evidence.records.length === 0) {
|
|
132
|
+
throw new Error(`${field}.records must be non-empty when attribution is available.`);
|
|
133
|
+
}
|
|
134
|
+
evidence.records.forEach((entry, index) => {
|
|
135
|
+
const record = asRecord(entry, `${field}.records[${index}]`);
|
|
136
|
+
if (resource) {
|
|
137
|
+
requiredMember(record.accountId, members, `${field}.records[${index}].accountId`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
requiredMember(record.beneficiaryAccountId, members, `${field}.records[${index}].beneficiaryAccountId`);
|
|
141
|
+
if (record.ownerAccountId !== undefined)
|
|
142
|
+
requiredMember(record.ownerAccountId, members, `${field}.records[${index}].ownerAccountId`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function validateAwsSource(value, field, allowDerived = false) {
|
|
147
|
+
const source = asRecord(value, field);
|
|
148
|
+
const allowed = allowDerived ? ['aws-native', 'spotto-derived'] : ['aws-native'];
|
|
149
|
+
requiredEnum(source.sourceKind, allowed, `${field}.sourceKind`);
|
|
150
|
+
}
|
|
151
|
+
function validateAwsShape(value, field) {
|
|
152
|
+
const shape = asRecord(value, field);
|
|
153
|
+
assertValue(shape.provider, 'aws', `${field}.provider`);
|
|
154
|
+
}
|
|
155
|
+
function validateBoundIdentities(value, members, organizationId, companyId, estateId, managementAccountId, field) {
|
|
156
|
+
if (Array.isArray(value)) {
|
|
157
|
+
value.forEach((entry, index) => validateBoundIdentities(entry, members, organizationId, companyId, estateId, managementAccountId, `${field}[${index}]`));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (!value || typeof value !== 'object') {
|
|
161
|
+
if (typeof value === 'string')
|
|
162
|
+
validateArnAccount(value, members, field);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
for (const [key, child] of Object.entries(value)) {
|
|
166
|
+
if (FORBIDDEN_ORGANIZATION_COMMITMENTS_KEYS.has(key)) {
|
|
167
|
+
throw new Error(`${field}.${key} is not allowed in an AWS organization commitments artifact.`);
|
|
168
|
+
}
|
|
169
|
+
if (key === 'accountId' || key === 'ownerAccountId' || key === 'beneficiaryAccountId' || key === 'payerAccountId') {
|
|
170
|
+
requiredMember(child, members, `${field}.${key}`);
|
|
171
|
+
}
|
|
172
|
+
if (key === 'organizationId')
|
|
173
|
+
assertValue(child, organizationId, `${field}.${key}`);
|
|
174
|
+
if (key === 'companyId')
|
|
175
|
+
assertValue(child, companyId, `${field}.${key}`);
|
|
176
|
+
if (key === 'estateId')
|
|
177
|
+
assertValue(child, estateId, `${field}.${key}`);
|
|
178
|
+
if (key === 'managementAccountId')
|
|
179
|
+
assertAccount(child, managementAccountId, `${field}.${key}`);
|
|
180
|
+
if (key === 'providerName')
|
|
181
|
+
assertValue(child, ProviderName.Aws, `${field}.${key}`);
|
|
182
|
+
if (key === 'provider')
|
|
183
|
+
assertValue(child, 'aws', `${field}.${key}`);
|
|
184
|
+
if (key === 'sourceKind' && child === 'azure-native') {
|
|
185
|
+
throw new Error(`${field}.${key} cannot contain Azure-native evidence.`);
|
|
186
|
+
}
|
|
187
|
+
validateBoundIdentities(child, members, organizationId, companyId, estateId, managementAccountId, `${field}.${key}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function validateArnAccount(value, members, field) {
|
|
191
|
+
if (!value.startsWith('arn:'))
|
|
192
|
+
return;
|
|
193
|
+
const arnAccountId = value.split(':', 6)[4];
|
|
194
|
+
if (arnAccountId && !members.has(arnAccountId))
|
|
195
|
+
throw new Error(`${field} contains an ARN outside the declared organization membership.`);
|
|
196
|
+
}
|
|
197
|
+
function requiredMember(value, members, field) {
|
|
198
|
+
const accountId = requiredAccountId(value, field);
|
|
199
|
+
if (!members.has(accountId))
|
|
200
|
+
throw new Error(`${field} must belong to the declared organization membership.`);
|
|
201
|
+
return accountId;
|
|
202
|
+
}
|
|
203
|
+
function requiredAccountId(value, field) {
|
|
204
|
+
const accountId = requiredString(value, field);
|
|
205
|
+
assertAccount(value, accountId, field);
|
|
206
|
+
return accountId;
|
|
207
|
+
}
|
|
208
|
+
function requiredOrganizationId(value, field) {
|
|
209
|
+
const organizationId = requiredString(value, field);
|
|
210
|
+
if (!ORGANIZATION_ID.test(organizationId))
|
|
211
|
+
throw new Error(`${field} must be a canonical AWS organization id.`);
|
|
212
|
+
return organizationId;
|
|
213
|
+
}
|
|
214
|
+
function validateExpectedMembers(expected, actual) {
|
|
215
|
+
const normalized = expected.map((accountId, index) => requiredAccountId(accountId, `expected.memberAccountIds[${index}]`));
|
|
216
|
+
if (new Set(normalized).size !== normalized.length)
|
|
217
|
+
throw new Error('expected.memberAccountIds must not contain duplicates.');
|
|
218
|
+
const sorted = [...normalized].sort((left, right) => left.localeCompare(right));
|
|
219
|
+
if (JSON.stringify(sorted) !== JSON.stringify(normalized))
|
|
220
|
+
throw new Error('expected.memberAccountIds must be sorted.');
|
|
221
|
+
if (JSON.stringify(normalized) !== JSON.stringify(actual)) {
|
|
222
|
+
throw new Error('expected.memberAccountIds must match the artifact membership exactly.');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { AWS_ORGANIZATION_COMMITMENTS_ATTRIBUTION_UNAVAILABLE_REASONS, } from './organizationCommitments.js';
|
|
2
|
+
import { validateAwsOrganizationCommitmentsPlanningViewIdentity, } from './organizationCommitmentsValidation.js';
|
|
3
|
+
import { AWS_ORGANIZATION_COMMITMENTS_PLANNING_LOGICAL_NAME, AWS_ORGANIZATION_COMMITMENTS_PUBLIC_ARTIFACT_SCHEMA_VERSION, } from './portalOrganizationCommitmentsPlanningPublicArtifacts.js';
|
|
4
|
+
import { AWS_PORTAL_PUBLIC_ARTIFACT_SCHEMA_VERSION } from './portalPublicArtifacts.js';
|
|
5
|
+
import { asRecord, assertExactKeys, assertPublicJson, assertValue, finiteNumber, isoTimestamp, requiredEnum, requiredString, validateGeneration, } from './portalPublicArtifactValidationCommon.js';
|
|
6
|
+
const TOP_LEVEL_KEYS = [
|
|
7
|
+
'schemaVersion',
|
|
8
|
+
'portalSchemaVersion',
|
|
9
|
+
'provider',
|
|
10
|
+
'artifactType',
|
|
11
|
+
'artifactGeneration',
|
|
12
|
+
'logicalName',
|
|
13
|
+
'manifestRevision',
|
|
14
|
+
'version',
|
|
15
|
+
'generatedAt',
|
|
16
|
+
'month',
|
|
17
|
+
'providerScope',
|
|
18
|
+
'accounts',
|
|
19
|
+
'utilizationSummary',
|
|
20
|
+
'expirySummary',
|
|
21
|
+
'inventory',
|
|
22
|
+
'payerAggregates',
|
|
23
|
+
'sharingPosture',
|
|
24
|
+
'pricingContext',
|
|
25
|
+
'freshness',
|
|
26
|
+
'purchaseRecommendations',
|
|
27
|
+
'allocation',
|
|
28
|
+
'resourceAttribution',
|
|
29
|
+
];
|
|
30
|
+
/** Validates one untrusted organization Commitments Planning view without its Portal envelope. */
|
|
31
|
+
export function validateAwsOrganizationCommitmentsPlanningView(value, expected) {
|
|
32
|
+
validateAwsOrganizationCommitmentsPlanningViewIdentity(value, expected);
|
|
33
|
+
validateBody(value);
|
|
34
|
+
assertPublicJson(value, 'organizationCommitmentsPlanning');
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
/** Validates one untrusted immutable AWS organization Commitments Planning artifact. */
|
|
38
|
+
export function validateAwsPortalOrganizationCommitmentsPlanningArtifact(value, expected) {
|
|
39
|
+
const artifact = asRecord(value, 'artifact');
|
|
40
|
+
assertExactKeys(artifact, TOP_LEVEL_KEYS, 'artifact');
|
|
41
|
+
assertValue(artifact.schemaVersion, AWS_ORGANIZATION_COMMITMENTS_PUBLIC_ARTIFACT_SCHEMA_VERSION, 'artifact.schemaVersion');
|
|
42
|
+
assertValue(artifact.portalSchemaVersion, AWS_PORTAL_PUBLIC_ARTIFACT_SCHEMA_VERSION, 'artifact.portalSchemaVersion');
|
|
43
|
+
assertValue(artifact.provider, 'aws', 'artifact.provider');
|
|
44
|
+
assertValue(artifact.artifactType, 'organization-commitments-planning', 'artifact.artifactType');
|
|
45
|
+
assertValue(artifact.logicalName, AWS_ORGANIZATION_COMMITMENTS_PLANNING_LOGICAL_NAME, 'artifact.logicalName');
|
|
46
|
+
requiredString(artifact.manifestRevision, 'artifact.manifestRevision');
|
|
47
|
+
assertValue(artifact.version, '1.0', 'artifact.version');
|
|
48
|
+
const generation = validateGeneration(artifact.artifactGeneration, 'artifact.artifactGeneration');
|
|
49
|
+
const generatedAt = isoTimestamp(artifact.generatedAt, 'artifact.generatedAt');
|
|
50
|
+
if (Date.parse(generatedAt) > Date.parse(generation.generatedAt)) {
|
|
51
|
+
throw new Error('artifact.generatedAt must not be newer than the Portal generation.');
|
|
52
|
+
}
|
|
53
|
+
if (artifact.month !== undefined)
|
|
54
|
+
requiredString(artifact.month, 'artifact.month');
|
|
55
|
+
validateAwsOrganizationCommitmentsPlanningView(artifact, expected);
|
|
56
|
+
assertPublicJson(artifact, 'artifact');
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
function validateBody(view) {
|
|
60
|
+
records(view.accounts, 'artifact.accounts', validateAccount);
|
|
61
|
+
validateUtilizationSummary(view.utilizationSummary, 'artifact.utilizationSummary');
|
|
62
|
+
exact(view.expirySummary, ['expired', 'expiring30d', 'expiring60d', 'expiring90d', 'expiring180d'], 'artifact.expirySummary');
|
|
63
|
+
records(view.inventory, 'artifact.inventory', validateInventory);
|
|
64
|
+
records(view.payerAggregates, 'artifact.payerAggregates', validatePayerAggregate);
|
|
65
|
+
validateSharingPosture(view.sharingPosture, 'artifact.sharingPosture');
|
|
66
|
+
validatePricingContext(view.pricingContext, 'artifact.pricingContext');
|
|
67
|
+
optionalRecord(view.freshness, 'artifact.freshness', validateFreshness);
|
|
68
|
+
optionalRecords(view.purchaseRecommendations, 'artifact.purchaseRecommendations', validatePurchaseRecommendation);
|
|
69
|
+
validateAttribution(view.allocation, 'artifact.allocation', validateAllocationRecord);
|
|
70
|
+
validateAttribution(view.resourceAttribution, 'artifact.resourceAttribution', validateResourceAttributionRecord);
|
|
71
|
+
}
|
|
72
|
+
function validateSharingPosture(value, field) {
|
|
73
|
+
const posture = exact(value, ['status', 'reason'], field);
|
|
74
|
+
assertValue(posture.status, 'unknown', `${field}.status`);
|
|
75
|
+
assertValue(posture.reason, 'not-collected', `${field}.reason`);
|
|
76
|
+
}
|
|
77
|
+
function validateAccount(value, field) {
|
|
78
|
+
const account = exact(value, ['accountId', 'displayName', 'role', 'inventoryStatus', 'lastSuccessfulRefreshAt'], field);
|
|
79
|
+
requiredString(account.accountId, `${field}.accountId`);
|
|
80
|
+
optionalString(account.displayName, `${field}.displayName`);
|
|
81
|
+
requiredEnum(account.role, ['management', 'member'], `${field}.role`);
|
|
82
|
+
requiredEnum(account.inventoryStatus, ['current', 'stale', 'partial', 'unavailable'], `${field}.inventoryStatus`);
|
|
83
|
+
optionalTimestamp(account.lastSuccessfulRefreshAt, `${field}.lastSuccessfulRefreshAt`);
|
|
84
|
+
}
|
|
85
|
+
function validateUtilizationSummary(value, field) {
|
|
86
|
+
const summary = exact(value, ['total', 'withData', 'sevenDayAverage', 'thirtyDayAverage', 'sevenDayAggregates', 'thirtyDayAggregates', 'byBenefitType'], field);
|
|
87
|
+
nonNegative(summary.total, `${field}.total`);
|
|
88
|
+
nonNegative(summary.withData, `${field}.withData`);
|
|
89
|
+
optionalNumber(summary.sevenDayAverage, `${field}.sevenDayAverage`);
|
|
90
|
+
optionalNumber(summary.thirtyDayAverage, `${field}.thirtyDayAverage`);
|
|
91
|
+
optionalRecords(summary.sevenDayAggregates, `${field}.sevenDayAggregates`, validateWeightedAggregate);
|
|
92
|
+
optionalRecords(summary.thirtyDayAggregates, `${field}.thirtyDayAggregates`, validateWeightedAggregate);
|
|
93
|
+
records(summary.byBenefitType, `${field}.byBenefitType`, (entry, itemField) => {
|
|
94
|
+
const item = exact(entry, ['benefitType', 'total', 'withData', 'sevenDayAverage', 'thirtyDayAverage', 'sevenDayAggregates', 'thirtyDayAggregates'], itemField);
|
|
95
|
+
requiredEnum(item.benefitType, ['reservation', 'savings-plan'], `${itemField}.benefitType`);
|
|
96
|
+
nonNegative(item.total, `${itemField}.total`);
|
|
97
|
+
nonNegative(item.withData, `${itemField}.withData`);
|
|
98
|
+
optionalNumber(item.sevenDayAverage, `${itemField}.sevenDayAverage`);
|
|
99
|
+
optionalNumber(item.thirtyDayAverage, `${itemField}.thirtyDayAverage`);
|
|
100
|
+
optionalRecords(item.sevenDayAggregates, `${itemField}.sevenDayAggregates`, validateWeightedAggregate);
|
|
101
|
+
optionalRecords(item.thirtyDayAggregates, `${itemField}.thirtyDayAggregates`, validateWeightedAggregate);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function validateWeightedAggregate(value, field) {
|
|
105
|
+
const aggregate = exact(value, ['used', 'reserved', 'percentage', 'sampleCount', 'unit', 'currency'], field);
|
|
106
|
+
finiteNumber(aggregate.used, `${field}.used`);
|
|
107
|
+
finiteNumber(aggregate.reserved, `${field}.reserved`);
|
|
108
|
+
optionalNumber(aggregate.percentage, `${field}.percentage`);
|
|
109
|
+
if (aggregate.sampleCount !== undefined)
|
|
110
|
+
nonNegative(aggregate.sampleCount, `${field}.sampleCount`);
|
|
111
|
+
requiredEnum(aggregate.unit, ['hours', 'quantity', 'currency'], `${field}.unit`);
|
|
112
|
+
optionalString(aggregate.currency, `${field}.currency`);
|
|
113
|
+
}
|
|
114
|
+
function validateInventory(value, field) {
|
|
115
|
+
const item = exact(value, [
|
|
116
|
+
'id',
|
|
117
|
+
'benefitType',
|
|
118
|
+
'commitmentFamily',
|
|
119
|
+
'sourceKind',
|
|
120
|
+
'sourceId',
|
|
121
|
+
'provider',
|
|
122
|
+
'ownerAccountId',
|
|
123
|
+
'shape',
|
|
124
|
+
'scope',
|
|
125
|
+
'type',
|
|
126
|
+
'displayName',
|
|
127
|
+
'status',
|
|
128
|
+
'purchaseDate',
|
|
129
|
+
'expiryDate',
|
|
130
|
+
'daysToExpiry',
|
|
131
|
+
'commitmentAmount',
|
|
132
|
+
'commitmentCurrencyCode',
|
|
133
|
+
'commitmentGrain',
|
|
134
|
+
'commitmentUnit',
|
|
135
|
+
'skuName',
|
|
136
|
+
'location',
|
|
137
|
+
'term',
|
|
138
|
+
'termMonths',
|
|
139
|
+
'billingPlan',
|
|
140
|
+
'utilization',
|
|
141
|
+
], field);
|
|
142
|
+
requiredString(item.id, `${field}.id`);
|
|
143
|
+
requiredEnum(item.benefitType, ['reservation', 'savings-plan'], `${field}.benefitType`);
|
|
144
|
+
optionalString(item.commitmentFamily, `${field}.commitmentFamily`);
|
|
145
|
+
assertValue(item.sourceKind, 'aws-native', `${field}.sourceKind`);
|
|
146
|
+
optionalString(item.sourceId, `${field}.sourceId`);
|
|
147
|
+
assertValue(item.provider, 'aws', `${field}.provider`);
|
|
148
|
+
requiredString(item.ownerAccountId, `${field}.ownerAccountId`);
|
|
149
|
+
optionalRecord(item.shape, `${field}.shape`, validateShape);
|
|
150
|
+
requiredEnum(item.scope, ['Shared', 'Single', 'ManagementGroup', 'Unknown'], `${field}.scope`);
|
|
151
|
+
requiredString(item.type, `${field}.type`);
|
|
152
|
+
optionalString(item.displayName, `${field}.displayName`);
|
|
153
|
+
requiredString(item.status, `${field}.status`);
|
|
154
|
+
optionalString(item.purchaseDate, `${field}.purchaseDate`);
|
|
155
|
+
optionalString(item.expiryDate, `${field}.expiryDate`);
|
|
156
|
+
optionalNumber(item.daysToExpiry, `${field}.daysToExpiry`);
|
|
157
|
+
optionalNumber(item.commitmentAmount, `${field}.commitmentAmount`);
|
|
158
|
+
optionalString(item.commitmentCurrencyCode, `${field}.commitmentCurrencyCode`);
|
|
159
|
+
optionalString(item.commitmentGrain, `${field}.commitmentGrain`);
|
|
160
|
+
optionalString(item.commitmentUnit, `${field}.commitmentUnit`);
|
|
161
|
+
optionalString(item.skuName, `${field}.skuName`);
|
|
162
|
+
optionalString(item.location, `${field}.location`);
|
|
163
|
+
optionalString(item.term, `${field}.term`);
|
|
164
|
+
optionalNumber(item.termMonths, `${field}.termMonths`);
|
|
165
|
+
optionalString(item.billingPlan, `${field}.billingPlan`);
|
|
166
|
+
optionalRecord(item.utilization, `${field}.utilization`, validateUtilization);
|
|
167
|
+
}
|
|
168
|
+
function validatePayerAggregate(value, field) {
|
|
169
|
+
const aggregate = exact(value, [
|
|
170
|
+
'benefitType',
|
|
171
|
+
'commitmentFamily',
|
|
172
|
+
'payerAccountId',
|
|
173
|
+
'windowStart',
|
|
174
|
+
'windowEnd',
|
|
175
|
+
'eligibleCost',
|
|
176
|
+
'coveredCost',
|
|
177
|
+
'uncoveredCost',
|
|
178
|
+
'coveragePercent',
|
|
179
|
+
'utilization',
|
|
180
|
+
'source',
|
|
181
|
+
], field);
|
|
182
|
+
requiredEnum(aggregate.benefitType, ['reservation', 'savings-plan'], `${field}.benefitType`);
|
|
183
|
+
requiredString(aggregate.commitmentFamily, `${field}.commitmentFamily`);
|
|
184
|
+
requiredString(aggregate.payerAccountId, `${field}.payerAccountId`);
|
|
185
|
+
requiredString(aggregate.windowStart, `${field}.windowStart`);
|
|
186
|
+
requiredString(aggregate.windowEnd, `${field}.windowEnd`);
|
|
187
|
+
optionalRecord(aggregate.eligibleCost, `${field}.eligibleCost`, validateMoney);
|
|
188
|
+
optionalRecord(aggregate.coveredCost, `${field}.coveredCost`, validateMoney);
|
|
189
|
+
optionalRecord(aggregate.uncoveredCost, `${field}.uncoveredCost`, validateMoney);
|
|
190
|
+
optionalNumber(aggregate.coveragePercent, `${field}.coveragePercent`);
|
|
191
|
+
optionalRecord(aggregate.utilization, `${field}.utilization`, validateUtilization);
|
|
192
|
+
validateSource(aggregate.source, `${field}.source`, false);
|
|
193
|
+
}
|
|
194
|
+
function validatePurchaseRecommendation(value, field) {
|
|
195
|
+
const recommendation = exact(value, [
|
|
196
|
+
'id',
|
|
197
|
+
'groupKey',
|
|
198
|
+
'commitmentFamily',
|
|
199
|
+
'action',
|
|
200
|
+
'purchaseScope',
|
|
201
|
+
'payerAccountId',
|
|
202
|
+
'recommendedAccountId',
|
|
203
|
+
'source',
|
|
204
|
+
'targetShape',
|
|
205
|
+
'title',
|
|
206
|
+
'description',
|
|
207
|
+
'confidence',
|
|
208
|
+
'riskLevel',
|
|
209
|
+
'termMonths',
|
|
210
|
+
'quantity',
|
|
211
|
+
'estimatedAnnualSavings',
|
|
212
|
+
'estimatedAnnualCost',
|
|
213
|
+
'estimatedTermSavings',
|
|
214
|
+
'estimatedTermCost',
|
|
215
|
+
'notes',
|
|
216
|
+
], field);
|
|
217
|
+
requiredString(recommendation.id, `${field}.id`);
|
|
218
|
+
requiredString(recommendation.groupKey, `${field}.groupKey`);
|
|
219
|
+
requiredString(recommendation.commitmentFamily, `${field}.commitmentFamily`);
|
|
220
|
+
requiredEnum(recommendation.action, ['buy', 'unlock', 'savings-plan', 'none'], `${field}.action`);
|
|
221
|
+
assertValue(recommendation.purchaseScope, 'payer', `${field}.purchaseScope`);
|
|
222
|
+
requiredString(recommendation.payerAccountId, `${field}.payerAccountId`);
|
|
223
|
+
optionalString(recommendation.recommendedAccountId, `${field}.recommendedAccountId`);
|
|
224
|
+
validateSource(recommendation.source, `${field}.source`, false);
|
|
225
|
+
validateShape(recommendation.targetShape, `${field}.targetShape`);
|
|
226
|
+
optionalString(recommendation.title, `${field}.title`);
|
|
227
|
+
optionalString(recommendation.description, `${field}.description`);
|
|
228
|
+
optionalString(recommendation.confidence, `${field}.confidence`);
|
|
229
|
+
optionalString(recommendation.riskLevel, `${field}.riskLevel`);
|
|
230
|
+
optionalNumber(recommendation.termMonths, `${field}.termMonths`);
|
|
231
|
+
optionalNumber(recommendation.quantity, `${field}.quantity`);
|
|
232
|
+
optionalRecord(recommendation.estimatedAnnualSavings, `${field}.estimatedAnnualSavings`, validateMoney);
|
|
233
|
+
optionalRecord(recommendation.estimatedAnnualCost, `${field}.estimatedAnnualCost`, validateMoney);
|
|
234
|
+
optionalRecord(recommendation.estimatedTermSavings, `${field}.estimatedTermSavings`, validateMoney);
|
|
235
|
+
optionalRecord(recommendation.estimatedTermCost, `${field}.estimatedTermCost`, validateMoney);
|
|
236
|
+
optionalStrings(recommendation.notes, `${field}.notes`);
|
|
237
|
+
}
|
|
238
|
+
function validateAttribution(value, field, validateRecord) {
|
|
239
|
+
const evidence = asRecord(value, field);
|
|
240
|
+
if (evidence.status === 'unavailable') {
|
|
241
|
+
assertExactKeys(evidence, ['status', 'reason'], field);
|
|
242
|
+
requiredEnum(evidence.reason, AWS_ORGANIZATION_COMMITMENTS_ATTRIBUTION_UNAVAILABLE_REASONS, `${field}.reason`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
assertValue(evidence.status, 'available', `${field}.status`);
|
|
246
|
+
assertExactKeys(evidence, ['status', 'source', 'records'], field);
|
|
247
|
+
validateSource(evidence.source, `${field}.source`, true);
|
|
248
|
+
records(evidence.records, `${field}.records`, validateRecord, true);
|
|
249
|
+
}
|
|
250
|
+
function validateAllocationRecord(value, field) {
|
|
251
|
+
const record = exact(value, [
|
|
252
|
+
'beneficiaryAccountId',
|
|
253
|
+
'ownerAccountId',
|
|
254
|
+
'commitmentId',
|
|
255
|
+
'benefitType',
|
|
256
|
+
'commitmentFamily',
|
|
257
|
+
'windowStart',
|
|
258
|
+
'windowEnd',
|
|
259
|
+
'eligibleCost',
|
|
260
|
+
'coveredCost',
|
|
261
|
+
'uncoveredCost',
|
|
262
|
+
], field);
|
|
263
|
+
requiredString(record.beneficiaryAccountId, `${field}.beneficiaryAccountId`);
|
|
264
|
+
optionalString(record.ownerAccountId, `${field}.ownerAccountId`);
|
|
265
|
+
optionalString(record.commitmentId, `${field}.commitmentId`);
|
|
266
|
+
requiredEnum(record.benefitType, ['reservation', 'savings-plan'], `${field}.benefitType`);
|
|
267
|
+
requiredString(record.commitmentFamily, `${field}.commitmentFamily`);
|
|
268
|
+
requiredString(record.windowStart, `${field}.windowStart`);
|
|
269
|
+
requiredString(record.windowEnd, `${field}.windowEnd`);
|
|
270
|
+
optionalRecord(record.eligibleCost, `${field}.eligibleCost`, validateMoney);
|
|
271
|
+
optionalRecord(record.coveredCost, `${field}.coveredCost`, validateMoney);
|
|
272
|
+
optionalRecord(record.uncoveredCost, `${field}.uncoveredCost`, validateMoney);
|
|
273
|
+
}
|
|
274
|
+
function validateResourceAttributionRecord(value, field) {
|
|
275
|
+
const record = exact(value, ['accountId', 'resourceId', 'resourceName', 'resourceType', 'benefitIds', 'windowStart', 'windowEnd', 'coveredCost'], field);
|
|
276
|
+
requiredString(record.accountId, `${field}.accountId`);
|
|
277
|
+
requiredString(record.resourceId, `${field}.resourceId`);
|
|
278
|
+
optionalString(record.resourceName, `${field}.resourceName`);
|
|
279
|
+
optionalString(record.resourceType, `${field}.resourceType`);
|
|
280
|
+
strings(record.benefitIds, `${field}.benefitIds`);
|
|
281
|
+
requiredString(record.windowStart, `${field}.windowStart`);
|
|
282
|
+
requiredString(record.windowEnd, `${field}.windowEnd`);
|
|
283
|
+
optionalRecord(record.coveredCost, `${field}.coveredCost`, validateMoney);
|
|
284
|
+
}
|
|
285
|
+
function validateShape(value, field) {
|
|
286
|
+
const shape = exact(value, [
|
|
287
|
+
'provider',
|
|
288
|
+
'resourceType',
|
|
289
|
+
'commitmentFamily',
|
|
290
|
+
'skuName',
|
|
291
|
+
'normalizedSkuName',
|
|
292
|
+
'location',
|
|
293
|
+
'region',
|
|
294
|
+
'availabilityZone',
|
|
295
|
+
'platform',
|
|
296
|
+
'reservationProductName',
|
|
297
|
+
'reservedResourceType',
|
|
298
|
+
'meterCategory',
|
|
299
|
+
'meterSubCategory',
|
|
300
|
+
'meterName',
|
|
301
|
+
'unit',
|
|
302
|
+
'attributes',
|
|
303
|
+
], field);
|
|
304
|
+
assertValue(shape.provider, 'aws', `${field}.provider`);
|
|
305
|
+
if (shape.attributes !== undefined)
|
|
306
|
+
asRecord(shape.attributes, `${field}.attributes`);
|
|
307
|
+
}
|
|
308
|
+
function validateSource(value, field, allowDerived) {
|
|
309
|
+
const source = exact(value, ['sourceKind', 'sourceId', 'sourceName', 'generatedAt', 'observedAt', 'notes'], field);
|
|
310
|
+
requiredEnum(source.sourceKind, allowDerived ? ['aws-native', 'spotto-derived'] : ['aws-native'], `${field}.sourceKind`);
|
|
311
|
+
optionalString(source.sourceId, `${field}.sourceId`);
|
|
312
|
+
optionalString(source.sourceName, `${field}.sourceName`);
|
|
313
|
+
optionalTimestamp(source.generatedAt, `${field}.generatedAt`);
|
|
314
|
+
optionalTimestamp(source.observedAt, `${field}.observedAt`);
|
|
315
|
+
optionalStrings(source.notes, `${field}.notes`);
|
|
316
|
+
}
|
|
317
|
+
function validateMoney(value, field) {
|
|
318
|
+
const money = exact(value, ['amount', 'currency', 'source', 'windowStart', 'windowEnd', 'flags'], field);
|
|
319
|
+
finiteNumber(money.amount, `${field}.amount`);
|
|
320
|
+
requiredString(money.currency, `${field}.currency`);
|
|
321
|
+
optionalString(money.source, `${field}.source`);
|
|
322
|
+
optionalString(money.windowStart, `${field}.windowStart`);
|
|
323
|
+
optionalString(money.windowEnd, `${field}.windowEnd`);
|
|
324
|
+
optionalStrings(money.flags, `${field}.flags`);
|
|
325
|
+
}
|
|
326
|
+
function validateUtilization(value, field) {
|
|
327
|
+
const utilization = exact(value, ['trend', 'oneDay', 'sevenDay', 'thirtyDay', 'source'], field);
|
|
328
|
+
optionalString(utilization.trend, `${field}.trend`);
|
|
329
|
+
optionalNumber(utilization.oneDay, `${field}.oneDay`);
|
|
330
|
+
optionalNumber(utilization.sevenDay, `${field}.sevenDay`);
|
|
331
|
+
optionalNumber(utilization.thirtyDay, `${field}.thirtyDay`);
|
|
332
|
+
optionalString(utilization.source, `${field}.source`);
|
|
333
|
+
}
|
|
334
|
+
function validatePricingContext(value, field) {
|
|
335
|
+
const pricing = exact(value, ['source', 'currency', 'assumptions', 'confidenceNotes', 'calculatorDeepLink'], field);
|
|
336
|
+
requiredEnum(pricing.source, ['retail', 'negotiated', 'unknown', 'recommendation-apis'], `${field}.source`);
|
|
337
|
+
optionalString(pricing.currency, `${field}.currency`);
|
|
338
|
+
optionalStrings(pricing.assumptions, `${field}.assumptions`);
|
|
339
|
+
optionalString(pricing.confidenceNotes, `${field}.confidenceNotes`);
|
|
340
|
+
optionalString(pricing.calculatorDeepLink, `${field}.calculatorDeepLink`);
|
|
341
|
+
}
|
|
342
|
+
function validateFreshness(value, field) {
|
|
343
|
+
const freshness = exact(value, ['status', 'generatedAt', 'entries', 'warnings'], field);
|
|
344
|
+
requiredEnum(freshness.status, ['current', 'stale', 'partial', 'unavailable'], `${field}.status`);
|
|
345
|
+
isoTimestamp(freshness.generatedAt, `${field}.generatedAt`);
|
|
346
|
+
records(freshness.entries, `${field}.entries`, (entry, itemField) => {
|
|
347
|
+
const item = exact(entry, ['section', 'status', 'generatedAt', 'observedAt', 'lastSuccessfulSyncAt', 'reason', 'sourceKind'], itemField);
|
|
348
|
+
requiredString(item.section, `${itemField}.section`);
|
|
349
|
+
requiredEnum(item.status, ['current', 'stale', 'partial', 'unavailable'], `${itemField}.status`);
|
|
350
|
+
optionalTimestamp(item.generatedAt, `${itemField}.generatedAt`);
|
|
351
|
+
optionalTimestamp(item.observedAt, `${itemField}.observedAt`);
|
|
352
|
+
optionalTimestamp(item.lastSuccessfulSyncAt, `${itemField}.lastSuccessfulSyncAt`);
|
|
353
|
+
optionalString(item.reason, `${itemField}.reason`);
|
|
354
|
+
optionalString(item.sourceKind, `${itemField}.sourceKind`);
|
|
355
|
+
});
|
|
356
|
+
optionalStrings(freshness.warnings, `${field}.warnings`);
|
|
357
|
+
}
|
|
358
|
+
function exact(value, allowed, field) {
|
|
359
|
+
const record = asRecord(value, field);
|
|
360
|
+
assertExactKeys(record, allowed, field);
|
|
361
|
+
return record;
|
|
362
|
+
}
|
|
363
|
+
function records(value, field, validate, nonEmpty = false) {
|
|
364
|
+
if (!Array.isArray(value) || (nonEmpty && value.length === 0)) {
|
|
365
|
+
throw new Error(`${field} must be ${nonEmpty ? 'a non-empty' : 'an'} array.`);
|
|
366
|
+
}
|
|
367
|
+
value.forEach((entry, index) => validate(entry, `${field}[${index}]`));
|
|
368
|
+
}
|
|
369
|
+
function optionalRecords(value, field, validate) {
|
|
370
|
+
if (value !== undefined)
|
|
371
|
+
records(value, field, validate);
|
|
372
|
+
}
|
|
373
|
+
function optionalRecord(value, field, validate) {
|
|
374
|
+
if (value !== undefined)
|
|
375
|
+
validate(value, field);
|
|
376
|
+
}
|
|
377
|
+
function strings(value, field) {
|
|
378
|
+
if (!Array.isArray(value))
|
|
379
|
+
throw new Error(`${field} must be an array.`);
|
|
380
|
+
value.forEach((entry, index) => requiredString(entry, `${field}[${index}]`));
|
|
381
|
+
}
|
|
382
|
+
function optionalStrings(value, field) {
|
|
383
|
+
if (value !== undefined)
|
|
384
|
+
strings(value, field);
|
|
385
|
+
}
|
|
386
|
+
function optionalString(value, field) {
|
|
387
|
+
if (value !== undefined)
|
|
388
|
+
requiredString(value, field);
|
|
389
|
+
}
|
|
390
|
+
function optionalTimestamp(value, field) {
|
|
391
|
+
if (value !== undefined)
|
|
392
|
+
isoTimestamp(value, field);
|
|
393
|
+
}
|
|
394
|
+
function optionalNumber(value, field) {
|
|
395
|
+
if (value !== undefined)
|
|
396
|
+
finiteNumber(value, field);
|
|
397
|
+
}
|
|
398
|
+
function nonNegative(value, field) {
|
|
399
|
+
const number = finiteNumber(value, field);
|
|
400
|
+
if (number < 0)
|
|
401
|
+
throw new Error(`${field} must not be negative.`);
|
|
402
|
+
}
|
package/dist/esm/aws/requests.js
CHANGED
|
@@ -3,7 +3,7 @@ export const AWS_COMMAND_SCHEMA_VERSION = 1;
|
|
|
3
3
|
/** Canonical provider wire value for AWS commands. */
|
|
4
4
|
export const AWS_COMMAND_PROVIDER = 'aws';
|
|
5
5
|
/** Entities handled by AWS estate orchestration. */
|
|
6
|
-
export const AWS_COMMAND_ENTITIES = ['estate', 'account', 'billing-source'];
|
|
6
|
+
export const AWS_COMMAND_ENTITIES = ['estate', 'account', 'billing-source', 'organization-commitments'];
|
|
7
7
|
/** Actions supported across AWS estate orchestration commands. */
|
|
8
8
|
export const AWS_COMMAND_ACTIONS = ['reconcile', 'refresh', 'delete'];
|
|
9
9
|
/** Credential-shaped keys forbidden from shared AWS configuration and commands. */
|
|
@@ -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;
|