dig-download 0.24.0

Multi-source download orchestrator for the DIG Node peer network — locates content holders via dig-dht, fans byte ranges across multiple peers simultaneously over dig-nat (dig.fetchRange), verifies each range independently against the capsule's chain-anchored merkle root, rebalances around dropped/slow/bad sources, and reassembles into the node's store with pause + resume that never refetches a verified range.
Documentation
# On a push to a RELEASE LINE — the default branch, or any `release/X.Y` maintenance branch —
# regenerate CHANGELOG.md from Conventional Commits (git-cliff), commit it to that same branch, THEN
# tag that commit vX.Y.Z and push the tag. The changelog is therefore INCLUDED in the tag. The pushed
# tag triggers this repo's deploy/release workflow (wired `on: push: tags: ['v*']`).
#
# `release/X.Y` branches exist because a downstream consumer can be pinned to an older minor while a
# breaking cascade lands on `main` (dig-node pins `dig-download = "0.19"`; see
# .github/RELEASE_LINES.md). Before this workflow understood them, a backport had no path to a tag at
# all, which is how 0.19.1 reached crates.io untagged and untraceable to a commit (#41). Every branch
# this fires on pushes its OWN ref back — never a hard-coded `main`, which would push a 0.19 changelog
# onto the 0.20 line.
#
# The tag is pushed with RELEASE_TOKEN (a PAT) — a tag pushed by the default GITHUB_TOKEN does NOT
# trigger downstream workflows (GitHub anti-recursion), which would break deploy-on-tag. The same
# PAT pushes the changelog commit (its identity must be allowed past branch protection —
# enforce_admins is off; the PAT is an org-admin/owner). See CLAUDE.md §3.6.
#
# Idempotent + loop-safe: no-op if the version's tag already exists; skips its own changelog commit.
name: Release

on:
  push:
    branches:
      - main
      - 'release/**'

concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: false

permissions:
  contents: write

jobs:
  release:
    name: Changelog + tag
    if: ${{ !startsWith(github.event.head_commit.message, 'chore(release):') }}
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
          token: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}

      - name: Resolve version + skip if already tagged
        id: ver
        shell: bash
        run: |
          set -euo pipefail
          pkg() { [ -f package.json ] && jq -r '.version // ""' package.json 2>/dev/null || echo ""; }
          crg() {
            [ -f Cargo.toml ] || { echo ""; return; }
            python3 -c 'import tomllib; d=tomllib.load(open("Cargo.toml","rb")); v=d.get("package",{}).get("version") or d.get("workspace",{}).get("package",{}).get("version") or ""; print(v if isinstance(v,str) else "")' 2>/dev/null || echo ""
          }
          PV="$(pkg)"; CV="$(crg)"; VER="${PV:-$CV}"
          if [ -z "$VER" ]; then echo "skip=true" >>"$GITHUB_OUTPUT"; echo "No version file — nothing to release."; exit 0; fi
          TAG="v$VER"
          if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null \
             || git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then
            echo "skip=true" >>"$GITHUB_OUTPUT"; echo "Tag $TAG already exists — no-op."; exit 0
          fi
          echo "skip=false" >>"$GITHUB_OUTPUT"
          echo "tag=$TAG" >>"$GITHUB_OUTPUT"
          echo "Releasing $TAG"

      - name: Install git-cliff
        if: steps.ver.outputs.skip == 'false'
        uses: taiki-e/install-action@v2
        with:
          tool: git-cliff

      - name: Generate changelog
        if: steps.ver.outputs.skip == 'false'
        run: git-cliff --config cliff.toml --tag "${{ steps.ver.outputs.tag }}" --output CHANGELOG.md

      - name: Commit changelog, tag, push
        if: steps.ver.outputs.skip == 'false'
        shell: bash
        run: |
          set -euo pipefail
          git config user.name "dig-release-bot"
          git config user.email "release-bot@users.noreply.github.com"
          git add CHANGELOG.md
          # `chore(release):` prefix so this workflow skips its own push (loop guard above).
          git commit -m "chore(release): ${{ steps.ver.outputs.tag }}" || echo "changelog unchanged"
          git tag -a "${{ steps.ver.outputs.tag }}" -m "Release ${{ steps.ver.outputs.tag }}"
          # Push back to the branch that triggered us. A hard-coded `main` here would land a
          # maintenance line's changelog on the development line.
          git push origin "HEAD:${{ github.ref_name }}"
          git push origin "${{ steps.ver.outputs.tag }}"
          echo "Pushed changelog commit + ${{ steps.ver.outputs.tag }} — deploy-on-tag will fire."