This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git


The following commit(s) were added to refs/heads/main by this push:
     new 6cd9fcf  feat(security-issue-sync): detect active RC-vote threads and 
propose `rc voting` label (#361)
6cd9fcf is described below

commit 6cd9fcfaad2c5f8d5dc123c14b1c3efc07a08937
Author: Jarek Potiuk <[email protected]>
AuthorDate: Thu May 28 20:22:14 2026 +0200

    feat(security-issue-sync): detect active RC-vote threads and propose `rc 
voting` label (#361)
    
    Follow-up to apache/airflow-steward#360 (generate-cve-json: gate
    DRAFT → REVIEW on active release vote). #360 added the gating
    mechanism on the generator side — the configured `rc voting` label
    on the tracker is what makes `compute_cna_private_state` emit
    REVIEW instead of DRAFT. This PR teaches the sync skill where the
    label comes from: scan the project's dev mailing list for active
    `[VOTE]` threads and propose adding the label when one matches the
    tracker's fix-PR milestone.
    
    Mechanics (all in `.claude/skills/security-issue-sync/SKILL.md`):
    
    * New sub-step `1h. Detect active release-vote threads`. Opt-in,
      gated on the same `[workflow].release_vote_gating` flag from
      #360. PonyMail is the primary read source (`dev@<project>`,
      21-day window, `[VOTE]` subject filter), Gmail is the fallback.
      Match the version in the vote subject against the tracker's
      fix-PR milestone. Only fires for trackers in the
      `pr merged` → `fix released` window.
    * New row in the `1d` signal table pointing at `1h`.
    * New paragraph under the `2b` Labels bullet covering the add /
      remove proposal shapes. The remove path piggy-backs on the
      existing `pr merged` → `fix released` transition (vote passed,
      release shipped — historical).
    
    Hard rule preserved: no auto-apply. The label is always a
    numbered proposal requiring user confirmation; the human read of
    *"yes, that vote is for our carrier release"* is the gate, since
    the label has real downstream effects on the embedded CVE JSON
    state.
    
    Out of scope (deliberate): failed-vote detection. The heuristic
    is fragile (no canonical `[RESULT]` shape for failures) and the
    re-add cost on the next RC is low. The team removes the label by
    hand when a vote fails.
    
    Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
---
 .claude/skills/security-issue-sync/SKILL.md | 150 ++++++++++++++++++++++++++++
 1 file changed, 150 insertions(+)

diff --git a/.claude/skills/security-issue-sync/SKILL.md 
b/.claude/skills/security-issue-sync/SKILL.md
index 1e75775..e18b90b 100644
--- a/.claude/skills/security-issue-sync/SKILL.md
+++ b/.claude/skills/security-issue-sync/SKILL.md
@@ -729,6 +729,7 @@ update, label change, or next-step recommendation in Step 2:
 | Reporter reply with a confirmed credit line (*"please credit me as …"*, 
*"use handle X"*, *"anonymous is fine"*) | Replace the `Reporter credited as` 
placeholder with the confirmed form; mark the credit question as resolved so 
the next status-update draft does not re-ask it. |
 | Reporter explicit opt-out of credit (*"do not credit me"*, *"anonymous"*) | 
Set the field to `anonymous` and flag the advisory to use that form. |
 | Release manager's `[RESULT][VOTE] Release Airflow <version>` on `<dev-list>` 
for a version that carries the fix | Record the release manager in the "Known 
release managers" subsection of [`AGENTS.md`](../../../AGENTS.md) if not 
already there; flag Step 13 (advisory) as assigned to that person. |
+| Open `[VOTE] Release <project> <version>` thread on 
`dev@<project>.apache.org` for a version that matches the tracker's fix-PR 
milestone, *and* the project has opted into release-vote gating 
([`[workflow].release_vote_gating` in 
`cve-json-config.toml`](../../../tools/vulnogram/generate-cve-json/SKILL.md)) | 
Propose adding the configured `rc voting` label (default name; see [Step 
1h](#1h-detect-active-release-vote-threads-opt-in-asf-projects)). The label 
feeds back into the CVE-JSON gen [...]
 | Advisory archived on `<users-list>` (the announcement message is now visible 
in `lists.apache.org/list.html?<users-list>` — scan the archive with the CVE ID 
when `fix released` is set and the *"Public advisory URL"* body field is empty) 
| This is the **post-advisory lifecycle close-out trigger**. Propose, in a 
single combined apply: (1) populate the *"Public advisory URL"* body field with 
the archive URL; (2) **extract the public-facing short summary from the 
advisory email body** (the [...]
 | Advisory message sent to `[email protected]` / `<users-list>` but archive 
URL not yet visible | No-op transition; **do not** flip the `fix released → 
announced` labels here. The label flip is part of the combined "archive URL 
captured" apply above and only fires when the archive URL is confirmed live on 
`lists.apache.org` (this is the load-bearing real-world signal that the 
advisory actually shipped — a `[VOTE]/[ANNOUNCE]` mail thread in flight without 
an archived URL is ambiguous). |
 | Project-board column drifted from the issue's label-derived state (e.g. a 
tracker carries `pr merged` but is still in the `PR created` column on [Project 
2](<project-board-url>), or `announced` + *Public advisory URL* body field 
populated but the column is still `Fix released`) | Propose moving the project 
item to the correct column per the mapping table in Step 2b. The board is the 
primary security-team overview surface; a stale column hides ownership handoffs 
from the team at a glance. |
@@ -1058,6 +1059,124 @@ dispositions (`invalid` / `duplicate` /
 `wontfix`) — skip the cve.org check entirely and drop the tracker
 from the closed-bucket sweep.
 
+### 1h. Detect active release-vote threads (opt-in, ASF projects)
+
+**Opt-in.** This sub-step only fires when the project has enabled
+release-vote gating in the CVE-JSON generator's config — i.e. when
+`[workflow].release_vote_gating` is `true` in
+[`<project-config>/tools/vulnogram/cve-json-config.toml`](../../../<project-config>/tools/vulnogram/cve-json-config.toml).
+Adopters that publish advisories without a separate release-vote
+step leave the flag off; the sync skill skips this sub-step
+entirely for them and the `rc voting` label is never proposed.
+
+**Why this step exists.** The CVE-JSON generator's `CNA_private.state`
+field follows a tri-state machine: `DRAFT` until the CVE is review-
+ready, then `REVIEW` once an RC for the carrier release is being
+voted, then `PUBLIC` after the advisory ships (see
+[`tools/vulnogram/generate-cve-json/SKILL.md`](../../../tools/vulnogram/generate-cve-json/SKILL.md)
+for the full state machine). The gating is driven by a tracker label
+(`[workflow].rc_voting_label`, default `"rc voting"`); this sub-step
+is the **only place** the sync skill proposes adding or removing
+that label, so the manual *"is there a vote in progress?"* check
+lives here and nowhere else.
+
+**Which trackers this applies to.** Only trackers in the
+`pr merged` → `fix released` window. Concretely:
+
+- Tracker carries `pr merged` (fix landed in `<upstream>`).
+- Tracker does **not** yet carry `fix released` (release has not
+  shipped).
+- A fix-PR milestone is known and parseable (e.g.
+  `Airflow 3.2.3`, `Providers 2026-04-21`); without a target
+  release version there is nothing to match against.
+
+Trackers outside this window are skipped:
+
+- `cve allocated` only (no public PR yet) — too early; nothing
+  to vote on.
+- `fix released` — too late; the vote already happened, the
+  release shipped, and any remaining `rc voting` label is
+  removed by the existing `pr merged` → `fix released`
+  transition (see Step 2b *Labels* bullet).
+- `announced` / closed — out of scope entirely.
+
+**Backend selection.** PonyMail is the primary read source for
+this step regardless of inbox-latency considerations, because
+`dev@<project>.apache.org` is a public list with no private-list
+gate and PonyMail's archive view gives a consistent cross-team
+read. Fall back to Gmail only when PonyMail MCP is not enabled
+/ not authenticated. Per-tracker budget: ≤ 1 archive search
+(adds to the Step 1d combined envelope).
+
+**Resolving the dev-list address.** Default to
+`dev@<project-domain>` derived from the project's `project_url`
+(e.g. `https://airflow.apache.org/` → `[email protected]`).
+Adopters who use a non-standard dev-list address (the project's
+top-level list is somewhere else, or the release-vote conversation
+happens on a sub-team list) can override by setting
+`[workflow].release_vote_list` in the same TOML config — the sync
+skill reads that field if present, falls back to the derived
+default otherwise.
+
+**Query shape (PonyMail).** Search the project's dev list for
+recent `[VOTE]` threads. The window is **last 21 days** —
+generous enough to catch a vote that just opened (the typical
+ASF vote runs 72 hours, but releases sometimes re-cut RCs and
+the conversation spans a couple of weeks) but short enough that
+the result set stays manageable.
+
+```text
+mcp__ponymail__search_list(
+  list: "dev",
+  domain: "<project-domain>",
+  query: "[VOTE]",
+  timespan: "lte=21d",
+  emails_only: true
+)
+```
+
+**Matching against the tracker's fix milestone.** For each
+returned thread:
+
+1. Extract the version from the subject line. ASF [VOTE] subjects
+   follow the convention `[VOTE] Release <Project> <X.Y.Z> from
+   <X.Y.Z>rcN` (sometimes with sibling artifacts on the same vote
+   — `& Task SDK <A.B.C> from <A.B.C>rcM`). Parse the **first**
+   `<X.Y.Z>` token after the project name.
+2. Compare against the tracker's fix-PR milestone (the milestone
+   on the PR, not on the tracker — the tracker's milestone may
+   be a wave-month label like `Providers 2026-04-21`, but the
+   PR's milestone carries the actual carrier version like
+   `Airflow 3.2.3`). For wave-based provider milestones, also
+   check whether the vote's release ships the right *provider
+   wave* — the wave milestone on the tracker (e.g. *Providers
+   2026-04-21*) maps to a `[VOTE] Release Providers …` thread.
+3. Check the thread's most recent message: a
+   `[RESULT][VOTE]` reply is a closed vote. Open votes have **no**
+   `[RESULT]` reply yet. Closed votes do not warrant a label
+   add — by the time the vote has resolved, either the release
+   shipped (and `fix released` flow takes over) or the vote
+   failed (and the team will cut a fresh RC; the next sync will
+   pick up the new `[VOTE]` thread).
+
+**Record in the per-tracker state bag.** Two booleans:
+
+- `release_vote_in_progress`: `true` when the conditions above
+  fire — a tracker in the `pr merged` window has a matching
+  open `[VOTE]` thread on the right list.
+- `release_vote_thread_url`: the PonyMail thread URL if found
+  (`https://lists.apache.org/thread/<hash>?<list>@<domain>`) —
+  used as the rationale in the Step 2b proposal.
+
+**Hard rule — no auto-apply.** Like every other signal in this
+step, the result feeds into Step 2b's proposal and only applies
+after the user confirms. The sync skill never adds the `rc voting`
+label silently — the label has real downstream effects (the next
+CVE-JSON regen flips the embedded `CNA_private.state` to `REVIEW`,
+which a release manager pastes into Vulnogram), so the human
+read of *"yes, that vote is for our carrier release"* is the
+required gate.
+
 ---
 
 ## Step 2 — Build a proposal (do not apply anything yet)
@@ -1076,6 +1195,37 @@ Each proposed change is a **numbered item** and must be 
explicit about *what*
 will change and *why*. Group them by category:
 
 - **Labels to add / remove** — e.g. *"remove `needs triage`; add `airflow`"*. 
Reason: one scope label is required by the process once triage is complete.
+
+  **Release-vote label (opt-in, ASF projects only).** When release-
+  vote gating is enabled (`[workflow].release_vote_gating = true` in
+  the CVE-JSON generator's config), Step 1h's detection feeds two
+  more proposal shapes into this category:
+
+  - *Add* the configured `rc voting` label (default `"rc voting"`)
+    when a matching open `[VOTE]` thread was detected on
+    `dev@<project>.apache.org` and the tracker is in the
+    `pr merged` window. The proposal must quote the PonyMail
+    thread URL as the rationale so the security team can spot-
+    check the match before confirming — *"detected active vote
+    thread: `<thread-url>` carrying version X.Y.Z, matches fix-PR
+    milestone"*.
+  - *Remove* the `rc voting` label when the existing
+    `pr merged` → `fix released` transition fires (the release
+    shipped — the vote passed and is now historical). This
+    removal piggy-backs on the same proposal that adds
+    `fix released` and removes `pr merged`; surface it as part
+    of the same numbered item so the user confirms one combined
+    label flip rather than two separate ones. The label can
+    also be removed by hand if a vote *fails* and the team
+    re-cuts an RC; the sync skill does not actively detect
+    failed votes (the heuristic is fragile and the human re-add
+    on the next vote is cheap).
+
+  The label gating is **only** consulted for projects that
+  opted in via the generator config. For non-ASF adopters that
+  did not opt in, this entire sub-paragraph is a no-op — the
+  label is never proposed, added, or referenced, and the
+  generator's legacy *"ready ⇒ REVIEW"* behaviour applies.
 - **Milestone** — propose the matching release milestone on the
   issue. The milestone format depends on the scope label and is
   project-specific; for the adopting project see

Reply via email to