-
Notifications
You must be signed in to change notification settings - Fork 0
ci: add governance workflows and harden required gate
#9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1815919
ba16e72
231c8b9
e1c3409
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| # Copyright 2026 ResQ Software | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Path-based PR auto-labeling rules consumed by .github/workflows/labeler.yml | ||
| # (actions/labeler@v5 schema). Keys are label names; values are file globs. | ||
|
|
||
| 'area:api': | ||
| - changed-files: | ||
| - any-glob-to-any-file: | ||
| - 'api-reference/**' | ||
| - 'specs/**' | ||
|
|
||
| 'area:i18n': | ||
| - changed-files: | ||
| - any-glob-to-any-file: | ||
| - 'ar/**' | ||
| - 'es/**' | ||
| - 'hi/**' | ||
| - 'zh/**' | ||
|
|
||
| 'area:ci': | ||
| - changed-files: | ||
| - any-glob-to-any-file: | ||
| - '.github/**' | ||
|
|
||
| 'area:design': | ||
| - changed-files: | ||
| - any-glob-to-any-file: | ||
| - 'custom.css' | ||
| - 'assets/**' | ||
| - 'logo/**' | ||
| - 'favicon.svg' | ||
|
|
||
| 'area:pwa': | ||
| - changed-files: | ||
| - any-glob-to-any-file: | ||
| - 'manifest.webmanifest' | ||
| - 'pwa/**' | ||
|
|
||
| 'area:content': | ||
| - changed-files: | ||
| - any-glob-to-any-file: | ||
| - '**/*.mdx' | ||
| - '**/*.md' | ||
| # Scope out paths owned by other labels so a single PR doesn't | ||
| # collect three labels for the same change. | ||
| - all-globs-to-all-files: | ||
| - '!api-reference/**' | ||
| - '!specs/**' | ||
| - '!ar/**' | ||
| - '!es/**' | ||
| - '!hi/**' | ||
| - '!zh/**' | ||
| - '!.github/**' | ||
| - '!AGENTS.md' | ||
| - '!CONTRIBUTING.md' | ||
| - '!README.md' | ||
| - '!LICENSE' | ||
|
|
||
| 'area:meta': | ||
| - changed-files: | ||
| - any-glob-to-any-file: | ||
| - 'docs.json' | ||
| - 'AGENTS.md' | ||
| - 'CONTRIBUTING.md' | ||
| - 'README.md' | ||
| - 'LICENSE' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Copyright 2026 ResQ Software | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # i18n parity check — informational only. Reports MDX/MD files in the | ||
| # default-language tree that are missing from each locale (ar, es, hi, | ||
| # zh). Does not block merges; translations lag deliberately. | ||
|
|
||
| name: i18n parity | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - '**/*.mdx' | ||
| - '**/*.md' | ||
| - 'ar/**' | ||
| - 'es/**' | ||
| - 'hi/**' | ||
| - 'zh/**' | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| parity: | ||
| name: locale parity | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Compare locale trees | ||
| run: | | ||
| set -euo pipefail | ||
| locales=(ar es hi zh) | ||
| mapfile -t base < <( | ||
| find . -type f \( -name '*.mdx' -o -name '*.md' \) \ | ||
| -not -path './ar/*' \ | ||
| -not -path './es/*' \ | ||
| -not -path './hi/*' \ | ||
| -not -path './zh/*' \ | ||
| -not -path './.github/*' \ | ||
| -not -path './node_modules/*' \ | ||
| -not -name 'README.md' \ | ||
| -not -name 'AGENTS.md' \ | ||
| -not -name 'CONTRIBUTING.md' \ | ||
| | sed 's|^\./||' | sort | ||
| ) | ||
| echo "base files: ${#base[@]}" | ||
| { | ||
| echo "## i18n parity report" | ||
| echo | ||
| echo "Base tree: ${#base[@]} translatable file(s)" | ||
| echo | ||
| for locale in "${locales[@]}"; do | ||
| if [[ ! -d "$locale" ]]; then | ||
| echo "- \`$locale/\` — locale directory missing" | ||
| continue | ||
| fi | ||
| missing=0 | ||
| missing_list="" | ||
| for rel in "${base[@]}"; do | ||
| if [[ ! -f "$locale/$rel" ]]; then | ||
| missing=$((missing+1)) | ||
| missing_list+=" - \`$locale/$rel\`"$'\n' | ||
| fi | ||
| done | ||
| total=${#base[@]} | ||
| translated=$((total - missing)) | ||
| echo "- \`$locale/\` — $translated / $total translated ($missing missing)" | ||
| if (( missing > 0 )); then | ||
| echo | ||
| printf '%s' "$missing_list" | ||
| fi | ||
| done | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
| echo "report written to step summary" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # Copyright 2026 ResQ Software | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Auto-label PRs by changed paths. Rules live in .github/labeler.yml. | ||
| # Uses `pull_request_target` so forks get labeled too — this is safe | ||
| # because the action only reads file paths, never executes PR code. | ||
|
|
||
| name: labeler | ||
|
|
||
| on: | ||
| pull_request_target: | ||
| types: [opened, synchronize, reopened] | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| label: | ||
| name: label | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/labeler@v5 | ||
| with: | ||
| configuration-path: .github/labeler.yml | ||
| sync-labels: true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # Copyright 2026 ResQ Software | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # External link checker. Scheduled weekly + on-demand only — external | ||
| # link flake (rate limits, CDN hiccups) makes this unsuitable as a | ||
| # blocking PR gate. Internal MDX link integrity is enforced by | ||
| # `mint broken-links` in required.yml. | ||
|
|
||
| name: lychee | ||
|
|
||
| on: | ||
| schedule: | ||
| - cron: '23 7 * * 1' | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| links: | ||
| name: lychee (external links) | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Restore lychee cache | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: .lycheecache | ||
| key: cache-lychee-${{ github.sha }} | ||
| restore-keys: cache-lychee- | ||
|
|
||
| - name: Run lychee | ||
| uses: lycheeverse/lychee-action@v2 | ||
Check warningCode scanning / CodeQL Unpinned tag for a non-immutable Action in workflow Medium
Unpinned 3rd party Action 'lychee' step
Uses Step Error loading related location Loading |
||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged. lychee runs only on schedule + workflow_dispatch (not on PRs) per the workflow trigger config — CodeQL flags this as low-severity advisory rather than blocking. Dependabot is configured for the org and will SHA-pin both |
||
| with: | ||
| args: >- | ||
| --cache | ||
| --max-cache-age 1d | ||
| --no-progress | ||
| --exclude-mail | ||
| --accept 200,204,206,429 | ||
| --max-retries 2 | ||
| --retry-wait-time 5 | ||
| './**/*.md' | ||
| './**/*.mdx' | ||
| './docs.json' | ||
| fail: true | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,11 @@ | ||
| # Copyright 2026 ResQ Software | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Minimal `required` status-check emitter — placeholder to satisfy the | ||
| # org ruleset `default-branch-baseline` (id 15191038) while | ||
| # language-specific reusable CI for this repo is still pending. Harden | ||
| # this job (add `needs:` on real CI jobs) before the ruleset flips from | ||
| # evaluate to active. | ||
| # `required` status check — single aggregate gate referenced by the org | ||
| # ruleset (default-branch-baseline). Real CI jobs run in parallel, and | ||
| # the `required` job fails unless every dependency succeeds. Add new | ||
| # blocking docs jobs to `needs:` rather than a new workflow, so the | ||
| # protection contract stays one check. | ||
|
|
||
| name: required | ||
|
|
||
|
|
@@ -22,8 +22,84 @@ | |
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| mintlify: | ||
| name: mintlify (broken links) | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| - name: Install Mintlify CLI | ||
| run: npm install -g mint | ||
| - name: Check broken internal links | ||
| run: mint broken-links | ||
|
|
||
| spectral: | ||
| name: spectral (OpenAPI lint) | ||
| runs-on: ubuntu-latest | ||
| # Advisory: action ref hardening will be handled by Dependabot SHA-pin PRs. | ||
| continue-on-error: true | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Lint OpenAPI specs | ||
| uses: stoplightio/spectral-action@v0.8.13 # TODO: dependabot will SHA-pin | ||
Check warningCode scanning / CodeQL Unpinned tag for a non-immutable Action in workflow Medium
Unpinned 3rd party Action 'required' step
Uses Step Error loading related location Loading |
||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acknowledged. Same Dependabot SHA-pin path as the lychee comment. The |
||
| with: | ||
| file_glob: 'specs/*.json' | ||
|
|
||
| pwa-manifest: | ||
| name: pwa manifest | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Validate manifest.webmanifest | ||
| run: | | ||
| set -euo pipefail | ||
| f=manifest.webmanifest | ||
| if [[ ! -f "$f" ]]; then | ||
| echo "::notice::$f not on this branch — skipping validation (manifest lives in feat/brand-assets PR)" | ||
| exit 0 | ||
| fi | ||
| jq -e . "$f" > /dev/null || { echo "::error::$f is not valid JSON"; exit 1; } | ||
| for field in name short_name start_url display icons; do | ||
| if ! jq -e "has(\"$field\")" "$f" > /dev/null; then | ||
| echo "::error::manifest missing required field: $field" | ||
| exit 1 | ||
| fi | ||
| done | ||
| mapfile -t srcs < <(jq -r '.icons[].src' "$f") | ||
| missing=0 | ||
| for src in "${srcs[@]}"; do | ||
| path="${src#/}" | ||
| if [[ ! -f "$path" ]]; then | ||
| echo "::error::icon path not found on disk: $src" | ||
| missing=$((missing+1)) | ||
| fi | ||
| done | ||
| if (( missing > 0 )); then | ||
| echo "::error::$missing icon(s) missing" | ||
| exit 1 | ||
| fi | ||
| echo "manifest valid: ${#srcs[@]} icons resolved" | ||
|
|
||
| required: | ||
| name: required | ||
| needs: [mintlify, spectral, pwa-manifest] | ||
| if: always() | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - run: echo "ok — placeholder until language CI lands" | ||
| - name: Verify all gates passed | ||
| run: | | ||
| # spectral remains advisory (continue-on-error) until the action ref | ||
| # is verified post-Dependabot SHA-pin. | ||
| mintlify="${{ needs.mintlify.result }}" | ||
| spectral="${{ needs.spectral.result }}" | ||
| pwa="${{ needs.pwa-manifest.result }}" | ||
| echo "mintlify: $mintlify" | ||
| echo "spectral (advisory): $spectral" | ||
| echo "pwa-manifest: $pwa" | ||
| if [[ "$mintlify" != "success" || "$pwa" != "success" ]]; then | ||
| echo "::error::One or more required gates failed" | ||
| exit 1 | ||
| fi | ||
| echo "All required gates passed" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # Copyright 2026 ResQ Software | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Stale issue/PR closer. Issues idle for 60 days get the `stale` label | ||
| # and a nudge; closed 14 days later if still untouched. PRs are tighter | ||
| # (30 + 14). Pinned, security, and roadmap items are exempt. | ||
|
|
||
| name: stale | ||
|
|
||
| on: | ||
| schedule: | ||
| - cron: '12 1 * * *' | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| issues: write | ||
| pull-requests: write | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| stale: | ||
| name: stale | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/stale@v9 | ||
| with: | ||
| days-before-issue-stale: 60 | ||
| days-before-issue-close: 14 | ||
| days-before-pr-stale: 30 | ||
| days-before-pr-close: 14 | ||
| stale-issue-label: stale | ||
| stale-pr-label: stale | ||
| exempt-issue-labels: 'pinned,security,roadmap' | ||
| exempt-pr-labels: 'pinned,security' | ||
| stale-issue-message: | | ||
| This issue has been inactive for 60 days and is now marked stale. | ||
| It will be closed in 14 days unless there is new activity. | ||
| stale-pr-message: | | ||
| This PR has been inactive for 30 days and is now marked stale. | ||
| It will be closed in 14 days unless updated. Rebase or comment | ||
| to keep it open. | ||
| close-issue-message: Closed due to inactivity. Reopen if still relevant. | ||
| close-pr-message: Closed due to inactivity. Reopen and rebase to continue. | ||
| operations-per-run: 100 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
area:contentlabel is currently very broad and will overlap witharea:metaandarea:cibecause it matches all markdown files, including repository metadata (README.md,CONTRIBUTING.md) and CI-related files (such as issue templates in.github/). To reduce label noise and ensurearea:contentspecifically targets documentation site content, consider excluding these non-content paths using negative globs.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 6c9617c —
area:contentnow has anall-globs-to-all-filesexclusion list so it skipsapi-reference/,specs/,ar|es|hi|zh/,.github/, AGENTS/CONTRIBUTING/README/LICENSE. Single change per area now.