@spottoai/types-package 1.0.2-beta.395 → 1.0.2-beta.397

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.
@@ -1,6 +1,6 @@
1
1
  import { ENVIRONMENT_ARTIFACT_KINDS_V1, ENVIRONMENT_CONTRACT_LIMITS_V1, ENVIRONMENT_DOCUMENT_NAMES_V1, ENVIRONMENT_EFFORTS_V1, ENVIRONMENT_FINDING_KINDS_V1, ENVIRONMENT_IMPACTS_V1, ENVIRONMENT_PILLARS_V1, ENVIRONMENT_SEVERITIES_V1, } from './contracts.js';
2
2
  import { CURRENCY_PATTERN, DECIMAL_PATTERN, ENVIRONMENT_RUN_ID_PATTERN, SHA256_PATTERN, hasExactKeys, hasSafeContainerShape, isBoundedString, isCanonicalUtcTimestamp, isCustomerString, isNonNegativeInteger, isRecord, isSafeLabel, isScopeIdentifier, isSourceIdentity, utf8ByteLength, } from './internal.js';
3
- import { isEnvironmentLogicalEvidenceReferenceV1, isEnvironmentLogicalResourceReferenceV1 } from './references.js';
3
+ import { deriveEnvironmentAzureResourceTypeV1, isEnvironmentLogicalEvidenceReferenceV1, isEnvironmentLogicalResourceReferenceV1, parseEnvironmentLogicalResourceReferenceV1, } from './references.js';
4
4
  const DOCUMENT_NAMES = new Set(ENVIRONMENT_DOCUMENT_NAMES_V1);
5
5
  const ARTIFACT_KINDS = new Set(ENVIRONMENT_ARTIFACT_KINDS_V1);
6
6
  const PILLARS = new Set(ENVIRONMENT_PILLARS_V1);
@@ -17,6 +17,10 @@ const MONEY_PROVENANCE = new Set([
17
17
  'recommendation',
18
18
  ]);
19
19
  const SAVINGS_ADDITIVITY = new Set(['additive', 'scenario-non-additive']);
20
+ const SPEND_SOURCES = new Set(['billing', 'estimated', 'blended']);
21
+ const SPEND_SOURCE_CONFIDENCES = new Set(['high', 'medium', 'low', 'unknown']);
22
+ const SAVINGS_PROJECTIONS = new Set(['projected-monthly', 'observed-period', 'unknown']);
23
+ const DATE_WINDOW_PATTERN = /^\d{4}-\d{2}-\d{2}\/\d{4}-\d{2}-\d{2}$/u;
20
24
  const CHANGE_DIRECTIONS = new Set(['increase', 'decrease', 'unchanged', 'unknown']);
21
25
  const scopesEqual = (left, right) => left.kind === right.kind && left.tenantId === right.tenantId && left.companyId === right.companyId && left.subscriptionId === right.subscriptionId;
22
26
  /** Validates a bounded, local Portal route suitable for client-visible evidence. */
@@ -45,6 +49,16 @@ const isAzureResourceTypeArray = (value) => Array.isArray(value) &&
45
49
  AZURE_RESOURCE_TYPE_PATTERN.test(item)) &&
46
50
  new Set(value).size === value.length &&
47
51
  value.every((item, index) => index === 0 || value[index - 1] < item);
52
+ const resourceTypesMatchReferences = (resourceTypes, resourceReferences) => {
53
+ const derivedTypes = Array.from(new Set(resourceReferences.flatMap(reference => {
54
+ const parsed = parseEnvironmentLogicalResourceReferenceV1(reference);
55
+ if (parsed === null)
56
+ return [];
57
+ const resourceType = deriveEnvironmentAzureResourceTypeV1(parsed.resourceId);
58
+ return resourceType === null ? [] : [resourceType];
59
+ }))).sort();
60
+ return derivedTypes.length === resourceTypes.length && derivedTypes.every((resourceType, index) => resourceTypes[index] === resourceType);
61
+ };
48
62
  const isPercentage = (value) => typeof value === 'string' && DECIMAL_PATTERN.test(value) && Number(value) <= 100;
49
63
  /** Validates one admitted environment pillar. */
50
64
  export const isEnvironmentPillarV1 = (value) => typeof value === 'string' && PILLARS.has(value);
@@ -105,9 +119,31 @@ export const isEnvironmentRunIdV1 = (value) => typeof value === 'string' &&
105
119
  value !== '.' &&
106
120
  value !== '..' &&
107
121
  ENVIRONMENT_RUN_ID_PATTERN.test(value);
122
+ const isMoneyComposition = (value, currencyCode) => isRecord(value) &&
123
+ hasExactKeys(value, ['billingBacked', 'estimated', 'minorUnitScale', 'currencyCode']) &&
124
+ isNonNegativeInteger(value.billingBacked) &&
125
+ isNonNegativeInteger(value.estimated) &&
126
+ isNonNegativeInteger(value.minorUnitScale) &&
127
+ value.minorUnitScale <= 6 &&
128
+ typeof value.currencyCode === 'string' &&
129
+ (value.currencyCode === 'unknown' || CURRENCY_PATTERN.test(value.currencyCode)) &&
130
+ value.currencyCode === currencyCode;
131
+ /** Validates the producer projection rule and window behind a savings amount. */
132
+ export const isEnvironmentSavingsBasisV1 = (value) => isRecord(value) &&
133
+ hasExactKeys(value, ['projection'], ['observedPeriod', 'stableWindow', 'containsLegacySavings']) &&
134
+ typeof value.projection === 'string' &&
135
+ SAVINGS_PROJECTIONS.has(value.projection) &&
136
+ (value.observedPeriod === undefined || isSafeLabel(value.observedPeriod)) &&
137
+ (value.stableWindow === undefined || (typeof value.stableWindow === 'string' && DATE_WINDOW_PATTERN.test(value.stableWindow))) &&
138
+ (value.containsLegacySavings === undefined || typeof value.containsLegacySavings === 'boolean');
108
139
  /** Validates canonical decimal money with explicit currency, basis, period, and provenance. */
109
140
  export const isEnvironmentMoneyValueV1 = (value) => isRecord(value) &&
110
- hasExactKeys(value, ['amount', 'currencyCode', 'basis', 'period', 'provenance'], ['savingsAdditivity']) &&
141
+ hasExactKeys(value, ['amount', 'currencyCode', 'basis', 'period', 'provenance'], ['savingsAdditivity', 'spendSource', 'spendSourceConfidence', 'composition', 'savingsBasis']) &&
142
+ (value.spendSource === undefined || (typeof value.spendSource === 'string' && SPEND_SOURCES.has(value.spendSource))) &&
143
+ (value.spendSourceConfidence === undefined ||
144
+ (typeof value.spendSourceConfidence === 'string' && SPEND_SOURCE_CONFIDENCES.has(value.spendSourceConfidence))) &&
145
+ (value.composition === undefined || isMoneyComposition(value.composition, value.currencyCode)) &&
146
+ (value.savingsBasis === undefined || isEnvironmentSavingsBasisV1(value.savingsBasis)) &&
111
147
  typeof value.amount === 'string' &&
112
148
  DECIMAL_PATTERN.test(value.amount) &&
113
149
  typeof value.currencyCode === 'string' &&
@@ -118,8 +154,11 @@ export const isEnvironmentMoneyValueV1 = (value) => isRecord(value) &&
118
154
  typeof value.provenance === 'string' &&
119
155
  MONEY_PROVENANCE.has(value.provenance) &&
120
156
  (value.savingsAdditivity === undefined || (typeof value.savingsAdditivity === 'string' && SAVINGS_ADDITIVITY.has(value.savingsAdditivity)));
121
- const isObservedMoney = (value) => isEnvironmentMoneyValueV1(value) && value.savingsAdditivity === undefined;
122
- const isSavingsMoney = (value) => isEnvironmentMoneyValueV1(value) && value.savingsAdditivity !== undefined;
157
+ const isObservedMoney = (value) => isEnvironmentMoneyValueV1(value) && value.savingsAdditivity === undefined && value.savingsBasis === undefined;
158
+ const isSavingsMoney = (value) => isEnvironmentMoneyValueV1(value) &&
159
+ value.savingsAdditivity !== undefined &&
160
+ value.spendSource === undefined &&
161
+ value.composition === undefined;
123
162
  /** Validates the closed coverage-state union and state-specific freshness rules. */
124
163
  export const isEnvironmentCoverageStateV1 = (value) => {
125
164
  if (!isRecord(value) || typeof value.status !== 'string')
@@ -244,6 +283,7 @@ const isRecommendation = (value) => isRecord(value) &&
244
283
  (value.affectedResources === undefined || isEnvironmentCardinalityV1(value.affectedResources)) &&
245
284
  (value.potentialSavings === undefined || isSavingsMoney(value.potentialSavings)) &&
246
285
  isResourceReferenceArray(value.resourceReferences) &&
286
+ resourceTypesMatchReferences(value.affectedResourceTypes, value.resourceReferences) &&
247
287
  isReferenceArray(value.sourceReferences);
248
288
  const isChange = (value) => isRecord(value) &&
249
289
  hasExactKeys(value, ['key', 'pillars', 'safeLabel', 'description', 'direction', 'sourceReferences'], ['amount']) &&
@@ -287,6 +327,127 @@ const isCostSummary = (value) => isRecord(value) &&
287
327
  (value.observedCost === undefined || isObservedMoney(value.observedCost)) &&
288
328
  (value.potentialSavings === undefined || isSavingsMoney(value.potentialSavings)) &&
289
329
  (value.costRecommendationCount === undefined || isNonNegativeInteger(value.costRecommendationCount));
330
+ const SECTION_ITEMS = ENVIRONMENT_CONTRACT_LIMITS_V1.sectionListItems;
331
+ const isSectionList = (value, itemValidator) => isBoundedList(value, itemValidator) && value.items.length <= SECTION_ITEMS;
332
+ const isSectionCount = (value) => value === undefined || isNonNegativeInteger(value);
333
+ const isLabeledCount = (value) => isRecord(value) &&
334
+ hasExactKeys(value, ['key', 'safeLabel', 'count']) &&
335
+ isGeneralKey(value.key) &&
336
+ isSafeLabel(value.safeLabel) &&
337
+ isNonNegativeInteger(value.count);
338
+ const isSubjectReference = (value) => isRecord(value) &&
339
+ hasExactKeys(value, ['key', 'safeLabel'], ['detail', 'resourceReference']) &&
340
+ isGeneralKey(value.key) &&
341
+ isSafeLabel(value.safeLabel) &&
342
+ (value.detail === undefined || isCustomerString(value.detail)) &&
343
+ (value.resourceReference === undefined || isEnvironmentLogicalResourceReferenceV1(value.resourceReference));
344
+ const isCommitmentPurchaseOption = (value) => isRecord(value) &&
345
+ hasExactKeys(value, ['key', 'safeLabel'], ['termMonths', 'estimatedMonthlySavings']) &&
346
+ isGeneralKey(value.key) &&
347
+ isSafeLabel(value.safeLabel) &&
348
+ isSectionCount(value.termMonths) &&
349
+ (value.estimatedMonthlySavings === undefined || isSavingsMoney(value.estimatedMonthlySavings));
350
+ const isCommitmentCoverageRow = (value) => isRecord(value) &&
351
+ hasExactKeys(value, ['key', 'safeLabel', 'benefitLabels'], ['detail', 'resourceReference']) &&
352
+ isGeneralKey(value.key) &&
353
+ isSafeLabel(value.safeLabel) &&
354
+ Array.isArray(value.benefitLabels) &&
355
+ value.benefitLabels.length <= SECTION_ITEMS &&
356
+ value.benefitLabels.every(isSafeLabel) &&
357
+ (value.detail === undefined || isCustomerString(value.detail)) &&
358
+ (value.resourceReference === undefined || isEnvironmentLogicalResourceReferenceV1(value.resourceReference));
359
+ const isCommitmentsSection = (value) => isRecord(value) &&
360
+ hasExactKeys(value, ['coverage', 'purchaseOptions', 'benefitCoverage', 'sourceReferences'], ['benefitCount', 'utilizationPercent30Day', 'utilizationPercent7Day', 'expiredCount', 'expiring90DayCount', 'expiring180DayCount']) &&
361
+ isEnvironmentCoverageStateV1(value.coverage) &&
362
+ isSectionCount(value.benefitCount) &&
363
+ (value.utilizationPercent30Day === undefined || isPercentage(value.utilizationPercent30Day)) &&
364
+ (value.utilizationPercent7Day === undefined || isPercentage(value.utilizationPercent7Day)) &&
365
+ isSectionCount(value.expiredCount) &&
366
+ isSectionCount(value.expiring90DayCount) &&
367
+ isSectionCount(value.expiring180DayCount) &&
368
+ isSectionList(value.purchaseOptions, isCommitmentPurchaseOption) &&
369
+ isSectionList(value.benefitCoverage, isCommitmentCoverageRow) &&
370
+ isReferenceArray(value.sourceReferences);
371
+ const isBudget = (value) => isRecord(value) &&
372
+ hasExactKeys(value, ['key', 'safeLabel'], ['period', 'amount', 'currentSpend', 'forecastSpend', 'consumedPercent']) &&
373
+ isGeneralKey(value.key) &&
374
+ isSafeLabel(value.safeLabel) &&
375
+ (value.period === undefined || isSafeLabel(value.period)) &&
376
+ (value.amount === undefined || isObservedMoney(value.amount)) &&
377
+ (value.currentSpend === undefined || isObservedMoney(value.currentSpend)) &&
378
+ (value.forecastSpend === undefined || isObservedMoney(value.forecastSpend)) &&
379
+ (value.consumedPercent === undefined || (typeof value.consumedPercent === 'string' && DECIMAL_PATTERN.test(value.consumedPercent)));
380
+ const isBudgetSection = (value) => isRecord(value) &&
381
+ hasExactKeys(value, ['coverage', 'budgets', 'sourceReferences']) &&
382
+ isEnvironmentCoverageStateV1(value.coverage) &&
383
+ isSectionList(value.budgets, isBudget) &&
384
+ isReferenceArray(value.sourceReferences);
385
+ const isIdleResourceSection = (value) => isRecord(value) &&
386
+ hasExactKeys(value, ['coverage', 'recommendationCount', 'resourceCount', 'subjects', 'sourceReferences'], ['monthlyWaste']) &&
387
+ isEnvironmentCoverageStateV1(value.coverage) &&
388
+ isNonNegativeInteger(value.recommendationCount) &&
389
+ isNonNegativeInteger(value.resourceCount) &&
390
+ (value.monthlyWaste === undefined || isSavingsMoney(value.monthlyWaste)) &&
391
+ isSectionList(value.subjects, isSubjectReference) &&
392
+ isReferenceArray(value.sourceReferences);
393
+ const isPublicIpExposureSection = (value) => isRecord(value) &&
394
+ hasExactKeys(value, ['coverage', 'totalCount', 'exposedSubjects', 'remediationOptions', 'sourceReferences'], ['assignedCount', 'unassignedCount', 'basicSkuCount', 'httpsExposedCount', 'rdpExposedCount', 'sshExposedCount']) &&
395
+ isEnvironmentCoverageStateV1(value.coverage) &&
396
+ isNonNegativeInteger(value.totalCount) &&
397
+ [value.assignedCount, value.unassignedCount, value.basicSkuCount, value.httpsExposedCount, value.rdpExposedCount, value.sshExposedCount].every(isSectionCount) &&
398
+ isSectionList(value.exposedSubjects, isSubjectReference) &&
399
+ isSectionList(value.remediationOptions, isLabeledCount) &&
400
+ isReferenceArray(value.sourceReferences);
401
+ const isSecretStoreCoverageSection = (value) => isRecord(value) &&
402
+ hasExactKeys(value, ['coverage', 'vaultCount', 'inspectedVaultCount', 'reasons', 'sourceReferences']) &&
403
+ isEnvironmentCoverageStateV1(value.coverage) &&
404
+ isNonNegativeInteger(value.vaultCount) &&
405
+ isNonNegativeInteger(value.inspectedVaultCount) &&
406
+ isSectionList(value.reasons, isLabeledCount) &&
407
+ isReferenceArray(value.sourceReferences);
408
+ const isTagCoverageSection = (value) => isRecord(value) &&
409
+ hasExactKeys(value, [
410
+ 'coverage',
411
+ 'resourceCount',
412
+ 'taggedResourceCount',
413
+ 'untaggedResourceCount',
414
+ 'distinctTagKeyCount',
415
+ 'topTagKeys',
416
+ 'sourceReferences',
417
+ ]) &&
418
+ isEnvironmentCoverageStateV1(value.coverage) &&
419
+ isNonNegativeInteger(value.resourceCount) &&
420
+ isNonNegativeInteger(value.taggedResourceCount) &&
421
+ isNonNegativeInteger(value.untaggedResourceCount) &&
422
+ isNonNegativeInteger(value.distinctTagKeyCount) &&
423
+ value.taggedResourceCount + value.untaggedResourceCount === value.resourceCount &&
424
+ isSectionList(value.topTagKeys, isLabeledCount) &&
425
+ isReferenceArray(value.sourceReferences);
426
+ const isChangeSignalSection = (value) => isRecord(value) &&
427
+ hasExactKeys(value, ['coverage', 'windowDays', 'changeCount', 'topOperations', 'sourceReferences'], ['materialChangeCount', 'securityRelevantCount']) &&
428
+ isEnvironmentCoverageStateV1(value.coverage) &&
429
+ isNonNegativeInteger(value.windowDays) &&
430
+ isNonNegativeInteger(value.changeCount) &&
431
+ isSectionCount(value.materialChangeCount) &&
432
+ isSectionCount(value.securityRelevantCount) &&
433
+ isSectionList(value.topOperations, isLabeledCount) &&
434
+ isReferenceArray(value.sourceReferences);
435
+ const isHealthEvent = (value) => isRecord(value) &&
436
+ hasExactKeys(value, ['key', 'safeLabel', 'severity'], ['eventType', 'startedAt']) &&
437
+ isGeneralKey(value.key) &&
438
+ isSafeLabel(value.safeLabel) &&
439
+ typeof value.severity === 'string' &&
440
+ SEVERITIES.has(value.severity) &&
441
+ (value.eventType === undefined || isSafeLabel(value.eventType)) &&
442
+ (value.startedAt === undefined || isCanonicalUtcTimestamp(value.startedAt));
443
+ const isHealthEventSection = (value) => isRecord(value) &&
444
+ hasExactKeys(value, ['coverage', 'totalCount', 'activeCount', 'resolvedCount', 'activeEvents', 'sourceReferences']) &&
445
+ isEnvironmentCoverageStateV1(value.coverage) &&
446
+ isNonNegativeInteger(value.totalCount) &&
447
+ isNonNegativeInteger(value.activeCount) &&
448
+ isNonNegativeInteger(value.resolvedCount) &&
449
+ isSectionList(value.activeEvents, isHealthEvent) &&
450
+ isReferenceArray(value.sourceReferences);
290
451
  /** Validates the strict, bounded multi-pillar subscription environment projection. */
291
452
  export const isEnvironmentSubscriptionProjectionV1 = (value) => {
292
453
  if (!hasSafeContainerShape(value) || !isRecord(value))
@@ -309,7 +470,7 @@ export const isEnvironmentSubscriptionProjectionV1 = (value) => {
309
470
  'changes',
310
471
  'warnings',
311
472
  'sourceReferences',
312
- ]) ||
473
+ ], ['commitments', 'budgets', 'idleResources', 'publicIpExposure', 'secretStoreCoverage', 'tagCoverage', 'changeSignals', 'healthEvents']) ||
313
474
  value.schemaVersion !== 1 ||
314
475
  !isEnvironmentScopeV1(value.scope) ||
315
476
  !isEnvironmentSourceBindingV1(value.sourceBinding) ||
@@ -331,6 +492,14 @@ export const isEnvironmentSubscriptionProjectionV1 = (value) => {
331
492
  !isBoundedList(value.recommendations, isRecommendation) ||
332
493
  !isBoundedList(value.changes, isChange) ||
333
494
  !isBoundedList(value.warnings, isWarning) ||
495
+ (value.commitments !== undefined && !isCommitmentsSection(value.commitments)) ||
496
+ (value.budgets !== undefined && !isBudgetSection(value.budgets)) ||
497
+ (value.idleResources !== undefined && !isIdleResourceSection(value.idleResources)) ||
498
+ (value.publicIpExposure !== undefined && !isPublicIpExposureSection(value.publicIpExposure)) ||
499
+ (value.secretStoreCoverage !== undefined && !isSecretStoreCoverageSection(value.secretStoreCoverage)) ||
500
+ (value.tagCoverage !== undefined && !isTagCoverageSection(value.tagCoverage)) ||
501
+ (value.changeSignals !== undefined && !isChangeSignalSection(value.changeSignals)) ||
502
+ (value.healthEvents !== undefined && !isHealthEventSection(value.healthEvents)) ||
334
503
  !isReferenceArray(value.sourceReferences)) {
335
504
  return false;
336
505
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spottoai/types-package",
3
- "version": "1.0.2-beta.395",
3
+ "version": "1.0.2-beta.397",
4
4
  "description": "Shared TypeScript interfaces for SpottoAI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",