Legacy: deploy previously governed public beta #1
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: Deploy public ROSS beta | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release_id: | |
| description: Approved immutable release identifier | |
| required: true | |
| type: string | |
| fly_organization: | |
| description: Fly.io organization slug shown in the Fly dashboard | |
| required: true | |
| default: personal | |
| type: string | |
| api_app_name: | |
| description: Existing or new Fly.io name for the ROSS API | |
| required: true | |
| default: ross-ranadeoss-api | |
| type: string | |
| web_app_name: | |
| description: Existing or new Fly.io name for the public ROSS app | |
| required: true | |
| default: ross-ranadeoss-public | |
| type: string | |
| file_worker_app_name: | |
| description: Existing or new private Fly app for malware scanning and conversion | |
| required: true | |
| default: ross-ranadeoss-file-worker | |
| type: string | |
| confirm_auth_configuration: | |
| description: I enabled Supabase email sign-up and Confirm email, and configured the app redirect URL | |
| required: true | |
| default: false | |
| type: boolean | |
| confirm_public_beta_approval: | |
| description: I have the recorded go-live approval for this exact release candidate | |
| required: true | |
| default: false | |
| type: boolean | |
| confirm_upload_scan_infrastructure: | |
| description: I applied the scan migration, made the Supabase bucket private, and configured the alert webhook | |
| required: true | |
| default: false | |
| type: boolean | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: deploy-public-ross-beta | |
| cancel-in-progress: false | |
| jobs: | |
| deploy: | |
| name: Deploy verified-account ROSS beta to Toronto | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| environment: public-beta | |
| env: | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} | |
| ROSS_SUPABASE_URL: ${{ secrets.ROSS_SUPABASE_URL }} | |
| ROSS_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.ROSS_SUPABASE_PUBLISHABLE_KEY }} | |
| ROSS_SUPABASE_SECRET_KEY: ${{ secrets.ROSS_SUPABASE_SECRET_KEY }} | |
| ROSS_S3_ENDPOINT_URL: ${{ secrets.ROSS_S3_ENDPOINT_URL }} | |
| ROSS_S3_REGION: ${{ secrets.ROSS_S3_REGION }} | |
| ROSS_S3_ACCESS_KEY_ID: ${{ secrets.ROSS_S3_ACCESS_KEY_ID }} | |
| ROSS_S3_SECRET_ACCESS_KEY: ${{ secrets.ROSS_S3_SECRET_ACCESS_KEY }} | |
| ROSS_SECURITY_ALERT_WEBHOOK_URL: ${{ secrets.ROSS_SECURITY_ALERT_WEBHOOK_URL }} | |
| ROSS_SECURITY_ALERT_WEBHOOK_SECRET: ${{ secrets.ROSS_SECURITY_ALERT_WEBHOOK_SECRET }} | |
| API_APP: ${{ inputs.api_app_name }} | |
| WEB_APP: ${{ inputs.web_app_name }} | |
| WORKER_APP: ${{ inputs.file_worker_app_name }} | |
| FLY_ORG: ${{ inputs.fly_organization }} | |
| INPUT_RELEASE_ID: ${{ inputs.release_id }} | |
| VERIFY_RELEASE_TAG: "true" | |
| PUBLIC_WEBSITE_URL: https://ross-ontario.augustmaat.chatgpt.site | |
| POLICY_VERSION: 2026-07-17-public-beta | |
| steps: | |
| - name: Check out approved candidate | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate operator confirmations | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ "${{ inputs.confirm_auth_configuration }}" != "true" ]; then | |
| echo "Supabase email sign-up, Confirm email, and redirect configuration must be confirmed." >&2 | |
| exit 1 | |
| fi | |
| if [ "${{ inputs.confirm_public_beta_approval }}" != "true" ]; then | |
| echo "Recorded public-beta go-live approval is required." >&2 | |
| exit 1 | |
| fi | |
| if [ "${{ inputs.confirm_upload_scan_infrastructure }}" != "true" ]; then | |
| echo "Upload scan infrastructure must be applied and verified before deployment." >&2 | |
| exit 1 | |
| fi | |
| - name: Verify immutable release and final completion gate | |
| run: | | |
| node scripts/verify-final-release-id.mjs | |
| npm run final:check | |
| - name: Validate deployment inputs and secrets | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| for name in \ | |
| FLY_API_TOKEN \ | |
| ROSS_SUPABASE_URL \ | |
| ROSS_SUPABASE_PUBLISHABLE_KEY \ | |
| ROSS_SUPABASE_SECRET_KEY \ | |
| ROSS_S3_ENDPOINT_URL \ | |
| ROSS_S3_REGION \ | |
| ROSS_S3_ACCESS_KEY_ID \ | |
| ROSS_S3_SECRET_ACCESS_KEY \ | |
| ROSS_SECURITY_ALERT_WEBHOOK_URL \ | |
| ROSS_SECURITY_ALERT_WEBHOOK_SECRET; do | |
| if [ -z "${!name:-}" ]; then | |
| echo "Required GitHub Actions secret is missing: ${name}" >&2 | |
| exit 1 | |
| fi | |
| done | |
| for value in "$API_APP" "$WEB_APP" "$WORKER_APP"; do | |
| if ! [[ "$value" =~ ^[a-z0-9][a-z0-9-]{2,61}[a-z0-9]$ ]]; then | |
| echo "Fly app names must use 4-63 lowercase letters, numbers, or hyphens." >&2 | |
| exit 1 | |
| fi | |
| done | |
| if [ "$API_APP" = "$WEB_APP" ]; then | |
| echo "The API and web app names must be different." >&2 | |
| exit 1 | |
| fi | |
| if [ "$WORKER_APP" = "$API_APP" ] || [ "$WORKER_APP" = "$WEB_APP" ]; then | |
| echo "The private file worker must use its own Fly app." >&2 | |
| exit 1 | |
| fi | |
| - uses: superfly/flyctl-actions/setup-flyctl@master | |
| with: | |
| version: 0.4.49 | |
| - name: Create Fly applications when absent | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if ! flyctl status --app "$API_APP" >/dev/null 2>&1; then | |
| flyctl apps create "$API_APP" --org "$FLY_ORG" | |
| fi | |
| if ! flyctl status --app "$WEB_APP" >/dev/null 2>&1; then | |
| flyctl apps create "$WEB_APP" --org "$FLY_ORG" | |
| fi | |
| if ! flyctl status --app "$WORKER_APP" >/dev/null 2>&1; then | |
| flyctl apps create "$WORKER_APP" --org "$FLY_ORG" | |
| fi | |
| - name: Configure private file worker | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| FILE_WORKER_SHARED_SECRET="$(openssl rand -hex 32)" | |
| echo "::add-mask::${FILE_WORKER_SHARED_SECRET}" | |
| echo "FILE_WORKER_SHARED_SECRET=${FILE_WORKER_SHARED_SECRET}" >> "$GITHUB_ENV" | |
| flyctl secrets set --stage --app "$WORKER_APP" \ | |
| "FILE_WORKER_SHARED_SECRET=${FILE_WORKER_SHARED_SECRET}" \ | |
| "FILE_WORKER_STORAGE_ORIGINS=${ROSS_S3_ENDPOINT_URL}" | |
| - name: Deploy private scan and conversion worker | |
| run: >- | |
| bash scripts/fly-deploy-with-retry.sh . | |
| --config deploy/fly/file-worker.toml | |
| --app "$WORKER_APP" | |
| --remote-only | |
| --no-cache | |
| --flycast | |
| --ha=false | |
| - name: Configure public-beta API secrets | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| WEB_URL="https://${WEB_APP}.fly.dev" | |
| API_URL="https://${API_APP}.fly.dev" | |
| WORKER_URL="http://${WORKER_APP}.flycast" | |
| flyctl secrets set --stage --app "$API_APP" \ | |
| "SUPABASE_URL=${ROSS_SUPABASE_URL}" \ | |
| "SUPABASE_SECRET_KEY=${ROSS_SUPABASE_SECRET_KEY}" \ | |
| "R2_ENDPOINT_URL=${ROSS_S3_ENDPOINT_URL}" \ | |
| "R2_REGION=${ROSS_S3_REGION}" \ | |
| "R2_ACCESS_KEY_ID=${ROSS_S3_ACCESS_KEY_ID}" \ | |
| "R2_SECRET_ACCESS_KEY=${ROSS_S3_SECRET_ACCESS_KEY}" \ | |
| "R2_BUCKET_NAME=ross-private-files" \ | |
| "FILE_WORKER_URL=${WORKER_URL}" \ | |
| "FILE_WORKER_SHARED_SECRET=${FILE_WORKER_SHARED_SECRET}" \ | |
| "SECURITY_ALERT_WEBHOOK_URL=${ROSS_SECURITY_ALERT_WEBHOOK_URL}" \ | |
| "SECURITY_ALERT_WEBHOOK_SECRET=${ROSS_SECURITY_ALERT_WEBHOOK_SECRET}" \ | |
| "ROSS_UPLOAD_SCAN_REQUIRED=true" \ | |
| "ROSS_ENV=staging" \ | |
| "ROSS_HOSTED_MODE=controlled-beta" \ | |
| "ROSS_REQUIRE_VERIFIED_EMAIL=true" \ | |
| "HOSTED_MODEL_PROVIDERS=openai" \ | |
| "ROSS_DATA_BOUNDARY_VERSION=${POLICY_VERSION}" \ | |
| "ROSS_RELEASE_ID=${INPUT_RELEASE_ID}" \ | |
| "RATE_LIMIT_GENERAL_MAX=180" \ | |
| "RATE_LIMIT_CHAT_MAX=20" \ | |
| "RATE_LIMIT_UPLOAD_MAX=20" \ | |
| "RATE_LIMIT_EXPORT_MAX=5" \ | |
| "DOWNLOAD_TOKEN_TTL_SECONDS=86400" \ | |
| "CORS_ALLOWED_ORIGINS=${WEB_URL}" \ | |
| "FRONTEND_URL=${WEB_URL}" \ | |
| "API_PUBLIC_URL=${API_URL}" | |
| ensure_random_secret() { | |
| local key="$1" | |
| if ! flyctl secrets list --app "$API_APP" --json \ | |
| | jq -e --arg key "$key" '.[] | select((.Name // .name) == $key)' \ | |
| >/dev/null; then | |
| flyctl secrets set --stage --app "$API_APP" \ | |
| "${key}=$(openssl rand -hex 32)" | |
| fi | |
| } | |
| ensure_random_secret DOWNLOAD_SIGNING_SECRET | |
| ensure_random_secret USER_API_KEYS_ENCRYPTION_SECRET | |
| ensure_random_secret MCP_CONNECTORS_ENCRYPTION_SECRET | |
| - name: Deploy ROSS API | |
| run: >- | |
| bash scripts/fly-deploy-with-retry.sh . | |
| --config deploy/fly/api.toml | |
| --app "$API_APP" | |
| --remote-only | |
| --no-cache | |
| --ha=false | |
| - name: Deploy public ROSS application | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| WEB_URL="https://${WEB_APP}.fly.dev" | |
| API_URL="https://${API_APP}.fly.dev" | |
| bash scripts/fly-deploy-with-retry.sh . \ | |
| --config deploy/fly/frontend.toml \ | |
| --app "$WEB_APP" \ | |
| --remote-only \ | |
| --no-cache \ | |
| --ha=false \ | |
| --build-arg "NEXT_PUBLIC_SUPABASE_URL=${ROSS_SUPABASE_URL}" \ | |
| --build-arg "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY=${ROSS_SUPABASE_PUBLISHABLE_KEY}" \ | |
| --build-arg "NEXT_PUBLIC_API_BASE_URL=${API_URL}" \ | |
| --build-arg "NEXT_PUBLIC_ROSS_APP_URL=${WEB_URL}" \ | |
| --build-arg "NEXT_PUBLIC_ROSS_WEBSITE_URL=${PUBLIC_WEBSITE_URL}" \ | |
| --build-arg "NEXT_PUBLIC_ROSS_HOSTED_MODE=controlled-beta" \ | |
| --build-arg "NEXT_PUBLIC_ROSS_DATA_BOUNDARY_VERSION=${POLICY_VERSION}" \ | |
| --build-arg "NEXT_PUBLIC_ROSS_TERMS_VERSION=${POLICY_VERSION}" \ | |
| --build-arg "NEXT_PUBLIC_ROSS_PRIVACY_VERSION=${POLICY_VERSION}" \ | |
| --build-arg "NEXT_PUBLIC_ROSS_SIGNUPS_ENABLED=true" | |
| - name: Verify public-beta services | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| API_URL="https://${API_APP}.fly.dev" | |
| WEB_URL="https://${WEB_APP}.fly.dev" | |
| curl --fail --retry 8 --retry-delay 5 --retry-all-errors \ | |
| "${API_URL}/health" | |
| curl --fail --retry 8 --retry-delay 5 --retry-all-errors \ | |
| "${WEB_URL}/signup" | grep -q "Create Account" | |
| { | |
| echo "## Public ROSS beta deployed" | |
| echo | |
| echo "- Create account: ${WEB_URL}/signup" | |
| echo "- Login: ${WEB_URL}/login" | |
| echo "- API health: ${API_URL}/health" | |
| echo "- Region: Toronto (yyz)" | |
| echo "- Email verification: API-enforced" | |
| echo "- Anonymous uploads and AI requests: disabled" | |
| echo "- Authenticated uploads: quarantined, scanned, and converted privately" | |
| echo "- Hosted data boundary: synthetic or non-confidential" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Verify required Ontario legal sources | |
| run: >- | |
| node scripts/observe-legal-sources.mjs | |
| --output artifacts/legal-source-health-live.json | |
| - name: Run deployed application smoke tests | |
| env: | |
| ROSS_E2E_API_URL: https://${{ env.API_APP }}.fly.dev | |
| ROSS_E2E_APP_URL: https://${{ env.WEB_APP }}.fly.dev | |
| run: npm run test:e2e | |
| - name: Upload sanitized legal-source observation | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: public-beta-legal-source-health-${{ github.run_id }} | |
| path: artifacts/legal-source-health-live.json | |
| if-no-files-found: warn | |
| retention-days: 14 |