```html

Preventing Deployment Regressions: Hard Rules for Multi-Environment S3 Sync

Over the last three hours, a deployment cycle to queenofsandiego.com introduced a critical regression: a stale local index.html overwrote production S3, wiping three working features simultaneously. This post documents the failure mode, the infrastructure safeguards that should have prevented it, and eight hard rules now enforced via Claude.md memory to catch this class of bug before deploy.

The Failure Mode

The regression deleted:

  • Hero section JADA → BOOK NOW crossfade animation (CSS transform chain, 4-second ease-in-out)
  • Stripe embedded checkout iframe mounting logic (lines ~3200–3300 in index.html)
  • Conditional hero text ("For Ranch & Coast readers...") that had been intentionally removed in a prior session

Root cause: local /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html was stale by ~2 commits relative to the live S3 object at s3://queenofsandiego.com/index.html. The deploy command synced local → S3 without first pulling and diffing the production version.

Why This Happened

The session violated two prior rules it had documented in its own summary:

  1. No pre-deploy pull-and-diff. The session had explicitly warned "pull S3 and diff before editing" but skipped this step on the next deployment cycle.
  2. Combined staging + prod deploy. Both environments were copied in a single command, violating the "staging-first" gatekeeping pattern.
  3. S3 lacks object versioning. CloudFront caching of a stale version masked the problem for ~20 minutes until cache TTL expired.

Infrastructure Context

queenofsandiego.com uses a three-tier deployment model:

  • S3 buckets:
    • s3://queenofsandiego.com/ — production
    • s3://staging.queenofsandiego.com/ — staging environment
    • Both serve via CloudFront distributions (prod: d2xyzabc123def.cloudfront.net, staging: separate dist)
  • Local source: /Users/cb/Documents/repos/sites/queenofsandiego.com/
  • Deployment method: aws s3 cp commands from local filesystem to S3, followed by CloudFront invalidation (/* wildcard to bust all cached objects)
  • Git tracking: index.html and related assets are version-controlled; changes merge to main branch before deployment

The Eight Hard Rules (D1–D8)

These rules are now baked into /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, auto-loaded on every QOS session:

D1: Before any edit to index.html or core assets, pull S3 and diff.
Command: aws s3 cp s3://queenofsandiego.com/index.html ./index.html.prod
Then: diff -u index.html.prod index.html | head -100
If S3 is ahead of local, escalate to CB before proceeding.

D2: Deploy to staging only. Single target per command.
Staging: aws s3 cp index.html s3://staging.queenofsandiego.com/index.html
Production: only after CB review of staging.queenofsandiego.com in browser.

D3: One logical change per deploy.
If editing hero CSS, hero HTML, and Stripe checkout in one file, 
deploy in three separate commands (or revert to three separate file edits).

D4: Obey your own prior-session warnings.
If a session summary says "pull and diff first," do it even if you don't remember why.
Assume your past self was right.

D5: Snapshot prod before overwriting.
Before any cp to production S3, save the current object:
aws s3 cp s3://queenofsandiego.com/index.html ./index.html.SNAPSHOT.$(date +%s)
Keep the last 3 snapshots in a local /snapshots/ dir.

D6: Print a six-line proof block before any cp command.
After every aws s3 cp, immediately print:
- Source file path (local)
- Source file hash (md5sum or sha256sum -b)
- Target S3 bucket + key
- Target environment (staging|prod)
- Current CloudFront dist ID for that environment
- Reason for this deploy (one sentence)
Print this in chat before hitting Enter.

D7: Maintain a feature-token registry.
Every time you add or modify a user-facing feature, log it:
- Feature name (e.g., "hero-jada-crossfade", "stripe-embedded-checkout")
- Unique grep token from the code (e.g., "JADA_HERO_FADE_V2", "STRIPE_CHECKOUT_MOUNT")
- Commit hash (git log --oneline -1)
Before deploy, grep the local file for all tokens. After CloudFront 
invalidation clears, re-fetch prod and grep again. Mismatch = regression.

D8: Escalate-to-CB rule: S3 ahead of local.
If any pull-and-diff shows production S3 has commits/edits that 
local doesn't have, stop. Message CB in Slack with the diff. 
Wait for acknowledgment before deploying anything.

Implementation: Memory Layers

To ensure these rules load automatically:

  • QOS-specific memory: Full eight rules + examples in /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md (auto-loaded at session start for QOS work).
  • Global pointer: Condensed summary in /Users/cb/Documents/repos/CLAUDE.md (loaded for all repos, directs to site-specific rules when working on QOS).
  • Snapshot mechanism: Session memory now tracks /snapshots/ directory state across turns, preventing accidental deletion of rollback copies.

CloudFront Invalidation Pattern

After any S3 deploy, invalidate the CloudFront distribution to clear edge caches immediately:

aws cloudfront create-invalidation \
  --distribution-id d2xyzabc123def \
  --paths "/*"

This forces all edge nodes to re-fetch from S3 origin within ~5 seconds, preventing stale-object exposure windows.

What's Next

These rules will be executed by Claude (Haiku or Sonnet 4.6, depending on turn complexity) on the Keely referral booking flow (sailjada.com), which uses the same three-tier S3+CloudFront pattern. The rules are language-agnostic and should generalize to any multi-environment static-site deployment.

For Sergio and other engineers reviewing this: the core lesson is that local source-of-truth assumptions break when S3 is