Add Edge Cookie (EC) product requirements document#511
Conversation
aram356
left a comment
There was a problem hiding this comment.
PRD Review — Completeness for Engineering Kickoff
Overall this is a strong PRD. The problem statement, goals, and feature surface are well-defined.
Main structural feedback: Several sections include implementation details (HMAC algorithms, KV generation markers, bcrypt, specific config formats, JSON schemas) that belong in a technical design doc, not a PRD. A PRD should clearly spell out what the system does and why — not how it's built. Separating these concerns will make the PRD more durable and keep implementation decisions with engineering where they can evolve.
Below are specific questions and suggestions per section. The goal is to close ambiguity so engineering can start without guessing at product intent.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Story 1 — EC generation and request context
Status: Partial
Done:
generate_ec_id()exists inedge_cookie.rsand produces{64hex}.{6alnum}normalize_ip()exists with IPv4 pass-through and IPv6 prefix normalization behaviorget_ec_id()readsX-ts-echeader, thents-eccookieget_or_generate_ec_id()reuses existing ID or generates a new one- Client IP source is
req.get_client_ip_addr() - Unit tests exist for normalization and format checks
Gaps / divergences:
- On missing client IP, generation falls back to
"unknown"; spec requires returning an error (no EC without IP) - IPv6 normalization currently emits a trailing
::; spec examples expect first 4 groups joined without:: - Random suffix uses
rand::thread_rng(); spec calls forfastly::randalignment - No
EcContextstruct yet (ec_value,cookie_was_present,ec_was_present,ec_generated,consent,client_ip) - No two-phase lifecycle (
read_from_request()pre-routing +generate_if_needed()in organic handlers only) - No
ec_hash()helper for extracting stable 64-char prefix - Settings are still
[edge_cookie](secret_key,counter_store,opid_store) instead of spec[ec]shape - No
ec/module tree yet (implementation is still spread across existing modules)
Story 2 — EC consent gating layer
Status: Partial (multiple divergences)
Done:
- Existing
allows_ec_creation()provides jurisdiction-based gating - Consent pipeline (
build_consent_context(),ConsentContext, TCF/GPP/US-Privacy, GPC) is implemented - Unit tests cover broad jurisdiction combinations
Gaps / divergences:
Jurisdiction::Unknowncurrently allows EC; spec requires fail-closed (false)- US-state logic currently can allow when
us_privacyis present even ifgpc == true; spec requires!gpcas an independent condition - Gating function is named
allows_ec_creation()and should be moved/aliased toec_consent_granted()underec/consent.rs
Story 3 — EC cookie helpers
Status: Partial
Done:
create_ec_cookie(),set_ec_cookie(), andexpire_ec_cookie()exist- Cookie attributes are mostly aligned (
Path=/,Secure,SameSite=Lax, 1-year max age, noHttpOnly) - Unit tests exist for cookie helpers
Gaps / divergences:
- Cookie domain currently uses
settings.publisher.cookie_domain; spec requires computed.{settings.publisher.domain} - No consolidated helpers for response behavior (
set_ec_on_response,clear_ec_on_response) finalize_response()does not own EC mutation logic today- Consent-withdrawal deletion is currently inline on publisher path, not centralized across all routes
Story 4 — KV identity graph
Status: Not started
Required:
KvIdentityGraph+ identity schema (KvEntry,KvConsent,KvGeo,KvPartnerId,KvMetadata)- CAS generation-marker read/modify/write with
MAX_CAS_RETRIES = 3 create_or_revive(),upsert_partner_id(),update_last_seen()(300s debounce)- Tombstone writes for withdrawal (
consent.ok = false, clear IDs, 24h TTL)
Note: existing consent/kv.rs is a separate consent persistence layer; do not conflate with EC identity graph.
Story 5 — Partner registry and admin endpoint
Status: Not started
Required:
PartnerRecord/PartnerStorewith all spec fields- API key hash verification (constant-time)
POST /admin/partners/registerwith Bearer token auth and strict validation- Route coverage updates and tests
Plan risk to resolve:
- Current code enforces admin route coverage via
Settings::ADMIN_ENDPOINTS+[[handlers]]basic-auth expectations. - Spec says
/admin/partners/registerauth is Bearer in-handler and should not rely on[[handlers]]basic auth. - This needs an explicit implementation decision before Story 5 merges.
Story 6 — EC middleware integration
Status: Not started
Required:
- Pre-routing
EcContext::read_from_request()on all requests generate_if_needed()only in organic routes (handle_publisher_request, integration proxy)- EC-specific handlers read-only (no generation)
finalize_response(settings, geo, ec_context, kv, response)owns cookie set/delete and tombstone writes- Background pull dispatch after
send_to_client()
Current behavior that must be refactored:
handle_publisher_request()performs inline generation + consent gating + cookie mutationintegration_registry.handle_proxy()generates and sets cookie directly/auctioncurrently generates EC in endpoint path
Story 7 — Pixel sync (GET /sync)
Status: Not started
Required:
- Endpoint + query validation (
partner,uid,return; optionalconsentfallback) - Partner lookup + exact return-domain validation
- Consent gating from
ec_context.consent - Rate limit (
{partner_id}:{ec_hash}) - KV upsert + redirect reason handling
Story 8 — Identity lookup (GET /identify)
Status: Not started
Required:
- Read-only EC handling (never generates)
- Response matrix (200/204/403) + degraded path on KV read failures
- CORS origin validation +
OPTIONS /identify - Response headers and payload shaping/caps
Story 9 — S2S batch sync (POST /api/v1/sync)
Status: Not started
Required:
- Bearer API key auth + per-partner rate limiting
- Batch size limit and per-mapping rejection reasons
200/207behavior and idempotent upserts
Story 10 — Pull sync dispatch
Status: Not started
Required:
- Background async dispatch with concurrency cap
- Trigger conditions + per-partner stale checks
- Domain allowlist validation + outbound Bearer auth
- Stateless rotating partner prioritization
- Rate limits and non-blocking error handling
Story 11 — Auction bidstream decoration
Status: Partial (re-scoped accurately)
Done:
- Auction request user ID is already derived from EC path in current flow
- Consent context is threaded through auction conversion
user.consentis already populated in outgoing OpenRTB mapping- OpenRTB generated types already support
user.eids
Gaps / divergences:
- No KV identity graph read in auction path to populate per-partner IDs
user.eidscurrently not populated from partner datagate_eids_by_consent()exists but is not wired to final EID emission- No
X-ts-eids,X-ts-ec-consent,X-ts-eids-truncatedresponse headers - Current
X-ts-ec-freshbehavior is transitional and not part of target spec output
Story 12 — End-to-end integration tests
Status: Not started
Required (Viceroy-based):
- Full loop: page load -> EC -> pixel sync -> identify -> auction EIDs
- Withdrawal: cookie deletion + tombstone
- KV degraded behavior
- CAS conflict handling
- Rate-limit paths
- Pull-sync null/404 no-op
Summary (spec stories)
| Story | Description | Status |
|---|---|---|
| 1 | EC generation + context | Partial |
| 2 | Consent gating | Partial |
| 3 | Cookie helpers | Partial |
| 4 | KV identity graph | Not started |
| 5 | Partner registry + admin | Not started |
| 6 | EC middleware integration | Not started |
| 7 | Pixel sync /sync | Not started |
| 8 | Identity lookup /identify | Not started |
| 9 | S2S batch sync | Not started |
| 10 | Pull sync dispatch | Not started |
| 11 | Auction decoration | Partial |
| 12 | E2E integration tests | Not started |
Summary
/sync), S2S batch API (/api/v1/sync), and bidstream decoration (/identify+/auction)Key sections
ts-ssccookie at apex domainGET /sync): Browser redirect-based partner ID sync with rate limiting and domain allowlistPOST /api/v1/sync): Authenticated bulk ID mapping push for DSPs and partners/identify) and full auction mode (/auction) with OpenRTB 2.6user.eids[ssc]and[features]TOML sections, partner registry in KV store