# satsignal-anchor — Bitbucket Pipelines adapter for satsignal.provenance.v1
# ---------------------------------------------------------------------------
# A sibling of the GitLab CI adapter (gitlab-ci.satsignal.yml) for the
# satsignal.provenance.v1 ingest schema (spec:
# https://proof.satsignal.cloud/spec-provenance). It is a thin
# translator: it fills the canonical provenance manifest from Bitbucket
# Pipelines predefined variables and POSTs it to Satsignal, which
# commits its SHA-256 on-chain and returns a portable .mbnt bundle.
# Bash-only (sha256sum + curl + jq) — same shape as the GitLab adapter
# and the GitHub Action, no SDK, no Satsignal account at verify time.
#
# Bitbucket has no `include: remote:` (unlike GitLab), so this file is a
# copy-paste reference: paste the `definitions:` step below into your
# project's bitbucket-pipelines.yml and reference it with a YAML anchor:
#
#     definitions:
#       steps:
#         - step: &satsignal-anchor          # <-- paste from this file
#             ...
#
#     pipelines:
#       default:
#         - step:
#             <<: *satsignal-anchor
#
# Required repository variables (Repository settings → Repository
# variables; tick "Secured" on the key):
#     SATSIGNAL_API_KEY    sk_... key with the anchors:create scope
#     SATSIGNAL_FOLDER     folder slug to file the proof under (e.g. "releases")
#     SATSIGNAL_MATTER     legacy alias of SATSIGNAL_FOLDER (still accepted)
#
# Optional (repository variables, or `export`ed in an earlier step):
#     SATSIGNAL_SUBJECT_PATH   file whose sha256 becomes subject.digest
#                              (default: the BITBUCKET_COMMIT, type "commit")
#     SATSIGNAL_SUBJECT_TYPE   subject.type override (default inferred)
#     SATSIGNAL_ATTESTATION    sha256[:hex] of an SLSA/in-toto/cosign
#                              attestation produced earlier in the
#                              pipeline — anchored as a reference, not
#                              re-issued (Satsignal is not your supply-
#                              chain identity layer; it timestamps it)
#     SATSIGNAL_ATTESTATION_TYPE  slsa|in-toto|github|npm|pypi|cosign|sigstore|custom
#     SATSIGNAL_API_BASE       default https://app.satsignal.cloud
#
# Outputs (Bitbucket step artifacts — note Bitbucket only keeps
# downloadable artifacts ~14 days and has no 1-year option like GitLab;
# this does not matter: the .mbnt is offline-verifiable per
# /spec-provenance §5 and chain-bound, so the proof outlives the
# artifact retention — store it wherever you keep release evidence):
#     satsignal-proof.json     the API response (txid, manifest_hash,
#                              also written as satsignal-receipt.json (compat copy)
#                              embeddable chain-anchor-v1 envelope)
#     satsignal-manifest.json  the canonical manifest that was anchored
#     *.mbnt                   the proof bundle — verify offline per
#                              /spec-provenance §5, no Satsignal call
# ---------------------------------------------------------------------------

definitions:
  steps:
    - step: &satsignal-anchor
        name: satsignal-anchor
        image: alpine:3.20
        script:
          - apk add --no-cache curl jq coreutils >/dev/null
          - |
            set -eu
            : "${SATSIGNAL_API_KEY:?set SATSIGNAL_API_KEY (secured repository variable)}"
            : "${SATSIGNAL_FOLDER:=${SATSIGNAL_MATTER:?set SATSIGNAL_FOLDER (folder slug; SATSIGNAL_MATTER also accepted)}}"
            SATSIGNAL_API_BASE="${SATSIGNAL_API_BASE:-https://app.satsignal.cloud}"
            SATSIGNAL_SUBJECT_PATH="${SATSIGNAL_SUBJECT_PATH:-}"
            SATSIGNAL_SUBJECT_TYPE="${SATSIGNAL_SUBJECT_TYPE:-}"
            SATSIGNAL_ATTESTATION="${SATSIGNAL_ATTESTATION:-}"
            SATSIGNAL_ATTESTATION_TYPE="${SATSIGNAL_ATTESTATION_TYPE:-custom}"

            # subject: hash an artifact if given, else anchor the commit SHA.
            if [ -n "$SATSIGNAL_SUBJECT_PATH" ]; then
              [ -f "$SATSIGNAL_SUBJECT_PATH" ] || { echo "SATSIGNAL_SUBJECT_PATH not found: $SATSIGNAL_SUBJECT_PATH" >&2; exit 1; }
              DIGEST="sha256:$(sha256sum "$SATSIGNAL_SUBJECT_PATH" | cut -d' ' -f1)"
              STYPE="${SATSIGNAL_SUBJECT_TYPE:-artifact}"
            else
              # No artifact: anchor a sha256 OF the 40-hex commit sha (the
              # manifest digest must be sha256; the git sha rides in identity).
              DIGEST="sha256:$(printf '%s' "$BITBUCKET_COMMIT" | sha256sum | cut -d' ' -f1)"
              STYPE="${SATSIGNAL_SUBJECT_TYPE:-commit}"
            fi

            # Bitbucket sets BITBUCKET_BRANCH on branch builds and
            # BITBUCKET_TAG on tag builds (never both) — fold to one ref.
            REF="${BITBUCKET_BRANCH:-${BITBUCKET_TAG:-}}"
            # Bitbucket exposes no pipeline-URL var; build the canonical one.
            PIPEURL=""
            if [ -n "${BITBUCKET_BUILD_NUMBER:-}" ]; then
              PIPEURL="https://bitbucket.org/${BITBUCKET_REPO_FULL_NAME}/pipelines/results/${BITBUCKET_BUILD_NUMBER}"
            fi

            # Build the canonical manifest with jq (no shell-quoting hazards).
            # Empty identity values are dropped so the manifest stays minimal
            # and re-canonicalizes identically across reruns. Structurally
            # identical to gitlab-ci.satsignal.yml — only the var sources
            # differ. Bitbucket has no actor *login* var; the triggerer is
            # a UUID, so identity.actor carries that (honestly a UUID).
            jq -n \
              --arg repo    "$BITBUCKET_REPO_FULL_NAME" \
              --arg stype   "$STYPE" \
              --arg digest  "$DIGEST" \
              --arg actor   "${BITBUCKET_STEP_TRIGGERER_UUID:-}" \
              --arg commit  "${BITBUCKET_COMMIT:-}" \
              --arg ref     "$REF" \
              --arg build   "${BITBUCKET_BUILD_NUMBER:-}" \
              --arg job     "${BITBUCKET_STEP_UUID:-}" \
              --arg pipeuid "${BITBUCKET_PIPELINE_UUID:-}" \
              --arg pipeurl "$PIPEURL" \
              --arg att     "$SATSIGNAL_ATTESTATION" \
              --arg atttype "$SATSIGNAL_ATTESTATION_TYPE" \
              '
              def norm($d): if ($d|startswith("sha256:")) then $d else "sha256:"+$d end;
              {
                schema: "satsignal.provenance.v1",
                source:  { type: "bitbucket", id: $repo },
                subject: { type: $stype, digest: norm($digest) },
                identity: ( {
                    provider: "bitbucket", actor: $actor, repo: $repo,
                    commit: $commit, ref: $ref,
                    workflow_run: (if $build=="" then "" else "build/"+$build end),
                    job: $job, pipeline_uuid: $pipeuid
                  } | with_entries(select(.value != "")) ),
                claims: ( { pipeline_url: $pipeurl } | with_entries(select(.value != "")) ),
                privacy: { onchain_mode: "hash_only" }
              }
              + ( if $att=="" then {}
                  else { attestations: [ { type: $atttype, digest: norm($att) } ] }
                  end )
              ' > satsignal-manifest.json

            jq -n --slurpfile m satsignal-manifest.json \
              --arg matter "$SATSIGNAL_FOLDER" \
              --arg label  "bitbucket ${BITBUCKET_REPO_FULL_NAME} build ${BITBUCKET_BUILD_NUMBER:-?}" \
              '{ folder_slug: $matter, label: $label, manifest: $m[0] }' > satsignal-body.json

            echo "POST ${SATSIGNAL_API_BASE}/api/v1/provenance/anchor (folder=${SATSIGNAL_FOLDER})"
            code=$(curl -sS -o satsignal-proof.json -w '%{http_code}' \
              -X POST \
              -H "Authorization: Bearer ${SATSIGNAL_API_KEY}" \
              -H 'Content-Type: application/json' \
              --data @satsignal-body.json \
              "${SATSIGNAL_API_BASE}/api/v1/provenance/anchor")
            if [ "$code" != "200" ]; then
              echo "anchor failed (HTTP $code):" >&2; cat satsignal-proof.json >&2; exit 1
            fi

            TXID=$(jq -r '.txid' satsignal-proof.json)
            MH=$(jq -r '.manifest_hash' satsignal-proof.json)
            BURL=$(jq -r '.bundle_url // empty' satsignal-proof.json); cp -f satsignal-proof.json satsignal-receipt.json  # legacy filename kept for downstream globs
            echo "anchored: txid=$TXID manifest_hash=$MH"

            # Pull the proof bundle so the receipt is self-contained in the
            # step artifacts (verify offline per /spec-provenance §5).
            if [ -n "$BURL" ]; then
              curl -sS -H "Authorization: Bearer ${SATSIGNAL_API_KEY}" \
                -o "satsignal-${BITBUCKET_BUILD_NUMBER:-proof}.mbnt" "$BURL" \
                && echo "bundle saved: satsignal-${BITBUCKET_BUILD_NUMBER:-proof}.mbnt"
            fi
        artifacts:
          - satsignal-proof.json
          - satsignal-receipt.json
          - satsignal-manifest.json
          - "satsignal-*.mbnt"
