Skip to content

Add manual API release workflow for production and staging - #632

Merged
thomasluizon merged 1 commit into
mainfrom
feature/ticket-816-api-release
Sep 28, 2026
Merged

thomasluizon merged 1 commit into
mainfrom
feature/ticket-816-api-release

Conversation

@thomasluizon

Copy link
Copy Markdown
Owner

Closes thomasluizon/orbit-tickets#816

Change

  • Replaced .github/workflows/deploy-api.yml with .github/workflows/release.yml. It runs only by manual dispatch from main, rejects a production branch other than main before checkout, checks that the checkout matches the selected branch head, and serializes releases per environment. The production Render deploy, health check, live commit check, and GitHub Deployment behavior carry forward. Staging sets its tracked branch and disables auto-deploy before triggering the selected commit, then checks staging health and records its Deployment.
  • Updated infra/staging.tf to disable staging API auto-deploy, preserve its workflow-selected branch across Terraform applies, create the staging landing site with the production landing build and delivery settings, and output landing_staging_service_id.
  • Updated README.md, infra/README.md, and CLAUDE.md with the manual release path and Terraform apply caution.

The workflow uses one release path for both environments so commit resolution, Render polling, health verification, and GitHub Deployment recording stay aligned. No Render service was changed by this PR branch.

Assumptions

  • The staging landing site starts with tracked branch main and copies production landing headers and routes; the alternative was a staging-specific initial branch or delivery settings.
  • Terraform preserves the staging landing branch after creation for its own release workflow; the alternative was resetting it to main on every apply.

Manual steps

  • In the infra Terraform workspace, review a refreshed saved plan with the approved web image digests and apply it. Cancel the Render Dashboard deploy started by the staging API setting change unless that branch head is the intended release. Confirm GET https://api.render.com/v1/services/srv-dasotg8473hc739a5gjg reports autoDeploy: no, and confirm the new orbit-landing-staging site exists in the Render Staging environment.
  • In thomasluizon/orbit-landing-page, open Settings > Secrets and variables > Actions > Variables and set RENDER_LANDING_STAGING_SERVICE_ID to the landing_staging_service_id Terraform output. Read the variable back in that screen and compare it with the output before the landing release workflow runs.

The RENDER_STAGING_SERVICE_ID variable is already set in thomasluizon/orbit-api to srv-dasotg8473hc739a5gjg and was read back with gh variable list. The existing RENDER_API_KEY secret is present in the repository.

Test evidence

  • actionlint .github/workflows/release.yml: passed.
  • terraform fmt -check -recursive infra: passed.
  • terraform -chdir=infra validate: passed.
  • Saved plan with -refresh=false because no Cloudflare API token is available in this worker: render_web_service.staging_api updates only auto_deploy from true to false; render_web_service.staging_web and render_web_service.production_api have no change; render_web_service.production_web is a create, not an in-place update; render_static_site.staging_landing is a create. The plan was not applied.
  • dotnet build Orbit.slnx: 0 errors.
  • dotnet test: 6,813 passed, 0 failed.
  • Dash and timeless checks passed. No route, endpoint, or module changed, so architecture artifacts were not regenerated.

External interface evidence

  • Read-only live GET https://api.render.com/v1/services/srv-dasotg8473hc739a5gjg with RENDER_MCP_TOKEN returned the projection { "id": "srv-dasotg8473hc739a5gjg", "branch": "redesign/main", "autoDeploy": "yes" }. Reproduce with curl -fsS -H "Authorization: Bearer $RENDER_MCP_TOKEN" https://api.render.com/v1/services/srv-dasotg8473hc739a5gjg | jq '{id, branch, autoDeploy}'. The Render update service reference confirms PATCH accepts branch and autoDeploy and returns both fields. No mutating Render API call was made here.
  • Read-only live GET https://api.render.com/v1/services/srv-dasotg8473hc739a5gjg/deploys?limit=1 returned the projection { "deploy": { "id": "dep-dass32bl550s739k9cq0", "status": "live", "commit": { "id": "e1bbb37ad1bfb2030764da256d589f9393cec78c" } } }. Reproduce with the same authorization header and jq '.[0] | {deploy: {id: .deploy.id, status: .deploy.status, commit: {id: .deploy.commit.id}}}'. The Render trigger deploy reference, retrieve deploy reference, and list deploys reference confirm the request and response fields. The workflow sets the tracked branch before passing commitId; cross-branch POST behavior was not tested against a live service.
  • gh api repos/thomasluizon/orbit-api/branches/redesign/main --jq '{name: .name, sha: .commit.sha}' returned { "name": "redesign/main", "sha": "e1bbb37ad1bfb2030764da256d589f9393cec78c" }. The GitHub branch reference confirms commit.sha.
  • gh api repos/thomasluizon/orbit-api/deployments --jq '.[0] | {id: .id, ref: .ref, environment: .environment}' returned an existing deployment with id, ref, and environment; the GitHub Deployment reference confirms the create response id. A read-only status GET returned state, environment, log_url, and environment_url for that deployment.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes in commit 62bc3052 across the manual release workflow, staging Terraform resources, and deployment guidance.

  • Release workflow: Selects production or staging, validates the requested branch and commit, and verifies the Render deployment before recording its GitHub Deployment.
  • Staging infrastructure: Disables API auto deploy, preserves release selected branches, and provisions a landing static site with its service ID output.
  • Documentation: Describes the release inputs, Terraform apply precautions, and repository variable setup.

Pullfrog  | View workflow run | Using GPT Sol | 𝕏

@sonarqubecloud

Copy link
Copy Markdown

@thomasluizon
thomasluizon merged commit 498015a into main Sep 28, 2026
27 checks passed
@thomasluizon
thomasluizon deleted the feature/ticket-816-api-release branch September 28, 2026 03:16
thomasluizon added a commit to thomasluizon/orbit-landing-page that referenced this pull request Sep 28, 2026
## Summary

- Replace the production-only deploy workflow with a manual release
workflow dispatched from `main`.
- Keep production fixed to the `main` branch and its existing Render
site and public domain checks.
- Set the staging Render site's tracked branch before deploying its
resolved head commit, verify the `orbit-build` marker, and record a
staging GitHub Deployment.

Related: thomasluizon/orbit-tickets#818

## Assumptions

- The `branch` input names a branch head; accepting a tag or arbitrary
commit ID was rejected.
- Staging verification and its Deployment URL use the Render service
URL; checking the production public domain for staging was rejected.

## Manual steps

- Apply the Terraform change in thomasluizon/orbit-api#632. In Render
Dashboard > Static Sites > `orbit-landing-staging` > Settings >
Auto-Deploy, confirm it shows Off; a read-only service API response with
`autoDeploy: no` proves the setting took effect.
- In GitHub > `thomasluizon/orbit-landing-page` > Settings > Secrets and
variables > Actions > Variables, set `RENDER_LANDING_STAGING_SERVICE_ID`
to the new staging site's service ID. A staging release that records a
Deployment for the selected branch head proves it took effect.
- In the same GitHub Variables screen, set `RENDER_LANDING_SERVICE_ID`
to the production `orbit-landing` service ID. A production release that
records a Deployment for `main` proves it took effect.
- In GitHub > `thomasluizon/orbit-landing-page` > Settings > Secrets and
variables > Actions > Secrets, confirm `RENDER_API_KEY` can access both
Render sites. Successful service reads and deploys for both environments
prove access.

## Verification

- `npm test`: 30 passed.
- `npm run build`: build and sitemap test passed.
- `npm run lint`, `npm run format:check`, `npm run check`, and `npm
audit --audit-level=moderate`: passed.
- Focused workflow checks passed for dispatch inputs, embedded
JavaScript syntax, five release selection cases, and mocked production
and staging request sequences.
thomasluizon added a commit that referenced this pull request Sep 28, 2026
…) (#635)

* Guard web service Terraform plans before apply (#631)

* Guard Terraform web service updates before apply

* Guard Terraform web service replacement and deletion

(cherry picked from commit 7a1e26a)

* Add manual API release workflow for production and staging (#632)

(cherry picked from commit 498015a)

* Add Amazon SES delivery and signed event handling (#628)

* Add SES identities, DNS, and delivery infrastructure

* Add SES sender and signed SNS event handling

* Classify SES webhook and clarify email contract

* Fix SES cutover controls and webhook verification

* Extract shared email composition for SES and Resend

* Isolate staging SES events and sender credentials

* Escape interpolated names in the SES isolation test patterns

CodeQL js/incomplete-hostname-regexp flagged the tuple values flowing into
new RegExp; every interpolated value is now escaped as a literal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f770b47)

* Keep staging awake and reseed its free database (#626)

* Add staging keepalive and database reseed lifecycle

* Document and strengthen staging seed command

* Keep Terraform plan details out of workflow logs

* Require successful health responses in staging workflows

* Parse Render creation time portably

* Isolate staging database state and gate reseed on deploy

* Read the staging database id from the main state before moving it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Correlate staging reseed deploy with live commit

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ee40ae9)

* fix: point apex DNS at Render landing site (#634)

(cherry picked from commit 65d355f)

* Move uploads to private S3 with stable read links (#625)

* Add configurable S3 upload signing

* Provision private S3 upload buckets and scoped credentials

* Expose storage provider cutover as Terraform variables

* Add stable read redirects for private S3 uploads

* Map public upload reads in agent catalog

* Fix upload read validation and S3 bucket safeguards

* Keep S3 upload reads available across provider rollback

* Bound anonymous upload reads per client IP

(cherry picked from commit 395239f)

* Fix Render database names in API connection strings (#633)

* fix: derive Render database names from connection URLs

* Generate the staging database name pattern with a match timeout

SonarCloud S6444 and SYSLIB1045 on the seed target check: the pattern is now
a GeneratedRegex with a one second timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0fd416f)

* Complete carried error copy and generated gating matrix

* Keep SES event notifications recoverable (#746)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant