Deploying a hosted site manually + rollback workflow

Most hosted-site deploys happen automatically on push, but there are cases where you want to trigger one by hand — testing the pipeline, recovering from a missed webhook, or rolling back to a known-good commit.

Manual deploy:
1. Open Builder → Sites → the hosted site
2. Click Deploy now on the site card
3. Turtini re-pulls the connected branch's tip and runs the full pipeline (Trees API → scan → upload → GC)
4. The button shows "Deploying…" while it runs; the badge updates to OK or Failed when done

Use this when:
• You changed the connected branch (e.g. switched from main to dist) and want an immediate refresh
• A webhook failed (visible as integrationEvents with status: 'error') and you want to retry
• You want to verify the pipeline still works after a Turtini platform deploy
• You manually edited the GCS bucket and want a clean re-sync

Rollback to an older commit:
The pipeline always pulls the current branch tip. There's no "deploy this specific commit" button in V1. To roll back:
1. Identify the good commit (look at hostedSites/{siteId}.lastDeploy.commitSha for the previously-good one)
2. On GitHub, force-push the branch to that commit:
git checkout <branchName>
git reset --hard <good-commit-sha>
git push --force-with-lease origin <branchName>
3. The webhook fires, Turtini pulls the old commit, deploy completes

Or use a revert commit (cleaner history):
git revert <bad-commit-sha>
git push

Either way the deploy auto-fires from the resulting push.

Audit trail:
Every deploy (auto or manual) writes a row to integrationEvents with kind: hosted_site_deploy. Includes commit SHA, file count, scan findings count, source ('webhook' | 'manual' | 'setup'), actorUid, and outcome. Read this from Settings → Integrations → GitHub or via the listHostedSites callable. Use it to confirm a manual deploy actually ran, or to track down "why did the deploy fail at 3am last night?"

Deploy locks:
There's no hard concurrency lock on a single site. Two webhook events arriving at the same time can both kick off a pull. The "last writer wins" semantics are fine because GCS uploads are idempotent at the path level. If you see weird half-state, hit Deploy now to force a clean re-sync.

Failed deploys + the previous version:
A failed deploy doesn't replace the last-good files in the bucket — the upload step is conditional on a clean scan. Visitors continue seeing the last successful version. The site card surfaces the failure (red badge, errorMessage on lastDeploy) so you know to fix the issue and retry.