Skip to content

feat(triage-security): add discover entry point for untriaged Vulnerabilities - #344

Merged
mrizzi merged 4 commits into
mainfrom
TC-6931
Oct 15, 2026
Merged

mrizzi merged 4 commits into
mainfrom
TC-6931

Conversation

@mrizzi

@mrizzi mrizzi commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a discover subcommand to pre_triage_security.py: python3 pre_triage_security.py discover <project_root> [--max-results N] runs the triage-security SKILL.md Discovery-mode "Untriaged issues" JQL on the trusted runner and prints {"issues": [{"key", "url"}]} in JQL order (default cap 20). The TC-6918 GitLab poller uses it to dispatch one triage-security run per issue.
  • Fails closed: missing/invalid CLAUDE.md config, missing JIRA_SERVER_URL, a non-numeric issue type ID, a non-positive cap, a Jira failure, or a key outside the configured project all exit 1 with ERROR: ... on stderr and print nothing on stdout.
  • A test reads query 1 from SKILL.md and asserts the generated JQL against it, so the two can't drift apart. SKILL.md gets a one-line note about keeping them in sync. Fullsend-mode instructions are unchanged.
  • Re-pins and re-locks the triage-security harness to 9fabe0ef (latest, after the TC-6961 fix; triage-security only; .fullsend/harness/verify-pr.yaml and its lock entry are untouched).
  • TC-6961 (review fix): an unreadable or non-UTF-8 CLAUDE.md (or security matrix) now exits 1 with ERROR: cannot read ... instead of a traceback, in discover, collect and parse-configuration (new _read_text helper).

Notes for review

  • Project key is quoted. The project key is quoted and _jql_escaped (project = "TC"), the same way collect does it. The SKILL.md template has it unquoted, so the drift test fills in the quoted form.
  • Paging. jira-client.py has no separate search_jql_all subcommand; search_jql_all is reached through search_jql --all. A cap of 50 or less uses one page (--max-results N). Above 50 it uses --all and then trims to the cap. The JQL itself does the filtering; nothing is filtered afterwards.

Merge with a merge commit (not squash/rebase): .fullsend/ pins the commit SHA 9fabe0ef.

Test plan

  • python3 -m pytest plugins/sdlc-workflow/scripts/ -q: 704 passed, 3 skipped
  • uvx skillsaw: 0 errors (triage-security SKILL.md at 15,875 tokens, under the 16k budget)
  • git diff main -- .fullsend/harness/verify-pr.yaml: empty

Implements TC-6931

🤖 Generated with Claude Code

Summary by Sourcery

Enable secure automated discovery of untriaged vulnerability issues for downstream triage dispatch.

New Features:

  • Add a discovery command that lists untriaged vulnerability issues and their Jira URLs for automated triage polling.

Bug Fixes:

  • Fail closed with clear errors for invalid configuration, Jira failures, unsafe issue keys, invalid result limits, and unreadable or malformed configuration files.

Enhancements:

  • Keep discovery JQL aligned with the triage-security skill query and preserve Jira ordering with bounded paging.

Documentation:

  • Document the requirement to keep the discovery command synchronized with the triage-security skill query.

Tests:

  • Add coverage for discovery query generation, pagination, output ordering, validation failures, Jira failures, and configuration read errors.

Chores:

  • Re-pin and re-lock the triage-security harness to the updated upstream revision.

mrizzi and others added 2 commits October 15, 2026 11:01
…bilities

Add a 'discover' subcommand to pre_triage_security.py that runs the
triage-security Discovery-mode "Untriaged issues" JQL on the trusted
runner and prints {"issues": [{"key", "url"}]} in JQL order, capped
by --max-results (default 20). The GitLab poller uses it to dispatch
one triage-security run per issue without re-implementing the query.

Keys are validated against the configured project and any config or
Jira failure exits 1 with an ERROR on stderr. A test asserts the JQL
against the SKILL.md query so the two cannot drift.

Implements TC-6931

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pin the triage-security harness base to the discover entry point
commit and re-lock triage-security only; verify-pr is unchanged.

Implements TC-6931

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR introduces a trusted-runner discover entry point that validates project and Jira configuration, executes the documented untriaged Vulnerability JQL with bounded paging, and emits safely constrained issue key/URL records for the TC-6918 poller. It adds synchronization-focused tests and refreshes only the triage-security harness pin and lock metadata.

Sequence diagram for discovering untriaged security issues

sequenceDiagram
    participant Poller as TC6918Poller
    participant Runner as TrustedRunner
    participant Script as pre_triage_security.py
    participant Jira as JiraClient

    Poller->>Runner: Run discover project_root --max-results N
    Runner->>Script: discover_untriaged(project_root, max_results)
    Script->>Script: parse_security_configuration(CLAUDE.md)
    Script->>Script: untriaged_jql(configuration)
    Script->>Jira: search_jql(--jql, --fields key, paging)
    Jira-->>Script: Issues in JQL order
    Script->>Script: Validate keys and trim to cap
    Script-->>Runner: {issues: [{key, url}]}
    Runner-->>Poller: Dispatch one triage-security run per issue
Loading

Flow diagram for fail-closed discovery validation

flowchart TD
    A[discover project_root] --> B{Valid cap?}
    B -- No --> E[ERROR on stderr; no stdout]
    B -- Yes --> C{JIRA_SERVER_URL and CLAUDE.md valid?}
    C -- No --> E
    C -- Yes --> D{Configuration and issue type ID valid?}
    D -- No --> E
    D -- Yes --> F[Build untriaged JQL]
    F --> G{Jira search succeeds?}
    G -- No --> E
    G -- Yes --> H{Every issue key belongs to project?}
    H -- No --> E
    H -- Yes --> I[Emit issue key and browse URL JSON]
Loading

File-Level Changes

Change Details Files
Added a fail-closed discovery CLI for retrieving untriaged Vulnerability issues in JQL order.
  • Builds the Discovery-mode JQL from project configuration with numeric issue-type validation and escaped project keys.
  • Validates Jira URL, CLAUDE.md configuration, result cap, returned issue keys, and Jira responses before emitting JSON.
  • Uses single-page searches up to 50 results and --all with client-side capping above 50.
  • Adds the discover subcommand and machine-readable {issues:[{key,url}]} output.
plugins/sdlc-workflow/scripts/pre_triage_security.py
Added coverage for discovery behavior and synchronized implementation with the documented query.
  • Reads the untriaged query from SKILL.md and asserts generated JQL equivalence.
  • Tests ordering, URL construction, paging thresholds, caps, malformed or foreign keys, Jira failures, invalid configuration, and empty stdout on errors.
  • Documents that the script and SKILL.md query must remain synchronized.
plugins/sdlc-workflow/scripts/test_pre_triage_security.py
plugins/sdlc-workflow/skills/triage-security/SKILL.md
Re-pinned the triage-security Fullsend harness and refreshed its lock metadata to the implementation commit.
  • Updates triage-security harness, plugin, scripts, resources, and skill references to commit b0d5d6a.
  • Refreshes corresponding hashes and fetch timestamps while leaving verify-pr pinning unchanged.
.fullsend/harness/triage-security.yaml
.fullsend/lock.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 1 issue

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

## Individual Comments

### Comment 1
<location path="plugins/sdlc-workflow/scripts/pre_triage_security.py" line_range="879" />
<code_context>
+    claude_path = Path(project_root) / "CLAUDE.md"
+    if not claude_path.is_file():
+        raise EvidenceError("target project CLAUDE.md is required")
+    configuration = parse_security_configuration(claude_path.read_text())
+    project = configuration["project_key"]
+    # One search_jql page holds at most 50 issues; page with --all only when the
</code_context>
<issue_to_address>
**Configuration read prints traceback**

When the target `CLAUDE.md` exists but is unreadable or contains invalid UTF-8, `Path.read_text()` raises `OSError` or `UnicodeDecodeError`, and `main()` catches neither exception. The command prints a traceback instead of the promised `ERROR: ...` diagnostic.

Handle read and decode errors through the same error-reporting path that prints the documented `ERROR: ...` diagnostic.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: plugins/sdlc-workflow/scripts/pre_triage_security.py:879


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

Comment thread plugins/sdlc-workflow/scripts/pre_triage_security.py Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

🤖 Finished Verify Pr · ✅ Success · Started 9:09 AM UTC · Completed 9:24 AM UTC

Commit: df5b3dd · View workflow run →

Runtime: claude · Model: claude-opus-4-8 · Effort: high · Cost: $6.57

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Eval Results

Source head: 6abb66a
Merge: c1f76fd

Eval Results: triage-security

Eval Passed Failed Pass Rate
eval-1 13/13 0 100%
eval-10 5/5 0 100%
eval-11 5/5 0 100%
eval-12 5/5 0 100%
eval-13 5/5 0 100%
eval-14 5/5 0 100%
eval-15 5/5 0 100%
eval-16 7/7 0 100%
eval-17 5/5 0 100%
eval-18 5/5 0 100%
eval-19 5/5 0 100%
eval-2 5/5 0 100%
eval-20 4/4 0 100%
eval-21 4/4 0 100%
eval-22 4/4 0 100%
eval-23 4/4 0 100%
eval-24 4/4 0 100%
eval-25 4/4 0 100%
eval-26 5/5 0 100%
eval-27 5/5 0 100%
eval-28 5/5 0 100%
eval-29 5/5 0 100%
eval-3 5/5 0 100%
eval-30 4/4 0 100%
eval-31 4/4 0 100%
eval-32 4/4 0 100%
eval-4 5/5 0 100%
eval-5 6/6 0 100%
eval-6 6/6 0 100%
eval-7 5/5 0 100%
eval-8 8/8 0 100%
eval-9 5/5 0 100%

Pass rate: 100% · Tokens: 61,947 · Duration: 114s

Baseline (27ad1c49): 98% · 68,770 tokens · 133s


Generated by sdlc-workflow/run-evals v0.13.10

@github-actions github-actions 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.

Native Fullsend Eval Results

Head: df5b3dd
Merge: 1b87f12
Trusted workflow: 7ef0f3c
Reviewed eval source: 7ef0f3c

Native job: success; complete: true.
Execution evidence: valid; quality score (advisory): 26/28 passed.

Case Execution evidence Quality score (advisory)
033-absent valid 4/4
034-empty valid 5/5
035-malformed valid 3/5
036-valid valid 7/7
037-release valid 7/7

@fullsend-ai-review

Copy link
Copy Markdown

[sdlc-workflow/verify-pr] Re: @sourcery-ai[bot] review - Classified as code change request - sub-task TC-6961 created to address the single blocking finding (uncaught read/decode error for an unreadable or invalid-UTF-8 CLAUDE.md at pre_triage_security.py:879). No other actionable items in the review body.

@fullsend-ai-review

Copy link
Copy Markdown

Verification Report for TC-6931 (commit df5b3dd)

Check Result Details
Review Feedback WARN 1 code change request (sourcery-ai[bot], uncaught read/decode error at pre_triage_security.py:879); sub-task created
Root-Cause Investigation DONE implement-task skill gap (reused an incomplete sibling file-read pattern); root-cause task created
Scope Containment PASS PR changes exactly the 5 task-specified files; no out-of-scope or unimplemented files
Diff Size PASS +278/-47 across 5 files; proportionate (new test file + discover entry point + mechanical re-lock)
Commit Traceability PASS Both commits reference TC-6931
Sensitive Patterns PASS No secrets in added lines (lock.yaml sha256 are integrity hashes)
CI Status PASS 8/8 head-SHA checks completed/success
Acceptance Criteria WARN 5/5 functionally met; criterion 3 has an uncaught OSError/UnicodeDecodeError edge case at :879 - tracked via sub-task
Test Quality PASS Repetitive PASS, Test Documentation PASS (docstrings + given/when/then), Eval Quality N/A
Test Change Classification ADDITIVE +6 test functions, +12 parametrized cases, zero removals/relaxations
Verification Commands PASS JQL-equality verified directly; verify-pr.yaml untouched; pytest/skillsaw green on CI

Overall: WARN

Overall WARN. The PR is in scope, proportionate, traceable, secret-free, and CI is fully green; the new discover entry point and its JQL match the triage-security Discovery-mode query, with additive, well-documented tests. One confirmed reviewer finding remains: an unreadable or invalid-UTF-8 target CLAUDE.md makes pre_triage_security.py read_text() raise OSError/UnicodeDecodeError that main() does not catch, printing a traceback instead of the contract-promised 'ERROR: ...' exit (a partial gap against acceptance criterion 3). A review-feedback sub-task tracks the fix (covering the sibling collect/parse-configuration call sites too), and a root-cause task targets the implement-task skill so future implementations route file read/decode failures through the documented error path. No code was modified and the PR was not merged - a human reviewer decides whether to merge.


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

Path.read_text() raises OSError or UnicodeDecodeError for an unreadable
or non-UTF-8 file, and main() caught neither, so discover, collect and
parse-configuration printed a traceback instead of 'ERROR: ...'. Route
every CLAUDE.md and security-matrix read through a _read_text helper
that raises EvidenceError, mirroring validate_bundle's read idiom.

Implements TC-6961

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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

Sourcery assessment

Approved.

@fullsend-ai-review

fullsend-ai-review Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

🤖 Finished Verify Pr · ✅ Success · Started 9:36 AM UTC · Completed 9:47 AM UTC

Commit: 9fabe0e · View workflow run →

Runtime: claude · Model: claude-opus-4-8 · Effort: high · Cost: $5.59

@fullsend-ai-review

Copy link
Copy Markdown

Verification Report for TC-6931 (commit 9fabe0e)

Check Result Details
Review Feedback WARN 1 code change request (sourcery-ai[bot]: uncaught OSError/UnicodeDecodeError on CLAUDE.md read at pre_triage_security.py:879). Already classified and tracked by sub-task TC-6961, and fixed in head commit 9fabe0e. No new/unclassified items this run.
Root-Cause Investigation N/A No sub-tasks were created this run (the single finding was already tracked by TC-6961 in a prior run); nothing new to investigate.
Scope Containment PASS All 5 PR files exactly match the task's Files to Modify; no out-of-scope or unimplemented files.
Diff Size PASS 381 lines across 5 files (+331/-50), proportionate to scope; bulk is the 225-line additive test file.
Commit Traceability WARN 2 of 3 commits reference TC-6931; commit 9fabe0e references sub-task TC-6961 (review-feedback child on this PR) rather than the parent task.
Sensitive Patterns PASS No secrets/credentials in added lines; sha256 lock digests and os.environ reads correctly excluded.
CI Status PASS All 8 head-SHA check-runs completed with conclusion success.
Acceptance Criteria PASS 5 of 5 criteria met (discover subcommand + JSON output, JQL kept in sync with SKILL.md via test, fail-closed error handling, collect unchanged, verify-pr harness/lock untouched).
Test Quality PASS Repetitive Test Detection PASS (data-varying cases parameterized); Test Documentation PASS (all added tests have docstrings/given-when-then); Eval Quality: N/A (no run-evals eval result reviews detected).
Test Change Classification ADDITIVE Sole test file modified but purely additive (+225/-0); coverage expanded, nothing removed or weakened.
Verification Commands PASS pytest -> Script Unit Tests (3.11-3.14) success; skillsaw -> Skill Lint success; verify-pr.yaml absent from changeset (git diff empty).

Overall: WARN

Two advisory WARNs, no FAIL. Review Feedback - one sourcery-ai finding (uncaught CLAUDE.md read/decode error at pre_triage_security.py:879) is already tracked by sub-task TC-6961 and fixed in head commit 9fabe0e; no new or unclassified review items this run, so no sub-tasks were created. Commit Traceability - commit 9fabe0e references the child sub-task TC-6961 instead of the parent TC-6931, which is expected for a review-feedback commit on this PR. All scope, security, CI, acceptance-criteria, test-quality, and verification-command checks pass. No code changes are required by this verification; a human reviewer decides whether to merge.


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

@github-actions github-actions 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.

Native Fullsend Eval Results

Head: 9fabe0e
Merge: 0033cb0
Trusted workflow: 7ef0f3c
Reviewed eval source: 7ef0f3c

Native job: success; complete: true.
Execution evidence: valid; quality score (advisory): 28/28 passed.

Case Execution evidence Quality score (advisory)
033-absent valid 4/4
034-empty valid 5/5
035-malformed valid 5/5
036-valid valid 7/7
037-release valid 7/7

Pin the triage-security harness base to the CLAUDE.md read-error fix
so fullsend runs serve the updated pre_triage_security.py. Re-lock
triage-security only; verify-pr is unchanged.

Implements TC-6961

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

🤖 Finished Verify Pr · ✅ Success · Started 9:55 AM UTC · Completed 10:04 AM UTC

Commit: 6abb66a · View workflow run →

Runtime: claude · Model: claude-opus-4-8 · Effort: high · Cost: $5.17

@fullsend-ai-review

Copy link
Copy Markdown

Verification Report for TC-6931 (commit 6abb66a)

Check Result Details
Review Feedback WARN 1 code change request (sourcery-ai[bot]: uncaught OSError/UnicodeDecodeError on CLAUDE.md read at pre_triage_security.py:879). Already classified and tracked by sub-task TC-6961, and fixed in the head tree via the _read_text helper. No new/unclassified items this run.
Root-Cause Investigation N/A No sub-tasks created this run; the finding was already tracked by TC-6961 and root-caused by TC-6962 in prior runs (idempotent). Nothing new to investigate.
Scope Containment PASS All 5 PR files exactly match the task's Files to Modify; no out-of-scope or unimplemented files.
Diff Size PASS 381 lines across 5 files (+331/-50), proportionate to a new-entry-point feature; bulk is the 225-line additive test file.
Commit Traceability WARN 2 of 4 commits reference TC-6931; commits 9fabe0e and 6abb66a reference sub-task TC-6961 (the review-feedback child on this same branch) rather than the parent task.
Sensitive Patterns PASS No secrets/credentials in added lines; sha256 lock digests and the JIRA_SERVER_URL env read are correctly excluded as false positives.
CI Status PASS All 8 head-SHA checks completed/success (Script Unit Tests 3.11-3.14, Skill Lint, Plugin Validation, Sourcery review, Trigger Eval Dispatch).
Acceptance Criteria PASS 5/5 criteria met, each backed by an appended unit test; the AC3 read/decode edge case is fixed in the head tree.
Test Quality PASS Repetitive Test Detection PASS, Test Documentation PASS (docstrings + given/when/then), Eval Quality N/A.
Test Change Classification ADDITIVE test_pre_triage_security.py modified +225/-0, purely appended new tests; no deletions or weakened assertions.
Verification Commands PASS Equivalent CI gates (pytest + skillsaw) passed on the head SHA; diff confirms .fullsend/harness/verify-pr.yaml is untouched.

Overall: WARN

The PR implements the discover entry point for untriaged Vulnerabilities exactly as specified: all functional checks pass, CI is green (8/8), acceptance criteria are fully met with matching unit tests, and no secrets were introduced. Two non-blocking WARNs are informational: (1) Review Feedback - the single sourcery-ai finding (uncaught read/decode error at pre_triage_security.py:879) was already classified, tracked by sub-task TC-6961, root-caused by TC-6962, and resolved in the head tree via the _read_text helper; (2) Commit Traceability - the two re-lock/fix commits reference sub-task TC-6961 instead of the parent TC-6931, which is expected since they implement the review-feedback child on the same branch. No new sub-tasks or root-cause tasks were required this run (idempotent). A human reviewer decides whether to merge.


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

@github-actions github-actions 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.

Native Fullsend Eval Results

Head: 6abb66a
Merge: c1f76fd
Trusted workflow: 7ef0f3c
Reviewed eval source: 7ef0f3c

Native job: success; complete: true.
Execution evidence: valid; quality score (advisory): 27/28 passed.

Case Execution evidence Quality score (advisory)
033-absent valid 4/4
034-empty valid 5/5
035-malformed valid 4/5
036-valid valid 7/7
037-release valid 7/7

@mrizzi
mrizzi merged commit 2381c37 into main Oct 15, 2026
20 checks passed
@mrizzi
mrizzi deleted the TC-6931 branch October 15, 2026 11:51
mrizzi added a commit to mrizzi/sdlc-plugins that referenced this pull request Oct 15, 2026
PR RHEcosystemAppEng#344 was rebase-merged, so the pinned 9fabe0e is not on main and
only resolves through refs/pull/344/head. Re-pin the triage-security
base to c489134, the same content as it landed on main, and re-lock
triage-security only. Pinned file hashes are unchanged; verify-pr is
untouched.

Refs TC-6931

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant