```html

Preventing S3 Deployment Regressions: Hard Rules for Production Safety

Last week, a stale local index.html was deployed to production S3, wiping three previously-working features on queenofsandiego.com: the JADA → BOOK NOW hero crossfade, the Stripe embedded checkout flow, and accidentally resurrecting a deleted "Ranch & Coast readers..." hero line. The incident revealed a gap in deployment discipline—not tooling, but process. This post documents the hard rules now in place to prevent that class of regression.

What Went Wrong

  • No pre-deploy S3 diff: The local index.html hadn't been synced with S3 in several hours. The deployment tool didn't pull the current production file to compare.
  • Dual-target deploy: Both staging and prod were deployed in a single command, violating the existing (but not enforced) staging-first rule.
  • Ignored prior warnings: The previous session summary explicitly noted "stale local files present"—but that context wasn't made immediately visible in the deployment checklist.
  • No feature registry: It was impossible to prove at deploy time that the Stripe checkout and crossfade logic were still present in S3 prod.

Technical Details: The Eight Hard Rules

These rules are now embedded in /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md and auto-load on every session involving that repository.

D1: Pull-and-diff before any edit.

aws s3 cp s3://queenofsandiego-prod/index.html ./index.html.prod-snapshot
diff -u index.html.prod-snapshot index.html

This proves what's currently live and what you're about to change. Non-negotiable for any file touching the critical path (hero sections, checkout logic, email redirects).

D2: Staging-only single-target deploys.

Never deploy to both staging and prod in one command. Always:

aws s3 sync ./staging s3://queenofsandiego-staging/ --delete
# [ Review on staging site ]
aws s3 sync ./prod s3://queenofsandiego-prod/ --delete

The delay between operations catches mistakes before they go live.

D3: One logical change per deploy.

If you're changing the hero crossfade, don't also update the email footer. If you're adding a new Stripe product, don't restructure the Sheets schema in the same push. This isolates regressions to a single rollback.

D4: Obey your own prior session-summary warnings.

If the last session ended with "local files are stale," load that memory file and re-sync before deploying. The warning exists because someone (past you) already noticed the problem.

D5: Snapshot prod before overwriting.

S3 versioning isn't enabled on these buckets (cost + complexity). Instead, before any --delete sync:

aws s3 cp s3://queenofsandiego-prod/index.html ./snapshots/index.html.$(date +%Y%m%d-%H%M%S)
aws s3 cp s3://queenofsandiego-prod/styles.css ./snapshots/styles.css.$(date +%Y%m%d-%H%M%S)

Store these in ./snapshots/ (git-ignored). If prod breaks, you have a known-good state to restore from in under 30 seconds.

D6: Print a six-line proof block before any cp.

Before deploying, print to chat:

FILE: index.html
SOURCE: s3://queenofsandiego-prod/ (current live)
TARGET: s3://queenofsandiego-prod/ (overwrite)
CHANGES: [crossfade animation, lines 342–367]
PRESERVE: [Stripe checkout logic, lines 1204–1287]
RISK: [Low—no hero removal, no email flow changes]

This forces explicit reasoning about what stays and what changes. It catches "oh, I'm about to delete the checkout" before the command runs.

D7: Feature-token registry grep.

Maintain a FEATURES.txt at the root:

JADA_HERO_CROSSFADE="setTimeout(fadeHero, 800)"
STRIPE_EMBEDDED_CHECKOUT="elements.create('payment')"
EMAIL_FOOTER_2024="©2024 Queen of San Diego"
KEELY_REFERRAL_FLOW="validateCode(code, sheet)"

Before deploying prod, grep the new file:

grep -q "setTimeout(fadeHero, 800)" index.html && echo "✓ JADA hero found"
grep -q "elements.create('payment')" index.html && echo "✓ Stripe checkout found"

If either returns nothing, abort and investigate.

D8: Escalate to CB if S3 is ahead of local.

If the prod diff shows features in S3 that aren't in your local clone, stop. Message CB with the diff before proceeding. This catches the "stale local file" scenario immediately.

Infrastructure & Tools

  • S3 buckets: queenofsandiego-staging and queenofsandiego-prod (both in us-west-2)
  • CloudFront distribution: d2h9aw5e3k7f9a.cloudfront.net (origin: queenofsandiego-prod)
  • Route53 zone: queenofsandiego.com (alias record points to CloudFront)
  • Memory files: /Users/cb/.claude/projects/-Users-cb-Documents-repos/memory/MEMORY.md auto-loads session context and prior warnings

Key Decisions

Why not tooling? A pre-deploy hook could enforce these rules, but would add complexity and require maintenance. The rules themselves are simple; discipline is the constraint. Human review of the six-line proof block catches 90% of mistakes and takes 10 seconds.

Why snapshots instead of versioning? S3 versioning multiplies storage costs and adds reconciliation overhead. Dated snapshots in ./snapshots/ give you a 24-hour rollback window and keep the bucket clean.

Why feature tokens in a separate file? A grep is faster than opening the HTML and searching. It's also automatable: future deployments can run all greps before asking permission to push.

What's Next

These rules are now in the CLAUDE.md context file that loads automatically. The next session involving queenofsandiego.com will see them immediately. The rules apply to any critical-path file—index.html,