From 274e7e6701e429721ad56555e40fce4fef23c3ee Mon Sep 17 00:00:00 2001 From: EraPartner Date: Thu, 7 May 2026 22:13:09 +0200 Subject: [PATCH 1/2] ci: add compose volume sync check and final CI gate - verify-compose-sync job in ci.yml: extracts named volumes from both docker-compose.yml and packaging/electron/resources/docker-compose.yml and fails if they diverge; included in quality-gate needs list. Catches the v1.0.2 class of data-wipe bug at PR time, not release time. - ci-complete job in ci.yml: aggregates trivy-scan, docker-verify, and test-live-api-contracts so branch protection has one required status check covering the full Docker-tier pipeline. Set "CI Complete" as the required check in branch protection settings. - release.yml verify job: same compose volume sync check runs as the first step before version-tag and audit checks, blocking the release if volumes are out of sync. - ADR-051, docs/guides/cicd-pipelines.md, docs/architecture/electron.md, docs/guides/deployment.md updated to document both changes. --- .github/workflows/ci.yml | 48 +++++ .github/workflows/release.yml | 12 ++ .../051-docker-compose-sync-named-volumes.md | 119 +++++++++++ docs/adr/index.md | 8 +- docs/architecture/electron.md | 31 ++- docs/guides/cicd-pipelines.md | 187 +++++++++++++++--- docs/guides/deployment.md | 2 + 7 files changed, 375 insertions(+), 32 deletions(-) create mode 100644 docs/adr/051-docker-compose-sync-named-volumes.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a82b6942..6898d501 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -256,6 +256,32 @@ jobs: - name: Run backend tests run: bun run test + # ───────────────────────────────────────────────────────────────────────────── + # Compose sync — named volumes in docker-compose.yml must match the embedded + # packaging/electron/resources/docker-compose.yml. Divergence caused the + # v1.0.2 data-wipe bug; catch it early in CI, not just at release time. + # ───────────────────────────────────────────────────────────────────────────── + verify-compose-sync: + name: Verify Compose Volume Sync + runs-on: ubuntu-24.04 + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + + - name: Check named volumes match between compose files + run: | + ROOT_VOLS=$(awk '/^volumes:/{found=1; next} found && /^ [a-zA-Z]/{gsub(/:$/, "", $1); print $1}' docker-compose.yml | sort) + ELECTRON_VOLS=$(awk '/^volumes:/{found=1; next} found && /^ [a-zA-Z]/{gsub(/:$/, "", $1); print $1}' packaging/electron/resources/docker-compose.yml | sort) + echo "Root volumes : $ROOT_VOLS" + echo "Electron volumes: $ELECTRON_VOLS" + if [ "$ROOT_VOLS" != "$ELECTRON_VOLS" ]; then + echo "ERROR: Named volumes out of sync — add missing volumes to packaging/electron/resources/docker-compose.yml" + exit 1 + fi + echo "Compose volume sync OK." + # ───────────────────────────────────────────────────────────────────────────── # Quality Gate — all quality checks must pass before Docker builds run. # Prevents wasting expensive Docker build time on broken commits. @@ -273,6 +299,7 @@ jobs: - build-frontend - test-frontend - test-backend + - verify-compose-sync if: always() steps: - name: Check all gates passed @@ -475,3 +502,24 @@ jobs: - name: Tear down if: always() run: docker compose down -v + + # ───────────────────────────────────────────────────────────────────────────── + # CI Complete — aggregates the Docker-tier results so branch protection has a + # single required-status-check that covers the full pipeline. + # Set "CI Complete" as the required check in branch protection settings. + # ───────────────────────────────────────────────────────────────────────────── + ci-complete: + name: CI Complete + runs-on: ubuntu-24.04 + timeout-minutes: 5 + needs: [trivy-scan, docker-verify, test-live-api-contracts] + if: always() + steps: + - name: Check Docker-tier stages passed + run: | + results='${{ toJson(needs.*.result) }}' + if echo "$results" | grep -qE '"(failure|cancelled)"'; then + echo "CI failed in Docker tier. Job results: $results" + exit 1 + fi + echo "All CI stages complete." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa9249ce..2f765b8a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,6 +60,18 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile + - name: Check named volumes match between compose files + run: | + ROOT_VOLS=$(awk '/^volumes:/{found=1; next} found && /^ [a-zA-Z]/{gsub(/:$/, "", $1); print $1}' docker-compose.yml | sort) + ELECTRON_VOLS=$(awk '/^volumes:/{found=1; next} found && /^ [a-zA-Z]/{gsub(/:$/, "", $1); print $1}' packaging/electron/resources/docker-compose.yml | sort) + echo "Root volumes : $ROOT_VOLS" + echo "Electron volumes: $ELECTRON_VOLS" + if [ "$ROOT_VOLS" != "$ELECTRON_VOLS" ]; then + echo "ERROR: Named volumes out of sync — add missing volumes to packaging/electron/resources/docker-compose.yml before releasing" + exit 1 + fi + echo "Compose volume sync OK." + - name: Check version tag matches package.json run: | TAG="${{ github.event.inputs.tag || github.ref_name }}" diff --git a/docs/adr/051-docker-compose-sync-named-volumes.md b/docs/adr/051-docker-compose-sync-named-volumes.md new file mode 100644 index 00000000..73f907c8 --- /dev/null +++ b/docs/adr/051-docker-compose-sync-named-volumes.md @@ -0,0 +1,119 @@ +--- +title: Docker Compose Named Volumes Sync Policy +type: adr +status: accepted +date: 2026-05-07 +tags: [adr, docker-compose, named-volumes, ci-cd, electron, release, data-loss, v1.0.2-bug] +description: Enforces synchronization of named volumes between root and embedded Docker Compose files via CI gate to prevent data loss on updates +aliases: [compose-sync, volume-sync, named-volumes-policy] +related_code: [".github/workflows/ci.yml", ".github/workflows/release.yml", "docker-compose.yml", "packaging/electron/resources/docker-compose.yml"] +--- + +# ADR-051: Docker Compose Named Volumes Sync Policy + +## Status +Accepted (May 2026) + +## Date +2026-05-07 + +## Context + +### The v1.0.2 Attachment Wipe Bug + +Vision ships as an Electron desktop application. The packaged app includes an embedded Docker Compose file (`packaging/electron/resources/docker-compose.yml`) that is baked into the `.app` bundle at build time. This embedded file mirrors the production Docker Compose configuration (`docker-compose.yml` at repo root). + +In v1.0.2, a named volume was added to the root `docker-compose.yml` to persist attachment files. However, **the embedded Electron compose file was not updated**. When users launched the packaged app: + +1. The app's embedded compose file lacked the new volume definition +2. Docker Compose ran with only the volumes from the embedded file +3. The attachment volume mount failed silently or used a different location +4. On the next update, file locations changed or data was not persisted +5. **Result:** Users lost all attachments + +This was a **critical data loss bug** that should have been caught before release. + +### Synchronization Challenge + +Both compose files serve different purposes: +- **Root `docker-compose.yml`:** Source of truth for production/development deployments +- **Embedded `packaging/electron/resources/docker-compose.yml`:** Packaged into the Electron `.app`; must be manually synced at release + +The sync is manual and easy to forget because: +- The embedded file is in a subdirectory (`packaging/electron/resources/`) +- There is no automated check that they match +- Developers might add a volume to root and forget to propagate it +- The bug only surfaces in packaged builds, not during dev + +## Decision + +**Enforce strict synchronization of named volumes between both Docker Compose files via automated CI checks.** + +### CI Implementation + +1. **`verify-compose-sync` job in ci.yml** (May 2026) + - Runs on every push to main and PR + - Extracts named volumes from both compose files + - Compares them; fails if they diverge + - Added to `quality-gate` prerequisites (blocks Docker build) + +2. **`verify` job in release.yml** (May 2026) + - Runs before any Docker push or packaging + - Same volume sync check as ci.yml + - Must pass before release can proceed + +### Sync Check Logic + +```bash +ROOT_VOLS=$(awk '/^volumes:/{found=1; next} found && /^ [a-zA-Z]/{gsub(/:$/, "", $1); print $1}' docker-compose.yml | sort) +ELECTRON_VOLS=$(awk '/^volumes:/{found=1; next} found && /^ [a-zA-Z]/{gsub(/:$/, "", $1); print $1}' packaging/electron/resources/docker-compose.yml | sort) + +if [ "$ROOT_VOLS" != "$ELECTRON_VOLS" ]; then + echo "ERROR: Named volumes out of sync" + exit 1 +fi +``` + +Parses the YAML `volumes:` section, extracts named volume keys, compares. + +### Workflow Implications + +**Adding a new named volume:** + +1. Edit `docker-compose.yml` — add volume definition +2. **Must also edit** `packaging/electron/resources/docker-compose.yml` — add same volume +3. Push changes to PR +4. CI `verify-compose-sync` job runs + - If volumes match: passes, PR can merge + - If volumes don't match: fails, blocks merge until both files are in sync +5. Once merged to main, pre-release checks and release.yml verify job confirm sync before packaging + +**Key constraint:** *Every new named volume must be added to both files, or the PR will be blocked.* + +## Consequences + +### Positive + +- **Prevents data loss:** Automated guard rail catches sync drift before release +- **Release safety:** `verify` job in release.yml ensures no data-loss bugs ship +- **Developer awareness:** CI failure message educates: "add missing volumes to packaging/electron/resources/docker-compose.yml" +- **Cost:** Minimal — simple YAML parsing, runs in <5 seconds + +### Neutral + +- Adds one more CI gate in quality-gate (verify-compose-sync) +- Developers must remember to sync both files when adding volumes + - Mitigated by CI failure message + - Mitigated by future docs and PR templates + +### Negative + +- None identified; the policy is defensive and low-cost + +## Related + +- [[docs/guides/cicd-pipelines#3-verify-compose-sync--docker-compose-sync-check|CI/CD Pipelines: Verify Compose Sync]] — Detailed CI job documentation +- [[docs/guides/deployment#6-admin-endpoints-security|Deployment Guide: Admin Endpoints Security]] — Cross-references compose sync requirement +- [[docs/architecture/electron#cicd-integration-april-may-2026|Electron Architecture: CI/CD Integration]] — Release workflow context +- `.github/workflows/ci.yml` — `verify-compose-sync` job +- `.github/workflows/release.yml` — `verify` job compose check diff --git a/docs/adr/index.md b/docs/adr/index.md index 893970a7..f7a236bd 100644 --- a/docs/adr/index.md +++ b/docs/adr/index.md @@ -5,8 +5,8 @@ status: active date: 2026-04-23 updated: 2026-05-07 last_modified: 2026-05-07 -tags: [adr, index, architecture, decisions, phase-1, phase-4, phase-5, phase-6, phase-7, security, dependency-slim-down, container-hardening, docker, backup, encryption, aead, aes-256-gcm, codeql, dependabot, rate-limiting, tailwind-v4, css-architecture, dependencies, ai, streaming, useSyncExternalStore, bug-hunt, recovery-hardening, updated-at-constraints, concurrent-backup, ci-cd, secrets-scanning, supply-chain-security, gitleaks, deps-audit, trivy-scan] -description: Architecture Decision Records documenting significant technical choices and their rationale. May 2026: CI supply chain security tooling (ADR-050), Phase 6.1–7 bug hunt recovery hardening (ADR-049), AI Chat module-level stream store (ADR-048), Tailwind CSS v4 migration (ADR-047). +tags: [adr, index, architecture, decisions, phase-1, phase-4, phase-5, phase-6, phase-7, security, dependency-slim-down, container-hardening, docker, backup, encryption, aead, aes-256-gcm, codeql, dependabot, rate-limiting, tailwind-v4, css-architecture, dependencies, ai, streaming, useSyncExternalStore, bug-hunt, recovery-hardening, updated-at-constraints, concurrent-backup, ci-cd, secrets-scanning, supply-chain-security, gitleaks, deps-audit, trivy-scan, docker-compose-sync, named-volumes, data-loss] +description: Architecture Decision Records documenting significant technical choices and their rationale. May 2026: Docker Compose volume sync policy (ADR-051), CI supply chain security tooling (ADR-050), Phase 6.1–7 bug hunt recovery hardening (ADR-049), AI Chat module-level stream store (ADR-048), Tailwind CSS v4 migration (ADR-047). aliases: [ADRs, decisions, architecture decisions] --- @@ -45,6 +45,10 @@ See [[docs/adr/template\|the ADR template]] for the format to use when creating ## Recent Decisions +### 2026-05-07: Docker Compose Named Volumes Sync Policy + +[[docs/adr/051-docker-compose-sync-named-volumes|ADR-051]] — Enforce strict synchronization of named volumes between root `docker-compose.yml` and embedded `packaging/electron/resources/docker-compose.yml` via CI gates. Automated `verify-compose-sync` job in ci.yml runs on every push/PR, extracting and comparing named volumes from both files; fails if divergence detected. Same check added to release.yml `verify` job before any packaging. Prevents v1.0.2 data-loss bug where missing attachment volume in embedded Electron compose caused attachments to vanish on updates. Consequences: new named volumes blocked from merging until both files are synced, release safety guaranteed, developer awareness via CI failure message. + ### 2026-05-07: CI Supply Chain Security Tooling [[docs/adr/050-ci-supply-chain-security-tooling|ADR-050]] — Four-layer supply chain hardening via CI automation: (1) **Secrets scan** — gitleaks in CI (`secrets-scan` job, full history on every push/PR) + pre-commit hook (local dev, staged changes only), blocks merge if credentials/tokens/keys found; (2) **Dependency audit** — `bun audit --audit-level=high` fails on HIGH/CRITICAL vulns, dependency overrides for basic-ftp/ip-address/postcss pinned to safe versions; (3) **Container scan** — Trivy scans Docker image for OS package CVEs, blocks merge if HIGH/CRITICAL found; (4) **Electron hardening** — `session.defaultSession.setPermissionRequestHandler` denies all renderer permission requests (camera, mic, geolocation, clipboard), strict CSP on error.html (no unsafe-inline). Consequences: secrets never reach CI, transitive dependency exploits blocked, container images patched, renderer cannot escalate to system resources even if XSSed. Friction: developer pre-commit hook setup, CI latency +2-3 min, gitleaks false-positive maintenance. diff --git a/docs/architecture/electron.md b/docs/architecture/electron.md index 431dd2c8..d25eb21f 100644 --- a/docs/architecture/electron.md +++ b/docs/architecture/electron.md @@ -417,20 +417,35 @@ On failure at any step: error toast shown, user can manually restore from `pre-u - Manual "Check for Updates" button - Displays latest available version if newer -#### CI/CD Integration (April 2026) +#### CI/CD Integration (April–May 2026) + +**release.yml** (May 2026): +- `verify` job checks: + - Named volumes in `docker-compose.yml` match `packaging/electron/resources/docker-compose.yml` + - Version tag matches both package.json files + - JS dependencies audit passes + - Blocks all other jobs until checks pass -**release.yml:** -- `verify` job (version tag check, lint, tests) blocks all others - `docker` job pushes image with version tag to GHCR - `package-mac` job generates `.sha256` checksum alongside ZIP - Both artifacts uploaded to GitHub release -**ci.yml:** -- Lint, typecheck, unit tests -- `docker-verify` job: build image, start compose, poll health on port 3002 -- `security-scan` job: Trivy scan with SARIF upload to GitHub Security +**ci.yml** (May 2026): +- Early stage: + - `secrets-scan`, `deps-audit`, `pip-audit`, `lint`, `typecheck`, `build-frontend`, `test-frontend`, `test-backend` + - `verify-compose-sync` — compares named volumes between root and embedded compose files + - `quality-gate` — aggregates all early checks, gates expensive Docker build + +- Docker stage (after quality-gate passes): + - `build-image` — builds Docker image once, reused by downstream jobs + - `trivy-scan` — scans image for OS/system CVEs + - `docker-verify` — container health check (build image, start compose, poll health on port 3002) + - `test-live-api-contracts` — validates MSW fixtures against real backend responses + - `ci-complete` — final aggregation gate (set as required status check in branch protection) + +**Security focus:** Compose sync verification prevents the v1.0.2 data-loss bug where omitted volumes caused attachments to vanish on update. -Detailed workflow definitions in [[docs/features/application-updates|Application Updates Feature]]. +Detailed workflow definitions in [[docs/features/application-updates|Application Updates Feature]] and [[docs/guides/cicd-pipelines|CI/CD Pipelines Guide]]. --- diff --git a/docs/guides/cicd-pipelines.md b/docs/guides/cicd-pipelines.md index 06a1a0b7..ca727f66 100644 --- a/docs/guides/cicd-pipelines.md +++ b/docs/guides/cicd-pipelines.md @@ -4,10 +4,10 @@ type: guide status: active date: 2026-04-28 updated: 2026-05-07 -tags: [guide, cicd, github-actions, testing, linting, docker, release, packaging, automation, april-2026, may-2026, security, secrets-scan, deps-audit, trivy-scan] -description: GitHub Actions CI/CD pipelines including continuous integration checks, supply chain security scanning (secrets, dependencies, container images), and release automation with checksums -aliases: [github-actions, ci-cd, pipelines, release-workflow, testing-automation, security-scanning] -related_code: [".github/workflows/ci.yml", ".github/workflows/release.yml", ".gitleaks.toml", ".githooks/pre-commit", "packaging/electron/main.js", "packaging/electron/assets/error.html"] +tags: [guide, cicd, github-actions, testing, linting, docker, release, packaging, automation, april-2026, may-2026, security, secrets-scan, deps-audit, trivy-scan, quality-gate, verify-compose-sync, ci-complete, live-api-contracts, branch-protection] +description: GitHub Actions CI/CD pipelines including continuous integration checks, supply chain security scanning (secrets, dependencies, container images), quality gates, Docker Compose sync verification, and release automation with checksums +aliases: [github-actions, ci-cd, pipelines, release-workflow, testing-automation, security-scanning, quality-gates, branch-protection] +related_code: [".github/workflows/ci.yml", ".github/workflows/release.yml", ".gitleaks.toml", ".githooks/pre-commit", "packaging/electron/main.js", "packaging/electron/assets/error.html", "packaging/electron/resources/docker-compose.yml", "docker-compose.yml"] --- # CI/CD Pipelines @@ -167,7 +167,38 @@ HIGH: CVE-2024-XXXXX (openssl) --- -#### 1. **lint** — Code Quality +#### 3. **verify-compose-sync** — Docker Compose Sync Check + +Verifies that named volumes in `docker-compose.yml` match those in `packaging/electron/resources/docker-compose.yml` (the embedded Electron compose file). + +```yaml +verify-compose-sync: + name: Verify Compose Volume Sync + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - name: Check named volumes match between compose files + run: | + ROOT_VOLS=$(awk '/^volumes:/{found=1; next} found && /^ [a-zA-Z]/{gsub(/:$/, "", $1); print $1}' docker-compose.yml | sort) + ELECTRON_VOLS=$(awk '/^volumes:/{found=1; next} found && /^ [a-zA-Z]/{gsub(/:$/, "", $1); print $1}' packaging/electron/resources/docker-compose.yml | sort) + if [ "$ROOT_VOLS" != "$ELECTRON_VOLS" ]; then + echo "ERROR: Named volumes out of sync" + exit 1 + fi +``` + +**Why it's critical:** + +Named volumes in Docker Compose define persistent data storage. If a volume is added to the root `docker-compose.yml` but omitted from the embedded Electron compose, the packaged app will not share that volume — data stored in the root deployment won't be accessible in the desktop app, or vice versa. This caused the **v1.0.2 bug** where attachments were wiped on update. + +**Policy:** Blocks quality gate if volumes diverge; must add all new named volumes to both compose files before merging. + +**Related:** [[docs/adr/046-named-volumes-attachment-wipe-bug|ADR-046]] (v1.0.2 attachments bug analysis + fix) + +--- + +#### 4. **lint** — Code Quality Runs ESLint on frontend and backend source code. @@ -290,7 +321,38 @@ docker-verify: **Failure:** Indicates a runtime issue; must be resolved before merging. -#### 6. **test-e2e-visual** — Visual Regression (Main Pushes Only) +#### 6. **test-live-api-contracts** — Live API Contract Tests + +Validates that MSW (Mock Service Worker) fixture schemas match actual backend responses. Catches divergence between frontend test stubs and production API contracts. + +```yaml +test-live-api-contracts: + name: Test (Live API Contracts) + needs: [build-image] + if: ${{ !github.event.pull_request.draft }} + steps: + - uses: actions/checkout@v4 + - name: Download image artifact + uses: actions/download-artifact@v4 + - name: Load Docker image + run: docker load < /tmp/vision-ci.tar + - name: Start services with Docker Compose + run: docker compose -f docker-compose.yml up -d + - name: Run live API contract tests + run: cd apps/frontend && bun run vitest run src/test/live-contracts/live-contracts.test.ts +``` + +**What it tests:** +- Frontend MSW fixtures against real backend responses +- All API endpoint contracts (GET, POST, PUT, DELETE) +- Response shape, status codes, and error handling +- Identifies when backend contracts change without updating test stubs + +**Policy:** Blocks merge if fixtures diverge from reality. Runs only on non-draft PRs to avoid CI spam. + +**Failure:** Indicates API contract mismatch; either update backend or update frontend fixtures accordingly. + +#### 7. **test-e2e-visual** — Visual Regression (Main Pushes Only) Captures and compares full-page screenshots of critical pages. Runs **only on push to main**, never on PRs. @@ -324,7 +386,7 @@ test-e2e-visual: - Tagged with `continue-on-error: true` to allow the merge while still capturing visual artifacts for human review - Snapshots are automatically updated on main pushes, establishing the visual baseline for subsequent PR comparisons -#### 7. **security-scan** — Vulnerability Scanning +#### 8. **security-scan** — Vulnerability Scanning Uses Trivy to scan the codebase and dependencies for known vulnerabilities. @@ -356,6 +418,82 @@ security-scan: --- +#### 9. **quality-gate** — Pre-Docker Quality Checkpoint + +Aggregates all pre-Docker quality checks to prevent wasting expensive Docker build cycles on broken commits. + +```yaml +quality-gate: + needs: + - secrets-scan + - deps-audit + - pip-audit + - lint + - typecheck + - build-frontend + - test-frontend + - test-backend + - verify-compose-sync + if: always() + steps: + - name: Check all gates passed + run: | + results='${{ toJson(needs.*.result) }}' + if echo "$results" | grep -qE '"(failure|cancelled)"'; then + echo "Quality gate failed" + exit 1 + fi +``` + +**Checks:** +- All nine prerequisite jobs must pass +- Runs regardless of individual failures (`if: always()`) but fails if any needed job failed +- Blocks expensive Docker image build until quality gates are green + +**Failure:** Indicates an error in earlier stage; must be fixed in that stage before Docker build runs. + +**Design:** Gating expensive Docker build after cheap linting/testing prevents CI resource waste on broken code. + +#### 10. **ci-complete** — Docker-Tier Aggregation + +Final aggregation gate that combines all Docker-intensive CI stages (image scanning, container health, live API contracts). This job should be set as the **single required status check** in GitHub branch protection settings. + +```yaml +ci-complete: + name: CI Complete + needs: [trivy-scan, docker-verify, test-live-api-contracts] + if: always() + steps: + - name: Check Docker-tier stages passed + run: | + results='${{ toJson(needs.*.result) }}' + if echo "$results" | grep -qE '"(failure|cancelled)"'; then + echo "CI failed in Docker tier" + exit 1 + fi +``` + +**Why separate from quality-gate?** +- Quality-gate runs early, gates expensive Docker build, saves CI time +- ci-complete runs after Docker build, aggregates all Docker results +- Branch protection should require only ci-complete, not individual job names +- If ci-complete passes, entire CI pipeline succeeded + +**What it aggregates:** +1. **trivy-scan** — Container image CVE report +2. **docker-verify** — Image builds + backend health check +3. **test-live-api-contracts** — API contracts validated against live backend + +**Setting as required check:** +1. Go to GitHub repository → Settings → Branches +2. Under "Branch protection rules", edit rule for "main" +3. Set "ci-complete" as the single required status check +4. Remove individual job names if previously set (ci-complete is the sufficient check) + +**Failure:** Indicates failure in Docker-tier scanning or container health; must be fixed before merging. + +--- + ## Release Workflow (release.yml) The release workflow publishes new versions of Vision to Docker Container Registry (GHCR) and GitHub Releases (Electron packages). @@ -383,27 +521,32 @@ verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Verify version tag + - name: Check named volumes match between compose files run: | - VERSION_TAG=${GITHUB_REF#refs/tags/v} - PKG_VERSION=$(jq -r '.version' packaging/electron/package.json) - if [[ "$VERSION_TAG" != "$PKG_VERSION" ]]; then - echo "Tag mismatch: $VERSION_TAG vs $PKG_VERSION" + ROOT_VOLS=$(awk '/^volumes:/{found=1; next} found && /^ [a-zA-Z]/{gsub(/:$/, "", $1); print $1}' docker-compose.yml | sort) + ELECTRON_VOLS=$(awk '/^volumes:/{found=1; next} found && /^ [a-zA-Z]/{gsub(/:$/, "", $1); print $1}' packaging/electron/resources/docker-compose.yml | sort) + if [ "$ROOT_VOLS" != "$ELECTRON_VOLS" ]; then + echo "ERROR: Named volumes out of sync" exit 1 fi - - name: Lint - run: bun run lint - - name: Type check - run: bun run typecheck - - name: Test - run: bun run test + - name: Check version tag matches package.json + run: | + TAG="${{ github.event.inputs.tag || github.ref_name }}" + TAG_VERSION="${TAG#v}" + ROOT_VERSION=$(node -p "require('./package.json').version") + PKG_VERSION=$(node -p "require('./packaging/electron/package.json').version") + if [ "$TAG_VERSION" != "$ROOT_VERSION" ] || [ "$TAG_VERSION" != "$PKG_VERSION" ]; then + echo "ERROR: Version mismatch" + exit 1 + fi + - name: Audit JS dependencies + run: bun audit --audit-level=high ``` **Checks:** -1. **Version alignment:** Tag (e.g., `v1.2.3`) must match `packaging/electron/package.json` -2. **Lint:** No style violations -3. **Type check:** All TypeScript types valid -4. **Test:** All unit tests pass (frontend + backend) +1. **Compose volumes sync:** Named volumes in `docker-compose.yml` must match `packaging/electron/resources/docker-compose.yml` +2. **Version alignment:** Tag (e.g., `v1.2.3`) must match both `package.json` and `packaging/electron/package.json` +3. **Dependency audit:** No HIGH or CRITICAL vulnerabilities in release **Failure:** Release is blocked; must fix code and re-tag. diff --git a/docs/guides/deployment.md b/docs/guides/deployment.md index c4d23d5f..93e01123 100644 --- a/docs/guides/deployment.md +++ b/docs/guides/deployment.md @@ -133,6 +133,8 @@ The backend's admin API (`/api/admin/*`) is protected by: **Important:** If you modify `docker-compose.yml` to change the port binding from `127.0.0.1` to `0.0.0.0`, you **must** set `ADMIN_AUTH_TOKEN` to prevent LAN access to dangerous admin operations. See [[docs/adr/037-admin-auth-localhost-fallback|ADR-037]] for details. +**Critical synchronization:** Any named volumes added to `docker-compose.yml` **must also be added** to `packaging/electron/resources/docker-compose.yml` (the embedded Electron app compose file). Omitting a volume from the embedded file causes data loss on updates — see [[docs/guides/cicd-pipelines#3-verify-compose-sync--docker-compose-sync-check|CI/CD Pipelines: Verify Compose Sync]] and [[docs/adr/046-named-volumes-attachment-wipe-bug|ADR-046]] for details. + ### 7. Container Hardening Vision's `docker-compose.yml` includes defense-in-depth hardening: From a22037020f504b5d02fe29d6a46911a4119af636 Mon Sep 17 00:00:00 2001 From: EraPartner Date: Thu, 7 May 2026 22:25:20 +0200 Subject: [PATCH 2/2] ci: upload Trivy SARIF to GitHub Security for code scanning gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Branch protection requires Trivy code scanning results. The trivy-scan job only output table format — never uploaded SARIF, so the gate was perpetually blocked. Run Trivy twice: once as table (exit-code 1, fails pipeline on HIGH/CRITICAL) and once as SARIF (exit-code 0, always runs so results upload even when the table scan finds issues). Upload SARIF via codeql-action/upload-sarif to satisfy the code scanning requirement. --- .github/workflows/ci.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6898d501..d8368566 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -355,6 +355,7 @@ jobs: needs: [build-image] permissions: contents: read + security-events: write # required to upload SARIF to GitHub Security steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 @@ -367,7 +368,7 @@ jobs: - name: Load Docker image run: docker load < /tmp/vision-ci.tar - - name: Run Trivy vulnerability scan + - name: Run Trivy vulnerability scan (table — fail on HIGH/CRITICAL) uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: vision:ci @@ -375,6 +376,23 @@ jobs: severity: HIGH,CRITICAL exit-code: '1' + - name: Run Trivy vulnerability scan (SARIF — upload to GitHub Security) + if: always() + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: vision:ci + format: sarif + output: trivy-results.sarif + severity: HIGH,CRITICAL + exit-code: '0' + + - name: Upload Trivy SARIF to GitHub Security + if: always() + uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4 + with: + sarif_file: trivy-results.sarif + category: trivy + # ───────────────────────────────────────────────────────────────────────────── # Docker verify — spin up compose, hit /health # ─────────────────────────────────────────────────────────────────────────────