Decoupling Guest-Page URLs from Event IDs: A Zero-Risk URL Refactoring Pattern

What Was Done

We investigated renaming a guest-page URL from /g/2026-06-27-esmi to /g/2026-06-27-shumway-memorial without breaking any dependent systems. The core question: if we change the human-readable slug in the path, what breaks downstream?

Through systematic enumeration of the architecture, we discovered that the URL slug is almost entirely cosmetic—a lesson in loose coupling that makes this refactoring trivial to execute but requires understanding why the system is resilient to the change.

Technical Details: Why the URL Doesn't Matter

The guest-page system has a subtle but critical architectural property: the canonical event identifier lives inside the HTML, not in the URL path.

When a guest page is generated (via jada-ops/templates/guest-page-generator.py or similar), the HTML is populated with an embedded EVENT_ID constant. For this event, that constant is "2026-06-27-esmi". This value appears in the page JavaScript, not derived from the URL:

<script>
const EVENT_ID = "2026-06-27-esmi";
const PHOTO_CODE = "ZM9DMZ";
// gallery fetch and presign operations key off EVENT_ID
</script>

All downstream operations—photo gallery fetches, upload presigning, confirmation webhooks—validate against the DynamoDB item keyed on event_id=2026-06-27-esmi, not the URL you're visiting. The URL is purely for human readability and SEO.

This means the system tolerates multiple URLs pointing to the same event. You can deploy the identical HTML at /g/2026-06-27-shumway-memorial and:

  • Photo code ZM9DMZ validation still passes (DDB lookup on EVENT_ID, not slug)
  • Guest uploads presign to the same S3 prefix (keyed on EVENT_ID in the Lambda)
  • Photo approval in the admin console works identically (event_id is the lookup key)
  • The gallery displays the same 23 approved photos (S3 prefix is event-based, not slug-based)

Infrastructure: Where the Slug Lives

The guest page exists in two places:

  1. Source control: jada-estate/g/2026-06-27-esmi (no extension; the file is plain HTML).
  2. S3 distribution: Synced to the apex CloudFront distribution for queenofsandiego.com via jada-deploy script, which reads the local g/ directory and uploads each file to the S3 origin bucket.

CloudFront behavior: The /g/* path has a generic rewrite rule that strips the .html extension at request time. This is configured in CloudFront function code (not a Lambda origin request) to avoid latency. When you request /g/2026-06-27-esmi, the edge function internally maps it to /g/2026-06-27-esmi.html before hitting the S3 origin.

Why DNS alias won't work: A naive approach might be to add a Route53 alias to hide "esmi" from the URL. This fails because "esmi" is in the path (/g/2026-06-27-esmi), not the hostname. DNS aliases control hostname resolution, not path rewriting. The solution is not DNS; it's a second S3 object.

The Refactoring: Pure File Copy

The change requires no code modifications, no database migrations, and no infrastructure changes:

  1. Copy the HTML file in the repo: cp jada-estate/g/2026-06-27-esmi jada-estate/g/2026-06-27-shumway-memorial
  2. Commit and push to the repo.
  3. Run jada-deploy, which syncs the g/ directory to S3 and invalidates the CloudFront distribution (/g/* paths are marked CachingDisabled, so no explicit invalidation is needed, but the deploy script handles it).
  4. Verify: curl https://queenofsandiego.com/g/2026-06-27-shumway-memorial returns the HTML, JavaScript parses EVENT_ID="2026-06-27-esmi", and photo operations work.

The old URL continues to work unchanged. Both slugs serve identical content.

Blast Radius Analysis

We enumerated every system that could conceivably reference the URL slug:

  • DynamoDB: Charter metadata lives on event_id (e.g., event_id=2026-06-27-esmi), not the slug. Zero impact.
  • Photo S3 prefixes: Photos are stored under s3://jada-guest-uploads/2026-06-27-esmi/*. The upload presigner derives this prefix from the HTML's embedded EVENT_ID, not the URL. Zero impact.
  • Compliance filings (§7117): Keyed on event_id in DynamoDB. Zero impact.
  • Waiver sheets: Link references are either absolute URLs or event-id-based lookups, not slug-based. Zero impact.
  • Trip sheets: Reference the event_id, not the URL slug.
  • Analytics / traffic logs: Will show two separate URL paths hitting the same event. Not a breaking change; just a data artifact.
  • Texted links to Esmi: The old URL continues to work forever, so any link she forwards remains valid.
  • Test suite: The test file tests/test_guest_pages.py auto-discovers all files in the repo's g/ directory. Adding a new file automatically includes it in validation—no registration needed.

Key Decisions

Why copy instead of rename? Renaming in the repo would invalidate any external links (texts, emails, proposals) pointing to the old slug. By keeping both, we maximize backward compatibility at zero cost (S3 storage is negligible, and CloudFront serves both with identical cache efficiency).

Why not a URL redirect? This is tempting but unnecessary. A 301 redirect from the old path to the new one would cause the address bar to change, which contradicts the request to "show the shumway name in the address bar" for new links while preserving old ones. Dual S3 objects sidestep the tradeoff.

Why the extensionless URL works: CloudFront's function layer strips .html transparently, making /g/2026-06-27-shumway-memorial serve /g/2026-06-27-shumway-memorial.html from S3. This keeps URLs clean without origin-request Lambdas (which add latency). The rewrite is stateless and deterministic.

What's Next

Once signoff is confirmed on the honoree name (the current page memorializes the living client; the correct decedent has a sibling page at /g/2026-06-27-pearl-memorial), deployment is a one-line file copy followed by jada-deploy. Estimated time: 3–5 minutes, zero rollback risk.

The pattern generalizes: any event can have multiple slug aliases with no operational cost, making URL refactoring safe and reversible across the guest-page system.