a4aa 0.4.2

Adapter-For-All-Agents — unified agent client adapter: process supervision, stdio/PTY transports, JSON-RPC and ACP lanes, CLI drivers, and a stdio sidecar for hosts in other languages.
Documentation
# Generated release train for Adapter-For-All-Agents.
# Regenerate from the release declaration instead of editing this file by
# hand; .github/release.lock records the source inputs that produced it.
#
# Two ways to release, one pipeline:
#
# - Weekly train: every Fri through 23:59 UTC+08:00 (Asia/Shanghai). The candidate
#   is the last commit merged into the default branch before the cutoff.
#   The bump is the highest `release:patch`, `release:minor` or
#   `release:major` label among its pull requests, patch when none is set.
#   The train runs as a rehearsal.
# - Run workflow: releases the head of the default branch (or the
#   requested commit) now, at the version typed in or bumped by the chosen
#   level.
#
# There is deliberately no tag trigger: `publish.yml` accepts release
# inputs only from verify runs on the default branch, and a pushed tag is
# only as trustworthy as whoever pushed it. The release job creates the
# tag itself after verification.
#
# A candidate identical to the last published release is skipped. A failed
# step stops the run until a person reruns it; a rerun finishes a partly
# uploaded version instead of skipping past it.
#
# Two files split at the trust boundary. This file is the verify side: its
# `verify` job runs on every trigger with a read-only token — it selects,
# waits on siblings, verifies, versions and packages the candidate, then
# hands the `.crate` set and the release decision over as artifacts.
# `publish.yml` (the release side) is the only file that holds the OIDC
# publishing job; it runs the default branch's own copy after this verify
# train completes, so a candidate ref can never supply the workflow that
# receives the publishing identity.
#
# Credentials: this file needs none; a read-only token suffices.
name: verify

on:
  schedule:
    - cron: "0 16 * * 5"
  workflow_dispatch:
    inputs:
      version:
        description: Exact version to publish, such as 0.3.0 or v0.3.0; empty bumps by level
        required: false
        default: ""
      level:
        description: Bump level when no version is given
        type: choice
        options: [patch, minor, major]
        default: patch
      candidate:
        description: Commit or branch to release; empty takes the head of the default branch
        required: false
        default: ""
      rehearse:
        description: Rehearse only; nothing is pushed, tagged or uploaded
        type: boolean
        default: false

permissions: {}

concurrency:
  group: release-train
  cancel-in-progress: false

jobs:
  verify:
    # The job name is rendered by GitHub from this workflow file, outside the
    # reach of anything a step executes: it is the signature the publish gate
    # binds the release decision to. `sig=` versions the wire format so a
    # stale signature refuses instead of misparsing; `sched=` signs the same
    # expression RELEASE_MODE runs under; `rehearse`/`version`/`level`/`cand`
    # sign the dispatch inputs. The scheduled candidate is computed after the
    # name renders, so it cannot be signed — the publish job re-derives it
    # from the same cutoff and requires equality. The parser lives in
    # publish.yml's `gate` job — keep both sides of this format in sync.
    name: verify-train | sig=1 | sched=${{ github.event_name == 'schedule' && 'dry-run' || inputs.rehearse && 'dry-run' || 'live' }} | rehearse=${{ inputs.rehearse }} | version=${{ inputs.version || '-' }} | level=${{ inputs.level }} | cand=${{ inputs.candidate || '-' }}
    runs-on: ubuntu-latest
    timeout-minutes: 180
    permissions:
      contents: read
      pull-requests: read
    outputs:
      release: ${{ steps.version.outputs.release }}
      next: ${{ steps.version.outputs.next }}
      needs_set: ${{ steps.version.outputs.needs_set }}
      excludes: ${{ steps.version.outputs.excludes }}
      sha: ${{ steps.candidate.outputs.sha }}
      branch: ${{ steps.candidate.outputs.branch }}
    env:
      # The train's declared mode governs scheduled runs only; a manual
      # dispatch releases live unless it opted into a rehearsal — the same
      # expression the signed job name reports in `sched=`.
      RELEASE_MODE: ${{ github.event_name == 'schedule' && 'dry-run' || inputs.rehearse && 'dry-run' || 'live' }}
      RELEASE_KIND: ${{ github.event_name == 'schedule' && 'train' || 'manual' }}
      INPUT_VERSION: ${{ inputs.version }}
      INPUT_LEVEL: ${{ inputs.level }}
      SIBLINGS: '[{"dir":"Agent-Context-Operators","slug":"Unka-Malloc/Agent-Context-Operators","crates":["acop","acop-antigravity","acop-claude","acop-cli","acop-codex","acop-copilot","acop-cursor","acop-dsh","acop-hermes","acop-kilo-code","acop-kimi-code","acop-openclaw","acop-opencode","acop-pi"]},{"dir":"Hook-Engine","slug":"Unka-Malloc/Agent-Hook-Engine","crates":["ahe"]},{"dir":"Metera","slug":"Unka-Malloc/Metera","crates":["agent-statistics","collection-store","balance-detector","token-usage-detector"]}]'
      DIR: 'Adapter-For-All-Agents'
      # Optional registry-index override (repository variable); empty
      # falls through to release.py's documented crates.io default.
      CARGO_REGISTRY_INDEX: ${{ vars.CARGO_REGISTRY_INDEX }}
    defaults:
      run:
        shell: bash
        working-directory: Adapter-For-All-Agents
    steps:
      # The checkout runs candidate code (verify scripts, build scripts);
      # persist-credentials must stay off so the repository token never
      # reaches a file it could read. gh calls get GH_TOKEN explicitly.
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          path: ${{ env.DIR }}
          fetch-depth: 0
          persist-credentials: false

      # Copied before the candidate checkout replaces the working tree, so the
      # helper always matches this workflow.
      - name: Stage the train helper
        run: cp .github/release.py "$RUNNER_TEMP/release.py"

      - name: Select the candidate
        id: candidate
        env:
          GH_TOKEN: ${{ github.token }}
          OVERRIDE: ${{ inputs.candidate }}
        run: |
          default_branch="$(gh api "repos/$GITHUB_REPOSITORY" --jq .default_branch)"
          python3 "$RUNNER_TEMP/release.py" candidate --weekday=fri --time=23:59 \
            --utc-offset=+08:00 --default-branch="$default_branch" --override="$OVERRIDE"

      - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
        with:
          components: rustfmt, clippy

      - name: Install the pinned toolchain
        run: |
          if [ -f rust-toolchain.toml ] || [ -f rust-toolchain ]; then
            rustup toolchain install
          fi

      - name: Wait for cross-repository dependencies
        id: deps
        run: python3 "$RUNNER_TEMP/release.py" deps --wait-minutes="${{ env.RELEASE_MODE == 'live' && '150' || '5' }}"

      - name: Check out Agent-Context-Operators at its resolved release
        if: steps.deps.outputs.ready == 'true'
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          repository: Unka-Malloc/Agent-Context-Operators
          ref: ${{ steps.deps.outputs['ref_Agent-Context-Operators'] }}
          path: Agent-Context-Operators
          persist-credentials: false

      - name: Check out Hook-Engine at its resolved release
        if: steps.deps.outputs.ready == 'true'
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          repository: Unka-Malloc/Agent-Hook-Engine
          ref: ${{ steps.deps.outputs['ref_Hook-Engine'] }}
          path: Hook-Engine
          persist-credentials: false

      - name: Check out Metera at its resolved release
        if: steps.deps.outputs.ready == 'true'
        uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with:
          repository: Unka-Malloc/Metera
          ref: ${{ steps.deps.outputs['ref_Metera'] }}
          path: Metera
          persist-credentials: false

      - name: Compute the release version
        id: version
        if: steps.deps.outputs.ready == 'true'
        env:
          GH_TOKEN: ${{ github.token }}
          CANDIDATE: ${{ steps.candidate.outputs.sha }}
          BRANCH: ${{ steps.candidate.outputs.branch }}
        run: python3 "$RUNNER_TEMP/release.py" version --candidate="$CANDIDATE" --branch="$BRANCH"

      - name: Verify
        if: steps.version.outputs.release == 'true'
        run: |
          scripts/verify.sh

      - name: Check surface-freshness
        if: steps.version.outputs.release == 'true'
        run: python3 $RUNNER_TEMP/release.py freshness --window-days=14
      # Every mode runs the release-shaped bump: the manifest rewriter is
      # hand-written, and a rehearsal that skipped it would let a defect
      # surface for the first time inside the OIDC job — after the approval
      # gate. That job still recreates the bump from the bound commit with
      # its own staged helper, so nothing a verify-runner produced crosses
      # the boundary either way.
      - name: Apply the release version
        if: >-
          steps.version.outputs.release == 'true' &&
          steps.version.outputs.needs_set == 'true'
        env:
          NEXT: ${{ steps.version.outputs.next }}
        run: |
          python3 "$RUNNER_TEMP/release.py" set-version "$NEXT"
          cargo update --workspace

      # Rehearsals build the .crate set with --no-verify — manifest
      # normalization and member-pin resolution run for real, only the
      # compile and the upload dry-run stay unexercised — then restore the
      # tree the bump rewrote. Live runs pay for `cargo publish --dry-run`.
      # No artifacts ship to the OIDC job either way: it repackages the
      # bound commit itself with `cargo package --no-verify`, collecting
      # manifest-declared files without compiling or running build scripts.
      - name: Package
        if: steps.version.outputs.release == 'true'
        env:
          EXCLUDES: ${{ steps.version.outputs.excludes }}
        run: |
          args=()
          for crate in $EXCLUDES; do args+=(--exclude "$crate"); done
          if [ "$RELEASE_MODE" = "live" ]; then
            cargo publish --workspace --dry-run --allow-dirty "${args[@]}"
          else
            cargo package --workspace --allow-dirty --no-verify "${args[@]}"
            # Restore what the rehearsal rewrote: tracked manifests and the
            # lockfile, plus a Cargo.lock created where none was tracked.
            git checkout -- .
            git clean -fq -- Cargo.lock
          fi

      # The release train (default branch) reads this data-only file for the
      # candidate pointer and a skip pre-filter — the only fields the gate
      # consumes, so they are the only fields written. It lands for every
      # trigger so the gate can short-circuit cleanly on unqualified
      # candidates: a deps-not-ready or no-release run still produces a
      # parseable record. Nothing here authorizes a publish: the gate
      # derives kind and mode from GitHub-minted run metadata (the event and
      # this job's signed name), and the release job recomputes the version
      # decision from the bound commit. Values still expand from immutable
      # run metadata inline — never from a variable candidate code could
      # persist through $GITHUB_ENV.
      - name: Record the release inputs
        if: always()
        run: |
          mkdir -p target/release-inputs
          cat > target/release-inputs/env <<EOF
          sha=${{ steps.candidate.outputs.sha }}
          release=${{ steps.version.outputs.release || 'false' }}
          EOF

      - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
        if: always()
        with:
          name: release-inputs
          path: ${{ env.DIR }}/target/release-inputs/
          retention-days: 7
          if-no-files-found: error