From 533f1d0c708008162869f34670fb3f6d4b4bf702 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Thu, 23 Apr 2026 11:11:27 +0200 Subject: [PATCH 1/3] chore: prepare v0.9.2 release Bumps version to 0.9.2 and finalizes CHANGELOG with one-line summaries for each PR merged since 0.9.1. Highlights: - ADO AAD bearer-token auth (#856) - Governance Guide + enterprise docs IA refactor (#851, #858) - Merge Gate orchestrator + single-authority aggregation (#865, #867) - Landing + first-package docs rewrite (#855, #866) - gh-aw imports migration (#864) - Custom-port surfacing fix (#804) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 21 +++++++++++++++++---- pyproject.toml | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc0a248f..725ab4c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,12 +8,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.9.2] - 2026-04-23 + ### Added -- New `enterprise/governance-guide.md` documentation page: flagship governance reference for CISO / VPE / Platform Tech Lead audiences, covering enforcement points, bypass contract, failure semantics, air-gapped operation, rollout playbook, and known gaps. Trims duplicated content in `governance.md`, `apm-policy.md`, and `integrations/github-rulesets.md`. Adds `templates/apm-policy-starter.yml`. (#851) -- `apm install` now supports Azure DevOps AAD bearer-token auth via `az account get-access-token`, with PAT-first fallback for orgs that disable PAT creation. Closes #852 (#856) -- New CI safety net: `merge-gate.yml` orchestrator turns dropped `pull_request` webhook deliveries into clear red checks instead of stuck `Expected -- Waiting for status to be reported`. Triggers on both `pull_request` and `pull_request_target` for redundancy. (#865) (PR follow-up to #856 CI flake) -- `merge-gate.yml` now aggregates ALL PR-time required checks (`Build & Test (Linux)` + 4 stubs from `ci-integration-pr-stub.yml`) into a single `Merge Gate / gate` verdict. Branch protection requires only this single check, decoupling the ruleset from CI workflow topology (Tide / bors pattern). +- `apm install` supports Azure DevOps AAD bearer-token auth via `az account get-access-token`, with PAT-first fallback for orgs that disable PAT creation. Closes #852 (#856) +- New `enterprise/governance-guide.md`: flagship governance reference for CISO / VPE / Platform Tech Lead audiences; trims duplication across `governance.md`, `apm-policy.md`, `integrations/github-rulesets.md`; adds `templates/apm-policy-starter.yml`. (#851) +- Enterprise docs IA refactor: hub page + merged team guides, deduped governance content. (#858) +- Landing page rewritten around the three-pillar spine. (#855) +- First-package tutorial rewritten end-to-end; fixes `.apm/` anatomy hallucinations. (#866) + +### Changed + +- gh-aw workflows now use `imports:` for shared APM context instead of the deprecated `dependencies:` field. (#864) +- CI: `merge-gate.yml` orchestrator turns dropped `pull_request` webhook deliveries into clear red checks instead of stuck `Expected -- Waiting for status to be reported`; triggers on both `pull_request` and `pull_request_target` for redundancy. (#865) +- CI: `Merge Gate / gate` now aggregates all PR-time required checks (`Build & Test (Linux)` + 4 stubs) into a single verdict; branch protection requires only this one check, decoupling the ruleset from CI workflow topology (Tide / bors pattern). (#867) + +### Fixed + +- `apm install` surfaces the custom port in clone / `ls-remote` error messages for generic git hosts. (#804) ## [0.9.1] - 2026-04-22 diff --git a/pyproject.toml b/pyproject.toml index bd38a23d..f99a0391 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "apm-cli" -version = "0.9.1" +version = "0.9.2" description = "MCP configuration tool" readme = "README.md" requires-python = ">=3.10" From ee74c650c089d23f28ac47814364456d6590bb64 Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Thu, 23 Apr 2026 11:30:25 +0200 Subject: [PATCH 2/3] ci: simplify merge-gate to single pull_request trigger The dual-trigger pattern (pull_request + pull_request_target with concurrency cancel-in-progress) shipped in #865 was over-engineered. It produced TWO 'gate' check-runs per SHA -- one SUCCESS, one CANCELLED -- and branch protection's status-check rollup treats CANCELLED as failure, so PRs were silently BLOCKED unless an admin overrode (which masked the bug on #867). GitHub Actions has no primitive for 'either of these events succeeded'. World-class OSS projects (kubernetes, rust, deno, next.js) accept this and use a single trigger. The cost: a dropped 'pull_request' webhook (rare; observed once on PR #856) requires manual recovery. Recovery paths now documented at top of file: - push empty commit - gh workflow run merge-gate.yml -f pr_number=NNN - close + reopen PR Replaces the dual-trigger + bootstrap-fetch dance with a clean two-job flow: resolve-sha (handles workflow_dispatch input or PR head) then gate (sparse checkout + run script). Same script, same exit codes, same EXPECTED_CHECKS env. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/merge-gate.yml | 123 +++++++++++++++---------------- CHANGELOG.md | 5 +- 2 files changed, 62 insertions(+), 66 deletions(-) diff --git a/.github/workflows/merge-gate.yml b/.github/workflows/merge-gate.yml index cffa3058..aa31011f 100644 --- a/.github/workflows/merge-gate.yml +++ b/.github/workflows/merge-gate.yml @@ -1,31 +1,31 @@ # Merge Gate -- single-authority orchestrator that aggregates ALL required # PR-time checks into one verdict. Branch protection requires only this -# check; this workflow verifies all underlying checks via the Checks API. +# check; this workflow polls the Checks API for all underlying checks. # # Why this file exists: # GitHub's required-status-checks model is name-based, not workflow-based. -# We tier our CI: ci.yml runs Tier 1 on `pull_request` and emits -# `Build & Test (Linux)`, while ci-integration-pr-stub.yml stubs the four -# Tier 2 checks via `pull_request_target`. That asymmetry means required -# checks depend on TWO independent webhook deliveries succeeding. If the -# `pull_request` event is dropped (transient, observed on PR #856), 4/5 -# stubs go green and the 5th hangs in "Expected -- Waiting" indefinitely. +# Our CI is tiered: ci.yml emits 'Build & Test (Linux)' and +# ci-integration-pr-stub.yml emits four stubs that hold positions for +# merge-queue jobs in ci-integration.yml. Without this gate, branch +# protection had to require all 5 checks individually -- adding or +# renaming a check meant a ruleset edit. With this gate, branch +# protection requires only 'Merge Gate / gate' and the gate aggregates. +# Tide / bors pattern. # -# This workflow collapses N separately-required checks into a single -# `Merge Gate / gate` check that: -# - dispatches via two redundant triggers (pull_request + -# pull_request_target) so a single dropped delivery is recoverable; -# - polls the Checks API for ALL underlying required checks; -# - exits red if any check fails, never appears, or never completes; -# - is the SOLE required check, decoupling branch protection from -# workflow topology (Tide / bors pattern). +# Why a single trigger (not dual pull_request + pull_request_target): +# We tried dual-trigger redundancy in PR #865 to harden against rare +# dropped 'pull_request' webhook deliveries (observed once on PR #856). +# It backfired: 'concurrency: cancel-in-progress' produced TWO check-runs +# per SHA -- one SUCCESS and one CANCELLED -- which poisons branch +# protection's status-check rollup ('CANCELLED' counts as failure -> +# PR BLOCKED). No GitHub Actions primitive cleanly de-duplicates checks +# across event channels. World-class OSS projects (k8s, rust, deno, +# next.js) accept this and use a single trigger plus manual recovery. # -# Security: -# `pull_request_target` is used here for redundancy ONLY. This workflow -# never checks out PR code under that trigger, never interpolates PR data -# into `run:`, and has read-only token permissions. The classic -# pull_request_target+checkout(head) exploit is impossible by construction. -# See ci-integration-pr-stub.yml for the same security model. +# Recovery if a 'pull_request' webhook is dropped: +# - Push an empty commit: git commit --allow-empty -m 'retrigger' && git push +# - Or trigger manually: gh workflow run merge-gate.yml -f pr_number=NNN +# - Or close + reopen the PR. name: Merge Gate @@ -36,18 +36,18 @@ on: - 'docs/**' - '.gitignore' - 'LICENSE' - pull_request_target: - branches: [ main ] - types: [ opened, synchronize, reopened, ready_for_review ] - paths-ignore: - - 'docs/**' - - '.gitignore' - - 'LICENSE' + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to re-run the gate against' + required: true + type: string -# Single in-flight gate per PR. If both pull_request and pull_request_target -# fire (the happy redundant case), the second one cancels the first. +# Dedup pushes to the same PR: cancel any older in-flight gate run on +# the same PR head. Now safe -- only one trigger channel, so cancellations +# only happen on rapid push-after-push, not on cross-event collisions. concurrency: - group: merge-gate-${{ github.event.pull_request.number || github.ref }} + group: merge-gate-${{ github.event.pull_request.number || inputs.pr_number || github.ref }} cancel-in-progress: true permissions: @@ -56,53 +56,47 @@ permissions: pull-requests: read jobs: + resolve-sha: + name: resolve + runs-on: ubuntu-24.04 + outputs: + sha: ${{ steps.sha.outputs.sha }} + steps: + - name: Resolve PR head SHA + id: sha + env: + GH_TOKEN: ${{ github.token }} + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + sha=$(gh api "repos/${{ github.repository }}/pulls/${{ inputs.pr_number }}" --jq '.head.sha') + else + sha="${{ github.event.pull_request.head.sha }}" + fi + if [ -z "$sha" ]; then + echo "::error::Could not resolve PR head SHA" + exit 1 + fi + echo "sha=$sha" >> "$GITHUB_OUTPUT" + echo "[merge-gate] resolved head SHA: $sha" + gate: name: gate + needs: resolve-sha runs-on: ubuntu-24.04 timeout-minutes: 35 steps: - # On pull_request we can safely checkout PR head: the runner has no - # secrets and a read-only token. Under pull_request_target we MUST NOT - # checkout PR head -- we fetch from the base branch via API instead. - - name: Checkout PR head (pull_request only) - if: github.event_name == 'pull_request' + - name: Checkout PR head uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ needs.resolve-sha.outputs.sha }} sparse-checkout: | .github/scripts/ci - - name: Fetch script from base (pull_request_target only) - if: github.event_name == 'pull_request_target' - env: - GH_TOKEN: ${{ github.token }} - run: | - mkdir -p .github/scripts/ci - # Self-bootstrap: if the script does not yet exist on base (i.e. this - # is the PR adding the script), degrade to a no-op that passes. Once - # the script lands on main, this branch becomes a real gate. - status=$(curl -fsSL -o .github/scripts/ci/merge_gate_wait.sh \ - --retry 5 --retry-delay 3 --retry-connrefused --max-time 30 \ - -w '%{http_code}' \ - -H "Authorization: Bearer ${GH_TOKEN}" \ - -H "Accept: application/vnd.github.raw" \ - "https://api.github.com/repos/${{ github.repository }}/contents/.github/scripts/ci/merge_gate_wait.sh?ref=${GITHUB_BASE_REF}" \ - || echo "404") - if [ "$status" = "404" ] || [ ! -s .github/scripts/ci/merge_gate_wait.sh ]; then - echo "::warning::merge_gate_wait.sh not found on base ref '${GITHUB_BASE_REF}' yet -- self-bootstrap pass." - cat > .github/scripts/ci/merge_gate_wait.sh <<'BOOTSTRAP' - #!/usr/bin/env bash - echo "[merge-gate] self-bootstrap pass: script not yet on base" - exit 0 - BOOTSTRAP - fi - chmod +x .github/scripts/ci/merge_gate_wait.sh - - name: Wait for all required checks env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} - SHA: ${{ github.event.pull_request.head.sha }} + SHA: ${{ needs.resolve-sha.outputs.sha }} # All PR-time checks the gate aggregates. Keep this in sync with # the underlying workflows: ci.yml emits Build & Test (Linux), # ci-integration-pr-stub.yml emits the other four. @@ -114,3 +108,4 @@ jobs: run: | chmod +x .github/scripts/ci/merge_gate_wait.sh .github/scripts/ci/merge_gate_wait.sh + diff --git a/CHANGELOG.md b/CHANGELOG.md index 725ab4c5..74cb7af5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,8 +21,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - gh-aw workflows now use `imports:` for shared APM context instead of the deprecated `dependencies:` field. (#864) -- CI: `merge-gate.yml` orchestrator turns dropped `pull_request` webhook deliveries into clear red checks instead of stuck `Expected -- Waiting for status to be reported`; triggers on both `pull_request` and `pull_request_target` for redundancy. (#865) -- CI: `Merge Gate / gate` now aggregates all PR-time required checks (`Build & Test (Linux)` + 4 stubs) into a single verdict; branch protection requires only this one check, decoupling the ruleset from CI workflow topology (Tide / bors pattern). (#867) +- CI: `merge-gate.yml` orchestrator turns dropped `pull_request` webhook deliveries into clear red checks instead of stuck `Expected -- Waiting for status to be reported`. (#865) +- CI: `Merge Gate / gate` aggregates all PR-time required checks (`Build & Test (Linux)` + 4 stubs) into a single verdict; branch protection requires only this one check, decoupling the ruleset from CI workflow topology (Tide / bors pattern). (#867, #868) +- CI: `merge-gate.yml` simplified to a single `pull_request` trigger with `workflow_dispatch` for manual recovery; the dual-trigger redundancy attempt was poisoning the branch-protection rollup with `CANCELLED` check-runs. (#868) ### Fixed From 07726cf04ec9b9d37982039ea2bd7380cc00b25d Mon Sep 17 00:00:00 2001 From: danielmeppiel Date: Thu, 23 Apr 2026 11:37:35 +0200 Subject: [PATCH 3/3] ci: collapse merge-gate into a single job (one check-run in PR UI) The two-job split (resolve-sha + gate) created two visible check-runs. Inlining the SHA resolution as a step within the gate job leaves only one check-run -- 'Merge Gate / gate' -- on the PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/merge-gate.yml | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/.github/workflows/merge-gate.yml b/.github/workflows/merge-gate.yml index aa31011f..a85d711d 100644 --- a/.github/workflows/merge-gate.yml +++ b/.github/workflows/merge-gate.yml @@ -56,11 +56,10 @@ permissions: pull-requests: read jobs: - resolve-sha: - name: resolve + gate: + name: gate runs-on: ubuntu-24.04 - outputs: - sha: ${{ steps.sha.outputs.sha }} + timeout-minutes: 35 steps: - name: Resolve PR head SHA id: sha @@ -79,16 +78,10 @@ jobs: echo "sha=$sha" >> "$GITHUB_OUTPUT" echo "[merge-gate] resolved head SHA: $sha" - gate: - name: gate - needs: resolve-sha - runs-on: ubuntu-24.04 - timeout-minutes: 35 - steps: - name: Checkout PR head uses: actions/checkout@v4 with: - ref: ${{ needs.resolve-sha.outputs.sha }} + ref: ${{ steps.sha.outputs.sha }} sparse-checkout: | .github/scripts/ci @@ -96,7 +89,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} - SHA: ${{ needs.resolve-sha.outputs.sha }} + SHA: ${{ steps.sha.outputs.sha }} # All PR-time checks the gate aggregates. Keep this in sync with # the underlying workflows: ci.yml emits Build & Test (Linux), # ci-integration-pr-stub.yml emits the other four. @@ -109,3 +102,4 @@ jobs: chmod +x .github/scripts/ci/merge_gate_wait.sh .github/scripts/ci/merge_gate_wait.sh +