How FFC Automation Works
Free For Charity runs its infrastructure β Cloudflare DNS, WHMCS billing, Microsoft 365, Zeffy donations, Google Analytics, and the GitHub sites themselves β through 102 auditable GitHub Actions workflows. This page is the living catalog: what each workflow does, how they are numbered and named, and the safety model that protects every run. It is generated from the workflows themselves, so it cannot drift.
The numbering convention β and why
Every workflow has a 3-digit number whose first digit is the system the workflow targets. A new admin (or an AI agent) can tell from the number alone which external API a workflow drives, and new workflows can never collide β each category has its own numbering space.
Naming: workflows are named NNN. Target - Description [TAG] and their files are NNN-slug.yml. The [TAG] lists every API the workflow calls, joined with + (for example [CF+M365]).
What counts as βan APIβ: the API the workflow actually calls β not the service the records are for (creating Microsoft 365 DNS records via the Cloudflare API is [CF]), and not plumbing (fetching credentials from Azure Key Vault, or posting a result comment to GitHub, never counts).
Multi-system workflows keep one number from the system that owns the deliverable (writes beat reads). They exist as single workflows only when they are aggregators (a report that joins several sources) or transactions (two writes that must succeed together to avoid a half-configured state).
The safety model
A live change generally has to get past several independent layers, not just one:
- Read vs. write credentials. Read-only workflows load reader-scope credentials and cannot mutate anything; write workflows load writer-scope credentials.
- Environment approval gates. Write environments (and some sensitive read environments) require a human reviewer to approve the run before it proceeds.
dry_rundefaults to preview. Write workflows preview what they would change; going live requires explicitly passingdry_run=false.- Typed confirmation for the highest-stakes actions (for example, domain registration requires retyping the exact domain).
- No stored secrets. Credentials are fetched from Azure Key Vault at run time via OIDC and masked line-by-line; nothing sensitive lives in the repository.
The complete catalog
Generated from the workflow files in FreeForCharity/FFC-Cloudflare-Automation. Safety levels: Reads = no external mutation; Writes (dry-run default) = mutates only with dry_run=false; Writes (gated) = mutates when run, protected by an approval gate.
Cloudflare / DNS / Domain
23 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 101 | Domain - Status (All Sources) 101-domain-status.yml | CFM365 | Reads M365 job reads FFC-tenant status; waits on m365-prod approval | workflow_dispatch |
| 102 | Domain - Add to FFC Cloudflare + WHMCS Nameservers (Admin) 102-domain-add-ffc-cloudflare-and-whmcs.yml | CFWHMCS | Writes (gated) β | workflow_dispatch |
| 103 | Domain - Enforce Standard (GitHub Apex + M365) 103-enforce-domain-standard.yml | CFM365 | Writes (dry-run default) `dry_run` (default true); exo jobs act on FFC tenant β `skip_m365=true` for external-tenant charities | workflow_dispatch |
| 104 | Domain - Export Inventory (All Sources) 104-domain-export-inventory.yml | CFM365WHMCSWPMUDEV | Reads waits on m365-prod and wpmudev-prod approvals (WHMCS job ungated) | workflow_dispatch |
| 105 | DNS - Manage Record (Manual / Issue Label) 105-manage-record.yml | CF | Writes (dry-run default) `dry_run` (default true); issue-label trig | issues, workflow_dispatch |
| 106 | DNS - Enforce Standard (DNS-only) 106-enforce-standard.yml | CF | Writes (dry-run default) `dry_run` (default true) | workflow_dispatch |
| 107 | DNS - Audit Compliance (Report) 107-audit-compliance.yml | CF | Reads β | workflow_dispatch |
| 108 | DNS - Export Cloudflare Zones (Report) 108-export-summary.yml | CF | Reads β | workflow_dispatch |
| 109 | DNS - Export All Records (Full, per-record) 109-dns-export-all-records.yml | CF | Reads β | workflow_dispatch |
| 110 | DNS - Create Zone (Admin) 110-cloudflare-zone-create.yml | CF | Writes (gated) β | workflow_dispatch |
| 111 | DNS - Create Redirect Rule (Admin) 111-dns-create-redirect-rule.yml | CF | Writes (dry-run default) `dry_run` (default true) | workflow_dispatch |
| 112 | DNS - Bulk Replace A-record IP (All Zones) 112-dns-bulk-replace-a-ip.yml | CF | Writes (gated) high blast radius; serialized | workflow_dispatch |
| 113 | Domain - Registrar Search / Check / Register (Admin, DRAFT) 113-cloudflare-domain-register.yml | CF | Writes (gated) `mode` (default `check`); register needs `mode=execute-register` + `confirm_domain` | issues, workflow_dispatch |
| 114 | Domain - Validate Cloudflare Registrar API Access (Read-only) 114-cloudflare-registrar-access-check.yml | CF | Reads never charges; but `cloudflare-tokens-from-kv` runs with **`scope: write`** β loads `wr-all-*` CF tokens, so **not auto-approvable** (#915) | workflow_dispatch |
| 115 | Domain - Transfer Readiness Preflight (Report) 115-domain-transfer-preflight.yml | WHMCS | Reads β | workflow_dispatch |
| 116 | Domain - Transfer EPP/Auth Code Probe (Admin) 116-domain-transfer-epp-probe.yml | WHMCS | Writes (dry-run default) `dry-run` vs `execute` | workflow_dispatch |
| 117 | Domain - Post-Transfer Verification (Report) 117-domain-transfer-verify.yml | CF | Reads β | workflow_dispatch |
| 118 | Domain - Registrar Lock / Unlock 118-whmcs-domain-lock.yml | WHMCS | Writes (dry-run default) `dry_run` (default true) | workflow_dispatch |
| 119 | DNS - Bulk Staging CNAME -> GitHub Pages (FFC-EX) 119-bulk-staging-cname-github-pages.yml | CF | Writes (dry-run default) `dry_run` (default true); serialized | workflow_dispatch |
| 120 | DNS + GH Pages - Bulk Cutover staging -> Apex (FFC-EX) 120-bulk-cutover-to-github-pages.yml | CFGH | Writes (dry-run default) `dry_run` (default true); serialized | workflow_dispatch |
| 121 | DNS + GH Pages - Fleet Cutover Preflight (FFC-EX) 121-fleet-cutover-preflight.yml | Reads read-only DoH/HTTPS probes; go/no-go for 120 | workflow_dispatch | |
| 122 | Cloudflare - Zone Member Add (Domain Admin) 122-cloudflare-zone-member-add.yml | CF | Writes (dry-run default) `dry_run` (default true); preview job always runs read-only; idempotent (existing member = no change) | workflow_dispatch |
| 123 | Domain - Inbound Transfer Preflight (Report) 123-domain-inbound-transfer-preflight.yml | Reads read-only RDAP + offline analysis; classifies foreign-registrar domains for the eNom/WHMCS inbound path | workflow_dispatch |
WHMCS
30 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 201 | WHMCS - Export Domains (Report) 201-whmcs-export-domains.yml | WHMCS | Reads ungated read lane; artifacts are export catalogs | workflow_dispatch |
| 202 | WHMCS - Export Products (Report) 202-whmcs-export-products.yml | WHMCS | Reads ungated read lane; artifacts are export catalogs | workflow_dispatch |
| 203 | WHMCS - Export Payment Methods (Research) 203-whmcs-export-payment-methods.yml | WHMCS | Reads ungated read lane; artifacts are export catalogs | workflow_dispatch |
| 204 | WHMCS - Charity Onboard (client + contacts + order) 204-whmcs-charity-onboard.yml | WHMCS | Writes (dry-run default) `dry_run` (default true); idempotent | workflow_dispatch |
| 205 | WHMCS - Open Ticket (manual) 205-whmcs-ticket-open.yml | WHMCS | Writes (gated) one-way GitHubβWHMCS | workflow_dispatch |
| 206 | WHMCS - Issue to Ticket (one-way) 206-whmcs-issue-to-ticket.yml | WHMCS | Writes (gated) one-way GitHubβWHMCS | issues |
| 207 | WHMCS - Ticket Respond (templated) 207-whmcs-ticket-respond.yml | WHMCS | Writes (dry-run default) `dry_run` (default true); live reply needs admin username | workflow_dispatch |
| 208 | WHMCS - Export Tickets (Report) 208-whmcs-tickets-export.yml | WHMCS | Reads β | workflow_dispatch |
| 209 | WHMCS - Tickets Triage (Open/Customer-Reply) 209-whmcs-tickets-triage.yml | WHMCS | Reads summary masks PII | schedule, workflow_dispatch |
| 210 | WHMCS - Orders Triage (Pending/Fraud/Active) 210-whmcs-orders-triage.yml | WHMCS | Reads summary masks PII | schedule, workflow_dispatch |
| 211 | WHMCS - Order Update (accept/cancel/fraud) 211-whmcs-order-update.yml | WHMCS | Writes (dry-run default) `dry_run` (default true); one order at a time | workflow_dispatch |
| 212 | WHMCS - Product Add (catalog) 212-whmcs-product-add.yml | WHMCS | Writes (dry-run default) `dry_run` (default true); idempotent | workflow_dispatch |
| 213 | WHMCS -> Zeffy Payments Import (Draft) 213-whmcs-zeffy-payments-import-draft.yml | WHMCS | Reads (builds a file) output is a draft; serialized | workflow_dispatch |
| 214 | WHMCS - Clients Metrics (aggregate, no PII) 214-whmcs-clients-metrics.yml | WHMCS | Reads aggregate counts only β no PII in artifact or summary | workflow_dispatch |
| 215 | WHMCS - Nonprofit Clients Metrics (service evidence, no PII) 215-whmcs-nonprofit-clients-metrics.yml | WHMCS | Reads aggregate counts + product names only (no PII) | workflow_dispatch |
| 216 | WHMCS - Activity Metrics (full history, no PII) 216-whmcs-activity-metrics.yml | WHMCS | Reads aggregate counts only (no PII) | workflow_dispatch |
| 217 | WHMCS - Client Fields Survey (aggregate, no PII) 217-whmcs-client-fields-survey.yml | WHMCS | Reads throttled per-client loop; vocabulary-matched values only (no PII) | workflow_dispatch |
| 218 | WHMCS - Sites-List Reconciliation + Product Alignment 218-whmcs-siteslist-reconciliation.yml | WHMCS | Reads aggregate summary; artifact carries org-domain gap lists only | workflow_dispatch |
| 219 | WHMCS - Application Detail (Client + Orders, PII-masked) 219-whmcs-application-detail.yml | WHMCS | Reads single-client projection; personal contact fields always masked | workflow_dispatch |
| 220 | WHMCS - Served-Per-Year Metrics (span evidence, no PII) 220-whmcs-served-metrics.yml | WHMCS | Reads aggregate counts only (no PII) | workflow_dispatch |
| 221 | WHMCS - Application Search (find by domain/org) 221-whmcs-application-search.yml | WHMCS | Reads GetClientsProducts sweep; personal contact fields masked; **ungated** β `whmcs-secrets-from-kv` with `scope: read` on the `READ_ALL_FFC_AZURE_*` (ffc-admin-kv-reader) identity β `read-all-ffc-whmcs-*` (#920; was β
`whmcs-prod` on the writer identity, held under #915) | workflow_dispatch |
| 222 | WHMCS - Cloudflare Registrar Product Alignment 222-whmcs-product-alignment.yml | WHMCSCF | Writes (dry-run default) dry_run default true; $0 AddOrder of pid 39 onto registrar-domain clients; idempotent | workflow_dispatch |
| 223 | WHMCS - Import Cloudflare Registrar Domains as Domain Records 223-whmcs-import-cloudflare-domains.yml | WHMCSCF | Writes (dry-run default) dry_run default true; creates/populates domain records (registrar None) from CF facts | workflow_dispatch |
| 224 | WHMCS - GitHub Pages Product Alignment 224-whmcs-github-pages-product-alignment.yml | WHMCSGH | Writes (dry-run default) dry_run default true; $0 AddOrder of pid 40 onto FFC-EX GitHub-Pages-site clients; idempotent | workflow_dispatch |
| 225 | WHMCS - Domain Order URL Verify 225-whmcs-domain-order-url-verify.yml | WHMCS | Reads report-only; GETs charity-supplied URLs; never accepts/cancels an order | schedule, workflow_dispatch |
| 226 | WHMCS - Application Triage (rank + accept onboarding orders) 226-whmcs-application-triage.yml | WHMCS | Writes (report default) report + reconcile-report read-only; approve accepts ONLY listed $0 orderids; reconcile cancels ONLY listed orderids + emails those clients a re-file email (email-first, no cancel-all) | workflow_dispatch |
| 227 | cPanel - Deploy WHMCS Hooks (FTPS/lftp) 227-whmcs-hooks-deploy.yml | cPanel | Writes (verify default) mode=verify (read-only) vs deploy; hooks input is basename-validated | workflow_dispatch |
| 228 | WHMCS - Fraud Review (FraudLabs Pro) 228-whmcs-fraud-review.yml | FRAUDLABSWHMCS | Writes (issues only) read-only external APIs; masks PII; recommends only (never clears β clearing is via gated 211); scaffold inert until the FraudLabs Pro key + fraudlabspro-prod-read env are provisioned; daily weekday schedule upserts one rolling GitHub tracking issue (issues:write, ambient token) that self-closes when the Fraud queue clears | schedule, workflow_dispatch |
| 230 | WHMCS - Record Field Set (client/contact/service/domain) 230-whmcs-record-field-set.yml | WHMCS | Writes (record field) `dry_run` (default true); one record + one field per dispatch; strict per-target allowlist of writable fields; refuses to replace a different existing value without `force`; reports `previousValue` | workflow_dispatch |
| 231 | WHMCS - Domain Order Add (Register/Transfer) (Admin) 231-whmcs-domain-order-add.yml | WHMCS | Writes (dry-run default) `mode` (default `dry-run`) previews AddOrder and writes nothing; `execute` places a real order; pid fixed by `order_type` (41 transfer / 39 register); underlying script skips when the client already has a non-terminated service for the product unless `allow_duplicate`; `no_email` defaults true so a first live run cannot surprise a charity; price forced to 0 | workflow_dispatch |
Microsoft (M365 / Azure / Graph)
8 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 301 | M365 (FFC Tenant) - Domain Preflight (Read-only) 301-m365-domain-preflight.yml | M365CF | Reads M365 job waits on m365-prod approval | workflow_dispatch |
| 302 | M365 (FFC Tenant) - List Tenant Domains 302-m365-list-domains.yml | M365 | Reads waits on m365-prod approval | workflow_dispatch |
| 303 | M365 (FFC Tenant) - Domain Status + DKIM (Toolbox) 303-m365-domain-and-dkim.yml | M365 | Reads read-oriented toolbox; waits on m365-prod approval | workflow_dispatch |
| 304 | M365 (FFC Tenant) - Enable DKIM (Exchange Online) 304-m365-dkim-enable.yml | M365CF | Writes (gated) β | workflow_dispatch |
| 305 | M365 (FFC Tenant) - Add Tenant Domain (INTERNAL ONLY) 305-m365-add-tenant-domain.yml | M365 | Writes (dry-run default) `dry_run` (default true); also gated by m365-prod approval | workflow_dispatch |
| 306 | Discover - Uncaptured Comms (FFC Tenant M365, PII masked) 306-discover-uncaptured-comms.yml | M365 | Reads PII masked; dispatch-only; org mailboxes only; waits on m365-prod approval | workflow_dispatch |
| 320 | Azure - Key Vault Secret Inventory (audit) 320-azure-kv-secret-inventory.yml | MS | Reads values never printed; placeholder/stale flags only | schedule, workflow_dispatch |
| 321 | Azure - KV Credential Liveness + Expiry Monitor 321-azure-kv-credential-liveness.yml | MSGHCF | Reads daily two-signal credential health check: expiry/enabled for every vault secret from the attribute listing (values never read), plus a read-only liveness probe (`GET /user`, Cloudflare `GET /zones`) for the four `read-all-*` credentials that can verify themselves β the library refuses a `wr-all-*` probe target before anything is read, so write-scope material is expiry-monitored without ever being fetched; a probe that cannot be evaluated is reported as unverifiable, never as passing; values never printed or persisted; rolling issue upsert/close; ungated by design (a credential monitor must not wait on a human) | schedule, workflow_dispatch |
Zeffy
3 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 401 | Zeffy - Campaigns Export 401-zeffy-campaigns-export.yml | ZEFFY | Reads (+ PR delivery) PII masked; the published `docs/data/ffc-zeffy-campaigns.json` is title/url/status only (no financials); `deliver` opens a reviewable data PR via CBM_TOKEN; loads `wr-all-ffc-zeffy-api-key` (default `write`) **and** `wr-all-cbm-github-pat` β **not auto-approvable** (#915; on the β
list 2026-07-23 β 2026-07-29) | workflow_dispatch |
| 402 | Zeffy - Payments Export (PII masked) 402-zeffy-payments-export.yml | ZEFFY | Reads PII masked; never `-IncludePii` | workflow_dispatch |
| 403 | Zeffy - Contacts Export (PII masked) 403-zeffy-contacts-export.yml | ZEFFY | Reads PII masked; never `-IncludePii` | workflow_dispatch |
| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 501 | Google - API Smoke (GA4 connectivity) 501-google-api-smoke.yml | GOOGLE | Reads read-only; fails closed; reusable via `workflow_call` | workflow_call, workflow_dispatch |
| 502 | Google - Analytics Report (GA4 -> JSON) 502-google-analytics-report.yml | GOOGLE | Reads delivers JSON to ffcadmin via PR (`read-all-cbm-github-pat` from KV, ungated lane #834); PII-safe aggregates. Same PR also syncs the workflow catalog and the Agentic OS status feed (`agentic-os-status.json`, generated by `scripts/generate-agentic-os-status.py`, REST-only) | schedule, workflow_dispatch |
| 503 | Google - GTM Provision (per-charity container) 503-google-gtm-provision.yml | GOOGLE | Writes (dry-run default) dry_run default true; seeds GA4/Clarity/Meta; delegates POC access | workflow_dispatch |
| 504 | Google - GTM Container Backups (weekly export) 504-google-gtm-backup.yml | GOOGLE | Reads read-only exports; live-version JSON artifacts (90d) | schedule, workflow_dispatch |
| 505 | Google - GA4 Property Provision (per-charity) 505-google-ga-property-provision.yml | GOOGLE | Writes (dry-run default) dry_run default true; one property per charity; idempotent by stream defaultUri | workflow_dispatch |
| 506 | Google - Fleet Telemetry Reachability 506-google-fleet-telemetry-reachability.yml | GOOGLE | Reads read-only; enumerates GTM containers, GA4 properties/streams and Search Console sites, cross-checks them against each site's SERVED HTML, and ranks traffic. Reports "no data" distinctly from "no traffic" so an unmeasured site never sorts as unpopular. Weekly Mon 07:23 + dispatch | schedule, workflow_dispatch |
WPMUDEV
1 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 601 | WPMUDEV - Export Sites/Domains (Read-only) 601-wpmudev-export-sites.yml | WPMUDEV | Reads waits on wpmudev-prod approval | workflow_dispatch |
GitHub (Website + Repo)
29 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 701 | Website - Provision (Issue Assigned) 701-website-provision.yml | CFRepo | Writes (gated) `repo` chained behind `dns` approval | issues |
| 702 | Domain - Deploy Static Clone to FFC-EX Repo 702-ffc-ex-clone-deploy.yml | Writes (gated) ungated preflight (repo must exist; refuses live sites and sibling-domain repos unless `force=true`); opens a draft PR (never pushes); serialized | workflow_dispatch | |
| 703 | Sites List - Generate (CSV + JSON) 703-sites-list-generate.yml | GH | Writes (data PR only) dispatches read-only exports 201/108/601 (601 waits on its own wpmudev-prod gate); regenerates `sites-list/` CSV+JSON; opens a data PR (never pushes to `main`); weekly Mon 08:00Z + dispatch; serialized concurrency group; loads `wr-all-cbm-ffc-copilot-mcp-github-pat` β **held on credential scope**, not on a taxonomy gap (#915) | schedule, workflow_dispatch |
| 704 | Website - Analytics Wire 704-website-analytics-wire.yml | GH | Writes (dry-run default) opens a draft PR (never pushes); `dry_run` (default true); idempotent | workflow_dispatch |
| 720 | Repo - Create GitHub Repo 720-create-repo.yml | Repo | Writes (gated) dispatch-only; creates an FFC-EX repo from a template (no dry_run β gate is the only stop); duplicate-repo warning text in inputs; visibility/Pages options set at creation | workflow_dispatch |
| 721 | Repo - Deploy GitHub Pages 721-deploy-pages.yml | Repo | plumbing | push, workflow_dispatch |
| 722 | Repo - CI Validate and Test 722-ci.yml | Repo | plumbing | merge_group, pull_request, push |
| 723 | Repo - CodeQL Security Analysis 723-codeql-analysis.yml | Repo | plumbing | merge_group, pull_request, push, schedule, workflow_dispatch |
| 724 | Repo - Initialize Labels 724-initialize-labels.yml | Repo | plumbing | workflow_dispatch |
| 725 | Repo - Sync Labels 725-sync-labels.yml | Repo | plumbing | push, workflow_dispatch |
| 726 | Repo - Rulesets + Settings Drift Audit 726-repo-rulesets-drift-audit.yml | Org | Reads report only; `read-all-cbm-ffc-copilot-mcp-github-pat` from KV over OIDC; ungated lane (#834) so the daily audit is never cancelled waiting at a gate | schedule, workflow_dispatch |
| 727 | Repo - Phantom Revert Guard 727-phantom-revert-guard.yml | Repo | plumbing | merge_group, pull_request, workflow_dispatch |
| 728 | Repo - AI Agent Hooks Validate 728-ai-agent-hooks-validate.yml | Repo | plumbing | pull_request, push |
| 729 | Repo - Add Collaborator 729-repo-add-collaborator.yml | Repo | Writes (**live default**) β οΈ `dry_run` defaults to **false** | workflow_dispatch |
| 730 | Repo - Audit Environment Approval Gates 730-repo-audit-environment-gates.yml | Repo | Reads report only (environment reviewer config) | push, workflow_dispatch |
| 731 | Repo - Actions Run Metrics (30d per-workflow stats) 731-actions-run-metrics.yml | GH | Reads GITHUB_TOKEN read-only; JSON artifact | schedule, workflow_dispatch |
| 733 | Repo - Credential Rotation Reminders (quarterly) 733-credential-rotation-reminders.yml | GH | Writes (issues only) quarterly reminder issues; rotations stay human/gated | schedule, workflow_dispatch |
| 734 | Repo - Stale Waiting-Run Janitor 734-stale-waiting-run-janitor.yml | Repo | Writes (cancels runs) cancels runs left waiting >N days at a gate; never approves; dispatch dry-run supported | schedule, workflow_dispatch |
| 735 | Repo - Dependabot Affected Repos 735-repo-dependabot-affected-repos.yml | Org | Reads weekly org inventory (feeds smoke-protected waves); PR-only + auto-merge; `read-all-cbm-github-pat` from KV over OIDC on the ungated lane (#834) | schedule, workflow_dispatch |
| 736 | Repo - Archive / Application Denied (Admin) 736-repo-archive.yml | Repo | Writes (dry-run default) ungated preflight (live archive requires a matching successful dry-run within 48h; fails fast on missing/already-archived; warns on recent push/Pages/open-issue references); archive-only (reversible, never deletes); `dry_run` default true; typed `confirm_repo`; org-locked + denylist | workflow_dispatch |
| 737 | Repo - Claim Sync 737-claim-sync.yml | Repo | Writes (issues/labels only) syncs `claimed` label from linked PRs (pull_request event, GITHUB_TOKEN) + daily sweep reconciling this repo's claims against every open PR in the org β one `search/issues` read, writes only this repo's issues (ambient GITHUB_TOKEN β CBM_TOKEN is gated-env-only and empty on schedule); releases when no open PR in any repo still references the issue, or after 48h idle for a hand-labeled claim; no external API, ungated; sweep `dry_run` via dispatch | pull_request, schedule, workflow_dispatch |
| 738 | Repo - Fleet Smoke Engine Drift Audit 738-fleet-smoke-engine-drift-audit.yml | Org | Reads weekly SHA-256 byte-identity audit of `post-deploy-smoke.yml` across the fleet vs canonical (FFC-IN-FFC_Single_Page_Template@main); rolling issue upsert/close on divergence; GITHUB_TOKEN (public reads + own-repo issue), no external API, ungated | schedule, workflow_dispatch |
| 739 | Repo - Process Health Metrics Report 739-process-health-metrics.yml | GH | Reads weekly "monitor the monitors" REST sweep (backlog + data-pipeline signals) posted as a new comment on #719 with week-over-week trends (baseline read from the prior comment's hidden data block); JSON artifact; GITHUB_TOKEN (own-repo issue/Actions reads + one comment), no external API, ungated | schedule, workflow_dispatch |
| 740 | Repo - Scheduled Workflow Failure Alert (rolling issue) 740-scheduled-workflow-failure-alert.yml | GH | Writes (issues only) **polls** every scheduled hub workflow twice hourly (`schedule` + `workflow_dispatch`; the `workflow_run` event has never fired in this repo β #843) and upserts one rolling issue per watched workflow (marker keyed by workflow name, so only that workflow's own green run closes it); absorbs the retired 732 Google lane (502/504); reports `cancelled`/`timed_out` as well as `failure`, latest completed default-branch run only; does **not** alert on a declined/expired approval gate (no job `failure` + β₯1 job `cancelled` β logged via `core.notice`, not an alert) β an unreadable job list still alerts, and a watched name matching no workflow fails the run loudly; ambient GITHUB_TOKEN, no external API, ungated by design (must not be blocked by the gate it watches) | schedule, workflow_dispatch |
| 741 | Repo - Fleet Security Audit Coverage 741-fleet-security-audit-coverage.yml | Org | Reads weekly coverage audit of dependency-vulnerability detection across the FFC-EX fleet (a repo counts as covered only with BOTH `security-audit.yml` and an `audit:high` script) PLUS a lockfile-resolution check β real `npm ci --dry-run` per Node repo, since a lockfile that exists but does not resolve makes the nightly audit scan nothing (#889, ledger L06); rolling issue upsert/close on any gap; reports only β never bumps a dependency or writes to a fleet repo; GITHUB_TOKEN (public reads + own-repo issue), npm registry read-only, ungated | schedule, workflow_dispatch |
| 742 | Repo - Fleet Security Audit Backfill 742-fleet-security-audit-backfill.yml | Org | Writes (dry-run default) closes the gap 741 measures: opens one PR per uncovered FFC-EX repo adding BOTH halves of the audit pair (`security-audit.yml` + the `audit:high` script) in a single commit, never a `partial`; cron staggered deterministically by repo name across 06:00β06:59, skipping the crowded `:17`; `dry_run` (default true) runs the ungated `plan` job only, so a preview needs no approval; never pushes a target's default branch and never bumps a dependency (#822 owns remediation); repos with no `package-lock.json` are reported, not wired | workflow_dispatch |
| 743 | Website - Fleet Security Header Audit 743-fleet-security-header-audit.yml | Reads weekly audit of the security headers each FFC-delivered site ACTUALLY SERVES (HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, CSP), measured by fetching the live response and never by looking for a file in a repo β `public/_headers` is inert on this stack, so a source-file check reports coverage that does not exist (#884); scope is every domain in an FFC Cloudflare zone (derived column, not the curated host category); report-only CSP does not count as a CSP and a `<meta>` tag satisfies nothing; rolling issue upsert/close on any gap; plain HTTPS GETs to public sites, no credentials, no external API, GITHUB_TOKEN for the issue only, ungated | schedule, workflow_dispatch | |
| 744 | Repo - Public Feed Freshness 744-repo-public-feed-freshness.yml | GH | Reads daily OUTCOME check on the published Agentic OS status feed in `FFC-IN-ffcadmin.org` (`agentic-os-status.json`, both the `public/data/` and `src/data/` copies), asserting how old the timestamp the public page actually serves is β the three existing mechanisms are all PROCESS checks (#921 watches whether 502 succeeds, #908 watches PRs stuck behind `main`, #771 watches drift between the two copies) and every one of them was green while the page sat three days stale (#977); stale after 36h, so one missed daily tick does not report; a path that is missing, unreadable, or carries an absent/empty/non-ISO/future `generated_at` is a finding and never a pass, and a registered path the run produced no observation for is reported rather than skipped; rolling issue upsert/close; **deliberately holds no Key Vault credential** β ambient GITHUB_TOKEN only, since a monitor depending on the credential whose death it reveals goes dark when it is needed; ungated | schedule, workflow_dispatch |
| 745 | Repo - Agentic OS Board Audit 745-agentic-os-board-audit.yml | GH | Reads daily audit of the public Agentic OS board (org project #9) against the real backlog, running `scripts/audit-agentic-os-board.py` β which #968 shipped and nothing invoked, making it assurance only when a human remembered (#969); the board has NO auto-add so every card and Status is placed by hand and the audit is the compensating control, which on a manual process has to be automatic or it inherits the same failure mode; reports the three sets (missing from board / no Status / closed-or-merged but not `Done`) to the Conductor Log and is SILENT when clean, following 734; a run that could not read one of its two sides is reported as INCOMPLETE and never as clean, since the script exits 1 for both findings and enumeration failure and the exit code alone cannot tell them apart; read-only against GitHub (adds no card, sets no Status, closes nothing), Key Vault PAT on the reader identity for the org-level ProjectsV2 read the ambient token cannot do, ambient GITHUB_TOKEN for the issue comment, ungated | schedule, workflow_dispatch |
Candid (GuideStar)
2 workflows| # | Workflow | APIs | Safety | Runs on |
|---|---|---|---|---|
| 801 | Candid - Charity Check (EIN) 801-candid-charity-check.yml | CANDID | Reads read-only IRS-status lookup; public org data only | workflow_dispatch |
| 802 | Candid - Essentials Search 802-candid-essentials-search.yml | CANDID | Reads read-only profile search; public org data only | workflow_dispatch |
For AI agents & new admins
A machine-readable version of this catalog is published at https://ffcadmin.org/data/workflow-catalog.json (stable URL; source of truth: docs/workflow-catalog.json in the automation repository, regenerated on every workflow change β CI fails if it drifts). Schema: workflows[] with number, title, apis, file, triggers, environments, safetyLevel, approvalEnv, guard, category. To pick a workflow: match the first digit to the system you need to act on, prefer Reads before Writes, and always run write workflows with the default dry_run preview first.
Related: Technology Stack Β· Sites List