Skip to content

fix(purl): resolve vulnerability statuses for advisory-synthesized recommend winners - #2764

Open
ruromero wants to merge 3 commits into
guacsec:mainfrom
ruromero:TC-6902
Open

ruromero wants to merge 3 commits into
guacsec:mainfrom
ruromero:TC-6902

Conversation

@ruromero

@ruromero ruromero commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Add fallback vulnerability status resolution for advisory-synthesized recommend winners whose v5 UUIDs have no backing versioned_purl row
  • New fetch_vulnerability_statuses_by_version() queries purl_status directly by base PURL ID + version string, bypassing the versioned_purl JOIN that excluded synthetic winners
  • Synthetic winners detected via Uuid::new_v5 comparison in the per-winner loop
  • Updated known-limitation comment in extend_with_advisory_versions
  • TC-7121: Track advisory-synthesized version IDs at creation time instead of inferring provenance from UUIDv5 equality shared by persisted rows, avoiding unnecessary fallback queries for persisted winners

Note: This PR includes TC-6899's commit (per-winner isolation fix) as a prerequisite — rebase onto main after #2761 merges.

Test plan

  • New get_recommendations_advisory_only test passes (deletes real versioned_purl row to force synthetic path, asserts non-empty vulnerabilities)
  • All 15 get_recommendations* tests pass (including TC-6899's get_recommendations_multi_stream)
  • All 8 recommend_report* tests pass
  • cargo clippy clean, cargo fmt clean

Implements TC-6902

🤖 Generated with Claude Code

Summary by Sourcery

Resolve vulnerability statuses for advisory-synthesized recommendation winners.

Bug Fixes:

  • Restore vulnerability metadata on recommendations whose advisory-derived winner versions lack a corresponding database version record.

Enhancements:

  • Add coverage for advisory-only recommendation winners and update the related limitation documentation.

Tests:

  • Add an endpoint test verifying advisory-synthesized recommendations include vulnerability metadata.

@sourcery-ai

sourcery-ai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Recommendation status resolution is now isolated per winner to prevent vulnerability metadata leaking between version streams, with a direct database fallback for synthetic advisory-derived winners lacking versioned_purl rows. New tests cover both multi-stream isolation and advisory-only vulnerability restoration.

Sequence diagram for per-winner vulnerability status resolution

sequenceDiagram
    participant Recommend as recommend_purls()
    participant PurlService
    participant DB as Database

    loop Each recommendation winner
        Recommend->>PurlService: fetch_vulnerability_statuses(base_purl_id, versioned_purl_id)
        PurlService->>DB: Query statuses joined to versioned_purl
        DB-->>PurlService: StatusInfo for this winner
        alt No statuses and synthetic winner
            PurlService->>PurlService: Uuid::new_v5(base_purl_id, version)
            PurlService->>PurlService: fetch_vulnerability_statuses_by_version(base_purl_id, version)
            PurlService->>DB: Query purl_status by base PURL and version range
            DB-->>PurlService: StatusInfo for synthetic winner
        end
    end
    Recommend->>PurlService: assemble_recommend_entry(winner, statuses_by_vp)
    PurlService-->>Recommend: Recommendation with isolated vulnerability metadata
Loading

File-Level Changes

Change Details Files
Resolve recommendation vulnerability statuses independently for each winner and restore metadata for advisory-synthesized versions.
  • Replace batched status lookup with per-winner queries keyed by versioned PURL ID.
  • Detect synthetic winner UUIDs deterministically and fall back to direct purl_status lookup by base PURL and version.
  • Assemble vulnerability metadata from winner-scoped statuses to prevent cross-version contamination.
  • Update advisory-version limitation documentation to describe fallback resolution.
modules/fundamental/src/purl/service/mod.rs
Add regression coverage for isolated multi-stream recommendations and advisory-only synthetic winners.
  • Verify different version streams receive their corresponding vendor rebuild recommendations and vulnerabilities.
  • Delete a real versioned_purl row and verify the synthesized recommendation still includes vulnerability metadata.
modules/fundamental/src/purl/endpoints/test.rs

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 2 issues

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

## Individual Comments

### Comment 1
<location path="modules/fundamental/src/purl/service/mod.rs" line_range="646-653" />
<code_context>
+        // winner. Isolating each winner to its own query ensures version-range matching is
+        // scoped to that winner's specific versioned PURL.
+        let mut statuses_by_vp: HashMap<Uuid, Vec<StatusInfo>> = HashMap::new();
+        for winner in &winners {
+            let per_winner = Self::fetch_vulnerability_statuses(
+                std::iter::once(winner.base.id),
+                std::iter::once(winner.versioned_purl.id),
+                connection,
+            )
+            .await?;
+            if let Some(infos) = per_winner.into_values().next() {
+                statuses_by_vp.insert(winner.versioned_purl.id, infos);
+            } else {
</code_context>
<issue_to_address>
**Bulk recommendations stall or mix statuses**

When a request produces many winners, or an advisory update commits between winner fetches, `recommend_purls` calls `fetch_vulnerability_statuses` serially per winner, repeating status and association queries. Large requests hold database capacity and time out, while read-committed statements can return statuses from different advisory states in one response.

Fetch statuses for all winners in one batched operation under a consistent database snapshot.
</issue_to_address>

### Comment 2
<location path="modules/fundamental/src/purl/endpoints/test.rs" line_range="455-459" />
<code_context>
+
+    // And each recommendation has non-empty vulnerability metadata
+    assert!(
+        !rec_1[0]["vulnerabilities"].as_array().unwrap().is_empty(),
+        "stream 1 should have vulnerability metadata"
+    );
+    assert!(
+        !rec_2[0]["vulnerabilities"].as_array().unwrap().is_empty(),
+        "stream 2 should have vulnerability metadata"
+    );
</code_context>
<issue_to_address>
**Wrong stream statuses pass**

When both version streams’ status rows are returned in one base-PURL batch, `get_recommendations_multi_stream` lines 455–460 only require non-empty vulnerability arrays. Since both fixtures use the same CVE with `fixed` status (lines 411–412 and 421–422), a batched lookup that assigns one stream’s status to the other still passes the test.

Give the two streams distinguishable statuses and assert the expected status for each recommendation.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and if version-range matching selects the wrong advisory status, the recommendation API can label a vendor rebuild incorrectly—for example, appearing fixed when it is still affected—and users may make remediation decisions based on that result. Reverting stops future incorrect responses, but cannot undo decisions already made.

Blocking findings: modules/fundamental/src/purl/service/mod.rs:653, modules/fundamental/src/purl/endpoints/test.rs:459


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

Comment thread modules/fundamental/src/purl/service/mod.rs
Comment thread modules/fundamental/src/purl/endpoints/test.rs Outdated
@ruromero
ruromero marked this pull request as draft October 13, 2026 15:21
@ruromero
ruromero marked this pull request as ready for review October 16, 2026 08:08
@ruromero ruromero added the backport release/0.7.z Backport (0.7.z) label Oct 16, 2026
…commend winners

Advisory-synthesized winners (from extend_with_advisory_versions) have
deterministic v5 UUIDs with no backing versioned_purl row. The
fetch_vulnerability_statuses JOIN on versioned_purl excluded them,
producing empty vulnerability arrays even when purl_status data existed.

Add fetch_vulnerability_statuses_by_version fallback that queries
purl_status directly by base_purl_id and version string, bypassing the
versioned_purl JOIN. Synthetic winners are detected via v5 UUID
comparison in the per-winner loop.

Implements TC-6902

Assisted-by: Claude Code

@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 2 issues

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

## Individual Comments

### Comment 1
<location path="modules/fundamental/src/purl/service/mod.rs" line_range="659-664" />
<code_context>
+                let is_synthetic = winner.versioned_purl.id
+                    == Uuid::new_v5(&winner.base.id, winner.versioned_purl.version.as_bytes());
+                if is_synthetic {
+                    let infos = Self::fetch_vulnerability_statuses_by_version(
+                        winner.base.id,
+                        &winner.versioned_purl.version,
+                        connection,
+                    )
+                    .await?;
+                    if !infos.is_empty() {
+                        statuses_by_vp.insert(winner.versioned_purl.id, infos);
</code_context>
<issue_to_address>
**Advisory-only recommendations disappear**

When an SBOM package's winning vendor version is synthesized from an advisory and has no backing `versioned_purl` row, `report_for_sboms` calls only `fetch_vulnerability_statuses`, which cannot resolve the synthetic ID, so the status list stays empty and the report skips the package and its vulnerability counts.

Route synthetic winners through the advisory-derived status lookup instead of relying only on `fetch_vulnerability_statuses`.
</issue_to_address>

### Comment 2
<location path="modules/fundamental/src/purl/endpoints/test.rs" line_range="487-488" />
<code_context>
+    use sea_orm::ConnectionTrait;
+    ctx.db
+        .execute_unprepared(
+            "DELETE FROM versioned_purl WHERE version = '1.0.0.redhat-00001' \
+             AND base_purl_id = (SELECT id FROM base_purl WHERE name = 'advlib')",
+        )
+        .await?;
</code_context>
<issue_to_address>
**Test deletes unrelated package versions**

When the test runs against an external database containing another base PURL named `advlib` with version `1.0.0.redhat-00001`, the DELETE subquery matches only `base_purl.name`; when another package with that name has the same version, the test deletes its version row too and leaves the external test database modified.

Constrain the subquery by the full PURL identity and restore the deleted row or run the test against an isolated database.
</issue_to_address>

Sourcery assessment

Approval pending. 2 findings to address first.

Blocking findings: modules/fundamental/src/purl/service/mod.rs:664, modules/fundamental/src/purl/endpoints/test.rs:488


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

Comment thread modules/fundamental/src/purl/service/mod.rs Outdated
Comment thread modules/fundamental/src/purl/endpoints/test.rs
@ruromero

Copy link
Copy Markdown
Contributor Author

[sdlc-workflow/verify-pr] Re: @sourcery-ai[bot] review (5505884499) — Classified as nit — summary of inline comments already classified individually (4260515881: suggestion, 4260515908: code change request addressed). No sub-task created.

[sdlc-workflow/verify-pr] Re: @sourcery-ai[bot] review (5544849852) — Classified as nit — summary of inline comments already classified individually (4292342724: suggestion, 4292342739: suggestion). No sub-task created.

@ruromero

Copy link
Copy Markdown
Contributor Author

Verification Report for TC-6902 (commit e36fe08)

Check Result Details
Review Feedback PASS 3 suggestions, 1 code change request (addressed), 2 nits; no new sub-tasks
Root-Cause Investigation N/A No sub-tasks created
Scope Containment PASS Exactly 2 files match task spec (mod.rs, test.rs)
Diff Size PASS 136 lines across 2 files — proportionate for fallback query + reproducer test
Commit Traceability PASS Single commit references "Implements TC-6902"
Sensitive Patterns PASS No secrets detected in added lines
CI Status WARN 4/7 pass (macos, ubuntu, supply chain audit, sourcery); 3 pending (bench, windows, test)
Acceptance Criteria PASS 5/5 criteria met
Test Quality PASS No repetitive tests; doc comments present; Eval Quality: N/A
Test Change Classification ADDITIVE New test function added, no modifications to existing tests
Verification Commands N/A CI checks cover verification

Overall: WARN

CI checks still pending (bench, windows-2025, test). Compilation and clippy pass on both macOS and Ubuntu. All 5 acceptance criteria verified against the code:

  1. Reproducer test (get_recommendations_advisory_only) ingests advisory-only CSAF VEX, deletes versioned_purl row, asserts non-empty vulnerability metadata
  2. Synthetic detection via Uuid::new_v5 comparison matches generation at extend_with_advisory_versions
  3. Fallback query (fetch_vulnerability_statuses_by_version) queries purl_status directly — no versioned_purl JOIN
  4. Known-limitation comment updated to reference fallback mechanism
  5. No regression — changes are additive; existing code paths unmodified

This comment was AI-generated by sdlc-workflow/verify-pr v0.13.10.

@ruromero
ruromero requested a review from a team October 16, 2026 10:11

@mrizzi mrizzi 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.

These comments don't repeat the existing sourcery threads. Comments on is_synthetic and the by-version query are about an assumption in the TC-6902 spec itself, and the PR implements that spec faithfully. The rest are CONVENTIONS.md items, and each names its section.

if let Some(infos) = per_winner.into_values().next() {
statuses_by_vp.insert(winner.versioned_purl.id, infos);
} else {
let is_synthetic = winner.versioned_purl.id

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 matches the TC-6902 spec, but the spec's assumption doesn't hold. Real versioned_purl rows are also created as Uuid::new_v5(package_uuid, effective_version) (purl.version_uuid(), modules/ingestor/src/graph/purl/mod.rs:267 → common/src/purl.rs:130-132), and winner.base.id is package_uuid. So for non-epoch purls, is_synthetic is true for every winner. The new test shows this: after it deletes the real row, the synthetic winner from extend_with_advisory_versions has the same ID that row had.

The results are still correct, because for a real winner with no statuses the fallback also returns nothing. But every real "clean" winner pays for a second query that can't return anything, synthetic winners always run a first query that can't match, and the name and comments promise a distinction the code doesn't make. If it stays, please at least document that it is a "statuses were empty" gate, not real synthetic detection.

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.

[sdlc-workflow/verify-pr] Classified as code change request — UUIDv5 equality is also used for persisted version rows, so it does not uniquely establish that a winner is synthetic. Sub-task TC-7121 created to resolve and document the fallback eligibility.

/// literal against `version_range`, bypassing the `versioned_purl` JOIN that fails for
/// advisory-synthesized winners.
#[instrument(skip_all, err(level = tracing::Level::INFO))]
async fn fetch_vulnerability_statuses_by_version<C: ConnectionTrait>(

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.

Non-blocking, possibly a follow-up: running fetch_vulnerability_statuses_by_version for every winner gives the same results for real rows (same VersionMatches filter, and the version is already in winner.versioned_purl.version). It would remove the detection branch and the double query, and report_for_sboms could reuse it. It keeps the per-winner isolation from TC-6899. I realise this departs from acceptance criteria 2 and 3 as written, so it may be better tracked separately.

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.

[sdlc-workflow/verify-pr] Classified as suggestion — querying every winner by version may avoid the UUID branch, but it broadens the change beyond the stated acceptance criteria and is framed as a possible follow-up. No sub-task created.

.instrument(info_span!("querying purl statuses for synthetic winner"))
.await?;

let vulns = all_statuses

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.

CONVENTIONS.md § Code Duplication

Lines 806-846 copy about 40 lines of fetch_vulnerability_statuses (lines 721-770): the four loaders, the slug map, the four-way zip, and building each StatusInfo. The task's "Files to Modify" already anticipates "possibly extract a new helper function", and the Reuse Candidates list suggests adapting fetch_vulnerability_statuses rather than copying it. CONVENTIONS.md § Code Duplication asks to consolidate blocks longer than ~20 lines. Please pull this into a shared helper, for example load_status_infos(statuses: Vec<purl_status::Model>, connection) -> Result<Vec<(Uuid, StatusInfo)>, Error>, so that changes to StatusInfo (such as advisory_date precedence or slug defaults) only happen in one place.

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.

[sdlc-workflow/verify-pr] Classified as code change request — this matches CONVENTIONS.md § Code Duplication; the new status mapping duplicates the existing loader logic. Sub-task TC-7122 created to extract shared mapping.

.await?;

let vulns = all_statuses
.load_one(vulnerability::Entity, connection)

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.

CONVENTIONS.md § Tracing spans on sub-operations

The load_one calls (vulnerability, advisory, status) and the load_many_to_many call (remediations) at lines 806-822 have no .instrument(info_span!(...)). The original function wraps the same calls (lines 724-745: "loading vulnerabilities", "loading advisories", …). Under § Tracing spans on sub-operations, SeaORM loaders have no built-in instrumentation and should be wrapped. Right now a slow advisory-only request shows one opaque span. A shared helper (see the Code Duplication comment) would fix this too.

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.

[sdlc-workflow/verify-pr] Classified as code change request — this matches CONVENTIONS.md § Tracing spans on sub-operations for the SeaORM association loaders. Sub-task TC-7125 created to add the spans.

"pkg:maven/com.example/advlib@1.0.0.redhat-00001"
);
assert!(
!rec[0]["vulnerabilities"].as_array().unwrap().is_empty(),

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 meets the criterion literally, but bug TC-6887's Expected Result is "vulnerability metadata (CVE, status, remediation)". A wrong version-range match, a status from another advisory, or a slug that falls back to unknown would all still pass. This is the same weakness fixed in get_recommendations_multi_stream (e36fe08). Please assert CVE-2099-20001 with status fixed.

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.

[sdlc-workflow/verify-pr] Classified as code change request — a non-empty vulnerability array can still contain the wrong CVE or status. Sub-task TC-7123 created to assert the expected CVE and fixed status.

.left_join(version_range::Entity)
.filter(purl_status::Column::BasePurlId.eq(base_purl_id))
.filter(SimpleExpr::FunctionCall(
Func::cust(trustify_common::db::VersionMatches)

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 small CONVENTIONS.md items in the new function:

  • line 798 (§ Import Style): trustify_common::db::VersionMatches should be imported in the existing use trustify_common::{db::{...}} block. Line 714 has the same problem, so it's worth fixing both.
  • line 811 (§ Type inference): let status_slug_map: HashMap<_, _> = ….collect() → .collect::<HashMap<_, _>>(). Line 734 has the same pattern.
  • line 824 (§ Pre-allocating collections): Vec::new() → Vec::with_capacity(all_statuses.len()), taking the length before the zip consumes all_statuses.
  • line 841 (§ Iterator ownership): advisory is owned here (the loop consumes advisories_loaded), so advisory.identifier can be moved instead of .clone()d.

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.

[sdlc-workflow/verify-pr] Classified as code change request — the cited import, type inference, preallocation, and iterator ownership practices are documented in CONVENTIONS.md. Sub-task TC-7124 created to apply them.

.await?;

// And the real versioned_purl row is removed, forcing the synthetic path
use sea_orm::ConnectionTrait;

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.

CONVENTIONS.md § Logging framework, § Import Nesting

  • line 497 (§ Logging framework): new code should use tracing:: rather than log::info!. The convention also asks to migrate a file's log:: calls when you touch it. This file already uses tracing::debug! elsewhere.
  • line 485 (§ Import Nesting / § Import Style): move use sea_orm::ConnectionTrait; from inside the function body into the file's import block at the top.

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.

[sdlc-workflow/verify-pr] Classified as code change request — this matches CONVENTIONS.md § Logging framework and § Import Nesting. Sub-task TC-7126 created to update the logging and import style.

@ruromero

Copy link
Copy Markdown
Contributor Author

[sdlc-workflow/verify-pr] Re: @mrizzi review — Classified as suggestion — this review summarizes the spec-assumption concern and convention-backed inline feedback; the actionable items are tracked in the inline threads. No separate sub-task created.

@ruromero

Copy link
Copy Markdown
Contributor Author

Verification Report for TC-6902 (commit dd8b97e)

Check Result Details
Review Feedback WARN Six new code-change requests are tracked by Jira subtasks TC-7121 through TC-7126; one suggestion created no task. Classification replies are posted.
Root-Cause Investigation DONE Three root-cause tasks TC-7127 through TC-7129 were created and linked; analysis comments are posted to those tasks.
Scope Containment PASS Exactly the two task Files to Modify changed.
Diff Size PASS 136 additions and 1 deletion across 2 expected files.
Commit Traceability PASS Both commit messages include TC-6902; the implementation commit says Implements TC-6902.
Sensitive Patterns PASS No sensitive patterns detected in added lines.
CI Status PASS All 10 PR checks pass.
Acceptance Criteria FAIL 4/5 pass. The UUIDv5 comparison does not distinguish advisory-synthesized winners from ordinary persisted version rows.
Test Quality PASS No repetitive tests; test documentation is present; Eval Quality: N/A.
Test Change Classification ADDITIVE One test added; no test coverage weakened.
Verification Commands PASS Recommend tests: 15 passed. Recommend-report tests: 8 passed. User confirmed local cargo xtask precommit passed with the required Podman configuration.

Overall: FAIL

The UUIDv5 predicate in recommend_purls() is not a unique synthetic-winner discriminator: Purl::version_uuid() and persisted versioned_purl rows use the same base-PURL/version inputs. See common/src/purl.rs:130-135 and modules/ingestor/src/graph/purl/mod.rs:267. This can cause unnecessary fallback queries for ordinary winners and does not satisfy acceptance criterion 2.

The advisory-only reproducer passes but asserts only non-empty vulnerability metadata; exact CVE/status assertions are tracked in TC-7123. The local precommit result is user-confirmed; this session earlier lacked the Podman socket configuration.

Review follow-ups: TC-7121 (fallback eligibility), TC-7122 (shared status mapping), TC-7123 (exact test assertions), TC-7124 (service conventions), TC-7125 (loader instrumentation), TC-7126 (test conventions).

Root-cause follow-ups: TC-7127 (semantic-value assertions), TC-7128 (conventions audit), TC-7129 (validate predicates against target and control paths).


This comment was AI-generated by sdlc-workflow/verify-pr v0.13.10.

Track synthesized version IDs explicitly because persisted rows use the same UUIDv5 scheme. Keep advisory-only fallback status lookup without querying for persisted winners.

Implements TC-7121

Assisted-by: Claude Code

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

backport release/0.7.z Backport (0.7.z)

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants