Files
Jordan Ritter 1fbe44f983 feat(ci): daily GHCR unlinked-package audit + Slack alert (#4304)
## Summary

Adds `.github/workflows/ghcr_unlinked_packages.yml` — a scheduled
GitHub Actions workflow that audits all CopilotKit org container
packages daily and Slack-alerts when any are unlinked from a source
repository (`repository: null` on the GHCR API).

## Why

When a GHCR container package is unlinked from a repo, workflow
builds in `CopilotKit/CopilotKit` get `403 Forbidden` on push to GHCR
— the workflow `GITHUB_TOKEN` only has package-write permissions
when the package is linked to the actor's repo. We hit this twice in
quick succession:

- `showcase-ops` — caught manually after a failed deploy
- `showcase-pocketbase` — caught by a preemptive scan

There is **no GitHub API to programmatically link a package to a
repo** — it is UI-only. So the only way to prevent future surprises
is to detect drift early via a scheduled audit + Slack alert.

## Behavior

- Runs daily at 14:00 UTC, plus `workflow_dispatch` for ad-hoc runs.
- Lists every container package in the `CopilotKit` org via
  `gh api /orgs/CopilotKit/packages?package_type=container`.
- Filters for `repository == null`.
- If any are unlinked, posts a Slack message that includes the count,
  a bulleted list with deep links to each package's settings page,
  the exact UI fix steps, and a footer noting the failure mode.
- Exits 0 in all non-error cases. The Slack message IS the alert;
  failing the workflow on drift would create noisy red CI checks.

## Required setup before this workflow can fire

Two new repo secrets must be added:

1. **`ORG_READ_PACKAGES_PAT`** — a fine-grained PAT with
   `read:packages` scope, org-scoped to `CopilotKit`. The default
   `secrets.GITHUB_TOKEN` does NOT have org-package-list scope. The
   workflow fails loudly if this is missing.

2. **`SLACK_WEBHOOK_GHCR_DRIFT`** — a CopilotKit-internal Slack
   incoming-webhook URL for an alerts channel. If missing, the audit
   still runs and logs a warning; only the Slack post is skipped.

Without these secrets the workflow will either fail loudly (PAT) or
log-only (webhook). It will not silently mask drift.

## Test plan

- [ ] Add `ORG_READ_PACKAGES_PAT` and `SLACK_WEBHOOK_GHCR_DRIFT` repo
secrets.
- [ ] Trigger via `workflow_dispatch` on `main` post-merge.
- [ ] Confirm the audit lists packages and reports the unlinked count.
- [ ] If drift exists, confirm Slack receives the alert with working
deep links.
- [ ] If no drift, confirm workflow exits 0 with a "no drift" log line
and skips the Slack post.
2026-04-26 18:04:43 -07:00
..