@mitre/hdf-schema 3.0.0 → 3.1.0-rc.1

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 (48) hide show
  1. package/LICENSE.md +55 -0
  2. package/README.md +96 -41
  3. package/dist/go/hdf.go +148 -104
  4. package/dist/helpers.js +4 -44
  5. package/dist/index.d.ts +26 -1
  6. package/dist/index.js +26 -1
  7. package/dist/schemas/hdf-amendments.schema.json +178 -53
  8. package/dist/schemas/hdf-baseline.schema.json +181 -56
  9. package/dist/schemas/hdf-comparison.schema.json +523 -108
  10. package/dist/schemas/hdf-evidence-package.schema.json +175 -50
  11. package/dist/schemas/hdf-plan.schema.json +181 -56
  12. package/dist/schemas/hdf-results.schema.json +502 -87
  13. package/dist/schemas/hdf-system.schema.json +190 -65
  14. package/dist/ts/hdf-amendments.d.ts +43 -15
  15. package/dist/ts/hdf-amendments.js +18 -7
  16. package/dist/ts/hdf-amendments.ts +44 -15
  17. package/dist/ts/hdf-results.d.ts +91 -37
  18. package/dist/ts/hdf-results.js +40 -20
  19. package/dist/ts/hdf-results.ts +91 -36
  20. package/package.json +44 -44
  21. package/dist/python/hdf_amendments.py +0 -695
  22. package/dist/python/hdf_baseline.py +0 -782
  23. package/dist/python/hdf_comparison.py +0 -1771
  24. package/dist/python/hdf_evidence_package.py +0 -593
  25. package/dist/python/hdf_plan.py +0 -363
  26. package/dist/python/hdf_results.py +0 -2163
  27. package/dist/python/hdf_system.py +0 -904
  28. package/src/schemas/hdf-amendments.schema.json +0 -97
  29. package/src/schemas/hdf-baseline.schema.json +0 -190
  30. package/src/schemas/hdf-comparison.schema.json +0 -107
  31. package/src/schemas/hdf-evidence-package.schema.json +0 -227
  32. package/src/schemas/hdf-plan.schema.json +0 -92
  33. package/src/schemas/hdf-results.schema.json +0 -304
  34. package/src/schemas/hdf-system.schema.json +0 -136
  35. package/src/schemas/primitives/amendments.schema.json +0 -155
  36. package/src/schemas/primitives/common.schema.json +0 -814
  37. package/src/schemas/primitives/comparison.schema.json +0 -809
  38. package/src/schemas/primitives/component.schema.json +0 -518
  39. package/src/schemas/primitives/data-flow.schema.json +0 -158
  40. package/src/schemas/primitives/extensions.schema.json +0 -342
  41. package/src/schemas/primitives/parameter.schema.json +0 -128
  42. package/src/schemas/primitives/plan.schema.json +0 -128
  43. package/src/schemas/primitives/platform.schema.json +0 -32
  44. package/src/schemas/primitives/result.schema.json +0 -133
  45. package/src/schemas/primitives/runner.schema.json +0 -83
  46. package/src/schemas/primitives/statistics.schema.json +0 -71
  47. package/src/schemas/primitives/system.schema.json +0 -132
  48. package/src/schemas/primitives/target.schema.json +0 -523
@@ -1,1771 +0,0 @@
1
- from enum import Enum
2
- from dataclasses import dataclass
3
- from typing import Optional, Any, List, Dict, TypeVar, Type, Callable, cast
4
- from uuid import UUID
5
- from datetime import datetime
6
- import dateutil.parser
7
-
8
-
9
- T = TypeVar("T")
10
- EnumT = TypeVar("EnumT", bound=Enum)
11
-
12
-
13
- def from_str(x: Any) -> str:
14
- assert isinstance(x, str)
15
- return x
16
-
17
-
18
- def from_none(x: Any) -> Any:
19
- assert x is None
20
- return x
21
-
22
-
23
- def from_union(fs, x):
24
- for f in fs:
25
- try:
26
- return f(x)
27
- except:
28
- pass
29
- assert False
30
-
31
-
32
- def from_bool(x: Any) -> bool:
33
- assert isinstance(x, bool)
34
- return x
35
-
36
-
37
- def to_enum(c: Type[EnumT], x: Any) -> EnumT:
38
- assert isinstance(x, c)
39
- return x.value
40
-
41
-
42
- def from_list(f: Callable[[Any], T], x: Any) -> List[T]:
43
- assert isinstance(x, list)
44
- return [f(y) for y in x]
45
-
46
-
47
- def to_class(c: Type[T], x: Any) -> dict:
48
- assert isinstance(x, c)
49
- return cast(Any, x).to_dict()
50
-
51
-
52
- def from_int(x: Any) -> int:
53
- assert isinstance(x, int) and not isinstance(x, bool)
54
- return x
55
-
56
-
57
- def from_dict(f: Callable[[Any], T], x: Any) -> Dict[str, T]:
58
- assert isinstance(x, dict)
59
- return { k: f(v) for (k, v) in x.items() }
60
-
61
-
62
- def from_float(x: Any) -> float:
63
- assert isinstance(x, (float, int)) and not isinstance(x, bool)
64
- return float(x)
65
-
66
-
67
- def to_float(x: Any) -> float:
68
- assert isinstance(x, (int, float))
69
- return x
70
-
71
-
72
- def from_datetime(x: Any) -> datetime:
73
- return dateutil.parser.parse(x)
74
-
75
-
76
- class AnnotationCategory(Enum):
77
- """The category of this annotation.
78
-
79
- The category of an annotation attached to a comparison.
80
- """
81
- BASELINE_CHANGE = "baselineChange"
82
- DRIFT = "drift"
83
- REMEDIATION = "remediation"
84
- SCANNER_NOTE = "scannerNote"
85
- WAIVER = "waiver"
86
-
87
-
88
- @dataclass
89
- class Annotation:
90
- """An annotation attached to a comparison, providing context or action items."""
91
-
92
- label: str
93
- """Human-readable label for this annotation."""
94
-
95
- category: Optional[AnnotationCategory] = None
96
- """The category of this annotation."""
97
-
98
- description: Optional[str] = None
99
- """Detailed description of the annotation."""
100
-
101
- needs_confirmation: Optional[bool] = None
102
- """Whether this annotation requires human confirmation before acting on it."""
103
-
104
- @staticmethod
105
- def from_dict(obj: Any) -> 'Annotation':
106
- assert isinstance(obj, dict)
107
- label = from_str(obj.get("label"))
108
- category = from_union([AnnotationCategory, from_none], obj.get("category"))
109
- description = from_union([from_str, from_none], obj.get("description"))
110
- needs_confirmation = from_union([from_bool, from_none], obj.get("needsConfirmation"))
111
- return Annotation(label, category, description, needs_confirmation)
112
-
113
- def to_dict(self) -> dict:
114
- result: dict = {}
115
- result["label"] = from_str(self.label)
116
- if self.category is not None:
117
- result["category"] = from_union([lambda x: to_enum(AnnotationCategory, x), from_none], self.category)
118
- if self.description is not None:
119
- result["description"] = from_union([from_str, from_none], self.description)
120
- if self.needs_confirmation is not None:
121
- result["needsConfirmation"] = from_union([from_bool, from_none], self.needs_confirmation)
122
- return result
123
-
124
-
125
- class BaselineDiffState(Enum):
126
- """The state of this baseline in the comparison.
127
-
128
- The state of this component in the comparison.
129
- """
130
- ABSENT = "absent"
131
- NEW = "new"
132
- UNCHANGED = "unchanged"
133
- UPDATED = "updated"
134
-
135
-
136
- @dataclass
137
- class BaselineDiff:
138
- """Comparison of a baseline between sources."""
139
-
140
- name: str
141
- """Name of the baseline being compared."""
142
-
143
- state: BaselineDiffState
144
- """The state of this baseline in the comparison."""
145
-
146
- mapping_source: Optional[str] = None
147
- """The source of any ID mapping used to correlate requirements across baseline versions."""
148
-
149
- new_version: Optional[str] = None
150
- """Version of the baseline in the new source."""
151
-
152
- old_version: Optional[str] = None
153
- """Version of the baseline in the old source."""
154
-
155
- @staticmethod
156
- def from_dict(obj: Any) -> 'BaselineDiff':
157
- assert isinstance(obj, dict)
158
- name = from_str(obj.get("name"))
159
- state = BaselineDiffState(obj.get("state"))
160
- mapping_source = from_union([from_str, from_none], obj.get("mappingSource"))
161
- new_version = from_union([from_str, from_none], obj.get("newVersion"))
162
- old_version = from_union([from_str, from_none], obj.get("oldVersion"))
163
- return BaselineDiff(name, state, mapping_source, new_version, old_version)
164
-
165
- def to_dict(self) -> dict:
166
- result: dict = {}
167
- result["name"] = from_str(self.name)
168
- result["state"] = to_enum(BaselineDiffState, self.state)
169
- if self.mapping_source is not None:
170
- result["mappingSource"] = from_union([from_str, from_none], self.mapping_source)
171
- if self.new_version is not None:
172
- result["newVersion"] = from_union([from_str, from_none], self.new_version)
173
- if self.old_version is not None:
174
- result["oldVersion"] = from_union([from_str, from_none], self.old_version)
175
- return result
176
-
177
-
178
- class ComparisonMode(Enum):
179
- """The mode of comparison being performed.
180
-
181
- The mode of comparison. 'temporal' compares the same target over time. 'baseline'
182
- compares against a golden reference. 'fleet' compares across multiple systems.
183
- 'multiSource' compares outputs from different scanners. 'baselineEvolution' compares two
184
- baseline documents to detect requirement changes between versions. 'systemDrift' compares
185
- two system documents to detect component-level changes.
186
- """
187
- BASELINE = "baseline"
188
- BASELINE_EVOLUTION = "baselineEvolution"
189
- FLEET = "fleet"
190
- MULTI_SOURCE = "multiSource"
191
- SYSTEM_DRIFT = "systemDrift"
192
- TEMPORAL = "temporal"
193
-
194
-
195
- class Op(Enum):
196
- """The type of change operation."""
197
-
198
- ADD = "add"
199
- REMOVE = "remove"
200
- REPLACE = "replace"
201
-
202
-
203
- @dataclass
204
- class FieldChange:
205
- """A single field-level change between two versions of a requirement."""
206
-
207
- op: Op
208
- """The type of change operation."""
209
-
210
- path: str
211
- """JSON Pointer path to the changed field."""
212
-
213
- new_value: Any
214
- """The new value of the field (for 'add' and 'replace' operations)."""
215
-
216
- old_value: Any
217
- """The previous value of the field (for 'remove' and 'replace' operations)."""
218
-
219
- @staticmethod
220
- def from_dict(obj: Any) -> 'FieldChange':
221
- assert isinstance(obj, dict)
222
- op = Op(obj.get("op"))
223
- path = from_str(obj.get("path"))
224
- new_value = obj.get("newValue")
225
- old_value = obj.get("oldValue")
226
- return FieldChange(op, path, new_value, old_value)
227
-
228
- def to_dict(self) -> dict:
229
- result: dict = {}
230
- result["op"] = to_enum(Op, self.op)
231
- result["path"] = from_str(self.path)
232
- if self.new_value is not None:
233
- result["newValue"] = self.new_value
234
- if self.old_value is not None:
235
- result["oldValue"] = self.old_value
236
- return result
237
-
238
-
239
- @dataclass
240
- class ComponentDiff:
241
- """Comparison of a single component between two system document versions."""
242
-
243
- name: str
244
- """Component name used for matching across system versions."""
245
-
246
- state: BaselineDiffState
247
- """The state of this component in the comparison."""
248
-
249
- after: Any
250
- """Component snapshot from the new system document."""
251
-
252
- before: Any
253
- """Component snapshot from the old system document."""
254
-
255
- field_changes: Optional[List[FieldChange]] = None
256
- """Detailed field-level changes between the before and after component snapshots."""
257
-
258
- @staticmethod
259
- def from_dict(obj: Any) -> 'ComponentDiff':
260
- assert isinstance(obj, dict)
261
- name = from_str(obj.get("name"))
262
- state = BaselineDiffState(obj.get("state"))
263
- after = obj.get("after")
264
- before = obj.get("before")
265
- field_changes = from_union([lambda x: from_list(FieldChange.from_dict, x), from_none], obj.get("fieldChanges"))
266
- return ComponentDiff(name, state, after, before, field_changes)
267
-
268
- def to_dict(self) -> dict:
269
- result: dict = {}
270
- result["name"] = from_str(self.name)
271
- result["state"] = to_enum(BaselineDiffState, self.state)
272
- if self.after is not None:
273
- result["after"] = self.after
274
- if self.before is not None:
275
- result["before"] = self.before
276
- if self.field_changes is not None:
277
- result["fieldChanges"] = from_union([lambda x: from_list(lambda x: to_class(FieldChange, x), x), from_none], self.field_changes)
278
- return result
279
-
280
-
281
- class ChangeReason(Enum):
282
- """The reason a requirement's state changed between sources."""
283
-
284
- BASELINE_UPGRADED = "baselineUpgraded"
285
- CONFIG_CHANGED = "configChanged"
286
- CONTROL_MAPPED = "controlMapped"
287
- IMPACT_CHANGED = "impactChanged"
288
- METADATA_CHANGED = "metadataChanged"
289
- OVERRIDE_ADDED = "overrideAdded"
290
- OVERRIDE_EXPIRED = "overrideExpired"
291
- OVERRIDE_MODIFIED = "overrideModified"
292
- OVERRIDE_REMOVED = "overrideRemoved"
293
- RESULT_CHANGED = "resultChanged"
294
- SCANNER_CHANGED = "scannerChanged"
295
- TARGET_CHANGED = "targetChanged"
296
-
297
-
298
- class ConflictResolution(Enum):
299
- """How the conflict was resolved.
300
-
301
- How a conflict between multiple scanner results was resolved.
302
- """
303
- MANUAL = "manual"
304
- MOST_RECENT = "mostRecent"
305
- MOST_SEVERE = "mostSevere"
306
- UNRESOLVED = "unresolved"
307
-
308
-
309
- @dataclass
310
- class Value:
311
- source_index: int
312
- """Zero-based index into the sources array."""
313
-
314
- source_label: str
315
- """Human-readable label for the source."""
316
-
317
- value: Any
318
- """The value reported by this source for the conflicting field."""
319
-
320
- @staticmethod
321
- def from_dict(obj: Any) -> 'Value':
322
- assert isinstance(obj, dict)
323
- source_index = from_int(obj.get("sourceIndex"))
324
- source_label = from_str(obj.get("sourceLabel"))
325
- value = obj.get("value")
326
- return Value(source_index, source_label, value)
327
-
328
- def to_dict(self) -> dict:
329
- result: dict = {}
330
- result["sourceIndex"] = from_int(self.source_index)
331
- result["sourceLabel"] = from_str(self.source_label)
332
- result["value"] = self.value
333
- return result
334
-
335
-
336
- @dataclass
337
- class ScannerConflict:
338
- """A conflict between scanner results for the same requirement."""
339
-
340
- field: str
341
- """The field where the conflict occurs."""
342
-
343
- values: List[Value]
344
- """The conflicting values from each source."""
345
-
346
- resolution: Optional[ConflictResolution] = None
347
- """How the conflict was resolved."""
348
-
349
- resolved_index: Optional[int] = None
350
- """Index of the source whose value was chosen as the resolution."""
351
-
352
- @staticmethod
353
- def from_dict(obj: Any) -> 'ScannerConflict':
354
- assert isinstance(obj, dict)
355
- field = from_str(obj.get("field"))
356
- values = from_list(Value.from_dict, obj.get("values"))
357
- resolution = from_union([ConflictResolution, from_none], obj.get("resolution"))
358
- resolved_index = from_union([from_int, from_none], obj.get("resolvedIndex"))
359
- return ScannerConflict(field, values, resolution, resolved_index)
360
-
361
- def to_dict(self) -> dict:
362
- result: dict = {}
363
- result["field"] = from_str(self.field)
364
- result["values"] = from_list(lambda x: to_class(Value, x), self.values)
365
- if self.resolution is not None:
366
- result["resolution"] = from_union([lambda x: to_enum(ConflictResolution, x), from_none], self.resolution)
367
- if self.resolved_index is not None:
368
- result["resolvedIndex"] = from_union([from_int, from_none], self.resolved_index)
369
- return result
370
-
371
-
372
- class MatchStrategy(Enum):
373
- """The strategy that was used to match this requirement across sources.
374
-
375
- The strategy used to match requirements across sources. 'exactId' matches by identical
376
- IDs. 'mappedId' uses an ID mapping table. 'cciMatch'/'nistMatch' match by framework
377
- identifiers. 'fuzzyTitle'/'fuzzyContent' use text similarity.
378
-
379
- The primary strategy used to match requirements across sources.
380
- """
381
- CCI_MATCH = "cciMatch"
382
- EXACT_ID = "exactId"
383
- FUZZY_CONTENT = "fuzzyContent"
384
- FUZZY_TITLE = "fuzzyTitle"
385
- MAPPED_ID = "mappedId"
386
- NIST_MATCH = "nistMatch"
387
-
388
-
389
- class RequirementState(Enum):
390
- """The state of this requirement in the comparison.
391
-
392
- SARIF-compatible vocabulary extended for security. 'new' = present only in new source,
393
- 'absent' = present only in old, 'unchanged' = same effective status, 'updated' = status
394
- changed (generic), 'fixed' = was failing now passing, 'regressed' = was passing now
395
- failing, 'moved' = reorganized same content, 'split'/'merged' = reserved for v1.1.
396
- """
397
- ABSENT = "absent"
398
- FIXED = "fixed"
399
- MERGED = "merged"
400
- MOVED = "moved"
401
- NEW = "new"
402
- REGRESSED = "regressed"
403
- SPLIT = "split"
404
- UNCHANGED = "unchanged"
405
- UPDATED = "updated"
406
-
407
-
408
- @dataclass
409
- class RequirementDiff:
410
- """A comparison of a single requirement between sources, including state, changes, and full
411
- before/after snapshots.
412
- """
413
- after: Any
414
- """The requirement as it appeared in the new source. Null when state is 'absent'."""
415
-
416
- before: Any
417
- """The requirement as it appeared in the old/reference source. Null when state is 'new'."""
418
-
419
- change_reasons: List[ChangeReason]
420
- """The reasons for the state change."""
421
-
422
- field_changes: List[FieldChange]
423
- """Detailed field-level changes between the before and after versions."""
424
-
425
- id: str
426
- """The canonical requirement identifier used for this diff."""
427
-
428
- state: RequirementState
429
- """The state of this requirement in the comparison."""
430
-
431
- after_sensitive: Optional[Dict[str, Any]] = None
432
- """Sensitive data from the new source that should not be included in the main after snapshot."""
433
-
434
- annotation_ids: Optional[List[str]] = None
435
- """IDs of annotations attached to this requirement diff."""
436
-
437
- before_sensitive: Optional[Dict[str, Any]] = None
438
- """Sensitive data from the old source that should not be included in the main before
439
- snapshot.
440
- """
441
- conflicts: Optional[List[ScannerConflict]] = None
442
- """Conflicts between multiple scanner results for this requirement."""
443
-
444
- match_confidence: Optional[float] = None
445
- """Confidence score for the match (0-1)."""
446
-
447
- match_manual: Optional[bool] = None
448
- """Whether the match was manually confirmed by a human."""
449
-
450
- match_strategy: Optional[MatchStrategy] = None
451
- """The strategy that was used to match this requirement across sources."""
452
-
453
- new_effective_status: Optional[str] = None
454
- """The effective status of the requirement in the new source."""
455
-
456
- new_id: Optional[str] = None
457
- """The requirement ID in the new source, if different from the canonical id."""
458
-
459
- new_impact: Optional[float] = None
460
- """The impact score of the requirement in the new source (0-1)."""
461
-
462
- old_effective_status: Optional[str] = None
463
- """The effective status of the requirement in the old source."""
464
-
465
- old_id: Optional[str] = None
466
- """The requirement ID in the old source, if different from the canonical id."""
467
-
468
- old_impact: Optional[float] = None
469
- """The impact score of the requirement in the old source (0-1)."""
470
-
471
- source_index: Optional[int] = None
472
- """Index into the sources array for multi-source comparisons."""
473
-
474
- title: Optional[str] = None
475
- """The requirement title for human readability."""
476
-
477
- @staticmethod
478
- def from_dict(obj: Any) -> 'RequirementDiff':
479
- assert isinstance(obj, dict)
480
- after = obj.get("after")
481
- before = obj.get("before")
482
- change_reasons = from_list(ChangeReason, obj.get("changeReasons"))
483
- field_changes = from_list(FieldChange.from_dict, obj.get("fieldChanges"))
484
- id = from_str(obj.get("id"))
485
- state = RequirementState(obj.get("state"))
486
- after_sensitive = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("afterSensitive"))
487
- annotation_ids = from_union([lambda x: from_list(from_str, x), from_none], obj.get("annotationIds"))
488
- before_sensitive = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("beforeSensitive"))
489
- conflicts = from_union([lambda x: from_list(ScannerConflict.from_dict, x), from_none], obj.get("conflicts"))
490
- match_confidence = from_union([from_float, from_none], obj.get("matchConfidence"))
491
- match_manual = from_union([from_bool, from_none], obj.get("matchManual"))
492
- match_strategy = from_union([MatchStrategy, from_none], obj.get("matchStrategy"))
493
- new_effective_status = from_union([from_str, from_none], obj.get("newEffectiveStatus"))
494
- new_id = from_union([from_str, from_none], obj.get("newId"))
495
- new_impact = from_union([from_float, from_none], obj.get("newImpact"))
496
- old_effective_status = from_union([from_str, from_none], obj.get("oldEffectiveStatus"))
497
- old_id = from_union([from_str, from_none], obj.get("oldId"))
498
- old_impact = from_union([from_float, from_none], obj.get("oldImpact"))
499
- source_index = from_union([from_int, from_none], obj.get("sourceIndex"))
500
- title = from_union([from_str, from_none], obj.get("title"))
501
- return RequirementDiff(after, before, change_reasons, field_changes, id, state, after_sensitive, annotation_ids, before_sensitive, conflicts, match_confidence, match_manual, match_strategy, new_effective_status, new_id, new_impact, old_effective_status, old_id, old_impact, source_index, title)
502
-
503
- def to_dict(self) -> dict:
504
- result: dict = {}
505
- result["after"] = self.after
506
- result["before"] = self.before
507
- result["changeReasons"] = from_list(lambda x: to_enum(ChangeReason, x), self.change_reasons)
508
- result["fieldChanges"] = from_list(lambda x: to_class(FieldChange, x), self.field_changes)
509
- result["id"] = from_str(self.id)
510
- result["state"] = to_enum(RequirementState, self.state)
511
- if self.after_sensitive is not None:
512
- result["afterSensitive"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.after_sensitive)
513
- if self.annotation_ids is not None:
514
- result["annotationIds"] = from_union([lambda x: from_list(from_str, x), from_none], self.annotation_ids)
515
- if self.before_sensitive is not None:
516
- result["beforeSensitive"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.before_sensitive)
517
- if self.conflicts is not None:
518
- result["conflicts"] = from_union([lambda x: from_list(lambda x: to_class(ScannerConflict, x), x), from_none], self.conflicts)
519
- if self.match_confidence is not None:
520
- result["matchConfidence"] = from_union([to_float, from_none], self.match_confidence)
521
- if self.match_manual is not None:
522
- result["matchManual"] = from_union([from_bool, from_none], self.match_manual)
523
- if self.match_strategy is not None:
524
- result["matchStrategy"] = from_union([lambda x: to_enum(MatchStrategy, x), from_none], self.match_strategy)
525
- if self.new_effective_status is not None:
526
- result["newEffectiveStatus"] = from_union([from_str, from_none], self.new_effective_status)
527
- if self.new_id is not None:
528
- result["newId"] = from_union([from_str, from_none], self.new_id)
529
- if self.new_impact is not None:
530
- result["newImpact"] = from_union([to_float, from_none], self.new_impact)
531
- if self.old_effective_status is not None:
532
- result["oldEffectiveStatus"] = from_union([from_str, from_none], self.old_effective_status)
533
- if self.old_id is not None:
534
- result["oldId"] = from_union([from_str, from_none], self.old_id)
535
- if self.old_impact is not None:
536
- result["oldImpact"] = from_union([to_float, from_none], self.old_impact)
537
- if self.source_index is not None:
538
- result["sourceIndex"] = from_union([from_int, from_none], self.source_index)
539
- if self.title is not None:
540
- result["title"] = from_union([from_str, from_none], self.title)
541
- return result
542
-
543
-
544
- class FormatVersion(Enum):
545
- THE_100 = "1.0.0"
546
-
547
-
548
- @dataclass
549
- class Generator:
550
- """Information about the tool that generated this comparison.
551
-
552
- Information about the tool that generated this HDF file.
553
- """
554
- name: str
555
- """The name of the software that produced this HDF file. Example: 'gosec-to-hdf'."""
556
-
557
- version: str
558
- """The version of the tool. Example: '5.22.3'."""
559
-
560
- @staticmethod
561
- def from_dict(obj: Any) -> 'Generator':
562
- assert isinstance(obj, dict)
563
- name = from_str(obj.get("name"))
564
- version = from_str(obj.get("version"))
565
- return Generator(name, version)
566
-
567
- def to_dict(self) -> dict:
568
- result: dict = {}
569
- result["name"] = from_str(self.name)
570
- result["version"] = from_str(self.version)
571
- return result
572
-
573
-
574
- class HashAlgorithm(Enum):
575
- """The hash algorithm used for the checksum.
576
-
577
- Supported cryptographic hash algorithms for checksums and integrity verification.
578
- """
579
- SHA256 = "sha256"
580
- SHA384 = "sha384"
581
- SHA512 = "sha512"
582
-
583
-
584
- @dataclass
585
- class Integrity:
586
- """Cryptographic integrity information for verifying this comparison document.
587
-
588
- Cryptographic integrity information for verifying the HDF file has not been tampered
589
- with. If algorithm is provided, checksum must also be provided, and vice versa.
590
- """
591
- algorithm: Optional[HashAlgorithm] = None
592
- """The hash algorithm used for the checksum."""
593
-
594
- checksum: Optional[str] = None
595
- """The checksum value."""
596
-
597
- signature: Optional[str] = None
598
- """Optional cryptographic signature."""
599
-
600
- signed_by: Optional[str] = None
601
- """Identifier of who signed this file."""
602
-
603
- @staticmethod
604
- def from_dict(obj: Any) -> 'Integrity':
605
- assert isinstance(obj, dict)
606
- algorithm = from_union([HashAlgorithm, from_none], obj.get("algorithm"))
607
- checksum = from_union([from_str, from_none], obj.get("checksum"))
608
- signature = from_union([from_str, from_none], obj.get("signature"))
609
- signed_by = from_union([from_str, from_none], obj.get("signedBy"))
610
- return Integrity(algorithm, checksum, signature, signed_by)
611
-
612
- def to_dict(self) -> dict:
613
- result: dict = {}
614
- if self.algorithm is not None:
615
- result["algorithm"] = from_union([lambda x: to_enum(HashAlgorithm, x), from_none], self.algorithm)
616
- if self.checksum is not None:
617
- result["checksum"] = from_union([from_str, from_none], self.checksum)
618
- if self.signature is not None:
619
- result["signature"] = from_union([from_str, from_none], self.signature)
620
- if self.signed_by is not None:
621
- result["signedBy"] = from_union([from_str, from_none], self.signed_by)
622
- return result
623
-
624
-
625
- @dataclass
626
- class MatchingConfig:
627
- """Configuration for how requirements were matched across sources.
628
-
629
- Configuration for how requirements are matched across sources.
630
- """
631
- primary_strategy: MatchStrategy
632
- """The primary strategy used to match requirements across sources."""
633
-
634
- fallback_strategies: Optional[List[MatchStrategy]] = None
635
- """Ordered list of fallback strategies tried when the primary strategy fails to find a match."""
636
-
637
- fingerprint_fields: Optional[List[str]] = None
638
- """Fields used to compute a fingerprint for fuzzy matching."""
639
-
640
- mapping_table_uri: Optional[str] = None
641
- """URI pointing to an external mapping table used for ID translation."""
642
-
643
- minimum_confidence: Optional[float] = None
644
- """Minimum confidence score (0-1) required to accept a match."""
645
-
646
- @staticmethod
647
- def from_dict(obj: Any) -> 'MatchingConfig':
648
- assert isinstance(obj, dict)
649
- primary_strategy = MatchStrategy(obj.get("primaryStrategy"))
650
- fallback_strategies = from_union([lambda x: from_list(MatchStrategy, x), from_none], obj.get("fallbackStrategies"))
651
- fingerprint_fields = from_union([lambda x: from_list(from_str, x), from_none], obj.get("fingerprintFields"))
652
- mapping_table_uri = from_union([from_str, from_none], obj.get("mappingTableUri"))
653
- minimum_confidence = from_union([from_float, from_none], obj.get("minimumConfidence"))
654
- return MatchingConfig(primary_strategy, fallback_strategies, fingerprint_fields, mapping_table_uri, minimum_confidence)
655
-
656
- def to_dict(self) -> dict:
657
- result: dict = {}
658
- result["primaryStrategy"] = to_enum(MatchStrategy, self.primary_strategy)
659
- if self.fallback_strategies is not None:
660
- result["fallbackStrategies"] = from_union([lambda x: from_list(lambda x: to_enum(MatchStrategy, x), x), from_none], self.fallback_strategies)
661
- if self.fingerprint_fields is not None:
662
- result["fingerprintFields"] = from_union([lambda x: from_list(from_str, x), from_none], self.fingerprint_fields)
663
- if self.mapping_table_uri is not None:
664
- result["mappingTableUri"] = from_union([from_str, from_none], self.mapping_table_uri)
665
- if self.minimum_confidence is not None:
666
- result["minimumConfidence"] = from_union([to_float, from_none], self.minimum_confidence)
667
- return result
668
-
669
-
670
- class PackageDiffState(Enum):
671
- """The state of this package: added (new in new SBOM), removed (absent from new SBOM),
672
- updated (version changed), unchanged.
673
- """
674
- ADDED = "added"
675
- REMOVED = "removed"
676
- UNCHANGED = "unchanged"
677
- UPDATED = "updated"
678
-
679
-
680
- @dataclass
681
- class PackageDiff:
682
- """Comparison of a single package between two SBOM versions, matched by purl."""
683
-
684
- purl: str
685
- """Package URL (purl) used as the identity key for matching across SBOMs."""
686
-
687
- state: PackageDiffState
688
- """The state of this package: added (new in new SBOM), removed (absent from new SBOM),
689
- updated (version changed), unchanged.
690
- """
691
- licenses: Optional[List[str]] = None
692
- """License identifiers for this package."""
693
-
694
- name: Optional[str] = None
695
- """Human-readable package name."""
696
-
697
- new_version: Optional[str] = None
698
- """Package version in the new SBOM."""
699
-
700
- old_version: Optional[str] = None
701
- """Package version in the old SBOM."""
702
-
703
- @staticmethod
704
- def from_dict(obj: Any) -> 'PackageDiff':
705
- assert isinstance(obj, dict)
706
- purl = from_str(obj.get("purl"))
707
- state = PackageDiffState(obj.get("state"))
708
- licenses = from_union([lambda x: from_list(from_str, x), from_none], obj.get("licenses"))
709
- name = from_union([from_str, from_none], obj.get("name"))
710
- new_version = from_union([from_str, from_none], obj.get("newVersion"))
711
- old_version = from_union([from_str, from_none], obj.get("oldVersion"))
712
- return PackageDiff(purl, state, licenses, name, new_version, old_version)
713
-
714
- def to_dict(self) -> dict:
715
- result: dict = {}
716
- result["purl"] = from_str(self.purl)
717
- result["state"] = to_enum(PackageDiffState, self.state)
718
- if self.licenses is not None:
719
- result["licenses"] = from_union([lambda x: from_list(from_str, x), from_none], self.licenses)
720
- if self.name is not None:
721
- result["name"] = from_union([from_str, from_none], self.name)
722
- if self.new_version is not None:
723
- result["newVersion"] = from_union([from_str, from_none], self.new_version)
724
- if self.old_version is not None:
725
- result["oldVersion"] = from_union([from_str, from_none], self.old_version)
726
- return result
727
-
728
-
729
- @dataclass
730
- class BaselineRef:
731
- """Reference to the baseline used in this source assessment."""
732
-
733
- name: str
734
- """Name of the baseline used in this source."""
735
-
736
- version: Optional[str] = None
737
- """Version of the baseline used in this source."""
738
-
739
- @staticmethod
740
- def from_dict(obj: Any) -> 'BaselineRef':
741
- assert isinstance(obj, dict)
742
- name = from_str(obj.get("name"))
743
- version = from_union([from_str, from_none], obj.get("version"))
744
- return BaselineRef(name, version)
745
-
746
- def to_dict(self) -> dict:
747
- result: dict = {}
748
- result["name"] = from_str(self.name)
749
- if self.version is not None:
750
- result["version"] = from_union([from_str, from_none], self.version)
751
- return result
752
-
753
-
754
- @dataclass
755
- class Checksum:
756
- """Cryptographic checksum of the source document for integrity verification.
757
-
758
- Cryptographic checksum for baseline integrity verification.
759
- """
760
- algorithm: HashAlgorithm
761
- """The hash algorithm used for the checksum."""
762
-
763
- value: str
764
- """The checksum value."""
765
-
766
- @staticmethod
767
- def from_dict(obj: Any) -> 'Checksum':
768
- assert isinstance(obj, dict)
769
- algorithm = HashAlgorithm(obj.get("algorithm"))
770
- value = from_str(obj.get("value"))
771
- return Checksum(algorithm, value)
772
-
773
- def to_dict(self) -> dict:
774
- result: dict = {}
775
- result["algorithm"] = to_enum(HashAlgorithm, self.algorithm)
776
- result["value"] = from_str(self.value)
777
- return result
778
-
779
-
780
- class TypeEnum(Enum):
781
- """The type of identifier. Use 'email' for email addresses, 'username' for user accounts,
782
- 'system' for automated systems, 'simple' for basic string identifiers without additional
783
- classification, or 'other' for custom identity systems.
784
- """
785
- EMAIL = "email"
786
- OTHER = "other"
787
- SIMPLE = "simple"
788
- SYSTEM = "system"
789
- USERNAME = "username"
790
-
791
-
792
- @dataclass
793
- class Identity:
794
- """Identity of the person or system that approved this override.
795
-
796
- Represents an identity that performed an action, such as capturing evidence or applying
797
- an override.
798
-
799
- Team or individual responsible for this component. Enables per-component ownership when
800
- different teams manage different parts of a system.
801
- """
802
- identifier: str
803
- """The identifier value. Example: 'user@example.com', 'jdoe', 'automated-scanner-01'."""
804
-
805
- type: TypeEnum
806
- """The type of identifier. Use 'email' for email addresses, 'username' for user accounts,
807
- 'system' for automated systems, 'simple' for basic string identifiers without additional
808
- classification, or 'other' for custom identity systems.
809
- """
810
- description: Optional[str] = None
811
- """Optional description of the identity or identity system, particularly useful when type is
812
- 'other'.
813
- """
814
-
815
- @staticmethod
816
- def from_dict(obj: Any) -> 'Identity':
817
- assert isinstance(obj, dict)
818
- identifier = from_str(obj.get("identifier"))
819
- type = TypeEnum(obj.get("type"))
820
- description = from_union([from_str, from_none], obj.get("description"))
821
- return Identity(identifier, type, description)
822
-
823
- def to_dict(self) -> dict:
824
- result: dict = {}
825
- result["identifier"] = from_str(self.identifier)
826
- result["type"] = to_enum(TypeEnum, self.type)
827
- if self.description is not None:
828
- result["description"] = from_union([from_str, from_none], self.description)
829
- return result
830
-
831
-
832
- @dataclass
833
- class InputOverride:
834
- """An override of a baseline input value for a specific component. Enables system-specific
835
- tailoring of baseline parameters.
836
- """
837
- input_name: str
838
- """Name of the input being overridden. Must match an Input.name in the referenced baseline."""
839
-
840
- value: Any
841
- """The overridden value. Should match the type of the original input."""
842
-
843
- approved_by: Optional[Identity] = None
844
- """Identity of the person or system that approved this override."""
845
-
846
- baseline_ref: Optional[str] = None
847
- """Name of the baseline this override applies to. If omitted, applies to all baselines that
848
- define this input.
849
- """
850
- justification: Optional[str] = None
851
- """Rationale for why this override is needed."""
852
-
853
- @staticmethod
854
- def from_dict(obj: Any) -> 'InputOverride':
855
- assert isinstance(obj, dict)
856
- input_name = from_str(obj.get("inputName"))
857
- value = obj.get("value")
858
- approved_by = from_union([Identity.from_dict, from_none], obj.get("approvedBy"))
859
- baseline_ref = from_union([from_str, from_none], obj.get("baselineRef"))
860
- justification = from_union([from_str, from_none], obj.get("justification"))
861
- return InputOverride(input_name, value, approved_by, baseline_ref, justification)
862
-
863
- def to_dict(self) -> dict:
864
- result: dict = {}
865
- result["inputName"] = from_str(self.input_name)
866
- result["value"] = self.value
867
- if self.approved_by is not None:
868
- result["approvedBy"] = from_union([lambda x: to_class(Identity, x), from_none], self.approved_by)
869
- if self.baseline_ref is not None:
870
- result["baselineRef"] = from_union([from_str, from_none], self.baseline_ref)
871
- if self.justification is not None:
872
- result["justification"] = from_union([from_str, from_none], self.justification)
873
- return result
874
-
875
-
876
- class CloudProvider(Enum):
877
- AWS = "aws"
878
- AZURE = "azure"
879
- GCP = "gcp"
880
- OCI = "oci"
881
- OTHER = "other"
882
-
883
-
884
- class SbomFormat(Enum):
885
- """Format of the SBOM (embedded or referenced). Required when sbom or sbomRef is present."""
886
-
887
- CYCLONEDX = "cyclonedx"
888
- SPDX = "spdx"
889
-
890
-
891
- class Description(Enum):
892
- """IP address of the host."""
893
-
894
- APPLICATION = "application"
895
- ARTIFACT = "artifact"
896
- CLOUD_ACCOUNT = "cloudAccount"
897
- CLOUD_RESOURCE = "cloudResource"
898
- CONTAINER_IMAGE = "containerImage"
899
- CONTAINER_INSTANCE = "containerInstance"
900
- CONTAINER_PLATFORM = "containerPlatform"
901
- DATABASE = "database"
902
- HOST = "host"
903
- NETWORK = "network"
904
- REPOSITORY = "repository"
905
-
906
-
907
- @dataclass
908
- class Component:
909
- """A system component. Uses discriminated union pattern with 'type' field as discriminator.
910
- Superset of Target with identity, external IDs, and SBOM support.
911
-
912
- A physical or virtual server, workstation, or network device.
913
-
914
- Base properties shared by all component types. Extends the Target concept with stable
915
- identity, external references, and SBOM embedding.
916
-
917
- A static container image (not running).
918
-
919
- A running container instance.
920
-
921
- A container orchestration platform (Kubernetes, OpenShift, ECS, etc.).
922
-
923
- A cloud provider account (AWS account, Azure subscription, GCP project).
924
-
925
- A specific cloud resource (EC2 instance, S3 bucket, Azure VM, etc.).
926
-
927
- A code repository (for SAST tools).
928
-
929
- A running application or API (for DAST tools).
930
-
931
- A software artifact or dependency (for SCA tools).
932
-
933
- A network segment or network device.
934
-
935
- A database instance.
936
- """
937
- name: str
938
- """Human-readable name for this component."""
939
-
940
- type: Description
941
- """Component type discriminator. Same values as Target types."""
942
-
943
- baseline_refs: Optional[List[str]] = None
944
- """Names of baselines that apply to this component."""
945
-
946
- component_id: Optional[UUID] = None
947
- """Stable UUID (RFC 4122) for this component. Required in hdf-system documents, optional in
948
- hdf-results. Enables cross-document correlation, diffing, and data flow references.
949
- """
950
- description: Optional[str] = None
951
- """Description of this component's role or purpose."""
952
-
953
- external_ids: Optional[Dict[str, str]] = None
954
- """Map of external identifier scheme to value. Well-known schemes: aws (instance ID), azure
955
- (resource ID), cmdb (asset ID), emass (system ID), cve (CVE ID). Custom schemes are
956
- allowed.
957
- """
958
- input_overrides: Optional[List[InputOverride]] = None
959
- """System-specific overrides for baseline input values."""
960
-
961
- labels: Optional[Dict[str, str]] = None
962
- """Optional key-value labels for flexible grouping. Well-known keys: system, component,
963
- environment, region, team. Values must be strings.
964
- """
965
- owner: Optional[Identity] = None
966
- """Team or individual responsible for this component. Enables per-component ownership when
967
- different teams manage different parts of a system.
968
- """
969
- sbom: Any
970
- """Embedded CycloneDX or SPDX SBOM document representing this component's software
971
- inventory. The sbomFormat field determines which format constraints apply.
972
- """
973
- sbom_format: Optional[SbomFormat] = None
974
- """Format of the SBOM (embedded or referenced). Required when sbom or sbomRef is present."""
975
-
976
- sbom_ref: Optional[str] = None
977
- """URI reference to an external CycloneDX or SPDX SBOM document for this component. May be a
978
- relative path, absolute URI, or fragment identifier.
979
- """
980
- target_selector: Optional[Dict[str, str]] = None
981
- """Label selector to match targets belonging to this component during migration. Targets
982
- with matching labels are automatically included.
983
- """
984
- fqdn: Optional[str] = None
985
- """Fully qualified domain name."""
986
-
987
- ip_address: Optional[str] = None
988
- """IP address of the host."""
989
-
990
- mac_address: Optional[str] = None
991
- """MAC address in colon-separated hexadecimal format."""
992
-
993
- os_name: Optional[str] = None
994
- """Operating system name."""
995
-
996
- os_version: Optional[str] = None
997
- """Operating system version."""
998
-
999
- digest: Optional[str] = None
1000
- """Image digest for immutable reference."""
1001
-
1002
- image_id: Optional[str] = None
1003
- """Container image ID."""
1004
-
1005
- registry: Optional[str] = None
1006
- """Container registry. Example: 'docker.io'."""
1007
-
1008
- repository: Optional[str] = None
1009
- """Repository name. Example: 'library/nginx'."""
1010
-
1011
- tag: Optional[str] = None
1012
- """Image tag. Example: '1.25'."""
1013
-
1014
- container_id: Optional[str] = None
1015
- """Running container ID."""
1016
-
1017
- image: Optional[str] = None
1018
- """Image the container was started from."""
1019
-
1020
- runtime: Optional[str] = None
1021
- """Container runtime. Example: 'docker', 'containerd', 'cri-o'."""
1022
-
1023
- cluster_name: Optional[str] = None
1024
- """Cluster name."""
1025
-
1026
- namespace: Optional[str] = None
1027
- """Namespace within the cluster, if applicable."""
1028
-
1029
- platform_type: Optional[str] = None
1030
- """Platform type. Example: 'kubernetes', 'openshift', 'ecs', 'docker-swarm'."""
1031
-
1032
- version: Optional[str] = None
1033
- """Platform version.
1034
-
1035
- Application version.
1036
-
1037
- Package version.
1038
-
1039
- Database version.
1040
- """
1041
- account_id: Optional[str] = None
1042
- """Cloud account identifier."""
1043
-
1044
- provider: Optional[CloudProvider] = None
1045
- """Cloud provider."""
1046
-
1047
- region: Optional[str] = None
1048
- """Cloud region, if applicable.
1049
-
1050
- Cloud region where the resource resides.
1051
- """
1052
- arn: Optional[str] = None
1053
- """Amazon Resource Name (AWS only)."""
1054
-
1055
- resource_id: Optional[str] = None
1056
- """Provider-specific resource identifier."""
1057
-
1058
- resource_type: Optional[str] = None
1059
- """Type of cloud resource. Example: 'ec2:instance', 's3:bucket'."""
1060
-
1061
- branch: Optional[str] = None
1062
- """Branch that was scanned."""
1063
-
1064
- commit: Optional[str] = None
1065
- """Commit SHA that was scanned."""
1066
-
1067
- url: Optional[str] = None
1068
- """Repository URL.
1069
-
1070
- Application URL (for DAST tools).
1071
- """
1072
- environment: Optional[str] = None
1073
- """Environment. Example: 'production', 'staging', 'development'."""
1074
-
1075
- checksum: Optional[str] = None
1076
- """Package checksum for verification."""
1077
-
1078
- package_manager: Optional[str] = None
1079
- """Package manager. Example: 'npm', 'maven', 'pip', 'nuget'."""
1080
-
1081
- package_name: Optional[str] = None
1082
- """Package name."""
1083
-
1084
- cidr: Optional[str] = None
1085
- """Network CIDR block."""
1086
-
1087
- gateway: Optional[str] = None
1088
- """Network gateway address."""
1089
-
1090
- engine: Optional[str] = None
1091
- """Database engine. Example: 'postgresql', 'mysql', 'oracle', 'mssql'."""
1092
-
1093
- host: Optional[str] = None
1094
- """Database host."""
1095
-
1096
- port: Optional[int] = None
1097
- """Database port."""
1098
-
1099
- @staticmethod
1100
- def from_dict(obj: Any) -> 'Component':
1101
- assert isinstance(obj, dict)
1102
- name = from_str(obj.get("name"))
1103
- type = Description(obj.get("type"))
1104
- baseline_refs = from_union([lambda x: from_list(from_str, x), from_none], obj.get("baselineRefs"))
1105
- component_id = from_union([lambda x: UUID(x), from_none], obj.get("componentId"))
1106
- description = from_union([from_str, from_none], obj.get("description"))
1107
- external_ids = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("externalIds"))
1108
- input_overrides = from_union([lambda x: from_list(InputOverride.from_dict, x), from_none], obj.get("inputOverrides"))
1109
- labels = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("labels"))
1110
- owner = from_union([Identity.from_dict, from_none], obj.get("owner"))
1111
- sbom = obj.get("sbom")
1112
- sbom_format = from_union([SbomFormat, from_none], obj.get("sbomFormat"))
1113
- sbom_ref = from_union([from_str, from_none], obj.get("sbomRef"))
1114
- target_selector = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("targetSelector"))
1115
- fqdn = from_union([from_str, from_none], obj.get("fqdn"))
1116
- ip_address = from_union([from_str, from_none], obj.get("ipAddress"))
1117
- mac_address = from_union([from_str, from_none], obj.get("macAddress"))
1118
- os_name = from_union([from_str, from_none], obj.get("osName"))
1119
- os_version = from_union([from_str, from_none], obj.get("osVersion"))
1120
- digest = from_union([from_str, from_none], obj.get("digest"))
1121
- image_id = from_union([from_str, from_none], obj.get("imageId"))
1122
- registry = from_union([from_str, from_none], obj.get("registry"))
1123
- repository = from_union([from_str, from_none], obj.get("repository"))
1124
- tag = from_union([from_str, from_none], obj.get("tag"))
1125
- container_id = from_union([from_str, from_none], obj.get("containerId"))
1126
- image = from_union([from_str, from_none], obj.get("image"))
1127
- runtime = from_union([from_str, from_none], obj.get("runtime"))
1128
- cluster_name = from_union([from_str, from_none], obj.get("clusterName"))
1129
- namespace = from_union([from_str, from_none], obj.get("namespace"))
1130
- platform_type = from_union([from_str, from_none], obj.get("platformType"))
1131
- version = from_union([from_str, from_none], obj.get("version"))
1132
- account_id = from_union([from_str, from_none], obj.get("accountId"))
1133
- provider = from_union([from_none, CloudProvider], obj.get("provider"))
1134
- region = from_union([from_str, from_none], obj.get("region"))
1135
- arn = from_union([from_str, from_none], obj.get("arn"))
1136
- resource_id = from_union([from_str, from_none], obj.get("resourceId"))
1137
- resource_type = from_union([from_str, from_none], obj.get("resourceType"))
1138
- branch = from_union([from_str, from_none], obj.get("branch"))
1139
- commit = from_union([from_str, from_none], obj.get("commit"))
1140
- url = from_union([from_str, from_none], obj.get("url"))
1141
- environment = from_union([from_str, from_none], obj.get("environment"))
1142
- checksum = from_union([from_str, from_none], obj.get("checksum"))
1143
- package_manager = from_union([from_str, from_none], obj.get("packageManager"))
1144
- package_name = from_union([from_str, from_none], obj.get("packageName"))
1145
- cidr = from_union([from_str, from_none], obj.get("cidr"))
1146
- gateway = from_union([from_str, from_none], obj.get("gateway"))
1147
- engine = from_union([from_str, from_none], obj.get("engine"))
1148
- host = from_union([from_str, from_none], obj.get("host"))
1149
- port = from_union([from_int, from_none], obj.get("port"))
1150
- return Component(name, type, baseline_refs, component_id, description, external_ids, input_overrides, labels, owner, sbom, sbom_format, sbom_ref, target_selector, fqdn, ip_address, mac_address, os_name, os_version, digest, image_id, registry, repository, tag, container_id, image, runtime, cluster_name, namespace, platform_type, version, account_id, provider, region, arn, resource_id, resource_type, branch, commit, url, environment, checksum, package_manager, package_name, cidr, gateway, engine, host, port)
1151
-
1152
- def to_dict(self) -> dict:
1153
- result: dict = {}
1154
- result["name"] = from_str(self.name)
1155
- result["type"] = to_enum(Description, self.type)
1156
- if self.baseline_refs is not None:
1157
- result["baselineRefs"] = from_union([lambda x: from_list(from_str, x), from_none], self.baseline_refs)
1158
- if self.component_id is not None:
1159
- result["componentId"] = from_union([lambda x: str(x), from_none], self.component_id)
1160
- if self.description is not None:
1161
- result["description"] = from_union([from_str, from_none], self.description)
1162
- if self.external_ids is not None:
1163
- result["externalIds"] = from_union([lambda x: from_dict(from_str, x), from_none], self.external_ids)
1164
- if self.input_overrides is not None:
1165
- result["inputOverrides"] = from_union([lambda x: from_list(lambda x: to_class(InputOverride, x), x), from_none], self.input_overrides)
1166
- if self.labels is not None:
1167
- result["labels"] = from_union([lambda x: from_dict(from_str, x), from_none], self.labels)
1168
- if self.owner is not None:
1169
- result["owner"] = from_union([lambda x: to_class(Identity, x), from_none], self.owner)
1170
- if self.sbom is not None:
1171
- result["sbom"] = self.sbom
1172
- if self.sbom_format is not None:
1173
- result["sbomFormat"] = from_union([lambda x: to_enum(SbomFormat, x), from_none], self.sbom_format)
1174
- if self.sbom_ref is not None:
1175
- result["sbomRef"] = from_union([from_str, from_none], self.sbom_ref)
1176
- if self.target_selector is not None:
1177
- result["targetSelector"] = from_union([lambda x: from_dict(from_str, x), from_none], self.target_selector)
1178
- if self.fqdn is not None:
1179
- result["fqdn"] = from_union([from_str, from_none], self.fqdn)
1180
- if self.ip_address is not None:
1181
- result["ipAddress"] = from_union([from_str, from_none], self.ip_address)
1182
- if self.mac_address is not None:
1183
- result["macAddress"] = from_union([from_str, from_none], self.mac_address)
1184
- if self.os_name is not None:
1185
- result["osName"] = from_union([from_str, from_none], self.os_name)
1186
- if self.os_version is not None:
1187
- result["osVersion"] = from_union([from_str, from_none], self.os_version)
1188
- if self.digest is not None:
1189
- result["digest"] = from_union([from_str, from_none], self.digest)
1190
- if self.image_id is not None:
1191
- result["imageId"] = from_union([from_str, from_none], self.image_id)
1192
- if self.registry is not None:
1193
- result["registry"] = from_union([from_str, from_none], self.registry)
1194
- if self.repository is not None:
1195
- result["repository"] = from_union([from_str, from_none], self.repository)
1196
- if self.tag is not None:
1197
- result["tag"] = from_union([from_str, from_none], self.tag)
1198
- if self.container_id is not None:
1199
- result["containerId"] = from_union([from_str, from_none], self.container_id)
1200
- if self.image is not None:
1201
- result["image"] = from_union([from_str, from_none], self.image)
1202
- if self.runtime is not None:
1203
- result["runtime"] = from_union([from_str, from_none], self.runtime)
1204
- if self.cluster_name is not None:
1205
- result["clusterName"] = from_union([from_str, from_none], self.cluster_name)
1206
- if self.namespace is not None:
1207
- result["namespace"] = from_union([from_str, from_none], self.namespace)
1208
- if self.platform_type is not None:
1209
- result["platformType"] = from_union([from_str, from_none], self.platform_type)
1210
- if self.version is not None:
1211
- result["version"] = from_union([from_str, from_none], self.version)
1212
- if self.account_id is not None:
1213
- result["accountId"] = from_union([from_str, from_none], self.account_id)
1214
- if self.provider is not None:
1215
- result["provider"] = from_union([from_none, lambda x: to_enum(CloudProvider, x)], self.provider)
1216
- if self.region is not None:
1217
- result["region"] = from_union([from_str, from_none], self.region)
1218
- if self.arn is not None:
1219
- result["arn"] = from_union([from_str, from_none], self.arn)
1220
- if self.resource_id is not None:
1221
- result["resourceId"] = from_union([from_str, from_none], self.resource_id)
1222
- if self.resource_type is not None:
1223
- result["resourceType"] = from_union([from_str, from_none], self.resource_type)
1224
- if self.branch is not None:
1225
- result["branch"] = from_union([from_str, from_none], self.branch)
1226
- if self.commit is not None:
1227
- result["commit"] = from_union([from_str, from_none], self.commit)
1228
- if self.url is not None:
1229
- result["url"] = from_union([from_str, from_none], self.url)
1230
- if self.environment is not None:
1231
- result["environment"] = from_union([from_str, from_none], self.environment)
1232
- if self.checksum is not None:
1233
- result["checksum"] = from_union([from_str, from_none], self.checksum)
1234
- if self.package_manager is not None:
1235
- result["packageManager"] = from_union([from_str, from_none], self.package_manager)
1236
- if self.package_name is not None:
1237
- result["packageName"] = from_union([from_str, from_none], self.package_name)
1238
- if self.cidr is not None:
1239
- result["cidr"] = from_union([from_str, from_none], self.cidr)
1240
- if self.gateway is not None:
1241
- result["gateway"] = from_union([from_str, from_none], self.gateway)
1242
- if self.engine is not None:
1243
- result["engine"] = from_union([from_str, from_none], self.engine)
1244
- if self.host is not None:
1245
- result["host"] = from_union([from_str, from_none], self.host)
1246
- if self.port is not None:
1247
- result["port"] = from_union([from_int, from_none], self.port)
1248
- return result
1249
-
1250
-
1251
- class OriginalFormat(Enum):
1252
- """The original format of the source document before conversion to HDF."""
1253
-
1254
- HDF_V2 = "hdf-v2"
1255
- INSPEC_V1 = "inspec-v1"
1256
- OSCAL_AR = "oscal-ar"
1257
- SARIF = "sarif"
1258
- XCCDF = "xccdf"
1259
-
1260
-
1261
- class SourceRole(Enum):
1262
- """The role of this source in the comparison.
1263
-
1264
- The role of a source document in the comparison.
1265
- """
1266
- GOLDEN = "golden"
1267
- NEW = "new"
1268
- OLD = "old"
1269
- REFERENCE = "reference"
1270
- SYSTEM = "system"
1271
-
1272
-
1273
- @dataclass
1274
- class Tool:
1275
- """The security tool that produced the assessment data in this source.
1276
-
1277
- The security tool that produced the assessment data represented in this HDF file. Aligns
1278
- with SARIF, OSCAL, and CycloneDX terminology.
1279
- """
1280
- format: Optional[str] = None
1281
- """The file format, if it is a recognized named format shared by multiple tools. Examples:
1282
- 'SARIF', 'XCCDF'. Omit for tool-specific formats where the tool name already implies the
1283
- format (Nessus XML, gosec JSON).
1284
- """
1285
- name: Optional[str] = None
1286
- """The name of the security tool that produced the data. Examples: 'gosec', 'Semgrep',
1287
- 'OpenSCAP', 'AWS Config', 'Nessus'. Omit if the tool cannot be identified.
1288
- """
1289
- version: Optional[str] = None
1290
- """Version of the source tool, if available in the tool's output. Example: '5.22.3'."""
1291
-
1292
- @staticmethod
1293
- def from_dict(obj: Any) -> 'Tool':
1294
- assert isinstance(obj, dict)
1295
- format = from_union([from_str, from_none], obj.get("format"))
1296
- name = from_union([from_str, from_none], obj.get("name"))
1297
- version = from_union([from_str, from_none], obj.get("version"))
1298
- return Tool(format, name, version)
1299
-
1300
- def to_dict(self) -> dict:
1301
- result: dict = {}
1302
- if self.format is not None:
1303
- result["format"] = from_union([from_str, from_none], self.format)
1304
- if self.name is not None:
1305
- result["name"] = from_union([from_str, from_none], self.name)
1306
- if self.version is not None:
1307
- result["version"] = from_union([from_str, from_none], self.version)
1308
- return result
1309
-
1310
-
1311
- @dataclass
1312
- class Source:
1313
- """A source document participating in the comparison."""
1314
-
1315
- label: str
1316
- """Human-readable label for this source. Example: 'Before remediation scan'."""
1317
-
1318
- role: SourceRole
1319
- """The role of this source in the comparison."""
1320
-
1321
- assessment_timestamp: Optional[datetime] = None
1322
- """When the source assessment was performed. ISO 8601 format."""
1323
-
1324
- baseline_ref: Optional[BaselineRef] = None
1325
- """Reference to the baseline used in this source assessment."""
1326
-
1327
- checksum: Optional[Checksum] = None
1328
- """Cryptographic checksum of the source document for integrity verification."""
1329
-
1330
- components: Optional[List[Component]] = None
1331
- """The components assessed in this source."""
1332
-
1333
- original_format: Optional[OriginalFormat] = None
1334
- """The original format of the source document before conversion to HDF."""
1335
-
1336
- tool: Optional[Tool] = None
1337
- """The security tool that produced the assessment data in this source."""
1338
-
1339
- uri: Optional[str] = None
1340
- """URI pointing to the source document."""
1341
-
1342
- @staticmethod
1343
- def from_dict(obj: Any) -> 'Source':
1344
- assert isinstance(obj, dict)
1345
- label = from_str(obj.get("label"))
1346
- role = SourceRole(obj.get("role"))
1347
- assessment_timestamp = from_union([from_datetime, from_none], obj.get("assessmentTimestamp"))
1348
- baseline_ref = from_union([BaselineRef.from_dict, from_none], obj.get("baselineRef"))
1349
- checksum = from_union([Checksum.from_dict, from_none], obj.get("checksum"))
1350
- components = from_union([lambda x: from_list(Component.from_dict, x), from_none], obj.get("components"))
1351
- original_format = from_union([OriginalFormat, from_none], obj.get("originalFormat"))
1352
- tool = from_union([Tool.from_dict, from_none], obj.get("tool"))
1353
- uri = from_union([from_str, from_none], obj.get("uri"))
1354
- return Source(label, role, assessment_timestamp, baseline_ref, checksum, components, original_format, tool, uri)
1355
-
1356
- def to_dict(self) -> dict:
1357
- result: dict = {}
1358
- result["label"] = from_str(self.label)
1359
- result["role"] = to_enum(SourceRole, self.role)
1360
- if self.assessment_timestamp is not None:
1361
- result["assessmentTimestamp"] = from_union([lambda x: x.isoformat(), from_none], self.assessment_timestamp)
1362
- if self.baseline_ref is not None:
1363
- result["baselineRef"] = from_union([lambda x: to_class(BaselineRef, x), from_none], self.baseline_ref)
1364
- if self.checksum is not None:
1365
- result["checksum"] = from_union([lambda x: to_class(Checksum, x), from_none], self.checksum)
1366
- if self.components is not None:
1367
- result["components"] = from_union([lambda x: from_list(lambda x: to_class(Component, x), x), from_none], self.components)
1368
- if self.original_format is not None:
1369
- result["originalFormat"] = from_union([lambda x: to_enum(OriginalFormat, x), from_none], self.original_format)
1370
- if self.tool is not None:
1371
- result["tool"] = from_union([lambda x: to_class(Tool, x), from_none], self.tool)
1372
- if self.uri is not None:
1373
- result["uri"] = from_union([from_str, from_none], self.uri)
1374
- return result
1375
-
1376
-
1377
- @dataclass
1378
- class StateCounts:
1379
- """State counts for critical severity requirements.
1380
-
1381
- Counts of requirements in each state.
1382
-
1383
- State counts for high severity requirements.
1384
-
1385
- State counts for low severity requirements.
1386
-
1387
- State counts for medium severity requirements.
1388
- """
1389
- absent: Optional[int] = None
1390
- """Number of requirements present only in the old source."""
1391
-
1392
- fixed: Optional[int] = None
1393
- """Number of requirements that changed from failing to passing."""
1394
-
1395
- moved: Optional[int] = None
1396
- """Number of requirements that were reorganized without content change."""
1397
-
1398
- new: Optional[int] = None
1399
- """Number of requirements present only in the new source."""
1400
-
1401
- regressed: Optional[int] = None
1402
- """Number of requirements that changed from passing to failing."""
1403
-
1404
- unchanged: Optional[int] = None
1405
- """Number of requirements with the same effective status."""
1406
-
1407
- updated: Optional[int] = None
1408
- """Number of requirements with a generic status change."""
1409
-
1410
- @staticmethod
1411
- def from_dict(obj: Any) -> 'StateCounts':
1412
- assert isinstance(obj, dict)
1413
- absent = from_union([from_int, from_none], obj.get("absent"))
1414
- fixed = from_union([from_int, from_none], obj.get("fixed"))
1415
- moved = from_union([from_int, from_none], obj.get("moved"))
1416
- new = from_union([from_int, from_none], obj.get("new"))
1417
- regressed = from_union([from_int, from_none], obj.get("regressed"))
1418
- unchanged = from_union([from_int, from_none], obj.get("unchanged"))
1419
- updated = from_union([from_int, from_none], obj.get("updated"))
1420
- return StateCounts(absent, fixed, moved, new, regressed, unchanged, updated)
1421
-
1422
- def to_dict(self) -> dict:
1423
- result: dict = {}
1424
- if self.absent is not None:
1425
- result["absent"] = from_union([from_int, from_none], self.absent)
1426
- if self.fixed is not None:
1427
- result["fixed"] = from_union([from_int, from_none], self.fixed)
1428
- if self.moved is not None:
1429
- result["moved"] = from_union([from_int, from_none], self.moved)
1430
- if self.new is not None:
1431
- result["new"] = from_union([from_int, from_none], self.new)
1432
- if self.regressed is not None:
1433
- result["regressed"] = from_union([from_int, from_none], self.regressed)
1434
- if self.unchanged is not None:
1435
- result["unchanged"] = from_union([from_int, from_none], self.unchanged)
1436
- if self.updated is not None:
1437
- result["updated"] = from_union([from_int, from_none], self.updated)
1438
- return result
1439
-
1440
-
1441
- @dataclass
1442
- class SeverityBreakdown:
1443
- """State counts broken down by severity level.
1444
-
1445
- Breakdown of state counts by severity level.
1446
- """
1447
- critical: Optional[StateCounts] = None
1448
- """State counts for critical severity requirements."""
1449
-
1450
- high: Optional[StateCounts] = None
1451
- """State counts for high severity requirements."""
1452
-
1453
- low: Optional[StateCounts] = None
1454
- """State counts for low severity requirements."""
1455
-
1456
- medium: Optional[StateCounts] = None
1457
- """State counts for medium severity requirements."""
1458
-
1459
- @staticmethod
1460
- def from_dict(obj: Any) -> 'SeverityBreakdown':
1461
- assert isinstance(obj, dict)
1462
- critical = from_union([StateCounts.from_dict, from_none], obj.get("critical"))
1463
- high = from_union([StateCounts.from_dict, from_none], obj.get("high"))
1464
- low = from_union([StateCounts.from_dict, from_none], obj.get("low"))
1465
- medium = from_union([StateCounts.from_dict, from_none], obj.get("medium"))
1466
- return SeverityBreakdown(critical, high, low, medium)
1467
-
1468
- def to_dict(self) -> dict:
1469
- result: dict = {}
1470
- if self.critical is not None:
1471
- result["critical"] = from_union([lambda x: to_class(StateCounts, x), from_none], self.critical)
1472
- if self.high is not None:
1473
- result["high"] = from_union([lambda x: to_class(StateCounts, x), from_none], self.high)
1474
- if self.low is not None:
1475
- result["low"] = from_union([lambda x: to_class(StateCounts, x), from_none], self.low)
1476
- if self.medium is not None:
1477
- result["medium"] = from_union([lambda x: to_class(StateCounts, x), from_none], self.medium)
1478
- return result
1479
-
1480
-
1481
- @dataclass
1482
- class PerSourceSummary:
1483
- """Summary statistics for a single source in a multi-source comparison."""
1484
-
1485
- label: str
1486
- """Human-readable label for this source."""
1487
-
1488
- source_index: int
1489
- """Zero-based index into the sources array identifying which source this summary is for."""
1490
-
1491
- absent: Optional[int] = None
1492
- """Number of requirements present only in the old source."""
1493
-
1494
- fixed: Optional[int] = None
1495
- """Number of requirements that changed from failing to passing."""
1496
-
1497
- moved: Optional[int] = None
1498
- """Number of requirements that were reorganized without content change."""
1499
-
1500
- new: Optional[int] = None
1501
- """Number of requirements present only in the new source."""
1502
-
1503
- regressed: Optional[int] = None
1504
- """Number of requirements that changed from passing to failing."""
1505
-
1506
- unchanged: Optional[int] = None
1507
- """Number of requirements with the same effective status."""
1508
-
1509
- updated: Optional[int] = None
1510
- """Number of requirements with a generic status change."""
1511
-
1512
- @staticmethod
1513
- def from_dict(obj: Any) -> 'PerSourceSummary':
1514
- assert isinstance(obj, dict)
1515
- label = from_str(obj.get("label"))
1516
- source_index = from_int(obj.get("sourceIndex"))
1517
- absent = from_union([from_int, from_none], obj.get("absent"))
1518
- fixed = from_union([from_int, from_none], obj.get("fixed"))
1519
- moved = from_union([from_int, from_none], obj.get("moved"))
1520
- new = from_union([from_int, from_none], obj.get("new"))
1521
- regressed = from_union([from_int, from_none], obj.get("regressed"))
1522
- unchanged = from_union([from_int, from_none], obj.get("unchanged"))
1523
- updated = from_union([from_int, from_none], obj.get("updated"))
1524
- return PerSourceSummary(label, source_index, absent, fixed, moved, new, regressed, unchanged, updated)
1525
-
1526
- def to_dict(self) -> dict:
1527
- result: dict = {}
1528
- result["label"] = from_str(self.label)
1529
- result["sourceIndex"] = from_int(self.source_index)
1530
- if self.absent is not None:
1531
- result["absent"] = from_union([from_int, from_none], self.absent)
1532
- if self.fixed is not None:
1533
- result["fixed"] = from_union([from_int, from_none], self.fixed)
1534
- if self.moved is not None:
1535
- result["moved"] = from_union([from_int, from_none], self.moved)
1536
- if self.new is not None:
1537
- result["new"] = from_union([from_int, from_none], self.new)
1538
- if self.regressed is not None:
1539
- result["regressed"] = from_union([from_int, from_none], self.regressed)
1540
- if self.unchanged is not None:
1541
- result["unchanged"] = from_union([from_int, from_none], self.unchanged)
1542
- if self.updated is not None:
1543
- result["updated"] = from_union([from_int, from_none], self.updated)
1544
- return result
1545
-
1546
-
1547
- @dataclass
1548
- class ComparisonSummary:
1549
- """Summary statistics for the overall comparison."""
1550
-
1551
- matched_count: int
1552
- """Number of requirements successfully matched between sources."""
1553
-
1554
- total: int
1555
- """Total number of unique requirements across all sources."""
1556
-
1557
- unmatched_new_count: int
1558
- """Number of requirements in the new source with no match in the old source."""
1559
-
1560
- unmatched_old_count: int
1561
- """Number of requirements in the old source with no match in the new source."""
1562
-
1563
- absent: Optional[int] = None
1564
- """Number of requirements present only in the old source."""
1565
-
1566
- average_match_confidence: Optional[float] = None
1567
- """Average confidence score across all requirement matches (0-1)."""
1568
-
1569
- by_severity: Optional[SeverityBreakdown] = None
1570
- """State counts broken down by severity level."""
1571
-
1572
- compliance_delta: Optional[float] = None
1573
- """Change in compliance percentage (new - old)."""
1574
-
1575
- fixed: Optional[int] = None
1576
- """Number of requirements that changed from failing to passing."""
1577
-
1578
- moved: Optional[int] = None
1579
- """Number of requirements that were reorganized without content change."""
1580
-
1581
- new: Optional[int] = None
1582
- """Number of requirements present only in the new source."""
1583
-
1584
- new_compliance_percent: Optional[float] = None
1585
- """Compliance percentage of the new source (0-100)."""
1586
-
1587
- old_compliance_percent: Optional[float] = None
1588
- """Compliance percentage of the old source (0-100)."""
1589
-
1590
- per_source: Optional[List[PerSourceSummary]] = None
1591
- """Summary statistics for each individual source in a multi-source comparison."""
1592
-
1593
- regressed: Optional[int] = None
1594
- """Number of requirements that changed from passing to failing."""
1595
-
1596
- unchanged: Optional[int] = None
1597
- """Number of requirements with the same effective status."""
1598
-
1599
- updated: Optional[int] = None
1600
- """Number of requirements with a generic status change."""
1601
-
1602
- @staticmethod
1603
- def from_dict(obj: Any) -> 'ComparisonSummary':
1604
- assert isinstance(obj, dict)
1605
- matched_count = from_int(obj.get("matchedCount"))
1606
- total = from_int(obj.get("total"))
1607
- unmatched_new_count = from_int(obj.get("unmatchedNewCount"))
1608
- unmatched_old_count = from_int(obj.get("unmatchedOldCount"))
1609
- absent = from_union([from_int, from_none], obj.get("absent"))
1610
- average_match_confidence = from_union([from_float, from_none], obj.get("averageMatchConfidence"))
1611
- by_severity = from_union([SeverityBreakdown.from_dict, from_none], obj.get("bySeverity"))
1612
- compliance_delta = from_union([from_float, from_none], obj.get("complianceDelta"))
1613
- fixed = from_union([from_int, from_none], obj.get("fixed"))
1614
- moved = from_union([from_int, from_none], obj.get("moved"))
1615
- new = from_union([from_int, from_none], obj.get("new"))
1616
- new_compliance_percent = from_union([from_float, from_none], obj.get("newCompliancePercent"))
1617
- old_compliance_percent = from_union([from_float, from_none], obj.get("oldCompliancePercent"))
1618
- per_source = from_union([lambda x: from_list(PerSourceSummary.from_dict, x), from_none], obj.get("perSource"))
1619
- regressed = from_union([from_int, from_none], obj.get("regressed"))
1620
- unchanged = from_union([from_int, from_none], obj.get("unchanged"))
1621
- updated = from_union([from_int, from_none], obj.get("updated"))
1622
- return ComparisonSummary(matched_count, total, unmatched_new_count, unmatched_old_count, absent, average_match_confidence, by_severity, compliance_delta, fixed, moved, new, new_compliance_percent, old_compliance_percent, per_source, regressed, unchanged, updated)
1623
-
1624
- def to_dict(self) -> dict:
1625
- result: dict = {}
1626
- result["matchedCount"] = from_int(self.matched_count)
1627
- result["total"] = from_int(self.total)
1628
- result["unmatchedNewCount"] = from_int(self.unmatched_new_count)
1629
- result["unmatchedOldCount"] = from_int(self.unmatched_old_count)
1630
- if self.absent is not None:
1631
- result["absent"] = from_union([from_int, from_none], self.absent)
1632
- if self.average_match_confidence is not None:
1633
- result["averageMatchConfidence"] = from_union([to_float, from_none], self.average_match_confidence)
1634
- if self.by_severity is not None:
1635
- result["bySeverity"] = from_union([lambda x: to_class(SeverityBreakdown, x), from_none], self.by_severity)
1636
- if self.compliance_delta is not None:
1637
- result["complianceDelta"] = from_union([to_float, from_none], self.compliance_delta)
1638
- if self.fixed is not None:
1639
- result["fixed"] = from_union([from_int, from_none], self.fixed)
1640
- if self.moved is not None:
1641
- result["moved"] = from_union([from_int, from_none], self.moved)
1642
- if self.new is not None:
1643
- result["new"] = from_union([from_int, from_none], self.new)
1644
- if self.new_compliance_percent is not None:
1645
- result["newCompliancePercent"] = from_union([to_float, from_none], self.new_compliance_percent)
1646
- if self.old_compliance_percent is not None:
1647
- result["oldCompliancePercent"] = from_union([to_float, from_none], self.old_compliance_percent)
1648
- if self.per_source is not None:
1649
- result["perSource"] = from_union([lambda x: from_list(lambda x: to_class(PerSourceSummary, x), x), from_none], self.per_source)
1650
- if self.regressed is not None:
1651
- result["regressed"] = from_union([from_int, from_none], self.regressed)
1652
- if self.unchanged is not None:
1653
- result["unchanged"] = from_union([from_int, from_none], self.unchanged)
1654
- if self.updated is not None:
1655
- result["updated"] = from_union([from_int, from_none], self.updated)
1656
- return result
1657
-
1658
-
1659
- @dataclass
1660
- class HdfComparison:
1661
- """Structured comparison between two or more HDF security assessment documents. Supports
1662
- temporal, baseline, fleet, and multi-source comparison modes.
1663
- """
1664
- comparison_mode: ComparisonMode
1665
- """The mode of comparison being performed."""
1666
-
1667
- format_version: FormatVersion
1668
- """Schema version for this comparison format."""
1669
-
1670
- requirement_diffs: List[RequirementDiff]
1671
- """Detailed comparison of individual requirements between sources."""
1672
-
1673
- sources: List[Source]
1674
- """The source documents being compared. At least two sources are required."""
1675
-
1676
- summary: ComparisonSummary
1677
- """Summary statistics for the overall comparison."""
1678
-
1679
- annotations: Optional[Dict[str, Annotation]] = None
1680
- """Map of annotation IDs to annotation objects, providing context or action items for
1681
- requirement diffs.
1682
- """
1683
- baseline_diffs: Optional[List[BaselineDiff]] = None
1684
- """Comparison of baselines between sources."""
1685
-
1686
- component_diffs: Optional[List[ComponentDiff]] = None
1687
- """Comparison of components between two system documents. Used in systemDrift mode."""
1688
-
1689
- drift: Optional[List[RequirementDiff]] = None
1690
- """External/metadata changes separate from status changes (Terraform pattern)."""
1691
-
1692
- extensions: Optional[Dict[str, Any]] = None
1693
- """Reserved for tool-specific data not defined in the HDF standard."""
1694
-
1695
- generator: Optional[Generator] = None
1696
- """Information about the tool that generated this comparison."""
1697
-
1698
- integrity: Optional[Integrity] = None
1699
- """Cryptographic integrity information for verifying this comparison document."""
1700
-
1701
- matching: Optional[MatchingConfig] = None
1702
- """Configuration for how requirements were matched across sources."""
1703
-
1704
- package_diffs: Optional[List[PackageDiff]] = None
1705
- """Comparison of packages between two SBOMs. Used in systemDrift mode for SBOM comparison."""
1706
-
1707
- system_ref: Optional[str] = None
1708
- """URI identifying the system being compared in systemDrift mode."""
1709
-
1710
- timestamp: Optional[datetime] = None
1711
- """When this comparison was performed."""
1712
-
1713
- @staticmethod
1714
- def from_dict(obj: Any) -> 'HdfComparison':
1715
- assert isinstance(obj, dict)
1716
- comparison_mode = ComparisonMode(obj.get("comparisonMode"))
1717
- format_version = FormatVersion(obj.get("formatVersion"))
1718
- requirement_diffs = from_list(RequirementDiff.from_dict, obj.get("requirementDiffs"))
1719
- sources = from_list(Source.from_dict, obj.get("sources"))
1720
- summary = ComparisonSummary.from_dict(obj.get("summary"))
1721
- annotations = from_union([lambda x: from_dict(Annotation.from_dict, x), from_none], obj.get("annotations"))
1722
- baseline_diffs = from_union([lambda x: from_list(BaselineDiff.from_dict, x), from_none], obj.get("baselineDiffs"))
1723
- component_diffs = from_union([lambda x: from_list(ComponentDiff.from_dict, x), from_none], obj.get("componentDiffs"))
1724
- drift = from_union([lambda x: from_list(RequirementDiff.from_dict, x), from_none], obj.get("drift"))
1725
- extensions = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("extensions"))
1726
- generator = from_union([Generator.from_dict, from_none], obj.get("generator"))
1727
- integrity = from_union([Integrity.from_dict, from_none], obj.get("integrity"))
1728
- matching = from_union([MatchingConfig.from_dict, from_none], obj.get("matching"))
1729
- package_diffs = from_union([lambda x: from_list(PackageDiff.from_dict, x), from_none], obj.get("packageDiffs"))
1730
- system_ref = from_union([from_str, from_none], obj.get("systemRef"))
1731
- timestamp = from_union([from_datetime, from_none], obj.get("timestamp"))
1732
- return HdfComparison(comparison_mode, format_version, requirement_diffs, sources, summary, annotations, baseline_diffs, component_diffs, drift, extensions, generator, integrity, matching, package_diffs, system_ref, timestamp)
1733
-
1734
- def to_dict(self) -> dict:
1735
- result: dict = {}
1736
- result["comparisonMode"] = to_enum(ComparisonMode, self.comparison_mode)
1737
- result["formatVersion"] = to_enum(FormatVersion, self.format_version)
1738
- result["requirementDiffs"] = from_list(lambda x: to_class(RequirementDiff, x), self.requirement_diffs)
1739
- result["sources"] = from_list(lambda x: to_class(Source, x), self.sources)
1740
- result["summary"] = to_class(ComparisonSummary, self.summary)
1741
- if self.annotations is not None:
1742
- result["annotations"] = from_union([lambda x: from_dict(lambda x: to_class(Annotation, x), x), from_none], self.annotations)
1743
- if self.baseline_diffs is not None:
1744
- result["baselineDiffs"] = from_union([lambda x: from_list(lambda x: to_class(BaselineDiff, x), x), from_none], self.baseline_diffs)
1745
- if self.component_diffs is not None:
1746
- result["componentDiffs"] = from_union([lambda x: from_list(lambda x: to_class(ComponentDiff, x), x), from_none], self.component_diffs)
1747
- if self.drift is not None:
1748
- result["drift"] = from_union([lambda x: from_list(lambda x: to_class(RequirementDiff, x), x), from_none], self.drift)
1749
- if self.extensions is not None:
1750
- result["extensions"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.extensions)
1751
- if self.generator is not None:
1752
- result["generator"] = from_union([lambda x: to_class(Generator, x), from_none], self.generator)
1753
- if self.integrity is not None:
1754
- result["integrity"] = from_union([lambda x: to_class(Integrity, x), from_none], self.integrity)
1755
- if self.matching is not None:
1756
- result["matching"] = from_union([lambda x: to_class(MatchingConfig, x), from_none], self.matching)
1757
- if self.package_diffs is not None:
1758
- result["packageDiffs"] = from_union([lambda x: from_list(lambda x: to_class(PackageDiff, x), x), from_none], self.package_diffs)
1759
- if self.system_ref is not None:
1760
- result["systemRef"] = from_union([from_str, from_none], self.system_ref)
1761
- if self.timestamp is not None:
1762
- result["timestamp"] = from_union([lambda x: x.isoformat(), from_none], self.timestamp)
1763
- return result
1764
-
1765
-
1766
- def hdf_comparison_from_dict(s: Any) -> HdfComparison:
1767
- return HdfComparison.from_dict(s)
1768
-
1769
-
1770
- def hdf_comparison_to_dict(x: HdfComparison) -> Any:
1771
- return to_class(HdfComparison, x)