## Bugs in this PR

## Bug #1 — SBOM + attestation silently skipped

v7.57.0 and v7.58.0 both shipped with zero release assets. No SBOM, no Sigstore attestation, no supply-chain provenance — even though every step existed in `release.yml`. Cause: once release-please started creating the GitHub Release (which it does automatically), the `if: steps.check.outputs.exists == 'false'` guard on every downstream step turned the whole pipeline into a no-op.

### The over-gated pattern

#### Beforev7.58.0

    # .github/workflows/release.yml
    - name: Check if release exists
      id: check
      run: if gh release view "$TAG" ...

    - name: Create GitHub Release
      if: steps.check.outputs.exists == 'false'
    - name: Setup Node.js for SBOM
      if: steps.check.outputs.exists == 'false'    <-- silently skipped
    - name: Generate SBOM (CycloneDX)
      if: steps.check.outputs.exists == 'false'    <-- silently skipped
    - name: Upload SBOM to release
      if: steps.check.outputs.exists == 'false'    <-- silently skipped
    - name: Attest build provenance
      if: steps.check.outputs.exists == 'false'    <-- silently skipped

#### Afterthis PR

    # .github/workflows/release.yml
    - name: Check if release exists
      id: check
      run: if gh release view "$TAG" ...

    - name: Create GitHub Release
      if: steps.check.outputs.exists == 'false'   <-- only this is gated
    - name: Setup Node.js for SBOM
    - name: Generate SBOM (CycloneDX)
    - name: Upload SBOM to release
      run: gh release upload "$TAG" --clobber    <-- idempotent re-run
    - name: Attest build provenance                <-- runs every time;
                                                       attestations append-only

Evidence for the bug
  `gh release view v7.57.0 --json assets` → `{"assets": []}`
  `gh release view v7.58.0 --json assets` → `{"assets": []}`
Why the SBOM step can safely re-run
  `gh release upload --clobber` overwrites existing assets with the same name. Sigstore attestations are append-only (each run adds a new attestation; old ones stay verifiable). So running SBOM + attestation on an already-existing release has no destructive effect.
Why not backfill v7.57.0 / v7.58.0
  Backfill would require deleting + re-tagging, which re-triggers release-please and creates churn. The fix is forward-only — v7.58.1 onward will have SBOMs and attestations.

## Bug #2 — changelog-to-props.mjs parser fails on release-please format

The `Release Video` workflow red-X'd on every release-please release. Root cause: `scripts/changelog-to-props.mjs` only matched the Keep-a-Changelog header written by `bin/bump-version.sh`. release-please writes a different header format, so the parser exited with `Error: Version X.Y.Z not found in CHANGELOG.md`.

### The parse regex

#### Beforeregex only matches KAC

    // scripts/changelog-to-props.mjs
    const match = line.match(
      /^## \[([^\]]+)\]\s*-\s*(\d{4}-\d{2}-\d{2})/
    );

    // bullets:     - Description
    line.match(/^- /)

    // section:     ### Added | Changed | Fixed
    /^### (Added|Changed|Fixed)/i

    // Fails on release-please:
    // ## [7.58.0](https://github.com/.../compare/...) (2026-04-20)
    // * Feature — text ([#N](url)) ([hash](url))
    // ### Features | ### Bug Fixes

#### Afterhandles both formats

    // scripts/changelog-to-props.mjs
    const VERSION_HEADER_RE =
      /^## \[([^\]]+)\](?:\([^)]*\))?\s*(?:-|\()\s*(\d{4}-\d{2}-\d{2})/;

    // bullets: both `- ` and `* `
    line.match(/^[-*] /)
    text.replace(/\s*\(\[[^\]]+\]\([^)]*\)\)/g, '');  // strip link artifacts

    // section: map conventional-commit → KAC categories
    const SECTION_CATEGORY = {
      features: 'added',
      'bug fixes': 'fixed',
      'performance improvements': 'changed',
      'miscellaneous chores': 'changed',
      ...
    };

Regression test (new in this PR)
  `tests/unit/test-changelog-parser.sh` — 8 assertions covering both header formats, both bullet prefixes, section mapping, link stripping, and missing-version exit behavior.
Evidence for the bug
  Release Video workflow run `24684971611` on v7.58.0 failed with `Error: Version 7.58.0 not found in CHANGELOG.md` in the Parse Changelog job. Render Videos + Upload to Release jobs were then skipped.