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 60 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.

60
Workflows
27
Read-only
24
Write (gated / dry-run)
9
Repo plumbing

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:

  1. Read vs. write credentials. Read-only workflows load reader-scope credentials and cannot mutate anything; write workflows load writer-scope credentials.
  2. Environment approval gates. Write environments (and some sensitive read environments) require a human reviewer to approve the run before it proceeds.
  3. dry_run defaults to preview. Write workflows preview what they would change; going live requires explicitly passing dry_run=false.
  4. Typed confirmation for the highest-stakes actions (for example, domain registration requires retyping the exact domain).
  5. 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.

1xx

Cloudflare / DNS / Domain

20 workflows
#WorkflowAPIsSafetyRuns on
101
Domain - Status (All Sources)
101-domain-status.yml
CFM365
Reads
M365 job 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)
workflow_dispatch
104
Domain - Export Inventory (All Sources)
104-domain-export-inventory.yml
CFM365WHMCSWPMUDEV
Reads
waits on whmcs-prod, m365-prod, and wpmudev-prod approvals
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
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
2xx

WHMCS

13 workflows
#WorkflowAPIsSafetyRuns on
201
WHMCS - Export Domains (Report)
201-whmcs-export-domains.yml
WHMCS
Reads
gated only by the env approval
workflow_dispatch
202
WHMCS - Export Products (Report)
202-whmcs-export-products.yml
WHMCS
Reads
gated only by the env approval
workflow_dispatch
203
WHMCS - Export Payment Methods (Research)
203-whmcs-export-payment-methods.yml
WHMCS
Reads
gated only by the env approval
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
3xx

Microsoft (M365 / Azure / Graph)

6 workflows
#WorkflowAPIsSafetyRuns on
301
M365 - Domain Preflight (Read-only)
301-m365-domain-preflight.yml
M365CF
Reads
M365 job waits on m365-prod approval
workflow_dispatch
302
M365 - List Tenant Domains
302-m365-list-domains.yml
M365
Reads
waits on m365-prod approval
workflow_dispatch
303
M365 - Domain Status + DKIM (Toolbox)
303-m365-domain-and-dkim.yml
M365
Reads
read-oriented toolbox; waits on m365-prod approval
workflow_dispatch
304
M365 - Enable DKIM (Exchange Online)
304-m365-dkim-enable.yml
M365CF
Writes (gated)
workflow_dispatch
305
M365 - Add Tenant Domain (Admin)
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 (M365, PII masked)
306-discover-uncaptured-comms.yml
M365
Reads
PII masked; dispatch-only; org mailboxes only; waits on m365-prod approval
workflow_dispatch
4xx

Zeffy

3 workflows
#WorkflowAPIsSafetyRuns on
401
Zeffy - Campaigns Export
401-zeffy-campaigns-export.yml
ZEFFY
Reads
PII masked; never `-IncludePii`
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
5xx

Google

3 workflows
#WorkflowAPIsSafetyRuns 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 (CBM_TOKEN); PII-safe aggregates
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
6xx

WPMUDEV

1 workflows
#WorkflowAPIsSafetyRuns on
601
WPMUDEV - Export Sites/Domains (Read-only)
601-wpmudev-export-sites.yml
WPMUDEV
Reads
waits on wpmudev-prod approval
workflow_dispatch
7xx

GitHub (Website + Repo)

14 workflows
#WorkflowAPIsSafetyRuns 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)
opens a draft PR (never pushes); serialized
workflow_dispatch
703
Sites List - Generate (CSV + JSON)
703-sites-list-generate.yml
GH
plumbingschedule, workflow_dispatch
720
Repo - Create GitHub Repo
720-create-repo.yml
Repo
plumbingworkflow_dispatch
721
Repo - Deploy GitHub Pages
721-deploy-pages.yml
Repo
plumbingpush, workflow_dispatch
722
Repo - CI Validate and Test
722-ci.yml
Repo
plumbingmerge_group, pull_request, push
723
Repo - CodeQL Security Analysis
723-codeql-analysis.yml
Repo
plumbingmerge_group, pull_request, push, schedule, workflow_dispatch
724
Repo - Initialize Labels
724-initialize-labels.yml
Repo
plumbingworkflow_dispatch
725
Repo - Sync Labels
725-sync-labels.yml
Repo
plumbingpush, workflow_dispatch
726
Repo - Rulesets + Settings Drift Audit
726-repo-rulesets-drift-audit.yml
Org
Reads
report only
schedule, workflow_dispatch
727
Repo - Phantom Revert Guard
727-phantom-revert-guard.yml
Repo
plumbingmerge_group, pull_request, workflow_dispatch
728
Repo - AI Agent Hooks Validate
728-ai-agent-hooks-validate.yml
Repo
plumbingpull_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

For AI agents & new admins

A machine-readable version of this catalog lives at docs/workflow-catalog.json in the automation repository and is regenerated on every workflow change (CI fails if it drifts). 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