@spottoai/types-package 1.0.2-dev1138.1 → 1.0.2-dev1138.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/azure/financialAnalytics.d.ts +80 -8
  2. package/dist/azure/financialAnalytics.d.ts.map +1 -1
  3. package/dist/azure/financialAnalyticsDelivery.d.ts +72 -3
  4. package/dist/azure/financialAnalyticsDelivery.d.ts.map +1 -1
  5. package/dist/azure/financialAnalyticsDelivery.js +7 -1
  6. package/dist/azure/financialAnalyticsDelivery.js.map +1 -1
  7. package/dist/azure/financialAnalyticsDeliveryValidation.d.ts +9 -1
  8. package/dist/azure/financialAnalyticsDeliveryValidation.d.ts.map +1 -1
  9. package/dist/azure/financialAnalyticsDeliveryValidation.js +190 -10
  10. package/dist/azure/financialAnalyticsDeliveryValidation.js.map +1 -1
  11. package/dist/azure/financialAnalyticsDisplayKernel.d.ts +12 -0
  12. package/dist/azure/financialAnalyticsDisplayKernel.d.ts.map +1 -0
  13. package/dist/azure/financialAnalyticsDisplayKernel.js +144 -0
  14. package/dist/azure/financialAnalyticsDisplayKernel.js.map +1 -0
  15. package/dist/azure/financialAnalyticsValidation.d.ts.map +1 -1
  16. package/dist/azure/financialAnalyticsValidation.js +101 -19
  17. package/dist/azure/financialAnalyticsValidation.js.map +1 -1
  18. package/dist/azure/financialAuthorityView.d.ts +4 -0
  19. package/dist/azure/financialAuthorityView.d.ts.map +1 -1
  20. package/dist/azure/financialAuthorityView.js.map +1 -1
  21. package/dist/azure/financialAuthorityViewValidation.d.ts.map +1 -1
  22. package/dist/azure/financialAuthorityViewValidation.js +39 -0
  23. package/dist/azure/financialAuthorityViewValidation.js.map +1 -1
  24. package/dist/azure/financialChargeComposition.d.ts +81 -0
  25. package/dist/azure/financialChargeComposition.d.ts.map +1 -0
  26. package/dist/azure/financialChargeComposition.js +41 -0
  27. package/dist/azure/financialChargeComposition.js.map +1 -0
  28. package/dist/azure/financialChargeCompositionKernel.d.ts +3 -0
  29. package/dist/azure/financialChargeCompositionKernel.d.ts.map +1 -0
  30. package/dist/azure/financialChargeCompositionKernel.js +49 -0
  31. package/dist/azure/financialChargeCompositionKernel.js.map +1 -0
  32. package/dist/azure/financialChargeCompositionValidation.d.ts +8 -0
  33. package/dist/azure/financialChargeCompositionValidation.d.ts.map +1 -0
  34. package/dist/azure/financialChargeCompositionValidation.js +164 -0
  35. package/dist/azure/financialChargeCompositionValidation.js.map +1 -0
  36. package/dist/azure/financialDataflow.d.ts +45 -2
  37. package/dist/azure/financialDataflow.d.ts.map +1 -1
  38. package/dist/azure/financialDataflow.js.map +1 -1
  39. package/dist/azure/financialDataflowKernel.d.ts +31 -0
  40. package/dist/azure/financialDataflowKernel.d.ts.map +1 -0
  41. package/dist/azure/financialDataflowKernel.js +169 -0
  42. package/dist/azure/financialDataflowKernel.js.map +1 -0
  43. package/dist/azure/financialDataflowValidation.d.ts +5 -2
  44. package/dist/azure/financialDataflowValidation.d.ts.map +1 -1
  45. package/dist/azure/financialDataflowValidation.js +203 -30
  46. package/dist/azure/financialDataflowValidation.js.map +1 -1
  47. package/dist/azure/financialEvidenceAssessmentValidation.d.ts.map +1 -1
  48. package/dist/azure/financialEvidenceAssessmentValidation.js +7 -0
  49. package/dist/azure/financialEvidenceAssessmentValidation.js.map +1 -1
  50. package/dist/azure/financialPolicy.d.ts +19 -3
  51. package/dist/azure/financialPolicy.d.ts.map +1 -1
  52. package/dist/azure/financialPolicy.js.map +1 -1
  53. package/dist/azure/financialPolicyDelivery.d.ts +75 -1
  54. package/dist/azure/financialPolicyDelivery.d.ts.map +1 -1
  55. package/dist/azure/financialPolicyDelivery.js +5 -1
  56. package/dist/azure/financialPolicyDelivery.js.map +1 -1
  57. package/dist/azure/financialPolicyDeliveryValidation.d.ts +16 -2
  58. package/dist/azure/financialPolicyDeliveryValidation.d.ts.map +1 -1
  59. package/dist/azure/financialPolicyDeliveryValidation.js +238 -5
  60. package/dist/azure/financialPolicyDeliveryValidation.js.map +1 -1
  61. package/dist/azure/financialPolicyKernel.d.ts +38 -0
  62. package/dist/azure/financialPolicyKernel.d.ts.map +1 -0
  63. package/dist/azure/financialPolicyKernel.js +232 -0
  64. package/dist/azure/financialPolicyKernel.js.map +1 -0
  65. package/dist/azure/financialPolicyValidation.d.ts +1 -0
  66. package/dist/azure/financialPolicyValidation.d.ts.map +1 -1
  67. package/dist/azure/financialPolicyValidation.js +111 -16
  68. package/dist/azure/financialPolicyValidation.js.map +1 -1
  69. package/dist/azure/financialSavingsAuthority.d.ts +29 -0
  70. package/dist/azure/financialSavingsAuthority.d.ts.map +1 -1
  71. package/dist/azure/financialSavingsAuthority.js +2 -1
  72. package/dist/azure/financialSavingsAuthority.js.map +1 -1
  73. package/dist/azure/financialSavingsAuthorityValidation.d.ts.map +1 -1
  74. package/dist/azure/financialSavingsAuthorityValidation.js +3 -0
  75. package/dist/azure/financialSavingsAuthorityValidation.js.map +1 -1
  76. package/dist/azure/financialSavingsCoordinateValidation.d.ts.map +1 -1
  77. package/dist/azure/financialSavingsCoordinateValidation.js +36 -0
  78. package/dist/azure/financialSavingsCoordinateValidation.js.map +1 -1
  79. package/dist/azure/financialScope.d.ts +6 -0
  80. package/dist/azure/financialScope.d.ts.map +1 -1
  81. package/dist/azure/financialScope.js +6 -0
  82. package/dist/azure/financialScope.js.map +1 -1
  83. package/dist/azure/financialScopeBaseline.d.ts +8 -2
  84. package/dist/azure/financialScopeBaseline.d.ts.map +1 -1
  85. package/dist/azure/financialScopeBaseline.js +14 -1
  86. package/dist/azure/financialScopeBaseline.js.map +1 -1
  87. package/dist/azure/financialScopeBaselineValidation.d.ts.map +1 -1
  88. package/dist/azure/financialScopeBaselineValidation.js +15 -1
  89. package/dist/azure/financialScopeBaselineValidation.js.map +1 -1
  90. package/dist/azure/financialScopeEvidence.d.ts +1 -1
  91. package/dist/azure/financialScopeEvidence.d.ts.map +1 -1
  92. package/dist/azure/metrics.d.ts +2 -2
  93. package/dist/azure/metrics.d.ts.map +1 -1
  94. package/dist/azure/prices.d.ts +16 -3
  95. package/dist/azure/prices.d.ts.map +1 -1
  96. package/dist/azure/recommendations.d.ts +12 -4
  97. package/dist/azure/recommendations.d.ts.map +1 -1
  98. package/dist/azure/reports.d.ts +3 -2
  99. package/dist/azure/reports.d.ts.map +1 -1
  100. package/dist/azure/reports.js.map +1 -1
  101. package/dist/azure/resources.d.ts +12 -0
  102. package/dist/azure/resources.d.ts.map +1 -1
  103. package/dist/azure/subscriptions.d.ts +4 -2
  104. package/dist/azure/subscriptions.d.ts.map +1 -1
  105. package/dist/azure/views.d.ts +21 -5
  106. package/dist/azure/views.d.ts.map +1 -1
  107. package/dist/azure/views.js.map +1 -1
  108. package/dist/esm/azure/financialAnalyticsDelivery.js +5 -0
  109. package/dist/esm/azure/financialAnalyticsDeliveryValidation.js +184 -11
  110. package/dist/esm/azure/financialAnalyticsDisplayKernel.js +139 -0
  111. package/dist/esm/azure/financialAnalyticsValidation.js +101 -19
  112. package/dist/esm/azure/financialAuthorityViewValidation.js +39 -0
  113. package/dist/esm/azure/financialChargeComposition.js +36 -0
  114. package/dist/esm/azure/financialChargeCompositionKernel.js +44 -0
  115. package/dist/esm/azure/financialChargeCompositionValidation.js +155 -0
  116. package/dist/esm/azure/financialDataflowKernel.js +163 -0
  117. package/dist/esm/azure/financialDataflowValidation.js +199 -29
  118. package/dist/esm/azure/financialEvidenceAssessmentValidation.js +7 -0
  119. package/dist/esm/azure/financialPolicyDelivery.js +4 -0
  120. package/dist/esm/azure/financialPolicyDeliveryValidation.js +227 -7
  121. package/dist/esm/azure/financialPolicyKernel.js +225 -0
  122. package/dist/esm/azure/financialPolicyValidation.js +110 -16
  123. package/dist/esm/azure/financialSavingsAuthority.js +1 -0
  124. package/dist/esm/azure/financialSavingsAuthorityValidation.js +3 -0
  125. package/dist/esm/azure/financialSavingsCoordinateValidation.js +36 -0
  126. package/dist/esm/azure/financialScope.js +6 -0
  127. package/dist/esm/azure/financialScopeBaseline.js +12 -0
  128. package/dist/esm/azure/financialScopeBaselineValidation.js +15 -1
  129. package/dist/events/cost.d.ts +4 -0
  130. package/dist/events/cost.d.ts.map +1 -1
  131. package/package.json +3 -2
@@ -1,12 +1,52 @@
1
1
  import { sha256Utf8 } from '../common/sha256.js';
2
2
  import { parseCanonicalDecimal } from '../common/exactDecimal.js';
3
3
  import { isCanonicalExactMoney } from './financialValidationPrimitives.js';
4
- import { FINANCIAL_POLICY_ACTION_ATTEMPT_CONTRACT_VERSION_V1, FINANCIAL_POLICY_READ_PROJECTION_CONTRACT_VERSION_V1, } from './financialPolicyDelivery.js';
5
- import { canonicalizeFinancialDataflowJsonV1, hasFinancialDataflowExactFieldsV1, isFinancialDataflowHashV1, isFinancialDataflowIdentityV1, isFinancialDataflowIsoInstantV1, isFinancialDataflowRecordV1, } from './financialDataflowValidation.js';
6
- import { createFinancialPolicyEvaluationActionAuditIdV1, createFinancialPolicyEvaluationReadProjectionIdV1, isFinancialPolicyDefinitionRevisionV1, isFinancialPolicyEvaluationV1, } from './financialPolicyValidation.js';
4
+ import { isFinancialAnalyticsProjectionV1 } from './financialAnalyticsValidation.js';
5
+ import { FINANCIAL_POLICY_ACTION_ATTEMPT_CONTRACT_VERSION_V1, FINANCIAL_POLICY_ACTION_COMPLETION_CONTRACT_VERSION_V1, FINANCIAL_POLICY_DEFINITION_COMMAND_CONTRACT_VERSION_V1, FINANCIAL_POLICY_READ_CURRENT_POINTER_CONTRACT_VERSION_V1, FINANCIAL_POLICY_READ_MANIFEST_CONTRACT_VERSION_V1, FINANCIAL_POLICY_READ_PROJECTION_CONTRACT_VERSION_V1, } from './financialPolicyDelivery.js';
6
+ import { canonicalizeFinancialDataflowJsonV1, hasFinancialDataflowExactFieldsV1, isFinancialDataflowHashV1, isFinancialDataflowIdentityV1, isFinancialDataflowIsoInstantV1, isFinancialDataflowJsonGzipArtifactDescriptorV1, isFinancialDataflowRecordV1, } from './financialDataflowValidation.js';
7
+ import { createFinancialPolicyDefinitionRevisionIdV1, createFinancialPolicyEvaluationActionAuditIdV1, createFinancialPolicyEvaluationReadProjectionIdV1, isFinancialPolicyDefinitionRevisionIdentityPreimageV1, isFinancialPolicyDefinitionRevisionV1, isFinancialPolicyEvaluationV1, } from './financialPolicyValidation.js';
7
8
  const SIGNAL_KINDS = new Set(['budget-current-spend', 'budget-forecast', 'cost-anomaly']);
8
9
  const RESULTS = new Set(['matched', 'not-matched', 'partial', 'unavailable']);
9
10
  const ACTION_STATUSES = new Set(['succeeded', 'retryable-failure', 'terminal-failure']);
11
+ const isCanonicalPolicyRevision = (value) => {
12
+ if (typeof value !== 'string' || !/^[1-9]\d*$/.test(value))
13
+ return false;
14
+ const parsed = Number(value);
15
+ return Number.isSafeInteger(parsed) && String(parsed) === value;
16
+ };
17
+ export const createFinancialPolicyDestinationsDigestV1 = (destinationRefIds) => `sha256:${sha256Utf8(canonicalizeFinancialDataflowJsonV1([...destinationRefIds].sort()))}`;
18
+ export const isFinancialPolicyDefinitionCommandV1 = (value) => {
19
+ if (!isFinancialDataflowRecordV1(value) ||
20
+ !hasFinancialDataflowExactFieldsV1(value, ['schemaVersion', 'contractVersion', 'displayName', 'effectiveState', 'coordinateRequest', 'criteria', 'schedule', 'destinationRefIds'], ['expectedRevision']) ||
21
+ value.schemaVersion !== 1 ||
22
+ value.contractVersion !== FINANCIAL_POLICY_DEFINITION_COMMAND_CONTRACT_VERSION_V1 ||
23
+ (value.expectedRevision !== undefined && !isCanonicalPolicyRevision(value.expectedRevision)) ||
24
+ !isFinancialDataflowIdentityV1(value.displayName) ||
25
+ (value.effectiveState !== 'enabled' && value.effectiveState !== 'disabled') ||
26
+ !Array.isArray(value.destinationRefIds)) {
27
+ return false;
28
+ }
29
+ const identity = {
30
+ schemaVersion: 1,
31
+ contractVersion: 'financial-policy-definition/v1',
32
+ companyId: 'contract-company',
33
+ definitionId: 'contract-definition',
34
+ displayName: value.displayName,
35
+ revision: '1',
36
+ effectiveState: value.effectiveState,
37
+ coordinateRequest: value.coordinateRequest,
38
+ criteria: value.criteria,
39
+ schedule: value.schedule,
40
+ destinationRefIds: value.destinationRefIds,
41
+ destinationsDigest: createFinancialPolicyDestinationsDigestV1(value.destinationRefIds),
42
+ authoredAt: '2026-01-01T00:00:00.000Z',
43
+ authoredByUserId: 'contract-author',
44
+ };
45
+ if (!isFinancialPolicyDefinitionRevisionIdentityPreimageV1(identity))
46
+ return false;
47
+ const definition = { ...identity, policyDefinitionRevisionId: createFinancialPolicyDefinitionRevisionIdV1(identity) };
48
+ return isFinancialPolicyDefinitionRevisionV1(definition);
49
+ };
10
50
  const isNonNegativeDecimal = (value) => {
11
51
  if (typeof value !== 'string' || !isCanonicalExactMoney({ amount: value, currencyCode: 'AUD' }))
12
52
  return false;
@@ -33,6 +73,7 @@ export const isFinancialPolicyEvaluationReadProjectionV1 = (value) => isFinancia
33
73
  'companyId',
34
74
  'definitionId',
35
75
  'definitionRevision',
76
+ 'policyDefinitionRevisionId',
36
77
  'coordinateId',
37
78
  'currentSpendCompositionId',
38
79
  'signalKind',
@@ -40,7 +81,7 @@ export const isFinancialPolicyEvaluationReadProjectionV1 = (value) => isFinancia
40
81
  'result',
41
82
  'reasonCode',
42
83
  'matchedThresholds',
43
- ], ['analyticsProjectionId']) &&
84
+ ], ['analyticsProjectionId', 'analyticsProjection']) &&
44
85
  value.schemaVersion === 1 &&
45
86
  value.contractVersion === FINANCIAL_POLICY_READ_PROJECTION_CONTRACT_VERSION_V1 &&
46
87
  isFinancialDataflowHashV1(value.evaluationId) &&
@@ -48,12 +89,24 @@ export const isFinancialPolicyEvaluationReadProjectionV1 = (value) => isFinancia
48
89
  isFinancialDataflowIdentityV1(value.companyId) &&
49
90
  isFinancialDataflowIdentityV1(value.definitionId) &&
50
91
  isFinancialDataflowIdentityV1(value.definitionRevision) &&
92
+ isFinancialDataflowHashV1(value.policyDefinitionRevisionId) &&
51
93
  isFinancialDataflowHashV1(value.coordinateId) &&
52
94
  isFinancialDataflowHashV1(value.currentSpendCompositionId) &&
53
95
  (value.analyticsProjectionId === undefined || isFinancialDataflowHashV1(value.analyticsProjectionId)) &&
96
+ (value.analyticsProjection === undefined || isFinancialAnalyticsProjectionV1(value.analyticsProjection)) &&
97
+ (value.analyticsProjectionId === undefined
98
+ ? value.analyticsProjection === undefined
99
+ : value.analyticsProjection?.analyticsProjectionId === value.analyticsProjectionId &&
100
+ value.analyticsProjection.currentSpendCompositionId === value.currentSpendCompositionId &&
101
+ value.analyticsProjection.coordinate.companyId === value.companyId &&
102
+ value.analyticsProjection.coordinate.periodRole === 'projection-target') &&
54
103
  typeof value.signalKind === 'string' &&
55
104
  SIGNAL_KINDS.has(value.signalKind) &&
56
- (value.signalKind === 'budget-current-spend' ? value.analyticsProjectionId === undefined : value.analyticsProjectionId !== undefined) &&
105
+ (value.signalKind === 'budget-current-spend'
106
+ ? value.analyticsProjectionId === undefined
107
+ : value.result === 'unavailable'
108
+ ? value.analyticsProjectionId === undefined || isFinancialDataflowHashV1(value.analyticsProjectionId)
109
+ : value.analyticsProjectionId !== undefined) &&
57
110
  isFinancialDataflowIsoInstantV1(value.evaluatedAt) &&
58
111
  typeof value.result === 'string' &&
59
112
  RESULTS.has(value.result) &&
@@ -61,7 +114,7 @@ export const isFinancialPolicyEvaluationReadProjectionV1 = (value) => isFinancia
61
114
  isMatchedThresholds(value.matchedThresholds) &&
62
115
  Array.isArray(value.matchedThresholds) &&
63
116
  (value.result === 'matched' ? value.matchedThresholds.length > 0 : value.matchedThresholds.length === 0);
64
- export const isFinancialPolicyEvaluationReadProjectionCompatibleV1 = (projection, evaluation) => {
117
+ export const isFinancialPolicyEvaluationReadProjectionCompatibleV1 = (projection, evaluation, analyticsProjection) => {
65
118
  if (!isFinancialPolicyEvaluationReadProjectionV1(projection) || !isFinancialPolicyEvaluationV1(evaluation))
66
119
  return false;
67
120
  const expected = {
@@ -72,17 +125,138 @@ export const isFinancialPolicyEvaluationReadProjectionCompatibleV1 = (projection
72
125
  companyId: evaluation.companyId,
73
126
  definitionId: evaluation.definitionId,
74
127
  definitionRevision: evaluation.definitionRevision,
128
+ policyDefinitionRevisionId: evaluation.policyDefinitionRevisionId,
75
129
  coordinateId: evaluation.coordinateId,
76
130
  currentSpendCompositionId: evaluation.currentSpendCompositionId,
77
131
  ...(evaluation.analyticsProjectionId === undefined ? {} : { analyticsProjectionId: evaluation.analyticsProjectionId }),
132
+ ...(analyticsProjection === undefined ? {} : { analyticsProjection }),
78
133
  signalKind: evaluation.signalKind,
79
134
  evaluatedAt: evaluation.evaluatedAt,
80
135
  result: evaluation.result,
81
136
  reasonCode: evaluation.reasonCode,
82
137
  matchedThresholds: evaluation.matchedThresholds,
83
138
  };
84
- return canonicalizeFinancialDataflowJsonV1(projection) === canonicalizeFinancialDataflowJsonV1(expected);
139
+ return ((evaluation.analyticsProjectionId === undefined
140
+ ? analyticsProjection === undefined
141
+ : analyticsProjection?.analyticsProjectionId === evaluation.analyticsProjectionId) &&
142
+ canonicalizeFinancialDataflowJsonV1(projection) === canonicalizeFinancialDataflowJsonV1(expected));
143
+ };
144
+ export const canonicalizeFinancialPolicyEvaluationReadCurrentPointerIdentityV1 = (value) => {
145
+ if (!isFinancialDataflowRecordV1(value) ||
146
+ !hasFinancialDataflowExactFieldsV1(value, [
147
+ 'schemaVersion',
148
+ 'contractVersion',
149
+ 'companyId',
150
+ 'definitionId',
151
+ 'signalKind',
152
+ 'pointerRevision',
153
+ 'definitionRevision',
154
+ 'policyDefinitionRevisionId',
155
+ 'evaluationId',
156
+ 'readProjectionId',
157
+ 'projectionArtifactDigest',
158
+ 'evaluatedAt',
159
+ 'promotedAt',
160
+ ]) ||
161
+ value.schemaVersion !== 1 ||
162
+ value.contractVersion !== FINANCIAL_POLICY_READ_CURRENT_POINTER_CONTRACT_VERSION_V1 ||
163
+ !isFinancialDataflowIdentityV1(value.companyId) ||
164
+ !isFinancialDataflowIdentityV1(value.definitionId) ||
165
+ typeof value.signalKind !== 'string' ||
166
+ !SIGNAL_KINDS.has(value.signalKind) ||
167
+ !Number.isSafeInteger(value.pointerRevision) ||
168
+ value.pointerRevision < 1 ||
169
+ !isFinancialDataflowIdentityV1(value.definitionRevision) ||
170
+ !isFinancialDataflowHashV1(value.policyDefinitionRevisionId) ||
171
+ !isFinancialDataflowHashV1(value.evaluationId) ||
172
+ value.readProjectionId !== createFinancialPolicyEvaluationReadProjectionIdV1(value.evaluationId) ||
173
+ !isFinancialDataflowHashV1(value.projectionArtifactDigest) ||
174
+ !isFinancialDataflowIsoInstantV1(value.evaluatedAt) ||
175
+ !isFinancialDataflowIsoInstantV1(value.promotedAt)) {
176
+ throw new TypeError('Invalid FinancialPolicyEvaluationReadCurrentPointerIdentityPreimageV1.');
177
+ }
178
+ return canonicalizeFinancialDataflowJsonV1(value);
179
+ };
180
+ export const createFinancialPolicyEvaluationReadCurrentPointerDigestV1 = (value) => `sha256:${sha256Utf8(canonicalizeFinancialPolicyEvaluationReadCurrentPointerIdentityV1(value))}`;
181
+ export const isFinancialPolicyEvaluationReadCurrentPointerV1 = (value) => {
182
+ if (!isFinancialDataflowRecordV1(value) || !Object.prototype.hasOwnProperty.call(value, 'pointerDigest'))
183
+ return false;
184
+ const { pointerDigest, ...identity } = value;
185
+ try {
186
+ return (isFinancialDataflowHashV1(pointerDigest) &&
187
+ pointerDigest === createFinancialPolicyEvaluationReadCurrentPointerDigestV1(identity));
188
+ }
189
+ catch {
190
+ return false;
191
+ }
192
+ };
193
+ export const isFinancialPolicyEvaluationReadCurrentPointerCompatibleV1 = (pointer, projection, projectionArtifactDigest) => isFinancialPolicyEvaluationReadCurrentPointerV1(pointer) &&
194
+ isFinancialPolicyEvaluationReadProjectionV1(projection) &&
195
+ isFinancialDataflowHashV1(projectionArtifactDigest) &&
196
+ pointer.companyId === projection.companyId &&
197
+ pointer.definitionId === projection.definitionId &&
198
+ pointer.signalKind === projection.signalKind &&
199
+ pointer.definitionRevision === projection.definitionRevision &&
200
+ pointer.policyDefinitionRevisionId === projection.policyDefinitionRevisionId &&
201
+ pointer.evaluationId === projection.evaluationId &&
202
+ pointer.readProjectionId === projection.readProjectionId &&
203
+ pointer.projectionArtifactDigest === projectionArtifactDigest &&
204
+ pointer.evaluatedAt === projection.evaluatedAt;
205
+ const isReadManifestIdentity = (value) => isFinancialDataflowRecordV1(value) &&
206
+ hasFinancialDataflowExactFieldsV1(value, [
207
+ 'schemaVersion',
208
+ 'contractVersion',
209
+ 'companyId',
210
+ 'definitionId',
211
+ 'policyDefinitionRevisionId',
212
+ 'definitionRevision',
213
+ 'evaluationId',
214
+ 'readProjectionId',
215
+ 'signalKind',
216
+ 'projection',
217
+ 'publishedAt',
218
+ ]) &&
219
+ value.schemaVersion === 1 &&
220
+ value.contractVersion === FINANCIAL_POLICY_READ_MANIFEST_CONTRACT_VERSION_V1 &&
221
+ isFinancialDataflowIdentityV1(value.companyId) &&
222
+ isFinancialDataflowIdentityV1(value.definitionId) &&
223
+ isFinancialDataflowHashV1(value.policyDefinitionRevisionId) &&
224
+ isFinancialDataflowIdentityV1(value.definitionRevision) &&
225
+ isFinancialDataflowHashV1(value.evaluationId) &&
226
+ value.readProjectionId === createFinancialPolicyEvaluationReadProjectionIdV1(value.evaluationId) &&
227
+ typeof value.signalKind === 'string' &&
228
+ SIGNAL_KINDS.has(value.signalKind) &&
229
+ isFinancialDataflowJsonGzipArtifactDescriptorV1(value.projection) &&
230
+ isFinancialDataflowIsoInstantV1(value.publishedAt);
231
+ export const createFinancialPolicyEvaluationReadManifestDigestV1 = (value) => {
232
+ if (!isReadManifestIdentity(value))
233
+ throw new TypeError('Invalid FinancialPolicyEvaluationReadManifestIdentityPreimageV1.');
234
+ return `sha256:${sha256Utf8(canonicalizeFinancialDataflowJsonV1(value))}`;
235
+ };
236
+ export const isFinancialPolicyEvaluationReadManifestV1 = (value) => {
237
+ if (!isFinancialDataflowRecordV1(value) || !Object.prototype.hasOwnProperty.call(value, 'manifestDigest'))
238
+ return false;
239
+ const { manifestDigest, ...identity } = value;
240
+ try {
241
+ return (isFinancialDataflowHashV1(manifestDigest) &&
242
+ manifestDigest === createFinancialPolicyEvaluationReadManifestDigestV1(identity));
243
+ }
244
+ catch {
245
+ return false;
246
+ }
85
247
  };
248
+ export const isFinancialPolicyEvaluationReadManifestCompatibleV1 = (manifest, projection, verifiedProjectionArtifactDigest) => isFinancialPolicyEvaluationReadManifestV1(manifest) &&
249
+ isFinancialPolicyEvaluationReadProjectionV1(projection) &&
250
+ isFinancialDataflowHashV1(verifiedProjectionArtifactDigest) &&
251
+ manifest.companyId === projection.companyId &&
252
+ manifest.definitionId === projection.definitionId &&
253
+ manifest.policyDefinitionRevisionId === projection.policyDefinitionRevisionId &&
254
+ manifest.definitionRevision === projection.definitionRevision &&
255
+ manifest.evaluationId === projection.evaluationId &&
256
+ manifest.readProjectionId === projection.readProjectionId &&
257
+ manifest.signalKind === projection.signalKind &&
258
+ manifest.publishedAt === projection.evaluatedAt &&
259
+ manifest.projection.sha256 === verifiedProjectionArtifactDigest;
86
260
  const isActionAttemptIdentity = (value) => isFinancialDataflowRecordV1(value) &&
87
261
  hasFinancialDataflowExactFieldsV1(value, [
88
262
  'schemaVersion',
@@ -135,3 +309,49 @@ export const isFinancialPolicyActionAttemptCompatibleV1 = (attempt, evaluation,
135
309
  Date.parse(attempt.attemptedAt) >= Date.parse(evaluation.evaluatedAt) &&
136
310
  evaluation.policyDefinitionRevisionId === definition.policyDefinitionRevisionId &&
137
311
  definition.destinationRefIds.includes(attempt.destinationRefId);
312
+ export const canonicalizeFinancialPolicyActionCompletionIdentityV1 = (value) => {
313
+ if (!isFinancialDataflowRecordV1(value) ||
314
+ !hasFinancialDataflowExactFieldsV1(value, [
315
+ 'schemaVersion',
316
+ 'contractVersion',
317
+ 'actionAuditId',
318
+ 'evaluationId',
319
+ 'companyId',
320
+ 'destinationRefId',
321
+ 'completedAt',
322
+ 'executorVersion',
323
+ ]) ||
324
+ value.schemaVersion !== 1 ||
325
+ value.contractVersion !== FINANCIAL_POLICY_ACTION_COMPLETION_CONTRACT_VERSION_V1 ||
326
+ !isFinancialDataflowHashV1(value.actionAuditId) ||
327
+ !isFinancialDataflowHashV1(value.evaluationId) ||
328
+ !isFinancialDataflowIdentityV1(value.companyId) ||
329
+ !isFinancialDataflowIdentityV1(value.destinationRefId) ||
330
+ !isFinancialDataflowIsoInstantV1(value.completedAt) ||
331
+ !isFinancialDataflowIdentityV1(value.executorVersion)) {
332
+ throw new TypeError('Invalid FinancialPolicyActionCompletionIdentityPreimageV1.');
333
+ }
334
+ return canonicalizeFinancialDataflowJsonV1(value);
335
+ };
336
+ export const createFinancialPolicyActionCompletionIdV1 = (value) => `sha256:${sha256Utf8(canonicalizeFinancialPolicyActionCompletionIdentityV1(value))}`;
337
+ export const isFinancialPolicyActionCompletionV1 = (value) => {
338
+ if (!isFinancialDataflowRecordV1(value) || !Object.prototype.hasOwnProperty.call(value, 'completionId'))
339
+ return false;
340
+ const { completionId, ...identity } = value;
341
+ try {
342
+ return (isFinancialDataflowHashV1(completionId) &&
343
+ completionId === createFinancialPolicyActionCompletionIdV1(identity));
344
+ }
345
+ catch {
346
+ return false;
347
+ }
348
+ };
349
+ export const isFinancialPolicyActionCompletionCompatibleV1 = (completion, evaluation, definition) => isFinancialPolicyActionCompletionV1(completion) &&
350
+ isFinancialPolicyEvaluationV1(evaluation) &&
351
+ isFinancialPolicyDefinitionRevisionV1(definition) &&
352
+ completion.actionAuditId === evaluation.actionAuditId &&
353
+ completion.evaluationId === evaluation.evaluationId &&
354
+ completion.companyId === evaluation.companyId &&
355
+ completion.completedAt === evaluation.evaluatedAt &&
356
+ definition.companyId === completion.companyId &&
357
+ definition.destinationRefIds.includes(completion.destinationRefId);
@@ -0,0 +1,225 @@
1
+ import { formatExactDecimalValue, multiplyExactDecimalValues, parseCanonicalDecimal, subtractExactDecimalValues, } from '../common/exactDecimal.js';
2
+ import { createFinancialDataflowCoordinateIdV1 } from './financialDataflowValidation.js';
3
+ import { FINANCIAL_POLICY_EVALUATION_CONTRACT_VERSION_V1, } from './financialPolicy.js';
4
+ import { createFinancialPolicyEvaluationActionAuditIdV1, createFinancialPolicyEvaluationIdV1, createFinancialPolicyEvaluationReadProjectionIdV1, isFinancialPolicyEvaluationCompatibleV1, } from './financialPolicyValidation.js';
5
+ export const FINANCIAL_POLICY_ALGORITHM_VERSION_V1 = 'financial-policy/shared-v1';
6
+ const compare = (left, right) => {
7
+ const difference = subtractExactDecimalValues(parseCanonicalDecimal(left), parseCanonicalDecimal(right)).coefficient;
8
+ return difference < 0n ? -1 : difference > 0n ? 1 : 0;
9
+ };
10
+ const percentageMatched = (amount, denominator, threshold) => {
11
+ const parsedDenominator = parseCanonicalDecimal(denominator);
12
+ if (parsedDenominator.coefficient <= 0n)
13
+ return false;
14
+ const left = multiplyExactDecimalValues(parseCanonicalDecimal(amount), parseCanonicalDecimal('100'));
15
+ const right = multiplyExactDecimalValues(parsedDenominator, parseCanonicalDecimal(threshold));
16
+ return subtractExactDecimalValues(left, right).coefficient >= 0n;
17
+ };
18
+ /**
19
+ * Shared exact-decimal display classification for Budget surfaces. This is a
20
+ * read-model policy only; alert matching continues to use the thresholds in a
21
+ * versioned Financial Policy Definition.
22
+ */
23
+ export const classifyFinancialBudgetPositionV1 = (request) => {
24
+ if (request.budgetAmount === undefined)
25
+ return { status: 'unbudgeted' };
26
+ if (request.amount === undefined)
27
+ return { status: 'unavailable' };
28
+ try {
29
+ const atRiskPercent = request.atRiskPercent ?? '95';
30
+ const amount = parseCanonicalDecimal(request.amount);
31
+ const budget = parseCanonicalDecimal(request.budgetAmount);
32
+ const risk = parseCanonicalDecimal(atRiskPercent);
33
+ if (budget.coefficient <= 0n || risk.coefficient < 0n || compare(atRiskPercent, '100') > 0) {
34
+ return { status: 'unbudgeted' };
35
+ }
36
+ const varianceAmount = formatExactDecimalValue(subtractExactDecimalValues(amount, budget));
37
+ const status = compare(request.amount, request.budgetAmount) > 0
38
+ ? 'over-budget'
39
+ : percentageMatched(request.amount, request.budgetAmount, atRiskPercent)
40
+ ? 'at-risk'
41
+ : 'on-track';
42
+ return {
43
+ status,
44
+ varianceAmount,
45
+ amount: request.amount,
46
+ budgetAmount: request.budgetAmount,
47
+ atRiskPercent,
48
+ };
49
+ }
50
+ catch {
51
+ return { status: 'unavailable' };
52
+ }
53
+ };
54
+ const isExpectedCalendarMonthTargetPartial = (composition) => composition.coordinate.period.windowKind === 'calendar-month' &&
55
+ composition.amount.status === 'partial' &&
56
+ composition.amount.reasonCodes.length === 1 &&
57
+ composition.amount.reasonCodes[0] === 'coverage-incomplete';
58
+ /**
59
+ * Returns the exact current amount that Budget semantics may compare. An open
60
+ * calendar month is intentionally partial until the period closes; every
61
+ * other partial/unavailable state remains non-comparable.
62
+ */
63
+ export const selectFinancialBudgetComparableCurrentAmountV1 = (composition) => composition.amount.status === 'available'
64
+ ? composition.amount.amount
65
+ : isExpectedCalendarMonthTargetPartial(composition)
66
+ ? composition.amount.knownAmount
67
+ : undefined;
68
+ const budgetThresholds = (amount, budget, thresholds) => [
69
+ ...thresholds.amounts
70
+ .filter(threshold => compare(amount, threshold) >= 0)
71
+ .map(configuredValue => ({ thresholdKind: 'amount', configuredValue })),
72
+ ...thresholds.percents
73
+ .filter(threshold => percentageMatched(amount, budget, threshold))
74
+ .map(configuredValue => ({ thresholdKind: 'percent', configuredValue })),
75
+ ];
76
+ const anomalyThresholds = (definition, projection) => {
77
+ if (definition.criteria.kind !== 'cost-anomaly' || projection.status === 'unavailable' || projection.result.kind !== 'anomaly')
78
+ return [];
79
+ const configured = [
80
+ ...(definition.criteria.minimumAmount === undefined
81
+ ? []
82
+ : [{ thresholdKind: 'minimum-amount', configuredValue: definition.criteria.minimumAmount }]),
83
+ ...(definition.criteria.minimumDelta === undefined
84
+ ? []
85
+ : [{ thresholdKind: 'minimum-delta', configuredValue: definition.criteria.minimumDelta }]),
86
+ ...(definition.criteria.minimumPercentChange === undefined
87
+ ? []
88
+ : [{ thresholdKind: 'minimum-percent-change', configuredValue: definition.criteria.minimumPercentChange }]),
89
+ ];
90
+ return projection.result.events.some(event => configured.every(threshold => {
91
+ if (threshold.thresholdKind === 'minimum-amount')
92
+ return compare(event.observed.amount, threshold.configuredValue) >= 0;
93
+ if (threshold.thresholdKind === 'minimum-delta')
94
+ return compare(event.delta.amount, threshold.configuredValue) >= 0;
95
+ return percentageMatched(event.delta.amount, event.expected.amount, threshold.configuredValue);
96
+ }))
97
+ ? configured
98
+ : [];
99
+ };
100
+ const materialize = (identity, definition, currentSpend, analytics) => {
101
+ const evaluationId = createFinancialPolicyEvaluationIdV1(identity);
102
+ const evaluation = {
103
+ ...identity,
104
+ evaluationId,
105
+ readProjectionId: createFinancialPolicyEvaluationReadProjectionIdV1(evaluationId),
106
+ actionAuditId: createFinancialPolicyEvaluationActionAuditIdV1(evaluationId),
107
+ };
108
+ if (!isFinancialPolicyEvaluationCompatibleV1(evaluation, definition, currentSpend, analytics)) {
109
+ throw new TypeError('Financial policy evaluation does not bind its definition and exact financial inputs.');
110
+ }
111
+ return evaluation;
112
+ };
113
+ export const evaluateFinancialPolicyV1 = (request) => {
114
+ if (request.definition.effectiveState !== 'enabled')
115
+ return [];
116
+ const common = {
117
+ schemaVersion: 1,
118
+ contractVersion: FINANCIAL_POLICY_EVALUATION_CONTRACT_VERSION_V1,
119
+ companyId: request.definition.companyId,
120
+ policyDefinitionRevisionId: request.definition.policyDefinitionRevisionId,
121
+ definitionId: request.definition.definitionId,
122
+ definitionRevision: request.definition.revision,
123
+ coordinateId: createFinancialDataflowCoordinateIdV1(request.currentSpend.coordinate),
124
+ currentSpendCompositionId: request.currentSpend.compositionId,
125
+ evaluatedAt: request.evaluatedAt,
126
+ policyAlgorithmVersion: FINANCIAL_POLICY_ALGORITHM_VERSION_V1,
127
+ };
128
+ if (request.definition.criteria.kind === 'budget') {
129
+ const evaluations = [];
130
+ const currentThresholds = request.definition.criteria.currentSpendThresholds;
131
+ if (currentThresholds) {
132
+ const expectedOpenCalendarMonth = isExpectedCalendarMonthTargetPartial(request.currentSpend);
133
+ const comparableCurrentAmount = selectFinancialBudgetComparableCurrentAmountV1(request.currentSpend);
134
+ const matched = comparableCurrentAmount === undefined
135
+ ? []
136
+ : budgetThresholds(comparableCurrentAmount, request.definition.criteria.budget.amount, currentThresholds);
137
+ const unavailable = request.currentSpend.amount.status === 'unavailable';
138
+ const partial = request.currentSpend.amount.status === 'partial' && !expectedOpenCalendarMonth;
139
+ evaluations.push(materialize({
140
+ ...common,
141
+ signalKind: 'budget-current-spend',
142
+ result: unavailable ? 'unavailable' : partial ? 'partial' : matched.length > 0 ? 'matched' : 'not-matched',
143
+ reasonCode: unavailable
144
+ ? 'current-spend-unavailable'
145
+ : partial
146
+ ? 'current-spend-partial'
147
+ : expectedOpenCalendarMonth && matched.length > 0
148
+ ? 'current-spend-threshold-matched-open-period'
149
+ : expectedOpenCalendarMonth
150
+ ? 'current-spend-threshold-not-matched-open-period'
151
+ : matched.length > 0
152
+ ? 'current-spend-threshold-matched'
153
+ : 'current-spend-threshold-not-matched',
154
+ matchedThresholds: matched,
155
+ }, request.definition, request.currentSpend));
156
+ }
157
+ const forecastThresholds = request.definition.criteria.forecastThresholds;
158
+ if (forecastThresholds) {
159
+ const projection = request.analytics?.forecast;
160
+ if (!projection) {
161
+ evaluations.push(materialize({
162
+ ...common,
163
+ signalKind: 'budget-forecast',
164
+ result: 'unavailable',
165
+ reasonCode: 'analytics-projection-unavailable',
166
+ matchedThresholds: [],
167
+ }, request.definition, request.currentSpend));
168
+ }
169
+ else {
170
+ const unavailable = projection.status === 'unavailable' || request.currentSpend.amount.status === 'unavailable';
171
+ const partial = projection.status === 'partial' ||
172
+ (request.currentSpend.amount.status === 'partial' && !isExpectedCalendarMonthTargetPartial(request.currentSpend));
173
+ const matched = !unavailable && !partial && projection.result.kind === 'forecast'
174
+ ? budgetThresholds(projection.result.projectedTotal.amount, request.definition.criteria.budget.amount, forecastThresholds)
175
+ : [];
176
+ evaluations.push(materialize({
177
+ ...common,
178
+ analyticsProjectionId: projection.analyticsProjectionId,
179
+ signalKind: 'budget-forecast',
180
+ result: unavailable ? 'unavailable' : partial ? 'partial' : matched.length > 0 ? 'matched' : 'not-matched',
181
+ reasonCode: unavailable
182
+ ? 'forecast-unavailable'
183
+ : partial
184
+ ? 'forecast-partial'
185
+ : matched.length > 0
186
+ ? 'forecast-threshold-matched'
187
+ : 'forecast-threshold-not-matched',
188
+ matchedThresholds: matched,
189
+ }, request.definition, request.currentSpend, projection));
190
+ }
191
+ }
192
+ return evaluations;
193
+ }
194
+ const projection = request.analytics?.anomaly;
195
+ if (!projection) {
196
+ return [
197
+ materialize({
198
+ ...common,
199
+ signalKind: 'cost-anomaly',
200
+ result: 'unavailable',
201
+ reasonCode: 'analytics-projection-unavailable',
202
+ matchedThresholds: [],
203
+ }, request.definition, request.currentSpend),
204
+ ];
205
+ }
206
+ const unavailable = projection.status === 'unavailable' || request.currentSpend.amount.status === 'unavailable';
207
+ const partial = projection.status === 'partial' || request.currentSpend.amount.status === 'partial';
208
+ const matched = unavailable || partial ? [] : anomalyThresholds(request.definition, projection);
209
+ return [
210
+ materialize({
211
+ ...common,
212
+ analyticsProjectionId: projection.analyticsProjectionId,
213
+ signalKind: 'cost-anomaly',
214
+ result: unavailable ? 'unavailable' : partial ? 'partial' : matched.length > 0 ? 'matched' : 'not-matched',
215
+ reasonCode: unavailable
216
+ ? 'anomaly-unavailable'
217
+ : partial
218
+ ? 'anomaly-partial'
219
+ : matched.length > 0
220
+ ? 'anomaly-threshold-matched'
221
+ : 'anomaly-threshold-not-matched',
222
+ matchedThresholds: matched,
223
+ }, request.definition, request.currentSpend, projection),
224
+ ];
225
+ };