diff --git a/.claude/references/deployment-devops.md b/.claude/references/deployment-devops.md index 72a422e..f356ce1 100644 --- a/.claude/references/deployment-devops.md +++ b/.claude/references/deployment-devops.md @@ -1,21 +1,30 @@ # GForce Deployment & DevOps +The pipeline implementation lives in +[shared-github-actions](https://github.com/Gforce-Innovation-Kft/shared-github-actions) +(`sf-pr-validate.yml` / `sf-release.yml` reusable workflows); this repo only +ships thin callers. Operational setup guide: [docs/CICD.md](../../docs/CICD.md). +If the pipeline needs something the shared workflows lack, raise a change +request there — never copy workflow logic into this repo. + --- -## 1. Branching Strategy +## 1. Branching Strategy (trunk-based) ``` -main ← production-ready; protected; requires PR + approval + CI pass - └── develop ← integration branch; auto-deploys to staging - └── feature/REQ-001-account-enhancements ← short-lived, per-ticket - └── feature/REQ-002-lwc-account-form - └── hotfix/critical-bug-fix ← branches from main, merges to main + develop +main ← protected by ruleset; every merge deploys via the devhub gate + └── feature/REQ-001-account-enhancements ← short-lived, per-ticket + └── feature/REQ-002-lwc-account-form + └── hotfix/critical-bug-fix ← same flow, expedited review ``` -- `main` → production (deploy-production.yml, manual approval gate) -- `develop` → staging (deploy-staging.yml, auto on push) -- `feature/*` → scratch org only (validated by validate-pr.yml on PR to develop) -- `hotfix/*` → branches from `main`, merges to both `main` and `develop` +- `feature/*` → PR to `main` → validated by `pr-validate.yml` (`jest` + + `scratch-org` deploy/test) and `release.yml`'s `validate` job (delta + check-only deploy with selected Apex tests against the Dev Hub) +- merge to `main` → `release.yml` → **`devhub` GitHub Environment gate** + (required reviewer) → quick deploy of the PR-validated request +- No long-lived `develop` branch. Additional environments (integration/uat) + are added as extra gated jobs in `release.yml`, not extra branches. --- @@ -36,104 +45,104 @@ Always reference the REQ number when one exists. Every PR must: -- Pass CI (validate-pr.yml): scratch org creation + push + Apex tests + PMD/Code Analyzer +- Pass CI: `pr-validate.yml` → shared `sf-pr-validate.yml@v1` (`jest` + + `scratch-org` deploy/test), and `release.yml` → shared `sf-release.yml@v1` + (`validate`: delta check-only deploy with selected Apex tests against the + Dev Hub) +- Be **up to date with `main`** before merge (ruleset-enforced — this keeps the + validated deploy request identical to what merges, which is what makes quick + deploy safe) - Have a completed PULL_REQUEST_TEMPLATE.md checklist - Link to the relevant REQ-NNN.yaml or ticket -- Be reviewed by at least one other developer before merge +- Be reviewed (CODEOWNERS enforced) - Not contain `System.debug` calls, hardcoded IDs, or `SeeAllData=true` - Have test coverage ≥ 85% for changed classes -Squash merge into `develop`. No merge commits on `main`. +Squash or merge commits only — rebase-merges rewrite SHAs and break the +quick-deploy lookup (the fallback delta deploy covers it, but tests re-run). --- -## 4. Scratch Org Validation (CI) - -The `validate-pr.yml` workflow: +## 4. PR Validation (CI) -1. Authenticates to Dev Hub via `DEVHUB_AUTH_URL` secret -2. Creates a scratch org using `config/scratch-orgs/ci.json` -3. Pushes all source (TestDataFactory is source-tracked in force-app — no package install needed) -4. Runs all Apex tests (`sf apex run test --test-level RunLocalTests --result-format json`) -5. Runs Salesforce Code Analyzer (PMD rules) -6. **Always** deletes the scratch org (even on failure) — prevents org leaks +Two workflows run on every PR: -Scratch org lifespan in CI: 1 day max. Always pass `--duration-days 1`. - ---- +`pr-validate.yml` calls the shared `sf-pr-validate.yml@v1`: -## 5. Staging Promotion +1. `jest` — runs `npm test` (skips with a notice if no test script exists) +2. `scratch-org` — creates a scratch org from `config/scratch-orgs/ci.json` + (`--duration-days 1`), pushes source, assigns permission sets, runs all + local tests with coverage, and **always** deletes the org -Push to `develop` triggers `deploy-staging.yml`: +`release.yml` calls the shared `sf-release.yml@v1` (`validate` job): -1. Authenticates via `STAGING_AUTH_URL` secret -2. Runs `sf project deploy start --target-org staging` (source format) -3. Runs smoke tests (manual or automated Apex test subset) - -Staging = always green. If a deploy breaks staging, roll forward (fix) not roll back. +1. Generates a delta `package.xml` between the PR base and head + (sfdx-git-delta) +2. `sf-find-tests` selects the Apex tests covering the changed classes + (naming match + reference scan) +3. Runs a **check-only deploy of the delta against the Dev Hub** + (auth via `DEVHUB_AUTH_URL`) with `RunSpecifiedTests` — falls back to + `RunLocalTests` when Apex changed but no tests matched, and runs no tests + at all for metadata-only deltas. The resulting validated deploy request is + the quick-deploy handle, shipped in the `sf-release-` artifact --- -## 6. Production Promotion +## 5. Deployment (promotion) -Push to `main` triggers `deploy-production.yml`: +Merge to `main` triggers `release.yml` → shared `sf-release.yml@v1` +(`quick-deploy` job): -1. Requires manual approval (GitHub environment protection: `production`) -2. Authenticates via `PRODUCTION_AUTH_URL` secret -3. Runs `sf project deploy start --target-org production` -4. Posts Slack notification on success/failure +1. Waits at the **`devhub` GitHub Environment** for required-reviewer + approval +2. Creates a GitHub Deployment record (audit trail, links the Salesforce + deploy-request page) +3. **Quick deploys** the PR-validated request (no tests re-run); falls back to + a delta deploy (same recorded test plan), then a full deploy of all + package directories +4. Uploads the audit artifact: delta manifest, deploy result JSON, JUnit test + results, quick-deploy decision -Production deployments: business hours only (08:00–17:00 CET). No Friday deployments. +Bootstrap/re-baseline: `gh workflow run release.yml -f full-deploy=true`. +Production deployments: business hours only (08:00–17:00 CET). No Friday +deployments. --- -## 7. Secret Management - -| Secret | Used in | Value source | -| --------------------- | --------------------- | --------------------------------------------------------- | -| `DEVHUB_AUTH_URL` | validate-pr.yml | `sf org display --target-org devhub --verbose --json` | -| `STAGING_AUTH_URL` | deploy-staging.yml | `sf org display --target-org staging --verbose --json` | -| `PRODUCTION_AUTH_URL` | deploy-production.yml | `sf org display --target-org production --verbose --json` | - -Store in GitHub repo secrets (Settings → Secrets and variables → Actions). Never commit auth URLs. +## 6. Secret & Variable Management -To generate an auth URL: +| Name | Kind | Used by | Value source | +| ----------------- | ---------------------------------------- | -------------- | ------------------------------------------------------------------------------------- | +| `DEVHUB_AUTH_URL` | Repo secret | both workflows | `sf org display --target-org --verbose --json \| jq -r '.result.sfdxAuthUrl'` | +| `SF_ORG_ALIAS` | `devhub` environment variable (optional) | quick-deploy | CLI alias, defaults to the environment name | -```bash -sf org display --target-org --verbose --json | jq -r '.result.sfdxAuthUrl' -``` +Never commit auth URLs. For real client projects, move the auth URL to an +**environment secret** behind the gate, one per target org. --- -## 8. Package Installation in CI +## 7. Package Installation in CI TestDataFactory is source-tracked in `force-app/main/default/classes/` and deploys with the regular source push — no package install step is needed for it. -If the project adds unlocked packages, install them like this after the source push: - -```yaml -- name: Install packages - run: sf package install --package <04t-package-id> --target-org $SCRATCH_ORG_ALIAS --no-prompt --wait 10 -``` - -For more than one package, add them to a `scripts/install-packages.sh` and call it from all workflow jobs that need them. +If the project adds unlocked packages, install them after the source push via a +`scripts/install-packages.sh` invoked from the caller workflow (or raise a +change request to add package-install inputs to the shared workflows). --- -## 9. Code Analyzer (PMD) +## 8. Code Analyzer (PMD) — local use only -Run the Salesforce Code Analyzer on every PR, using the project ruleset -(`config/pmd-ruleset.xml` — all standard Apex categories, with naming -conventions adjusted for given_when_then test methods and fflib PascalCase -Application factories): +Code Analyzer is **not** wired into CI (dropped when the callers were rebuilt +on the shared `sf-pr-validate.yml` / `sf-release.yml` workflows — it is not +one of the PR gates). Run it locally before opening a PR — the project +ruleset lives at `config/pmd-ruleset.xml`: ```bash sf scanner run --target force-app/ --pmdconfig config/pmd-ruleset.xml --format table --severity-threshold 2 ``` -Severity 1–2: block the PR. Severity 3: warn but allow merge with justification. - Key PMD rules enforced: - `ApexSOQLInjection` — all SOQL through selectors with bind vars @@ -143,12 +152,14 @@ Key PMD rules enforced: --- -## 10. Quality Gates Summary - -| Gate | Trigger | Must Pass | -| ------------------------ | -------------------- | --------------------- | -| Lint + Prettier | Pre-commit (Husky) | Yes | -| Apex tests (scratch org) | PR to develop/main | Yes (≥85% coverage) | -| PMD Code Analyzer | PR to develop/main | Severity 1–2 = block | -| Manual PR review | PR to develop/main | ≥1 approval | -| Manual approval | Deploy to production | Required (GitHub env) | +## 9. Quality Gates Summary + +| Gate | Trigger | Must Pass | +| ------------------------------------ | ------------------ | --------------------------- | +| Lint + Prettier | Pre-commit (Husky) | Yes | +| `jest` | PR to main | Yes | +| Delta check-only deploy (`validate`) | PR to main | Yes | +| Apex tests (`scratch-org`) | PR to main | Yes (≥85% coverage) | +| Branch up to date with main | PR merge (ruleset) | Yes | +| Manual PR review | PR to main | CODEOWNERS approval | +| Manual approval | Deploy | `devhub` GitHub Environment | diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml deleted file mode 100644 index 0b6903f..0000000 --- a/.github/workflows/deploy-production.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Deploy to Production - -on: - push: - branches: - - main - -jobs: - deploy: - name: Deploy → Production - runs-on: ubuntu-latest - environment: production # requires manual approval in GitHub repo settings - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Install Salesforce CLI - run: npm install -g @salesforce/cli --silent - - - name: Authenticate production org - run: | - echo "${{ secrets.PRODUCTION_AUTH_URL }}" > production-auth.txt - sf org login sfdx-url --sfdx-url-file production-auth.txt --alias production - rm production-auth.txt - - - name: Deploy source - run: sf project deploy start --target-org production - - - name: Run post-deploy tests - run: | - sf apex run test \ - --target-org production \ - --test-level RunLocalTests \ - --result-format human \ - --wait 20 diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml deleted file mode 100644 index 4b1a422..0000000 --- a/.github/workflows/deploy-staging.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Deploy to Staging - -on: - push: - branches: - - main - -jobs: - deploy: - name: Deploy → Staging - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Install Salesforce CLI - run: npm install -g @salesforce/cli --silent - - - name: Authenticate staging org - run: | - echo "${{ secrets.STAGING_AUTH_URL }}" > staging-auth.txt - sf org login sfdx-url --sfdx-url-file staging-auth.txt --alias staging - rm staging-auth.txt - - - name: Deploy source - run: sf project deploy start --target-org staging - - - name: Run smoke tests - run: | - sf apex run test \ - --target-org staging \ - --test-level RunLocalTests \ - --result-format human \ - --wait 15 diff --git a/.github/workflows/pr-validate.yml b/.github/workflows/pr-validate.yml new file mode 100644 index 0000000..a9e639b --- /dev/null +++ b/.github/workflows/pr-validate.yml @@ -0,0 +1,22 @@ +# Thin caller — all validation logic lives in shared-github-actions. +# See docs/CICD.md for the pipeline overview and required setup. +name: PR Validate +on: + pull_request: + branches: [main] + +concurrency: + group: pr-validate-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + validate: + uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-pr-validate.yml@v1 + with: + # Temporary until gforceinnovation/sf-ci is published on Docker Hub + container-image: gforceinnovation/sf-ci:latest + secrets: + sfdx-auth-url: ${{ secrets.DEVHUB_AUTH_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6533adc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,34 @@ +# Thin caller — delta validation on PRs, gated quick deploy on merge. +# The `devhub` GitHub Environment holds the required-reviewer gate. +# Bootstrap a fresh org with: gh workflow run release.yml -f full-deploy=true +name: Release +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + inputs: + full-deploy: + description: "Deploy every package directory (bootstrap / re-baseline)" + type: boolean + default: false + +concurrency: + group: release-${{ github.event_name == 'pull_request' && github.event.pull_request.number || 'main' }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + actions: read + +jobs: + release: + uses: Gforce-Innovation-Kft/shared-github-actions/.github/workflows/sf-release.yml@v1 + with: + environment: devhub + # Temporary until gforceinnovation/sf-ci is published on Docker Hub + container-image: gforceinnovation/sf-ci:latest + full-deploy: ${{ github.event_name == 'workflow_dispatch' && inputs.full-deploy }} + secrets: + sfdx-auth-url: ${{ secrets.DEVHUB_AUTH_URL }} diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml deleted file mode 100644 index 13e4be9..0000000 --- a/.github/workflows/validate-pr.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Validate PR - -on: - pull_request: - branches: - - main - -concurrency: - group: validate-pr-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - validate: - name: Scratch Org Validation - runs-on: ubuntu-latest - env: - SCRATCH_ALIAS: ci-scratch-${{ github.run_id }} - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: "npm" - - - name: Install Salesforce CLI - run: npm install -g @salesforce/cli --silent - - - name: Install npm dependencies - run: npm ci --silent - - - name: Authenticate Dev Hub - run: | - echo "${{ secrets.DEVHUB_AUTH_URL }}" > devhub-auth.txt - sf org login sfdx-url --sfdx-url-file devhub-auth.txt --alias devhub --set-default-dev-hub - rm devhub-auth.txt - - - name: Create scratch org - run: | - sf org create scratch \ - --definition-file config/scratch-orgs/ci.json \ - --alias $SCRATCH_ALIAS \ - --duration-days 1 \ - --target-dev-hub devhub - - - name: Push source - run: sf project deploy start --target-org $SCRATCH_ALIAS - - - name: Assign permission sets - run: | - for ps in force-app/main/default/permissionsets/*.permissionset-meta.xml; do - [ -e "$ps" ] || continue - sf org assign permset --name "$(basename "$ps" .permissionset-meta.xml)" --target-org $SCRATCH_ALIAS - done - - - name: Run Apex tests - run: | - sf apex run test \ - --target-org $SCRATCH_ALIAS \ - --test-level RunLocalTests \ - --result-format json \ - --output-dir test-results \ - --wait 20 - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: apex-test-results - path: test-results/ - - - name: Run Code Analyzer (PMD) - run: | - sf plugins install @salesforce/sfdx-scanner - sf scanner run \ - --target force-app/ \ - --pmdconfig config/pmd-ruleset.xml \ - --format table \ - --severity-threshold 2 - - - name: Delete scratch org - if: always() - run: sf org delete scratch --target-org $SCRATCH_ALIAS --no-prompt 2>/dev/null || true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21ba848..82fa947 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,41 +3,50 @@ This applies both to the template itself and to projects bootstrapped from it. Full details live in `.claude/references/deployment-devops.md`. -## Branch model +## Branch model (trunk-based) ``` -main ← production-ready; protected; PR + approval + CI pass required - └── develop ← integration branch; auto-deploys to staging - └── feature/REQ-001-short-description ← short-lived, per-ticket - └── hotfix/critical-bug-fix ← from main, merges to main + develop +main ← protected by ruleset; every merge deploys via the devhub gate + └── feature/REQ-001-short-description ← short-lived, per-ticket + └── hotfix/critical-bug-fix ← same flow, expedited review ``` Branch names: `feature/REQ-NNN-…`, `bugfix/REQ-NNN-…`, `hotfix/…`, `chore/…` — -always reference the REQ number when one exists. +always reference the REQ number when one exists. No long-lived `develop` +branch; additional environments are extra gated jobs in `release.yml`, not +extra branches (see [docs/CICD.md](docs/CICD.md)). ## Pull requests Every PR must: -- Pass CI (`validate-pr.yml`: scratch org + source push + Apex tests + Code Analyzer) +- Pass CI: `pr-validate.yml` → shared `sf-pr-validate.yml@v1` (`jest` + + scratch-org deploy/test), and `release.yml` → shared `sf-release.yml@v1` + (`validate`: delta check-only deploy with selected Apex tests against the + Dev Hub) +- Be up to date with `main` before merge (ruleset-enforced — keeps the + validated deploy request identical to what merges, enabling quick deploy) - Complete the `PULL_REQUEST_TEMPLATE.md` checklist - Link the relevant `docs/product/requirements/REQ-NNN.yaml` or ticket - Have ≥ 1 review approval - Contain no `System.debug`, hardcoded IDs, or `SeeAllData=true` - Keep test coverage ≥ 85% for changed classes -Squash-merge into `develop`. No merge commits on `main`. +Squash-merge or merge commit — never rebase-merge (rewritten SHAs break the +quick-deploy lookup). ## Quality gates -| Gate | Trigger | Must pass | -| ------------------------ | ------------------ | -------------------------- | -| Lint + Prettier | Pre-commit (Husky) | Yes | -| Apex tests (scratch org) | PR to develop/main | Yes (≥ 85% coverage) | -| Code Analyzer (PMD) | PR to develop/main | Severity 1–2 = block | -| Template contract tests | PR to develop/main | Yes (`npm run test:setup`) | -| Manual review | PR to develop/main | ≥ 1 approval | -| Manual approval | Production deploy | GitHub `production` env | +| Gate | Trigger | Must pass | +| ------------------------------------ | ------------------ | -------------------------- | +| Lint + Prettier | Pre-commit (Husky) | Yes | +| `jest` | PR to main | Yes | +| Delta check-only deploy (`validate`) | PR to main | Yes | +| Apex tests (`scratch-org`) | PR to main | Yes (≥ 85% coverage) | +| Template contract tests | PR to main | Yes (`npm run test:setup`) | +| Branch up to date with main | PR merge (ruleset) | Yes | +| Manual review | PR to main | ≥ 1 approval (CODEOWNERS) | +| Manual approval | Deploy | GitHub `devhub` env | ## Hard rules for code diff --git a/README.md b/README.md index 15081e6..79ea112 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Reusable Salesforce DX scaffold for GForce Innovation client engagements. - **fflib enterprise patterns** — Application factory, Domain / Selector / Service / Unit of Work layers (fflib-apex-common + fflib-apex-mocks as git submodules) - **NebulaLogger** — structured logging everywhere, `System.debug` banned by convention and PMD - **AI pair-programming, ready on clone** — `CLAUDE.md` conventions, 89 vendored [sf-skills](https://github.com/forcedotcom/sf-skills) pinned by `skills-lock.json`, GForce custom skills, coding-rule references in `.claude/references/`, and a graphify knowledge graph -- **CI/CD** — scratch-org PR validation, staging auto-deploy, production deploy with manual approval, and a template self-verification workflow +- **CI/CD** — PR checks (`jest` + scratch-org deploy/test) plus a delta check-only validate against the Dev Hub, gated `devhub` deploy with quick-deploy promotion (delta → full fallback) and a full audit trail (GitHub Deployments + artifacts), plus a template self-verification workflow — all via [shared reusable workflows](https://github.com/Gforce-Innovation-Kft/shared-github-actions) - **Worked reference feature** — FX Invoice Conversion (`Invoice__c`, trigger → handler → domain → selector → service → UoW → gateway, LWC, tests). It demonstrates every layer end-to-end; strip or replace it once your real requirements land. See `docs/product/PRODUCT.md`. - **Test scaffolding** — TestDataFactory (source-tracked, no package install), Jest for LWC, contract tests that keep the template itself honest @@ -63,15 +63,13 @@ sf apex run test --test-level RunLocalTests ## CI/CD and required secrets -Workflows live in `.github/workflows/`. `Template Verify` needs **no secrets**; the other three need org auth URLs added under **Settings → Secrets and variables → Actions**: +The pipeline is two thin callers (`pr-validate.yml`, `release.yml`) over the reusable workflows in [shared-github-actions](https://github.com/Gforce-Innovation-Kft/shared-github-actions): PR = `jest` + scratch-org deploy/test (`pr-validate.yml`) and a delta check-only validate against the Dev Hub (`release.yml`); merge = required-reviewer gate on the `devhub` GitHub Environment, then a **quick deploy** of the PR-validated request (falls back to delta → full deploy). Every deploy leaves a GitHub Deployment record and an audit artifact. `Template Verify` needs **no secrets**; org access needs exactly one: -| Secret | Used by | How to generate | -| --------------------- | ----------------------- | ------------------------------------------------------------------------------------ | -| `DEVHUB_AUTH_URL` | `validate-pr.yml` | `sf org display --target-org devhub --verbose --json \| jq -r '.result.sfdxAuthUrl'` | -| `STAGING_AUTH_URL` | `deploy-staging.yml` | same, with `--target-org staging` | -| `PRODUCTION_AUTH_URL` | `deploy-production.yml` | same, with `--target-org production` | +| Secret | Used by | How to generate | +| ----------------- | -------------------------------- | ------------------------------------------------------------------------------------ | +| `DEVHUB_AUTH_URL` | `pr-validate.yml`, `release.yml` | `sf org display --target-org devhub --verbose --json \| jq -r '.result.sfdxAuthUrl'` | -Also add GitHub **environment protection** named `production` (manual approval) for production deploys. Branch model and quality gates are documented in [CONTRIBUTING.md](CONTRIBUTING.md). +Also add GitHub **environment protection** named `devhub` (required reviewer) and the branch ruleset on `main`. Full setup, quick-deploy mechanics, and the audit-trail story are in [docs/CICD.md](docs/CICD.md); branch model and quality gates in [CONTRIBUTING.md](CONTRIBUTING.md). ## Working with AI (Claude Code) diff --git a/docs/CICD.md b/docs/CICD.md new file mode 100644 index 0000000..7a6c0e7 --- /dev/null +++ b/docs/CICD.md @@ -0,0 +1,75 @@ +# CI/CD Pipeline + +Trunk-based flow against a single Dev Hub (production) org. Two thin +workflows call the reusable layer in +[shared-github-actions](https://github.com/Gforce-Innovation-Kft/shared-github-actions) +at the `v1` release tag — all pipeline logic is versioned there. + +## Flow + +```mermaid +flowchart LR + subgraph PR["Pull request"] + J[jest\nnpm test] ~~~ S[scratch-org\ndeploy + RunLocalTests] + D[delta package] --> T[sf-find-tests\nrelevant Apex tests] --> V[check-only validate\nvs Dev Hub] + V -->|deploy request id| A[(sf-release artifact)] + end + PR -->|merge| G{devhub environment\nmanual approval} + G --> Q[quick deploy\nvalidated request] + A -.->|validation.json| Q + Q -->|stale / consumed| F[delta deploy → full deploy] +``` + +**PR Validate** (`pr-validate.yml` → `sf-pr-validate.yml@v1`) + +- `jest` — runs `npm test` (skips with a notice if no test script exists). +- `scratch-org` — 1-day scratch org from `config/scratch-orgs/ci.json`: + deploy, assign permission sets, `RunLocalTests` with coverage, always + deleted. + +**Release** (`release.yml` → `sf-release.yml@v1`) + +- On PR: delta `package.xml` (sfdx-git-delta) → `sf-find-tests` selects the + Apex tests covering the changed classes (naming match + reference scan) → + check-only deploy against the Dev Hub (`RunSpecifiedTests`; falls back to + `RunLocalTests` when Apex changed but no tests matched; no tests for + metadata-only deltas). The deploy request id is saved in the + `sf-release-` artifact. +- On merge: the `quick-deploy` job waits for approval on the `devhub` + environment, then runs `sf project deploy quick` with the validated + request — no tests re-run, the org accepts the already-validated + package. Fallbacks: delta deploy (same recorded test plan) → full deploy + of every `packageDirectories` entry. Manual bootstrap: + `gh workflow run release.yml -f full-deploy=true`. + +## Why quick deploy is safe here + +The `main` ruleset requires branches to be up to date before merging, so +the merged tree is identical to the validated PR head. The deploy job +additionally checks: same org id, same head SHA, validation younger than +10 days — otherwise it falls back to a real deploy. + +## Required setup + +| Piece | Value | +| ----------------- | ----------------------------------------------------------------------------------------- | +| Repo secret | `DEVHUB_AUTH_URL` — SFDX auth URL of the Dev Hub | +| Environment | `devhub`, required reviewer = release manager | +| Ruleset on `main` | require PR, require `jest` / `scratch-org` / `validate` checks, require branch up to date | + +## Audit trail + +Every run leaves artifacts (90-day retention by default): the delta +manifest and generated source, the validate/deploy results, the selected +tests, and `quick-deploy-decision.json` recording why quick deploy was or +was not used. Deployments to `devhub` also appear in the repo's +Deployments sidebar (recorded automatically by the environment binding). +For longer retention, sync artifacts to external storage (e.g. S3) from a +scheduled workflow. + +## Limitations + +- Fork PRs fail validation (secrets are not exposed to forks) — use + same-repo branches. +- Approving a deploy more than 10 days after validation falls back to a + full delta redeploy (tests re-run).