```html

Preventing S3 Deployment Regressions: Hard Rules for Multi-Environment Sailboat Booking Infrastructure

Over the last three hours, a development session inadvertently deployed a stale local index.html to S3 production, wiping three working features on the queenofsandiego.com booking site: the hero JADA → BOOK NOW crossfade animation, the Stripe embedded checkout modal, and inadvertently resurrected a deleted "For Ranch & Coast readers..." hero line that had been intentionally removed. This post documents the root cause, the eight hard rules now enforced to prevent recurrence, and the infrastructure patterns that should have caught this earlier.

What Happened: The Regression Chain

  • Root cause: Local development environment had an older index.html snapshot that lacked three features present in S3 production.
  • The mistake: A deployment command uploaded the stale local file directly to both staging and prod in a single operation, overwriting the newer production code without a diff review.
  • Violated precedent: A prior session summary had explicitly warned about local files being stale — that warning was present in the context but not heeded.
  • Bypass of process: The deployment violated an existing "staging-first, single-target" rule that was already documented but not yet formalized into auto-loading guard clauses.

The three lost features were live and working; they were only present in S3, not in the local git repo at that moment. This is a classic infrastructure anti-pattern: treating local files as source-of-truth when they're out of sync with the deployed environment.

Eight Hard Rules Now Enforced (D1–D8)

To prevent this pattern from recurring, eight mandatory rules have been added to /Users/cb/Documents/repos/sites/queenofsandiego.com/CLAUDE.md (auto-loaded on every Claude session for queenofsandiego.com work) and a condensed pointer added to the top-level repo CLAUDE.md for other sites:

  1. D1 — Pull S3 and diff before editing: Before modifying any HTML/CSS/JS file that exists in S3, run aws s3 cp s3://[bucket]/index.html ./s3-current-index.html and diff against local. Document what's ahead in S3.
  2. D2 — Staging-only, single-target deploys: Never deploy to both staging and prod in one command. Always deploy to staging first (aws s3 cp ./index.html s3://staging.[domain]/index.html), wait for CB review/test, then promote to prod as a separate, intentional step.
  3. D3 — One logical change per deployment: If editing multiple features (hero animation, checkout modal, email template), commit and deploy each change separately. This makes regression recovery surgical — if prod breaks, you know which change caused it.
  4. D4 — Obey your own prior warnings: If a prior session summary (in CLAUDE.md or memory files) flags "local files are stale" or "S3 is ahead," treat that as blocking. Escalate to CB if you can't reconcile.
  5. D5 — Snapshot prod before overwriting: S3 versioning is not enabled on the production bucket. Before any cp to prod, run aws s3 cp s3://sailjada.com/index.html ./backups/prod-index-$(date +%s).html. Keep the last 5 snapshots locally.
  6. D6 — Six-line proof block before deployment: Before executing any aws s3 cp to prod, print a six-line summary to chat:
    🔒 PROD DEPLOY PROOF:
    File: ./index.html (local) → s3://sailjada.com/index.html
    Size: XXX bytes | MD5: abc123...
    Features verified: [list 3–4 features you tested locally]
    CB approval: [link to Slack/email or explicit "awaiting"]
    Go/No-Go: [VERIFIED READY or BLOCKED]
  7. D7 — Feature-token registry: Maintain a searchable list (in memory or CLAUDE.md) of feature identifiers ("JADA_FADE", "STRIPE_CHECKOUT", "RANCH_COAST_HERO"). Before deploying, grep the S3 prod version for each token. If tokens are missing from S3-current but present in your local edit, the diff is incomplete — you're about to lose code.
  8. D8 — Escalate to CB if S3 is ahead: If aws s3 ls shows prod was modified more recently than the last commit in the git repo, or if the S3 file is larger/different from local, do not deploy. Message CB immediately with both versions and the time delta.

Technical Infrastructure: Why This Matters

The queenofsandiego.com site uses a split-environment S3 architecture:

  • S3 bucket: sailjada.com (production HTML/CSS/JS)
  • S3 bucket: staging.sailjada.com (staging environment for CB review)
  • CloudFront distribution: d[...].cloudfront.net (cached at edge, invalidation required after S3 update)
  • Git repo: /Users/cb/Documents/repos/sites/queenofsandiego.com (source of truth for tracked files — but incomplete for deployed state)

The critical gap: Git contains only files that have been committed. S3 production may contain newer versions uploaded between commits. There is no version control on S3 itself, and CloudFront caching means a bad deploy takes 15–300 seconds to propagate globally.

The old workflow assumed local files = latest code. That assumption broke the moment features were edited directly in S3 (via console or prior deployment) without a corresponding commit.

Key Decision: Why Hard Rules Instead of Automation

A more "correct" solution would be:

  • Enable S3 versioning on both prod and staging buckets.
  • Add a pre-deploy hook that requires git diff s3://sailjada.com/index.html ./index.html to succeed with zero conflicts.
  • Implement a promotion pipeline: commit → staging auto-deploy → CloudFront invalidate staging → manual promote-to-prod button.

However, these require infrastructure changes (S3 settings, Lambda hooks, deployment tooling) that are outside the scope of the current session. The eight rules are enforceable immediately by Claude agents and CB, and they close the gap without new infrastructure.

Deployment Process Moving Forward

For any future change to sailjada booking flow:

# Step 1: Pull current prod state and diff
aws s3 cp s3://sailjada.com/index.html ./s3-current-index.html
diff -u ./s3-current-index.html ./index.html | head -50

# Step 2: Make ONE logical change, test locally
# (e.g., update Stripe session creation logic)

# Step 3: Deploy to staging only
aws s3 cp ./index.html s3://staging.sailjada.com/index.html
aws cloudfront create-invalidation --distribution-id d[...] --paths "/index.html"

# Step