feat(bot): add contextual room access #5942
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 | |
| - 'release-[0-9]*' | |
| pull_request: | |
| types: | |
| - opened | |
| - reopened | |
| - synchronize | |
| - labeled | |
| - unlabeled | |
| - ready_for_review | |
| concurrency: | |
| # PRs share a group so superseded revisions are cancelled. Pushes use their | |
| # immutable commit SHA so every main commit can publish an image. | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| build-main-image-contexts: | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| image-version: ${{ steps.image-version.outputs.version }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup environment | |
| uses: ./.github/actions/setup | |
| - name: Derive main image version | |
| id: image-version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| development_version="$(mise env --json | jq -r '.CHATTO_DEVELOPMENT_VERSION // empty')" | |
| if [[ -z "$development_version" ]]; then | |
| echo "::error::Could not read CHATTO_DEVELOPMENT_VERSION from mise.toml." | |
| exit 1 | |
| fi | |
| echo "version=${development_version}+${GITHUB_SHA:0:12}" >> "$GITHUB_OUTPUT" | |
| - name: Build frontend for production image | |
| env: | |
| CHATTO_BUILD_VERSION: ${{ steps.image-version.outputs.version }} | |
| CHATTO_FRONTEND_PRECOMPRESS: "1" | |
| run: mise build-frontend | |
| - name: Verify frontend build version | |
| env: | |
| EXPECTED_VERSION: ${{ steps.image-version.outputs.version }} | |
| shell: bash | |
| run: | | |
| jq -e --arg expected "$EXPECTED_VERSION" \ | |
| '.version == $expected' \ | |
| apps/frontend/build/_app/version.json | |
| - name: Build production image contexts | |
| shell: bash | |
| env: | |
| VERSION: ${{ steps.image-version.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| for arch in amd64 arm64; do | |
| context=".context/ci/docker/linux-${arch}" | |
| mkdir -p "$context/docker" "$context/LICENSES" | |
| cp docker/Dockerfile.goreleaser "$context/Dockerfile" | |
| cp docker/docker-entrypoint.sh "$context/docker/docker-entrypoint.sh" | |
| cp docker/nats-wrapper.sh "$context/docker/nats-wrapper.sh" | |
| cp LICENSES/AGPL-3.0-or-later.txt "$context/LICENSES/AGPL-3.0-or-later.txt" | |
| cp NOTICE "$context/NOTICE" | |
| ( | |
| cd cli | |
| # Chatto does not expose Gin's MessagePack binding; omit its large codec. | |
| CGO_ENABLED=0 GOOS=linux GOARCH="$arch" go build -trimpath \ | |
| -tags=nomsgpack \ | |
| -ldflags="-s -w -X main.Version=${VERSION}" \ | |
| -o "../$context/chatto" . | |
| ) | |
| done | |
| - name: Upload production image contexts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: production-image-contexts | |
| path: .context/ci/docker | |
| retention-days: 1 | |
| license-check: | |
| # runs-on: ubicloud-standard-4 | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup mise | |
| uses: jdx/mise-action@v4 | |
| with: | |
| experimental: true | |
| - name: Check license metadata | |
| run: mise license-check | |
| desktop: | |
| name: desktop (${{ matrix.platform }}) | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: linux | |
| os: ubuntu-latest | |
| artifact-path: apps/desktop/dist/chatto-desktop | |
| - platform: macos | |
| os: macos-latest | |
| artifact-path: apps/desktop/dist/Chatto Desktop.app | |
| - platform: windows | |
| os: windows-latest | |
| artifact-path: apps/desktop/dist/windows | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup environment | |
| uses: ./.github/actions/setup | |
| with: | |
| install-cli-deps: "false" | |
| - name: Check and test desktop shell | |
| run: mise test-desktop | |
| - name: Build desktop bundle | |
| run: mise desktop-build | |
| - name: Verify desktop bundle exists | |
| shell: bash | |
| env: | |
| ARTIFACT_PATH: ${{ matrix.artifact-path }} | |
| run: test -e "$ARTIFACT_PATH" | |
| - name: Verify macOS bundle signature | |
| if: runner.os == 'macOS' | |
| run: codesign --verify --deep --strict --verbose=2 "apps/desktop/dist/Chatto Desktop.app" | |
| - name: Upload desktop bundle | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: chatto-desktop-${{ matrix.platform }}-${{ runner.arch }} | |
| path: ${{ matrix.artifact-path }} | |
| if-no-files-found: error | |
| retention-days: 7 | |
| codegen-proto-drift: | |
| # runs-on: ubicloud-standard-4 | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup environment | |
| uses: ./.github/actions/setup | |
| - name: Fetch protobuf breaking base | |
| run: | | |
| base_ref="${GITHUB_BASE_REF:-${GITHUB_REF_NAME}}" | |
| git fetch --no-tags --depth=1 origin "${base_ref}:refs/remotes/origin/${base_ref}" | |
| - name: Check storage, internal, and Operator protobuf breaking changes | |
| run: | | |
| base_ref="${GITHUB_BASE_REF:-${GITHUB_REF_NAME}}" | |
| cd proto | |
| mise x -- buf breaking . --against "../.git#branch=origin/${base_ref},subdir=proto" \ | |
| --exclude-imports \ | |
| --exclude-path chatto/auth/v1 \ | |
| --exclude-path chatto/discovery/v1 \ | |
| --exclude-path chatto/api/v1 \ | |
| --exclude-path chatto/admin/v1 \ | |
| --exclude-path chatto/realtime/v1 \ | |
| --exclude-path chatto/core/v1/projection_snapshots.proto | |
| - name: Check projection snapshot schema contracts | |
| working-directory: cli | |
| run: mise x -- go test ./internal/core -run TestProjectionSnapshotContractsIncludeCurrentSchema -count=1 | |
| - name: Check public API protobuf breaking changes | |
| if: ${{ github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'api-breaking-change') }} | |
| run: | | |
| base_ref="${GITHUB_BASE_REF:-${GITHUB_REF_NAME}}" | |
| cd proto | |
| mise x -- buf breaking . --against "../.git#branch=origin/${base_ref},subdir=proto" \ | |
| --path chatto/auth/v1 \ | |
| --path chatto/discovery/v1 \ | |
| --path chatto/api/v1 \ | |
| --path chatto/admin/v1 \ | |
| --path chatto/realtime/v1 | |
| - name: Check Authling persisted protobuf breaking changes | |
| run: | | |
| base_ref="${GITHUB_BASE_REF:-${GITHUB_REF_NAME}}" | |
| if git cat-file -e "origin/${base_ref}:authling/buf.yaml" 2>/dev/null; then | |
| cd authling | |
| mise x -- buf breaking . \ | |
| --against "../.git#branch=origin/${base_ref},subdir=authling" \ | |
| --exclude-imports | |
| else | |
| echo "::notice::Authling protobufs are new; no base schema exists yet." | |
| fi | |
| - name: Acknowledge intentional public API protobuf breaking changes | |
| if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'api-breaking-change') }} | |
| run: | | |
| echo "::notice::Skipping public API Buf breaking check because this PR has the api-breaking-change label. Storage, internal, Operator, and codegen drift checks still run." | |
| - name: Lint Authling protobufs | |
| working-directory: authling | |
| run: mise x -- buf lint | |
| - name: Regenerate protobuf bindings | |
| run: | | |
| mise codegen-proto | |
| cd authling | |
| mise codegen-proto | |
| - name: Check generated protobuf bindings are current | |
| run: | | |
| if ! git diff --exit-code; then | |
| git status --short | |
| exit 1 | |
| fi | |
| test-workspace: | |
| # runs-on: ubicloud-standard-4 | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup environment | |
| uses: ./.github/actions/setup | |
| with: | |
| install-cli-deps: "false" | |
| - name: Setup Playwright | |
| uses: ./.github/actions/setup-playwright | |
| timeout-minutes: 15 | |
| - name: Run workspace typechecks | |
| run: pnpm run check | |
| - name: Run workspace linters | |
| run: pnpm run lint | |
| - name: Run workspace unit tests | |
| run: pnpm test | |
| - name: Build Storybook design-system catalog | |
| run: pnpm run build:storybook | |
| test-cli: | |
| # runs-on: ubicloud-standard-4 | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup environment | |
| uses: ./.github/actions/setup | |
| - name: Run Authling tests | |
| working-directory: authling | |
| run: mise test | |
| - name: Run application configuration tests | |
| run: mise test-appconfig | |
| - name: Run data cryptography tests | |
| run: mise test-datacrypto | |
| - name: Run event framework tests | |
| run: mise test-events | |
| - name: Run embedded NATS runtime tests | |
| run: mise test-natsruntime | |
| - name: Build frontend | |
| run: mise build-frontend | |
| - name: Run Go tests | |
| run: cd cli && go test -trimpath -p 1 -tags test_endpoints ./... | |
| test-authling-e2e: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup environment | |
| uses: ./.github/actions/setup | |
| - name: Setup Authling E2E dependencies | |
| working-directory: authling | |
| run: | | |
| mise install | |
| mise deps-web | |
| - name: Setup Playwright | |
| uses: ./.github/actions/setup-playwright | |
| timeout-minutes: 15 | |
| with: | |
| working-directory: authling | |
| - name: Run Authling E2E tests | |
| working-directory: authling | |
| run: mise test-e2e | |
| - name: Upload Playwright report | |
| uses: actions/upload-artifact@v7 | |
| if: ${{ failure() }} | |
| with: | |
| name: authling-playwright-report | |
| path: | | |
| authling/playwright-report/ | |
| authling/test-results/ | |
| retention-days: 14 | |
| test-e2e: | |
| # runs-on: ubicloud-standard-4 | |
| runs-on: ubuntu-latest | |
| name: test-e2e (${{ matrix.shard }}/4) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup environment | |
| uses: ./.github/actions/setup | |
| - name: Setup Playwright | |
| uses: ./.github/actions/setup-playwright | |
| timeout-minutes: 15 | |
| - name: Build E2E server | |
| run: mise build-e2e-server | |
| - name: Run E2E tests | |
| run: cd apps/frontend && pnpm exec playwright test --grep-invert @ffmpeg --shard=${{ matrix.shard }}/4 | |
| - name: Upload Playwright report | |
| uses: actions/upload-artifact@v7 | |
| if: ${{ failure() }} | |
| with: | |
| name: playwright-report-${{ matrix.shard }}-of-4 | |
| path: apps/frontend/playwright-report/ | |
| retention-days: 14 | |
| test-e2e-media: | |
| # runs-on: ubicloud-standard-4 | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Setup environment | |
| uses: ./.github/actions/setup | |
| timeout-minutes: 15 | |
| with: | |
| install-ffmpeg: "true" | |
| - name: Setup Playwright | |
| uses: ./.github/actions/setup-playwright | |
| timeout-minutes: 15 | |
| - name: Build E2E server | |
| run: mise build-e2e-server | |
| - name: Run media E2E tests | |
| run: cd apps/frontend && pnpm exec playwright test --grep @ffmpeg | |
| - name: Upload Playwright report | |
| uses: actions/upload-artifact@v7 | |
| if: ${{ failure() }} | |
| with: | |
| name: playwright-report-media | |
| path: apps/frontend/playwright-report/ | |
| retention-days: 14 | |
| test-e2e-performance: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 35 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup environment | |
| uses: ./.github/actions/setup | |
| - name: Setup Playwright | |
| uses: ./.github/actions/setup-playwright | |
| timeout-minutes: 15 | |
| - name: Prepare performance baseline | |
| id: performance-baseline | |
| shell: bash | |
| env: | |
| PERFORMANCE_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} | |
| run: | | |
| set -euo pipefail | |
| baseline_dir="$RUNNER_TEMP/chatto-performance-base" | |
| if [[ -z "$PERFORMANCE_BASE_SHA" || "$PERFORMANCE_BASE_SHA" =~ ^0+$ ]]; then | |
| echo "available=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if ! git cat-file -e "${PERFORMANCE_BASE_SHA}:apps/frontend/e2e-performance/large-server.performance.test.ts"; then | |
| echo "available=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git worktree add --detach "$baseline_dir" "$PERFORMANCE_BASE_SHA" | |
| echo "available=true" >> "$GITHUB_OUTPUT" | |
| - name: Run base-revision performance test | |
| if: steps.performance-baseline.outputs.available == 'true' | |
| working-directory: ${{ runner.temp }}/chatto-performance-base | |
| run: mise test-e2e-performance | |
| - name: Preserve base-revision results | |
| if: steps.performance-baseline.outputs.available == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| comparison_dir="apps/frontend/test-results/performance-comparison" | |
| mkdir -p "$comparison_dir" | |
| base_result=$(find "$RUNNER_TEMP/chatto-performance-base/apps/frontend/test-results/performance" -name performance-results.json -print -quit) | |
| cp "$base_result" "$comparison_dir/base.json" | |
| cp -R "$RUNNER_TEMP/chatto-performance-base/apps/frontend/playwright-report-performance" apps/frontend/playwright-report-performance-base | |
| - name: Build E2E server | |
| run: mise build-e2e-server | |
| - name: Run large-server E2E performance test | |
| run: mise test-e2e-performance | |
| - name: Preserve candidate results | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| comparison_dir="apps/frontend/test-results/performance-comparison" | |
| mkdir -p "$comparison_dir" | |
| candidate_result=$(find apps/frontend/test-results/performance -name performance-results.json -print -quit) | |
| cp "$candidate_result" "$comparison_dir/candidate.json" | |
| - name: Compare performance with base revision | |
| if: steps.performance-baseline.outputs.available == 'true' | |
| working-directory: apps/frontend | |
| shell: bash | |
| run: | | |
| set -o pipefail | |
| node scripts/compare-e2e-performance.mjs \ | |
| test-results/performance-comparison/base.json \ | |
| test-results/performance-comparison/candidate.json \ | |
| | tee -a "$GITHUB_STEP_SUMMARY" | |
| - name: Summarize candidate-only performance results | |
| if: steps.performance-baseline.outputs.available != 'true' | |
| shell: bash | |
| run: | | |
| result="apps/frontend/test-results/performance-comparison/candidate.json" | |
| { | |
| echo "## Large-server E2E performance" | |
| jq -r '"Fixture: \(.measurements.syntheticUsers) users, \(.measurements.messages) messages; \(.measurements.sampleCount)-sample medians (\(.measurements.startupMode) startup)"' "$result" | |
| echo "" | |
| echo "| Measurement | Duration (ms) |" | |
| echo "| --- | ---: |" | |
| jq -r '.measurements | to_entries[] | select(.key | endswith("Ms")) | "| \(.key) | \(.value | round) |"' "$result" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Remove performance baseline worktree | |
| if: ${{ always() && steps.performance-baseline.outputs.available == 'true' }} | |
| shell: bash | |
| run: git worktree remove --force "$RUNNER_TEMP/chatto-performance-base" | |
| - name: Upload performance results | |
| uses: actions/upload-artifact@v7 | |
| if: ${{ always() }} | |
| with: | |
| name: e2e-performance-results | |
| path: | | |
| apps/frontend/test-results/performance/ | |
| apps/frontend/test-results/performance-comparison/ | |
| apps/frontend/playwright-report-performance/ | |
| apps/frontend/playwright-report-performance-base/ | |
| retention-days: 30 | |
| publish-main-image: | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| needs: | |
| - build-main-image-contexts | |
| - license-check | |
| - codegen-proto-drift | |
| - test-workspace | |
| - test-cli | |
| - test-e2e | |
| - test-e2e-media | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Download production image contexts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: production-image-contexts | |
| path: docker-contexts | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Derive immutable main image tag | |
| id: main-image | |
| shell: bash | |
| run: echo "tag=main-$(date -u +%Y%m%dT%H%M%SZ)-${GITHUB_SHA:0:12}" >> "$GITHUB_OUTPUT" | |
| - name: Build and push amd64 image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: docker-contexts/linux-amd64 | |
| file: docker-contexts/linux-amd64/Dockerfile | |
| push: true | |
| platforms: linux/amd64 | |
| tags: ghcr.io/chattocorp/chatto:${{ github.sha }}-amd64 | |
| cache-from: type=gha,scope=chatto-main-amd64 | |
| cache-to: type=gha,scope=chatto-main-amd64,mode=max | |
| labels: | | |
| org.opencontainers.image.source=https://github.com/chattocorp/chatto | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.version=${{ needs.build-main-image-contexts.outputs.image-version }} | |
| org.opencontainers.image.licenses=AGPL-3.0-or-later | |
| - name: Build and push arm64 image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: docker-contexts/linux-arm64 | |
| file: docker-contexts/linux-arm64/Dockerfile | |
| push: true | |
| platforms: linux/arm64 | |
| tags: ghcr.io/chattocorp/chatto:${{ github.sha }}-arm64 | |
| cache-from: type=gha,scope=chatto-main-arm64 | |
| cache-to: type=gha,scope=chatto-main-arm64,mode=max | |
| labels: | | |
| org.opencontainers.image.source=https://github.com/chattocorp/chatto | |
| org.opencontainers.image.revision=${{ github.sha }} | |
| org.opencontainers.image.version=${{ needs.build-main-image-contexts.outputs.image-version }} | |
| org.opencontainers.image.licenses=AGPL-3.0-or-later | |
| - name: Publish multi-platform commit image | |
| shell: bash | |
| run: | | |
| docker buildx imagetools create \ | |
| --tag "ghcr.io/chattocorp/chatto:${GITHUB_SHA}" \ | |
| --tag "ghcr.io/chattocorp/chatto:${{ steps.main-image.outputs.tag }}" \ | |
| "ghcr.io/chattocorp/chatto:${GITHUB_SHA}-amd64" \ | |
| "ghcr.io/chattocorp/chatto:${GITHUB_SHA}-arm64" | |
| { | |
| echo "### Main commit image" | |
| echo | |
| echo "\`ghcr.io/chattocorp/chatto:${GITHUB_SHA}\`" | |
| echo "\`ghcr.io/chattocorp/chatto:${{ steps.main-image.outputs.tag }}\`" | |
| echo | |
| echo "Version: \`${{ needs.build-main-image-contexts.outputs.image-version }}\`" | |
| } >> "$GITHUB_STEP_SUMMARY" |