Chore(deps): bump the github-actions group across 1 directory with 29 updates #2149
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: NOTICE drift | |
| # Regenerates every shipping package's NOTICE from the canonical | |
| # `qv-notice-generate` scripts and verifies the committed files are still | |
| # in sync (a drift gate). This keeps third-party / model attributions correct | |
| # on every release instead of relying on a human remembering to run the skill. | |
| # Rolled out to the qvac monorepo (the pilot Tier-1 repo, docs/devops/TIER-1-SCOPE.md | |
| # §A) per QVAC-21558. | |
| # | |
| # Why this gate lives IN-repo (unlike the license/security gates): | |
| # license-compliance.yml and security-baseline.yml are thin callers to generic | |
| # reusable workflows in tetherto/qvac-actions. NOTICE generation is NOT generic — | |
| # it drives repo-local scripts (.cursor/skills/qv-notice-generate/scripts) over | |
| # this repo's package layout, models.prod.json, and vcpkg registries. So the gate | |
| # is a self-contained in-repo workflow that runs those scripts directly. | |
| # | |
| # Rollout stage: WARN-ONLY (shadow mode). NOTHING in the job fails a merge — | |
| # neither a generator crash (the regenerate step is `continue-on-error`) nor | |
| # detected drift (the "Report outcome" step only `exit 1`s when ENFORCE=true, set | |
| # via workflow_dispatch). This is required because package lockfiles are | |
| # gitignored in this repo (docs/devops/TIER-1-SCOPE.md §B1): without committed | |
| # lockfiles the JS/Python scans resolve latest-compatible versions, so an | |
| # unrelated upstream patch release can produce benign NOTICE drift. Promotion to | |
| # a required blocking check is a follow-up, gated on resolving that | |
| # non-determinism (commit lockfiles or scope the drift check to the deterministic | |
| # model/C++ sections) plus TL sign-off. | |
| # | |
| # Prerequisite for a real signal: NPM_TOKEN must be provisioned as a repo/org | |
| # secret (GitHub renders an unset secret as an empty string, which the generator | |
| # preflight rejects). Until then every run warns "generation did not complete" | |
| # instead of producing drift results — it just never blocks. | |
| # | |
| # Fork PRs are skipped: NOTICE generation needs the NPM_TOKEN org secret that | |
| # fork PRs never receive. The push-to-main and weekly scheduled runs are the | |
| # backstop. | |
| # | |
| # Scope note: "all packages the generator covers" is used as the shipping set | |
| # today. Narrowing to the locked C1/C4 shipping rows is gated on QVAC-19052 | |
| # (docs/devops/TIER-1-SCOPE.md §A is still a DRAFT stub). | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - "packages/**/package.json" | |
| - "packages/**/vcpkg.json" | |
| - "packages/**/vcpkg-configuration.json" | |
| - "packages/**/vcpkg/ports/**/portfile.cmake" | |
| - "packages/**/vcpkg/triplets/*.cmake" | |
| - "packages/**/requirements*.txt" | |
| - "packages/**/pyproject.toml" | |
| - "packages/registry-server/data/models.prod.json" | |
| - "packages/**/NOTICE" | |
| - ".cursor/skills/qv-notice-generate/**" | |
| - ".github/workflows/notice-drift.yml" | |
| pull_request: | |
| paths: | |
| - "packages/**/package.json" | |
| - "packages/**/vcpkg.json" | |
| - "packages/**/vcpkg-configuration.json" | |
| - "packages/**/vcpkg/ports/**/portfile.cmake" | |
| - "packages/**/vcpkg/triplets/*.cmake" | |
| - "packages/**/requirements*.txt" | |
| - "packages/**/pyproject.toml" | |
| - "packages/registry-server/data/models.prod.json" | |
| - "packages/**/NOTICE" | |
| - ".cursor/skills/qv-notice-generate/**" | |
| - ".github/workflows/notice-drift.yml" | |
| schedule: | |
| # Weekly freshness backstop (Mon 05:00 UTC): catches drift from upstream | |
| # dependency releases even when no NOTICE-affecting file changed in a PR. | |
| - cron: "0 5 * * 1" | |
| workflow_dispatch: | |
| inputs: | |
| enforce: | |
| description: "Fail the job on NOTICE drift (default: warn-only shadow mode)" | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| notice-drift: | |
| name: notice-drift | |
| # Skip fork PRs — generation needs org secrets forks never receive (see header). | |
| if: >- | |
| github.event_name != 'pull_request' || | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: ubuntu-latest | |
| # Full --all regeneration does an npm install + license-checker per package, | |
| # a pip venv for Python deps, and throttled GitHub API calls for every vcpkg | |
| # port, so it is minutes-long, not seconds. 45m leaves headroom. | |
| timeout-minutes: 45 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| env: | |
| # generate-notice.js reads GH_TOKEN for GitHub API (vcpkg registry | |
| # portfiles + repo license lookups). qvac-registry-vcpkg is public and the | |
| # scan only enumerates top-level packages/* (never packages/sdk/e2e, the | |
| # sole @tetherto consumer), so GITHUB_TOKEN is sufficient. | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # NPM_TOKEN must be provisioned as a repo/org secret for the generator's | |
| # preflight to pass (an unset secret renders as an empty string, which the | |
| # generator rejects). Until then the regenerate step below fails and, in | |
| # shadow mode, is reported as a warning rather than blocking. | |
| NPM_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| ENFORCE: ${{ github.event.inputs.enforce || 'false' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # 7.0.1 | |
| - name: Setup Node | |
| uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # 7.0.0 | |
| with: | |
| node-version: lts/* | |
| - name: Setup Python | |
| uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # 7.0.0 | |
| with: | |
| python-version: "3.x" | |
| - name: Regenerate NOTICE files for all shipping packages | |
| id: regen | |
| # Tolerate generator failure so the warn-only (shadow-mode) contract | |
| # actually holds: a crash here (missing NPM_TOKEN, npm/PyPI/GitHub API | |
| # hiccup) must not hard-fail the job — otherwise the schedule / push:main | |
| # backstops go permanently red. "Report outcome" below converts a failure | |
| # into a warning unless ENFORCE=true. continue-on-error is intentional | |
| # here precisely because this is a shadow-mode gate, not a Tier-1 blocker. | |
| continue-on-error: true | |
| run: node .cursor/skills/qv-notice-generate/scripts/generate-notice.js --all | |
| - name: Detect NOTICE drift | |
| id: drift | |
| if: steps.regen.outcome == 'success' | |
| shell: bash | |
| run: | | |
| set -uo pipefail | |
| # Mark any newly-required (untracked) NOTICE as intent-to-add so it shows | |
| # up in the diff. A missing committed NOTICE is the compliance case this | |
| # gate most needs to catch, and plain `git diff` only reports changes to | |
| # tracked files. node_modules, package-lock.json, and per-package .npmrc | |
| # the generator also touches stay gitignored, so the diff is still clean. | |
| git add --intent-to-add -- ':(glob)**/NOTICE' | |
| mapfile -t changed < <(git diff --name-only -- ':(glob)**/NOTICE') | |
| if [ ${#changed[@]} -eq 0 ]; then | |
| echo "drift=false" >> "$GITHUB_OUTPUT" | |
| echo "No NOTICE drift detected — committed files match freshly generated output." >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| echo "drift=true" >> "$GITHUB_OUTPUT" | |
| { | |
| echo "## NOTICE drift detected" | |
| echo | |
| echo "These committed NOTICE files differ from freshly generated output:" | |
| echo | |
| for f in "${changed[@]}"; do | |
| echo "- \`$f\`" | |
| done | |
| echo | |
| echo "Regenerate with the \`qv-notice-generate\` skill (or run" | |
| echo "\`node .cursor/skills/qv-notice-generate/scripts/generate-notice.js --all\`)" | |
| echo "and commit the result." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Comment drift on PR | |
| if: >- | |
| steps.regen.outcome == 'success' && | |
| steps.drift.outputs.drift == 'true' && | |
| github.event_name == 'pull_request' | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # 9.0.0 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const marker = '<!-- notice-drift -->'; | |
| const body = [ | |
| marker, | |
| '## NOTICE drift detected (shadow mode)', | |
| '', | |
| 'Committed `NOTICE` files differ from freshly generated output. This', | |
| 'check is **warn-only** for now and does not block the merge.', | |
| '', | |
| 'To resolve, regenerate and commit:', | |
| '', | |
| '```bash', | |
| 'source .env', | |
| 'node .cursor/skills/qv-notice-generate/scripts/generate-notice.js --all', | |
| '```', | |
| '', | |
| `See the [job summary](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) for the exact files.`, | |
| ].join('\n'); | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| per_page: 100, | |
| }); | |
| const existing = comments.find((c) => c.body && c.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body, | |
| }); | |
| } | |
| - name: Report outcome | |
| if: always() | |
| shell: bash | |
| env: | |
| REGEN_OUTCOME: ${{ steps.regen.outcome }} | |
| DRIFT: ${{ steps.drift.outputs.drift }} | |
| run: | | |
| set -uo pipefail | |
| # Shadow mode (ENFORCE != true): nothing here fails the job — neither a | |
| # generator crash nor detected drift. ENFORCE=true (workflow_dispatch) | |
| # flips both into hard failures for a blocking trial. | |
| if [ "$REGEN_OUTCOME" != "success" ]; then | |
| msg="NOTICE generation did not complete — see the 'Regenerate' step logs (e.g. a missing NPM_TOKEN secret)." | |
| if [ "$ENFORCE" = "true" ]; then | |
| echo "::error::$msg Failing (enforce mode)." | |
| exit 1 | |
| fi | |
| echo "::warning::$msg Not failing (shadow mode); no drift signal was produced this run." | |
| exit 0 | |
| fi | |
| if [ "$DRIFT" = "true" ]; then | |
| if [ "$ENFORCE" = "true" ]; then | |
| echo "::error::NOTICE drift detected (enforce mode). Failing the job." | |
| exit 1 | |
| fi | |
| echo "::warning::NOTICE drift detected (shadow mode). Not failing — see the job summary and PR comment." | |
| exit 0 | |
| fi | |
| echo "NOTICE files are in sync." |