Skip to content

Align CyTRICS v1.0.1 Schema#601

Open
willis89pr wants to merge 43 commits intomainfrom
schema
Open

Align CyTRICS v1.0.1 Schema#601
willis89pr wants to merge 43 commits intomainfrom
schema

Conversation

@willis89pr
Copy link
Copy Markdown
Collaborator

@willis89pr willis89pr commented Mar 24, 2026

Summary

This PR updates Surfactant’s CyTRICS implementation to align with schema v1.0.1. It introduces schema-validated read/write paths, adds the v1.0.1 BOM root fields and structured types expected by the schema (bomUUID, bomFormat, bomDescription, specVersion, optional authors, and optional tools), adds support for structured name/comments, softwareType, and relationship comments, switches captureTime handling to RFC 3339 strings, and enforces the requirement that software has at least one hash unless notHashable is true. It also removes legacy CyTRICS structures and command flows that are not part of the v1.0.1 document shape.

What changed

  • Added schema-backed CyTRICS validation and normalization in sbomtypes. SBOM.from_dict(...) / SBOM.from_json(...) now validate input against the checked-in CyTRICS 1.0.1 JSON schema, and SBOM.to_dict(...) / SBOM.to_json(...) validate serialized output before writing. This work also adds Author, Tool, NameEntry, and CommentEntry types plus RFC 3339 capture-time helpers, and updates SBOM, Software, Hardware, File, and Relationship to the 1.0.1 shape, including BOM root fields, softwareType, and relationship comments.

  • Removed legacy CyTRICS constructs from the typed model and merge/generate flow, including systems, analysisData, observations, starRelationships, provenance types, recordedInstitution, and legacy software component wrappers. The merge command and helper scripts no longer support config/system-wrapping behavior, the generate path drops --recorded_institution, and the CLI/script paths no longer create or wire in a synthetic top-level system object. The CycloneDX and SPDX writers were updated accordingly so they no longer emit system-derived components/packages from the old model.

  • Updated generation and plugin contracts to match the new schema-valid data flow. identify_file_type can now return either a string or list[str], generator code normalizes file-type results before extraction, extractor hooks are documented and enforced to return metadata JSON objects, and field hints are normalized into schema-compatible name, comments, vendor, and string fields before being applied. The Syft plugin was updated to emit NameEntry, CommentEntry, and RFC 3339 timestamps, and the plugin documentation was refreshed to describe the new hook behavior.

  • Hardened merge, serialization, and CLI behavior around the new model. Merge now rejects colliding hash data, preserves and merges relationship comments, rewrites and de-duplicates merged containerPath values, routes newly added software through the normal index/FS-tree update path, and rebuilds symlink edges from merged metadata after in-place software merges. cli add now validates captureTime before deserialization, install-path augmentation de-duplicates derived paths, cli_base no longer relies on the old dataclass-field pickle workaround, and stat now reads through the configured input plugin rather than assuming raw CyTRICS JSON.

  • Updated supporting scripts, docs, and dependencies to match the new workflow. merge_sbom.py is now a direct typed CyTRICS merge helper with stdin/stdout support plus root/cycle logging, merge_additional_metadata.py now validates sidecar metadata objects and fails on malformed or unmatched sha256hash values, merge_config.json was removed, and pyproject.toml adds the jsonschema runtime dependency plus pytest-asyncio for tests.

  • Refreshed fixtures and tests for 1.0.1 compliance. Sample SBOMs now include the required root fields, remove legacy root and per-software fields, convert captureTime values from epoch integers to RFC 3339 strings, and update relationship/unit tests to use valid UUIDs plus hashes or notHashable where required. New schema-focused and capture-time-focused tests were added, along with updated CLI, merge, file-type, FS-tree, serialization, and relationship coverage.

Why

The old CyTRICS path still allowed legacy document shapes, loosely typed metadata, legacy CLI/config flows, and timestamp/hash handling that do not match the v1.0.1 schema. This branch moves compliance checks into model construction, serialization, plugin ingestion, CLI entry points, and merge utilities so invalid data fails early instead of being silently carried forward or emitted.

Reviewer notes

  • This is intentionally a breaking cleanup for the typed CyTRICS model: top-level systems handling, related merge/config flows, provenance classes, recordedInstitution, and legacy software component wrappers are removed because they are not part of the v1.0.1 schema shape Surfactant now validates against.

  • User-visible command behavior changed with that cleanup. generate no longer exposes --recorded_institution. merge_sbom.py no longer supports --config_file, and merge no longer supports top-level system creation/wrapping options such as --system_uuid, --system_relationship, and --add_system.

  • Plugin authors should review the hook contract changes. identify_file_type may now return multiple file-type matches, and extract_file_info is expected to return a metadata object or None; plugin docs and built-in file-type ID plugins were updated to reflect that contract.

  • captureTime is now schema-driven everywhere and must be an RFC 3339 date-time string with timezone information. Legacy epoch-style timestamps are rejected by the new validation path and by the added tests.

  • Sidecar metadata merging is stricter, and merge semantics are safer: malformed additional metadata now errors, unmatched sha256hash values now fail fast, software entries with colliding hash data are refused during merge, and relationship comment data is preserved when duplicate relationships are merged.

Testing

Updated CLI, merge, file-type, FS-tree, serialization, Java/.NET/ELF/PE relationship, fixture, schema, and capture-time tests to match the v1.0.1 model and serialization rules. The test dependency set was also expanded to include pytest-asyncio.

- Replace epoch-based captureTime (int) with RFC 3339 strings (Optional[str])
  across hardware, software, SBOM, and provenance models
- Update Software creation to use RFC 3339 timestamps instead of time.time()
- Remove unused `time` import
- Add utility (utc_now_rfc3339) to generate schema-compliant UTC timestamps

This aligns with CyTRICS v1.0.1 schema requirements:
- type: ["string", "null"]
- format: "date-time" (RFC 3339)

Ensures consistent, timezone-aware timestamps across all captureTime fields.
@willis89pr willis89pr self-assigned this Mar 24, 2026
pre-commit-ci bot and others added 4 commits March 24, 2026 16:16
- Add tests/schema/test_cytrics_schema.py to validate captureTime against
  CyTRICS schema (docs/cytrics_schema/schema.json)
- Verify RFC 3339 date-time compliance for generated timestamps
- Ensure hardware and software captureTime accept string/null as defined
- Ensure file captureTime accepts string and rejects null
- Add negative tests for invalid formats (missing timezone, epoch integers)
- Include JSON Schema FormatChecker to enforce date-time validation

These tests prevent regression to epoch-based timestamps and ensure
alignment with CyTRICS v1.0.1 captureTime requirements.
Use correct relative import (`..utils`) to resolve ModuleNotFoundError
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 24, 2026

🧪 SBOM Results (16/16)

java_class_no1 (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:55Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -29,17 +30,16 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "326afcefa84a51113d49d623cf8902b7a07b4e98",
       "sha256": "990f9f530a833d2ab6ef1580235832a1849de3080efc69cc17cf6575e5a1c469",
       "size": 1091,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
+  "specVersion": "1.0.1",
+  "tools": null
 }

coff_files (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:55Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -32,17 +33,16 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "aad24871701ab7c50fec7f4f2afb7096e5292854",
       "sha256": "ed22c79e7ff516da5fb6310f6137bfe3b9724e9902c14ca624bfe0873f8f2d0c",
       "size": 2,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
+  "specVersion": "1.0.1",
+  "tools": null
 }

mac_os_dmg (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:55Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -29,17 +30,16 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "14ab23afddeca7d937d2618c4b38fd9985209618",
       "sha256": "38cd48fff9cf3a59bd2acae97ab73248b3f2661172ef6b621c5de478dc94b497",
       "size": 512,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
+  "specVersion": "1.0.1",
+  "tools": null
 }

rpm_pkg_files (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -67,22 +68,24 @@
           }
         }
       ],
-      "name": "hello_binary",
-      "provenance": null,
-      "recordedInstitution": null,
+      "name": [
+        {
+          "nameValue": "hello_binary"
+        }
+      ],
       "relationshipAssertion": "Unknown",
       "sha1": "46e93bf12d4a7b41f0203b147c3057f6c711db72",
       "sha256": "4aa70ebff2c671a047de68227c562e9ab5410d3c336a625103d3f4d67db8e2be",
       "size": 9217,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": "0.0.1"
     },
     {
-      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -140,22 +143,24 @@
           }
         }
       ],
-      "name": "hello_binary",
-      "provenance": null,
-      "recordedInstitution": null,
+      "name": [
+        {
+          "nameValue": "hello_binary"
+        }
+      ],
       "relationshipAssertion": "Unknown",
       "sha1": "e998a5bf9d91b7c0e8530d4e738532b7175a1246",
       "sha256": "2edf2a2a7db0dc5c9cfe86fc3066c0eec47e31acb74680de7b276bb2e535d684",
       "size": 10658,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": "0.0.1"
     },
     {
-      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ff94c3a0-6237-42fd-b041-20f7389b1077",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -214,18 +219,21 @@
           }
         }
       ],
-      "name": "hello_binary",
-      "provenance": null,
-      "recordedInstitution": null,
+      "name": [
+        {
+          "nameValue": "hello_binary"
+        }
+      ],
... and 15 more lines

cpio_files (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -29,21 +30,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "bc0a78891f719420815310fdeb8dd9b1ee8b4997",
       "sha256": "ce5d552a1efd21d3c6ba3dd68e61e4407a74840fd9969a9202a467b3e5e93f6a",
       "size": 7,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -64,21 +63,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "5329004a23509fd8d65680cb8beac4a38d2f8685",
       "sha256": "518be1719cbd871282234961c2772efe942ad6c75044fcf8ed9e4b6ae2e54318",
       "size": 512,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ff94c3a0-6237-42fd-b041-20f7389b1077",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -99,21 +96,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "1cf2793e168fcd7baa80cef01760acf468b57af2",
       "sha256": "c4658aa32155b2cba5fc9a7727bc29e5293a17c4d762c2d17a845f3eebc4a398",
       "size": 7,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "ff94c3a0-6237-42fd-b041-20f7389b1077",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "06d3de54-af8a-4365-8545-605d9ad5dfb1",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
... and 23 more lines

mach_o_dylib_test_no1 (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -29,21 +30,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "54333a8cdd621608db89dcda42f250537e6ad7a9",
       "sha256": "0923e616268df9f7098d95f77a8bb19f7e49d0338e6c98170ba84eb659d17af2",
       "size": 55672,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -64,17 +63,16 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "aab32f94173fa60e85133b5ed2911df506680beb",
       "sha256": "48d564076853bf40287bb4f220d4b281286d98cc3b929e147586b73348e4f017",
       "size": 51128,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
+  "specVersion": "1.0.1",
+  "tools": null
 }

zstandard (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -29,17 +30,16 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "fb2e51cbd24e286dd066bd419d77cd772967e384",
       "sha256": "f96deff1816083fdff8bc3e46c3fe6ca46a6bb49f4d5a00627616c13237a512c",
       "size": 13,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
+  "specVersion": "1.0.1",
+  "tools": null
 }

msitest_no1 (Link)

---
+++
@@ -1,20 +1,28 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [
     {
       "relationship": "Contains",
-      "xUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "yUUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5"
+      "xUUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "yUUID": "ff94c3a0-6237-42fd-b041-20f7389b1077"
     }
   ],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "This is a test file to create an msi.",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [
+        {
+          "author": null,
+          "comment": "This is a test file to create an msi.",
+          "fieldName": null,
+          "timestamp": null
+        }
+      ],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -54,13 +62,16 @@
           }
         }
       ],
-      "name": "Testing Hello 1.0 Installer",
-      "provenance": null,
-      "recordedInstitution": null,
+      "name": [
+        {
+          "nameValue": "Testing Hello 1.0 Installer"
+        }
+      ],
       "relationshipAssertion": "Unknown",
       "sha1": "8de8e4289c7956a370a64aa814f40bdc1b407d00",
       "sha256": "f9c66eb5a1f6c52c8d7ef2fb3bb0e8e0a0c103ae92048ce6b678152542a77c83",
       "size": 12288,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [
         "Test"
@@ -68,12 +79,11 @@
       "version": "{DAA384B0-26D7-4D34-B60E-B943AD4734F8}"
     },
     {
-      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ff94c3a0-6237-42fd-b041-20f7389b1077",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [
-        "52f1e504-b79c-4e6d-b0d4-397882a55ee6/Test/Hello 1.0/Hello.exe"
+        "39b66a99-f18e-40ee-b76d-146d4643dde4/Test/Hello 1.0/Hello.exe"
       ],
       "description": "",
       "fileName": [
@@ -164,17 +174,16 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "9432357f7e01bb47096d65366e1e214933f1bf3b",
       "sha256": "878610c407eda4c70efbdb97fd98c0d9f40144ba1c36b6b9a5d5446735fc0810",
       "size": 16376,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
+  "specVersion": "1.0.1",
+  "tools": null
 }

cd_iso_files (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -29,21 +30,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "7ebc3cb5bd7fbb2af9154158007832d679b2b607",
       "sha256": "1c492be42f6cd0ece109c33211f84dc6b979247c86eb3a423c418adb4a592b46",
       "size": 34822,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -64,21 +63,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "042ac80e15d8001a21d8fb72c11c55a5a04e31ef",
       "sha256": "72a4b42d3d6c98655a20eb7bdb4c77175202d179f85d745caf46b37f7b57eb87",
       "size": 36870,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ff94c3a0-6237-42fd-b041-20f7389b1077",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -99,17 +96,16 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "c8e858ca19ad3d02458c1cfb8f54eace71a47ce0",
       "sha256": "73d9cb06b878fb52aae297f55971595b33e53d9aa7a9d05ab7a9a7e5c5bc9be0",
       "size": 32774,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
+  "specVersion": "1.0.1",
+  "tools": null
 }

ELF_shared_obj_test_no1 (Link)

---
+++
@@ -1,20 +1,21 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [
     {
       "relationship": "Uses",
-      "xUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "yUUID": "39b66a99-f18e-40ee-b76d-146d4643dde4"
+      "xUUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "yUUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5"
     }
   ],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -108,21 +109,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "558931bab308cb5d7adb275f7f6a94757286fc63",
       "sha256": "f776715b7a01c4d4efc6be326b3e82ce546efd182c39040a7a9159f6dbe13398",
       "size": 16424,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -194,17 +193,16 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "f4a3465cbe01560333c6105e89141d771b0a9afb",
       "sha256": "45660f63c8d5f5e9305b15bff6c3c634b4a76ac66db326ecc2902dca1504ddb0",
       "size": 16184,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
+  "specVersion": "1.0.1",
+  "tools": null
 }

uimage_files (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -29,21 +30,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "bf87fabf9588a7aa7361714e8961934fbbf29808",
       "sha256": "87028a4e46ce0f53e995b0dd3e58dc29cfd80c260d51824630a6966347ae826a",
       "size": 448,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -64,21 +63,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "dbb714cc2e90b7de40da7dc42466d8c5212c7fcf",
       "sha256": "d3460597ede91d527b1e8729010a6fa55e912f319f91e225c46f209ecbf375b3",
       "size": 18,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ff94c3a0-6237-42fd-b041-20f7389b1077",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -116,18 +113,21 @@
           }
         }
       ],
-      "name": "Test uImage",
-      "provenance": null,
-      "recordedInstitution": null,
+      "name": [
+        {
+          "nameValue": "Test uImage"
+        }
+      ],
       "relationshipAssertion": "Unknown",
       "sha1": "9bb65a7b0bb913b9914e9aac72152504830a71f5",
       "sha256": "cff9c2c676d3c2d4402fe90ca65c02f00ca2a2e8d671d05b0b7e1a9f1ee5cc8a",
       "size": 125,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
... and 3 more lines

a_out_files (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -32,21 +33,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "f265f86a2f7bde59b88a47e53c0893d66a55a6cc",
       "sha256": "0dabc62368f8c774acf547ee84e794d172a72c0e8bb3c78d261a6e896ea60c42",
       "size": 4,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -70,21 +69,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "9bd0dc087572f4162635fda2e1eec23ef1b0e455",
       "sha256": "e4f0233cbbfea55e7ce3ae8a82de1362c56075493b63aa99dcd62e0e4346d28c",
       "size": 4,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ff94c3a0-6237-42fd-b041-20f7389b1077",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -108,21 +105,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "3c40724502d9196a1a86727c86eacc8132acbe03",
       "sha256": "c1fe42ca454aee9ca630fec1ed4b17aaab98108e25320bcbb853bb64a974d60b",
       "size": 4,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "ff94c3a0-6237-42fd-b041-20f7389b1077",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "06d3de54-af8a-4365-8545-605d9ad5dfb1",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
... and 49 more lines

srectest_no1 (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -29,17 +30,16 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "b59f1423b794390a12560e498592c92d804e5536",
       "sha256": "e154d9ac4c1c08c4ee8d40e69cf98eb3beb292dbe6b5cf0a16dfe386128e9c05",
       "size": 328,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
+  "specVersion": "1.0.1",
+  "tools": null
 }

sample_sboms (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -17,7 +18,7 @@
       "installPath": [
         "/home/runner/work/Surfactant/Surfactant/tests/data/sample_sboms/helics_sbom.json"
       ],
-      "md5": "746bec4e1a0e48c464e1f9472aabd4e1",
+      "md5": "dc303c0416198eabd80c0833016da36a",
       "metadata": [
         {
           "collectedBy": "Surfactant",
@@ -29,21 +30,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
-      "sha1": "8f246b4425bae57b8b40a955598e9c423d666f94",
-      "sha256": "957ae4c8dd42ca5f9e24afe426c589e505387057168eb7755031bfcf7bc3dae7",
-      "size": 21305,
+      "sha1": "205101815e82cbe5bcda3cd3c6026cc5f1cc5d89",
+      "sha256": "c5b9f3b6d080a2f19d6c018b0ffa7a4ee5c9189277d4dc8073e3a47cca284051",
+      "size": 20511,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -52,7 +51,7 @@
       "installPath": [
         "/home/runner/work/Surfactant/Surfactant/tests/data/sample_sboms/helics_libs_sbom.json"
       ],
-      "md5": "ab9c4ae608f984bb4b9953da84a01a6a",
+      "md5": "2a64245dc6d3914568c503fba27331f2",
       "metadata": [
         {
           "collectedBy": "Surfactant",
@@ -64,21 +63,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
-      "sha1": "70b315950b86b7358f57458f553a6a8f59b1d6e3",
-      "sha256": "715a0f4cbc0d4e3bae29a5e3e35d77740415ee679fba53da71ff577c35519239",
-      "size": 6633,
+      "sha1": "931e2460a30284f5f4c749de5627da46f82365fa",
+      "sha256": "4fe4d923c99c326bb49c8d2f8c4a4a6d9f24af13f51f49374ff49ff8a733b6b4",
+      "size": 6378,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ff94c3a0-6237-42fd-b041-20f7389b1077",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
... and 33 more lines

Windows_dll_test_no1 (Link)

---
+++
@@ -1,20 +1,21 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [
     {
       "relationship": "Uses",
-      "xUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "yUUID": "39b66a99-f18e-40ee-b76d-146d4643dde4"
+      "xUUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "yUUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5"
     }
   ],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -55,21 +56,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "e4e8ecba8d39ba23cf6f13498021049d62c3659c",
       "sha256": "de22b757eaa0ba2b79378722e8057d3052edc87caf543b17d8267bd2713162a8",
       "size": 58880,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -108,17 +107,16 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "77a6248d4bb7f7a58e0868d17057c62d92c9f1c1",
       "sha256": "42cc4d90b3348853ce4decc2c8c1142ff26623c53a058630be5bdd2f8d848c00",
       "size": 53248,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     }
   ],
-  "starRelationships": [],
-  "systems": []
+  "specVersion": "1.0.1",
+  "tools": null
 }

NET_app_config_test_no1 (Link)

---
+++
@@ -1,14 +1,15 @@
 {
-  "analysisData": [],
+  "authors": null,
+  "bomDescription": "",
+  "bomFormat": "cytrics",
+  "bomUUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
   "hardware": [],
-  "observations": [],
   "relationships": [],
   "software": [
     {
-      "UUID": "52f1e504-b79c-4e6d-b0d4-397882a55ee6",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "ConsoleApp2",
       "fileName": [
@@ -65,13 +66,16 @@
           "peSubsystemVersion": "6.0"
         }
       ],
-      "name": "ConsoleApp2",
-      "provenance": null,
-      "recordedInstitution": null,
+      "name": [
+        {
+          "nameValue": "ConsoleApp2"
+        }
+      ],
       "relationshipAssertion": "Unknown",
       "sha1": "b523dc5e1006140117757b1d787d0c2e88590dd7",
       "sha256": "3946ee78d95f3fa0ed383cedcb12315702dceaa81ca2ac375faeba7242f4c2c7",
       "size": 147968,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [
         "ConsoleApp2"
@@ -79,10 +83,9 @@
       "version": "1.0.0.0"
     },
     {
-      "UUID": "39b66a99-f18e-40ee-b76d-146d4643dde4",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "",
       "fileName": [
@@ -103,21 +106,19 @@
         }
       ],
       "name": null,
-      "provenance": null,
-      "recordedInstitution": null,
       "relationshipAssertion": "Unknown",
       "sha1": "94417a8cee755fe50edbab9c9ef28f5b4bcba8f9",
       "sha256": "3236fabb0e94db016757d7cffb6c2dfd3c3027dc36cb0a478fc13cd41de6fd73",
       "size": 337,
+      "softwareType": null,
       "supplementaryFiles": [],
       "vendor": [],
       "version": ""
     },
     {
-      "UUID": "ef7fb89c-8195-4c60-a2a3-43baff01bbc5",
-      "captureTime": 1609459200,
-      "comments": "",
-      "components": [],
+      "UUID": "ff94c3a0-6237-42fd-b041-20f7389b1077",
+      "captureTime": "2026-04-17T21:49:56Z",
+      "comments": [],
       "containerPath": [],
       "description": "hello",
       "fileName": [
@@ -242,13 +243,16 @@
           "peSubsystemVersion": "4.0"
         }
       ],
-      "name": "hello",
-      "provenance": null,
-      "recordedInstitution": null,
+      "name": [
+        {
+          "nameValue": "hello"
+        }
+      ],
       "relationshipAssertion": "Unknown",
       "sha1": "337de8afa9bb88f2df9c96dfc764a33f0f240d9f",
       "sha256": "f5110ae2b70f4adb47d774bcc98a40201096788c7e8cb09740b209f1a7990ff7",
       "size": 4096,
... and 13 more lines

For commit 32fb3e6 (Run 24588156305)
Compared against commit 3acd484 (Run 24358107793)

willis89pr and others added 23 commits March 24, 2026 17:01
- Add validate_capture_time utility enforcing RFC 3339 with required timezone
  using regex + datetime parsing
- Support nullable behavior and consistent error handling across models
- Add unit tests for capture_time validation (valid cases, null, and failures)
- Remove schema tests that incorrectly assumed jsonschema enforces timezone
- Keep schema tests focused on actual schema validation behavior

This separates schema validation from stricter application-level validation
and ensures captureTime values are consistently timezone-aware.
- add jsonschema to test dependency groups and optional test extras
- update pytest fixtures to use `name=` to avoid pylint redefined-outer-name warnings
- ensure schema validation tests run correctly in CI
- validate captureTime in dataclass __post_init__ (File, Hardware, Software, provenance)
- add validation at SBOM.create_software boundary
- enforce validation on captureTime updates in Software

Aligns runtime behavior with CyTRICS schema requirements.
…dling

- validate captureTime for software entries and nested components in CLI add path
- fix installPath update logic to handle None containerPath/installPath safely
- remove deprecated system-related CLI options and merge logic
- drop create_system_object and captureStart/captureEnd handling
- standardize graph node type comparison ("path" vs "Path")

Aligns CLI and merge behavior with CyTRICS v1.0.1 schema and improves input validation robustness.
…idation tests

- replace legacy epoch-based captureTime values with RFC 3339 strings in test data
- update CLI tests to use schema-compliant captureTime values
- add tests for nested SoftwareComponent captureTime validation
- add negative test for invalid component captureTime
- ensure test fixtures align with CyTRICS v1.0.1 date-time requirements

Fixes test failures caused by stricter captureTime validation and completes schema migration.
- remove legacy system config helper from merge tests
- update merge test calls to use the new three-argument signature
- delete tests for deprecated add_system and system_uuid behavior
- add coverage to assert merged output does not emit systems

Aligns merge tests with the CyTRICS v1.0.1 merge behavior.
…chema alignment

- remove System model and all system-related logic from SBOM
- remove provenance models and references across hardware, software, and observation
- delete analysisData model and related handling
- simplify SBOM merge and graph logic to software-only relationships
- update SBOM deserialization to drop legacy systems/analysisData handling
- clean up exports in sbomtypes __init__

Aligns SBOM data model with CyTRICS v1.0.1 schema by removing deprecated system
and provenance constructs and simplifying merge behavior.
…engthen validation

Add new schema-aligned models: Author, CommentEntry, NameEntry, Tool
Refactor Software, Hardware, File, and Relationship to match CyTRICS v1.0.1 structure
Replace primitive fields with structured types (e.g., name, comments, metadata)
Add enum support for relationshipAssertion
Introduce notHashable and enforce hash presence rules
Implement comprehensive runtime validation across all models (types, lists, UUIDs, RFC3339 timestamps)
Refactor Software.merge() to:
Treat scalar vs array fields consistently
Route all updates through _update_field() with full-object revalidation
Prevent invalid state via rollback on validation failure
Remove deprecated/unsupported schema elements:
Observation, StarRelationship, SoftwareComponent
Update SBOM:
Remove legacy observation/star relationship handling
Normalize relationship handling through graph as single source of truth
Ensure list fields use explicit typing (List[...]) and validate item types
Standardize metadata handling to List[Dict[str, Any]]
Update module exports (__init__.py) to reflect new schema types

BREAKING CHANGE:

Removes legacy SBOM fields (observations, starRelationships, SoftwareComponent)
Changes structure of Software, Hardware, and related types to match schema
Updates merge() behavior to enforce strict validation
Use explicit exception chaining in hardware, relationship, and software
UUID validation paths to satisfy Ruff B904 and preserve the original
exception context during error handling.
…ests

Update CycloneDX and SPDX writers to stop importing and emitting the
removed System model, and adjust ELF relationship tests to construct
SBOMs without the obsolete systems field as part of the CyTRICS v1.0.1
schema migration.
Update the Syft plugin to use RFC 3339 capture times and structured
NameEntry and CommentEntry values, and remove obsolete Software fields
dropped by the schema migration. Add TODOs to revisit nameType mapping
and migrate supplementary file paths to sbomtypes.File objects.
Remove CLI handling and tests for obsolete software components and
systems, update malformed test fixtures to use structured name/comment
fields, and migrate dotnet relationship tests to valid UUIDs with
notHashable software entries under the v1.0.1 schema.
Update ELF and PE relationship tests to use valid UUID strings and mark
synthetic software fixtures as notHashable under the CyTRICS v1.0.1
schema. Also refresh expected relationship assertions and clean up stale
test docstrings that referenced old placeholder IDs.
willis89pr and others added 15 commits April 14, 2026 21:32
- add top-level BOM fields and schema-aware serialization to SBOM
- preserve relationship comments through load, merge, and JSON output
- validate relationship data before mutating the graph
- make File and NameEntry optional-field handling schema compliant
- enforce UUIDv4 validation in Software
- update create_software to match the migrated model and sync graph/fs_tree state
- migrate fs_tree tests to valid UUID fixtures and add regression coverage
- rename hookspec.py reference to hookspecs.py
- update identify_file_type docs to reflect string or list[str] returns
- update extract_file_info docs to note metadata-object return
- fix hookspec line anchors for plugin hook links
Add Software.update_field() as a public wrapper around validated field
updates, and switch generate.py and _sbom.py to use it instead of
accessing _update_field() directly.

This preserves the existing validation behavior while resolving pylint
protected-access warnings.
Add CyTRICS schema validation to SBOM.from_dict/from_json and
SBOM.to_dict/to_json using the repository schema at
docs/cytrics_schema/schema.json.

Update merge fixtures to be valid CyTRICS 1.0.1 documents by adding
required bomFormat/specVersion fields and software hashes.

Adjust file-type tests to construct schema-valid Software entries via
Software.create_software_from_file() so strict Software validation
remains intact.
Replace the implicit raw-dict loading path in SBOM.__post_init__()
with an explicit SBOM._from_raw_dict() constructor and route
from_dict()/from_json() through it.

Update the HELICS sample SBOM to match the CyTRICS 1.0.1 schema by
adding bomFormat/specVersion, removing legacy unsupported fields,
and normalizing software comments to null.
Make relationship test SBOM fixtures explicitly declare
bomFormat="cytrics" and specVersion="1.0.1" so the tests reflect
the required CyTRICS document root fields instead of relying on
SBOM defaults.

Also update Java relationship fixtures to use valid UUIDs and
schema-valid Software entries with hashes, preserving strict
Software validation while keeping the tests green.
Update helics_binaries_sbom.json and helics_libs_sbom.json to
match the CyTRICS 1.0.1 schema by adding bomFormat/specVersion,
normalizing comments to null, removing legacy unsupported fields,
and dropping obsolete top-level sections.
Add jsonschema to the main project dependencies since schema
validation is now used outside the test-only path.

Remove jsonschema from the test dependency group and test
optional dependencies to avoid duplicating the requirement.
Refactor Software.__post_init__ into focused private validation
helpers without changing validation behavior.

Also omit notHashable from serialized software entries when it
is unset, so hashed artifacts do not emit notHashable: null.
Rewrite merge_sbom.py to use typed SBOM load/merge/write logic,
log roots and cycles, and remove the legacy raw-dict/system-entry
merge path.

Update README-merge_sbom.md to document the supported merge workflow
and remove the obsolete --config_file/system-entry behavior.

Harden merge_additional_metadata.py by routing through SBOM
validation/serialization, matching strict sidecar filenames,
handling duplicate sha256 matches, deduping repeated metadata,
deep-copying appended metadata, and erroring on malformed or
unmatched sidecar sha256hash values.

Remove the obsolete merge_config.json file.
@willis89pr willis89pr marked this pull request as ready for review April 17, 2026 22:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant