macp-runtime 0.7.4

MACP reference runtime: a coordination kernel and gRPC server enforcing session boundaries, message validation, append-only history, modes, and governance policy.
Documentation
name: Publish Crates

# Sole crates.io publisher for the workspace.
#
# Division of labour with release-plz.yml:
#   - release-plz owns versioning + git: it opens the version-bump PR and, on
#     merge, creates the git tags and the GitHub Release. Its config sets
#     `publish = false`, so it does NOT upload to crates.io.
#   - THIS workflow uploads to crates.io. `cargo publish --workspace` computes
#     the 7-crate dependency order itself and waits for index propagation
#     between crates — which release-plz's own publisher does not, and which
#     previously caused a partial publish (macp-modes tried to build against an
#     unpublished macp-policy).
#
# Invoked three ways:
#   - `workflow_call` from release-plz.yml, immediately after it creates the
#     tags. THIS is the live path.
#   - the `macp-runtime-v*` tag push. Kept as a backstop, but note it does NOT
#     fire for tags release-plz creates: GitHub does not start workflow runs
#     from events made with the default GITHUB_TOKEN (the recursion guard), so
#     this trigger sat inert and 0.6.1 was tagged 2026-07-12 and never reached
#     crates.io. It still fires for a tag pushed by a human or a PAT.
#   - `workflow_dispatch`, defaulting to a dry run, for recovery.
on:
  workflow_call:
    inputs:
      dry_run:
        description: "Package and verify without uploading"
        type: boolean
        default: false
    secrets:
      CARGO_REGISTRY_TOKEN:
        required: true
  push:
    tags: ["macp-runtime-v*"]
  workflow_dispatch:
    inputs:
      dry_run:
        description: "Package and verify without uploading"
        type: boolean
        default: true

env:
  CARGO_TERM_COLOR: always
  # Keep in sync with rust-toolchain.toml.
  RUST_TOOLCHAIN: "1.96.1"

jobs:
  publish:
    name: Publish to crates.io
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v7

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # v1
        with:
          toolchain: ${{ env.RUST_TOOLCHAIN }}

      - name: Install protoc
        uses: arduino/setup-protoc@f4d5893b897028ff5739576ea0409746887fa536 # v3.0.0
        with:
          version: "31.x"
          repo-token: ${{ github.token }}

      # `cargo publish --workspace` (stable since 1.90) computes the dependency
      # order itself, builds each crate's verify step against its sibling path
      # deps, and waits for each upload to appear on the index before publishing
      # its dependents.
      #
      # Re-run safety: any crate whose current version is already live is passed
      # via --exclude, so re-running after a mid-release failure skips the
      # already-published members instead of erroring on "already exists".
      - name: Publish workspace to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
          DRY_RUN: ${{ inputs.dry_run || false }}
        run: |
          set -euo pipefail

          is_published() {
            local name="$1" ver="$2" code
            code=$(curl -s -o /dev/null -w '%{http_code}' \
              -A "macp-runtime-release (https://github.com/multiagentcoordinationprotocol/macp-runtime)" \
              "https://crates.io/api/v1/crates/$name/$ver")
            [ "$code" = "200" ]
          }

          total=0
          exclude_args=()
          while read -r name ver; do
            total=$((total + 1))
            if is_published "$name" "$ver"; then
              echo "==> skip $name@$ver (already on crates.io)"
              exclude_args+=(--exclude "$name")
            fi
          done < <(cargo metadata --format-version 1 --no-deps \
            | jq -r '.packages[] | "\(.name) \(.version)"')

          if [ "${#exclude_args[@]}" -eq $((total * 2)) ]; then
            echo "All workspace crates already published; nothing to do."
            exit 0
          fi

          # ${arr[@]+...} guards against an empty array tripping `set -u`.
          if [ "${DRY_RUN:-false}" = "true" ]; then
            echo "==> dry-run publish workspace"
            cargo publish --workspace --dry-run --locked ${exclude_args[@]+"${exclude_args[@]}"}
          else
            echo "==> publish workspace"
            cargo publish --workspace --locked ${exclude_args[@]+"${exclude_args[@]}"}
          fi