GitHub¶
Last updated: 23 March 2026
This page documents the GitHub configuration, branch protection rules, CI pipelines, and operational learnings for the bailey-medics organisation.
Organisation¶
| Setting | Value |
|---|---|
| Organisation | bailey-medics |
| Plan | GitHub Enterprise |
| Primary repository | quillmedical |
| Data repository | quill-question-bank |
The Enterprise plan is required for metadata restrictions (branch naming enforcement via repository rulesets). The Team plan only supports branch naming rules in "evaluate" mode.
Repositories¶
quillmedical¶
The main application repository containing backend, frontend, infrastructure, and documentation.
quill-question-bank¶
Stores teaching question bank content (YAML config files and images). Synced to the app via GCS bucket. Has its own branch protection and naming rulesets mirroring the main repo.
Branch strategy¶
graph LR
A[feature/*] -->|PR| B[main]
C[hotfix/*] -->|PR| B
B -->|merge| D[clinical-live]
Branch types¶
| Branch | Purpose | Deploys to |
|---|---|---|
feature/* |
New features and non-urgent fixes | CI checks only |
hotfix/* |
Urgent production fixes | CI checks only |
main |
Integration branch | Staging + Teaching |
clinical-live |
Production-ready code | Production |
release/** |
Release candidates (future use) | — |
Rules¶
- All development happens on
feature/*orhotfix/*branches mainrequires a pull request — never push directly- Branch names must match
^(feature|hotfix)/.+(enforced at creation time) - Use
hotfix/*only for urgent production fixes;feature/*for everything else
Branch protection rulesets¶
Managed by Terraform in infra/github/branch_rules.tf. Four rulesets are defined across both repositories.
Manual apply required for GitHub rulesets
The infra/github/ Terraform uses a local state backend. Merging .tf changes to main does not automatically apply them. You must run terraform apply locally:
export GITHUB_TOKEN=$(gh auth token)
cd infra/github/
terraform plan -var-file=terraform.tfvars
terraform apply -var-file=terraform.tfvars
The GCP infrastructure Terraform (infra/) uses a remote backend and is applied automatically via the Terraform CI workflow.
Ruleset 1 — Protected branches (quillmedical)¶
Targets: main, clinical-live, release/**
| Rule | Setting |
|---|---|
| Pull request required | Yes (0 approvals while solo; increase when team grows) |
| Dismiss stale reviews on push | Yes |
| Required status checks | All 10 CI checks (strict — branch must be up-to-date) |
| Force push | Blocked |
| Branch deletion | Blocked |
| Bypass actors | None (applies to admins too) |
Ruleset 2 — Branch naming (quillmedical)¶
Targets: All branches except main, clinical-live, release/**
Pattern: ^(feature|hotfix)/.+ — branches that don't match are rejected at creation time.
Ruleset 3 — Protected branches (quill-question-bank)¶
Targets: main
Same PR and force-push rules as quillmedical, but no required status checks (no CI pipeline on the data repo).
Ruleset 4 — Branch naming (quill-question-bank)¶
Same ^(feature|hotfix)/.+ pattern as quillmedical.
Required status checks¶
All 10 checks must pass before a PR can merge to main. The strict policy also requires the branch to be up-to-date with main.
Python checks¶
| Check name | What it does |
|---|---|
Python styling |
Runs pre-commit hooks (ruff, black, mypy, bandit, cspell, trailing whitespace, YAML/TOML/JSON validation) |
Python unit |
Runs pytest (excludes integration and e2e markers) |
TypeScript checks¶
| Check name | What it does |
|---|---|
typescript_checks (eslint) |
ESLint on frontend source |
typescript_checks (prettier) |
Prettier formatting check |
typescript_checks (stylelint) |
CSS/SCSS linting |
typescript_checks (typecheck:all) |
TypeScript strict mode compilation |
typescript_checks (unit-test:run) |
Vitest unit tests |
typescript_checks (storybook:build) |
Storybook static build succeeds |
typescript_checks (storybook:test:ci) |
Storybook interaction tests (Playwright/Chromium) |
Security checks¶
| Check name | What it does |
|---|---|
Semgrep (frontend SAST) |
Static analysis security testing on frontend code |
CI/CD workflows¶
Four GitHub Actions workflows handle the full lifecycle. Each is self-contained — a change to one cannot affect the others.
Branch CI (branch-ci.yml)¶
Triggers: Push to any branch except main, clinical-live, release/**, and hotfix/** (covers feature/*, copilot/*, renovate/*, dependabot/*, etc.)
Jobs:
python_checks— Matrix:stylingandunit(Python 3.13)typescript_checks— Matrix: 7 parallel tasks (eslint, prettier, stylelint, typecheck:all, unit-test:run, storybook:build, storybook:test:ci)frontend_security— Semgrep SAST scanopen-pr— Auto-creates a PR tomainforfeature/*andcopilot/*branches only (Renovate/Dependabot create their own PRs)
All failures send Slack notifications.
Release and hotfix (release-hotfix.yml)¶
Triggers: Push to release/** and hotfix/**
Same checks as the feature workflow, plus a docs build job that runs after all checks pass. This builds both MkDocs (backend) and TypeDoc + Storybook (frontend) documentation to verify the docs still compile.
Deploy staging and teaching (deploy-staging-teaching.yml)¶
Triggers: Push to main, manual dispatch
Jobs:
changes— Detects which services changed (backend/frontend) using path filtersbuild— Builds Docker images and pushes to both staging and teaching Artifact Registrydeploy-staging— Updates Cloud Run services in the staging GCP projectdeploy-teaching— Updates Cloud Run services in the teaching GCP projectnotify— Slack notification on success or failure
Smoke tests hit /api/health on each environment after deploy (5 retries, 10 second intervals).
Deploy production (deploy-production.yml)¶
Triggers: Push to clinical-live, manual dispatch
Same structure as staging deploy but targets the production GCP project. Key differences:
concurrency.cancel-in-progress: false— never cancels a running production deploy- Ignores changes to
docs/**,*.md,safety/**,.github/prompts/** - Images tagged as
clinical-live-<sha>andlatest
Terraform (terraform.yml)¶
Triggers: Push/PR to main or clinical-live when infra/** files change
- PRs: Runs
terraform planfor staging, teaching, and production; posts plan output as a PR comment - main push: Runs
terraform applyfor staging and teaching - clinical-live push: Runs
terraform applyfor production
Documentation (docs.yml)¶
Triggers: Push to main when backend, frontend, docs, shared, or prompt files change
Builds and deploys to GitHub Pages:
- MkDocs (backend documentation)
- TypeDoc (frontend API docs)
- Storybook (component catalogue)
- OpenAPI/Swagger (auto-generated from FastAPI)
Pre-commit hooks¶
Local pre-commit hooks run on every commit (configured in .pre-commit-config.yaml). These match what CI runs in the Python styling check.
| Hook | Purpose |
|---|---|
| ruff | Python linting (rules: E, F, W, I, UP, B) with auto-fix |
| black | Python formatting (line-length 79) |
| trailing-whitespace | Remove trailing whitespace |
| end-of-file-fixer | Ensure files end with a newline |
| check-yaml | Validate YAML syntax |
| check-toml | Validate TOML syntax |
| check-json | Validate JSON syntax |
| cspell | Spelling checker (custom dictionary in cspell.config.json) |
| mypy | Python static type checking (strict mode) |
| bandit | Python security linting |
Frontend lint-staged hooks also run via Husky but only when frontend files are staged.
Slack notifications¶
All workflows send Slack notifications on failure via the slackapi/slack-github-action action using an incoming webhook (SLACK_WEBHOOK_URL secret). Deploy workflows also notify on success.
Secrets¶
GitHub Actions secrets are configured per-environment. Never commit tokens or keys.
| Secret | Used by |
|---|---|
GCP_STAGING_WIF_PROVIDER |
Workload Identity Federation for staging |
GCP_STAGING_SERVICE_ACCOUNT |
Staging service account |
GCP_STAGING_PROJECT_ID |
Staging GCP project ID |
GCP_TEACHING_WIF_PROVIDER |
Workload Identity Federation for teaching |
GCP_TEACHING_SERVICE_ACCOUNT |
Teaching service account |
GCP_TEACHING_PROJECT_ID |
Teaching GCP project ID |
GCP_PROD_WIF_PROVIDER |
Workload Identity Federation for production |
GCP_PROD_SERVICE_ACCOUNT |
Production service account |
GCP_PROD_PROJECT_ID |
Production GCP project ID |
SLACK_WEBHOOK_URL |
Slack notifications |
Authentication to GCP uses Workload Identity Federation — no long-lived service account keys.
Learnings¶
GitHub Enterprise is required for branch naming enforcement¶
The Team plan supports repository rulesets but only evaluates metadata restrictions (branch naming patterns) in "evaluate" mode — violations are logged but not blocked. Upgrading to Enterprise enables "active" enforcement, which rejects non-conforming branch names at creation time.
Terraform rulesets use local state for GitHub¶
The infra/github/ Terraform manages GitHub rulesets (branch protection, naming conventions) but uses a local state file. Unlike the GCP Terraform (which has a remote backend and CI-driven apply), GitHub ruleset changes must be applied manually after merging. A GitHub Actions workflow notification warns when this is needed.
All 10 status checks should be required¶
Initially only 5 "lean" checks were configured (Python styling, Python unit, eslint, typecheck, unit tests). This was expanded to all 10 checks including prettier, stylelint, storybook build, storybook tests, and Semgrep because in a healthcare application, bad UX can lead to patient harm — every check catches a different class of issue.
Auto-PR creation on feature and Copilot push¶
The branch CI workflow automatically creates a PR to main when pushing to a feature/* or copilot/* branch. The latest commit message is used as the PR title. This reduces friction and ensures every feature branch has a visible PR for review. Renovate and Dependabot branches also get CI checks but create their own PRs.
DCB 0129 clinical safety compliance¶
The branch protection rules form part of the auditable change-control process required by DCB 0129. They ensure:
- All changes go through pull-request review, creating an approval record
- The naming convention keeps the commit graph traceable for hazard-log and incident-response audits
- Force pushes are blocked on protected branches to preserve history
- No bypass actors are configured, so rules apply to everyone including admins