@spottoai/types-package 1.0.2-beta.360 → 1.0.2-beta.364

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 (37) hide show
  1. package/README.md +38 -0
  2. package/dist/azure/billingArtifactCanonicalization.d.ts +3 -1
  3. package/dist/azure/billingArtifactCanonicalization.d.ts.map +1 -1
  4. package/dist/azure/billingArtifactCanonicalization.js +123 -43
  5. package/dist/azure/billingArtifactCanonicalization.js.map +1 -1
  6. package/dist/azure/billingArtifactGeneration.d.ts +53 -1
  7. package/dist/azure/billingArtifactGeneration.d.ts.map +1 -1
  8. package/dist/azure/billingArtifactGeneration.js +190 -25
  9. package/dist/azure/billingArtifactGeneration.js.map +1 -1
  10. package/dist/azure/billingArtifactLimits.d.ts +19 -0
  11. package/dist/azure/billingArtifactLimits.d.ts.map +1 -0
  12. package/dist/azure/billingArtifactLimits.js +22 -0
  13. package/dist/azure/billingArtifactLimits.js.map +1 -0
  14. package/dist/azure/billingPlots.d.ts +26 -3
  15. package/dist/azure/billingPlots.d.ts.map +1 -1
  16. package/dist/azure/billingPlots.js +257 -10
  17. package/dist/azure/billingPlots.js.map +1 -1
  18. package/dist/azure/views.d.ts +60 -0
  19. package/dist/azure/views.d.ts.map +1 -1
  20. package/dist/azure/views.js +280 -4
  21. package/dist/azure/views.js.map +1 -1
  22. package/dist/common/artifactControlData.d.ts +16 -2
  23. package/dist/common/artifactControlData.d.ts.map +1 -1
  24. package/dist/common/artifactControlData.js +129 -23
  25. package/dist/common/artifactControlData.js.map +1 -1
  26. package/dist/esm/azure/billingArtifactCanonicalization.js +121 -42
  27. package/dist/esm/azure/billingArtifactGeneration.js +186 -25
  28. package/dist/esm/azure/billingArtifactLimits.js +18 -0
  29. package/dist/esm/azure/billingPlots.js +253 -10
  30. package/dist/esm/azure/views.js +278 -4
  31. package/dist/esm/common/artifactControlData.js +127 -22
  32. package/dist/esm/index.js +1 -0
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/package.json +4 -2
@@ -1,11 +1,93 @@
1
1
  /**
2
2
  * Billing cost analysis types for Azure cost visualization.
3
3
  */
4
- import { isArtifactOwnershipBinding, } from '../common/artifactEvidence.js';
4
+ import { isArtifactOwnershipBinding } from '../common/artifactEvidence.js';
5
5
  import { containsForbiddenArtifactControlData } from '../common/artifactControlData.js';
6
6
  import { isArtifactRevisionVector } from '../common/artifactEvidenceValidation.js';
7
7
  import { isBillingCompletedArtifactPublicationDecision, isBillingPartialArtifactPublicationDecision, } from './billingArtifactEvidence.js';
8
- const BILLING_DOCUMENT_STATES = new Set(['current', 'stale', 'partial', 'fallback', 'complete-empty']);
8
+ const LEGACY_FALLBACK_FORBIDDEN_OWN_FIELDS = [
9
+ 'schemaVersion',
10
+ 'ownership',
11
+ 'revision',
12
+ 'authority',
13
+ 'documentType',
14
+ 'publicationMode',
15
+ 'status',
16
+ 'generationId',
17
+ 'inputManifestDigest',
18
+ 'outputBindingDigest',
19
+ 'outputManifestDigest',
20
+ 'manifestDigest',
21
+ 'observationDigest',
22
+ 'sha256',
23
+ 'artifactEvidence',
24
+ 'publicationDecision',
25
+ 'artifacts',
26
+ 'manifestPath',
27
+ 'inputManifestPath',
28
+ 'outputManifestPath',
29
+ 'byteLength',
30
+ 'byteCount',
31
+ 'rowCount',
32
+ 'contentEncoding',
33
+ 'mediaType',
34
+ 'etag',
35
+ 'versionId',
36
+ 'publicationKey',
37
+ 'coveragePlanDigest',
38
+ 'messageId',
39
+ 'eventId',
40
+ 'correlationId',
41
+ 'idempotencyKey',
42
+ 'inputState',
43
+ 'processingState',
44
+ 'evaluation',
45
+ 'dependencies',
46
+ 'claims',
47
+ 'issues',
48
+ 'completedAt',
49
+ 'observedAt',
50
+ 'enqueuedAt',
51
+ ];
52
+ const BILLING_FORBIDDEN_CONTROL_FIELDS = new Set([
53
+ ...LEGACY_FALLBACK_FORBIDDEN_OWN_FIELDS,
54
+ 'artifactState',
55
+ 'artifactSource',
56
+ 'digest',
57
+ 'fingerprint',
58
+ 'checksum',
59
+ 'hash',
60
+ 'subscriptionId',
61
+ 'provider',
62
+ 'tenantId',
63
+ 'companyId',
64
+ 'cloudAccountId',
65
+ 'accountId',
66
+ 'ownershipEpochRevision',
67
+ 'sourceRevision',
68
+ 'policyRevision',
69
+ 'processing',
70
+ 'evidence',
71
+ 'publication',
72
+ 'required',
73
+ 'support',
74
+ 'applicability',
75
+ 'attempt',
76
+ 'coverage',
77
+ 'emptyEvidence',
78
+ 'freshness',
79
+ 'reasonCode',
80
+ 'acceptedRowCount',
81
+ 'emptyProofRef',
82
+ 'claimId',
83
+ 'sectionPaths',
84
+ 'requiredDependencies',
85
+ 'code',
86
+ 'blocking',
87
+ 'dependency',
88
+ ].map(field => field.toLowerCase()));
89
+ const BILLING_DOCUMENT_STATES = new Set(['current', 'stale', 'partial', 'complete-empty']);
90
+ const BILLING_VERIFIED_READ_STATES = new Set(['current', 'stale', 'complete-empty']);
9
91
  const SHA256_PATTERN = /^[a-f0-9]{64}$/;
10
92
  const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
11
93
  const isNonEmptyString = (value) => typeof value === 'string' && value.trim() === value && value.length > 0;
@@ -15,9 +97,145 @@ const isNullableFiniteNumber = (value) => value === null || isFiniteNumber(value
15
97
  const isNonNegativeInteger = (value) => Number.isSafeInteger(value) && Number(value) >= 0;
16
98
  const isPositiveInteger = (value) => Number.isSafeInteger(value) && Number(value) > 0;
17
99
  const isStringArray = (value) => Array.isArray(value) && value.every(isNonEmptyString);
100
+ const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
18
101
  const hasControlCharacters = (value) => Array.from(value).some(character => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127);
19
102
  const isPathSegment = (value) => isNonEmptyString(value) && !/[\\/?#%]/.test(value) && !hasControlCharacters(value) && value !== '.' && value !== '..';
20
103
  const publicationDecisionReferencesDigest = (value, digest) => isRecord(value) && Array.isArray(value.dependencies) && value.dependencies.some(dependency => isRecord(dependency) && dependency.digest === digest);
104
+ const allowedBillingCostAnalysisFields = (value, validationBranch) => {
105
+ const fields = [];
106
+ const allowText = (object, ...keys) => {
107
+ if (!isRecord(object))
108
+ return;
109
+ for (const key of keys)
110
+ fields.push({ object, key, allowUriScheme: true, allowDigestLike: true, allowControlField: true });
111
+ };
112
+ const allowTextArray = (object, key) => {
113
+ if (isRecord(object)) {
114
+ fields.push({ object, key, allowUriSchemeInStringArray: true, allowDigestLike: true, allowControlField: true });
115
+ }
116
+ };
117
+ const allowControl = (object, ...keys) => {
118
+ if (!isRecord(object))
119
+ return;
120
+ for (const key of keys)
121
+ fields.push({ object, key, allowControlField: true });
122
+ };
123
+ const allowDigest = (object, key) => {
124
+ if (isRecord(object))
125
+ fields.push({ object, key, allowDigestLike: true, allowControlField: true });
126
+ };
127
+ const allowChildren = (object, ...keys) => {
128
+ if (!isRecord(object))
129
+ return;
130
+ for (const key of keys)
131
+ fields.push({ object, key, allowChildArtifactFields: true });
132
+ };
133
+ allowText(value, 'subscriptionId', 'billingGenerationId', 'currencyCode', 'currencySymbol', 'forecastMethod');
134
+ allowControl(value, 'schemaVersion', 'ownership', 'revision', 'artifactEvidence');
135
+ allowChildren(value, 'ownership', 'revision', 'artifactEvidence', 'chartData', 'anomalies');
136
+ if (validationBranch !== 'business-v1')
137
+ allowControl(value, 'artifactState');
138
+ if (validationBranch === 'legacy-fallback')
139
+ allowControl(value, 'artifactSource');
140
+ allowDigest(value, 'inputManifestDigest');
141
+ allowDigest(value, 'outputBindingDigest');
142
+ if (isRecord(value.ownership)) {
143
+ allowControl(value.ownership, 'provider', 'ownershipEpochRevision');
144
+ allowText(value.ownership, 'tenantId', 'companyId', 'cloudAccountId', 'accountId');
145
+ }
146
+ if (isRecord(value.revision))
147
+ allowControl(value.revision, 'ownershipEpochRevision', 'sourceRevision', 'policyRevision');
148
+ if (isRecord(value.artifactEvidence)) {
149
+ const evidence = value.artifactEvidence;
150
+ allowControl(evidence, 'processing', 'evidence', 'publication', 'dependencies', 'claims', 'issues');
151
+ allowChildren(evidence, 'dependencies', 'claims', 'issues');
152
+ if (Array.isArray(evidence.dependencies)) {
153
+ for (const dependency of evidence.dependencies) {
154
+ allowControl(dependency, 'required', 'support', 'applicability', 'attempt', 'coverage', 'emptyEvidence', 'freshness', 'evidence', 'publication', 'sourceRevision', 'policyRevision', 'acceptedRowCount', 'emptyProofRef');
155
+ allowText(dependency, 'name', 'reasonCode', 'generationId');
156
+ allowDigest(dependency, 'digest');
157
+ if (isRecord(dependency) && isRecord(dependency.observedRange)) {
158
+ allowChildren(dependency, 'observedRange');
159
+ allowText(dependency.observedRange, 'timeZone');
160
+ }
161
+ }
162
+ }
163
+ if (Array.isArray(evidence.claims)) {
164
+ for (const claim of evidence.claims) {
165
+ allowControl(claim, 'evidence', 'publication', 'issues');
166
+ allowText(claim, 'claimId');
167
+ allowTextArray(claim, 'sectionPaths');
168
+ allowTextArray(claim, 'requiredDependencies');
169
+ if (!isRecord(claim) || !Array.isArray(claim.issues))
170
+ continue;
171
+ allowChildren(claim, 'issues');
172
+ for (const issue of claim.issues) {
173
+ allowControl(issue, 'blocking');
174
+ allowText(issue, 'code', 'dependency');
175
+ }
176
+ }
177
+ }
178
+ if (Array.isArray(evidence.issues)) {
179
+ for (const issue of evidence.issues) {
180
+ allowControl(issue, 'blocking');
181
+ allowText(issue, 'code', 'dependency');
182
+ }
183
+ }
184
+ }
185
+ if (isRecord(value.chartData)) {
186
+ allowControl(value.chartData, 'schemaVersion');
187
+ allowText(value.chartData, 'source');
188
+ allowChildren(value.chartData, 'dataWindow', 'views', 'detectors');
189
+ if (isRecord(value.chartData.views)) {
190
+ for (const [viewKey, view] of Object.entries(value.chartData.views)) {
191
+ allowChildren(value.chartData.views, viewKey);
192
+ allowText(view, 'aggregation', 'forecastMethod');
193
+ if (!isRecord(view))
194
+ continue;
195
+ allowChildren(view, 'trend', 'points', 'actualPoints', 'forecastPoints', 'fittedPoints');
196
+ allowText(view.trend, 'method');
197
+ for (const pointsKey of ['points', 'actualPoints', 'forecastPoints', 'fittedPoints']) {
198
+ const points = view[pointsKey];
199
+ if (!Array.isArray(points))
200
+ continue;
201
+ for (const point of points) {
202
+ allowText(point, 'date', 'month');
203
+ allowTextArray(point, 'anomalyMethods');
204
+ }
205
+ }
206
+ }
207
+ }
208
+ if (isRecord(value.chartData.detectors) && Array.isArray(value.chartData.detectors.methods)) {
209
+ allowChildren(value.chartData.detectors, 'methods');
210
+ for (const method of value.chartData.detectors.methods)
211
+ allowText(method, 'name', 'status', 'error');
212
+ }
213
+ }
214
+ if (!Array.isArray(value.anomalies))
215
+ return fields;
216
+ for (const anomaly of value.anomalies) {
217
+ allowText(anomaly, 'summary', 'confidence');
218
+ allowTextArray(anomaly, 'notes');
219
+ if (!isRecord(anomaly) || !Array.isArray(anomaly.drivers))
220
+ continue;
221
+ allowChildren(anomaly, 'impact', 'drivers');
222
+ for (const driver of anomaly.drivers) {
223
+ allowText(driver, 'type', 'name', 'summary');
224
+ if (!isRecord(driver) || !Array.isArray(driver.resources))
225
+ continue;
226
+ allowChildren(driver, 'resources');
227
+ for (const resource of driver.resources)
228
+ allowText(resource, 'name', 'resourceScope', 'summary');
229
+ }
230
+ }
231
+ return fields;
232
+ };
233
+ const containsForbiddenBillingCostAnalysisControlData = (value, validationBranch) => containsForbiddenArtifactControlData(value, allowedBillingCostAnalysisFields(value, validationBranch), {
234
+ rejectDigestLikeValues: true,
235
+ requireSafeAzureResourceIds: true,
236
+ forbiddenControlFields: BILLING_FORBIDDEN_CONTROL_FIELDS,
237
+ requireAllowedFieldTraversalContext: true,
238
+ });
21
239
  const isTrend = (value) => isRecord(value) && isNonEmptyString(value.method) && isFiniteNumber(value.slope) && isFiniteNumber(value.intercept);
22
240
  const isDailyPoint = (value) => isRecord(value) &&
23
241
  isNonEmptyString(value.date) &&
@@ -139,7 +357,11 @@ const hasValidMetadataEvidenceState = (state, evidence, billingGenerationId, inp
139
357
  return false;
140
358
  if (state !== 'complete-empty')
141
359
  return true;
360
+ if (!Array.isArray(anomalies))
361
+ return false;
142
362
  const billingHistory = evidence.dependencies.find(dependency => dependency.name === 'billing-history');
363
+ if (!isRecord(chartData))
364
+ return false;
143
365
  const dataWindow = chartData.dataWindow;
144
366
  const views = chartData.views;
145
367
  return (billingHistory?.emptyEvidence === 'complete-empty' &&
@@ -151,11 +373,27 @@ const hasValidMetadataEvidenceState = (state, evidence, billingGenerationId, inp
151
373
  Object.values(views).every(view => view === undefined) &&
152
374
  anomalies.length === 0);
153
375
  };
376
+ const hasValidBillingCostAnalysisBusinessFields = (value) => {
377
+ if (!isPathSegment(value.subscriptionId) || !isPathSegment(value.billingGenerationId))
378
+ return false;
379
+ if (!isChartData(value.chartData) || !Array.isArray(value.anomalies) || !value.anomalies.every(isAnomaly))
380
+ return false;
381
+ if (!isNonEmptyString(value.currencyCode) || !isNonEmptyString(value.currencySymbol))
382
+ return false;
383
+ if (value.forecastMethod !== undefined && !isNonEmptyString(value.forecastMethod))
384
+ return false;
385
+ return [value.forecastMonthTotal, value.forecastRemaining, value.forecastPeriodEnd].every(isOptionalFiniteNumber);
386
+ };
387
+ const isBillingCostAnalysisBusinessPayloadForBranch = (value, validationBranch) => !containsForbiddenBillingCostAnalysisControlData(value, validationBranch) &&
388
+ !LEGACY_FALLBACK_FORBIDDEN_OWN_FIELDS.some(field => hasOwn(value, field)) &&
389
+ hasValidBillingCostAnalysisBusinessFields(value);
390
+ /** Dependency-free validator for the complete legacy V1 business payload. */
391
+ export const isBillingCostAnalysisBusinessPayloadV1 = (value) => isRecord(value) && isBillingCostAnalysisBusinessPayloadForBranch(value, 'business-v1');
154
392
  /** Dependency-free validator for customer-readable V2 billing metadata. */
155
393
  export const isBillingCostAnalysisMetadataV2 = (value) => {
156
- if (!isRecord(value) || containsForbiddenArtifactControlData(value) || value.schemaVersion !== 2)
394
+ if (!isRecord(value) || containsForbiddenBillingCostAnalysisControlData(value, 'metadata-v2') || value.schemaVersion !== 2)
157
395
  return false;
158
- if (Object.prototype.hasOwnProperty.call(value, 'outputManifestDigest'))
396
+ if (hasOwn(value, 'outputManifestDigest'))
159
397
  return false;
160
398
  if (!isPathSegment(value.subscriptionId) || !isPathSegment(value.billingGenerationId))
161
399
  return false;
@@ -174,14 +412,19 @@ export const isBillingCostAnalysisMetadataV2 = (value) => {
174
412
  publicationDecisionReferencesDigest(value.artifactEvidence, value.outputBindingDigest)) {
175
413
  return false;
176
414
  }
177
- if (!isChartData(value.chartData) || !Array.isArray(value.anomalies) || !value.anomalies.every(isAnomaly))
415
+ if (!hasValidBillingCostAnalysisBusinessFields(value))
178
416
  return false;
179
417
  if (!hasValidMetadataEvidenceState(value.artifactState, value.artifactEvidence, value.billingGenerationId, value.inputManifestDigest, value.chartData, value.anomalies)) {
180
418
  return false;
181
419
  }
182
- if (!isNonEmptyString(value.currencyCode) || !isNonEmptyString(value.currencySymbol))
183
- return false;
184
- if (value.forecastMethod !== undefined && !isNonEmptyString(value.forecastMethod))
185
- return false;
186
- return [value.forecastMonthTotal, value.forecastRemaining, value.forecastPeriodEnd].every(isOptionalFiniteNumber);
420
+ return true;
187
421
  };
422
+ /** Dependency-free validator for an explicit legacy-transition fallback response. */
423
+ export const isBillingCostAnalysisLegacyFallbackResponse = (value) => isRecord(value) &&
424
+ value.artifactState === 'fallback' &&
425
+ value.artifactSource === 'legacy-transition' &&
426
+ isBillingCostAnalysisBusinessPayloadForBranch(value, 'legacy-fallback');
427
+ /** Dependency-free validator for an evidence-verified endpoint response. */
428
+ export const isBillingCostAnalysisVerifiedReadResponse = (value) => isBillingCostAnalysisMetadataV2(value) && BILLING_VERIFIED_READ_STATES.has(value.artifactState);
429
+ /** Dependency-free validator for the successful billing read-response union. */
430
+ export const isBillingCostAnalysisReadResponse = (value) => isBillingCostAnalysisVerifiedReadResponse(value) || isBillingCostAnalysisLegacyFallbackResponse(value);
@@ -1,6 +1,14 @@
1
- import { allowedArtifactReferenceField, containsForbiddenArtifactControlData, } from '../common/artifactControlData.js';
1
+ import { allowedArtifactReferenceField, allowedArtifactTraversalField, containsForbiddenArtifactControlData, } from '../common/artifactControlData.js';
2
2
  import { isArtifactOwnershipBinding, isArtifactPublicationDecision, isEnforceableArtifactOwnershipBinding, } from '../common/artifactEvidence.js';
3
3
  import { isArtifactRevisionVector, isStrictLogicalArtifactReference } from '../common/artifactEvidenceValidation.js';
4
+ export const PUBLISHED_VIEW_OBJECT_LIMITS_V1 = {
5
+ maxArtifacts: 512,
6
+ maxClaims: 128,
7
+ maxDependencies: 256,
8
+ maxIssues: 512,
9
+ maxClaimBindings: 512,
10
+ maxSectionPaths: 512,
11
+ };
4
12
  const isNonEmptyString = (value) => typeof value === 'string' && value.trim().length > 0;
5
13
  const isCanonicalIsoTimestamp = (value) => {
6
14
  if (!isNonEmptyString(value))
@@ -41,6 +49,9 @@ const isPositiveSafeInteger = (value) => Number.isSafeInteger(value) && Number(v
41
49
  const isNonNegativeSafeInteger = (value) => Number.isSafeInteger(value) && Number(value) >= 0;
42
50
  const isSha256 = (value) => typeof value === 'string' && SHA256_PATTERN.test(value);
43
51
  const allowedViewArtifactPaths = (artifacts) => Array.isArray(artifacts) ? artifacts.flatMap(artifact => allowedArtifactReferenceField(artifact, 'path')) : [];
52
+ const containsForbiddenViewArtifactControlData = (value, allowedReferenceFields = []) => containsForbiddenArtifactControlData(value, allowedReferenceFields, {
53
+ requireAllowedFieldTraversalContext: true,
54
+ });
44
55
  const isSafePathSegment = (value) => isStrictNonEmptyString(value) && !/[\\/?#%]/.test(value) && value !== '.' && value !== '..';
45
56
  const isStrictCanonicalIsoTimestamp = (value) => {
46
57
  if (!isStrictNonEmptyString(value))
@@ -70,6 +81,120 @@ const isViewArtifactDescriptor = (value, runId) => isRecord(value) &&
70
81
  VIEW_CONTENT_ENCODINGS.has(value.contentEncoding) &&
71
82
  isNonNegativeSafeInteger(value.byteLength) &&
72
83
  isSha256(value.sha256);
84
+ const isPublishedViewCoverage = (value) => value === 'complete' || value === 'partial';
85
+ const isProjectedSectionPathForArtifact = (value, artifactPath) => {
86
+ if (!isStrictNonEmptyString(value))
87
+ return false;
88
+ if (value === artifactPath)
89
+ return true;
90
+ if (!value.startsWith(`${artifactPath}#/`))
91
+ return false;
92
+ const pointer = value.slice(artifactPath.length + 1);
93
+ return !pointer.includes('\\') && !pointer.includes('?') && !/%(?:2f|2F|5c|5C)/.test(pointer);
94
+ };
95
+ const parseProjectedSectionPath = (value) => {
96
+ const fragmentIndex = value.indexOf('#');
97
+ const artifactPath = fragmentIndex < 0 ? value : value.slice(0, fragmentIndex);
98
+ if (!isStrictLogicalArtifactReference(artifactPath) || !isProjectedSectionPathForArtifact(value, artifactPath))
99
+ return undefined;
100
+ return {
101
+ artifactPath,
102
+ pointerSegments: fragmentIndex < 0 ? [] : value.slice(fragmentIndex + 2).split('/'),
103
+ };
104
+ };
105
+ const doProjectedSectionsOverlap = (left, right) => {
106
+ const parsedLeft = parseProjectedSectionPath(left);
107
+ const parsedRight = parseProjectedSectionPath(right);
108
+ if (!parsedLeft || !parsedRight || parsedLeft.artifactPath !== parsedRight.artifactPath)
109
+ return false;
110
+ if (parsedLeft.pointerSegments.length === 0 || parsedRight.pointerSegments.length === 0)
111
+ return true;
112
+ const sharedLength = Math.min(parsedLeft.pointerSegments.length, parsedRight.pointerSegments.length);
113
+ for (let index = 0; index < sharedLength; index += 1) {
114
+ const leftSegment = parsedLeft.pointerSegments[index];
115
+ const rightSegment = parsedRight.pointerSegments[index];
116
+ if (leftSegment === rightSegment)
117
+ continue;
118
+ if (leftSegment === '*' || leftSegment === '**' || rightSegment === '*' || rightSegment === '**')
119
+ return true;
120
+ return false;
121
+ }
122
+ return true;
123
+ };
124
+ const hasUnambiguousPublishedClaimSections = (decision) => {
125
+ const declaredSections = [];
126
+ for (const claim of decision.claims) {
127
+ if (new Set(claim.sectionPaths).size !== claim.sectionPaths.length)
128
+ return false;
129
+ for (const sectionPath of claim.sectionPaths) {
130
+ if (!parseProjectedSectionPath(sectionPath))
131
+ return false;
132
+ if (declaredSections.some(declaredSection => doProjectedSectionsOverlap(declaredSection, sectionPath)))
133
+ return false;
134
+ declaredSections.push(sectionPath);
135
+ }
136
+ }
137
+ return true;
138
+ };
139
+ const isPublishedViewArtifactDescriptor = (value, runId) => {
140
+ if (!isViewArtifactDescriptor(value, runId) || !isRecord(value))
141
+ return false;
142
+ const runPrefix = `runs/${runId}/`;
143
+ if (value.name !== value.path.slice(runPrefix.length))
144
+ return false;
145
+ if (!Array.isArray(value.claimBindings) ||
146
+ value.claimBindings.length === 0 ||
147
+ value.claimBindings.length > PUBLISHED_VIEW_OBJECT_LIMITS_V1.maxClaimBindings) {
148
+ return false;
149
+ }
150
+ const claimIds = new Set();
151
+ for (const binding of value.claimBindings) {
152
+ if (!isRecord(binding) || !isStrictNonEmptyString(binding.claimId) || claimIds.has(binding.claimId))
153
+ return false;
154
+ claimIds.add(binding.claimId);
155
+ if (!Array.isArray(binding.sectionPaths) ||
156
+ binding.sectionPaths.length === 0 ||
157
+ binding.sectionPaths.length > PUBLISHED_VIEW_OBJECT_LIMITS_V1.maxSectionPaths) {
158
+ return false;
159
+ }
160
+ if (new Set(binding.sectionPaths).size !== binding.sectionPaths.length)
161
+ return false;
162
+ if (!binding.sectionPaths.every(sectionPath => isProjectedSectionPathForArtifact(sectionPath, value.path)))
163
+ return false;
164
+ }
165
+ return true;
166
+ };
167
+ const hasPublishedViewDecisionBounds = (decision) => {
168
+ if (decision.claims.length === 0 ||
169
+ decision.claims.length > PUBLISHED_VIEW_OBJECT_LIMITS_V1.maxClaims ||
170
+ decision.dependencies.length > PUBLISHED_VIEW_OBJECT_LIMITS_V1.maxDependencies ||
171
+ decision.issues.length > PUBLISHED_VIEW_OBJECT_LIMITS_V1.maxIssues) {
172
+ return false;
173
+ }
174
+ let sectionPathCount = 0;
175
+ let issueCount = decision.issues.length;
176
+ for (const claim of decision.claims) {
177
+ sectionPathCount += claim.sectionPaths.length;
178
+ issueCount += claim.issues.length;
179
+ if (sectionPathCount > PUBLISHED_VIEW_OBJECT_LIMITS_V1.maxSectionPaths || issueCount > PUBLISHED_VIEW_OBJECT_LIMITS_V1.maxIssues) {
180
+ return false;
181
+ }
182
+ }
183
+ return true;
184
+ };
185
+ const hasPublishedViewArtifactBounds = (artifacts) => {
186
+ let claimBindingCount = 0;
187
+ let sectionPathCount = 0;
188
+ for (const artifact of artifacts) {
189
+ claimBindingCount += artifact.claimBindings.length;
190
+ for (const binding of artifact.claimBindings)
191
+ sectionPathCount += binding.sectionPaths.length;
192
+ if (claimBindingCount > PUBLISHED_VIEW_OBJECT_LIMITS_V1.maxClaimBindings || sectionPathCount > PUBLISHED_VIEW_OBJECT_LIMITS_V1.maxSectionPaths) {
193
+ return false;
194
+ }
195
+ }
196
+ return true;
197
+ };
73
198
  const hasRequiredViewDependencies = (decision) => {
74
199
  const dependencies = new Map(decision.dependencies.map(dependency => [dependency.name, dependency]));
75
200
  const billing = dependencies.get('billing');
@@ -86,7 +211,11 @@ const hasRequiredViewDependencies = (decision) => {
86
211
  };
87
212
  /** Validates an evidence-aware completed portal or plugin generation manifest. */
88
213
  export const isCompletedViewManifestV3 = (value) => {
89
- if (!isRecord(value) || containsForbiddenArtifactControlData(value, allowedViewArtifactPaths(value.artifacts)))
214
+ if (!isRecord(value) ||
215
+ containsForbiddenViewArtifactControlData(value, [
216
+ ...allowedArtifactTraversalField(value, 'artifacts'),
217
+ ...allowedViewArtifactPaths(value.artifacts),
218
+ ]))
90
219
  return false;
91
220
  if (value.schemaVersion !== 3 || value.status !== 'completed')
92
221
  return false;
@@ -116,6 +245,90 @@ export const isCompletedViewManifestV3 = (value) => {
116
245
  }
117
246
  return isArtifactPublicationDecision(value.publicationDecision) && hasRequiredViewDependencies(value.publicationDecision);
118
247
  };
248
+ const hasExactPublishedClaimProjection = (artifacts, decision) => {
249
+ if (!hasUnambiguousPublishedClaimSections(decision))
250
+ return false;
251
+ const claimById = new Map(decision.claims.map(claim => [claim.claimId, claim]));
252
+ const completedClaims = decision.claims.filter(claim => claim.publication === 'completed');
253
+ if (completedClaims.length === 0)
254
+ return false;
255
+ const expectedBindings = new Set();
256
+ for (const claim of completedClaims) {
257
+ if (claim.sectionPaths.length === 0 || new Set(claim.sectionPaths).size !== claim.sectionPaths.length)
258
+ return false;
259
+ for (const sectionPath of claim.sectionPaths)
260
+ expectedBindings.add(`${claim.claimId}\0${sectionPath}`);
261
+ }
262
+ const actualBindings = new Set();
263
+ for (const artifact of artifacts) {
264
+ for (const binding of artifact.claimBindings) {
265
+ const claim = claimById.get(binding.claimId);
266
+ if (!claim || claim.publication !== 'completed')
267
+ return false;
268
+ for (const sectionPath of binding.sectionPaths) {
269
+ if (!claim.sectionPaths.includes(sectionPath))
270
+ return false;
271
+ const key = `${binding.claimId}\0${sectionPath}`;
272
+ if (actualBindings.has(key))
273
+ return false;
274
+ actualBindings.add(key);
275
+ }
276
+ }
277
+ }
278
+ return expectedBindings.size === actualBindings.size && Array.from(expectedBindings).every(binding => actualBindings.has(binding));
279
+ };
280
+ const isPublishedDecisionForCoverage = (decision, coverage) => {
281
+ if (!hasRequiredViewDependencies(decision) || decision.processing !== 'succeeded')
282
+ return false;
283
+ const completedClaimCount = decision.claims.filter(claim => claim.publication === 'completed').length;
284
+ if (coverage === 'complete') {
285
+ return decision.publication === 'completed' && decision.evidence === 'complete' && completedClaimCount === decision.claims.length;
286
+ }
287
+ return (decision.publication === 'partial' && decision.evidence === 'partial' && completedClaimCount > 0 && completedClaimCount < decision.claims.length);
288
+ };
289
+ /** Validates a claim-projected portal or plugin generation, including observe-mode epoch-free manifests. */
290
+ export const isPublishedViewManifestV4 = (value) => {
291
+ if (!isRecord(value) ||
292
+ containsForbiddenViewArtifactControlData(value, [
293
+ ...allowedArtifactTraversalField(value, 'artifacts'),
294
+ ...allowedViewArtifactPaths(value.artifacts),
295
+ ])) {
296
+ return false;
297
+ }
298
+ if (value.schemaVersion !== 4 || value.status !== 'published' || !isPublishedViewCoverage(value.coverage))
299
+ return false;
300
+ if (!isSafePathSegment(value.runId) || !hasMatchingViewOwnership(value.subscriptionId, value.ownership, value.revision, false))
301
+ return false;
302
+ if (!isRecord(value.artifactGeneration) ||
303
+ value.artifactGeneration.runId !== value.runId ||
304
+ !isStrictCanonicalIsoTimestamp(value.artifactGeneration.generatedAt)) {
305
+ return false;
306
+ }
307
+ if (!Array.isArray(value.artifacts) || value.artifacts.length === 0 || value.artifacts.length > PUBLISHED_VIEW_OBJECT_LIMITS_V1.maxArtifacts) {
308
+ return false;
309
+ }
310
+ if (!value.artifacts.every(artifact => isPublishedViewArtifactDescriptor(artifact, value.runId)))
311
+ return false;
312
+ if (!hasPublishedViewArtifactBounds(value.artifacts))
313
+ return false;
314
+ if (new Set(value.artifacts.map(artifact => artifact.path)).size !== value.artifacts.length ||
315
+ new Set(value.artifacts.map(artifact => artifact.name)).size !== value.artifacts.length) {
316
+ return false;
317
+ }
318
+ if (!isPositiveSafeInteger(value.requestedArtifactCount) ||
319
+ value.requestedArtifactCount !== value.artifacts.length ||
320
+ !isNonNegativeSafeInteger(value.requestedResourceCount) ||
321
+ value.failedArtifactCount !== 0 ||
322
+ value.failedResourceCount !== 0 ||
323
+ !isSha256(value.compositeDependencyDigest) ||
324
+ !isStrictCanonicalIsoTimestamp(value.completedAt) ||
325
+ !isArtifactPublicationDecision(value.publicationDecision) ||
326
+ !hasPublishedViewDecisionBounds(value.publicationDecision)) {
327
+ return false;
328
+ }
329
+ return (isPublishedDecisionForCoverage(value.publicationDecision, value.coverage) &&
330
+ hasExactPublishedClaimProjection(value.artifacts, value.publicationDecision));
331
+ };
119
332
  const hasSameOwnership = (left, right) => left.provider === right.provider &&
120
333
  left.tenantId === right.tenantId &&
121
334
  left.companyId === right.companyId &&
@@ -141,6 +354,22 @@ const isViewSetV2SurfaceReference = (value, surface, subscriptionId, ownership,
141
354
  value.compositeDependencyDigest === compositeDependencyDigest &&
142
355
  isStrictCanonicalIsoTimestamp(value.completedAt));
143
356
  };
357
+ const isPublishedViewSetV3SurfaceReference = (value, surface, subscriptionId, ownership, revision, compositeDependencyDigest) => {
358
+ if (!isRecord(value) || !isSafePathSegment(value.runId) || !isPublishedViewCoverage(value.coverage))
359
+ return false;
360
+ const expectedManifestName = surface === 'portal' ? 'published-view-manifest.json' : 'published-plugin-generation.json';
361
+ if (value.manifestPath !== `runs/${value.runId}/${expectedManifestName}` || !isStrictLogicalArtifactReference(value.manifestPath))
362
+ return false;
363
+ if (!isEnforceableAzureOwnershipBinding(value.ownership) || !isArtifactRevisionVector(value.revision))
364
+ return false;
365
+ if (!hasMatchingViewOwnership(subscriptionId, value.ownership, value.revision, true))
366
+ return false;
367
+ if (!hasSameOwnership(ownership, value.ownership) || !hasSameRevision(revision, value.revision))
368
+ return false;
369
+ return (isSha256(value.manifestDigest) &&
370
+ value.compositeDependencyDigest === compositeDependencyDigest &&
371
+ isStrictCanonicalIsoTimestamp(value.completedAt));
372
+ };
144
373
  const hasMatchingSurfaceDependency = (decision, name, surface) => {
145
374
  const dependency = decision.dependencies.find(candidate => candidate.name === name);
146
375
  return (dependency !== undefined &&
@@ -152,9 +381,14 @@ const hasMatchingSurfaceDependency = (decision, name, surface) => {
152
381
  /** Validates the promoted pointer for an evidence-enforced portal/plugin view pair. */
153
382
  export const isCompletedAzureViewSetV2 = (value) => {
154
383
  const allowedReferences = isRecord(value)
155
- ? [...allowedArtifactReferenceField(value.portal, 'manifestPath'), ...allowedArtifactReferenceField(value.plugin, 'manifestPath')]
384
+ ? [
385
+ ...allowedArtifactTraversalField(value, 'portal'),
386
+ ...allowedArtifactTraversalField(value, 'plugin'),
387
+ ...allowedArtifactReferenceField(value.portal, 'manifestPath'),
388
+ ...allowedArtifactReferenceField(value.plugin, 'manifestPath'),
389
+ ]
156
390
  : [];
157
- if (!isRecord(value) || containsForbiddenArtifactControlData(value, allowedReferences))
391
+ if (!isRecord(value) || containsForbiddenViewArtifactControlData(value, allowedReferences))
158
392
  return false;
159
393
  if (value.schemaVersion !== 2 || value.status !== 'completed')
160
394
  return false;
@@ -179,3 +413,43 @@ export const isCompletedAzureViewSetV2 = (value) => {
179
413
  hasMatchingSurfaceDependency(value.publicationDecision, 'portal', value.portal) &&
180
414
  hasMatchingSurfaceDependency(value.publicationDecision, 'plugin', value.plugin));
181
415
  };
416
+ /** Validates the promoted pointer for one claim-projected portal/plugin generation pair. */
417
+ export const isPublishedAzureViewSetV3 = (value) => {
418
+ const allowedReferences = isRecord(value)
419
+ ? [
420
+ ...allowedArtifactTraversalField(value, 'portal'),
421
+ ...allowedArtifactTraversalField(value, 'plugin'),
422
+ ...allowedArtifactReferenceField(value.portal, 'manifestPath'),
423
+ ...allowedArtifactReferenceField(value.plugin, 'manifestPath'),
424
+ ]
425
+ : [];
426
+ if (!isRecord(value) || containsForbiddenViewArtifactControlData(value, allowedReferences))
427
+ return false;
428
+ if (value.schemaVersion !== 3 || value.status !== 'published' || !isPublishedViewCoverage(value.coverage))
429
+ return false;
430
+ if (!isSafePathSegment(value.subscriptionId) ||
431
+ !isStrictNonEmptyString(value.publicationId) ||
432
+ !isEnforceableAzureOwnershipBinding(value.ownership) ||
433
+ !isArtifactRevisionVector(value.revision) ||
434
+ !hasMatchingViewOwnership(value.subscriptionId, value.ownership, value.revision, true)) {
435
+ return false;
436
+ }
437
+ if (!isSha256(value.compositeDependencyDigest) || !isStrictCanonicalIsoTimestamp(value.completedAt))
438
+ return false;
439
+ if (!isPublishedViewSetV3SurfaceReference(value.portal, 'portal', value.subscriptionId, value.ownership, value.revision, value.compositeDependencyDigest) ||
440
+ !isPublishedViewSetV3SurfaceReference(value.plugin, 'plugin', value.subscriptionId, value.ownership, value.revision, value.compositeDependencyDigest)) {
441
+ return false;
442
+ }
443
+ const expectedCoverage = value.portal.coverage === 'complete' && value.plugin.coverage === 'complete' ? 'complete' : 'partial';
444
+ if (value.coverage !== expectedCoverage)
445
+ return false;
446
+ const laterSurfaceCompletedAt = Date.parse(value.portal.completedAt) >= Date.parse(value.plugin.completedAt) ? value.portal.completedAt : value.plugin.completedAt;
447
+ if (value.completedAt !== laterSurfaceCompletedAt)
448
+ return false;
449
+ return (isArtifactPublicationDecision(value.publicationDecision) &&
450
+ hasPublishedViewDecisionBounds(value.publicationDecision) &&
451
+ value.publicationDecision.publication === 'completed' &&
452
+ value.publicationDecision.evidence === value.coverage &&
453
+ hasMatchingSurfaceDependency(value.publicationDecision, 'portal', value.portal) &&
454
+ hasMatchingSurfaceDependency(value.publicationDecision, 'plugin', value.plugin));
455
+ };