Preventing S3 Deployment Regressions: Hard Rules for Production Safety

Last week, a stale local index.html file deployed to the queenofsandiego.com S3 production bucket wiped three working features: the hero JADA→BOOK NOW crossfade animation, the embedded Stripe checkout flow, and inadvertently resurrected a deleted "Ranch & Coast readers" hero section. The deployment violated staging-first protocol and ignored prior session warnings about local file drift. This post documents the architectural safeguards we've now codified to prevent this class of regression.

What Happened: The Incident

The S3 bucket for queenofsandiego.com (s3://queenofsandiego-www) holds the live static site served via CloudFront distribution E2ABCD1234567. A prior session had:

  • Implemented the hero fade CSS and JavaScript
  • Integrated Stripe's embedded checkout modal
  • Removed the Ranch & Coast promo hero variant

The incident session deployed from a local development copy that was hours out of date, overwriting the remote state without checking S3 first. Both staging and production buckets received the deployment in a single command, skipping the required staging-review gate.

Root Cause: Information Asymmetry

The deployment agent (Claude Sonnet 4.6) had no mechanism to:

  • Pull and diff before edit: Compare local /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html against the current S3 prod version before making changes
  • Enforce single-target deploys: Prevent staging and prod uploads in the same operation
  • Snapshot production state: Create a timestamped backup before any overwrite (S3 versioning was not enabled on this bucket)
  • Escalate on upstream changes: Stop and alert when S3 is ahead of local, requiring manual reconciliation

Additionally, the agent ignored its own prior session notes warning about stale local files—a sign that session memory wasn't being enforced as a binding constraint.

The Fix: Eight Hard Rules (D1–D8)

We've embedded these rules into /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md, which auto-loads on every session:

D1: Pull and Diff Before Edit

aws s3 cp s3://queenofsandiego-www/index.html /tmp/s3-index-prod.html
diff -u /tmp/s3-index-prod.html /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html | head -50

If the diff shows unexpected changes in S3, the agent must escalate to CB before proceeding.

D2: Staging-Only Single-Target Deploys

Every deployment must explicitly name its target. Staging first:

aws s3 cp /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html s3://queenofsandiego-staging/index.html

Production only after CB reviews staging:

aws s3 cp /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html s3://queenofsandiego-www/index.html

Never deploy both in one command or shell loop.

D3: One Logical Change Per Deploy

If a session modifies hero CSS, booking modal, and email templates, those are three separate deployments (to staging, each reviewed, then promoted). This makes regressions bisectable.

D4: Obey Your Own Prior Warnings

If a session summary says "local index.html is 6 hours old," the next session that touches S3 must pull and verify, or escalate. Session memory is not advice—it's a binding constraint.

D5: Snapshot Production Before Overwrite

aws s3 cp s3://queenofsandiego-www/index.html s3://queenofsandiego-www/backups/index.html.$(date +%s)

Keep backups for 7 days. (Note: We're enabling S3 versioning on this bucket separately.)

D6: Proof Block Before Any cp

Print a six-line proof block in chat before uploading anything:

SOURCE: /Users/cb/Documents/repos/sites/queenofsandiego.com/index.html
TARGET: s3://queenofsandiego-www/index.html (STAGING, review-gate required before PROD)
CHANGES: Hero fade CSS (lines 142–167), Stripe modal (lines 234–289)
REGRESSION CHECK: Grep confirms JADA_FADE_ANIMATION, StripeCheckout present
BACKUP: s3://queenofsandiego-www/backups/index.html.1704067200
REVIEW: awaiting CB confirmation before production upload

D7: Feature-Token Registry

Maintain a file FEATURE_TOKENS.md listing critical strings to grep for:

  • JADA_FADE_ANIMATION — hero crossfade JS marker
  • StripeCheckout — embedded checkout initialization
  • ranchcoast_hero — deleted promo section (should NOT appear in prod)

Before deploying to S3, grep the target bucket against these tokens:

aws s3 cp s3://queenofsandiego-www/index.html /tmp/s3-current.html
grep JADA_FADE_ANIMATION /tmp/s3-current.html && echo "✓ Hero fade present in S3"
grep StripeCheckout /tmp/s3-current.html && echo "✓ Stripe checkout present in S3"
grep ranchcoast_hero /tmp/s3-current.html && echo "✗ REGRESSION: deleted promo line found"

D8: Escalate to CB When S3 Is Ahead of Local

If the diff in D1 shows S3 has features not in the local repo, stop. Message CB with the diff. Do not overwrite. This prevents loss of live hotfixes.

Infrastructure Context

The deployment architecture for queenofsandiego.com uses:

  • S3 staging bucket: queenofsandiego-staging — no public access, used for CB review
  • S3 production bucket: queenofsandiego-www — public, served via CloudFront
  • CloudFront distributions:
    • Staging: E2STAG1234567 (optional, for testing)
    • Production: E2ABCD1234567 — origin queenofsandiego-www.s3.us-west-1.amazonaws.com
  • DNS: Route53 zone