Resolve AuthClientId from the shared-auth stack, not a hardcoded value #283
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 DataOps V1 Lambda | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - ".github/workflows/deploy-dataops-v1.yml" | |
| - "backend/**" | |
| - "content/**" | |
| - "frontend/**" | |
| - "infra/**" | |
| - "Makefile" | |
| - "package.json" | |
| - "package-lock.json" | |
| - "scripts/test-backend-transactions.sh" | |
| - "samconfig.toml" | |
| - "scripts/deploy/**" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: dataops-v1-deploy | |
| cancel-in-progress: false | |
| env: | |
| AWS_REGION: eu-west-1 | |
| AWS_DEFAULT_REGION: eu-west-1 | |
| AWS_ROLE_ARN: arn:aws:iam::817685572750:role/dataops-github-actions-deploy | |
| DOCS_GITHUB_OWNER: DataTalksClub | |
| DOCS_GITHUB_REPO: dataops-knowledge | |
| DOCS_GITHUB_TOKEN_SECRET: dataops-v1/knowledge/github-token | |
| DOCS_GITHUB_BRANCH: main | |
| jobs: | |
| checks: | |
| name: Check DataOps v1 app | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v6 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "20" | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Install Node workspace dependencies | |
| run: npm ci | |
| - name: Run frontend unit coverage | |
| run: npm run test:frontend:coverage | |
| - name: Run backend tests | |
| run: npm --prefix backend test | |
| - name: Gate all backend DynamoDB transactions | |
| run: npm run test:backend:transactions | |
| - name: Typecheck backend | |
| run: npm --prefix backend run typecheck | |
| - name: Build backend | |
| run: npm --prefix backend run build | |
| - name: Set up SAM | |
| uses: aws-actions/setup-sam@v3 | |
| with: | |
| use-installer: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Validate SAM template | |
| run: make sam-validate | |
| deploy: | |
| name: Deploy DataOps v1 app | |
| runs-on: ubuntu-latest | |
| needs: checks | |
| permissions: | |
| contents: read | |
| id-token: write | |
| env: | |
| AUTH_BASE_URL: https://auth.dtcdev.click | |
| AUTH_USER_POOL_ID: us-east-1_H7nJu52Bs | |
| # AUTH_ISSUER, AUTH_JWKS_URL, and AUTH_CLIENT_ID are resolved from the | |
| # shared-auth stack's own outputs in the "Resolve shared auth client" | |
| # step below, not hardcoded here — a hardcoded client_id goes stale | |
| # silently whenever the Cognito app client is recreated. | |
| AUTH_CALLBACK_URL: https://ops.dtcdev.click/auth/callback | |
| AUTH_LOGOUT_URL: https://ops.dtcdev.click/ | |
| AUTH_SESSION_LIFETIME_SECONDS: 28800 | |
| EMAIL_DOCUMENT_INTAKE_SECRET_ARN: ${{ secrets.EMAIL_DOCUMENT_INTAKE_SECRET_ARN }} | |
| EMAIL_DOCUMENT_EXTERNAL_SOURCE_BUCKET: ${{ vars.EMAIL_DOCUMENT_EXTERNAL_SOURCE_BUCKET }} | |
| EMAIL_DOCUMENT_EXTERNAL_SOURCE_PREFIX: ${{ vars.EMAIL_DOCUMENT_EXTERNAL_SOURCE_PREFIX }} | |
| EMAIL_DOCUMENT_EXTERNAL_SOURCE_KMS_KEY_ARN: ${{ secrets.EMAIL_DOCUMENT_EXTERNAL_SOURCE_KMS_KEY_ARN }} | |
| DAPIER_CREDENTIALS_TABLE_NAME: ${{ vars.DAPIER_CREDENTIALS_TABLE_NAME }} | |
| DAPIER_CREDENTIALS_TABLE_ARN: ${{ vars.DAPIER_CREDENTIALS_TABLE_ARN }} | |
| MAILING_EXPORTS_CONFIG: ${{ vars.MAILING_EXPORTS_CONFIG }} | |
| CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED: ${{ vars.CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED }} | |
| SPONSOR_FINANCE_ENABLED: ${{ vars.SPONSOR_FINANCE_ENABLED }} | |
| CONVERSATIONAL_EXECUTION_ENABLED: ${{ vars.CONVERSATIONAL_EXECUTION_ENABLED }} | |
| CONVERSATIONAL_ENABLED_PLUGINS: ${{ vars.CONVERSATIONAL_ENABLED_PLUGINS }} | |
| CONVERSATIONAL_TYPEFULLY_EXTERNAL_EXECUTION_ENABLED: ${{ vars.CONVERSATIONAL_TYPEFULLY_EXTERNAL_EXECUTION_ENABLED }} | |
| CONVERSATIONAL_TYPEFULLY_ACCOUNT_CONFIG_DIGEST: ${{ vars.CONVERSATIONAL_TYPEFULLY_ACCOUNT_CONFIG_DIGEST }} | |
| TYPEFULLY_ACCOUNT_CONFIG_REVISION: ${{ vars.TYPEFULLY_ACCOUNT_CONFIG_REVISION }} | |
| TYPEFULLY_BASE_URL: ${{ vars.TYPEFULLY_BASE_URL }} | |
| WORK_ENGINE_TYPEFULLY_API_TOKEN_SECRET_ARN: ${{ vars.WORK_ENGINE_TYPEFULLY_API_TOKEN_SECRET_ARN }} | |
| TYPEFULLY_SOCIAL_SET_ALEXEY: ${{ vars.TYPEFULLY_SOCIAL_SET_ALEXEY }} | |
| TYPEFULLY_SOCIAL_SET_DATATALKSCLUB: ${{ vars.TYPEFULLY_SOCIAL_SET_DATATALKSCLUB }} | |
| ZAI_CONVERSATIONAL_API_KEY_SECRET_ARN: ${{ vars.ZAI_CONVERSATIONAL_API_KEY_SECRET_ARN }} | |
| CONVERSATIONAL_TELEGRAM_VOICE_ENABLED: ${{ vars.CONVERSATIONAL_TELEGRAM_VOICE_ENABLED }} | |
| CONVERSATIONAL_TELEGRAM_PHOTO_ENABLED: ${{ vars.CONVERSATIONAL_TELEGRAM_PHOTO_ENABLED }} | |
| CONVERSATIONAL_ALARM_SUBSCRIPTION_CONFIRMED: ${{ vars.CONVERSATIONAL_ALARM_SUBSCRIPTION_CONFIRMED }} | |
| GROQ_TRANSCRIPTION_API_KEY_SECRET_ARN: ${{ vars.GROQ_TRANSCRIPTION_API_KEY_SECRET_ARN }} | |
| ZAI_VISION_API_KEY_SECRET_ARN: ${{ vars.ZAI_VISION_API_KEY_SECRET_ARN }} | |
| ZAI_VISION_MODEL: ${{ vars.ZAI_VISION_MODEL }} | |
| ZAI_VISION_BASE_URL: ${{ vars.ZAI_VISION_BASE_URL }} | |
| TELEGRAM_HANDLER_DEADLINE_MS: ${{ vars.TELEGRAM_HANDLER_DEADLINE_MS }} | |
| TELEGRAM_API_TIMEOUT_MS: ${{ vars.TELEGRAM_API_TIMEOUT_MS }} | |
| TELEGRAM_API_MAX_RESPONSE_BYTES: ${{ vars.TELEGRAM_API_MAX_RESPONSE_BYTES }} | |
| TELEGRAM_VOICE_MAX_BYTES: ${{ vars.TELEGRAM_VOICE_MAX_BYTES }} | |
| TELEGRAM_VOICE_MAX_SECONDS: ${{ vars.TELEGRAM_VOICE_MAX_SECONDS }} | |
| TELEGRAM_PHOTO_MAX_BYTES: ${{ vars.TELEGRAM_PHOTO_MAX_BYTES }} | |
| TELEGRAM_PHOTO_MAX_PIXELS: ${{ vars.TELEGRAM_PHOTO_MAX_PIXELS }} | |
| TELEGRAM_MEDIA_DOWNLOAD_TIMEOUT_MS: ${{ vars.TELEGRAM_MEDIA_DOWNLOAD_TIMEOUT_MS }} | |
| TELEGRAM_MEDIA_PROVIDER_TIMEOUT_MS: ${{ vars.TELEGRAM_MEDIA_PROVIDER_TIMEOUT_MS }} | |
| TELEGRAM_MEDIA_PROVIDER_MAX_RESPONSE_BYTES: ${{ vars.TELEGRAM_MEDIA_PROVIDER_MAX_RESPONSE_BYTES }} | |
| TELEGRAM_MEDIA_MAX_TEXT_BYTES: ${{ vars.TELEGRAM_MEDIA_MAX_TEXT_BYTES }} | |
| SPONSOR_COMMUNICATION_SEND_ENABLED: ${{ vars.SPONSOR_COMMUNICATION_SEND_ENABLED }} | |
| SPONSOR_COMMUNICATION_TEMPLATE_SECRET_ARN: ${{ vars.SPONSOR_COMMUNICATION_TEMPLATE_SECRET_ARN }} | |
| SPONSOR_COMMUNICATION_HMAC_SECRET_ARN: ${{ vars.SPONSOR_COMMUNICATION_HMAC_SECRET_ARN }} | |
| SPONSOR_SES_IDENTITY_ARN: ${{ vars.SPONSOR_SES_IDENTITY_ARN }} | |
| SPONSOR_SES_FROM_ADDRESS: ${{ vars.SPONSOR_SES_FROM_ADDRESS }} | |
| SPONSOR_SES_CONFIGURATION_SET_NAME: ${{ vars.SPONSOR_SES_CONFIGURATION_SET_NAME }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v6 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "20" | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Set up SAM | |
| uses: aws-actions/setup-sam@v3 | |
| with: | |
| use-installer: true | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v6.1.0 | |
| with: | |
| role-to-assume: ${{ env.AWS_ROLE_ARN }} | |
| role-session-name: dataops-v1-deploy | |
| aws-region: ${{ env.AWS_REGION }} | |
| - name: Resolve shared auth client | |
| run: | | |
| auth_output() { | |
| aws cloudformation describe-stacks --region us-east-1 --stack-name dtcdev-shared-auth \ | |
| --query "Stacks[0].Outputs[?OutputKey=='$1'].OutputValue" --output text | |
| } | |
| { | |
| echo "AUTH_CLIENT_ID=$(auth_output DataOpsClientId)" | |
| echo "AUTH_ISSUER=$(auth_output IssuerUrl)" | |
| echo "AUTH_JWKS_URL=$(auth_output JwksUrl)" | |
| } >> "$GITHUB_ENV" | |
| - name: Build SAM artifact | |
| run: make sam-build | |
| - name: Verify packaged canonical frontend | |
| run: make verify-sam-frontend | |
| - name: Verify packaged runtime infrastructure boundary | |
| run: make verify-sam-runtime-boundary | |
| - name: Test packaged frontend isolation | |
| run: make test-sam-frontend-isolation | |
| - name: Deploy DataOps v1 stack | |
| run: | | |
| set -euo pipefail | |
| if [ -z "$EMAIL_DOCUMENT_INTAKE_SECRET_ARN" ]; then | |
| echo "Required email-document intake secret ARN is not configured" >&2 | |
| exit 1 | |
| fi | |
| if [ -z "$DAPIER_CREDENTIALS_TABLE_NAME" ] || [ -z "$DAPIER_CREDENTIALS_TABLE_ARN" ]; then | |
| echo "Required Dapier credentials table name/ARN are not configured" >&2 | |
| exit 1 | |
| fi | |
| : "${EMAIL_DOCUMENT_EXTERNAL_SOURCE_PREFIX:=transfer/}" | |
| : "${MAILING_EXPORTS_CONFIG:=[]}" | |
| : "${CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED:=false}" | |
| : "${SPONSOR_FINANCE_ENABLED:=false}" | |
| : "${CONVERSATIONAL_EXECUTION_ENABLED:=false}" | |
| : "${CONVERSATIONAL_ENABLED_PLUGINS:=none}" | |
| : "${CONVERSATIONAL_TYPEFULLY_EXTERNAL_EXECUTION_ENABLED:=false}" | |
| : "${CONVERSATIONAL_TYPEFULLY_ACCOUNT_CONFIG_DIGEST:=sha256:0000000000000000000000000000000000000000000000000000000000000000}" | |
| : "${TYPEFULLY_ACCOUNT_CONFIG_REVISION:=unconfigured}" | |
| : "${TYPEFULLY_BASE_URL:=https://api.typefully.com}" | |
| : "${CONVERSATIONAL_TELEGRAM_VOICE_ENABLED:=false}" | |
| : "${CONVERSATIONAL_TELEGRAM_PHOTO_ENABLED:=false}" | |
| : "${CONVERSATIONAL_ALARM_SUBSCRIPTION_CONFIRMED:=false}" | |
| : "${ZAI_VISION_MODEL:=glm-4.6v}" | |
| : "${ZAI_VISION_BASE_URL:=https://api.z.ai/api/paas/v4/chat/completions}" | |
| : "${TELEGRAM_HANDLER_DEADLINE_MS:=28000}" | |
| : "${TELEGRAM_API_TIMEOUT_MS:=5000}" | |
| : "${TELEGRAM_API_MAX_RESPONSE_BYTES:=65536}" | |
| : "${TELEGRAM_VOICE_MAX_BYTES:=20971520}" | |
| : "${TELEGRAM_VOICE_MAX_SECONDS:=300}" | |
| : "${TELEGRAM_PHOTO_MAX_BYTES:=10485760}" | |
| : "${TELEGRAM_PHOTO_MAX_PIXELS:=20000000}" | |
| : "${TELEGRAM_MEDIA_DOWNLOAD_TIMEOUT_MS:=8000}" | |
| : "${TELEGRAM_MEDIA_PROVIDER_TIMEOUT_MS:=18000}" | |
| : "${TELEGRAM_MEDIA_PROVIDER_MAX_RESPONSE_BYTES:=65536}" | |
| : "${TELEGRAM_MEDIA_MAX_TEXT_BYTES:=16384}" | |
| : "${SPONSOR_COMMUNICATION_SEND_ENABLED:=false}" | |
| : "${SPONSOR_SES_IDENTITY_ARN:=arn:aws:ses:eu-west-1:000000000000:identity/example.invalid}" | |
| : "${SPONSOR_SES_FROM_ADDRESS:=no-reply@example.invalid}" | |
| : "${SPONSOR_SES_CONFIGURATION_SET_NAME:=dataops-sponsor-reviewed-send-disabled}" | |
| strict_boolean() { | |
| case "$2" in | |
| true|false) ;; | |
| *) echo "$1 must be exactly true or false" >&2; exit 1 ;; | |
| esac | |
| } | |
| strict_boolean CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED "$CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED" | |
| strict_boolean CONVERSATIONAL_EXECUTION_ENABLED "$CONVERSATIONAL_EXECUTION_ENABLED" | |
| strict_boolean CONVERSATIONAL_TYPEFULLY_EXTERNAL_EXECUTION_ENABLED "$CONVERSATIONAL_TYPEFULLY_EXTERNAL_EXECUTION_ENABLED" | |
| strict_boolean CONVERSATIONAL_TELEGRAM_VOICE_ENABLED "$CONVERSATIONAL_TELEGRAM_VOICE_ENABLED" | |
| strict_boolean CONVERSATIONAL_TELEGRAM_PHOTO_ENABLED "$CONVERSATIONAL_TELEGRAM_PHOTO_ENABLED" | |
| strict_boolean CONVERSATIONAL_ALARM_SUBSCRIPTION_CONFIRMED "$CONVERSATIONAL_ALARM_SUBSCRIPTION_CONFIRMED" | |
| case "$TELEGRAM_API_TIMEOUT_MS" in | |
| ''|*[!0-9]*) echo "TELEGRAM_API_TIMEOUT_MS must be an integer from 100 through 5000" >&2; exit 1 ;; | |
| esac | |
| if [ "$TELEGRAM_API_TIMEOUT_MS" -lt 100 ] || [ "$TELEGRAM_API_TIMEOUT_MS" -gt 5000 ]; then | |
| echo "TELEGRAM_API_TIMEOUT_MS must be an integer from 100 through 5000" >&2 | |
| exit 1 | |
| fi | |
| case "$CONVERSATIONAL_ENABLED_PLUGINS" in | |
| none|todo|typefully|todo,typefully) ;; | |
| *) echo "CONVERSATIONAL_ENABLED_PLUGINS must be a canonical plugin subset" >&2; exit 1 ;; | |
| esac | |
| if [ "$CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED" = "true" ] && [ -z "$ZAI_CONVERSATIONAL_API_KEY_SECRET_ARN" ]; then | |
| echo "Telegram ingress cannot be enabled without its z.ai secret ARN" >&2 | |
| exit 1 | |
| fi | |
| if [ "$CONVERSATIONAL_TYPEFULLY_EXTERNAL_EXECUTION_ENABLED" = "true" ] && { \ | |
| [ "$CONVERSATIONAL_EXECUTION_ENABLED" != "true" ] \ | |
| || { [ "$CONVERSATIONAL_ENABLED_PLUGINS" != "typefully" ] && [ "$CONVERSATIONAL_ENABLED_PLUGINS" != "todo,typefully" ]; } \ | |
| || [ -z "$WORK_ENGINE_TYPEFULLY_API_TOKEN_SECRET_ARN" ] \ | |
| || [ -z "$TYPEFULLY_SOCIAL_SET_ALEXEY" ] \ | |
| || [ -z "$TYPEFULLY_SOCIAL_SET_DATATALKSCLUB" ] \ | |
| || [ "$TYPEFULLY_ACCOUNT_CONFIG_REVISION" = "unconfigured" ]; \ | |
| }; then | |
| echo "Conversational Typefully execution requires the exact secret ARN, both account mappings, and a configuration revision" >&2 | |
| exit 1 | |
| fi | |
| if [ "$CONVERSATIONAL_TELEGRAM_VOICE_ENABLED" = "true" ] && { [ "$CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED" != "true" ] || [ -z "$GROQ_TRANSCRIPTION_API_KEY_SECRET_ARN" ]; }; then | |
| echo "Telegram voice requires the global adapter and Groq secret ARN" >&2 | |
| exit 1 | |
| fi | |
| if [ "$CONVERSATIONAL_TELEGRAM_PHOTO_ENABLED" = "true" ] && { [ "$CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED" != "true" ] || [ -z "$ZAI_VISION_API_KEY_SECRET_ARN" ]; }; then | |
| echo "Telegram photo requires the global adapter and z.ai vision secret ARN" >&2 | |
| exit 1 | |
| fi | |
| if [ "$SPONSOR_COMMUNICATION_SEND_ENABLED" = "true" ] && { [ -z "$SPONSOR_COMMUNICATION_TEMPLATE_SECRET_ARN" ] || [ -z "$SPONSOR_COMMUNICATION_HMAC_SECRET_ARN" ]; }; then | |
| echo "Sponsor reviewed sending cannot be enabled without separate template and HMAC secret ARNs" >&2 | |
| exit 1 | |
| fi | |
| sam deploy \ | |
| --config-env full-sandbox \ | |
| --parameter-overrides \ | |
| "ParameterKey=GitHubOwner,ParameterValue=$DOCS_GITHUB_OWNER" \ | |
| "ParameterKey=GitHubRepo,ParameterValue=$DOCS_GITHUB_REPO" \ | |
| "ParameterKey=GitHubTokenSecretName,ParameterValue=$DOCS_GITHUB_TOKEN_SECRET" \ | |
| "ParameterKey=GitHubBranch,ParameterValue=$DOCS_GITHUB_BRANCH" \ | |
| "ParameterKey=AuthBaseUrl,ParameterValue=$AUTH_BASE_URL" \ | |
| "ParameterKey=AuthUserPoolId,ParameterValue=$AUTH_USER_POOL_ID" \ | |
| "ParameterKey=AuthIssuer,ParameterValue=$AUTH_ISSUER" \ | |
| "ParameterKey=AuthJwksUrl,ParameterValue=$AUTH_JWKS_URL" \ | |
| "ParameterKey=AuthClientId,ParameterValue=$AUTH_CLIENT_ID" \ | |
| "ParameterKey=AuthCallbackUrl,ParameterValue=$AUTH_CALLBACK_URL" \ | |
| "ParameterKey=AuthLogoutUrl,ParameterValue=$AUTH_LOGOUT_URL" \ | |
| "ParameterKey=AuthSessionLifetimeSeconds,ParameterValue=$AUTH_SESSION_LIFETIME_SECONDS" \ | |
| "ParameterKey=EmailDocumentIntakeSecretArn,ParameterValue=$EMAIL_DOCUMENT_INTAKE_SECRET_ARN" \ | |
| "ParameterKey=EmailDocumentExternalSourceBucketName,ParameterValue=$EMAIL_DOCUMENT_EXTERNAL_SOURCE_BUCKET" \ | |
| "ParameterKey=EmailDocumentExternalSourcePrefix,ParameterValue=$EMAIL_DOCUMENT_EXTERNAL_SOURCE_PREFIX" \ | |
| "ParameterKey=EmailDocumentExternalSourceKmsKeyArn,ParameterValue=$EMAIL_DOCUMENT_EXTERNAL_SOURCE_KMS_KEY_ARN" \ | |
| "ParameterKey=DapierCredentialsTableName,ParameterValue=$DAPIER_CREDENTIALS_TABLE_NAME" \ | |
| "ParameterKey=DapierCredentialsTableArn,ParameterValue=$DAPIER_CREDENTIALS_TABLE_ARN" \ | |
| "ParameterKey=MailingExportsConfig,ParameterValue=$MAILING_EXPORTS_CONFIG" \ | |
| "ParameterKey=ConversationalTelegramIngressEnabled,ParameterValue=$CONVERSATIONAL_TELEGRAM_INGRESS_ENABLED" \ | |
| "ParameterKey=SponsorFinanceEnabled,ParameterValue=$SPONSOR_FINANCE_ENABLED" \ | |
| "ParameterKey=ConversationalExecutionEnabled,ParameterValue=$CONVERSATIONAL_EXECUTION_ENABLED" \ | |
| "ParameterKey=ConversationalEnabledPlugins,ParameterValue=$CONVERSATIONAL_ENABLED_PLUGINS" \ | |
| "ParameterKey=ConversationalTypefullyExternalExecutionEnabled,ParameterValue=$CONVERSATIONAL_TYPEFULLY_EXTERNAL_EXECUTION_ENABLED" \ | |
| "ParameterKey=ConversationalTypefullyAccountConfigDigest,ParameterValue=$CONVERSATIONAL_TYPEFULLY_ACCOUNT_CONFIG_DIGEST" \ | |
| "ParameterKey=TypefullyAccountConfigRevision,ParameterValue=$TYPEFULLY_ACCOUNT_CONFIG_REVISION" \ | |
| "ParameterKey=TypefullyBaseUrl,ParameterValue=$TYPEFULLY_BASE_URL" \ | |
| "ParameterKey=WorkEngineTypefullyApiTokenSecretArn,ParameterValue=$WORK_ENGINE_TYPEFULLY_API_TOKEN_SECRET_ARN" \ | |
| "ParameterKey=TypefullySocialSetAlexey,ParameterValue=$TYPEFULLY_SOCIAL_SET_ALEXEY" \ | |
| "ParameterKey=TypefullySocialSetDataTalksClub,ParameterValue=$TYPEFULLY_SOCIAL_SET_DATATALKSCLUB" \ | |
| "ParameterKey=ZaiConversationalApiKeySecretArn,ParameterValue=$ZAI_CONVERSATIONAL_API_KEY_SECRET_ARN" \ | |
| "ParameterKey=ConversationalTelegramVoiceEnabled,ParameterValue=$CONVERSATIONAL_TELEGRAM_VOICE_ENABLED" \ | |
| "ParameterKey=ConversationalTelegramPhotoEnabled,ParameterValue=$CONVERSATIONAL_TELEGRAM_PHOTO_ENABLED" \ | |
| "ParameterKey=ConversationalAlarmSubscriptionConfirmed,ParameterValue=$CONVERSATIONAL_ALARM_SUBSCRIPTION_CONFIRMED" \ | |
| "ParameterKey=DataOpsDeploymentId,ParameterValue=$GITHUB_SHA" \ | |
| "ParameterKey=GroqTranscriptionApiKeySecretArn,ParameterValue=$GROQ_TRANSCRIPTION_API_KEY_SECRET_ARN" \ | |
| "ParameterKey=ZaiVisionApiKeySecretArn,ParameterValue=$ZAI_VISION_API_KEY_SECRET_ARN" \ | |
| "ParameterKey=ZaiVisionModel,ParameterValue=$ZAI_VISION_MODEL" \ | |
| "ParameterKey=ZaiVisionBaseUrl,ParameterValue=$ZAI_VISION_BASE_URL" \ | |
| "ParameterKey=TelegramHandlerDeadlineMs,ParameterValue=$TELEGRAM_HANDLER_DEADLINE_MS" \ | |
| "ParameterKey=TelegramApiTimeoutMs,ParameterValue=$TELEGRAM_API_TIMEOUT_MS" \ | |
| "ParameterKey=TelegramApiMaxResponseBytes,ParameterValue=$TELEGRAM_API_MAX_RESPONSE_BYTES" \ | |
| "ParameterKey=TelegramVoiceMaxBytes,ParameterValue=$TELEGRAM_VOICE_MAX_BYTES" \ | |
| "ParameterKey=TelegramVoiceMaxSeconds,ParameterValue=$TELEGRAM_VOICE_MAX_SECONDS" \ | |
| "ParameterKey=TelegramPhotoMaxBytes,ParameterValue=$TELEGRAM_PHOTO_MAX_BYTES" \ | |
| "ParameterKey=TelegramPhotoMaxPixels,ParameterValue=$TELEGRAM_PHOTO_MAX_PIXELS" \ | |
| "ParameterKey=TelegramMediaDownloadTimeoutMs,ParameterValue=$TELEGRAM_MEDIA_DOWNLOAD_TIMEOUT_MS" \ | |
| "ParameterKey=TelegramMediaProviderTimeoutMs,ParameterValue=$TELEGRAM_MEDIA_PROVIDER_TIMEOUT_MS" \ | |
| "ParameterKey=TelegramMediaProviderMaxResponseBytes,ParameterValue=$TELEGRAM_MEDIA_PROVIDER_MAX_RESPONSE_BYTES" \ | |
| "ParameterKey=TelegramMediaMaxTextBytes,ParameterValue=$TELEGRAM_MEDIA_MAX_TEXT_BYTES" \ | |
| "ParameterKey=SponsorCommunicationSendEnabled,ParameterValue=$SPONSOR_COMMUNICATION_SEND_ENABLED" \ | |
| "ParameterKey=SponsorCommunicationTemplateSecretArn,ParameterValue=$SPONSOR_COMMUNICATION_TEMPLATE_SECRET_ARN" \ | |
| "ParameterKey=SponsorCommunicationHmacSecretArn,ParameterValue=$SPONSOR_COMMUNICATION_HMAC_SECRET_ARN" \ | |
| "ParameterKey=SponsorSesIdentityArn,ParameterValue=$SPONSOR_SES_IDENTITY_ARN" \ | |
| "ParameterKey=SponsorSesFromAddress,ParameterValue=$SPONSOR_SES_FROM_ADDRESS" \ | |
| "ParameterKey=SponsorSesConfigurationSetName,ParameterValue=$SPONSOR_SES_CONFIGURATION_SET_NAME" | |
| - name: Seed runtime users, workflow templates, and recurring configs | |
| run: | | |
| set -euo pipefail | |
| backend_function="$(aws cloudformation describe-stack-resource \ | |
| --stack-name dataops-v1 \ | |
| --logical-resource-id BackendFunction \ | |
| --query "StackResourceDetail.PhysicalResourceId" \ | |
| --output text)" | |
| seed_response="$RUNNER_TEMP/dataops-runtime-seeds.json" | |
| seed_error="$(aws lambda invoke \ | |
| --function-name "$backend_function" \ | |
| --cli-binary-format raw-in-base64-out \ | |
| --payload '{"source":"dataops.deploy","detail-type":"Runtime Seed","detail":{"dataopsAction":"sync-runtime-seeds"}}' \ | |
| --query FunctionError \ | |
| --output text \ | |
| "$seed_response")" | |
| if [ "$seed_error" != "None" ]; then | |
| echo "Runtime seed failed inside the deployed Lambda" >&2 | |
| exit 1 | |
| fi | |
| if ! jq -e ' | |
| def nonnegative_integer: type == "number" and floor == . and . >= 0; | |
| .statusCode == 200 | |
| and ((.body | fromjson) as $body | |
| | ($body | keys == ["recurring", "templates", "users"]) | |
| and ($body.users | keys == ["created", "processed", "unchanged", "updated"]) | |
| and ($body.users.processed == 3) | |
| and ([$body.users.created, $body.users.updated, $body.users.unchanged] | all(.[]; nonnegative_integer)) | |
| and ($body.users.created + $body.users.updated + $body.users.unchanged == 3) | |
| and ($body.templates | keys == ["created", "total", "unchanged", "updated"]) | |
| and ($body.templates.total == 11) | |
| and ([$body.templates.created, $body.templates.updated, $body.templates.unchanged] | all(.[]; nonnegative_integer)) | |
| and ($body.templates.created + $body.templates.updated + $body.templates.unchanged == 11) | |
| and ($body.recurring | keys == ["created", "repairedTasks", "skipped", "total", "updated"]) | |
| and ($body.recurring.total == 7) | |
| and ([$body.recurring.created, $body.recurring.updated, $body.recurring.skipped, $body.recurring.repairedTasks] | all(.[]; nonnegative_integer)) | |
| and ($body.recurring.created + $body.recurring.updated + $body.recurring.skipped == 7) | |
| ) | |
| ' "$seed_response" >/dev/null; then | |
| echo "Runtime seed did not confirm the exact users, Templates, and recurring-config contract" >&2 | |
| exit 1 | |
| fi | |
| echo "Runtime users, workflow templates, and recurring configs synchronized." | |
| - name: Smoke test deployed single-origin backend | |
| run: | | |
| backend_url="$(aws cloudformation describe-stacks \ | |
| --stack-name dataops-v1 \ | |
| --query "Stacks[0].Outputs[?OutputKey=='BackendUrl'].OutputValue" \ | |
| --output text)" | |
| # The Function URL output ends with a trailing slash; strip it so we hit | |
| # "/" and "/login" instead of malformed double-slash paths ("//", "//login"). | |
| backend_url="${backend_url%/}" | |
| # /login redirects to the shared Cognito authorize endpoint and root redirects locally. | |
| # The Function URL event normalization in handler.ts must work for this. | |
| # Retry a few times: immediately after a stack update the Function URL can | |
| # briefly return a cold-start/propagation blip before the new code settles. | |
| code="" | |
| login_code="" | |
| for attempt in 1 2 3 4 5; do | |
| code=$(curl -sS -o /dev/null -w "%{http_code}" "$backend_url/") | |
| login_code=$(curl -sS -o /dev/null -w "%{http_code}" "$backend_url/login") | |
| if [ "$code" = "302" ] && [ "$login_code" = "302" ]; then | |
| echo "Backend smoke OK on attempt $attempt: root=$code login=$login_code" | |
| exit 0 | |
| fi | |
| echo "Smoke attempt $attempt: root=$code login=$login_code (retrying)" | |
| sleep 10 | |
| done | |
| echo "Backend smoke test failed after retries: root expected 302 got $code; /login expected 302 got $login_code" | |
| exit 1 |