Carry main infrastructure and auth fixes into redesign main (#746) - #630
Conversation
* Add Terraform for Render production and staging * Handle Render database URLs without explicit ports * Keep production intact through the first apply and isolate staging integrations The imported API service ignores its own env_vars so the first apply only adds and links the environment group, and the existing Render project is imported (environments keyed by their live names) so the API stays in its Production environment while a Staging environment is added. Staging gets placeholder Stripe identifiers, staging return URLs, its own redirect allowlist and an invalid Supabase host, so it cannot touch production billing or storage. Empty custom domain lists become null, the landing no longer auto-deploys, and the web health check uses /api/health. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 1480d1b)
* feat: store confirmed waitlist marketing contacts locally * fix: preserve marketing opt-outs and canonicalize waitlist languages * Restore user marketing delivery after explicit opt-in (cherry picked from commit 4c7b76a)
* Implement Google authorization code sign in for ticket 796 * Register Google code routes in agent catalog * Parse Google token responses with installed SDK * fix: retry Google code sign-in after redemption (cherry picked from commit 2301ccc)
* Add Cloudflare DNS and Turnstile Terraform resources * Verify Cloudflare DNS answers before the registrar switch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 7e946a6)
* Add gated production API release workflow * Handle queued Render deploys and recheck the live commit before recording a release Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 78b42f2)
* fix: preserve workflow deployed web digests in Terraform * docs: guard web service applies against Render image tag bug (cherry picked from commit b8ea2c1)
(cherry picked from commit 5e5623e)
There was a problem hiding this comment.
Important
Preserve existing marketing opt-outs before switching broadcast audiences, and prevent Terraform applies from replacing approved web image digests.
Reviewed changes across the eight commits carrying main infrastructure and auth changes into redesign/main:
- Infrastructure: Adds Render, AWS, Cloudflare, and Turnstile Terraform resources, production API deployment workflow, and EF migration bundle.
- Marketing: Moves waitlist contacts into the API database and incorporates them into broadcast, consent, unsubscribe, and account deletion flows.
- Authentication: Adds Google authorization-code sign-in and localized errors; checked its endpoint contract against the paired consumer PR orbit-ui-mobile#1219.
- Verification: Adds unit coverage, Terraform validation, and Docker bundle checks.
⚠️ Existing marketing opt-outs are missing from the new broadcast audience
The new MarketingContacts table starts empty while the existing waitlist audience lives in Resend. Switching IMarketingContactsService to database storage does not transfer Resend's unsubscribe or suppression flags: an address that opted out in Resend can reconfirm in the database and receive a broadcast. The PR only makes a staging backfill conditional; production needs a consent-preserving transition before database-backed broadcasts are enabled.
Technical details
# Preserve marketing consent during the audience cutover
## Affected sites
- `src/Orbit.Infrastructure/Migrations/20260927211258_AddMarketingContacts.cs:14` creates an empty contact table.
- `src/Orbit.Api/Extensions/ServiceCollectionExtensions.Infrastructure.cs:72` switches contact persistence to that table.
- `src/Orbit.Infrastructure/Services/DatabaseMarketingContactsService.cs:16` creates a new active contact if none exists locally.
- `src/Orbit.Application/Marketing/Commands/SendMarketingBroadcastCommand.cs:66` sends to locally eligible waitlist contacts without consulting historical Resend opt-outs.
## Required outcome
- Import and verify existing confirmed contacts and their opt-out/suppression state before any database-backed broadcast can reach them, in every affected environment, or retain an equivalent authoritative suppression check through the transition.GPT Sol | 𝕏
|
Disposition of the review finding on |
|
@pullfrog review |
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes since the prior Pullfrog review: no new commits landed; rechecked the existing eight-commit carry and its resolved discussion.
- Rechecked infrastructure: Confirmed the Terraform digest concern is resolved in discussion and tracked for the next carry as
thomasluizon/orbit-tickets#814. - Rechecked marketing and authentication: Found no new changes to the database-backed audience or Google authorization-code flow.
GPT Sol | 𝕏

Change
Carries the seven specified main commits, in order, into
redesign/mainwithgit cherry-pick -x. Links thomasluizon/orbit-tickets#746. The standing ticket stays open.1480d1ba(Add Render Terraform for production and staging #621): added the Render and AWS Terraform foundation ininfra/, its validation workflow, and the root allowlist entry. No overlap with redesign code.4c7b76a4(Store confirmed waitlist contacts in Orbit database #620): added database backed waitlist contacts, marketing consent handling, and20260927211258_AddMarketingContacts. Resolved the waitlist handler conflict as described below. The migration snapshot includes the new table and unique email index.2301ccc2(Add Google authorization code sign in #622): added Google authorization code sign in, its auth operation, SDK exchange service, validator, tests, and generated OpenAPI routes. Resolved the error catalog conflict as described below. The carried controller test was aligned with redesign's existing error response shape.7e946a67(Add Cloudflare DNS and Turnstile to Terraform #623): added Cloudflare DNS and Turnstile Terraform resources and the DNS cutover check. No conflicts.78b42f2d(Gate production API releases through Render #624): added the gated production API release workflow and migration bundle path. No conflicts. Staging retainsDatabase__MigrateOnStartup=truewhile production uses the bundle.b8ea2c13(Keep Terraform from reverting web release digests #627): made Terraform ignore workflow controlled web image digests. No conflicts.5e5623ec(Configure staging billing from SSM and verify test purchases #629): added staging billing SSM inputs and test purchase coverage. No conflicts.None of the seven commits was already carried: each showed
+ingit cherry -vimmediately before its cherry-pick, and each corresponding feature or file was absent from the current redesign tree. All seven resulting commits retain their source hash in acherry picked from committrailer.Conflict verdicts
Both content conflicts came from the redesign error copy change in
23396af4(#532):src/Orbit.Application/Waitlist/Commands/ConfirmWaitlistCommand.cs: kept redesign'sInvalidWaitlistConfirmationerror and added main's signed language extraction, canonicalization, and contact language persistence. Both invalid token and invalid language return the shared error.src/Orbit.Application/Common/ErrorMessages.cs: kept redesign'sErrorCopybacked catalog and addedGoogleRedirectUriNotAllowedandGoogleCodeExchangeFailed. Added the two corresponding entries inErrorCopy.csso English and Portuguese lookups remain complete.The new assertion in
tests/Orbit.Infrastructure.Tests/Controllers/AuthControllerTests.csexpected lower case object members. It was corrected to assert the existing PascalCase error response and HTTP 401. The controller behavior remains the one already used byGoogleAuth.Migration order
20260927211258_AddMarketingContactsfollows the redesign and previously carried migrations, ending with20260927014645_DropFoldDueTimeScheduledRemindersTrigger.dotnet ef migrations listreturned one ordered history and exited 0. The local worktree has no database connection, so applied status was unavailable.dotnet ef migrations has-pending-model-changesexited 0 and reported no model changes. The migration uses EF'sCreateTableandCreateIndexmethods; no raw index SQL was added.External interface evidence
TokenResponse.FromHttpResponseAsync, token properties, signature validation settings, and email claims. The exchange service delegates response parsing to that SDK.ignore_changes. This worktree installed the pinned providers andterraform validateexited 0.TestPurchasemarker and AWS SSM value schema used by the carried billing change.Assumptions
ErrorCopycatalog; rejected restoring main's inline English error catalog because it would replace redesign behavior.Manual steps
Google__AllowedRedirectUris__0ishttps://staging.useorbit.org/auth-callbackandGoogle__ClientIdandGoogle__ClientSecretare configured in Render >orbit-staging-api> Environment. Add that callback in Google Cloud Console > APIs & Services > Credentials > OAuth 2.0 client > Authorized redirect URIs. A successful code exchange for that exact URI proves both settings.us-east-2, confirm/orbit/staging/api/Stripe__ProProductId,/orbit/staging/api/Stripe__MonthlyPriceIdUsd,/orbit/staging/api/Stripe__YearlyPriceIdUsd,/orbit/staging/api/Stripe__MonthlyPriceIdBrl, and/orbit/staging/api/Stripe__YearlyPriceIdBrlexist as String parameters, with/orbit/staging/api/Stripe__SecretKeyand/orbit/staging/api/Stripe__WebhookSecretas SecureString test values. A Terraform plan resolving all seven and mapping them toorbit-staging-apiproves the inputs exist.RENDER_API_KEY,CLOUDFLARE_API_TOKEN, AWS credentials, and ignoredinfra/local.tfvars; review and apply the Terraform plan. Verify Render'sorbit-staging-apienvironment group hasDatabase__MigrateOnStartup=true, the new billing keys, andBotProtection__SecretKey. Verify theturnstile_site_keyandcloudflare_name_serversTerraform outputs. The production API environment must retainDatabase__MigrateOnStartup=falseand./efbundleas its predeploy command.MarketingContactstable in the Render Postgres database, preserving opt outs. Matching distinct normalized email counts and sampled unsubscribe and suppression timestamps prove the import.Test evidence
env -u LANG LC_ALL=en_US.UTF-8 dotnet build Orbit.slnx -v quiet: exit 0, zero errors.env -u LANG LC_ALL=en_US.UTF-8 dotnet test: exit 0, 7,917 passed, zero failed across four projects. An earlier run found one failing carried controller assertion; its focused rerun passed 1/1 after the response shape correction.terraform fmt -check -recursive infra: exit 0.terraform -chdir=infra init -backend=false -input=false: exit 0.terraform -chdir=infra validate: exit 0.dotnet ef migrations list --project src/Orbit.Infrastructure --startup-project src/Orbit.Api --no-build: exit 0.dotnet ef migrations has-pending-model-changeswith the same project options: exit 0, no pending changes.node tools/arch-map.mjs,actionlintover the three changed workflows, root allowlist, dash baseline, timeless, suppression allowlist, andgit diff --check: all passed.dotnet buildregeneratedsrc/Orbit.Api/openapi.jsonafter the last cherry-pick, with no further diff.