πŸš€ SDLC & CI/CD Workflow

How Solomon is built, tested, and shipped to production

πŸ”€ Dual-Workflow Architecture

Solomon uses two GitHub Actions workflows to optimize deploy times based on what changed. Each workflow has its own concurrency lock so they never interfere with each other.

πŸ—οΈ

Full Deploy

deploy-and-release.yml

  • Trigger: Push to main (excludes functions/**)
  • Duration: ~10 minutes
  • Jobs: 5 (version β†’ build β†’ deploy β†’ release-notes β†’ create-release)
  • Builds Angular app + deploys hosting, Firestore, and functions
  • Concurrency group: deploy-app-${{ github.ref }}
⚑

Functions-Only Deploy

deploy-functions.yml

  • Trigger: Push to main with changes only in functions/**
  • Duration: ~2 minutes
  • Skips Angular build entirely
  • Ideal for voice ID changes, Cloud Function edits
  • Concurrency group: deploy-functions-${{ github.ref }}
  • Smart skip: If the push also touches files outside functions/, this workflow detects that the Full Deploy will handle it and skips all subsequent jobs automatically
Why separate concurrency groups? GitHub keeps one waiting run per concurrency lock and cancels an older waiting run when a newer one arrives. A shared lock between both workflows allowed a functions-only run to cancel a waiting full deploy. Each workflow now has its own lock (deploy-app-* and deploy-functions-*) so they queue independently.

πŸ“Š Full Pipeline (5 Jobs)

1
πŸ”’ Version Calculation
Fetches all git tags, parses latest version, scans commit log for conventional commit keywords to determine bump type.
git fetch --tags Semantic Versioning
↓
2
πŸ—οΈ Build Application
Node 22, injects version into version.ts, runs npm install then unit tests, security rules tests (Firestore + Storage emulator), and finally npm run build:prod. Uploads artifact with 1-day retention.
Node 22 Angular CLI npm install Karma/Jasmine Firebase Emulator (Java 21)
↓
3
πŸš€ Deploy to Firebase
Downloads artifact, builds functions, writes secrets to functions/.env, authenticates via WIF, deploys hosting + Firestore + functions.
firebase-tools@15.6.0 WIF Auth gcloud CLI
↓
4
πŸ“ AI Release Notes
Calls Claude API (claude-sonnet-4-6) with the commit log to generate categorized release notes. Falls back to grep/sed categorization on failure.
Claude API Conventional Commits
↓
5
🏷️ Create GitHub Release
Creates an annotated git tag, pushes it, then creates a GitHub Release with the AI-generated release notes.
git tag gh release create
Legend
Pipeline Stage
Tools & Technologies

⚑ Functions-Only Pipeline

When only functions/** files change, deploy-functions.yml runs a leaner pipeline. The version job now also checks whether the push touches any files outside functions/ β€” if so, all subsequent jobs are skipped because the Full Deploy workflow will handle everything (including functions).

Scope Check (version job)

The scope step computes the list of changed files between github.event.before and github.sha and filters out anything under functions/. If any outside files are found, it sets app_changed=true and all downstream jobs (test-functions, deploy, release) are skipped via an if condition.

  • Handles edge cases: unknown starting point (e.g. force push) defaults to deploying here
  • Skipped summary is written to the GitHub Actions step summary for visibility
Result: Every push is deployed exactly once. A push that changes both app and function files is deployed by the Full Deploy workflow; a push that changes only function files is deployed by the Functions-Only workflow.

πŸ§ͺ Security Rules Tests

The build job runs Firestore and Storage security rules tests against the Firebase Emulator before the production build.

Setup

  • Java 21 (Temurin) β€” required by the Firebase Emulator at this CLI version
  • firebase-tools@15.6.0 β€” used in every job, to run the emulators and to deploy
  • Test runner: npm run test:rules β€” covers tests/rules/firestore.test.mjs and tests/rules/storage.test.mjs
Order in the build job: unit tests β†’ security rules tests β†’ npm run build:prod. A rules failure blocks the build artifact from being created, preventing a broken deploy.

πŸ”’ Semantic Versioning

Version bumps are determined automatically from conventional commit messages in the commit log.

MAJOR (Breaking)
v2.0.0
BREAKING CHANGE or [major]
MINOR (Feature)
v1.3.0
feat: or [minor]
PATCH (Fix)
v1.2.4
fix: (default)
Tag format: v{major}.{minor}.{patch} β€” each release creates an annotated git tag and a GitHub Release with categorized notes.

πŸ” WIF Authentication

Solomon uses Workload Identity Federation (WIF) for GitHub Actions CI/CD β€” no stored service account keys.

Configuration

  • Pool: github-actions-pool
  • Provider: github-provider
  • Action: google-github-actions/auth@v2 with token_format: 'access_token'
  • Deploy token: FIREBASE_TOKEN: ${{ steps.auth.outputs.access_token }}
Critical: Do NOT set --allowed-audiences when creating the OIDC provider. The google-github-actions/auth@v2 action sends the provider resource name as the audience by default β€” setting a custom audience causes an "audience does not match" error.

πŸ“ AI Release Notes

Primary: Claude API

On each release, the pipeline sends the conventional commit log to Claude (claude-sonnet-4-6) which generates categorized markdown release notes:

  • Features β€” new capabilities
  • Bug Fixes β€” resolved issues
  • Performance β€” optimization changes
  • Documentation β€” doc updates
  • Refactoring β€” code improvements

Fallback: Shell Categorization

If the Claude API call fails (non-200 response), a shell-based fallback using grep and sed categorizes commits into the same sections.

⚠️ CI/CD Gotchas

Issue Solution
firebase-tools@13.15.3+ failed with 403 on the extensions API check (deploy) Fixed in later versions. Every job now uses firebase-tools@15.6.0
Firebase Emulator requires Java 21 with newer CLI versions Add actions/setup-java@v4 (Temurin 21) before running rules tests in CI
npm ci fails with 403/404 on older transitive deps Use npm install (resilient to registry errors)
SSL errors in Firebase deploy Not needed in CI: GitHub's runners trust Google's certificates. Never set NODE_TLS_REJECT_UNAUTHORIZED
WIF audience mismatch Omit --allowed-audiences on OIDC provider
Secret Manager permissions in deploy Write secrets to functions/.env in CI before deploy, not firebase.json secrets[] or gcloud run services update
Shared concurrency group between workflows lets functions-only run cancel waiting full deploy Give each workflow its own concurrency group: deploy-app-* and deploy-functions-*
Functions-only push also changes app files β€” both workflows trigger The scope step in deploy-functions.yml detects outside-functions changes and skips all downstream jobs, letting the Full Deploy handle it

πŸ› οΈ How to Deploy

# Deploys run only in GitHub Actions. Never deploy from a laptop. git checkout -b fix/short-description # ...make the change and commit it... npm run test:all git ship # opens and merges the pull request; the merge to main deploys
↑