```html

Diagnosing and Staging a Google Apps Script Deployment Outage: The 403/404 Reserve Widget Crisis

What Happened

On June 2, 2026, all eleven event-listing pages across sailjada.com and queenofsandiego.com stopped accepting reservations. The "Reserve" button—a Google Apps Script web app embedded as an iframe—returned either HTTP 403 (Forbidden) or 404 (Not Found). This was a total stoppage affecting every inbound booking funnel, yet silent to end users; they saw a broken button, not an error message.

Root Cause Analysis

The outage had two independent causes:

  • Primary (10 event pages): Apps Script deployment 1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3-NMX_9v0lQJ5 had its access control reverted to "Owner only" (or equivalent private), causing the public-facing endpoint https://script.google.com/macros/s/AKfycbWe...44Pme8wCA/exec to reject all unauthorized requests with 403.
  • Secondary (worship page): A separate deployment was entirely deleted, leaving the iframe pointing to a non-existent endpoint, returning 404.

Both issues appear to have been introduced during a prior session's re-authentication and tooling refresh. The Google OAuth tokens were refreshed, but deployment permissions were not verified before going live.

Investigation Workflow

The diagnosis followed a systematic reverse-engineering path:

  1. Identify the live endpoints: Inspected all 11 event pages' HTML to extract iframe src attributes. Found two distinct Apps Script project IDs being called.
  2. Test HTTP status: Curled both endpoints:
    curl -I https://script.google.com/macros/s/AKfycbWe...44Pme8wCA/exec
    # Returns 403 Forbidden
    
    curl -I https://script.google.com/macros/s/AKfycbWe...AFsLWaO3/exec
    # Returns 404 Not Found
  3. Cross-reference Apps Script projects: Searched the source repository and ops notes for mentions of the project ID 1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3 and found it referenced in deployment documentation.
  4. Inspect clasp configurations: Located .clasp.json files in the source tree to confirm which local project maps to which Google Apps Script deployment.
  5. Verify project accessibility: Authenticated to the Google Apps Script console and attempted to open the project. Confirmed it exists and is owned by the operations account, but the deployment had incorrect access settings.

Technical Details: The Fix (Staged, Not Yet Applied)

The fix is a single action in the Google Apps Script console, but understanding *why* it works requires clarity on Apps Script's deployment model:

  • Project ID vs. Deployment ID: The Apps Script *project* is the source code container. A *deployment* is a versioned, published instance of that project with its own URL and access control. The URL format is https://script.google.com/macros/s/{DEPLOYMENT_ID}/exec.
  • Access Control: Each deployment has an "Execute as" setting (who the code runs as) and a "Who has access" setting (who can invoke it). For a public reserve widget, access must be set to "Anyone."
  • Current State: The deployment exists and is reachable (not deleted), but its "Who has access" is set to a private mode (likely "Me" or an explicit user list), causing the 403.

The staged fix:

  1. Open script.google.com → Project 1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3-NMX_9v0lQJ5
  2. Navigate to Deploy → Manage deployments
  3. Click the active deployment (deployment ID ending in 44Pme8wCA)
  4. Set "Who has access" to "Anyone"
  5. Set "Execute as" to "Me" (the ops account)
  6. Click Deploy

This change updates the deployment's access control *without* changing its URL. The pages do not need redeployment; the same /exec URL will suddenly return 200 and execute correctly.

Why This Approach Was Chosen

  • Zero downtime for page code: The pages themselves are static (S3 + CloudFront). Changing the Apps Script deployment access does not require a page redeploy or cache invalidation.
  • Minimal blast radius: Apps Script deployments are isolated units. Re-configuring one does not affect others or the hosting infrastructure.
  • Atomic operation: Once "Anyone" access is set, the endpoint becomes live immediately. No intermediate states; the button works or it doesn't.
  • Diagnosability: The HTTP 403 is a clear signal of an access control issue, not a code bug or transient error. Fixing access control is the natural solution.

The Secondary Issue: Worship Page 404

The worship page's endpoint returns 404, indicating the deployment has been deleted entirely. The fix for this is a redeploy from source:

  1. Locate the worship reserve widget source code (likely in ~/Documents/repos/tools/ or a similar path).
  2. Verify the .clasp.json file points to the correct Apps Script project ID.
  3. Run clasp push to sync the source code to the project.
  4. Run clasp deploy to create a new deployment, or clasp deploy --deploymentId {ID} to redeploy into the same URL slot if the ID is known.
  5. If the old deployment ID is unknown, create a new deployment and update the worship page's iframe src to point to the new endpoint.

This requires coordinated updates: if a new deployment ID is generated, the worship page HTML must be regenerated and redeployed to S3 + CloudFront.

Infrastructure Context

  • Event pages: Hosted on S3 (bucket structure: queenofsandiego.com/events/) and distributed via CloudFront. Each page is a static HTML file with inline iframes pointing to the Apps Script endpoints.
  • Apps Script projects: Two independent Google Apps Script projects, each with one or more deployments. Deployments are the public-facing units.
  • Dependency graph: Pages → CloudFront → S3 HTML → iframe src (Apps Script endpoint). Pages are decoupled from Apps Script