Wave 22 — editor confiável: leituras coordenadas, falhas de revisão visíveis, jornada de aceite em navegador #789
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| quality: | |
| name: Quality and security | |
| runs-on: ubuntu-latest | |
| # Raised from 20 to 35 by two Wave 20 changes that landed in the same | |
| # phase, so the reasons are recorded together. | |
| # | |
| # First, the two capture product journeys encode their own fixtures and | |
| # render a 60 s 960x540 h264+aac file rather than reading a committed one. | |
| # Measured on a sixteen-core developer machine against a local PostgreSQL | |
| # 16: the react playback journey ran 21.8-42.8 s over an uninterrupted | |
| # N=10 (median 22.2 s) and 90.4 s once on a loaded box; the insufficient | |
| # evidence journey 20.3-22.0 s over N=3 and 88.2 s under the same load. So | |
| # about twenty seconds each when the machine is idle, four times that when | |
| # it is not. | |
| # | |
| # Second, the long-form synthesis journey started RENDERING the brief's | |
| # two-hour master instead of stopping at its plan. On an eight-core machine | |
| # that step cost 122.8, 125.6, 153.6, 159.3 and 194.5 s of FFmpeg over five | |
| # runs, and the whole suite went from about 213 s to 361.4, 400.1, 412.8 | |
| # and 559.1 s over four runs. | |
| # | |
| # The 35 minutes are a BUDGET, not a measurement. A hosted runner has fewer | |
| # cores than either machine those seconds came from, this repository has no | |
| # CI history to calibrate against, and how much of the old cap the job was | |
| # already using is not observable from a developer machine. If a real run | |
| # reports the job finishing well inside this, bring it back down. | |
| timeout-minutes: 35 | |
| env: | |
| CI: true | |
| APOLLO_API_ENVIRONMENT: production | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public | |
| services: | |
| postgres: | |
| image: pgvector/pgvector:0.8.5-pg16-trixie | |
| env: | |
| POSTGRES_DB: apollo_v2 | |
| POSTGRES_USER: apollo | |
| POSTGRES_PASSWORD: apollo-ci | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U apollo -d apollo_v2" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: | | |
| package-lock.json | |
| remotion/package-lock.json | |
| # `git diff --check` is required by TODO.md, which lists it among the | |
| # commands that must pass before a slice is called finished — "typecheck, | |
| # `git diff --check`, audits sem vulnerabilidades, migration validation, | |
| # bundle Remotion e build Next.js passam" — and repeats it in two more | |
| # acceptance lists. Four reports under `docs/quality` then record it as | |
| # approved: proof-mode-v1.md, batch-edit-v1.md, batch-partial-retry-v1.md | |
| # and source-deconstruction-v1.md. | |
| # | |
| # It is NOT one of the nine gates AGENTS.md enumerates. An earlier | |
| # version of this comment attributed the requirement there, in the one | |
| # sentence written to show the gate was mandated rather than invented; | |
| # `grep -n "diff --check" AGENTS.md` returns nothing. | |
| # | |
| # Every one of those documents reports the check as having passed on | |
| # somebody's machine, and until this step existed nothing made the next | |
| # machine repeat it: `grep -rn "diff --check" .github/ scripts/ | |
| # package.json` came back empty while the checked-out tree carried seven | |
| # blank lines at end of file. A gate nobody runs is a sentence in a | |
| # document. | |
| # | |
| # `lint:whitespace` diffs against `4b825dc…`, git's empty tree, so every | |
| # line in the repository is read as added and the whole tree is checked — | |
| # not just a branch's own diff, which has no base to compare against on a | |
| # push to main. Which rules apply per path is declared in `.gitattributes`. | |
| - name: Reject trailing whitespace and blank lines at end of file | |
| run: npm run lint:whitespace | |
| - name: Prepare integration environment | |
| run: touch .env | |
| - name: Install locked dependencies | |
| run: npm ci | |
| - name: Install locked renderer dependencies | |
| run: npm ci --prefix remotion | |
| - name: Audit dependencies | |
| run: npm run security:audit | |
| - name: Audit renderer dependencies | |
| run: npm run security:audit:remotion | |
| - name: Typecheck | |
| run: npm run typecheck | |
| - name: Verify architecture boundaries | |
| run: npm run lint | |
| - name: Lint application and tests | |
| run: npm run lint:code | |
| - name: Verify canonical domain language | |
| run: npm run domain-language:validate | |
| - name: Verify isolated local infrastructure contracts | |
| run: npm run infra:validate | |
| - name: Verify pinned platform versions | |
| run: npm run platform:validate | |
| - name: Run unit and contract tests | |
| run: npm test | |
| - name: Validate public API contracts | |
| run: npm run api:v1:validate | |
| - name: Verify UI capability parity report | |
| run: npm run api:parity:validate | |
| - name: Validate database schema and migrations | |
| run: npm run db:v2:validate | |
| - name: Apply database migrations | |
| run: npm run db:v2:migrate:deploy | |
| - name: Prepare synthetic phase gate PostgreSQL proof database | |
| run: | | |
| postgres_container="$(docker ps --filter 'ancestor=pgvector/pgvector:0.8.5-pg16-trixie' --format '{{.ID}}')" | |
| test -n "$postgres_container" | |
| docker exec -e PGPASSWORD=apollo-ci "$postgres_container" createdb -U apollo apollo_synthetic_gate_e2e | |
| npm run db:v2:migrate:deploy | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_synthetic_gate_e2e?schema=public&application_name=apollo-video-e2e-synthetic-phase-gate-migrate-${{ github.run_id }}&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run synthetic phase gate PostgreSQL proof | |
| run: npm run test:integration:synthetic-phase-gate-pg | |
| env: | |
| APOLLO_SYNTHETIC_PHASE_GATE_PG_E2E: "1" | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_synthetic_gate_e2e?schema=public&application_name=apollo-video-e2e-synthetic-phase-gate-${{ github.run_id }}&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Verify synthetic phase gate PostgreSQL cleanup | |
| if: always() | |
| run: | | |
| postgres_container="$(docker ps --filter 'ancestor=pgvector/pgvector:0.8.5-pg16-trixie' --format '{{.ID}}')" | |
| test -n "$postgres_container" | |
| orphans="$(docker exec -e PGPASSWORD=apollo-ci "$postgres_container" psql -U apollo -d postgres -Atc "select count(*) from pg_stat_activity where application_name = '$APOLLO_SYNTHETIC_PHASE_GATE_APP_NAME'")" | |
| test "$orphans" = 0 | |
| env: | |
| APOLLO_SYNTHETIC_PHASE_GATE_APP_NAME: apollo-video-e2e-synthetic-phase-gate-${{ github.run_id }} | |
| - name: Run source cleanup and separation FFmpeg goldens | |
| run: npm run test:integration:source-cleanup-goldens | |
| - name: Run synthetic production persistence tests | |
| run: npm run test:integration:synthetic-production-db | |
| - name: Run durable TTS-to-avatar provider journey | |
| run: npm run test:integration:provider-tts-avatar | |
| - name: Run bounded localization provider integration tests | |
| run: npm run test:integration:localization-provider | |
| - name: Run music-led montage analysis integration tests | |
| run: npm run test:integration:music-led-montage | |
| - name: Run vertical upload-to-proxy smoke | |
| run: npm run test:integration:vertical-smoke | |
| env: | |
| APOLLO_V2_VERTICAL_SMOKE: "1" | |
| - name: Run FFmpeg integration tests | |
| run: npm run test:integration:media | |
| - name: Run editorial renderer goldens | |
| run: npm run test:integration:editorial-renderer | |
| - name: Run transcript retiming worker goldens | |
| run: npm run test:integration:transcript-rate | |
| - name: Run long-form extraction golden | |
| run: npm run test:integration:contiguous-extraction-golden | |
| - name: Bundle Remotion renderer | |
| run: npm run remotion:build | |
| - name: Run synthetic presenter provider-to-MP4 golden | |
| run: npm run test:integration:synthetic-presenter | |
| - name: Run hybrid StoryPlan provider-to-MP4 golden | |
| run: npm run test:integration:hybrid-story | |
| - name: Run authorized Remotion smoke render | |
| run: npm run test:integration:render | |
| - name: Run proof mode goldens and worker-produced proof MP4s | |
| run: npm run test:integration:proof-mode-goldens | |
| env: | |
| APOLLO_PROOF_MODE_VISUAL_E2E: "1" | |
| - name: Build production application | |
| run: npm run build | |
| - name: Provision and migrate isolated contamination E2E database | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_contamination_e2e?schema=public&application_name=apollo-video-e2e-contamination-migrate-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| run: | | |
| postgres_container="$(docker ps --filter 'ancestor=pgvector/pgvector:0.8.5-pg16-trixie' --format '{{.ID}}')" | |
| test -n "$postgres_container" | |
| docker exec -e PGPASSWORD=apollo-ci "$postgres_container" createdb -U apollo apollo_contamination_e2e | |
| npm run db:v2:migrate:deploy | |
| - name: Run contamination-to-source-separation journey | |
| run: npm run test:integration:contamination-reports | |
| env: | |
| APOLLO_CONTAMINATION_E2E: "1" | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_contamination_e2e?schema=public&application_name=apollo-video-e2e-contamination-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Verify zero orphan contamination E2E backends | |
| if: always() | |
| run: | | |
| postgres_container="$(docker ps --filter 'ancestor=pgvector/pgvector:0.8.5-pg16-trixie' --format '{{.ID}}')" | |
| test -n "$postgres_container" | |
| orphans="$(docker exec -e PGPASSWORD=apollo-ci "$postgres_container" psql -U apollo -d postgres -Atc "select count(*) from pg_stat_activity where application_name = 'apollo-video-e2e-contamination-ci'")" | |
| test "$orphans" = 0 | |
| - name: Publish public API documentation artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apollo-public-api-${{ github.sha }} | |
| path: generated/public-api | |
| if-no-files-found: error | |
| retention-days: 30 | |
| - name: Run Prisma integration tests | |
| run: npm run test:integration:prisma | |
| - name: Run media artifact persistence tests | |
| run: npm run test:integration:artifacts | |
| - name: Run media transfer audit persistence tests | |
| run: npm run test:integration:media-transfer-audit | |
| - name: Run asset rights audit persistence tests | |
| run: npm run test:integration:asset-rights-audit | |
| - name: Run synthetic script plan persistence tests | |
| run: npm run test:integration:synthetic-script-plan | |
| - name: Run per-block generation cache and retry tests | |
| run: npm run test:integration:synthetic-block-generation | |
| - name: Run block audio concatenation and consolidated master tests | |
| run: npm run test:integration:synthetic-block-compilation | |
| - name: Run synthetic cache decision ledger tests | |
| run: npm run test:integration:synthetic-cache-decision | |
| - name: Run synthetic master asset persistence tests | |
| run: npm run test:integration:synthetic-master-asset | |
| - name: Run transformation brief and provider registry persistence tests | |
| run: npm run test:integration:transformation-registry | |
| - name: Run review-derived cleanup mask persistence tests | |
| run: npm run test:integration:review-cleanup-mask | |
| - name: Run Wave 18 editorial synthesis journey | |
| run: npm run test:e2e:editorial-synthesis | |
| - name: Run Wave 18 heterogeneous capture session journey | |
| run: npm run test:e2e:capture-heterogeneous | |
| - name: Run Wave 18 insufficient sync evidence journey | |
| run: npm run test:e2e:capture-insufficient-evidence | |
| - name: Run Wave 19 capture protocol, marker and diagnostic journeys | |
| run: npm run test:e2e:sync-diagnostic-journey | |
| - name: Run Wave 19 marker rendering against ffprobe | |
| run: npm run test:integration:sync-marker-render | |
| - name: Run Wave 19 clean marker detection fixtures | |
| run: npm run test:integration:marker-detection | |
| - name: Run Wave 19 marker robustness fixtures | |
| run: npm run test:integration:marker-robustness | |
| - name: Run Wave 20 falsification suite | |
| run: npm run test:unit:wave20-falsification | |
| - name: Run Wave 20 ffmpeg binary resolution guard | |
| run: npm run test:unit:ffmpeg-binary | |
| - name: Run Wave 20 multicam direction render against ffprobe | |
| run: npm run test:integration:multicam-direction-render | |
| # The calibration source for `SCREEN_ACTIVITY_SATURATION_BPS = 400`. Spec | |
| # 05 §29.1 and the FR-150 traceability row both quote this suite's | |
| # measured 0/4/103/137/3151 bps by name, and the script existed in | |
| # package.json without a CI step while `.integration.mjs` keeps it out of | |
| # `npm test` — so the two documents cited a number nothing re-measured. | |
| - name: Run Wave 20 multicam visual evidence against real pixels | |
| run: npm run test:integration:multicam-visual-evidence | |
| - name: Run Wave 20 multicam silence evidence against real samples | |
| run: npm run test:integration:multicam-silence-evidence | |
| # The composition root itself, executed. It needs `tsx` rather than the | |
| # default gate's strip-only loader because `repository-factory.ts` pulls | |
| # modules with TypeScript parameter properties. No database: PrismaClient | |
| # connects lazily, so the URL points at a closed port on purpose. | |
| - name: Run Wave 20 multicam composition root | |
| run: npm run test:integration:multicam-composition | |
| - name: Run Wave 20 react playback fingerprint fixture | |
| run: npm run test:integration:playback-map-fingerprint | |
| - name: Run Wave 20 colour measurement against decoded frames | |
| run: npm run test:integration:color-measurement | |
| - name: Run Wave 20 multicam colour match against real pixels | |
| run: npm run test:integration:color-match | |
| - name: Run Wave 20 colour visual evaluations and keep the artifacts | |
| id: color-visual-evaluations | |
| run: npm run test:integration:color-visual-evaluations | |
| env: | |
| APOLLO_COLOR_VISUAL_EVAL_OUTPUT: ${{ runner.temp }}/color-visual-evaluations | |
| # `always()` alone made this step demand files from a job that had failed | |
| # before the evaluations ran, so an unrelated failure earlier in the job | |
| # was reported twice and the second report named the wrong step. It now | |
| # runs whenever the producing step ran — including when that step itself | |
| # failed, which is exactly when its partial output is worth keeping — and | |
| # is skipped when the evaluations never started. `if-no-files-found` | |
| # stays `error`: if the suite ran, the files must exist. | |
| - name: Publish Wave 20 colour visual evaluation artifacts | |
| if: always() && steps.color-visual-evaluations.conclusion != 'skipped' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apollo-color-visual-evaluations-${{ github.sha }} | |
| path: ${{ runner.temp }}/color-visual-evaluations | |
| if-no-files-found: error | |
| retention-days: 30 | |
| - name: Run Wave 20 multi-range synthesis render against ffprobe | |
| run: npm run test:integration:synthesis-render | |
| - name: Run Wave 20 repository round trip against the schema | |
| run: npm run test:e2e:wave20-journey | |
| - name: Run Wave 20 capture sync worker against generated audio | |
| run: npm run test:integration:capture-sync-worker | |
| - name: Run Wave 19 sync diagnostic persistence against PostgreSQL | |
| run: npm run test:e2e:sync-diagnostic-persistence | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-sync-diagnostic-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_SYNC_DIAGNOSTIC_E2E: "1" | |
| - name: Run Wave 20 multicam, colour and playback persistence against PostgreSQL | |
| run: npm run test:e2e:wave20-persistence | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-wave20-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_WAVE20_PERSISTENCE_E2E: "1" | |
| - name: Run Wave 20 multicam direction round trip against PostgreSQL | |
| run: npm run test:e2e:multicam-direction | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-multicam-direction-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_MULTICAM_DIRECTION_E2E: "1" | |
| - name: Run Wave 20 react playback journey against PostgreSQL | |
| run: npm run test:e2e:playback-map | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-playback-map-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_PLAYBACK_MAP_E2E: "1" | |
| - name: Run Wave 20 react playback product journey against PostgreSQL | |
| run: npm run test:e2e:react-playback-journey | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-react-playback-journey-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_REACT_PLAYBACK_JOURNEY_E2E: "1" | |
| APOLLO_REACT_PLAYBACK_JOURNEY_OUTPUT: ${{ runner.temp }}/react-playback-journey | |
| # The suite reads the MP4 back with ffprobe before this runs, so the | |
| # upload is for a human, not for the assertion. `warn` rather than | |
| # `error`: a journey that failed before the render must show its own | |
| # failure, not a second one about a missing file. | |
| - name: Publish the react playback journey MP4 | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apollo-react-playback-journey-${{ github.sha }} | |
| path: ${{ runner.temp }}/react-playback-journey | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| - name: Run Wave 20 insufficient evidence product journey against PostgreSQL | |
| run: npm run test:e2e:insufficient-evidence-journey | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-insufficient-evidence-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_INSUFFICIENT_EVIDENCE_E2E: "1" | |
| - name: Run Wave 20 capture sync worker persistence against PostgreSQL | |
| run: npm run test:e2e:capture-sync-persistence | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-capture-sync-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_CAPTURE_SYNC_E2E: "1" | |
| - name: Run Wave 20 colour critic gate against PostgreSQL | |
| run: npm run test:e2e:color-critic | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-color-critic-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_COLOR_CRITIC_E2E: "1" | |
| - name: Run Wave 20 multicam long-form phase gate against PostgreSQL | |
| run: npm run test:e2e:multicam-longform-gate | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-multicam-gate-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_MULTICAM_GATE_E2E: "1" | |
| - name: Run Wave 20 phase gate journey through the published /v1 routes | |
| run: npm run test:e2e:phase-gate-journey | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-phase-gate-journey-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_PHASE_GATE_E2E: "1" | |
| - name: Run Wave 20 two-hour and ten-minute masters to rendered two-minute cuts against ffprobe and PostgreSQL | |
| run: npm run test:e2e:longform-synthesis | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-longform-synthesis-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_LONGFORM_SYNTHESIS_E2E: "1" | |
| # Both capture journeys refuse a database whose name does not say `e2e` | |
| # (`assertIsolatedDatabase`, tests/v2/helpers/capture-journey.mjs): each | |
| # deletes every row of its own workspace before and after the run, and | |
| # that guard is what keeps a misconfigured environment from doing the same | |
| # to a shared server. The service container is disposable, so they get | |
| # their own database inside it instead of the guard being relaxed — the | |
| # shape the long-form workflow step already uses on the Compose cluster. | |
| # | |
| # The database is created THROUGH the service container, the way the | |
| # orphan check above already does it (`docker exec ... psql`), and not | |
| # with a `psql` on the runner: a bare `psql` would be the only invocation | |
| # in this workflow that depends on the runner image shipping a PostgreSQL | |
| # client, and the day it stops shipping one this step fails before the | |
| # journeys have a chance to say anything. | |
| - name: Create the disposable capture journey database | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2_e2e_capture?schema=public | |
| run: | | |
| set -euo pipefail | |
| postgres_container="$(docker ps --filter 'ancestor=pgvector/pgvector:0.8.5-pg16-trixie' --format '{{.ID}}')" | |
| test -n "$postgres_container" | |
| docker exec -e PGPASSWORD=apollo-ci "$postgres_container" \ | |
| psql -U apollo -d postgres -c 'create database apollo_v2_e2e_capture' | |
| npm run db:v2:migrate:deploy | |
| - name: Run Wave 20 podcast multicam product journey against PostgreSQL | |
| run: npm run test:e2e:podcast-multicam-journey | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2_e2e_capture?schema=public&application_name=apollo-video-e2e-podcast-multicam-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_PODCAST_MULTICAM_E2E: "1" | |
| APOLLO_PODCAST_MULTICAM_OUTPUT: ${{ runner.temp }}/podcast-multicam-journey | |
| - name: Run Wave 20 teacher and screen product journey against PostgreSQL | |
| run: npm run test:e2e:teacher-screen-journey | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2_e2e_capture?schema=public&application_name=apollo-video-e2e-teacher-screen-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_TEACHER_SCREEN_E2E: "1" | |
| APOLLO_TEACHER_SCREEN_OUTPUT: ${{ runner.temp }}/teacher-screen-journey | |
| # Each suite reads its own MP4 back with ffprobe and samples its pixels | |
| # before this runs, so the upload is for a person, not for the assertion. | |
| # `warn` rather than `error`: a journey that failed before the render must | |
| # show its own failure, not a second one about a missing file. | |
| - name: Publish the capture journey MP4s | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apollo-capture-journeys-${{ github.sha }} | |
| path: | | |
| ${{ runner.temp }}/podcast-multicam-journey | |
| ${{ runner.temp }}/teacher-screen-journey | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| - name: Run Wave 19 capture protocol and diagnostic browser journey | |
| run: npm run test:e2e:sync-diagnostic-browser | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-sync-browser-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_SYNC_DIAGNOSTIC_BROWSER_E2E: "1" | |
| - name: Run Wave 20 multicam long-form phase gate browser journey | |
| run: npm run test:e2e:multicam-longform-gate-browser | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-multicam-gate-browser-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_MULTICAM_GATE_BROWSER_E2E: "1" | |
| - name: Run Wave 20 multicam, colour and playback operator browser journey | |
| run: npm run test:e2e:wave20-browser | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-wave20-browser-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_WAVE20_BROWSER_E2E: "1" | |
| # Wave 22: the journey a person actually meets — login, list, card, | |
| # play/pause/seek, one annotation and its replay, the reads each open | |
| # costs under production governance defaults, and the 409 / 429 / 401 | |
| # boundaries. Evidence JSON and screenshots are uploaded below; nothing | |
| # in them is a cookie, a token or a signed URL. | |
| - name: Run Wave 22 editor reliability browser journey | |
| id: editor-reliability | |
| run: npm run test:e2e:editor-reliability | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2_e2e_capture?schema=public&application_name=apollo-video-e2e-editor-reliability-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_EDITOR_RELIABILITY_E2E: "1" | |
| APOLLO_EDITOR_RELIABILITY_EVIDENCE_DIR: ${{ runner.temp }}/editor-reliability | |
| APOLLO_EDITOR_RELIABILITY_KEEP_EVIDENCE: "1" | |
| - name: Publish Wave 22 editor reliability evidence | |
| if: always() && steps.editor-reliability.conclusion != 'skipped' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apollo-editor-reliability-${{ github.sha }} | |
| path: ${{ runner.temp }}/editor-reliability | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| - name: Run Wave 18 capture sessions browser journey | |
| run: npm run test:e2e:capture-sessions-browser | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-capture-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_CAPTURE_SESSIONS_E2E: "1" | |
| - name: Run transformation production journey | |
| run: npm run test:e2e:transformation-production | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci@127.0.0.1:5432/apollo_v2?schema=public&application_name=apollo-video-e2e-transformation-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_TRANSFORMATION_E2E_SCREENSHOT: ${{ runner.temp }}/transformation-reviewed-and-accepted.png | |
| APOLLO_TRANSFORMATION_E2E_DEBUG: "1" | |
| - name: Publish transformation review visual evidence | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: transformation-review-${{ github.sha }} | |
| path: ${{ runner.temp }}/transformation-reviewed-and-accepted.png | |
| if-no-files-found: error | |
| retention-days: 30 | |
| - name: Run novelty budget decision persistence tests | |
| run: npm run test:integration:novelty-budget | |
| - name: Run presenter profile lifecycle tests | |
| run: npm run test:integration:presenter-lifecycle | |
| - name: Run durable operation persistence tests | |
| run: npm run test:integration:operations | |
| - name: Run webhook persistence tests | |
| run: npm run test:integration:webhooks | |
| - name: Run public API integration tests | |
| run: npm run test:integration:api | |
| local-infrastructure: | |
| name: Isolated Compose infrastructure | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 50 | |
| env: | |
| COMPOSE_PROJECT_NAME: apollo-f0-${{ github.run_id }} | |
| POSTGRES_DB: apollo_v2 | |
| POSTGRES_USER: apollo | |
| POSTGRES_PASSWORD: apollo-ci-compose-password | |
| POSTGRES_TUNNEL_PORT: 55432 | |
| MINIO_ROOT_USER: apollo-ci-storage | |
| MINIO_ROOT_PASSWORD: apollo-ci-storage-password | |
| MINIO_API_PORT: 59000 | |
| MINIO_CONSOLE_PORT: 59001 | |
| APOLLO_V2_S3_BUCKET: apollo-video-ci | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2?schema=public&application_name=apollo-video-compose-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| V2_DOCKER_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@postgres:5432/apollo_v2?schema=public&application_name=apollo-video-compose-runtime-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| APOLLO_BUILD_REVISION: compose-ci | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| # The Wave 20 journeys spawn `worker:v2:capture-sync`, and every | |
| # `worker:v2:*` script in this repository runs `tsx --env-file=.env`, | |
| # which exits 9 when the file is absent. The quality job has prepared it | |
| # since Wave 18; this job did not, and the podcast journey it gained in | |
| # Wave 20 reported `driver exited 9: node: .env: not found` on a runner | |
| # where nothing had created one. | |
| - name: Prepare integration environment | |
| run: touch .env | |
| - name: Install locked dependencies | |
| run: npm ci | |
| - name: Start isolated PostgreSQL and MinIO | |
| run: | | |
| docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml up -d --wait postgres minio | |
| docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml up --no-deps minio-init | |
| - name: Migrate clean Compose PostgreSQL | |
| run: npm run db:v2:migrate:deploy | |
| - name: Create the isolated proof integrity E2E database | |
| run: | | |
| exists="$(docker compose -f infra/postgres/compose.yml exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select 1 from pg_database where datname = 'apollo_video_v2_e2e'")" | |
| test "$exists" = 1 || docker compose -f infra/postgres/compose.yml exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres createdb -U "$POSTGRES_USER" apollo_video_v2_e2e | |
| - name: Migrate the isolated proof integrity E2E database | |
| run: npm run db:v2:migrate:deploy | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_video_v2_e2e?schema=public&application_name=apollo-video-e2e-proof-migrate-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run localization translation preflight persistence proof | |
| run: npm run test:integration:localization-preflight | |
| env: | |
| APOLLO_RUN_LOCALIZATION_PREFLIGHT_PG_E2E: "1" | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_video_v2_e2e?schema=public&application_name=apollo-video-e2e-localization-preflight-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run music analysis authenticated worker persistence proof | |
| run: npm run test:integration:music-analysis | |
| env: | |
| APOLLO_RUN_MUSIC_ANALYSIS_PG_E2E: "1" | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_video_v2_e2e?schema=public&application_name=apollo-video-e2e-music-analysis-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run music montage atomic publication persistence proof | |
| run: npm run test:integration:music-montage | |
| env: | |
| APOLLO_RUN_MUSIC_MONTAGE_PG_E2E: "1" | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_video_v2_e2e?schema=public&application_name=apollo-video-e2e-music-montage-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run proof integrity gate E2E against /v1 and PostgreSQL | |
| run: npm run test:e2e:proof-integrity | |
| env: | |
| APOLLO_PROOF_INTEGRITY_E2E: "1" | |
| APOLLO_E2E_SERVER_MODE: dev | |
| APOLLO_API_ENVIRONMENT: production | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_video_v2_e2e?schema=public&application_name=apollo-video-e2e-proof-integrity-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Provision the isolated ProofMode E2E database | |
| run: | | |
| docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres createdb -U "$POSTGRES_USER" apollo_v2_e2e | |
| npm run db:v2:migrate:deploy | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2_e2e?schema=public&application_name=apollo-video-e2e-ci-proof-modes&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| # The harness already backs off and retries a legitimate 429 | |
| # (GOVERNANCE_LIMIT_EXCEEDED). APOLLO_GOVERNANCE_ANOMALY_REQUEST_MINIMUM | |
| # additionally declares the size of this one supervised burst — 66 | |
| # authenticated /v1 calls — so the request-rate anomaly does not read a | |
| # scripted E2E as an attack and cost the job minutes of backoff. It is an | |
| # env knob that already exists (governanceAnomalyPolicyFromEnvironment); | |
| # no product default changes, and every other governance control | |
| # (rate limit, concurrency, quota, spend and error-rate anomaly) stays at | |
| # its production value for this run. | |
| - name: Run Phase 2 three-file reuse, validated hook, testimonial proof and ProofMode E2E | |
| run: npm run test:e2e:proof-modes | |
| env: | |
| APOLLO_COMPATIBILITY_GRAPH_E2E: "1" | |
| APOLLO_E2E_SERVER_MODE: dev | |
| APOLLO_API_ENVIRONMENT: production | |
| APOLLO_GOVERNANCE_ANOMALY_REQUEST_MINIMUM: "200" | |
| APOLLO_PROTECTED_PAYLOAD_KEY_ID: ci-protected-payload | |
| APOLLO_PROTECTED_PAYLOAD_KEY: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2_e2e?schema=public&application_name=apollo-video-e2e-ci-proof-modes&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Verify no orphan ProofMode E2E backends remain | |
| if: always() | |
| run: | | |
| orphans="$(docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name = 'apollo-video-e2e-ci-proof-modes'")" | |
| test "$orphans" = 0 | |
| - name: Run export matrix persistence and five-format worker E2E | |
| env: | |
| APOLLO_EXPORT_MATRIX_DB: "1" | |
| APOLLO_EXPORT_MATRIX_E2E: "1" | |
| APOLLO_E2E_SERVER_MODE: dev | |
| APOLLO_API_ENVIRONMENT: production | |
| APOLLO_V2_ARTIFACT_ROOT: ${{ runner.temp }}/apollo-v2-export-matrix | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2_e2e_export_matrix?schema=public&application_name=apollo-video-e2e-export-matrix-${{ github.run_id }}&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| run: | | |
| set -euo pipefail | |
| compose="docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml" | |
| preflight="$($compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name like 'apollo-video-e2e-%'")" | |
| test "$preflight" = 0 | |
| $compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres createdb -U "$POSTGRES_USER" apollo_v2_e2e_export_matrix | |
| npm run db:v2:migrate:deploy | |
| npm run test:integration:export-matrix | |
| npm run test:e2e:export-matrix | |
| - name: Verify zero orphan export matrix E2E backends | |
| if: always() | |
| env: | |
| APOLLO_EXPORT_MATRIX_APP_NAME: apollo-video-e2e-export-matrix-${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| compose="docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml" | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| backends="$($compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name = '$APOLLO_EXPORT_MATRIX_APP_NAME'")" | |
| if test "$backends" = 0; then exit 0; fi | |
| sleep 1 | |
| done | |
| $compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select pid, application_name, state from pg_stat_activity where application_name = '$APOLLO_EXPORT_MATRIX_APP_NAME'" | |
| exit 1 | |
| - name: Resume long-form indexing on a disposable Compose database | |
| env: | |
| APOLLO_LONG_FORM_WORKFLOW_E2E: "1" | |
| APOLLO_API_ENVIRONMENT: production | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2_e2e_long_form?schema=public&application_name=apollo-video-e2e-long-form-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| GROQ_API_KEY: ci-not-a-real-groq-credential-000 | |
| GROQ_TRANSCRIBE_MODEL: whisper-large-v3 | |
| GROQ_TRANSCRIBE_COST_MINOR_UNITS_PER_HOUR: "1" | |
| OPENAI_API_KEY: ci-not-a-real-openai-credential-0 | |
| OPENAI_DIARIZATION_MODEL: gpt-4o-transcribe-diarize | |
| OPENAI_DIARIZATION_COST_MINOR_UNITS_PER_HOUR: "1" | |
| APOLLO_PROTECTED_PAYLOAD_KEY_ID: ci-protected-payload | |
| APOLLO_PROTECTED_PAYLOAD_KEY: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | |
| run: | | |
| set -euo pipefail | |
| administer() { | |
| docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "$1" | |
| } | |
| preflight="$(administer "select count(*) from pg_stat_activity where application_name like 'apollo-video-e2e-%'")" | |
| printf 'preflight e2e backends: %s\n' "$preflight" | |
| test "$preflight" = 0 | |
| administer "create database apollo_v2_e2e_long_form" | |
| npm run db:v2:migrate:deploy | |
| npm run test:e2e:long-form-index-workflow | |
| orphans=1 | |
| for _ in 1 2 3 4 5 6 7 8 9 10; do | |
| orphans="$(administer "select count(*) from pg_stat_activity where application_name like 'apollo-video-e2e-long-form-ci%'")" | |
| if test "$orphans" = 0; then break; fi | |
| sleep 1 | |
| done | |
| printf 'postflight e2e backends: %s\n' "$orphans" | |
| test "$orphans" = 0 | |
| - name: Reconstruct proxy from PostgreSQL and versioned MinIO only | |
| run: node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON --test tests/v2/prisma-vertical-smoke.integration.mjs | |
| env: | |
| APOLLO_V2_VERTICAL_SMOKE: "1" | |
| APOLLO_V2_S3_RECONSTRUCTION_SMOKE: "1" | |
| APOLLO_V2_ARTIFACT_STORAGE_DRIVER: s3 | |
| APOLLO_V2_ARTIFACT_ROOT: ${{ runner.temp }}/apollo-v2-s3-staging | |
| APOLLO_V2_RENDER_WORK_ROOT: ${{ runner.temp }}/apollo-v2-s3-render | |
| APOLLO_V2_S3_ENDPOINT: http://127.0.0.1:59000 | |
| APOLLO_V2_S3_REGION: us-east-1 | |
| APOLLO_V2_S3_ACCESS_KEY_ID: ${{ env.MINIO_ROOT_USER }} | |
| APOLLO_V2_S3_SECRET_ACCESS_KEY: ${{ env.MINIO_ROOT_PASSWORD }} | |
| APOLLO_V2_S3_FORCE_PATH_STYLE: "true" | |
| APOLLO_V2_S3_ALLOW_INSECURE_HTTP: "true" | |
| - name: Run durable TTS-to-avatar journey against PostgreSQL and versioned MinIO | |
| run: npm run test:e2e:provider-journey | |
| env: | |
| APOLLO_V2_ARTIFACT_STORAGE_DRIVER: s3 | |
| APOLLO_V2_S3_ENDPOINT: http://127.0.0.1:59000 | |
| APOLLO_V2_S3_REGION: us-east-1 | |
| APOLLO_V2_S3_ACCESS_KEY_ID: ${{ env.MINIO_ROOT_USER }} | |
| APOLLO_V2_S3_SECRET_ACCESS_KEY: ${{ env.MINIO_ROOT_PASSWORD }} | |
| APOLLO_V2_S3_FORCE_PATH_STYLE: "true" | |
| APOLLO_V2_S3_ALLOW_INSECURE_HTTP: "true" | |
| # Exclusive per-run bucket: the test itself creates it, enables | |
| # versioning, proves zero orphan objects and deletes it afterwards. | |
| APOLLO_V2_S3_BUCKET: apollo-journey-${{ github.run_id }}-${{ github.run_attempt }} | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2?schema=public&application_name=apollo-video-e2e-provider-journey-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run block plan journey through /v1, durable workers and versioned MinIO | |
| run: npm run test:e2e:block-plan-journey | |
| env: | |
| APOLLO_V2_ARTIFACT_STORAGE_DRIVER: s3 | |
| APOLLO_V2_S3_ENDPOINT: http://127.0.0.1:59000 | |
| APOLLO_V2_S3_REGION: us-east-1 | |
| APOLLO_V2_S3_ACCESS_KEY_ID: ${{ env.MINIO_ROOT_USER }} | |
| APOLLO_V2_S3_SECRET_ACCESS_KEY: ${{ env.MINIO_ROOT_PASSWORD }} | |
| APOLLO_V2_S3_FORCE_PATH_STYLE: "true" | |
| APOLLO_V2_S3_ALLOW_INSECURE_HTTP: "true" | |
| # Exclusive per-run bucket: the test creates it, enables versioning, | |
| # proves zero orphan objects and deletes it afterwards. | |
| APOLLO_V2_S3_BUCKET: apollo-blockplan-${{ github.run_id }}-${{ github.run_attempt }} | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2?schema=public&application_name=apollo-video-e2e-block-plan-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| # The Wave 20 product journeys run in the `quality` job, which has | |
| # PostgreSQL and no MinIO, and every one of them used to pin | |
| # APOLLO_V2_ARTIFACT_STORAGE_DRIVER=local inside the test file — so the | |
| # briefing's "PostgreSQL 16 AND versioned object storage" was true of | |
| # neither half. The driver now comes from the environment, and these two | |
| # steps are the half that was missing: the journey with the heaviest | |
| # media path and the journey that decides whether the phase ships, both | |
| # against the versioned MinIO this job already runs. | |
| # | |
| # They run HERE and stay in `quality` too, on purpose: the `quality` runs | |
| # keep proving the local driver, and a difference between the two answers | |
| # is the signal worth having. | |
| - name: Provision the isolated capture journey database | |
| run: | | |
| docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres createdb -U "$POSTGRES_USER" apollo_v2_e2e_capture | |
| npm run db:v2:migrate:deploy | |
| env: | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2_e2e_capture?schema=public&application_name=apollo-video-e2e-capture-migrate-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run Wave 20 podcast multicam journey against PostgreSQL and versioned MinIO | |
| run: npm run test:e2e:podcast-multicam-journey | |
| env: | |
| APOLLO_PODCAST_MULTICAM_E2E: "1" | |
| APOLLO_API_ENVIRONMENT: production | |
| APOLLO_PODCAST_MULTICAM_OUTPUT: ${{ runner.temp }}/podcast-multicam-journey-s3 | |
| APOLLO_V2_ARTIFACT_STORAGE_DRIVER: s3 | |
| APOLLO_V2_S3_ENDPOINT: http://127.0.0.1:59000 | |
| APOLLO_V2_S3_REGION: us-east-1 | |
| APOLLO_V2_S3_ACCESS_KEY_ID: ${{ env.MINIO_ROOT_USER }} | |
| APOLLO_V2_S3_SECRET_ACCESS_KEY: ${{ env.MINIO_ROOT_PASSWORD }} | |
| APOLLO_V2_S3_FORCE_PATH_STYLE: "true" | |
| APOLLO_V2_S3_ALLOW_INSECURE_HTTP: "true" | |
| # Exclusive per-run bucket: the journey creates it, enables | |
| # versioning, proves zero orphan objects and deletes it afterwards. | |
| APOLLO_V2_S3_BUCKET: apollo-podcast-${{ github.run_id }}-${{ github.run_attempt }} | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2_e2e_capture?schema=public&application_name=apollo-video-e2e-podcast-multicam-s3-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run Wave 20 phase gate journey against PostgreSQL and versioned MinIO | |
| run: npm run test:e2e:phase-gate-journey | |
| env: | |
| APOLLO_PHASE_GATE_E2E: "1" | |
| APOLLO_API_ENVIRONMENT: production | |
| APOLLO_V2_ARTIFACT_STORAGE_DRIVER: s3 | |
| APOLLO_V2_S3_ENDPOINT: http://127.0.0.1:59000 | |
| APOLLO_V2_S3_REGION: us-east-1 | |
| APOLLO_V2_S3_ACCESS_KEY_ID: ${{ env.MINIO_ROOT_USER }} | |
| APOLLO_V2_S3_SECRET_ACCESS_KEY: ${{ env.MINIO_ROOT_PASSWORD }} | |
| APOLLO_V2_S3_FORCE_PATH_STYLE: "true" | |
| APOLLO_V2_S3_ALLOW_INSECURE_HTTP: "true" | |
| # The gate reads rows and manifests, never bytes — no route it | |
| # serves constructs artifact storage at all — so this bucket exists | |
| # to be asserted EMPTY at the end, which is what makes that sentence | |
| # falsifiable rather than a comment. No APOLLO_V2_RENDER_WORK_ROOT on | |
| # purpose: a criterion that starts reading bytes fails here with | |
| # PERSISTENCE_NOT_CONFIGURED instead of quietly succeeding. | |
| APOLLO_V2_S3_BUCKET: apollo-phase-gate-${{ github.run_id }}-${{ github.run_attempt }} | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2?schema=public&application_name=apollo-video-e2e-phase-gate-s3-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Verify zero orphan capture journey backends and buckets | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| compose="docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml" | |
| for name in apollo-video-e2e-podcast-multicam-s3-ci apollo-video-e2e-phase-gate-s3-ci; do | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| backends="$($compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name = '$name'")" | |
| if test "$backends" = 0; then break; fi | |
| sleep 1 | |
| done | |
| echo "$name backends: $backends" | |
| test "$backends" = 0 | |
| done | |
| for bucket in "apollo-podcast-${{ github.run_id }}-${{ github.run_attempt }}" "apollo-phase-gate-${{ github.run_id }}-${{ github.run_attempt }}"; do | |
| if $compose exec -T minio mc ls "local/$bucket" >/dev/null 2>&1; then | |
| echo "run-exclusive bucket $bucket still exists after cleanup" >&2 | |
| exit 1 | |
| fi | |
| done | |
| # Read back with ffprobe and sampled for pixels by the journey itself | |
| # before this runs, so the upload is for a person. Separate from the | |
| # `quality` job's copy on purpose: this is the file the object-storage | |
| # run delivered, and comparing the two is the point. | |
| - name: Publish the versioned-storage podcast journey MP4 | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apollo-podcast-journey-s3-${{ github.sha }} | |
| path: ${{ runner.temp }}/podcast-multicam-journey-s3 | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| - name: Run synthetic master cross-project reuse E2E against /v1 and PostgreSQL | |
| run: npm run test:e2e:synthetic-master-reuse | |
| env: | |
| # Local artifact storage on purpose: this journey proves reuse costs | |
| # nothing, so it must not depend on an object store round trip. | |
| APOLLO_V2_ARTIFACT_STORAGE_DRIVER: local | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2?schema=public&application_name=apollo-video-e2e-master-reuse-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run synthetic production, criticism and cache reuse journey against /v1, durable workers and PostgreSQL | |
| run: npm run test:e2e:synthetic-production-journey | |
| env: | |
| # Local artifact storage on purpose: this journey rewrites stored | |
| # bytes to prove a tampered blob is never reused, and reads them back | |
| # with ffprobe through the real critic adapters. | |
| APOLLO_V2_ARTIFACT_STORAGE_DRIVER: local | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2?schema=public&application_name=apollo-video-e2e-production-journey-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Verify zero orphan synthetic production journey backends | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| compose="docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml" | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| backends="$($compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name = 'apollo-video-e2e-production-journey-ci'")" | |
| if test "$backends" = 0; then break; fi | |
| sleep 1 | |
| done | |
| test "$backends" = 0 | |
| - name: Run synthetic cache eligibility and invalidation against PostgreSQL | |
| run: npm run test:integration:synthetic-cache-invalidation | |
| env: | |
| # Local artifact storage on purpose: this suite measures provider | |
| # traffic and cache decisions, not object store round trips. | |
| APOLLO_V2_ARTIFACT_STORAGE_DRIVER: local | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2?schema=public&application_name=apollo-video-cache-invalidation-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run synthetic critic report persistence against PostgreSQL | |
| run: npm run test:integration:synthetic-critic-report | |
| env: | |
| # Local artifact storage on purpose: this suite proves the verdict | |
| # projection and its fail-closed rehydration, not object store round | |
| # trips. | |
| APOLLO_V2_ARTIFACT_STORAGE_DRIVER: local | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2?schema=public&application_name=apollo-video-critic-report-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Verify zero orphan synthetic master reuse backends | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| compose="docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml" | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| backends="$($compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name = 'apollo-video-e2e-master-reuse-ci'")" | |
| if test "$backends" = 0; then break; fi | |
| sleep 1 | |
| done | |
| test "$backends" = 0 | |
| - name: Install locked renderer dependencies for the production build | |
| run: npm ci --prefix remotion | |
| - name: Build production application for the browser journey | |
| run: npm run build | |
| - name: Run localization media fail-closed persistence proof | |
| run: npm run test:integration:localization-media | |
| env: | |
| APOLLO_RUN_LOCALIZATION_MEDIA_PG_E2E: "1" | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_video_v2_e2e?schema=public&application_name=apollo-video-e2e-localization-media-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run localization media proxy-backed positive persistence proof | |
| run: npm run test:integration:localization-media-proxy | |
| env: | |
| APOLLO_RUN_LOCALIZATION_MEDIA_PROXY_PG_E2E: "1" | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_video_v2_e2e?schema=public&application_name=apollo-video-e2e-localization-media-proxy-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run localization browser journey against the production build | |
| run: npm run test:e2e:localization-browser | |
| env: | |
| APOLLO_LOCALIZATION_BROWSER_E2E: "1" | |
| PLAYWRIGHT_CHROME_EXECUTABLE: /usr/bin/google-chrome | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_video_v2_e2e?schema=public&application_name=apollo-video-e2e-localization-browser-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Run presenter lifecycle browser journey against PostgreSQL and versioned MinIO | |
| run: npm run test:e2e:presenter-lifecycle | |
| env: | |
| APOLLO_V2_ARTIFACT_STORAGE_DRIVER: s3 | |
| APOLLO_V2_S3_ENDPOINT: http://127.0.0.1:59000 | |
| APOLLO_V2_S3_REGION: us-east-1 | |
| APOLLO_V2_S3_ACCESS_KEY_ID: ${{ env.MINIO_ROOT_USER }} | |
| APOLLO_V2_S3_SECRET_ACCESS_KEY: ${{ env.MINIO_ROOT_PASSWORD }} | |
| APOLLO_V2_S3_FORCE_PATH_STYLE: "true" | |
| APOLLO_V2_S3_ALLOW_INSECURE_HTTP: "true" | |
| # Exclusive per-run bucket: the test creates it, enables versioning, | |
| # proves zero orphan objects and deletes it afterwards. | |
| APOLLO_V2_S3_BUCKET: apollo-presenter-${{ github.run_id }}-${{ github.run_attempt }} | |
| PLAYWRIGHT_CHROME_EXECUTABLE: /usr/bin/google-chrome | |
| V2_DATABASE_URL: postgresql://apollo:apollo-ci-compose-password@127.0.0.1:55432/apollo_v2?schema=public&application_name=apollo-video-e2e-presenter-ci&connection_limit=5&pool_timeout=10&connect_timeout=10 | |
| - name: Verify zero orphan presenter journey backends and objects | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| compose="docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml" | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| backends="$($compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name = 'apollo-video-e2e-presenter-ci'")" | |
| if test "$backends" = 0; then break; fi | |
| sleep 1 | |
| done | |
| test "$backends" = 0 | |
| if $compose exec -T minio mc ls "local/apollo-presenter-${{ github.run_id }}-${{ github.run_attempt }}" >/dev/null 2>&1; then | |
| echo "run-exclusive presenter bucket still exists after cleanup" >&2 | |
| exit 1 | |
| fi | |
| - name: Verify zero orphan block plan backends and objects | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| compose="docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml" | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| backends="$($compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name = 'apollo-video-e2e-block-plan-ci'")" | |
| if test "$backends" = 0; then break; fi | |
| sleep 1 | |
| done | |
| test "$backends" = 0 | |
| if $compose exec -T minio mc ls "local/apollo-blockplan-${{ github.run_id }}-${{ github.run_attempt }}" >/dev/null 2>&1; then | |
| echo "run-exclusive block plan bucket still exists after cleanup" >&2 | |
| exit 1 | |
| fi | |
| - name: Verify zero orphan provider journey backends and objects | |
| if: always() | |
| run: | | |
| set -euo pipefail | |
| compose="docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml" | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| backends="$($compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name = 'apollo-video-e2e-provider-journey-ci'")" | |
| if test "$backends" = 0; then break; fi | |
| sleep 1 | |
| done | |
| test "$backends" = 0 | |
| if $compose exec -T minio mc ls "local/apollo-journey-${{ github.run_id }}-${{ github.run_attempt }}" >/dev/null 2>&1; then | |
| echo "run-exclusive journey bucket still exists after cleanup" >&2 | |
| exit 1 | |
| fi | |
| - name: Verify PostgreSQL and versioned object round-trip | |
| run: | | |
| table_count="$(docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from information_schema.tables where table_schema = 'public'")" | |
| test "$table_count" -ge 128 | |
| version_info="$(docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml exec -T minio mc version info "local/$APOLLO_V2_S3_BUCKET")" | |
| printf '%s' "$version_info" | grep -qi enabled | |
| printf apollo-v2-compose-smoke | docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml exec -T minio mc pipe "local/$APOLLO_V2_S3_BUCKET/gates/smoke.txt" | |
| object_value="$(docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml exec -T minio mc cat "local/$APOLLO_V2_S3_BUCKET/gates/smoke.txt")" | |
| test "$object_value" = apollo-v2-compose-smoke | |
| - name: Start supervised app and workers | |
| run: | | |
| printf '%s\n' \ | |
| 'GROQ_API_KEY=ci-not-a-real-groq-credential-000' \ | |
| 'GROQ_TRANSCRIBE_MODEL=whisper-large-v3' \ | |
| 'GROQ_TRANSCRIBE_COST_MINOR_UNITS_PER_HOUR=1' \ | |
| 'OPENAI_API_KEY=ci-not-a-real-openai-credential-0' \ | |
| 'OPENAI_DIARIZATION_MODEL=gpt-4o-transcribe-diarize' \ | |
| 'OPENAI_DIARIZATION_COST_MINOR_UNITS_PER_HOUR=1' \ | |
| 'APOLLO_PROTECTED_PAYLOAD_KEY_ID=ci-protected-payload' \ | |
| 'APOLLO_PROTECTED_PAYLOAD_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \ | |
| > .env.local | |
| docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml -f infra/workflow/compose.yml up -d --build --wait app ingest-worker render-worker webhook-worker long-form-worker | |
| curl --fail --silent --show-error http://127.0.0.1:3333/v1/health > /dev/null | |
| for service in app ingest-worker render-worker webhook-worker long-form-worker; do | |
| container_id="$(docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml -f infra/workflow/compose.yml ps -q "$service")" | |
| status="$(docker inspect --format '{{.State.Status}}' "$container_id")" | |
| restart_policy="$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$container_id")" | |
| printf '%s id=%s status=%s restart=%s\n' "$service" "$container_id" "$status" "$restart_policy" | |
| test -n "$container_id" | |
| if test "$status" != running; then | |
| docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml -f infra/workflow/compose.yml logs --no-color --tail 100 "$service" | |
| exit 1 | |
| fi | |
| test "$restart_policy" = unless-stopped | |
| done | |
| sleep 5 | |
| for service in app ingest-worker render-worker webhook-worker long-form-worker; do | |
| container_id="$(docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml -f infra/workflow/compose.yml ps -q "$service")" | |
| test "$(docker inspect --format '{{.State.Status}}' "$container_id")" = running | |
| test "$(docker inspect --format '{{.RestartCount}}' "$container_id")" = 0 | |
| done | |
| # Same knob and same reasoning as the ProofMode step above: the harness | |
| # already backs off and waits out a legitimate 429 | |
| # (GOVERNANCE_LIMIT_EXCEEDED). APOLLO_GOVERNANCE_ANOMALY_REQUEST_MINIMUM | |
| # declares the size of this one supervised burst — seeding a 1.000 | |
| # document corpus plus its queries and evaluations is ~1.045 authenticated | |
| # /v1 calls at ~630 requests per minute — so the request-rate anomaly does | |
| # not read a scripted E2E as an attack and spend the job in backoff. It is | |
| # an env knob that already exists (governanceAnomalyPolicyFromEnvironment); | |
| # no product default changes, and every other governance control (rate | |
| # limit, concurrency, quota, spend and error-rate anomaly) stays at its | |
| # production value for this run. | |
| - name: Run cross-asset semantic search E2E on a dedicated database | |
| env: | |
| APOLLO_HYBRID_SEARCH_E2E: "1" | |
| APOLLO_GOVERNANCE_ANOMALY_REQUEST_MINIMUM: "5000" | |
| APOLLO_E2E_SERVER_MODE: dev | |
| APOLLO_API_ENVIRONMENT: sandbox | |
| APOLLO_SEMANTIC_EMBEDDING_PROVIDER: openai | |
| OPENAI_API_KEY: ci-not-a-real-openai-credential-0 | |
| APOLLO_E2E_HYBRID_SEARCH_DATABASE: apollo_v2_e2e_hybrid_search | |
| APOLLO_E2E_HYBRID_SEARCH_APP_NAME: apollo-video-e2e-hybrid-search-${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| compose="docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml" | |
| orphans="$($compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name like 'apollo-video-e2e-%'")" | |
| printf 'preflight e2e backends=%s\n' "$orphans" | |
| test "$orphans" = 0 | |
| $compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -Atc "create database \"$APOLLO_E2E_HYBRID_SEARCH_DATABASE\" owner \"$POSTGRES_USER\"" | |
| export V2_DATABASE_URL="postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:$POSTGRES_TUNNEL_PORT/$APOLLO_E2E_HYBRID_SEARCH_DATABASE?schema=public&application_name=$APOLLO_E2E_HYBRID_SEARCH_APP_NAME&connection_limit=5&pool_timeout=10&connect_timeout=10" | |
| npm run db:v2:migrate:deploy | |
| npm run test:e2e:hybrid-search | |
| - name: Verify zero orphan E2E backends after semantic search E2E | |
| if: always() | |
| env: | |
| APOLLO_E2E_HYBRID_SEARCH_APP_NAME: apollo-video-e2e-hybrid-search-${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| compose="docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml" | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| backends="$($compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select count(*) from pg_stat_activity where application_name = '$APOLLO_E2E_HYBRID_SEARCH_APP_NAME'")" | |
| printf 'postflight attempt=%s backends=%s\n' "$attempt" "$backends" | |
| if test "$backends" = 0; then | |
| exit 0 | |
| fi | |
| sleep 3 | |
| done | |
| $compose exec -T -e PGPASSWORD="$POSTGRES_PASSWORD" postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Atc "select pid, application_name, state, query_start from pg_stat_activity where application_name like 'apollo-video-e2e-%'" | |
| exit 1 | |
| - name: Stop isolated infrastructure | |
| if: always() | |
| run: docker compose -f infra/postgres/compose.yml -f infra/object-storage/compose.yml -f infra/workflow/compose.yml down --volumes --remove-orphans --timeout 30 |