Skip to content

ADR - EC integration#2227

Open
gildub wants to merge 52 commits intoguacsec:mainfrom
gildub:adr-ec-integration
Open

ADR - EC integration#2227
gildub wants to merge 52 commits intoguacsec:mainfrom
gildub:adr-ec-integration

Conversation

@gildub
Copy link
Copy Markdown
Contributor

@gildub gildub commented Feb 3, 2026

Summary by Sourcery

Documentation:

  • Add an ADR describing Enterprise Contract integration for SBOM policy validation, including system architecture, data model, API surface, and implementation phases.

Preview: https://github.com/gildub/trustify/blob/adr-ec-integration/docs/adrs/00014-enterprise-contract-integration.md

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Feb 3, 2026

Reviewer's Guide

Adds a new Architecture Decision Record (ADR) describing the planned integration of Enterprise Contract (Conforma) into Trustify, including architecture, data model, APIs, implementation phases, and open questions.

Sequence diagram for SBOM validation with Enterprise Contract

sequenceDiagram
    actor User
    participant WebUI
    participant ApiGateway
    participant EcEndpoints
    participant EcService
    participant PolicyManager
    participant ConformaExecutor
    participant ConformaCli
    participant ResultParser
    participant ResultPersistence
    participant Postgres
    participant S3Storage

    User->>WebUI: Open_SBOM_details
    User->>WebUI: Click_Validate_with_EC
    WebUI->>ApiGateway: POST_/api_v2_sboms_{id}_ec-validate
    ApiGateway->>EcEndpoints: handle_validate_request(sbomId, policyId)
    EcEndpoints->>EcService: validate_sbom(sbomId, policyId)

    EcService->>PolicyManager: get_policy_config(policyId)
    PolicyManager-->>EcService: EcPolicy

    EcService->>ConformaExecutor: request_validation(sbomId, EcPolicy)
    ConformaExecutor->>ConformaCli: spawn_process(sbomPath, policyRef, configuration)
    ConformaCli-->>ConformaExecutor: exit_code_stdout_stderr

    ConformaExecutor-->>EcService: raw_output_exit_code
    EcService->>ResultParser: parse_output(raw_output)
    ResultParser-->>EcService: EcValidationResult

    EcService->>ResultPersistence: save_results(EcValidationResult)
    ResultPersistence->>Postgres: INSERT_ec_validation_results
    EcService->>S3Storage: store_ec_report(reportContent)
    S3Storage-->>EcService: report_url

    EcService-->>EcEndpoints: EcValidationResult_with_report_url
    EcEndpoints-->>ApiGateway: HTTP_200_validation_result
    ApiGateway-->>WebUI: JSON_validation_result
    WebUI-->>User: Show_status_badge_and_violations

    note over EcService,ConformaExecutor: On_error_or_timeout_status_error_is_saved_and_returned
Loading

Entity relationship diagram for EC policy and validation result tables

erDiagram
    SBOM {
        UUID id PK
        STRING name
        TIMESTAMP created_at
    }

    EcPolicies {
        UUID id PK
        STRING name
        TEXT description
        STRING policy_ref
        STRING policy_type
        JSONB configuration
        TIMESTAMP created_at
        TIMESTAMP updated_at
    }

    EcValidationResults {
        UUID id PK
        UUID sbom_id FK
        UUID policy_id FK
        STRING status
        JSONB violations
        JSONB summary
        STRING report_url
        TIMESTAMP executed_at
        INTEGER execution_duration_ms
        STRING conforma_version
        TEXT error_message
    }

    SBOM ||--o{ EcValidationResults : has
    EcPolicies ||--o{ EcValidationResults : used_by
Loading

Class diagram for EC validation backend module structure

classDiagram
    class EcService {
        +validateSbom(sbomId: Uuid, policyId: Uuid) EcValidationResult
        +getLatestReport(sbomId: Uuid) EcValidationResult
        +getReportHistory(sbomId: Uuid) Vec~EcValidationResult~
    }

    class PolicyManager {
        +getPolicyConfig(policyId: Uuid) EcPolicy
        +validatePolicyRef(policyRef: String) bool
        +listPolicies() Vec~EcPolicy~
    }

    class ConformaExecutor {
        +requestValidation(sbomPath: String, policy: EcPolicy, timeoutMs: u64) ConformaRawResult
        +checkHealth() bool
    }

    class ResultParser {
        +parseOutput(rawJson: String, exitCode: i32) EcValidationResult
    }

    class ResultPersistence {
        +saveResults(result: EcValidationResult) EcValidationResult
        +getLatestForSbom(sbomId: Uuid) EcValidationResult
        +getHistoryForSbom(sbomId: Uuid) Vec~EcValidationResult~
    }

    class EcPolicy {
        +id: Uuid
        +name: String
        +description: String
        +policyRef: String
        +policyType: String
        +configuration: JsonValue
        +createdAt: DateTime
        +updatedAt: DateTime
    }

    class EcValidationResult {
        +id: Uuid
        +sbomId: Uuid
        +policyId: Uuid
        +status: String
        +violations: JsonValue
        +summary: JsonValue
        +reportUrl: String
        +executedAt: DateTime
        +executionDurationMs: i64
        +conformaVersion: String
        +errorMessage: String
    }

    class ConformaRawResult {
        +stdout: String
        +stderr: String
        +exitCode: i32
        +durationMs: i64
    }

    class EcError {
        +message: String
        +code: String
    }

    EcService --> PolicyManager : uses
    EcService --> ConformaExecutor : uses
    EcService --> ResultParser : uses
    EcService --> ResultPersistence : uses

    ResultPersistence --> EcValidationResult : persists
    PolicyManager --> EcPolicy : manages
    ConformaExecutor --> ConformaRawResult : returns
    EcService --> EcError : returns_on_failure
    ResultParser --> EcError : may_return
    ConformaExecutor --> EcError : may_return
Loading

File-Level Changes

Change Details Files
Document the architectural decision and design for integrating Enterprise Contract (Conforma) as an SBOM validation service.
  • Introduce ADR 00012 outlining context, goals, and acceptance criteria for Enterprise Contract integration.
  • Describe planned system, container, and component architectures using C4-style Mermaid diagrams for the EC validation module.
  • Define new database tables for policy references and validation results, including key fields and relationships.
  • Specify backend REST API endpoints for triggering validations, retrieving results, and managing policy references.
  • Detail phased implementation approach (backend CLI integration, frontend UI, future API migration) with module structure and technical considerations around execution, storage, error handling, security, and performance.
  • List consequences, alternatives considered, implementation tasks, and open questions to guide future development and refinement.
docs/adrs/00012-enterprise-contract-integration.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 5 issues, and left some high level feedback:

  • In the C4Context mermaid diagram, the Rel(trustify, s3, "I3s", S3/Minio Storager, trustify, ...) line looks malformed (extra parameters, typo in label), which will likely break rendering—please correct the relation syntax and labels.
  • In the component diagram, the route definition GET /sbms/{id}/ec-report appears to be a typo and should probably be GET /sboms/{id}/ec-report to stay consistent with the rest of the API design.
  • There are a few small wording/typo issues that could be cleaned up for clarity, e.g., policy selectreference management UI in the frontend tasks list and duplicated 'Policy delete confirmation' bullet, which might confuse future readers of the ADR.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the C4Context mermaid diagram, the `Rel(trustify, s3, "I3s", S3/Minio Storager, trustify, ...)` line looks malformed (extra parameters, typo in label), which will likely break rendering—please correct the relation syntax and labels.
- In the component diagram, the route definition `GET /sbms/{id}/ec-report` appears to be a typo and should probably be `GET /sboms/{id}/ec-report` to stay consistent with the rest of the API design.
- There are a few small wording/typo issues that could be cleaned up for clarity, e.g., `policy selectreference management UI` in the frontend tasks list and duplicated 'Policy delete confirmation' bullet, which might confuse future readers of the ADR.

## Individual Comments

### Comment 1
<location> `docs/adrs/00012-enterprise-contract-integration.md:82` </location>
<code_context>
+    Rel(user, trustify, "Request Compliance<br/>View compliance status", "API/GUI")
+    Rel(trustify, conforma, "Executes policy validation", "Spawn Process")
+    Rel(conforma, policyRepo, "Fetches policies", "Git/HTTPS")
+    Rel(trustify, s3, "I3s", S3/Minio Storager, trustify, $offsetX="-30", $offsetY="+20")
+
+    UpdateRelStyle(trustify, conforma, $offsetX="-40")
</code_context>

<issue_to_address>
**issue (typo):** Fix typos in the S3 relationship label and description ("I3s" and "Storager").

"I3s" should be "S3", and "S3/Minio Storager" should be updated to something like "S3/Minio Storage" to keep the diagram text clear and avoid confusion.

```suggestion
    Rel(trustify, s3, "S3", "S3/Minio Storage", trustify, $offsetX="-30", $offsetY="+20")
```
</issue_to_address>

### Comment 2
<location> `docs/adrs/00012-enterprise-contract-integration.md:105` </location>
<code_context>
+        ContainerDb(postgres, "PostgreSQL", "DBMS", "Stores SBOM metadata, relationships, <br/>and EC validation results")
+        Container(ecModule, "EC Validation Module", "Rust", "Orchestrates Conforma CLI<br/>execution and result persistence")
+        ContainerDb(s3, "Object Storage", "S3/Minio", "Stores SBOM documents and EC reports")
+        Container(storage, "Storage Service", "Rust", "Manages document storage<br/>(SBOMs, policies results)")
+    }
+
</code_context>

<issue_to_address>
**suggestion (typo):** Clarify the phrase "policies results" in the storage service description.

"Policies results" reads awkwardly. Consider "policy results" or "policies and results" for clarity.

```suggestion
        Container(storage, "Storage Service", "Rust", "Manages document storage<br/>(SBOMs, policy results)")
```
</issue_to_address>

### Comment 3
<location> `docs/adrs/00012-enterprise-contract-integration.md:160` </location>
<code_context>
+    }
+
+
+    Rel(api, ecEndpoints, "POST /sboms/{id}/ec-validate,<br/>GET /sbms/{id}/ec-report", "JSON/HTTPS")
+    Rel(ecEndpoints, ecService, "validate_sbom()<br/> get_ec_report()", "Function call")
+    Rel(ecService, policyManager, "get_policy_config()", "Function call")
</code_context>

<issue_to_address>
**issue (typo):** Fix the likely typo in the path `GET /sbms/{id}/ec-report`.

This relation string uses `/sbms/{id}/ec-report` while the rest of the API uses `/sboms/`. Please update this to `/sboms/{id}/ec-report` to match the documented endpoint.

```suggestion
    Rel(api, ecEndpoints, "POST /sboms/{id}/ec-validate,<br/>GET /sboms/{id}/ec-report", "JSON/HTTPS")
```
</issue_to_address>

### Comment 4
<location> `docs/adrs/00012-enterprise-contract-integration.md:355` </location>
<code_context>
+3. **Flexibility**: Support multiple policy configurations for different requirements
+4. **Integration Ready**: REST API enables integration with CI/CD pipelines
+5. **Scalability**: Async execution prevents blocking on long-running validations
+6. **Extensibility**: Module design allows future enhancement (webhooks, notifications, etc.)
+7. **Open Source**: Conforma is open-source and actively maintained
+
</code_context>

<issue_to_address>
**nitpick (typo):** Use the plural "enhancements" instead of "enhancement".

Because you list multiple examples in parentheses, use "future enhancements" rather than "future enhancement" for correct grammatical agreement.

```suggestion
6. **Extensibility**: Module design allows future enhancements (webhooks, notifications, etc.)
```
</issue_to_address>

### Comment 5
<location> `docs/adrs/00012-enterprise-contract-integration.md:462-464` </location>
<code_context>
+- [ ] Implement validation results display with summary statistics
+- [ ] Build violations list component with expandable details
+- [ ] Create validation history timeline view
+- [ ] Add policy selectreference management UI (admin pages)
+  - [ ] Policy reference list view with search/filter (shows name, external URL, type)
+  - [ ] Policy reference create/edit form (Git URL, OCI ref, auth config)
</code_context>

<issue_to_address>
**issue (typo):** Fix the typo "selectreference" in the frontend tasks checklist.

Consider rephrasing to something like “Add policy reference management UI (admin pages)” for clarity.

```suggestion
- [ ] Create validation history timeline view
- [ ] Add policy reference management UI (admin pages)
  - [ ] Policy reference list view with search/filter (shows name, external URL, type)
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@gildub gildub force-pushed the adr-ec-integration branch from d8a3b05 to 06ef31a Compare February 3, 2026 14:18
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 3, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.43%. Comparing base (bb24af9) to head (cb0bad1).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2227      +/-   ##
==========================================
+ Coverage   70.41%   70.43%   +0.01%     
==========================================
  Files         413      413              
  Lines       23874    23874              
  Branches    23874    23874              
==========================================
+ Hits        16812    16816       +4     
+ Misses       6152     6141      -11     
- Partials      910      917       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gildub gildub force-pushed the adr-ec-integration branch from f9f50d5 to 04a07fe Compare February 4, 2026 14:23
@ctron
Copy link
Copy Markdown
Contributor

ctron commented Feb 6, 2026

@gildub Just checking, the title says "WIP". Can we turn this into a draft PR? Or is it ready to be reviewed? So we can drop the WIP.

@gildub gildub changed the title [WIP] ADR EC integration ADR - EC integration Feb 9, 2026
Copy link
Copy Markdown
Contributor

@ctron ctron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR. A few observations:

  • The ADR feels quite verbose but I'm missing detailed information. I think it would be good to remove the obvious or not really relevant bits and provide more detail to the important parts. Keeping a focus on the tricky bits.
  • Reading through the ADR, it seems to touch the same/similar topics several times. But with different idea/outcomes/definitions. That feels confusing. I think topics like "database", "conforma API", etc should be discussed in a consistent section.

I still have some technical questions:

  • I'm still not sure what triggers the validation of an SBOM. To my understanding, the validation is not triggered until a user specifically asks for it? If that's the case, that doesn't fit the idea in the ADR of making things more automatic ("Automatically validate SBOMs against organizational policies"). Why can this be requested during the upload? Or all documents automatically get validated against some set of policies?
  • Why don't we re-use the existing document storage system? What data would we store anyway (as a BLOB) and what's the format of that document?
  • How do we scale this? How do we handle situations where multiple users ask for validations at the same time?
  • What happens when a user requests the same validation for the same SBOM again?
  • What's the definition of the Conforma API (even if it's a CLI).
  • Why can we put this behind a thin HTTP API wrapper, which might solve the scaling issue on k8s
  • What's the structure of the JSON fields we store and the data we exchange over the API?
  • How do we handle long-running requests? We already know that the UI will time out after a minute or two. If that's a problem, we should provide a mechanism to deal with this right away.


## Status

PROPOSED
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be "approved", because it would be once it's merged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can still see PROPOSED:

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@gildub gildub force-pushed the adr-ec-integration branch from 04a07fe to 6b70a46 Compare February 9, 2026 14:39
@gildub
Copy link
Copy Markdown
Contributor Author

gildub commented Feb 10, 2026

@ctron,

Thank you for the review, I'll reduce some redundancy in the document.

I'm still not sure what triggers the validation of an SBOM. To my understanding, the validation is not triggered until a user specifically asks for it? If that's the case, that doesn't fit the idea in the ADR of making things more automatic ("Automatically validate SBOMs against organizational policies"). Why can this be requested during the upload? Or all documents automatically get validated against some set of policies?

  • You're right this is not a fully automated validation per say and I will correct the phrasing accordingly. Meanwhile since a policy can be attached to a SBOM, once the validation is requested by a user, then the validation will be obtained automatically.

  • The current use case is to let the user trigger the validation but we can evolve from that.
    The advantage of not having the validation made during the upload is a choice allowing flexibility by not getting in the way of loading SBOMs and therefore not being caught in the critical path for Trustify key feature. We can offer fully automated validation once the feature is in place.

Why don't we re-use the existing document storage system? What data would we store anyway (as a BLOB) and what's the format of that document?

Sorry for any confusion around the storage system which is described as external because AWS or Minio objects are managed by Trustify but independent systems per say.
Trustify will store the JSON returned by Conforma CL.

  • How do we scale this? How do we handle situations where multiple users ask for validations at the same time?

The validation being an async process, once a validation is in progress it's flagged as such and any new request will refer to the initial one.

  • What happens when a user requests the same validation for the same SBOM again?

Because we store the results of the already requested validations we don't have to re-execute them.

  • What's the definition of the Conforma API (even if it's a CLI).

We expressed with Conforma team our need for an API. My short answer is yes but will take time.
I will add the CLI interface details in the document which looks like this : ec validate input --file /path/to/file.yaml --policy github.com/user/repo --output json

  • Why can we put this behind a thin HTTP API wrapper, which might solve the scaling issue on k8s

In the meantime Conforma API is available the only approach for now is to interface to Conforma through it's CL tool because it's not designed as a library which won't be a solution either for a Go wrapper.

  • What's the structure of the JSON fields we store and the data we exchange over the API?

Among the various format offered by Conforma CL we obviously will request the JSON format. The details will be added to the document.

  • How do we handle long-running requests? We already know that the UI will time out after a minute or two. If that's a problem, we should provide a mechanism to deal with this right away.

The request should be async and therefore not blocking for the UI. Once a validation has been issued, the SBOM's policy validation will be flagged as "In Progress" or equivalent.

@gildub gildub force-pushed the adr-ec-integration branch from 4794489 to 920340b Compare February 10, 2026 09:38
@ctron
Copy link
Copy Markdown
Contributor

ctron commented Feb 10, 2026

@ctron,

Thank you for the review, I'll reduce some redundancy in the document.

I'm still not sure what triggers the validation of an SBOM. To my understanding, the validation is not triggered until a user specifically asks for it? If that's the case, that doesn't fit the idea in the ADR of making things more automatic ("Automatically validate SBOMs against organizational policies"). Why can this be requested during the upload? Or all documents automatically get validated against some set of policies?

* You're right this is not a fully automated validation per say and I will correct the phrasing accordingly. Meanwhile since a policy can be attached to a SBOM, once the validation is requested by a user, then the validation will be obtained automatically.

👍

* The current use case is to let the user trigger the validation but we can evolve from that.
  The advantage of not having the validation made during the upload is a choice allowing flexibility by not getting in the way of loading SBOMs and therefore not being caught in the critical path for Trustify key feature. We can offer fully automated validation once the feature is in place.

👍 Then we should update the ADR to reflect this.

Why don't we re-use the existing document storage system? What data would we store anyway (as a BLOB) and what's the format of that document?

Sorry for any confusion around the storage system which is described as external because AWS or Minio objects are managed by Trustify but independent systems per say. Trustify will store the JSON returned by Conforma CL.

Well, we do have StorageService, which can store advisories, SBOMs, and I guess those documents too. We should fully use, to prevent code duplication. Also, this allows to use a standard filesystem. Has it's own tables. But I didn't see this reflected in the ADR.

  • How do we scale this? How do we handle situations where multiple users ask for validations at the same time?

The validation being an async process, once a validation is in progress it's flagged as such and any new request will refer to the initial one.

That's good. The ADR should describe how that works. Also in the DB tables, etc.

  • What happens when a user requests the same validation for the same SBOM again?

Because we store the results of the already requested validations we don't have to re-execute them.

Perfect, I'd love to see that described in the ADR.

  • What's the definition of the Conforma API (even if it's a CLI).

We expressed with Conforma team our need for an API. My short answer is yes but will take time. I will add the CLI interface details in the document which looks like this : ec validate input --file /path/to/file.yaml --policy github.com/user/repo --output json

Right, and this ADR should describe that. In a single spot in the document. Ideally, describe the HTTP wrapper around that.

  • Why can we put this behind a thin HTTP API wrapper, which might solve the scaling issue on k8s

In the meantime Conforma API is available the only approach for now is to interface to Conforma through it's CL tool because it's not designed as a library which won't be a solution either for a Go wrapper.

I understand that, but we could wrap it with an HTTP server, outside of the trustify process.

  • What's the structure of the JSON fields we store and the data we exchange over the API?

Among the various format offered by Conforma CL we obviously will request the JSON format. The details will be added to the document.

👍

  • How do we handle long-running requests? We already know that the UI will time out after a minute or two. If that's a problem, we should provide a mechanism to deal with this right away.

The request should be async and therefore not blocking for the UI. Once a validation has been issued, the SBOM's policy validation will be flagged as "In Progress" or equivalent.

Perfect, the ADR should describe in detail how that process works.

@gildub gildub force-pushed the adr-ec-integration branch 2 times, most recently from 388ce8d to 9883b36 Compare February 18, 2026 07:45
@gildub gildub requested a review from ctron February 18, 2026 09:21
@gildub gildub force-pushed the adr-ec-integration branch from b843449 to 3c94232 Compare February 23, 2026 08:49
Copy link
Copy Markdown
Contributor

@ctron ctron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work on the ADR.

  • A lot of comments just have been resolved, but not worked on. I can't link all of them. They are still valid. (e.g. table names, type column, …)
  • Some comments are still open and need to be worked on.
  • The main issue I still see is the strategy of running the conforma CLI directly from the trustd binary. This should be isolated into its own process. Otherwise we will cause ourselves a lot of troubles.


### The Data Model

**`ec_policies`** - Stores references to external policies, not the policies themselves
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still plural. We do use singular naming tables.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've now verified the entire document !


#### Conforma CLI Execution

Conforma is invoked via tokio::process::Command to avoid blocking the async runtime. All arguments are passed as an array — never as a shell string — to prevent CLI injection. Execution has a configurable timeout (default 5 minutes); large SBOMs are streamed to a temp file and passed by path rather than piped via stdin, which avoids OOM issues with the parent process.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this comment got lost somehow. I didn't see it addresses. See: #2227 (comment)

I still think that this approach is not a good approach and we should wrap conforma into an HTTP endpoint. And only call that endpoint.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron,

Let me clarify an important detail, the "Conforma Executer" component is already a detached service wrapping Conforma CLI in the sense it's interfacing (via spawned execution) with it.

Please develop why "this approach is not a good approach" and please explain what do you mean by wrapping conforma using an HTTP endpoint. How would that be different from the current components solution with "EC Endpoints", the "EC Service" and the "Conforma Executer" ?

The main reason against an HTTP wrapper is that Conforma doesn't have an API and is only a CLI program for now.

- Version compatibility
- Document required Conforma version; validate on startup
- Concurrent load exhausting resources
- Semaphore (default: 5)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work, as it's not know how many resources a conforma process uses. Running this in the same pod as trustd is a mistake. One pod should ideally run one process. Conforma may cause an OOM situation, which would result in trustify being unavailable as well.

Copy link
Copy Markdown
Contributor Author

@gildub gildub Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron,

You're right and when running in a cluster environment Conforma should effectively be running in a separate pod. Alternatively when running in a standalone deployment then it would require to have Conforma to be installed separately.

I'll update the ADR once we clarify the aproach.


#### Concurrency and Backpressure

Concurrent Conforma processes are bounded by a semaphore (default: 5). When the semaphore is exhausted, incoming validation requests return 429 Too Many Requests immediately rather than queuing or blocking indefinitely. This makes the capacity limit explicit to callers (e.g. CI pipelines can implement their own retry with backoff). If demand grows to warrant it, a proper queue (Redis/RabbitMQ) is the deferred alternative considered below.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also not work properly if we have multiple pods running.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, please clarify your concerns.

From you last comment, it becomes clearer Conforma should be deployed separately from Trustify. Conforma just need to be accessible to Trustify.


#### Data Retention

Validation results accumulate over time. A retention policy (default: 90 days, configurable) should be implemented to prune old ec_validation_results rows and their corresponding S3 reports. This is deferred to the implementation phase but must be included before production rollout.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that should be implemented, it should be outlined "how".

Copy link
Copy Markdown
Contributor Author

@gildub gildub Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, this is a suggestion that came from an earlier version. Meanwhile there are no such requirement, therefore I've removed it.


### Deferred Validation

I has been decided that uploaded SBOMs start in "Pending" status and are not discoverable until validated.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The required changes for this should be described in this ADR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, good point, done !

@gildub gildub requested a review from ctron February 23, 2026 14:05
@gildub gildub force-pushed the adr-ec-integration branch 2 times, most recently from 4efda2d to 66cfa31 Compare February 25, 2026 16:01
@gildub
Copy link
Copy Markdown
Contributor Author

gildub commented Feb 25, 2026

@ctron, I've replaced the former "Conforma Executor" component with an external "HTTP API" EC wrapper.

Trustify "EC Service" will make REST requests for policies validation (the HTTP API side) and the wrapper will in turn trigger Conform CLI via a system spawn as we have confirmation Conforma is only pure CLI without any library interaction possibilities.

@gildub gildub force-pushed the adr-ec-integration branch from 40e8244 to f004ba6 Compare March 3, 2026 12:57
@gildub
Copy link
Copy Markdown
Contributor Author

gildub commented Mar 3, 2026

@ctron,

regarding #2227 (review),

I'm still not sure what triggers the validation of an SBOM. To my understanding, the validation is not triggered until a user specifically asks for it? If that's the case, that doesn't fit the idea in the ADR of making things more automatic ("Automatically validate SBOMs against organizational policies"). Why can this be requested during the upload? Or all documents automatically get validated against some set of policies?

Why don't we re-use the existing document storage system? What data would we store anyway (as a BLOB) and what's the format of that document?

Sorry for any confusion it's to say it needs to use a storage system to store validation output (JSON).

How do we scale this? How do we handle situations where multiple users ask for validations at the same time?
What happens when a user requests the same validation for the same SBOM again?

The initial state of the rule validation of a given SBOM is "Pending" since it's not done at upload time for now.
Once a user requests the validation then the state changes to "In progress".
Any new validation request will be ignored as the first request registered the demand.

What's the definition of the Conforma API (even if it's a CLI).
There isn't any API or library interface so we'll handle JSON output directly.

Why can we put this behind a thin HTTP API wrapper, which might solve the scaling issue on k8s

Yes that's a better idea and we've acknowledge to use such approach.

What's the structure of the JSON fields we store and the data we exchange over the API?

The JSON structure is now available in the document through an example generated from Conforma CLI

How do we handle long-running requests? We already know that the UI will time out after a minute or two. If that's a problem, we should provide a mechanism to deal with this right away.

The "EC service" module will trigger the HTTP wrapper by API which will provide and ID.
The HTTP wrapper separately spawns "Conforma CLI" validation command.
Once the Conforma validation command completes, the HTTP Wrapper will feed the result and output back to Trustify (though the ID)

Copy link
Copy Markdown
Contributor

@ctron ctron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's heading in the right direction. A lot of comments still need to be resolves. Many of the had already been opened in a previous review but I didn't see them processed.

A few questions I still need to understand:

  • How is security handled between the trustify and the conforma HTTP endpoint? And the other way round?
  • what is the API of the HTTP endpoint wrapper?
  • what is the API for the callback? How is security handled?

- `configuration` (JSONB) - Branch, tag, auth credentials, etc.
- `created_at`, `updated_at` (TIMESTAMP)

**`ec_validation_results`** - one row per validation execution
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also make this singular, like the other.

- **Fail** — Conforma validation found policy violations; violation details are linked.
- **Error** — an execution error occurred (CLI crash, policy fetch failure, timeout). The error is surfaced separately, and the validation can be re-triggered.

The "In Progress" state serves as a concurrency guard: if a validation is already running for a given SBOM + policy pair, subsequent requests are rejected (409 Conflict), preventing duplicate work.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the case, then I think it is import to describe how we handle the concurrency (considering the database) and failures running the validation. For example:

  • A user requests the validation
  • The entry is marked as "In progress", but the validation didn't start (due to some issues).

How would the entry be recovered from that state? There are other cases, we should document how that would work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, the HTTP Wrapper returns a validation id which is then stored in database against the related SBOM.
This id is used later on, to provide the validation result.
If the validation fails, then the validation need to be reset to "Pending".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the validation fails, then the validation need to be reset to "Pending".

Who is in charge of this? What happens when the validation pod just crashes (or gets OOM killed)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this must be effectively taken care of and we need to discuss how.
As the HTTP Wrapper is an external systems (pods or standalone), Trustify cannot orchestrate them.
So a status indicating if HTTP wrapper is alive would inform the user.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering about this part:

If the validation fails, then the validation need to be reset to "Pending".

"needs to be", but who would do that. How does this work?

Copy link
Copy Markdown
Contributor Author

@gildub gildub Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effectively another state variable is needed to support the validation process and not be mixed up with the validation results.

I've added
ec_status (ENUM) - 'queued', 'in_progress', 'completed', 'failed'
An SBOM's validation state won't be updated unless the ec_status has 'completed'.


Each SBOM + policy pair has a validation state that follows this lifecycle:

- **Pending** — initial state, set when an SBOM is associated with a policy (e.g., a default policy assigned at SBOM upload time; upload itself is outside the scope of this ADR). Indicates no validation has been triggered yet for this SBOM against this policy.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few lines up, it says "Validation on upload is deferred to a follow-up version.", here we have "a default policy assigned at SBOM upload time; upload itself is outside the scope of this ADR". This seems confusing. If we are designing this, we should. Unless it's out of scope, then that maybe may end up in some "future work" section. In general, if we describe such a state machine, we probably need this though.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron , I'm not sure what you mean,

My initial understanding was that if a Policy is defined along a SBOM then validation could be triggered.
Now if a default Policy is defined globally on Trustify then the latter could be used if no policy is attached to an SBOM.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware of any default policy. Does this ADR describe something like this?

If so, then maybe I would again suggest do have those ideas before the "decision" section. Maybe it's just an ordering issue.

Copy link
Copy Markdown
Contributor Author

@gildub gildub Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, PM confirmed a default Policy define across the application would be used.
That clears up the need for a Policy to be attached to an SBOM to be validated as the default global" one can be used.

In the case a Policy is attached to a SBOM then that one would be used for validation.

What is stored where

- PostgreSQL: validation status, structured violations (JSONB), summary statistics, foreign keys to SBOM and policy. Indexed on sbom_id, status, executed_at.
- S3/Minio: full raw Conforma JSON report, linked from the DB row via report_url. Keeps DB rows small while preserving audit completeness.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I already wrote that. We do have a storage system for documents. report_url is not part of this. In my opinion, this must be aligned with the current architecture. Otherwise we are re-creating stuff that we already have.

If there is a good reason why we do that, then we should document that reason.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, yes the idea is use existing storage to store full original JSON validation report. I'm not sure what in those lines makes you think otherwise.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S3/Minio

The storage system uses S3 (compatible) stores, or file system. Having "S3/Minio" here, seems to indicate to me that the responsiblity is with S3. Not with the storage system. Also, I didn't see any relationships to the source_document table before.

linked from the DB row via report_url

Not sure how that works with the storage? What does the URL refer to? Maybe an example helps.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, my apologies as I wasn't using the right terminology, let me replace S3/Minio with storage (as file system or S3 equiv.) and update the ADR accordingly.

The URL was the path or name of the object in the storage system, I replaced it with report_path.


## Consequences

Using an EC Wrapper decouples the validation process into an external service. This better caters for large-scale deployments as EC validation has its own resource constraints. Meanwhile it adds infrastructure complexity as the EC Wrapper must be deployed and maintained alongside the Conforma CLI.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would see the "deployment" as a pod, running a container, which has the HTTP wrapper as well as the conforma CLI.

Maybe this needs to be fleshed out a bit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes thanks, added


#### Policy Management

ec_policies stores external references only. Conforma fetches the actual policy at validation time, which means Trustify does not cache policy content by default. The trade-off: validation always uses the latest policy version, but network failures or policy repo outages will cause execution errors. For private policy repositories, authentication credentials are stored in the configuration JSONB column and must be encrypted at rest; they are never logged.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I don't fully understand it. Above I read that trustify will delete cached policies. Here trustify does not cache content. If content is cached, wouldn't that then leads to stale policies? Wouldn't it make sense to have some TTL setting? What happens if one pod has a different version than others?

#### Policy Management

ec_policies stores external references only. Conforma fetches the actual policy at validation time, which means Trustify does not cache policy content by default. The trade-off: validation always uses the latest policy version, but network failures or policy repo outages will cause execution errors. For private policy repositories, authentication credentials are stored in the configuration JSONB column and must be encrypted at rest; they are never logged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can the policy version be evaluated?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, there seems to be no version field in a given Policy. Because Policy are git based the policy version is likely managed by using git branches or tags.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should come up wit a concrete plan here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, I don't understand your question as we evaluate a given policy which is identified from it's version, please clarify.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • policy_version (VARCHAR) - Policy commit hash or tag resolved at validation time

there seems to be no version field in a given Policy. Because Policy are git based the policy version is likely managed by using git branches or tags.

My question is: how do we know which version was used? How to find out the value to put in the field policy_version?


#### Policy Management

ec_policies stores external references only. Conforma fetches the actual policy at validation time, which means Trustify does not cache policy content by default. The trade-off: validation always uses the latest policy version, but network failures or policy repo outages will cause execution errors. For private policy repositories, authentication credentials are stored in the configuration JSONB column and must be encrypted at rest; they are never logged.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[…] and must be encrypted at rest

That should be documented as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, right, replaced with using AES crate.


#### Multi-tenancy

Policy references are global (shared across all users) in this initial implementation. Per-organization policy namespacing is out of scope here and should be addressed in a dedicated multi-tenancy ADR when Trustify adds org-level isolation more broadly.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe collect all of this in a "future work" section.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, I'm waiting for confirmation we can start assuming a default policy is defined across the board. As for future evolution, I'll leave it to the PM.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, this has been confirmed by PM. We will rely upon a default "Global" Policy. I've updated the Decision section accordingly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is not relevant for this ADR, can we move into a section "future work"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


### Structure of JSON returned from Conforma CLI validation request (from an example)

```json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think without any explanation on how this will be used, this doesn't help much.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ctron, right, we're now digging at that level of details.

@gildub gildub force-pushed the adr-ec-integration branch from 5c04bbe to 2424c62 Compare March 5, 2026 08:19
Copy link
Copy Markdown
Contributor

@ctron ctron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what the state of the ADR is. Added a few comments, I might have looked too early. Maybe it makes sense putting it back in draft, and then remove the draft when all items are addressed?


Each SBOM + policy pair has a validation state that follows this lifecycle:

- **Pending** — initial state, set when an SBOM is associated with a policy (e.g., a default policy assigned at SBOM upload time; upload itself is outside the scope of this ADR). Indicates no validation has been triggered yet for this SBOM against this policy.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware of any default policy. Does this ADR describe something like this?

If so, then maybe I would again suggest do have those ideas before the "decision" section. Maybe it's just an ordering issue.

- **Fail** — Conforma validation found policy violations; violation details are linked.
- **Error** — an execution error occurred (CLI crash, policy fetch failure, timeout). The error is surfaced separately, and the validation can be re-triggered.

The "In Progress" state serves as a concurrency guard: if a validation is already running for a given SBOM + policy pair, subsequent requests are rejected (409 Conflict), preventing duplicate work.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the validation fails, then the validation need to be reset to "Pending".

Who is in charge of this? What happens when the validation pod just crashes (or gets OOM killed)?

What is stored where

- PostgreSQL: validation status, structured violations (JSONB), summary statistics, foreign keys to SBOM and policy. Indexed on sbom_id, status, executed_at.
- S3/Minio: full raw Conforma JSON report, linked from the DB row via report_url. Keeps DB rows small while preserving audit completeness.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

S3/Minio

The storage system uses S3 (compatible) stores, or file system. Having "S3/Minio" here, seems to indicate to me that the responsiblity is with S3. Not with the storage system. Also, I didn't see any relationships to the source_document table before.

linked from the DB row via report_url

Not sure how that works with the storage? What does the URL refer to? Maybe an example helps.

- `report_url` (VARCHAR) - S3 URL to detailed report
- `executed_at` (TIMESTAMP)
- `execution_duration_ms` (INTEGER)
- `conforma_version` (VARCHAR) - Conforma CLI version used (e.g., `v0.8.83`), for reproducibility
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see above about the storage service. I don't think this aligns well with what we have right now.

If we are trying to abstract a bit from conforma, towards some more generic "validation" integration. How could be rename this then?

GET /api/v2/ec/policies # List policy references
GET /api/v2/ec/policies/{id} # Get policy reference
PUT /api/v2/ec/policies/{id} # Update policy reference (admin)
DELETE /api/v2/ec/policies/{id} # Delete policy reference (admin)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am ok with either way. I guess it's up to someone else to decide if keeping those results is important or not.

In the case it is, then we would have a dangling reference to an entry that no longer exists. We should specify this, as this might create issues when showing information later on. Like showing a name (vs ID) for a result.


#### Conforma CLI Execution (EC Wrapper)

The EC Wrapper invokes Conforma via process spawning (e.g., `tokio::process::Command`). All arguments are passed as an array — never as a shell string — to prevent CLI injection. Execution has a configurable timeout (default 5 minutes); large SBOMs are written to a temp file and passed by path rather than piped via stdin, which avoids OOM issues.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I can still see it:

Image

Maybe changes need to be pushed?

@gildub gildub force-pushed the adr-ec-integration branch 2 times, most recently from 28b1ce2 to 237769b Compare March 12, 2026 11:21
@gildub gildub force-pushed the adr-ec-integration branch from 5d28223 to 8ac603c Compare March 26, 2026 14:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants