Skip to content

Legacy: deploy previously governed public beta #1

Legacy: deploy previously governed public beta

Legacy: deploy previously governed public beta #1

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