Skip to content

feat(validators): persist validation reports and make queryable - #2752

Open
rh-jfuller wants to merge 4 commits into
guacsec:mainfrom
rh-jfuller:TC-6817
Open

rh-jfuller wants to merge 4 commits into
guacsec:mainfrom
rh-jfuller:TC-6817

Conversation

@rh-jfuller

@rh-jfuller rh-jfuller commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Persist validation report (with configs to control persistence) which comes with new read.validation perm, semantic validators are now feature gated and we decided to implement a complementary REST API.

hitting these endpoints requires read.validation perm:

from document

curl "$TRUSTIFY/api/v3/sbom/{id}/validation

everything, newest first, with a total

curl "$TRUSTIFY/api/v3/validation?total=true"

only the documents this instance refused

curl "$TRUSTIFY/api/v3/validation?q=blocked%3Dtrue"

filter on any column of the report

curl "$TRUSTIFY/api/v3/validation?q=validator%3Dcsaf-spec%26outcome%3Dfailed"

by document name: an SBOM's describing node, or an advisory identifier

curl "$TRUSTIFY/api/v3/validation?name=zookeeper"

one document, by digest -- the only way to reach a rejected document

curl "$TRUSTIFY/api/v3/validation/sha256:5f2b...c91a"

one document, by the ID of an ingested SBOM or advisory

curl "$TRUSTIFY/api/v3/validation/urn:uuid:2fd0d1b7-a908-4d63-9310-d57a7f77c6df"

Code

the primitives are usable from anywhere that already depends on the
ingestor. The by_* functions return a Select, so callers compose their own
ordering, filtering and pagination:

use sea_orm::{EntityTrait, QueryOrder};
use trustify_common::id::Id;
use trustify_entity::validation_report;
use trustify_module_ingestor::service::validation::store;

// current result of every validator that has run against one document
let current = store::latest_for_digest(&db, &sha256).await?;

// full history for a document, newest first
let history = store::by_digest(&sha256)
    .order_by_desc(validation_report::Column::CreatedAt)
    .all(&db)
    .await?;

// by internal ID or any supported digest
let reports = store::by_document_id(Id::Uuid(sbom_id))?.all(&db).await?;
ust
// by document name -- not unique, so paginate
let reports = store::by_document_name("zookeeper").limit(50).all(&db).await?;

// the digest this store is organised by, for a document you hold an ID for
let digest = store::digest_for_document(&db, Id::Uuid(sbom_id)).await?;
Through the service layer, with pagination and the standard query grammar:
let service = ValidationService::new(cache);
let page = service.list(query, paginated, &tx).await?;
let one  = service.get(Id::Sha256(digest), &tx).await?;
let named = service.get_by_name("zookeeper", paginated, &tx).await?;

Configuration

persist_reports: true          # false -> debug log only, table untouched
caps:
  max_findings: 200
  max_findings_bytes: 65536
validators:
  - name: csaf-spec
    persist: true              # per-validator opt-out
    revalidate: always         # on_change skips unchanged documents

While I was there I added a 'semantic-validation' Cargo feature, on by default, gating the three backend modules and making an optional dependency.

Summary by Sourcery

Persist semantic validation outcomes and make them securely queryable through a new REST API.

New Features:

  • Persist semantic validation results as append-only reports with configurable storage, finding limits, provenance, and revalidation behavior.
  • Expose authenticated, filterable and paginated REST endpoints for querying validation reports by document, digest, name, validator, outcome, or rejection status.
  • Add a dedicated read.validation permission for validation report access.

Enhancements:

  • Add query and storage primitives for retrieving current validation results and historical report entries.
  • Gate semantic validator backends behind an optional semantic-validation Cargo feature and verify feature-disabled builds in CI.

CI:

  • Add clippy and library test coverage for the ingestor with semantic validation disabled.

Documentation:

  • Document validation report persistence, querying, configuration, caps, and revalidation behavior.
  • Update OIDC permission documentation and generated API specifications for validation report access.

Tests:

  • Add coverage for report persistence, rejection recording, deduplication, change history, caps, cached revalidation, and validation report API queries.

Chores:

  • Add the database migration and entity model for validation reports.

The validator backends (scheck, CSAF spec, Conforma) were always
compiled in eg. pulling scheck dependency tree into every binary.

Add a 'semantic-validation' Cargo feature, on by default, gating the
three backend modules and making  an optional dependency.
@rh-jfuller rh-jfuller self-assigned this Oct 9, 2026
@sourcery-ai

sourcery-ai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR adds feature-gated compilation for the semantic validation subsystem, keeping it enabled by default while allowing dependency-free builds that reject configured semantic backends at runtime; CI now verifies the feature-off configuration.

File-Level Changes

Change Details Files
Make semantic-validation backends optional and support builds with the feature disabled.
  • Add a default-on Cargo feature that gates the scheck, CSAF, and Conforma modules and optional dependency.
  • Return a clear configuration error when a disabled backend is requested.
  • Conditionally compile backend-specific imports, builders, and tests.
  • Add CI clippy coverage for the ingestor library with default features disabled.
modules/ingestor/Cargo.toml
modules/ingestor/src/service/validation/config.rs
modules/ingestor/src/service/validation/mod.rs
.github/workflows/ci.yaml

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

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 4 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="modules/ingestor/src/service/validation/config.rs" line_range="122-123" />
<code_context>
     Ok(validators)
 }

+/// Build a single validator from its configuration.
+fn build_one(validator: &ValidatorConfig) -> Result<Arc<dyn Validator>, anyhow::Error> {
+    match &validator.backend {
+        #[cfg(feature = "semantic-validation")]
</code_context>
<issue_to_address>
**Validation reports are not retained**

When an ingestion completes with validation reports, `build_one` only constructs validators; after validation, `IngestorService::ingest` attaches reports to the in-memory `IngestResult` and does not store them. Once the ingestion response is gone, callers cannot retrieve reports for querying.

Persist reports with the ingested document and add a query path for retrieving them.
</issue_to_address>

### Comment 2
<location path="modules/ingestor/Cargo.toml" line_range="10-15" />
<code_context>
 rust-version.workspace = true

+[features]
+default = ["semantic-validation"]
+
+# The semantic validator subsystem (ADR 00020): scheck, CSAF spec and Conforma
+# backends. Disabling it drops the `scheck` dependency tree; the validator
+# config still parses, but building a configured validator fails. See ADR 00022.
+semantic-validation = ["dep:scheck"]
+
 [dependencies]
</code_context>
<issue_to_address>
**Server cannot disable semantic validation**

When a server or workspace binary is built intending to omit semantic validation, cargo feature unification enables `semantic-validation` through workspace dependents that declare `trustify-module-ingestor` with default features. The server and other composed binaries therefore still compile the validator backends and `scheck` dependency tree, even when built with `--no-default-features`.

Disable default features on the ingestor dependency throughout the workspace and add a forwarded feature where consumers need to enable semantic validation.
</issue_to_address>

### Comment 3
<location path="modules/ingestor/Cargo.toml" line_range="15" />
<code_context>
+# The semantic validator subsystem (ADR 00020): scheck, CSAF spec and Conforma
+# backends. Disabling it drops the `scheck` dependency tree; the validator
+# config still parses, but building a configured validator fails. See ADR 00022.
+semantic-validation = ["dep:scheck"]
+
 [dependencies]
</code_context>
<issue_to_address>
**CSAF dependency remains enabled**

When the ingestor is built with `--no-default-features`, with `semantic-validation` disabled, Cargo still compiles the unconditional `csaf-rs` dependency even though `service::validation::csaf` is gated off, so disabling the feature does not remove the CSAF backend’s dependency tree from builds.

Make `csaf-rs` optional and include `dep:csaf-rs` in the `semantic-validation` feature.
</issue_to_address>

### Comment 4
<location path="modules/ingestor/src/service/validation/config.rs" line_range="138-141" />
<code_context>
+        Backend::Conforma(conforma) => Ok(Arc::new(conforma::build(validator, conforma)?)),
+        #[allow(unreachable_patterns)]
+        backend => anyhow::bail!(
+            "validator '{}' uses backend {backend:?}, which is not compiled into this build \
+             (the 'semantic-validation' feature is disabled)",
+            validator.name,
+        ),
+    }
+}
</code_context>
<issue_to_address>
**Feature-off unit test fails**

When the ingestor unit tests are run with `--no-default-features`, `build` reaches this unsupported-backend error for the first validator before checking the duplicate name on the second, so `rejects_duplicate_validator_names` receives the feature-disabled error and fails its assertion when tests run without default features.

Make the duplicate-name test use a backend-independent setup or adjust the feature-off test/configuration so it can reach the duplicate check.
</issue_to_address>

Sourcery assessment

Approval pending. 4 findings to address first.

Blocking findings: modules/ingestor/src/service/validation/config.rs:123, modules/ingestor/Cargo.toml:15, modules/ingestor/Cargo.toml:15, modules/ingestor/src/service/validation/config.rs:141


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread modules/ingestor/src/service/validation/config.rs Outdated
Comment thread modules/ingestor/Cargo.toml
Comment thread modules/ingestor/Cargo.toml
Comment thread modules/ingestor/src/service/validation/config.rs
@rh-jfuller rh-jfuller changed the title (WIP) feat(validators): persist validation reports and make queryable feat(validators): persist validation reports and make queryable Oct 9, 2026
Reuse unchanged verdicts, serialize report deduplication, preserve combined
filters, and test the ingestor without semantic-validation.

@jcrossley3 jcrossley3 left a comment

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 my main concern is that I can't see exactly how to map the report results to specific elements of the validated document.

I understand the desire to have the report schema generic enough to support all the different types of documents we might ingest, but for the Conforma report specifically, we need a way to map the results to an SBOM's components.

Maybe we punt and just brute-force search for specific algorithm id's within the findings value, but can we conceive of a more elegant solution?

@rh-jfuller

rh-jfuller commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor Author

I understand the desire to have the report schema generic enough to support all the different types of documents we might ingest, but for the Conforma report specifically, we need a way to map the results to an SBOM's components.

it is a very good use case ... lets ignore the generic aspects and focus on the needful.

for components (in sbom) the right way to handle identy would be pURL (because a pURL in a specific sbom is safe) or its checksum (but often that can not be supplied) ... using the sbom own internal ID is doable as well - can you provide an example of conforma report processing an entire sbom ?

@jcrossley3

Copy link
Copy Markdown
Contributor

can you provide an example of conforma report processing an entire sbom ?

Just added in a commit to #2725 : https://github.com/gildub/trustify/tree/75decd040f12b4894903b15c8d0544de9906b10e/etc/test-data/cyclonedx/cryptographic

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants