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 09f4288  feat(security-issue-sync): pre-flight no-op classifier skips 
obvious-idle trackers in bulk mode (#414)
09f4288 is described below

commit 09f42880cfc0875e3f8980ac6151a6be4627e02f
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sun May 31 13:47:03 2026 +0200

    feat(security-issue-sync): pre-flight no-op classifier skips obvious-idle 
trackers in bulk mode (#414)
    
    Bulk sync (sync all, sync announced, etc.) currently dispatches
    one full subagent per resolved tracker. Each subagent loads the
    skill + does a `gh issue view` + reads comments + reads mail +
    returns a structured report — ~50 KB per subagent transcript.
    On bulk sweeps where 30–50% of trackers are in steady state
    (closed > 30d with `announced`, or open with the full
    cve-allocated + pr-merged + announced label set and no recent
    activity), the subagent's full work is a no-op that produces an
    empty proposal — pure waste.
    
    This change inserts a Step 1b pre-flight classifier between
    selector-resolution and subagent dispatch. One batched
    `gh api graphql` round-trip fetches `state`, `closedAt`,
    `updatedAt`, `labels`, and the last comment's author+timestamp
    for every resolved issue at once (aliased multi-field query,
    ~3 KB request, ~6 KB response for 30 issues). A conservative
    rule table classifies each as `dispatch` / `dispatch-urgent` /
    `skip-noop`; only the non-skipped ones get subagents.
    
    Safety:
    
    * Conservative — `skip-noop` fires only when multiple signals
      align (closed AND age AND label set AND inactive last comment
      AND bot last commenter).
    * `updatedAt` within last 7 days is an absolute override; never
      skip a tracker with recent activity regardless of other
      signals.
    * Pre-flight only applies to set-resolving selectors
      (`sync all`, `sync announced`, label/title selectors). An
      explicit number selector like `sync #232, #233` never skips.
    * Every skip appears in the proposal's "Pre-flight skipped"
      group with the rule that fired — never silent. The user can
      `force-sync <N>` any of them at confirmation.
    * `--no-preflight` opts out entirely.
    
    This is a skill-instruction change; no Python tool added. The
    orchestrator builds the GraphQL query directly. Rules can be
    iterated quickly by editing the table; if real-world results
    show the classifier is too aggressive or too timid, the patches
    are one-line edits to the rule table.
---
 .claude/skills/security-issue-sync/bulk-mode.md | 129 +++++++++++++++++++++++-
 1 file changed, 125 insertions(+), 4 deletions(-)

diff --git a/.claude/skills/security-issue-sync/bulk-mode.md 
b/.claude/skills/security-issue-sync/bulk-mode.md
index 99182d3..d8992a4 100644
--- a/.claude/skills/security-issue-sync/bulk-mode.md
+++ b/.claude/skills/security-issue-sync/bulk-mode.md
@@ -59,10 +59,103 @@ concurrently, which is exactly what the sync needs.
    When the selector resolves to zero issues, tell the user and stop
    — do not fall back to `sync all`.
 
+1b. **Pre-flight no-op classifier — skip trackers that obviously need no 
work.**
+    Before spawning subagents, do one batched read to fetch lightweight
+    state for every resolved tracker, classify each as
+    `dispatch` / `dispatch-urgent` / `skip-noop`, and dispatch
+    subagents only for the non-skipped ones. A no-op skip costs the
+    full ~50 KB subagent transcript per tracker plus the subagent's
+    own per-call API budget; on bulk sweeps where 30–50% of trackers
+    are in steady state, the classifier converts that into one
+    GraphQL round-trip.
+
+    **One query, one round-trip.** Build an aliased multi-field
+    GraphQL query that fetches state for every resolved issue at
+    once:
+
+    ```bash
+    gh api graphql --raw-field query="$(cat <<'GQL'
+    query {
+      repository(owner: "<owner>", name: "<repo>") {
+        i<N1>: issue(number: <N1>) {
+          number state closedAt updatedAt
+          labels(first: 30) { nodes { name } }
+          comments(last: 1) { nodes { author { login } createdAt } }
+        }
+        i<N2>: issue(number: <N2>) { ... }
+        # repeat one aliased block per resolved issue
+      }
+    }
+    GQL
+    )"
+    ```
+
+    The aliased-field form (`i<N>: issue(number: <N>) { ... }`)
+    works for any number of issues in a single query. For a 30-issue
+    bulk sweep the request is ~3 KB and the response is ~6 KB —
+    cheaper than a single subagent transcript.
+
+    **Classification rule table.** Apply the rules **in order**;
+    the first match wins. Conservative by design — `skip-noop`
+    fires only when multiple signals all align.
+
+    | Signals | Decision | Reason recorded in recap |
+    |---|---|---|
+    | `updatedAt` within the last **7 days** | `dispatch` | recent activity 
safety override — never skip |
+    | Last comment author is **not** a bot AND `createdAt` within last **24h** 
| `dispatch-urgent` | reporter just replied |
+    | Closed > **30 days** ago AND has `announced` label | `skip-noop` | 
`post-announce; CVE published` |
+    | Closed > **90 days** ago AND no `announced` label | `skip-noop` | `stale 
closed (invalid/duplicate/abandoned)` |
+    | Open AND has `cve allocated` + `pr merged` + `announced` AND last 
comment > 14d ago AND last comment author is a bot | `skip-noop` | `all phases 
done; awaiting closure heuristic` |
+    | Open AND has `cve allocated` + `pr merged` AND last comment > 14d ago 
AND last comment author is a bot | `skip-noop` | `awaiting release` |
+    | Anything else | `dispatch` | — |
+
+    **Bot detection.** The "author is a bot" test is *login matches
+    one of*: `github-actions[bot]`, `dependabot[bot]`, the
+    project's `<sync-bot>` handle if configured in
+    [`<project-config>/project.md`](../../../<project-config>/project.md),
+    or any GitHub user with the `[bot]` suffix. If the project has
+    a personal-account bot, list it in the override file at
+    
[`.apache-steward-overrides/security-issue-sync.md`](../../../docs/setup/agentic-overrides.md).
+
+    **Hard rules**:
+
+    - **Never silent.** Every `skip-noop` decision appears in the
+      recap under a *"Pre-flight skipped"* group with the rule
+      that fired and the signals it saw. The user can request a
+      forced sync of any skipped tracker by name at confirmation
+      time (*"force-sync #232"*) — the orchestrator then spawns a
+      subagent for that tracker on the next turn.
+    - **Selector overrides default behaviour.** If the user named
+      explicit issue numbers (`sync #232, #233`) rather than a
+      label/state selector, **never skip** — they asked for those
+      specific trackers and a silent skip would be surprising.
+      Pre-flight only applies when the selector resolved to a set
+      (`sync all`, `sync announced`, label/title selectors).
+    - **Opt-out.** Pass `--no-preflight` in the user's selector
+      (e.g. `sync all --no-preflight`) to bypass the classifier
+      entirely and dispatch a subagent for every resolved tracker.
+      Useful for trust-but-verify sweeps after a rule change.
+    - **Dispatch-urgent is just dispatch.** The `dispatch-urgent`
+      decision tells the orchestrator to flag the tracker in the
+      recap as *"recent reporter activity"*, but the subagent it
+      spawns is identical to the normal dispatch path. The
+      distinction is for the operator's attention, not the
+      subagent's behaviour.
+
+    **What pre-flight does NOT do.** It does **not** decide
+    *what action* a tracker needs — that is still the subagent's
+    job. It only decides whether spawning a subagent is worth it
+    at all. A tracker classified as `dispatch` still goes through
+    the full Step 1 (gather) → Step 2 (proposal) flow inside its
+    subagent.
+
 2. **Spawn one subagent per issue, in a single message.** Use the
    `general-purpose` subagent type and send all `Agent` tool calls in
    the **same assistant message** so they run concurrently. For 20
-   issues, that is 20 parallel `Agent` calls in one turn.
+   issues that survived pre-flight, that is 20 parallel `Agent`
+   calls in one turn. Trackers classified as `skip-noop` by Step 1b
+   are **not** dispatched — they only appear in the recap under the
+   *"Pre-flight skipped"* group.
 
    Each subagent prompt must be self-contained and must instruct the
    subagent to:
@@ -99,10 +192,21 @@ concurrently, which is exactly what the sync needs.
      change tracker state but do not alter the published CVE
      record.
 
-4. **Present both buckets as merged bulk proposals; the
+4. **Present buckets as merged bulk proposals; the
    CVE-affecting bucket gets a richer per-item view.** The
-   two buckets are presented to the user differently:
-
+   proposal has three groups:
+
+   - **Pre-flight skipped** *(if any)* — list every tracker the
+     Step 1b classifier marked `skip-noop`, one per line, with
+     the rule that fired and the signals it saw. Example:
+     `#232 (closed 2025-12-04, announced label) — post-announce; CVE 
published`.
+     This group is **informational** — the proposal does **not**
+     ask the user to confirm or apply anything for these trackers.
+     The user can request a forced sync of any skipped tracker
+     by name at confirmation (*"force-sync #232"*) and the
+     orchestrator dispatches a subagent for it on the next turn.
+     Render the group at the **top** of the proposal so the user
+     sees the skip context before the proposed actions.
    - **Non-CVE-affecting bucket** — fold into one combined
      proposal, same shape as the legacy bulk mode. The user
      confirms once with `all`, `NN:all`, `NN:1,3`, or per-issue
@@ -151,6 +255,12 @@ concurrently, which is exactly what the sync needs.
    - `<N>:skip` — skip tracker `<N>` entirely.
    - `<N>:edit <item-number>: <new value>` — replace the
      proposed item with a free-form override before applying.
+   - `force-sync <N>` — dispatch a subagent for a tracker that
+     Step 1b classified as `skip-noop`. The orchestrator runs
+     the full Step 1 gather for `<N>` on the next turn and
+     folds its result into the next proposal. Use when the
+     pre-flight heuristic was wrong and you know there's work
+     to do.
    - `cancel` / `none` — apply nothing.
 
    **Proposal order in the merged pack.** Trackers appear in
@@ -231,6 +341,17 @@ or an ambiguous credit line).
 - **Link-form self-check still applies** to the orchestrator's
   merged output — every `#NNN` must be rendered as a clickable link
   per Golden rule 2.
+- **Pre-flight skips are never silent.** Every Step 1b `skip-noop`
+  decision appears explicitly in the proposal's *"Pre-flight
+  skipped"* group with the rule that fired. The user can
+  `force-sync <N>` any of them at confirmation. The opt-out
+  `--no-preflight` flag bypasses Step 1b entirely.
+- **Pre-flight never skips an explicitly-named tracker.** If the
+  user named issue numbers in the selector (`sync #232, #233`),
+  Step 1b only runs the classifier for context (so the recap can
+  surface *"#232 looks idle — sync anyway?"*) but never actually
+  skips. Skip-eligible selectors are state/label/title selectors
+  like `sync all` or `sync announced`.
 
 ### When bulk mode is **not** appropriate
 

Reply via email to