Skip to content

chore(deps): bump astro from 6.2.1 to 6.4.7 in /docs #81

chore(deps): bump astro from 6.2.1 to 6.4.7 in /docs

chore(deps): bump astro from 6.2.1 to 6.4.7 in /docs #81

Workflow file for this run

name: Migration Parity and Benchmarks
on:
pull_request:
branches: [main]
workflow_dispatch:
inputs:
enforce_completion:
description: "Fail unless migration completion gates are fully satisfied"
required: false
default: false
type: boolean
permissions:
contents: read
issues: write
pull-requests: write
env:
PYTHON_VERSION: "3.12"
jobs:
detect-changes:
name: Detect Migration Changes
runs-on: ubuntu-24.04
outputs:
should-run: ${{ steps.filter.outputs.should-run }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check changed paths
id: filter
env:
HEAD_REF: ${{ github.event.pull_request.head.ref }}
shell: bash
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "should-run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "${{ github.event_name }}" = "pull_request" ] && [[ "$HEAD_REF" == crane/* ]]; then
echo "should-run=true" >> "$GITHUB_OUTPUT"
exit 0
fi
git diff --name-only \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}" \
| tee "$RUNNER_TEMP/changed-files.txt"
if grep -Eq '^(\.crane/|\.github/workflows/migration-ci\.yml$|cmd/|internal/|pkg/|go\.mod$|go\.sum$|pyproject\.toml$|scripts/ci/|src/|tests/benchmarks/|tests/parity/|tests/unit/test_crane_score\.py$)' "$RUNNER_TEMP/changed-files.txt"; then
echo "should-run=true" >> "$GITHUB_OUTPUT"
else
echo "should-run=false" >> "$GITHUB_OUTPUT"
fi
parity:
name: Python-vs-Go Parity Gate
needs: [detect-changes]
if: needs.detect-changes.outputs.should-run == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Install Python reference CLI
run: |
uv sync --extra dev
test -x "$GITHUB_WORKSPACE/.venv/bin/apm"
echo "APM_PYTHON_BIN=$GITHUB_WORKSPACE/.venv/bin/apm" >> "$GITHUB_ENV"
- name: Extract Python behavior contracts
run: |
uv run python scripts/ci/python_behavior_contracts.py extract \
--output "$RUNNER_TEMP/python-behavior-contracts.json"
echo "APM_PYTHON_CONTRACT_INVENTORY=$RUNNER_TEMP/python-behavior-contracts.json" >> "$GITHUB_ENV"
- name: Run CLI-agnostic Python behavior tests
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
ENFORCE_COMPLETION_INPUT: ${{ inputs.enforce_completion }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
go build -o "$RUNNER_TEMP/apm-go" ./cmd/apm
enforce_behavior_contracts=false
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "${ENFORCE_COMPLETION_INPUT:-false}" = "true" ]; then
enforce_behavior_contracts=true
elif [ "$EVENT_NAME" = "pull_request" ] && [[ "${HEAD_REF:-}" == crane/* ]]; then
enforce_behavior_contracts=true
fi
if [ "$enforce_behavior_contracts" = "true" ]; then
export APM_ENFORCE_PYTHON_BEHAVIOR_CONTRACTS=1
fi
set +e
APM_GO_BIN="$RUNNER_TEMP/apm-go" \
uv run pytest tests/parity/test_python_behavior_contracts.py -q --tb=short \
| tee "$RUNNER_TEMP/python-cli-contract-tests.txt"
status=${PIPESTATUS[0]}
set -e
echo "PYTHON_CLI_CONTRACT_STATUS=$status" >> "$GITHUB_ENV"
- name: Run Go parity tests
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
ENFORCE_COMPLETION_INPUT: ${{ inputs.enforce_completion }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
enforce_completion=false
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "${ENFORCE_COMPLETION_INPUT:-false}" = "true" ]; then
enforce_completion=true
elif [ "$EVENT_NAME" = "pull_request" ] && [[ "${HEAD_REF:-}" == crane/* ]]; then
enforce_completion=true
fi
echo "MIGRATION_COMPLETION_ENFORCED=$enforce_completion" >> "$GITHUB_ENV"
if [ "$enforce_completion" = "true" ]; then
export APM_ENFORCE_COMPLETION_GATES=1
fi
set +e
go test -json -skip '^TestGoCutover' ./... | tee "$RUNNER_TEMP/go-test-events.json"
status=${PIPESTATUS[0]}
set -e
echo "GO_TEST_STATUS=$status" >> "$GITHUB_ENV"
- name: Run Go-only cutover gate
shell: bash
run: |
set +e
APM_PYTHON_BIN="" \
APM_PYTHON_CONTRACT_INVENTORY="" \
PYTHONPATH="" \
VIRTUAL_ENV="" \
go test -json ./cmd/apm -run '^TestGoCutover' \
| tee "$RUNNER_TEMP/go-cutover-events.json"
status=${PIPESTATUS[0]}
set -e
cat "$RUNNER_TEMP/go-cutover-events.json" >> "$RUNNER_TEMP/go-test-events.json"
echo "GO_CUTOVER_STATUS=$status" >> "$GITHUB_ENV"
- name: Check upstream APM contract coverage
shell: bash
run: |
git remote add upstream https://github.com/microsoft/apm.git 2>/dev/null || \
git remote set-url upstream https://github.com/microsoft/apm.git
git fetch upstream main --prune
upstream_args=(
--upstream-ref upstream/main
--head-ref HEAD
--coverage tests/parity/upstream_contract_coverage.yml
--summary "$RUNNER_TEMP/upstream-apm-contracts.md"
)
if [ "${MIGRATION_COMPLETION_ENFORCED:-false}" = "true" ]; then
upstream_args+=(--enforce)
fi
set +e
uv run python scripts/ci/upstream_apm_contracts.py check \
"${upstream_args[@]}" \
| tee "$RUNNER_TEMP/upstream-apm-contracts.txt"
status=${PIPESTATUS[0]}
set -e
cat "$RUNNER_TEMP/upstream-apm-contracts.txt" >> "$RUNNER_TEMP/go-test-events.json"
echo "UPSTREAM_APM_STATUS=$status" >> "$GITHUB_ENV"
- name: Compute migration score
run: |
go run .crane/scripts/score.go < "$RUNNER_TEMP/go-test-events.json" | tee "$RUNNER_TEMP/migration-score.json"
coverage_args=(
--inventory "$RUNNER_TEMP/python-behavior-contracts.json"
--coverage tests/parity/python_contract_coverage.yml
--summary "$RUNNER_TEMP/python-contract-coverage.md"
)
if [ "${MIGRATION_COMPLETION_ENFORCED:-false}" != "true" ]; then
coverage_args+=(--allow-intentionally-incomplete --allow-obsolete-python-tests)
fi
set +e
uv run python scripts/ci/python_behavior_contracts.py check \
"${coverage_args[@]}"
coverage_status=$?
set -e
if [ "${MIGRATION_COMPLETION_ENFORCED:-false}" = "true" ] && [ "$coverage_status" != "0" ]; then
exit "$coverage_status"
fi
python - "$RUNNER_TEMP/migration-score.json" "${MIGRATION_COMPLETION_ENFORCED:-false}" <<'PY'
import json
import sys
with open(sys.argv[1], encoding="utf-8") as fh:
score = json.load(fh)
enforce_completion = sys.argv[2].lower() == "true"
print(json.dumps(score, indent=2, sort_keys=True))
if not enforce_completion:
print(
"::notice::Non-enforcing migration evidence run; "
"completion gates are enforced only for crane/* PRs and "
"manual runs with enforce_completion=true."
)
raise SystemExit(0)
if score.get("progress") != 1.0:
raise SystemExit("progress must be 1.0 for completion parity")
if score.get("migration_score") == 1.0 and not score.get("deletion_grade_ready"):
raise SystemExit("migration_score 1.0 requires deletion_grade_ready")
PY
if [ "${MIGRATION_COMPLETION_ENFORCED:-false}" = "true" ]; then
test "${PYTHON_CLI_CONTRACT_STATUS:-1}" = "0"
test "${GO_TEST_STATUS:-1}" = "0"
test "${GO_CUTOVER_STATUS:-1}" = "0"
test "${UPSTREAM_APM_STATUS:-1}" = "0"
else
if [ "${PYTHON_CLI_CONTRACT_STATUS:-1}" != "0" ]; then
echo "::notice::Python behavior contract tests are incomplete in collection mode."
fi
if [ "${GO_TEST_STATUS:-1}" != "0" ]; then
echo "::notice::Go parity tests are incomplete in collection mode."
fi
if [ "${GO_CUTOVER_STATUS:-1}" != "0" ]; then
echo "::notice::Go-only cutover gate is incomplete in collection mode."
fi
if [ "${UPSTREAM_APM_STATUS:-1}" != "0" ]; then
echo "::notice::Upstream APM freshness/contract coverage is incomplete in collection mode."
fi
fi
- name: Upload parity evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: migration-parity-evidence
path: |
${{ runner.temp }}/go-test-events.json
${{ runner.temp }}/go-cutover-events.json
${{ runner.temp }}/migration-score.json
${{ runner.temp }}/python-behavior-contracts.json
${{ runner.temp }}/python-contract-coverage.md
${{ runner.temp }}/python-cli-contract-tests.txt
${{ runner.temp }}/upstream-apm-contracts.txt
${{ runner.temp }}/upstream-apm-contracts.md
if-no-files-found: ignore
retention-days: 14
benchmarks:
name: Migration Benchmarks
needs: [detect-changes, parity]
if: always() && needs.detect-changes.outputs.should-run == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Install dependencies
run: uv sync --extra dev
- name: Build Go CLI
run: go build -o "$RUNNER_TEMP/apm-go" ./cmd/apm
- name: Run Python-vs-Go CLI benchmark
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
ENFORCE_COMPLETION_INPUT: ${{ inputs.enforce_completion }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
enforce_completion=false
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ "${ENFORCE_COMPLETION_INPUT:-false}" = "true" ]; then
enforce_completion=true
elif [ "$EVENT_NAME" = "pull_request" ] && [[ "${HEAD_REF:-}" == crane/* ]]; then
enforce_completion=true
fi
extra_args=()
if [ "$enforce_completion" != "true" ]; then
extra_args+=(--allow-failures)
fi
python scripts/ci/migration_cli_benchmark.py \
--python-bin "$GITHUB_WORKSPACE/.venv/bin/apm" \
--go-bin "$RUNNER_TEMP/apm-go" \
--json-out "$RUNNER_TEMP/migration-cli-benchmark.json" \
--markdown-out "$RUNNER_TEMP/migration-cli-benchmark.md" \
--max-ratio 5.0 \
"${extra_args[@]}"
- name: Run Python scaling guards
run: uv run pytest tests/benchmarks/test_scaling_guards.py -v
- name: Add benchmark summary
if: always()
run: |
if [ -f "$RUNNER_TEMP/migration-cli-benchmark.md" ]; then
cat "$RUNNER_TEMP/migration-cli-benchmark.md" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Download parity evidence
if: always()
continue-on-error: true
uses: actions/download-artifact@v4
with:
name: migration-parity-evidence
path: ${{ runner.temp }}/migration-parity-evidence
- name: Post benchmark PR comment
if: always() && github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PARITY_RESULT: ${{ needs.parity.result }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
if [ ! -f "$RUNNER_TEMP/migration-cli-benchmark.md" ]; then
echo "No migration benchmark markdown found; skipping PR comment."
exit 0
fi
SCORE_PATH="$RUNNER_TEMP/migration-parity-evidence/migration-score.json"
export SCORE_PATH
python - <<'PY' > "$RUNNER_TEMP/migration-benchmark-context.md"
from __future__ import annotations
import json
import os
import subprocess
from pathlib import Path
def gh_json(path: str) -> object | None:
try:
completed = subprocess.run(
["gh", "api", path],
check=True,
capture_output=True,
encoding="utf-8",
)
except subprocess.CalledProcessError:
return None
return json.loads(completed.stdout)
def subject(message: str) -> str:
return message.splitlines()[0] if message else "(no commit subject)"
def body_lines(message: str, limit: int = 5) -> list[str]:
items: list[str] = []
current: list[str] = []
for raw_line in message.splitlines()[1:]:
line = raw_line.strip()
if not line or line.startswith("Co-authored-by:"):
continue
if line.startswith(("Fixes ", "Run:")):
continue
is_bullet = line.startswith(("-", "*"))
cleaned = line.lstrip("-* ").strip()
if is_bullet:
if current:
items.append(" ".join(current))
current = [cleaned]
elif current:
current.append(cleaned)
else:
current = [cleaned]
if current:
items.append(" ".join(current))
return items[:limit]
def short(sha: str | None) -> str:
return (sha or "")[:7]
def is_trigger_only(message: str) -> bool:
return subject(message).strip().lower() in {"ci: trigger checks"}
def bool_word(value: object) -> str:
return "yes" if value is True else "no" if value is False else "unknown"
def format_float(value: object) -> str:
if isinstance(value, int | float):
return f"{value:.3f}".rstrip("0").rstrip(".")
return "unknown"
repo = os.environ["GITHUB_REPOSITORY"]
pr_number = os.environ["PR_NUMBER"]
head_sha = os.environ["HEAD_SHA"]
parity_result = os.environ.get("PARITY_RESULT", "unknown")
score_path = Path(os.environ["SCORE_PATH"])
commits = gh_json(f"repos/{repo}/pulls/{pr_number}/commits?per_page=100")
commits = commits if isinstance(commits, list) else []
head_commit = next((item for item in commits if item.get("sha") == head_sha), None)
if head_commit is None and commits:
head_commit = commits[-1]
head_message = ((head_commit or {}).get("commit") or {}).get("message", "")
change_commit = head_commit
if is_trigger_only(head_message):
for item in reversed(commits[:-1]):
message = (item.get("commit") or {}).get("message", "")
if not is_trigger_only(message):
change_commit = item
break
change_sha = (change_commit or {}).get("sha") or head_sha
change_message = ((change_commit or {}).get("commit") or {}).get("message", "")
commit_detail = gh_json(f"repos/{repo}/commits/{change_sha}") or {}
files = [item.get("filename", "") for item in commit_detail.get("files", [])]
files = [filename for filename in files if filename]
print("### What changed")
print()
print(f"- **PR head**: `{short(head_sha)}` -- {subject(head_message)}")
if change_sha != head_sha:
print(
f"- **Change commit**: `{short(change_sha)}` -- "
f"{subject(change_message)} (latest non-trigger commit)"
)
notes = body_lines(change_message)
if notes:
print("- **Commit notes**:")
for line in notes:
print(f" - {line}")
if files:
shown = files[:10]
extra = len(files) - len(shown)
suffix = f", +{extra} more" if extra > 0 else ""
print(f"- **Files touched**: {', '.join(f'`{name}`' for name in shown)}{suffix}")
print()
score: dict[str, object] = {}
if score_path.is_file():
score = json.loads(score_path.read_text(encoding="utf-8"))
print("### Parity snapshot")
print()
if score:
gates = score.get("gates") or []
failing = [
str(gate.get("name"))
for gate in gates
if isinstance(gate, dict) and gate.get("passing") is False
]
parity_passing = score.get("parity_passing", "?")
parity_total = score.get("parity_total", "?")
print(f"- **Score**: {format_float(score.get('migration_score'))}")
print(f"- **Progress**: {format_float(score.get('progress'))}")
print(f"- **Parity**: {parity_passing}/{parity_total}")
print(
"- **Tests**: "
f"Go {score.get('target_tests_passing', '?')}, "
f"Python {score.get('source_tests_passing', '?')}"
)
print(f"- **Deletion-grade ready**: {bool_word(score.get('deletion_grade_ready'))}")
print(f"- **Blocking gates**: {', '.join(failing) if failing else 'none'}")
else:
failing = []
print(f"- **Parity job result**: {parity_result}")
print("- **Score artifact**: unavailable")
print()
print("### Next work")
print()
failing_set = set(failing)
if score and not failing_set and score.get("deletion_grade_ready") is True:
print("- No benchmark or parity follow-up is needed; proceed to the completion gate.")
elif failing_set & {"upstream_freshness", "upstream_contracts"}:
print(
"- Refresh the upstream APM baseline/reviewed SHA and repair upstream "
"contract coverage until `upstream_freshness` and `upstream_contracts` pass."
)
elif failing_set & {
"surface_parity",
"help_parity",
"option_parity",
"functional_contracts",
"state_diff_contracts",
"python_behavior_contracts",
}:
print(
"- Fix the listed Python/Go contract drift, add or update parity coverage, "
"and rerun migration CI."
)
elif failing_set & {"benchmarks_pass"}:
print("- Investigate the benchmark regression and restore Go/Python return-code parity.")
elif score:
print("- Inspect the failing gate artifacts and turn the first failing gate into the next Crane task.")
else:
print("- Open the parity evidence artifact; the score summary was not available to this job.")
PY
marker="<!-- apm-migration-benchmark:${HEAD_SHA} -->"
{
echo "$marker"
echo "## Migration Benchmark Results"
echo
echo "- **Commit**: \`${HEAD_SHA}\`"
echo "- **Run**: ${RUN_URL}"
echo
cat "$RUNNER_TEMP/migration-benchmark-context.md"
echo
cat "$RUNNER_TEMP/migration-cli-benchmark.md"
} > "$RUNNER_TEMP/migration-benchmark-pr-comment.md"
comment_id=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --paginate \
--jq ".[] | select(.body | contains(\"${marker}\")) | .id" | tail -n 1)
if [ -n "$comment_id" ]; then
gh api \
--method PATCH \
"repos/${GITHUB_REPOSITORY}/issues/comments/${comment_id}" \
--field body@"$RUNNER_TEMP/migration-benchmark-pr-comment.md"
else
gh pr comment "$PR_NUMBER" --body-file "$RUNNER_TEMP/migration-benchmark-pr-comment.md"
fi
- name: Upload benchmark evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: migration-benchmark-evidence
path: |
${{ runner.temp }}/migration-cli-benchmark.json
${{ runner.temp }}/migration-cli-benchmark.md
if-no-files-found: ignore
retention-days: 14