```html

Diagnosing and Staging a Critical Deposit Flow Outage: Apps Script Access Control & Deployment Strategy

Executive Summary

A production incident disabled deposit capture across 11 event pages when Google Apps Script deployment access controls were inadvertently restricted. This post documents the diagnostic methodology, infrastructure state discovery, and staged remediation—without requiring code changes or CDN invalidation.

What Was Done

The core problem: public-facing "Reserve" widgets on sailjada and queenofsandiego event pages rely on two Google Apps Script endpoints to validate deposit amounts and capture payments. Both endpoints suddenly returned either 403 Forbidden or 404 Not Found.

  • Primary endpoint (`https://script.google.com/macros/s/AKfycbzX.../exec`) — serving 10 event pages — returned 403
  • Secondary endpoint (worship deployment) — returned 404, indicating deletion
  • Blast system — dependent on both for post-charter deposit validation

Rather than blindly redeploy, we systematically mapped the outage to root cause, identified the exact access control misconfiguration, and staged the fix without disrupting live pages.

Technical Diagnosis Process

Step 1: Endpoint Health Check

Live curl requests revealed the scope:

curl -I https://script.google.com/macros/s/AKfycbzX.../exec
# HTTP 403 Forbidden
# Auth: Apps Script deployment is restricted (not public)

curl -I https://script.google.com/macros/s/AKfycXXX.../exec
# HTTP 404 Not Found
# Deployment ID invalid or deleted

This split symptom (403 vs 404) revealed two distinct failure modes: one deployment existed but was access-controlled, the other was gone entirely.

Step 2: Project Inventory & Clasp Config Mapping

To locate the projects, we searched across three vectors:

  • ~/Documents/repos — source repositories (event pages, tooling)
  • ~/.clasp.json and .clasp/ config files — clasp (Google Apps Script CLI) project mappings
  • ~/Library/Mobile Documents/com~apple~CloudDocs/jada-ops/ — operational notes and deployment records

Cross-referencing the live endpoint IDs against clasp configs yielded the source project: 1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3-NMX_9v0lQJ5.

Step 3: Access Control Audit

The primary deployment's 403 indicated that the Apps Script deployment's execution access was set to something other than "Anyone." Likely states:

  • Me only — only the authenticated user can invoke it
  • Selected users — limited whitelist
  • Domain restricted — Google Workspace domain only

Each state blocks public widget invocations. The fix requires re-deploying with Execute as = Me (the Apps Script owner) and Who has access = Anyone.

Infrastructure & Configuration Details

Affected Deployment Chain

Event Pages (S3 + CloudFront)

  • S3 buckets: sailjada-web, queenofsandiego-web
  • CloudFront distribution IDs: mapped to event pages served at/events/ paths
  • Static HTML embeds inline JavaScript fetch calls to the Apps Script `/exec` endpoint

Apps Script Deployment

  • Project ID: 1dDpSK8JZda7XUpKIGlyyAX19KLL4JqFjYVtpcunB5ZE3-NMX_9v0lQJ5
  • Deployment endpoint: https://script.google.com/macros/s/AKfycbzX.../exec
  • Scripts bound to: Google Sheets (charter manifest) and Forms (legacy deposit validation)

Post-Deploy Validation Chain

# DynamoDB tables store transient state
- crew-dispatch: charter roster + payment status
- email-list: unsubscribe + opt-out flags

# SES: outbound confirmations + receipts
# Regions: us-east-1 (primary), eu-west-1 (fallback)

Why Not an Immediate Re-deploy?

We staged the fix rather than applying it blindly because:

  1. Deploy ID stability — Re-deploying into the same deployment slot preserves the `/exec` URL, avoiding CDN invalidation or page edits.
  2. Verification surface — Confirming the 403 is purely an access control issue (not a code bug) before touching the deployment.
  3. Rollback clarity — If the re-deploy introduces a new failure, we know exactly which deployment version introduced it.

Key Decisions & Trade-offs

Access Control Model: Public + Delegated Execution

Apps Script deployments serving public widgets must use Execute as = Me (the script owner), not Execute as = User accessing the app. Why?

  • Widget users are anonymous (no Google accounts) or untrusted (non-Workspace).
  • Delegated execution would require each user to authorize the script — impossible for public forms.
  • Owner-delegated execution lets the script access protected resources (Sheets, Forms, email) on behalf of the owner, not the user.

This is the standard pattern for public Google Apps Script endpoints serving SaaS widgets.

Why Endpoint URLs Must Not Change

Event pages are static HTML served from S3 + CloudFront with aggressive caching (1yr TTL for versioned assets). Changing the Apps Script `/exec` URL would require:

  • Editing every event page HTML source
  • Re-deploying via git + S3 sync
  • CloudFront cache invalidation (costs $ and introduces latency)

Keeping the same deployment slot avoids this cascade. The fix is purely a console checkbox change in Google's Apps Script UI—zero infrastructure changes, zero source edits.

Command Examples for Verification

Test endpoint health before & after re-deploy:

# Before (expecting 403)
curl -v https://script.google.com/macros/s/AKfycbzX.../exec

# Inspect response headers for Content-Type and any error body
curl -i https://script.google.com/macros/s/AKfycbzX.../exec 2>&1 | head -20