# satsignal-anchor — GitLab CI adapter for satsignal.provenance.v1
# ---------------------------------------------------------------------------
# The first external adapter 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
# GitLab CI 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 GitHub Action,
# no SDK, no Satsignal account needed at verify time.
#
# USAGE — add to your project's .gitlab-ci.yml:
#
#     include:
#       - remote: 'https://satsignal.cloud/gitlab-ci.satsignal.yml'
#
#     anchor:
#       extends: .satsignal_anchor
#       variables:
#         SATSIGNAL_SUBJECT_PATH: dist/app-1.4.2.tgz   # artifact to anchor
#         SATSIGNAL_SUBJECT_TYPE: artifact             # or: release|package|image|file
#
# Required CI/CD variables (Settings → CI/CD → Variables; mask 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:
#     SATSIGNAL_SUBJECT_PATH   file whose sha256 becomes subject.digest
#                              (default: the CI_COMMIT_SHA, 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 (job artifacts, kept 1 year):
#     satsignal-proof.json     the API response (txid, manifest_hash,
#                              also written as satsignal-receipt.json (compat copy)
#                              embeddable chain-anchor-v1 envelope)
#     *.mbnt                   the proof bundle — offline-verifiable per
#                              /spec-provenance §5, no Satsignal call
# ---------------------------------------------------------------------------

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

      # 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' "$CI_COMMIT_SHA" | sha256sum | cut -d' ' -f1)"
        STYPE="${SATSIGNAL_SUBJECT_TYPE:-commit}"
      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.
      jq -n \
        --arg repo    "$CI_PROJECT_PATH" \
        --arg stype   "$STYPE" \
        --arg digest  "$DIGEST" \
        --arg actor   "${GITLAB_USER_LOGIN:-}" \
        --arg commit  "${CI_COMMIT_SHA:-}" \
        --arg ref     "${CI_COMMIT_REF_NAME:-}" \
        --arg pipe    "${CI_PIPELINE_ID:-}" \
        --arg job     "${CI_JOB_ID:-}" \
        --arg runner  "${CI_RUNNER_DESCRIPTION:-}" \
        --arg pipeurl "${CI_PIPELINE_URL:-}" \
        --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: "gitlab", id: $repo },
          subject: { type: $stype, digest: norm($digest) },
          identity: ( {
              provider: "gitlab", actor: $actor, repo: $repo,
              commit: $commit, ref: $ref,
              workflow_run: (if $pipe=="" then "" else "pipeline/"+$pipe end),
              job: $job, runner: $runner
            } | 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  "gitlab ${CI_PROJECT_PATH} pipeline ${CI_PIPELINE_ID:-?}" \
        '{ 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
      # job artifacts (verify offline per /spec-provenance §5).
      if [ -n "$BURL" ]; then
        curl -sS -H "Authorization: Bearer ${SATSIGNAL_API_KEY}" \
          -o "satsignal-${CI_JOB_ID:-proof}.mbnt" "$BURL" \
          && echo "bundle saved: satsignal-${CI_JOB_ID:-proof}.mbnt"
      fi
  artifacts:
    when: always
    expire_in: 1 year
    paths:
      - satsignal-proof.json
      - satsignal-receipt.json
      - satsignal-manifest.json
      - "satsignal-*.mbnt"
