name: Source manifest

# Publishes a hash manifest for every tagged release so a copy of the source
# obtained from somewhere other than this repository can be checked against it.
#
# This exists because the repository has been the target of takedown demands.
# When that succeeds, mirrors appear, and without a manifest there is no way to
# tell a faithful mirror from a modified one. The manifest is attested to this
# workflow run, so its own provenance is verifiable with `gh attestation verify`.
#
# Scope, stated plainly: this verifies SOURCE. It does not verify any compiled
# app. See docs/VERIFYING-A-BUILD.md.

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:
    inputs:
      ref:
        description: 'Tag or commit to produce a manifest for'
        required: true

permissions:
  contents: read

jobs:
  manifest:
    runs-on: ubuntu-latest
    permissions:
      contents: write      # attach the manifest to the release
      id-token: write      # provenance attestation
      attestations: write
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.inputs.ref || github.ref }}
          # Full history so the commit the tag names can be recorded exactly.
          fetch-depth: 0

      - name: Build manifest
        id: build
        run: |
          set -euo pipefail
          ref_name="${{ github.event.inputs.ref || github.ref_name }}"
          commit="$(git rev-parse HEAD)"
          tree="$(git rev-parse HEAD^{tree})"

          # Hash every tracked file, in a stable order, with NUL separation so
          # paths containing spaces or newlines cannot shift the columns.
          git ls-files -z \
            | sort -z \
            | xargs -0 sha256sum \
            > files.sha256

          {
            echo "# bitchat source manifest"
            echo "#"
            echo "# ref:    ${ref_name}"
            echo "# commit: ${commit}"
            echo "# tree:   ${tree}"
            echo "# files:  $(wc -l < files.sha256 | tr -d ' ')"
            echo "#"
            echo "# Verify a checkout of this ref with:"
            echo "#   shasum -a 256 -c files.sha256"
            echo "# Hash checking alone ignores files this manifest does not list, and"
            echo "# the Xcode project compiles any source file present in the tree. So"
            echo "# also confirm nothing extra is present:"
            echo "#   git status --porcelain --ignored   # git checkout: must print nothing"
            echo "# or, for a tarball, diff this manifest's path list against find(1)."
            echo "# Full instructions: docs/VERIFYING-A-BUILD.md"
            echo "# The git tree hash above is the single value covering all tracked content:"
            echo "#   git rev-parse HEAD^{tree}"
            echo "#"
          } > SOURCE-MANIFEST.txt
          cat files.sha256 >> SOURCE-MANIFEST.txt

          echo "commit=${commit}" >> "$GITHUB_OUTPUT"
          echo "tree=${tree}" >> "$GITHUB_OUTPUT"

      - name: Self-check the manifest
        run: |
          set -euo pipefail
          # A manifest that does not validate against the tree it was made from
          # is worse than none, so fail loudly rather than publishing it.
          grep -v '^#' SOURCE-MANIFEST.txt > check.sha256
          sha256sum -c check.sha256 > /dev/null
          echo "manifest validates against this checkout"

      - name: Attest the manifest
        uses: actions/attest-build-provenance@v1
        with:
          subject-path: SOURCE-MANIFEST.txt

      - uses: actions/upload-artifact@v4
        with:
          name: source-manifest
          path: SOURCE-MANIFEST.txt

      - name: Attach to release
        if: startsWith(github.ref, 'refs/tags/')
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          set -euo pipefail
          # A tag may be pushed before its release exists; only attach when
          # there is a release to attach to, and never fail the run over it.
          if gh release view "${{ github.ref_name }}" >/dev/null 2>&1; then
            gh release upload "${{ github.ref_name }}" SOURCE-MANIFEST.txt --clobber
          else
            echo "no release for ${{ github.ref_name }} yet; manifest is available as a workflow artifact"
          fi
