verify-onboarding.yml

Re-verifies onboarded repos still report the required check, and opens one tracking issue on drift. Active — weekly, Tuesday 06:00 UTC, plus workflow_dispatch.

What this is

Re-verifies every onboarded repo weekly and opens one tracking issue on drift — a ruleset edited by hand, a renamed caller job, push protection turned off, artifacts left stale by an ignored sync pull request.

How to onboard it

It lives in this repository and is already here. It verifies the repositories in registry.json, so an estate with an empty register produces no issue and nothing to read.

  • Lives in: This (source) repo.
  • Trigger: Weekly, Tuesday 06:00 UTC, plus workflow_dispatch with an only <owner/name> override.

How to use it

Nothing, normally — it runs itself weekly. Use the only <owner/name> input to check one repository on demand, or run `redline verify --repo owner/name` yourself; neither needs a checkout of the target.

What a run does, in order:

  • Loops over registry.json (or one only <owner/name> override) calling `redline verify --repo` for each. The loop deliberately does not abort on a failure: a drifted repository exits non-zero by design, and stopping at the first one would leave the rest of the estate unverified every week.
  • Opens or updates a single tracking issue naming every repo that drifted, with the failing checks quoted. One issue updated in place, never one per run — a new issue per run turns a standing problem into a backlog nobody reads.

Active. GitHub only — Azure DevOps remote verification is outstanding, and an Azure entry in the register is reported as unsupported rather than skipped silently.

Expected output

A single tracking issue naming every repository that drifted, with its failing checks quoted — opened once and updated in place. A check that could not run appears as ?? and never fails a repository on its own: failing on the absence of evidence trains an operator to ignore the weekly issue, which costs more than the check is worth. No drift means no issue and no comment.

How to edit it

  1. Edit the YAML in workflows/ or .github/workflows/workflows/ holds files destined for other repositories; .github/workflows/ is this repository's own CI. The two are not interchangeable — check where this one lives before editing.
  2. actionlintCI lints .github/workflows/*.yml, workflows/*.yml and templates/redline.yml together. workflows/ is pointed at explicitly because actionlint's own discovery would skip it.
  3. node scripts/check-pins.mjsIf you add a third-party action, pin it to a 40-character commit SHA with a trailing # vX.Y.Z comment. First-party actions/* are referenced by tag. The pin checker re-resolves the SHA against the tag the comment claims.
  4. node scripts/validate.mjsAsserts the workflow files the bundle depends on still exist, and that the gate's job ids still match the required check name derived from them.

The full file

workflows/verify-onboarding.yml · 116 lines · 4.0 KB
# Lives in the Redline source repo. Re-verifies every onboarded repo on a schedule and
# opens one tracking issue when any of them has drifted — a ruleset edited by hand, a
# renamed caller job, push protection switched off, artifacts left stale by an ignored
# sync pull request.
#
# Drift is silent by nature: a repository whose gate stopped publishing looks exactly
# like one whose gate is green and quiet. This loop is what makes the difference visible
# without anyone going to look.
#
# Targets come from registry.json — the same derived register sync uses. A check that
# genuinely needs a working tree reports `??` rather than passing, so a repository is
# never reported healthy on the strength of a check that did not run.
name: Redline Verify Onboarding

on:
  schedule:
    - cron: '0 6 * * 2'
  workflow_dispatch:
    inputs:
      only:
        description: Verify a single repo, as owner/name
        type: string
        required: false

permissions:
  contents: read
  issues: write

concurrency:
  group: redline-verify-onboarding
  cancel-in-progress: false

jobs:
  verify:
    runs-on: ubuntu-latest
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Install
        run: npm ci

      - name: Build CLI
        run: npm run build

      - name: Verify every registered repository
        id: verify
        env:
          GH_TOKEN: ${{ secrets.REDLINE_ORG_READ_TOKEN }}
          ONLY: ${{ inputs.only }}
        run: |
          set -uo pipefail
          if [ ! -f registry.json ]; then
            echo "::error::registry.json is missing — the register has not been derived yet."
            exit 1
          fi

          if [ -n "$ONLY" ]; then
            repos="$ONLY"
          else
            # String concatenation rather than a template literal: `${...}` inside
            # single quotes is ambiguous to a reader and to shellcheck, which
            # cannot know the braces are JavaScript's. Avoiding the construct
            # beats suppressing the warning about it.
            repos=$(node -e 'const r=require("./registry.json");for(const e of r.entries)if(e.host==="github")console.log(e.org+"/"+e.repo)')
          fi

          : > drift.txt
          # `set -e` is deliberately NOT on for this loop: a drifted repository
          # exits non-zero by design, and aborting on the first one would leave
          # the rest of the estate unverified every week.
          while read -r repo; do
            [ -z "$repo" ] && continue
            echo "--- $repo"
            if ! node dist/bin/redline.js verify --repo "$repo" 2>&1 | tee /tmp/out.txt; then
              {
                echo "### $repo"
                echo '```'
                grep -E '^(FAIL|  \?\?)' /tmp/out.txt || cat /tmp/out.txt
                echo '```'
              } >> drift.txt
            fi
          done <<< "$repos"

          if [ -s drift.txt ]; then
            echo "drift=true" >> "$GITHUB_OUTPUT"
          else
            echo "drift=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Open or update the drift issue
        if: steps.verify.outputs.drift == 'true'
        env:
          GH_TOKEN: ${{ github.token }}
          TITLE: 'Redline: onboarded repositories have drifted'
        run: |
          set -euo pipefail
          # One issue, updated in place. A new issue per run turns a standing
          # problem into a backlog nobody reads.
          body=$(cat drift.txt)
          existing=$(gh issue list --state open --search "$TITLE in:title" --json number --jq '.[0].number // empty')
          if [ -n "$existing" ]; then
            gh issue comment "$existing" --body "$body"
          else
            gh issue create --title "$TITLE" --body "$body"
          fi

      - name: Summarise
        if: always()
        run: |
          if [ -s drift.txt ]; then cat drift.txt >> "$GITHUB_STEP_SUMMARY";
          else echo "No drift detected." >> "$GITHUB_STEP_SUMMARY"; fi