Skip to content

Add Edge Cookie (EC) product requirements document#511

Merged
aram356 merged 8 commits into
mainfrom
feature/ssc-plan
Mar 20, 2026
Merged

Add Edge Cookie (EC) product requirements document#511
aram356 merged 8 commits into
mainfrom
feature/ssc-plan

Conversation

@aram356
Copy link
Copy Markdown
Collaborator

@aram356 aram356 commented Mar 17, 2026

Summary

  • Adds the internal PRD for Server-Side Cookie (SSC), a consent-aware first-party identity system that replaces SyntheticID
  • Covers SSC identity generation (IP + salt only), consent lifecycle (TCF/GPP enforcement with real-time withdrawal), KV Store identity graph, pixel sync endpoint (/sync), S2S batch API (/api/v1/sync), and bidstream decoration (/identify + /auction)
  • Defines TS Lite deployment mode — a runtime configuration that exposes only SSC routes, enabling SSPs, DSPs, and identity providers to adopt SSC without the full Trusted Server feature surface

Key sections

  • Problem statement: SyntheticID signal degradation (UA reduction, IPv6 rotation), no consent enforcement, adoption blocked by full TS requirement
  • SSC identity: HMAC-SHA256 of IP (IPv6 /64 prefix) + publisher salt, ts-ssc cookie at apex domain
  • Consent lifecycle: TCF/GPP enforcement with region-based rules, real-time cookie + KV deletion on withdrawal
  • KV identity graph: SSC hash → partner ID map with atomic read-modify-write via generation markers
  • Pixel sync (GET /sync): Browser redirect-based partner ID sync with rate limiting and domain allowlist
  • S2S batch API (POST /api/v1/sync): Authenticated bulk ID mapping push for DSPs and partners
  • Bidstream decoration: Header-only mode (/identify) and full auction mode (/auction) with OpenRTB 2.6 user.eids
  • Configuration: New [ssc] and [features] TOML sections, partner registry in KV store

Copy link
Copy Markdown
Collaborator Author

@aram356 aram356 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread docs/internal/ssc-prd.md Outdated
Comment thread docs/internal/ssc-prd.md Outdated
Comment thread docs/internal/ssc-prd.md Outdated
Comment thread docs/internal/ssc-prd.md Outdated
Comment thread docs/internal/ssc-prd.md Outdated
Comment thread docs/internal/ssc-prd.md Outdated
Comment thread docs/internal/ssc-prd.md
Comment thread docs/internal/ssc-prd.md Outdated
Comment thread docs/internal/ssc-prd.md Outdated
Comment thread docs/internal/ssc-prd.md Outdated
@aram356 aram356 changed the title Add Server-Side Cookie (SSC) product requirements document Add Edge Cookie (EC) product requirements document Mar 17, 2026
@aram356 aram356 marked this pull request as draft March 17, 2026 16:09
@aram356 aram356 linked an issue Mar 17, 2026 that may be closed by this pull request
@ChristianPavilonis ChristianPavilonis self-requested a review March 19, 2026 15:22
@aram356 aram356 marked this pull request as ready for review March 19, 2026 15:23
Copy link
Copy Markdown
Collaborator

@ChristianPavilonis ChristianPavilonis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Story 1 — EC generation and request context

Status: Partial

Done:

  • generate_ec_id() exists in edge_cookie.rs and produces {64hex}.{6alnum}
  • normalize_ip() exists with IPv4 pass-through and IPv6 prefix normalization behavior
  • get_ec_id() reads X-ts-ec header, then ts-ec cookie
  • get_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 for fastly::rand alignment
  • No EcContext struct 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::Unknown currently allows EC; spec requires fail-closed (false)
  • US-state logic currently can allow when us_privacy is present even if gpc == true; spec requires !gpc as an independent condition
  • Gating function is named allows_ec_creation() and should be moved/aliased to ec_consent_granted() under ec/consent.rs

Story 3 — EC cookie helpers

Status: Partial

Done:

  • create_ec_cookie(), set_ec_cookie(), and expire_ec_cookie() exist
  • Cookie attributes are mostly aligned (Path=/, Secure, SameSite=Lax, 1-year max age, no HttpOnly)
  • 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/PartnerStore with all spec fields
  • API key hash verification (constant-time)
  • POST /admin/partners/register with 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/register auth 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 mutation
  • integration_registry.handle_proxy() generates and sets cookie directly
  • /auction currently generates EC in endpoint path

Story 7 — Pixel sync (GET /sync)

Status: Not started

Required:

  • Endpoint + query validation (partner, uid, return; optional consent fallback)
  • 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/207 behavior 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.consent is 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.eids currently not populated from partner data
  • gate_eids_by_consent() exists but is not wired to final EID emission
  • No X-ts-eids, X-ts-ec-consent, X-ts-eids-truncated response headers
  • Current X-ts-ec-fresh behavior 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

Comment thread docs/internal/ssc_technical_spec.md
Comment thread docs/internal/ssc_technical_spec.md Outdated
Comment thread docs/internal/ssc_technical_spec.md
@aram356 aram356 requested a review from prk-Jr March 19, 2026 21:52
@aram356 aram356 removed the request for review from prk-Jr March 20, 2026 21:40
@aram356 aram356 merged commit a63f1bf into main Mar 20, 2026
13 checks passed
@aram356 aram356 deleted the feature/ssc-plan branch March 20, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Requirements for edge cookie

3 participants