diff --git a/.github/workflows/ci-groom.yml b/.github/workflows/ci-groom.yml new file mode 100644 index 0000000..fd4408e --- /dev/null +++ b/.github/workflows/ci-groom.yml @@ -0,0 +1,82 @@ +name: CI - Groom + +# Thin caller enrolling THIS repo as the FIRST groom pilot (BE-3873 / Groom +# Phase 3). The groom logic (two-phase finder -> independent verifier audit, the +# durable dedup/rejection ledger, the GitHub-issue sink) lives in groom.yml in +# this same repo; the caller pins it by full commit SHA like every external +# consumer — a local `uses: ./...` path would run the PR's OWN copy of the +# auditor, letting a PR weaken the reviewer judging it. Named ci-groom.yml +# because the canonical caller path (groom.yml) is occupied here by the reusable +# itself (same reason as ci-cursor-review.yml / ci-detect-unreviewed-merge.yml). +# +# GROOMER-ONLY mode: groom only FINDS and FILES `groom`-labeled GitHub issues — +# no commits, no PRs, no auto-builder, never merges. It runs weekly on a schedule +# (never on-PR), plus a manual dispatch carrying a `dry_run` toggle so the audit + +# dedup can be spot-checked against a studio groom run on the same commit BEFORE +# trusting live filing (the Phase 3 dry-run parity gate). +# +# PREREQUISITE (operator): `secrets.ANTHROPIC_API_KEY` must be available to this +# repo (org- or repo-level) — the finder + verifier agents bill through it. Until +# it exists the run fails loudly at the agent step, deliberately, so the pilot is +# never silently inert. Filing uses the cloud-code-bot App identity (vars.APP_ID +# + secrets.CLOUD_CODE_BOT_PRIVATE_KEY — already provisioned here for +# cursor-review), so groom's issues are a distinct, queryable actor rather than +# github-actions[bot], and the caller needs no issues:write scope of its own. + +on: + schedule: + # Weekly, Monday 09:17 UTC. Off-peak + a non-round minute to dodge the + # top-of-hour GitHub scheduler congestion. Never fires on a PR. + - cron: '17 9 * * 1' + workflow_dispatch: + inputs: + dry_run: + description: >- + Run the full audit + dedup but do NOT open issues — print what WOULD be + filed to the job summary. Use it to spot-check parity against a studio + groom run on the same repo/commit before trusting live filing. + type: boolean + default: false + +# NOTE: intentionally NO caller-level `concurrency:` here. The reusable groom.yml +# already declares `concurrency: groom-${{ github.repository }}` (cancel-in- +# progress: false), which serializes runs per target repo across ALL callers — +# that is the real TOCTOU guard for the read-then-file dedup ledger. Duplicating +# the identical group in this caller would DEADLOCK the run: the caller holds the +# group while its reusable `uses:` job waits to acquire the SAME group with +# cancel-in-progress:false, so the reusable never gets the slot and the run hangs +# until timeout (GitHub Actions caller/callee same-group deadlock). Serialization +# lives in the reusable; the caller deliberately stays out of the group. + +jobs: + groom: + permissions: + # Least privilege: the App token (bot_app_id) does the issue I/O, so the + # caller's own github.token stays read-only. (Grant issues:write here ONLY + # if you drop bot_app_id and fall back to filing as github-actions[bot].) + contents: read + uses: Comfy-Org/github-workflows/.github/workflows/groom.yml@07154fb5d0d979017d79b822873cfbe86483bfb3 # main (groom.yml lands here — #49 / BE-3872) + with: + # File groom's issues as cloud-code-bot (App token), not github-actions[bot]. + bot_app_id: ${{ vars.APP_ID }} + # Keep the assets ref (finder/verifier briefs + dedup ledger) in lock-step + # with the `uses:` ref above, so a run always loads the briefs that match + # the workflow filing them. + workflows_ref: 07154fb5d0d979017d79b822873cfbe86483bfb3 + # Manual dispatch can dry-run; the weekly schedule always files live + # (github.event.inputs is null on a schedule event -> '' != 'true' -> false). + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + # Match the volume-gate window to this caller's ~weekly cron, so a week + # with nothing merged skips the (expensive) audit. + cadence: 7 + # A dry-run is the Phase 3 parity spot-check, so it must ALWAYS exercise the + # full audit + dedup — bypass the volume gate when dispatched with + # dry_run:true. Otherwise a quiet week (nothing merged in `cadence` days) + # would gate the dry-run to nothing and print no parity summary, defeating + # the dispatch's whole purpose. Live runs (the weekly schedule, where inputs + # is null -> '' != 'true' -> true, or a dry_run:false dispatch) keep the gate + # on so a quiescent repo still skips the spend. + volume_gate: ${{ github.event.inputs.dry_run != 'true' }} + secrets: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + BOT_APP_PRIVATE_KEY: ${{ secrets.CLOUD_CODE_BOT_PRIVATE_KEY }}