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 e322008  feat(sync): auto-push CVE JSON via Vulnogram OAuth API when 
available (#133)
e322008 is described below

commit e322008f1e87812f61642415ac6362371792fdf5
Author: Jarek Potiuk <[email protected]>
AuthorDate: Tue May 12 04:59:04 2026 +0200

    feat(sync): auto-push CVE JSON via Vulnogram OAuth API when available (#133)
    
    * feat(security-issue-sync): auto-push CVE JSON via Vulnogram OAuth API at 
sync time
    
    When the operator's machine has a valid Vulnogram OAuth session
    configured (the one-time `vulnogram-api-setup` per machine),
    `security-issue-sync` now pushes the regenerated CVE JSON to the
    record directly via `vulnogram-api-record-update` immediately after
    Step 5a's regen. The push is mechanical and follows from the same
    JSON the user just approved as part of the body update; no separate
    confirmation prompt. State-machine transitions
    (`DRAFT` → `REVIEW` → `READY` → `PUBLIC`) stay with the release
    manager because they include the CNA-feed dispatch trigger.
    
    Three outcomes from `vulnogram-api-check`:
    
    - `valid` → push runs; on success the timestamp is recorded and the
      release-manager hand-off comment is rendered from the new
      OAuth-pushed variant template (RM verifies + does the UI
      state-transition clicks, no paste step).
    - `expired` → push skipped, manual-paste hand-off applies; the
      recap nudges the operator to re-run `vulnogram-api-setup`.
    - `not-configured` → push skipped silently; today's manual-paste
      hand-off applies.
    
    New templates carry the same v1 marker as the manual-paste
    variants, so idempotency detection is unchanged. The Step 4 apply
    mechanic now flips into PATCH-edit-in-place mode when the marker
    is found and the existing body's variant differs from the variant
    the current sync would render — the comment URL, timeline position,
    and prior notifications survive the variant flip. Same
    PATCH-don't-post rationale as the rollup-comment upsert: a fresh
    duplicate comment buries the timeline.
    
    Generated-by: Claude Code (Opus 4.7)
    
    * ci(security-issue-sync): fix prek typos + doctoc on new oauth templates
    
    Two prek failures on apache/airflow-steward#133:
    
    1. typos hook flagged 'PATCHed' (typo tool reads 'Hed' as 'Head').
       Replaced with 'PATCH-edited' — the variant already used elsewhere
       in the same skill, no semantic change.
    2. doctoc hook regenerated TOCs for the two new template files. Added
       them at the top of each file matching the existing
       release-manager-handoff-comment.md convention.
    
    No behavior change — docs only.
    
    Generated-by: Claude Code (Opus 4.7)
    
    * ci(gmail-threading): add language tag to fenced example block (MD040)
    
    Markdownlint MD040 — fenced code blocks must declare a language. The 
example block at threading.md:125 (worked example of multi-thread Security 
mailing list field shape, landed in #131) was bare, now tagged `text`. Surfaces 
on this PR's CI because #131 merged with the prek failure unresolved.
---
 .claude/skills/security-issue-sync/SKILL.md        | 278 +++++++++++++++++----
 tools/gmail/threading.md                           |   2 +-
 ...release-manager-handoff-comment-oauth-pushed.md | 160 ++++++++++++
 ...ase-manager-publication-comment-oauth-pushed.md |  86 +++++++
 4 files changed, 481 insertions(+), 45 deletions(-)

diff --git a/.claude/skills/security-issue-sync/SKILL.md 
b/.claude/skills/security-issue-sync/SKILL.md
index 54d7f69..06a37ca 100644
--- a/.claude/skills/security-issue-sync/SKILL.md
+++ b/.claude/skills/security-issue-sync/SKILL.md
@@ -1534,29 +1534,56 @@ will change and *why*. Group them by category:
   release actually ships. Do not propose it on subsequent runs once
   it has already been posted (idempotency check below).
 
-  **Idempotency.** Before proposing, scan the issue's existing
-  comments for the marker
+  **Idempotency + variant edit-in-place.** Before proposing, scan
+  the issue's existing comments for the marker
   ```html
   <!-- apache-steward: release-manager-handoff v1 -->
   ```
-  exactly. If a comment carrying this marker already exists, **do
-  not propose a re-post** — surface as *"hand-off comment already
-  posted on `<comment-url>` (skipping)"* in the observed-state dump
-  and move on. The marker is on line 1 of the comment body so a
-  literal `gh issue view --json comments --jq` filter can detect it
-  cheaply.
+  exactly. The marker is on line 1 of the comment body so a
+  literal `gh issue view --json comments --jq` filter detects it
+  cheaply. Three outcomes:
+
+  - **No marker found.** Propose a fresh POST of the appropriate
+    variant (per Step 5c's decision).
+  - **Marker found, current body matches the variant the skill
+    would render this run.** No-op; surface as *"hand-off comment
+    already posted on `<comment-url>` and matches the current
+    variant (skipping)"* in the observed-state dump.
+  - **Marker found, current body does NOT match the variant the
+    skill would render this run.** Propose a PATCH-in-place
+    (rewrite the body to the current variant). Common cases:
+    a previous sync posted the manual-paste variant and this
+    sync's OAuth push succeeded → flip to the OAuth-pushed
+    variant; or vice-versa (cookie expired between sync runs).
+    The PATCH preserves the comment URL, the timeline position,
+    and any notifications already delivered for it; the body
+    flip is what the RM cares about. Same PATCH-don't-post
+    rationale as the rollup-comment upsert.
 
   **Body source.** The comment body comes from the project's
-  configured CVE tool — the path is
-  `tools/<cve-tool>/release-manager-handoff-comment.md` where
-  `<cve-tool>` is the value of `cve_tool` in
-  
[`<project-config>/project.md`](../../../<project-config>/project.md#cve-tooling)
-  (for projects on Vulnogram, that resolves to
-  
[`tools/vulnogram/release-manager-handoff-comment.md`](../../../tools/vulnogram/release-manager-handoff-comment.md)).
-  The template is parameterised; the substitutions the skill
-  performs are listed in the template's HTML-comment header. Do not
-  fork or paraphrase the template body in the proposal — load it
-  verbatim, substitute the placeholders, post.
+  configured CVE tool, in two **variants** picked by Step 5c:
+
+  - **OAuth-pushed variant** —
+    `tools/<cve-tool>/release-manager-handoff-comment-oauth-pushed.md`
+    (for Vulnogram:
+    
[`tools/vulnogram/release-manager-handoff-comment-oauth-pushed.md`](../../../tools/vulnogram/release-manager-handoff-comment-oauth-pushed.md)).
+    Used when Step 5b's `vulnogram-api-record-update` succeeded
+    this sync run.
+  - **Manual-paste variant (today's default)** —
+    `tools/<cve-tool>/release-manager-handoff-comment.md`
+    (for Vulnogram:
+    
[`tools/vulnogram/release-manager-handoff-comment.md`](../../../tools/vulnogram/release-manager-handoff-comment.md)).
+    Used when Step 5b skipped (no credentials, expired session)
+    or the push failed.
+
+  Both variants carry the same marker on line 1, so idempotency
+  detection is unchanged. Both templates are parameterised; the
+  substitutions the skill performs are listed in each template's
+  HTML-comment header (the OAuth-pushed variant additionally
+  takes `PUSH_TIMESTAMP`). Do not fork or paraphrase the
+  template body in the proposal — load it verbatim, substitute
+  the placeholders, post or PATCH per the idempotency rules
+  above.
 
   **Resolving placeholders.** All values come from configuration or
   from the tracker itself, so there is no free-form drafting:
@@ -1588,11 +1615,16 @@ will change and *why*. Group them by category:
     repo's `<project-config>/canned-responses.md`.
 
   **Apply mechanic** — see the *Release-manager hand-off comment*
-  bullet in Step 4 below; it is a fresh `gh issue comment`, not a
-  PATCH on the rollup.
+  bullet in Step 4 below; depending on the idempotency outcome it
+  is either a fresh `gh issue comment` (first hand-off) or a
+  `gh api -X PATCH` on the existing comment's REST id (variant
+  flip). Neither path PATCHes the rollup.
 
-  **Recap.** Surface the new comment URL in the recap (Step 6) so
-  the user can click through and verify the post.
+  **Recap.** Surface the comment URL (new or PATCH-edited) in the recap
+  (Step 6) so the user can click through and verify the result.
+  When the path was a PATCH, the recap notes which variant the
+  body now carries (*"flipped to OAuth-pushed variant after this
+  sync's auto-push succeeded"* or vice-versa).
 
 - **Publication-ready notification comment** — when this sync pass
   proposes populating the *Public advisory URL* body field (Step 14
@@ -1828,34 +1860,70 @@ before moving on to the next item. Use:
   comment with `gh api -X DELETE
   repos/<tracker>/issues/comments/<id>`. Never delete before the
   PATCH lands.
-- **Release-manager hand-off comment:** load the body template from
-  `tools/<cve-tool>/release-manager-handoff-comment.md`, substitute
-  the placeholders (per the *Release-manager hand-off comment*
-  bullet in Step 2b), write the result to a temp file, then post:
+- **Release-manager hand-off comment:** pick the body template
+  per the variant decision from
+  [Step 5c](#step-5c--reconcile-the-release-manager-hand-off-comment) —
+  `tools/<cve-tool>/release-manager-handoff-comment-oauth-pushed.md`
+  when this sync run's `vulnogram-api-record-update` push succeeded,
+  `tools/<cve-tool>/release-manager-handoff-comment.md` (manual-paste)
+  otherwise. Substitute the placeholders (per the *Release-manager
+  hand-off comment* bullet in Step 2b; the OAuth-pushed variant also
+  takes `PUSH_TIMESTAMP`), write the result to a temp file. Then
+  decide POST vs PATCH by grepping the tracker's comment list for
+  the marker:
 
   ```bash
-  gh issue comment <N> --repo <tracker> \
-    --body-file <tmpfile>
+  existing=$(gh issue view <N> --repo <tracker> --json comments \
+    --jq '[.comments[] | select(.body | startswith("<!-- apache-steward: 
release-manager-handoff v1 -->"))] | .[0].id // empty')
   ```
 
-  This is a **fresh comment**, not a PATCH on the rollup. The
-  `<!-- apache-steward: release-manager-handoff v1 -->` marker on
-  line 1 of the template is what subsequent sync runs grep for to
-  enforce idempotency — preserve it verbatim. Capture the new
-  comment URL from the post for the Step 6 recap.
-
-  Before posting, **scrub the resolved body** for the same bare-
-  name → `@`-handle replacements documented for the rollup PATCH
-  above, so the `RM_HANDLE` substitution actually notifies the
-  release manager.
+  - **No marker found (first hand-off, or marker lost)** — POST a
+    fresh comment:
+
+    ```bash
+    gh issue comment <N> --repo <tracker> --body-file <tmpfile>
+    ```
+
+  - **Marker found** — fetch the existing body, compare against the
+    re-rendered body for the current variant, and PATCH-edit
+    in-place only if they differ (skip the round-trip when the
+    body is byte-identical):
+
+    ```bash
+    # extract the REST id (not the GraphQL node id)
+    rest_id=$(gh api repos/<tracker>/issues/comments \
+      --jq '.[] | select(.node_id == "<existing>") | .id')
+    # PATCH
+    jq -n --rawfile body <tmpfile> '{body: $body}' | \
+      gh api -X PATCH repos/<tracker>/issues/comments/${rest_id} \
+        --input - --jq '{id, updated_at}'
+    ```
+
+  The PATCH path is what powers the "OAuth-pushed today, manual-paste
+  next sync (because the cookie expired)" recovery: the existing
+  comment's body flips between variants in place, keeping a single
+  comment as the canonical RM-facing surface and avoiding the
+  "fresh duplicate buries the timeline" failure mode (same rationale
+  as the rollup-comment PATCH-don't-post rule).
+
+  Capture the comment URL (POST or PATCH) for the Step 6 recap.
+  Before posting / PATCHing, **scrub the resolved body** for the
+  same bare-name → `@`-handle replacements documented for the rollup
+  PATCH above, so the `RM_HANDLE` substitution actually notifies
+  the release manager.
 - **Publication-ready notification comment:** same recipe as the
-  hand-off comment above, but loading
-  `tools/<cve-tool>/release-manager-publication-comment.md`. The
-  marker is `<!-- apache-steward: release-manager-publication-ready v1 -->`.
+  hand-off comment above (same variant decision, same POST-vs-PATCH
+  logic, same scrub), but loading
+  `tools/<cve-tool>/release-manager-publication-comment-oauth-pushed.md`
+  or `tools/<cve-tool>/release-manager-publication-comment.md` based
+  on the same Step 5c variant choice. The marker is
+  `<!-- apache-steward: release-manager-publication-ready v1 -->`.
   Apply right after the *Public advisory URL* body-field update has
-  landed and the CVE JSON has been regenerated (Step 5) — that way
-  the comment's *"the JSON has been regenerated to include the
-  archive URL"* claim is true at the moment the RM reads it.
+  landed, the CVE JSON has been regenerated (Step 5a), and (when
+  applicable) the OAuth push has landed (Step 5b) — that way the
+  comment's *"the JSON has been regenerated to include the archive
+  URL and pushed to the record"* claim is true at the moment the
+  RM reads it.
 - **Close / reopen:** `gh issue close <N> --repo <tracker> --reason completed` 
(or `not planned`).
   When this is a GitHub-backed tracker that uses a project board,
   **always** follow a successful close with the **archive-from-board**
@@ -2065,6 +2133,128 @@ recap so the user has one-click access to the attached 
JSON.
 
 ---
 
+## Step 5b — Push the regenerated JSON to Vulnogram via the OAuth API
+
+The regenerated JSON above is paste-ready for Vulnogram. **When the
+operator's machine has a valid Vulnogram OAuth session configured**
+(the one-time
+`uv run --project <framework>/tools/vulnogram/oauth-api vulnogram-api-setup`
+per machine — see
+[`tools/vulnogram/oauth-api/README.md`](../../../tools/vulnogram/oauth-api/README.md)),
+**sync pushes the JSON to the record directly** instead of leaving the
+paste step to the release manager. The push is mechanical and follows
+from the same JSON the user just approved as part of the body update;
+it does **not** advance the Vulnogram state machine
+(`DRAFT` → `REVIEW` → `READY` → `PUBLIC`) — those transitions stay
+with the RM because they include the CNA-feed dispatch trigger.
+
+### Decision flow
+
+1. **Skip-condition gate.** Skip 5b entirely when 5a was skipped
+   (no CVE allocated; tracker closed as invalid / duplicate / not
+   CVE worthy). There is no record to push to.
+
+2. **Probe the session** — `vulnogram-api-check`:
+
+   ```bash
+   uv run --project <framework>/tools/vulnogram/oauth-api vulnogram-api-check
+   ```
+
+   Three outcomes:
+
+   - **`valid`** → proceed to step 3.
+   - **`expired`** → skip the push, surface a one-line reminder in
+     the Step 6 recap: *"Vulnogram OAuth session expired — re-run
+     `vulnogram-api-setup` to restore automatic push; using
+     manual-paste hand-off this run."* Fall through to the
+     manual-paste hand-off variant for any 5c comment work below.
+   - **`not-configured`** → skip the push silently. Not every
+     operator runs the API path; that is fine, today's manual-paste
+     hand-off still works. Fall through to the manual-paste hand-off
+     variant for any 5c comment work below.
+
+3. **Extract the regenerated JSON.** The
+   [`generate-cve-json`](../../../tools/vulnogram/generate-cve-json/SKILL.md)
+   step in 5a embedded the JSON inside the tracker body between the
+   `<!-- generate-cve-json: cve=<CVE> version=v1 -->` /
+   `<!-- generate-cve-json:end ... -->` markers. Re-run the
+   generator with `--stdout` (no `--attach`) into a temporary file,
+   or extract from the body via `awk` between the markers — either
+   yields a byte-identical payload because the generator is
+   deterministic. Conventional path:
+   `/tmp/cve-<CVE-ID>-<N>.json`.
+
+4. **Push** — `vulnogram-api-record-update`:
+
+   ```bash
+   uv run --project <framework>/tools/vulnogram/oauth-api 
vulnogram-api-record-update \
+     --cve-id <CVE-ID> --json-file /tmp/cve-<CVE-ID>-<N>.json
+   ```
+
+   Capture the call's exit code and `stdout` / `stderr`:
+
+   - **`exit 0`** → push succeeded. Record the ISO-8601 timestamp
+     (`PUSH_TIMESTAMP`); the Step 5c comment work uses the
+     **OAuth-pushed variant** of the relevant template; the Step 6
+     recap includes *"CVE record auto-pushed to Vulnogram at
+     `PUSH_TIMESTAMP`."*
+   - **`exit ≠ 0`** → push failed. Surface the error verbatim in
+     the Step 6 recap and **fall back** to the manual-paste hand-off
+     for the Step 5c comment work. Do **not** retry on the same
+     sync run — a transient HTTP error or a schema rejection is
+     better surfaced once and re-tried on the next sync (after
+     either Gmail-side or body-side state has settled).
+
+5. **Idempotence note.** The Vulnogram upsert endpoint is
+   idempotent: re-posting the same JSON on a subsequent sync is a
+   no-op on Vulnogram's side. The sync skill does not need to
+   short-circuit "already pushed this JSON" — every successful
+   sync run that re-regenerated the JSON should re-push to keep
+   the record byte-identical to the tracker body.
+
+## Step 5c — Reconcile the release-manager hand-off comment
+
+The Step 12 (`pr merged` → `fix released`) **hand-off comment** and
+the Step 14 (advisory archived) **publication-ready notification**
+both come in two variants:
+
+| Variant | Template | When |
+|---|---|---|
+| Manual-paste (today's default) | 
[`tools/vulnogram/release-manager-handoff-comment.md`](../../../tools/vulnogram/release-manager-handoff-comment.md),
 
[`tools/vulnogram/release-manager-publication-comment.md`](../../../tools/vulnogram/release-manager-publication-comment.md)
 | Step 5b skipped (`expired` / `not-configured`) or the push failed |
+| OAuth-pushed | 
[`tools/vulnogram/release-manager-handoff-comment-oauth-pushed.md`](../../../tools/vulnogram/release-manager-handoff-comment-oauth-pushed.md),
 
[`tools/vulnogram/release-manager-publication-comment-oauth-pushed.md`](../../../tools/vulnogram/release-manager-publication-comment-oauth-pushed.md)
 | Step 5b's push succeeded this run |
+
+Both variants of each comment carry the **same marker** on line 1
+(`<!-- apache-steward: release-manager-handoff v1 -->` for the
+hand-off, `<!-- apache-steward: release-manager-publication-ready v1 -->`
+for the publication-ready). Idempotency detection still keys on the
+marker — the variant choice does not get its own marker. When the
+marker is found on the tracker, the existing comment's body is
+PATCH-edited in place to the variant that matches the current sync
+run's outcome (the rationale mirrors the rollup-comment PATCH-don't-
+post rule: a fresh duplicate comment buries the timeline). Concrete
+rules:
+
+- **First-time hand-off** (no existing comment, label transition
+  fires this run) → POST the appropriate variant.
+- **Subsequent sync, OAuth push succeeded this run** → PATCH the
+  existing comment to the OAuth-pushed body (refreshing the
+  `PUSH_TIMESTAMP` placeholder). If the existing comment is already
+  the OAuth-pushed variant, the only material change is the
+  timestamp — still PATCH; the timestamp is the audit trail.
+- **Subsequent sync, push failed (or skipped)** → PATCH the existing
+  comment to the manual-paste variant. The RM sees a fresh
+  "please paste" ask the moment the auto-push stops working,
+  which is the right escalation.
+- **Subsequent sync, no relevant transition fired and the JSON did
+  not change** → no PATCH. Idempotency: marker present, body
+  byte-identical, nothing to do.
+
+The apply mechanic for both POST and PATCH lives in Step 4 — see
+the *Release-manager hand-off comment* and *Publication-ready
+notification comment* bullets there.
+
+---
+
 ## Step 6 — Recap
 
 After the regeneration step finishes, print a short recap:
diff --git a/tools/gmail/threading.md b/tools/gmail/threading.md
index 6763d89..d593ac4 100644
--- a/tools/gmail/threading.md
+++ b/tools/gmail/threading.md
@@ -122,7 +122,7 @@ mentioned) is **primary**.
 
 Worked example. The body field on a real tracker reads:
 
-```
+```text
 No public archive URL — tracked privately on Gmail thread `19dc8d4675dfc1f1`.
 Aymane Maguiti (huntr.com bounty `abdbcf11-…`-class duplicate, ASF-relayed by 
@apache/security on 2026-05-04T09:22:25Z): Gmail thread `19def0954b27ac31`.
 ```
diff --git a/tools/vulnogram/release-manager-handoff-comment-oauth-pushed.md 
b/tools/vulnogram/release-manager-handoff-comment-oauth-pushed.md
new file mode 100644
index 0000000..4ccf7ba
--- /dev/null
+++ b/tools/vulnogram/release-manager-handoff-comment-oauth-pushed.md
@@ -0,0 +1,160 @@
+<!-- START doctoc generated TOC please keep comment here to allow auto update 
-->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents**  *generated with 
[DocToc](https://github.com/thlorenz/doctoc)*
+
+- [🚀 Release-manager hand-off — `CVE_ID` (CVE JSON 
auto-pushed)](#-release-manager-hand-off--cve_id-cve-json-auto-pushed)
+  - [Step-by-step](#step-by-step)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+<!-- SPDX-License-Identifier: Apache-2.0
+     https://www.apache.org/licenses/LICENSE-2.0 -->
+
+<!--
+     OAuth-pushed variant of the release-manager hand-off comment
+     posted by `security-issue-sync` at the `pr merged` → `fix released`
+     transition (Step 12 of the lifecycle), when the operator's machine
+     had a valid Vulnogram OAuth session at sync time **and** the
+     `vulnogram-api-record-update` push of the regenerated CVE JSON
+     succeeded.
+
+     This template is the counterpart to `release-manager-handoff-
+     comment.md` (the manual-paste variant). The sync skill picks
+     between the two based on the outcome of `vulnogram-api-check` +
+     `vulnogram-api-record-update` — see Step 5b of
+     `.claude/skills/security-issue-sync/SKILL.md` for the decision
+     flow.
+
+     Idempotency: the marker on line below is the **same** as the
+     manual-paste variant's. The skill's idempotency grep keys on the
+     marker only; the variant choice is detected by re-reading the
+     comment body and checking which template's signature it carries.
+     On a re-sync where the previous comment is the manual-paste
+     variant but the current push succeeded, the skill PATCH-edits the
+     comment body in place to the OAuth-pushed body (no second comment).
+
+     Placeholders the skill substitutes:
+
+       CVE_ID                    e.g. CVE-2026-40690
+       RM_HANDLE                 GitHub handle of the release manager
+                                 (with leading `@`)
+       SECURITY_LIST             e.g. security@<project>.apache.org
+       USERS_LIST                e.g. users@<project>.apache.org
+       ANNOUNCE_LIST             e.g. [email protected]
+       SOURCE_TAB_URL            <cve_tool_record_url_template>#source
+       EMAIL_TAB_URL             <cve_tool_record_url_template>#email
+       JSON_ANCHOR_URL           Tracker body deep-link to the embedded
+                                 CVE JSON section
+       ARCHIVE_SCAN_URL          The PonyMail / archive search URL for
+                                 USERS_LIST (parameterised on CVE_ID)
+       FRAMEWORK_RECORD_MD_URL   Link to tools/vulnogram/record.md on
+                                 the framework's GitHub
+       FRAMEWORK_OAUTH_API_README_URL  Link to tools/vulnogram/oauth-
+                                 api/README.md on the framework's
+                                 GitHub
+       FRAMEWORK_PROJECT_PATH    Substitution for `<framework>` in the
+                                 `uv run --project ...` invocations
+       FRAMEWORK_SYNC_SKILL_URL  Link to .claude/skills/security-issue-sync/
+                                 SKILL.md on the framework's GitHub
+       FRAMEWORK_README_URL      Link to README.md on the framework's
+                                 GitHub
+       CANNED_RESPONSES_URL      Link to <project-config>/canned-
+                                 responses.md on the tracker's GitHub
+       PUSH_TIMESTAMP            ISO-8601 timestamp of the most-recent
+                                 successful `vulnogram-api-record-
+                                 update` call (re-rendered on each
+                                 sync that re-pushes)
+-->
+<!-- apache-steward: release-manager-handoff v1 -->
+
+## 🚀 Release-manager hand-off — `CVE_ID` (CVE JSON auto-pushed)
+
+RM_HANDLE, the release containing the fix has shipped — this tracker
+now belongs to you. **The CVE JSON has already been pushed to
+[`#source`](SOURCE_TAB_URL) by the security team via the OAuth API at
+`PUSH_TIMESTAMP`**, so the only remaining writes for you are the
+Vulnogram-UI state-transition clicks, the advisory send, and the
+close.
+
+The paste-ready CVE JSON is embedded in this tracker's
+[issue body](JSON_ANCHOR_URL) and is regenerated automatically on
+every body change by the [`security-issue-sync`](FRAMEWORK_SYNC_SKILL_URL)
+skill — *and re-pushed via the OAuth API on the same sync run*, so the
+Vulnogram record at [`#source`](SOURCE_TAB_URL) stays in lock-step with
+the tracker body. No paste step is required from you in the normal
+case.
+
+### Step-by-step
+
+1. **Verify the record content.** Open [`#source`](SOURCE_TAB_URL) and
+   confirm it matches the [tracker body's embedded JSON](JSON_ANCHOR_URL).
+   The two should be byte-identical; if they diverge, the OAuth push
+   failed silently and the sync's recap will have flagged it — fall
+   back to the manual-paste instructions further down.
+
+2. **Move `DRAFT` → `REVIEW`** via the Vulnogram UI button.
+
+3. **Wait for CNA review.** Reviewer comments arrive by email on
+   `SECURITY_LIST` with the CVE ID in the subject line.
+   [`security-issue-sync`](FRAMEWORK_SYNC_SKILL_URL) detects them and
+   proposes matching body-field updates on this tracker; the security
+   team confirms, the embedded JSON regenerates, and the API re-push
+   lands automatically on the same sync run. Each push lands a fresh
+   entry on this tracker's rollup comment so you can see what changed
+   without opening Vulnogram. **If no reviewer comments arrive (common
+   case), skip to step 5.**
+
+4. **Re-verify** the record at [`#source`](SOURCE_TAB_URL) when the
+   rollup shows a subsequent push entry (the body has changed since
+   `PUSH_TIMESTAMP`).
+
+5. **Set `READY`.** Vulnogram UI button.
+
+6. **Preview the advisory email** on the [email tab](EMAIL_TAB_URL).
+   If anything needs to change, edit the corresponding tracker body
+   field; the JSON regenerates + auto-pushes; then re-preview.
+
+7. **Send advisory emails** from Vulnogram. The form sends to
+   `USERS_LIST` and `ANNOUNCE_LIST`. Then on this tracker, add the
+   `announced - emails sent` label and remove `fix released`.
+
+8. **(automatic) Archive URL captured.**
+   [`security-issue-sync`](FRAMEWORK_SYNC_SKILL_URL) scans the
+   [users-list archive](ARCHIVE_SCAN_URL) for the CVE ID on every run.
+   Once it finds the advisory, it populates the *Public advisory URL*
+   body field, regenerates the JSON to include the archive URL as a
+   `vendor-advisory` reference, **re-pushes via the OAuth API**, adds
+   the `announced` label — **and posts a follow-up comment on this
+   tracker** giving you the explicit go-ahead for the final state
+   move below.
+
+9. **Move `REVIEW` → `PUBLIC`** via the Vulnogram UI button. *Only
+   after the follow-up comment in step 8 fires.* The data write is
+   already done; the state transition is intentionally human-only
+   because it triggers the CNA-feed dispatch to `cve.org`.
+
+10. **Close the tracker** — close as completed; do not update any
+    labels. [`security-issue-sync`](FRAMEWORK_SYNC_SKILL_URL) archives
+    the project-board item so the closed tracker leaves the active
+    board.
+
+---
+
+**If the OAuth push fails on a future sync** (session expired, schema
+rejection, transient HTTP error), the sync's recap will flag it and
+this comment will be edited to the manual-paste variant — re-open
+[`#source`](SOURCE_TAB_URL), paste the JSON from the
+[tracker body](JSON_ANCHOR_URL), click **Save**, then continue with the
+state transitions above. The most common cause is a stale session
+cookie — re-run `uv run --project 
FRAMEWORK_PROJECT_PATH/tools/vulnogram/oauth-api vulnogram-api-setup`
+(see [`oauth-api/README.md`](FRAMEWORK_OAUTH_API_README_URL)) and the
+next sync will resume auto-pushing.
+
+---
+
+**References:**
+
+- Vulnogram state machine + paste flow: 
[`tools/vulnogram/record.md`](FRAMEWORK_RECORD_MD_URL).
+- OAuth API setup + record-update one-liner: 
[`oauth-api/README.md`](FRAMEWORK_OAUTH_API_README_URL).
+- Reusable email wording (if you draft anything by hand): 
[`canned-responses.md`](CANNED_RESPONSES_URL).
+- Full lifecycle (Steps 12-15): 
[`README.md`](FRAMEWORK_README_URL#for-release-managers--steps-1215).
diff --git 
a/tools/vulnogram/release-manager-publication-comment-oauth-pushed.md 
b/tools/vulnogram/release-manager-publication-comment-oauth-pushed.md
new file mode 100644
index 0000000..5897a94
--- /dev/null
+++ b/tools/vulnogram/release-manager-publication-comment-oauth-pushed.md
@@ -0,0 +1,86 @@
+<!-- START doctoc generated TOC please keep comment here to allow auto update 
-->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents**  *generated with 
[DocToc](https://github.com/thlorenz/doctoc)*
+
+- [📰 Advisory archived — ready for `PUBLIC` (CVE JSON 
auto-pushed)](#-advisory-archived--ready-for-public-cve-json-auto-pushed)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+<!-- SPDX-License-Identifier: Apache-2.0
+     https://www.apache.org/licenses/LICENSE-2.0 -->
+
+<!--
+     OAuth-pushed variant of the publication-ready notification
+     comment posted by `security-issue-sync` when the *Public advisory
+     URL* body field has just been populated, the CVE JSON has been
+     regenerated to carry the archive URL as a `vendor-advisory`
+     reference, **and** the regenerated JSON has been pushed to
+     Vulnogram via the OAuth API on the same sync pass (Step 14 of
+     the lifecycle).
+
+     This template is the counterpart to `release-manager-publication-
+     comment.md` (the manual-paste variant). The sync skill picks
+     between the two based on the outcome of `vulnogram-api-check` +
+     `vulnogram-api-record-update` — see Step 5b of
+     `.claude/skills/security-issue-sync/SKILL.md` for the decision
+     flow.
+
+     Idempotency: the marker on line below is the **same** as the
+     manual-paste variant's; the skill keys idempotency on the marker
+     and detects the variant by re-reading the body. On a re-sync
+     where the previous comment was manual-paste but the current push
+     succeeded, the skill PATCH-edits the body in place.
+
+     Placeholders the skill substitutes:
+
+       CVE_ID                e.g. CVE-2026-40690
+       RM_HANDLE             GitHub handle of the release manager
+                             (with leading `@`)
+       ARCHIVE_URL           The captured users-list archive URL
+       SOURCE_TAB_URL        <cve_tool_record_url_template>#source
+       JSON_ANCHOR_URL       Tracker body deep-link to the embedded
+                             CVE JSON section
+       CVE_ORG_URL           https://www.cve.org/CVERecord?id=CVE_ID
+       PUSH_TIMESTAMP        ISO-8601 timestamp of the
+                             `vulnogram-api-record-update` call
+                             this sync pass made
+-->
+<!-- apache-steward: release-manager-publication-ready v1 -->
+
+## 📰 Advisory archived — ready for `PUBLIC` (CVE JSON auto-pushed)
+
+RM_HANDLE, the advisory you sent in step 7 of the hand-off above has
+been archived on the public users-list. This sync pass made the
+following deterministic updates on this tracker — *and pushed the
+regenerated JSON to Vulnogram via the OAuth API at `PUSH_TIMESTAMP`*,
+so the record at [`#source`](SOURCE_TAB_URL) now carries the archive
+URL as a `vendor-advisory` reference:
+
+- **Public advisory URL** body field populated: [ARCHIVE_URL](ARCHIVE_URL)
+- The embedded CVE JSON regenerated to include the archive URL.
+- The regenerated JSON pushed to the CVE record via
+  `vulnogram-api-record-update` (no manual paste needed).
+- The `announced` label added.
+
+You can now do the final state move:
+
+1. **Verify the record content** at [`#source`](SOURCE_TAB_URL)
+   matches the [tracker body](JSON_ANCHOR_URL) — they should be
+   byte-identical. If they diverge, the push failed silently; fall
+   back to the manual paste below.
+
+2. **Move `REVIEW` → `PUBLIC`** via the Vulnogram UI. The record
+   propagates to [`cve.org`](CVE_ORG_URL) once the state lands.
+
+3. **Close this tracker** — close as completed; do not update any
+   labels. The `security-issue-sync` skill archives the project-board
+   item afterwards.
+
+That terminates the lifecycle. Thanks for driving this one.
+
+---
+
+**Manual paste fallback** (only if step 1's record content does not
+match): open [`#source`](SOURCE_TAB_URL), paste the JSON from this
+tracker's [issue body](JSON_ANCHOR_URL), click **Save**, then move
+`REVIEW` → `PUBLIC`.

Reply via email to