diff --git a/.github/workflows/production-marketplace-release.yml b/.github/workflows/production-marketplace-release.yml new file mode 100644 index 0000000..a88380d --- /dev/null +++ b/.github/workflows/production-marketplace-release.yml @@ -0,0 +1,59 @@ +name: Production marketplace release gate + +on: + pull_request: + +permissions: + contents: read + pull-requests: read + +jobs: + production-marketplace-release: + runs-on: ubuntu-latest + steps: + - name: Identify release-related changes + id: changes + uses: actions/github-script@v7 + with: + script: | + const files = await github.paginate(github.rest.pulls.listFiles, { + ...context.repo, + pull_number: context.issue.number, + per_page: 100, + }); + const paths = [ + ".github/workflows/production-marketplace-release.yml", + "docs/canvas-authoring/SHA256SUMS", + "docs/plugin-marketplace.md", + "docs/release-process.md", + "scripts/verify-plugin-marketplace.mjs", + "test/plugin-marketplace.test.mjs", + "test/fixtures/marketplace.candidate.json", + ]; + const prefixes = [ + ".github/plugin/", + "canvases/azure-functions-hosted-skills/", + "canvases/azure-resources-query/", + "plugins/canvas-authoring/", + ]; + const relevant = (path) => + paths.includes(path) || prefixes.some((prefix) => path.startsWith(prefix)); + const verify = files.length >= 3000 || files.some(({ filename, previous_filename }) => + relevant(filename) || (previous_filename && relevant(previous_filename))); + core.setOutput("verify", verify ? "true" : "false"); + core.info(verify ? "Run strict marketplace verification." : "Unrelated changes; skip marketplace verification."); + - uses: actions/checkout@v4 + if: steps.changes.outputs.verify == 'true' + with: + fetch-depth: 0 + fetch-tags: true + - uses: actions/setup-node@v4 + if: steps.changes.outputs.verify == 'true' + with: + node-version: '22' + - name: Test marketplace policy + if: steps.changes.outputs.verify == 'true' + run: node --test test/plugin-marketplace.test.mjs + - name: Verify production marketplace against release tags + if: steps.changes.outputs.verify == 'true' + run: node scripts/verify-plugin-marketplace.mjs diff --git a/docs/plugin-marketplace.md b/docs/plugin-marketplace.md index 1fd255a..d0cb40c 100644 --- a/docs/plugin-marketplace.md +++ b/docs/plugin-marketplace.md @@ -87,6 +87,16 @@ does not prove the build's origin. tags exist.** Pre-tag tests with temporary *local-only* tags in a disposable clone are synthetic candidate qualification, not release verification; never push those refs or present the result as a published install. +The `production-marketplace-release` pull-request check is always present. +For marketplace, product, verifier, and related release-file changes, it runs +the marketplace tests and then this unmodified strict validator with complete +Git history and fetched tags. An unrelated PR skips checkout and verification +but still reports a successful check; a failed changed-files lookup cannot +skip the gate. Relevant PRs are expected to fail until the approved product +PRs merge and all three real production tags exist; rerun after tag +publication. No candidate manifest or temporary tags satisfy this CI gate. +An administrator must require `production-marketplace-release` on protected +release branches; the workflow alone does not enforce merging policy. After publication, check the actual GitHub-hosted marketplace with fresh, isolated `HOME`, `COPILOT_HOME`, and `COPILOT_CACHE_HOME` directories. Confirm that each