Fix canary bypass audit step failing before checkout #5
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "SDK Canary (runtime compat gate)" | |
| # Nightly-style canary compatibility gate: installs an explicit version of the | |
| # @github/copilot runtime, builds the Node SDK, and runs the Node e2e suite | |
| # against it. This proves runtime <-> SDK compatibility. No publishing happens | |
| # here. | |
| env: | |
| HUSKY: 0 | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| runtime_version: | |
| description: "Exact @github/copilot version to test (e.g. 1.0.69 or 1.0.70-canary.<sha>)" | |
| required: true | |
| type: string | |
| runtime_source: | |
| description: "Where to install the runtime from" | |
| required: true | |
| type: choice | |
| options: | |
| - public | |
| - feed | |
| default: public | |
| force_publish: | |
| description: "Publish the SDK canary even if the e2e gate fails (use ONLY for a known flake). Bypasses the e2e signal; the build + feed-only guards still apply." | |
| required: false | |
| type: boolean | |
| default: false | |
| # TEMPORARY: branch-push trigger so we can validate the pipeline before the | |
| # workflow_dispatch entrypoint exists on main. Push events carry no dispatch | |
| # inputs, so the resolve job falls back to the currently pinned runtime from | |
| # public npm. Remove this trigger once the workflow lands on main. | |
| push: # TEMP: remove at finalize (coupled with the publish.if `event==push` clause) | |
| branches: [mackinnonbuck-sdk-canary-compat-gate] | |
| permissions: | |
| contents: read | |
| id-token: write | |
| jobs: | |
| resolve: | |
| name: "Resolve runtime inputs" | |
| if: github.event.repository.fork == false | |
| runs-on: ubuntu-latest | |
| outputs: | |
| RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }} | |
| RUNTIME_SOURCE: ${{ steps.normalize.outputs.RUNTIME_SOURCE }} | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| # Normalize whichever trigger fired into a single (RUNTIME_VERSION, | |
| # RUNTIME_SOURCE) pair that every downstream step references. Adding a | |
| # `repository_dispatch: types: [runtime-canary]` trigger later is purely | |
| # additive: add one more case that reads client_payload and defaults | |
| # source to 'feed'. No downstream rework required. | |
| - name: Normalize inputs | |
| id: normalize | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_VERSION: ${{ inputs.runtime_version }} | |
| INPUT_SOURCE: ${{ inputs.runtime_source }} | |
| run: | | |
| set -euo pipefail | |
| case "$EVENT_NAME" in | |
| workflow_dispatch) | |
| VERSION="$INPUT_VERSION" | |
| SOURCE="$INPUT_SOURCE" | |
| ;; | |
| push) | |
| # No dispatch inputs on push: fall back to the currently pinned | |
| # runtime from public npm so the run is self-consistent and a green | |
| # result proves the pipeline mechanics. | |
| SOURCE="public" | |
| VERSION="$(node -e "const v=require('./nodejs/package.json').dependencies['@github/copilot']; process.stdout.write(String(v).replace(/^[\^~]/, ''))")" | |
| ;; | |
| *) | |
| echo "::error::Unsupported event '$EVENT_NAME'." | |
| exit 1 | |
| ;; | |
| esac | |
| if [ -z "$VERSION" ]; then echo "::error::Could not determine runtime version."; exit 1; fi | |
| if [ -z "$SOURCE" ]; then SOURCE="public"; fi | |
| echo "Resolved RUNTIME_VERSION=$VERSION RUNTIME_SOURCE=$SOURCE" | |
| echo "RUNTIME_VERSION=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "RUNTIME_SOURCE=$SOURCE" >> "$GITHUB_OUTPUT" | |
| - name: Validate runtime version (semver) | |
| env: | |
| RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }} | |
| run: | | |
| if [[ ! "$RUNTIME_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9._-]+)?$ ]]; then | |
| echo "::error::Invalid runtime version '$RUNTIME_VERSION'. Expected semver (e.g. 1.0.69 or 1.0.70-canary.abc123)." | |
| exit 1 | |
| fi | |
| test: | |
| name: "e2e (${{ matrix.os }})" | |
| needs: resolve | |
| if: github.event.repository.fork == false | |
| environment: cicd | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| runs-on: ${{ matrix.os }} | |
| env: | |
| POWERSHELL_UPDATECHECK: Off | |
| RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} | |
| RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }} | |
| defaults: | |
| run: | |
| shell: bash | |
| working-directory: ./nodejs | |
| steps: | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| cache: "npm" | |
| cache-dependency-path: "./nodejs/package-lock.json" | |
| node-version: 22 | |
| - name: Install SDK dependencies | |
| run: npm ci --ignore-scripts | |
| - name: Install test harness dependencies | |
| working-directory: ./test/harness | |
| run: npm ci --ignore-scripts | |
| - name: Azure Login (OIDC -> id-cpd-ci) | |
| if: env.RUNTIME_SOURCE == 'feed' | |
| uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 | |
| with: | |
| client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci | |
| tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" | |
| allow-no-subscriptions: true | |
| # Route ONLY @github/* (the runtime + its 8 platform packages) to the | |
| # internal feed via a scoped registry. All other deps (e.g. detect-libc) | |
| # still resolve from public npm. A global --registry would break because | |
| # detect-libc is not on the feed. | |
| - name: Configure canary feed (.npmrc) | |
| if: env.RUNTIME_SOURCE == 'feed' | |
| run: | | |
| set -euo pipefail | |
| TOKEN="$(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv)" | |
| echo "::add-mask::$TOKEN" | |
| NPMRC="$(cat <<EOF | |
| @github:registry=https://pkgs.dev.azure.com/devdiv/_packaging/copilot-canary-test/npm/registry/ | |
| //pkgs.dev.azure.com/devdiv/_packaging/copilot-canary-test/npm/registry/:_authToken=${TOKEN} | |
| //pkgs.dev.azure.com/devdiv/_packaging/copilot-canary-test/npm/:_authToken=${TOKEN} | |
| EOF | |
| )" | |
| printf '%s\n' "$NPMRC" > .npmrc | |
| printf '%s\n' "$NPMRC" > ../test/harness/.npmrc | |
| echo "Wrote scoped @github registry .npmrc to ./nodejs and ./test/harness" | |
| - name: Override runtime version | |
| run: | | |
| set -euo pipefail | |
| echo "Installing @github/copilot@${RUNTIME_VERSION} (source: ${RUNTIME_SOURCE})" | |
| npm install "@github/copilot@${RUNTIME_VERSION}" --save-exact --ignore-scripts | |
| ( cd ../test/harness && npm install "@github/copilot@${RUNTIME_VERSION}" --save-exact --ignore-scripts ) | |
| - name: Verify installed runtime | |
| run: | | |
| set -euo pipefail | |
| node -e ' | |
| const fs = require("fs"); | |
| const expected = process.env.RUNTIME_VERSION; | |
| const pkg = require("./node_modules/@github/copilot/package.json"); | |
| if (pkg.version !== expected) { | |
| console.error(`::error::Installed @github/copilot version ${pkg.version} does not match requested ${expected}`); | |
| process.exit(1); | |
| } | |
| const dir = "./node_modules/@github"; | |
| const entries = fs.readdirSync(dir).filter((d) => d.startsWith("copilot-")); | |
| const plat = process.platform === "win32" ? "win32" : process.platform === "darwin" ? "darwin" : "linux"; | |
| const arch = process.arch; | |
| const match = entries.find((d) => d.includes(plat) && d.includes(arch)); | |
| if (!match) { | |
| console.error(`::error::No @github/copilot platform optional dep for ${plat}-${arch}. Present: ${entries.join(", ") || "(none)"}`); | |
| process.exit(1); | |
| } | |
| console.log(`Verified @github/copilot@${pkg.version} with platform package @github/${match}`); | |
| ' | |
| - name: Build SDK | |
| run: npm run build | |
| - name: Warm up PowerShell | |
| if: runner.os == 'Windows' | |
| run: pwsh.exe -Command "Write-Host 'PowerShell ready'" | |
| - name: Run Node.js SDK e2e tests | |
| env: | |
| COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} | |
| run: npm test | |
| publish: | |
| name: "Publish SDK canary (internal feed)" | |
| needs: [resolve, test] | |
| # Normally publish only runs when the e2e gate is green. Two bypasses: | |
| # - workflow_dispatch with force_publish=true: a human-acknowledged flake | |
| # override (audited via the actor on the run). | |
| # - github.event_name == 'push': TEMP: remove at finalize — branch-validation | |
| # bypass coupled with the temporary push trigger so we can exercise the | |
| # publish + bypass paths on this branch. Drop the `github.event_name == | |
| # 'push'` clause from the `if:` below together with the push trigger; after | |
| # that, force is honored only on real dispatches. | |
| # The bypass only skips the e2e *signal* — the publish job still runs the | |
| # build (so a broken build can't publish) and enforces feed-only + read-back. | |
| if: > | |
| !cancelled() && | |
| github.event.repository.fork == false && | |
| needs.resolve.result == 'success' && | |
| (needs.test.result == 'success' || | |
| (github.event_name == 'workflow_dispatch' && inputs.force_publish) || | |
| github.event_name == 'push') | |
| environment: cicd | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| env: | |
| RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} | |
| # Org-scoped internal TEST feed. The SDK canary MUST only ever go here, | |
| # never to public npm (@github/copilot-sdk is a live public package). | |
| FEED_URL: https://pkgs.dev.azure.com/devdiv/_packaging/copilot-canary-test/npm/registry/ | |
| defaults: | |
| run: | |
| shell: bash | |
| working-directory: ./nodejs | |
| steps: | |
| - name: Warn — publishing despite failed e2e gate (bypass) | |
| # always() so this audit is never skipped by prior-step status; it fires | |
| # specifically when publish proceeded without a green e2e gate. Runs at | |
| # the workspace root because it executes before checkout, so the job's | |
| # default working-directory (./nodejs) does not exist yet. | |
| if: always() && needs.test.result != 'success' | |
| working-directory: ${{ github.workspace }} | |
| run: | | |
| echo "::warning title=e2e gate bypassed::Publishing SDK canary despite a non-passing e2e gate (test job result: ${{ needs.test.result }}). Triggered by '${{ github.actor }}' via '${{ github.event_name }}'${{ (github.event_name == 'workflow_dispatch' && inputs.force_publish) && ' with force_publish=true' || '' }}. The e2e signal was bypassed; build + feed-only + read-back guards still apply." | |
| - uses: actions/checkout@v6.0.2 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 22 | |
| # Default public registry: installs build deps and the currently pinned | |
| # runtime. Do NOT write any feed .npmrc or scoped @github:registry line | |
| # here, or npm ci would try to fetch the runtime from the upstream-less | |
| # feed and 404. | |
| - name: Install SDK dependencies | |
| run: npm ci | |
| - name: Compute SDK canary version | |
| id: sdkver | |
| env: | |
| RUN_NUMBER: ${{ github.run_number }} | |
| SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| SHORT_SHA="${SHA:0:7}" | |
| SDK_VERSION="0.0.0-canary.${RUN_NUMBER}.g${SHORT_SHA}" | |
| if [[ ! "$SDK_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9._-]+)?$ ]]; then | |
| echo "::error::Computed SDK canary version '$SDK_VERSION' is not valid semver." | |
| exit 1 | |
| fi | |
| echo "SDK canary version: $SDK_VERSION" | |
| echo "SDK_VERSION=$SDK_VERSION" >> "$GITHUB_OUTPUT" | |
| - name: Set package version and pin runtime dependency | |
| env: | |
| SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} | |
| run: | | |
| set -euo pipefail | |
| npm version "$SDK_VERSION" --no-git-tag-version --allow-same-version | |
| # Exact pin (no caret) so the published SDK canary depends on precisely | |
| # the runtime version that was just tested by the e2e gate. | |
| npm pkg set "dependencies.@github/copilot=$RUNTIME_VERSION" | |
| echo "Pinned @github/copilot to $(npm pkg get dependencies.@github/copilot)" | |
| - name: Build SDK | |
| run: npm run build | |
| - name: Azure Login (OIDC -> id-cpd-ci) | |
| uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 | |
| with: | |
| client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci | |
| tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" | |
| allow-no-subscriptions: true | |
| # Auth-only .npmrc: just the two token lines, NO scoped registry line. | |
| # The publish target is supplied explicitly via publishConfig + --registry. | |
| - name: Configure feed auth (.npmrc) | |
| run: | | |
| set -euo pipefail | |
| TOKEN="$(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv)" | |
| echo "::add-mask::$TOKEN" | |
| cat > .npmrc <<EOF | |
| //pkgs.dev.azure.com/devdiv/_packaging/copilot-canary-test/npm/registry/:_authToken=${TOKEN} | |
| //pkgs.dev.azure.com/devdiv/_packaging/copilot-canary-test/npm/:_authToken=${TOKEN} | |
| EOF | |
| echo "Wrote auth-only .npmrc to ./nodejs" | |
| # Belt and suspenders (2 of 3): pin the publish target in the package too. | |
| - name: Set publishConfig registry | |
| run: npm pkg set "publishConfig.registry=$FEED_URL" | |
| # Belt and suspenders (3 of 3): fail loudly unless the effective publish | |
| # target is the internal feed. Guards against ever reaching public npm. | |
| - name: Assert publish target is the internal feed | |
| run: | | |
| set -euo pipefail | |
| EFFECTIVE="$(npm pkg get publishConfig.registry | tr -d '"')" | |
| echo "Effective publishConfig.registry: $EFFECTIVE" | |
| if [ "$EFFECTIVE" != "$FEED_URL" ]; then | |
| echo "::error::publishConfig.registry ('$EFFECTIVE') is not the internal feed ('$FEED_URL'). Refusing to publish." | |
| exit 1 | |
| fi | |
| - name: Publish SDK canary to internal feed | |
| run: npm publish --registry "$FEED_URL" | |
| - name: Read-back verify published canary | |
| env: | |
| SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} | |
| run: | | |
| set -euo pipefail | |
| PUBLISHED="$(npm view "@github/copilot-sdk@${SDK_VERSION}" version --registry "$FEED_URL")" | |
| PINNED="$(npm view "@github/copilot-sdk@${SDK_VERSION}" dependencies.@github/copilot --registry "$FEED_URL")" | |
| echo "Published version: $PUBLISHED" | |
| echo "Pinned @github/copilot: $PINNED" | |
| if [ "$PUBLISHED" != "$SDK_VERSION" ]; then | |
| echo "::error::Read-back version '$PUBLISHED' != published '$SDK_VERSION'." | |
| exit 1 | |
| fi | |
| if [ "$PINNED" != "$RUNTIME_VERSION" ]; then | |
| echo "::error::Read-back runtime pin '$PINNED' != tested '$RUNTIME_VERSION'." | |
| exit 1 | |
| fi | |
| echo "Read-back OK: @github/copilot-sdk@${SDK_VERSION} exact-depends on @github/copilot@${RUNTIME_VERSION}" |