From 00df85414232048510cf76131d102827ab20e0c2 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Wed, 11 Nov 2026 23:52:03 -0800 Subject: [PATCH 01/31] Add server-side identity on every adapter spec and plan Requirements for Axum deployability, identity on all four adapters, SharedID handling, shared state and first-party write-back, plus the merge order, epic tickets and fixes to existing PRs. --- .../2026-11-11-identity-all-adapters-plan.md | 248 ++++++++++ ...side-identity-all-adapters-requirements.md | 468 ++++++++++++++++++ 2 files changed, 716 insertions(+) create mode 100644 docs/superpowers/plans/2026-11-11-identity-all-adapters-plan.md create mode 100644 docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-requirements.md diff --git a/docs/superpowers/plans/2026-11-11-identity-all-adapters-plan.md b/docs/superpowers/plans/2026-11-11-identity-all-adapters-plan.md new file mode 100644 index 000000000..8e7b6acfe --- /dev/null +++ b/docs/superpowers/plans/2026-11-11-identity-all-adapters-plan.md @@ -0,0 +1,248 @@ +# Server-side identity on every adapter: merge order and tickets + +Status: draft plan, 2026-11-11. Requirements: +[the spec](../specs/2026-11-11-server-side-identity-all-adapters-requirements.md). +Epics: Trusted Server #1558, EdgeZero IABTechLab/edgezero#468. + +Every PR base and state below was read from GitHub on 2026-11-11. Every +"not implemented" claim comes from a five-part audit of both repositories: +main, every open PR head, the RC tags, branches with no PR, and PRs merged or +closed since 2026-10-01. Trusted Server PRs #1342 to #1423 and #1448 are out of +scope by decision. + +## Part 1: merge order + +### Is it stacked correctly? + +Trusted Server: + +| PR | Base on GitHub | Correct for the plan? | Fix | +| ----------------------------------- | -------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------ | +| #1557 | main | Yes | None | +| #1525 | main | Yes | None | +| #1526 | #1525's branch | No | Retarget to main. Its own diff applies cleanly there, so it does not wait on #1525's changes requested | +| #1516 | main | Yes | None | +| #1506 | main | Yes, but conflicting | Rebase after #1557, #1526 and #1516 land | +| #1527 | #1526's branch | Not merged | Close once #1506 covers its tests | +| #1528 | #1527's branch | No | Retarget onto main after #1506. Repin off `456313675`, a revision that exists only on EdgeZero's DO NOT MERGE #462 | +| #1487 | main | Yes, needs rebase | Rebase after #1526 and #1506 | +| #1489 | #1487's branch | Yes | None | +| #1488 | #1487's branch, old commit | No | Restack onto #1489's branch so the two form one line. It is conflicting and sits on an old #1487 commit | +| #1503 | main | Yes | None | +| #1543 | #1449's branch | Yes, but conflicting | Rebase onto #1449 | +| #1541, #1531 to #1540, #1424, #1499 | RC baselines | Not merged | Forward-port to main as new PRs; close each as its port lands | + +EdgeZero: + +| PR | Base on GitHub | Correct for the plan? | Fix | +| --------------------------------------- | -------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| #275 | main | Yes, missing fixes | Merge #460 into #275's branch first. Add #406's `9f090e57`, one copy of the CI trigger change, and #460's clippy fix from `ebf622c2` | +| #460 | #275's branch | Yes | Merge into #275's branch before #275 merges | +| #406 → #407 → #409 → #414 → #422 → #439 | linear chain | Yes, two commits misplaced | Move #414 `4ea99467` (production startup API) to #406 and #414 `9a985c99` (flaky test fix) to #407 | +| #463 | #422's branch | No | Drop the #439 commits it carries | +| #464 | #439's branch | No | Drop #454's prototype commits it carries | +| #453 | #439's branch | No | Rebase off its stale base | +| #444, #466, #452, #401, #389, #287 | main | Yes | #389 and #401 must redo the RC-only merges that fit them onto #275 (`7c07b8e7`, `2510ecc9`, `501a2dce`) | + +### Order + +Phase A runs now. Nothing in it needs new EdgeZero. + +1. TS T1, partner routes answered locally. +2. TS #1557. +3. TS #1526, retargeted to main. #1525 lands in parallel. +4. TS #1516. +5. EZ: #460 and the other #275 fixes go into #275's branch. The independent + PRs #444, #466, #452, #401 and #389 land in any order. +6. TS T3, `user.eids` placement. It is independent and can land any time. + +Phase B depends on Phase A. + +1. TS #1506, rebased, repinned to #275's head after the fold-in. +2. TS #1487, rebased onto #1526 and #1506. +3. TS #1489, then #1488 restacked onto it. +4. TS T2, SharedID correctness, removing the legacy `sharedId` path. +5. TS #1503, then #1449 and #1543. +6. EZ: #275 merges. Then #406, #407, #409, #414, #422 and #439 in order, + with the misplaced commits moved. Then #464 and #463, both rebased. Then + E1, conditional KV. Then a release tag, E6. #287 goes last. + +Phase C depends on the EdgeZero release. + +1. TS repins to the EdgeZero release tag. +2. TS #1528, retargeted and repinned. +3. TS T8, one record backend over EdgeZero, deleting the Fastly backend. +4. TS forward-ports of the RC line: native EC lifecycle (A2), geo (A4), + image (A6), AWS examples (A7). Close #1541, #1424, #1499, #1527 and the RC + units. + +Phase D is the new feature work: T9 to T14 and A3, A5, A8. + +## Part 2: tickets + +Each ticket names its requirement IDs from the spec. "Review item" means the +fix belongs in an open PR's review, not in a new issue. + +### Trusted Server epic #1558 + +Make the existing Axum self-hosting epic #1236 a sub-epic of #1558, and put the +AX tickets under it. #1236's item 2 proposes a hand-written Axum `EcKvStore` +on redb or Redis. Point it at T8 instead. + +| ID | Ticket | Reqs | Depends on | Type | +| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------------- | ------- | +| T1 | Answer identify and batch sync locally on Axum, Cloudflare and Spin | ID-3 | None | Task | +| T2 | Read SharedID from the publisher's Prebid `storage.name` (already in `managed_user_ids`), honor the `_pubcid_optout` cookie, store and send one `pubcid.org` atype 1 EID, dedupe across cookie, `ts-eids`, body and record, and delete the `sharedId` / `sharedid.org` path | SID-1 to SID-3 | #1488 | Feature | +| T3 | Write EIDs to OpenRTB 2.6 `user.eids` in the bidder, Prebid Server and APS builders (`openrtb.rs:372`, `prebid.rs:2882`, `aps.rs:1131`). Keep `user.ext.eids` only where a named downstream needs it | SID-4 | None | Feature | +| T8 | Run the EC record on one `EcKvStore` over EdgeZero's `KvStore`, and delete `adapter-fastly/src/ec_kv.rs` | ID-2 | E1, E6 | Feature | +| T9 | Run server-side providers on every adapter with a record store: remove the startup refusal, register provider kinds, classify navigations, collect after the response, rate limit | ID-4 to ID-6 | T8, E2, E3 | Feature | +| T10 | Run the EC lifecycle on Cloudflare and Spin (mint, record, `ts-ec`, identify, batch sync, pull sync) | ID-1, ID-2 | T8, E1 on those platforms | Feature | +| T11 | Write the SharedID cookie on the document response: Prebid's storage name and Domain, the `_cst` companion so Prebid does not rewrite it from script, expiry on consent denial or opt-out | WB-3 | T2, #1488 retention fix | Feature | +| T12 | Write the mirror cookie on Axum, Cloudflare and Spin | WB-4 | T9 | Feature | +| T13 | Identity-only tsjs mode: capture Prebid's IDs without the auction shim, read the mirror cookie, merge into Prebid | WB-6 | #1489 | Feature | +| T14 | Decide and implement `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | None | Task | +| T15 | Build settings, partner registry, store clients and the HTTP client once per isolate on Cloudflare and Spin. Remove Cloudflare's first-request `Env` capture and its config `block_on` | SH-1, SH-3, SH-4 | #1506 | Task | +| T16 | Protect record creation on every adapter. Off Fastly only the User-Agent is checked. On Fastly a JA4 plus a browser User-Agent passes | ID-7 | None | Feature | +| T17 | One identity conformance suite run against all four adapters. Today the EC scenarios run on Fastly only | ID-1 | None, grows with T9 and T10 | Task | + +Fixes inside open PRs (ID-8, SH-2, SH-3, SID-5, WB-4, AX-7) are listed in +Part 3, not as tickets. + +### Under #1236 (Axum deployable) + +Reuse #1142 (health and readiness), #1134 (no-geo behavior), #1141 (config +store) and #1139 (stores) where they fit. + +| ID | Ticket | Reqs | Depends on | Type | +| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ---------- | ------- | +| A1 | Serve Axum in production mode from main: native startup (#1528) on released EdgeZero, fail before listening, SIGTERM drain | AX-1, AX-7 | E6 | Feature | +| A2 | Forward-port the native EC lifecycle (#1536, #1537) onto the shared backend, without their adapter-local DynamoDB and celld code | ID-1, ID-3 | T8, A1 | Feature | +| A3 | Drain after-response tasks on SIGTERM | AX-1 | E2, A1 | Feature | +| A4 | Trusted client IP and geo behind CloudFront: forward-port #1532 without its US-no-region change that fails Cloudflare closed, trust IPv6 CloudFront ranges, enable geo by default in the reference deployment | AX-3 | A1 | Feature | +| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | A2, T9 | Task | +| A6 | Publish and attest the production image, not the dev-server image. One image contract for #1539 and #1543 | AX-5 | A1 | Feature | +| A7 | Reference infrastructure: wire DynamoDB, readiness that checks dependencies (#1142), JSON logs, more than one task | AX-6 | A1, A6 | Feature | +| A8 | Run the SIGTERM-during-record-write test in CI (`scripts/test-native-ec-image.mjs` from #1536) | AX-1 | A2 | Task | +| A9 | Use only `TRUSTED_SERVER__` environment names. The RC reads `TS_NATIVE_GEO_PROFILE_FILE`, `TS_NATIVE_BINDINGS_TOML` and `TS_APPCONFIG_TOKEN` | AX-2 | A1 | Task | + +### EdgeZero epic #468 + +Behavior and acceptance criteria only. Reuse existing issues where they fit. + +| ID | Ticket | New or reuse | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| E1 | `KvStore` conditional writes and strong reads: a read that returns a generation marker, create-if-absent, compare-and-swap on the marker, strongly consistent existence and prefix listing, `KvError::Unsupported` where a store cannot. Covers the DynamoDB provider without funnelling writes through one item or partition. #463's celld record store already has the semantics on a separate API | New. #410 and #437 rule out conditional writes today | +| E2 | Run bounded work after the response is sent on all four adapters, drained on Axum shutdown | New | +| E3 | Rate limiter: Edge Rate Limiting on Fastly, in-memory and shared-counter backends elsewhere | New | +| E4 | Trusted client IP on every adapter through one accessor | Reuse #89 with #90 to #92 and #397. New sub-issue for Spin | +| E5 | Geo with country and region on Fastly, Cloudflare and Spin through `ctx.geo()`. Main has no geo module; it exists only on the #439 stack, for Axum | New. Reuse #432 and #438 for Axum | +| E6 | Release a tag containing #275 through #439 | New, under #391, linked from #468 | + +## Part 3: fixes to existing PRs + +Fixes go to each PR's author as review findings. EdgeZero fixes are requests; +Trusted Server does not push to EdgeZero. Line references are at the PR heads +listed in Part 1. + +### Trusted Server + +- **#1506 (cutover).** + - Rebase after #1557, #1526 and #1516. It conflicts with main today. + - Repin to #275's head after #460 folds in. Then drop the + `origin_readthrough_enabled` rejection (`config.rs:317`), since cache tags + return. The repin also brings `b97efec1`, which stops Spin sending + `Content-Length: 0` on bodiless DELETE and OPTIONS. + - Port #1527's native-client, response-owner and managed Fastly delivery + tests if it has no equivalents. + - Update the description. It still says "Docs only". +- **#1526 (async EC store).** Retarget to main. Address its open review from + round 1, including the test gap for finalize under `block_on`. +- **#1525.** Address its open review from round 1. +- **#1487 (provider framework).** + - Rebase onto #1526 and #1506. Make lookup collection an `.await` inside the + now-async finalize. Today `main.rs:699` calls `block_on` inside a function + that #1526 wraps in `block_on` (`main.rs:550`), which panics. + - Move per-request lookups out of the `thread_local` into the request. + - Keep saving `/auction` body EIDs. A test at `finalize.rs:1434` asserts that + they are dropped. + - Send provider lookups through EdgeZero's `HttpClient` after #1506. + - Decide the rate-limit key. `identity:{provider}:{IP-derived prefix}` gives + everyone behind one IP one budget. +- **#1489 (mirror and pull sync).** Give every mirrored ID a bounded lifetime. + An unspecified expiry becomes `Expiry::Never` + (`coordinator/validate.rs:132-135`), and the pull-sync provider writes such + IDs (`pull_sync.rs:128`). Closed #1461 capped mirrored IDs at 24 hours. +- **#1488 (vendors).** + - Restack onto #1489's branch and resolve the conflicts. It sits on an old + #1487 commit, and the two siblings overlap in 11 files. + - Keep main's SharedID retention: the stored value wins and lives as long as + the record. Today a cookie-read SharedID expires in 24 hours and a + differing cookie replaces it. + - Read the SharedID cookie name from `managed_user_ids[].storage.name` + instead of adding a second setting, and default the source to + `pubcid.org`. + - After the restack, startup must refuse an enabled provider kind that is not + registered. The old base silently ignores it. + - Settle the LiveRamp Custom ID, which everyone behind one IP shares, and + confirm LiveRamp's response shape. The parser accepts one envelope; one + LiveRamp page shows a list. + - Address its open review from round 1. +- **#1528 (native startup).** + - Retarget onto main after #1506. + - Repin off `456313675`, which exists only on EdgeZero's DO NOT MERGE #462. + - Fix the compile failures: `AxumDevServerConfig` is missing `ingress` in a + CLI test, there is a type error, and there are two new high CodeQL alerts. + - Remove the per-request `AtomicBool` from the Fastly `AppState`. +- **#1543 (image publishing).** Rebase onto #1449; it conflicts. It publishes + the dev-server image (`STOPSIGNAL SIGINT`), so agree one production image + contract with #1539 before it ships as the release image. +- **RC units, fixed during the forward-port, not on the RC:** + - #1532: drop the core change that maps a US request with no region to + `Unknown`. It fails every US Cloudflare request closed. + - #1536: do not port the adapter-local DynamoDB backend. Its rows share one + partition key, and every insert first updates one allocator item. + - #1537: its example config breaks startup on Axum, Cloudflare and Spin. + - #1539: fix the failing Axum test. + - #1540: trust IPv6 CloudFront ranges, enable geo by default, and wire the + DynamoDB module. + - All units: replace `TS_*` environment names. + - #1541: drop the commits from closed #1545. +- **Close:** #1527 after its tests move, #1310 (superseded by #1488's ID5 + provider), and #1424, #1499 and #1541 once main matches the RC tag. + +### EdgeZero (requests to the PR authors) + +- **#275.** Absorb #460, #406's `9f090e57` (the CLI template emits + Fastly-only image code that #275 gates) and one copy of the CI trigger + change, which today sits in #406, #460 and #414. Optionally guard all five + egress unwind sites against a panicking logger; #409's `0769cbfd` covers + three. It also has changes requested, 20 of 95 threads open, and a red + watchdog fixture test. +- **#460.** Validate tags (printable ASCII, non-empty) and make Cloudflare + reject tags instead of silently using the default. Fix its red Cloudflare + wasm clippy; the fix exists only in RC commit `ebf622c2`. It removes `Copy` + from a #275 public type, so it must land before #275 releases. +- **#406.** Take `run_production_app_with_options_and_initializer` from #414 + (`4ea99467`). The Trusted Server RC's native startup calls it. +- **#407.** Take the flaky-test fix from #414 (`9a985c99`). +- **#414.** Drop those two commits once they move down. +- **#463.** Rebase onto #422 without the #439 commits it carries. +- **#464.** Rebase onto #439 without #454's KV geo prototype. +- **#453.** Rebase off its stale base. +- **#389 and #401.** Redo the RC-only merges that fit them onto #275 + (`7c07b8e7` for #389; `2510ecc9` and `501a2dce` for #401). +- **Every stack PR.** The descriptions cite CI results at heads 4 to 50 commits + old. Refresh them. + +### Outside these epics + +The #275 follow-ups belong to the cutover (Trusted Server #1505, +EdgeZero #436), not to identity: + +- Cache tags: reuse #459. #460 still lacks tag validation, and Cloudflare + silently falls back instead of rejecting. +- TLS chain-only verification: reuse #81, stating that verification is never + turned off. +- Idle bound for streamed bodies: reuse #82. +- Fire-and-forget send and a published outbound test double: new. +- Spin host override: mostly in #275; only the probe evidence is missing. diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-requirements.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-requirements.md new file mode 100644 index 000000000..b292bc7eb --- /dev/null +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-requirements.md @@ -0,0 +1,468 @@ +# Server-side identity on every adapter: requirements and PR sequence + +Status: draft. Read at Trusted Server main `598a85170` and EdgeZero main +`e11ea308` (crates at `v0.0.11`) on 2026-11-11. + +## Goals + +1. Axum is deployable as a production service on AWS, behind CloudFront and an + ALB. +2. Server-side identity works on every adapter: Fastly, Axum, Cloudflare and + Spin. +3. SharedID is read, stored and passed to bidders the way Prebid defines it, + and shared state is passed through each adapter the right way. +4. Identity is written back as first-party cookies on the HTML document + response. + +The first Axum publisher runs client-side Prebid and wants identity without the +auction orchestrator. Its pages pass through Trusted Server, so cookies can be +set on the document response, which Safari does not cap. + +## Where things stand + +Two lines of code carry Axum today. + +- **Main.** Axum is a dev server. It binds loopback, reads config and secrets + from env vars only, takes the client IP from the socket peer, has no geo, + and stops on Ctrl-C only. Only Fastly has server-side identity. Axum, + Cloudflare and Spin build an `EcContext` for consent alone: no store, no + `ts-ec` cookie, no partner registry. +- **Axum RC line.** Tag `v0.1.1-rc-axum.202611.2` is the head of draft #1541 + (`7c6e68d94`) and is the source of the deployed demo. It adds production + startup, the EC record in DynamoDB and celld, CloudFront geo, a container + image and AWS examples. It branches from main at `2dd968aee` (2026-10-20). + It carries 74 commits that are not on main and is 29 commits behind it. It + pins EdgeZero tag `v0.0.12-rc-axum.202611.1`, which is EdgeZero #431 ("DO + NOT MERGE"). It has none of the provider stack, so there is no server-side + ID5 or LiveRamp, no SharedID write-back and no mirror cookie. + +Capability by adapter on main, and the open PR that adds each capability +elsewhere: + +| Capability | Fastly | Axum | Cloudflare | Spin | +| ----------------------------------- | -------------- | ------------------------------------- | -------------------------------- | -------------------------------- | +| EC minted and `ts-ec` set | main | #1536 (RC line) | none | none | +| `EcKvStore` backend for the record | main (KV, CAS) | #1536 DynamoDB, #1537 celld (RC line) | none | none | +| Identify | main | #1536 (RC line) | none | none | +| Batch sync, pull sync, admin lookup | main | admin lookup answers 501 locally | admin lookup answers 501 locally | admin lookup answers 501 locally | +| Auction EIDs from the record | main | #1536 (RC line) | none | none | +| Server-side providers | #1487 | refused at startup on #1487 | refused at startup on #1487 | refused at startup on #1487 | +| Trusted client IP and geo | main | #1528, #1532 (EdgeZero RC only) | `cf-connecting-ip`, country only | TCP peer, no geo | +| After-response work | main | none | none | none | +| Rate limiting | main (ERL) | none | none | none | +| First-party cookie write-back | main | RC line | none | none | + +EdgeZero main provides none of these for Axum: production startup, SIGTERM +drain, trusted client IP, AWS config and secret stores, DynamoDB, geo, or +concurrent outbound HTTP with deadlines. Open PRs #275 through #439 add them +(see the sequence below). No EdgeZero PR adds a portable after-response hook, +conditional KV writes, a portable client-IP accessor, geo on Fastly, +Cloudflare or Spin, or a rate limiter. The `KvStore` contract on EdgeZero main +has get, put, delete, exists and list only. EdgeZero main has no geo module; +`ctx.geo()` exists only on the #439 stack, and only Axum fills it there. + +Main has no server-side providers. The single page-load write model, the +navigation permit and the startup refusal of providers off Fastly all exist +only on #1487 and the PRs stacked on it. Axum, Cloudflare and Spin do build a +partner registry, per request, for `/_ts/admin/eids`. + +### How the EC record write is split + +The record logic is already shared. `KvIdentityGraph` in +`crates/trusted-server-core/src/ec/kv.rs` owns the compare-and-swap retry +loops, consent tombstones, entry validation and the page-load write for every +adapter. An adapter supplies only the narrow `EcKvStore` trait in +`crates/trusted-server-core/src/ec/kv_backend.rs`. It has five operations: + +- a lookup that returns a generation marker +- a strongly consistent `key_exists` +- `insert` with `Add`, `Overwrite` or `IfGenerationMatch` +- a strongly consistent prefix listing +- delete + +Each backend is written by hand because EdgeZero's `KvStore` gives none of +these guarantees. The Fastly backend calls the Fastly SDK directly +(`crates/trusted-server-adapter-fastly/src/ec_kv.rs`). The RC line adds +DynamoDB and celld backends inside the Axum adapter (`ec_kv.rs`, +`celld_ec.rs`). Cloudflare already reaches EdgeZero's `KvStore` for generic +runtime KV, but that store cannot back the EC record. + +The target is one `EcKvStore` implementation in core, written over EdgeZero's +`KvStore` once EdgeZero offers conditional writes and strong reads (EZ-2). The +per-adapter backends are then deleted. The DynamoDB and celld conditional +logic belongs in EdgeZero's store providers, not in the Trusted Server Axum +adapter. + +Platform limits to confirm in the EZ-2 design: Cloudflare Workers KV is +eventually consistent and has no compare-and-swap, so a Cloudflare backend +needs a strongly consistent store such as Durable Objects or D1. Spin's +key-value interface needs checking for compare-and-swap. + +## Requirements + +Each requirement states behavior and how to accept it. The PR sequence below +maps them to work. + +### AX: Axum deployable + +- **AX-1 Production serving.** The service binds a configured address. It + exits non-zero before listening when config, secrets or a required store + fails to load. On SIGTERM it stops accepting, finishes in-flight requests and + after-response tasks, and exits within a configured grace shorter than the + ECS stop timeout. Accept: an ECS task stop during a request completes the + request and its record write, and CI runs that test. The RC line has the + startup, SIGTERM and drain parts, and the test passes locally + (`scripts/test-native-ec-image.mjs` from #1536), but CI does not run it. + Draining after-response tasks needs EZ-1; EdgeZero #406 drains connections + only. +- **AX-2 Managed config and secrets.** Config and secrets load from AWS stores + through EdgeZero store providers. Environment overrides use the full + `TRUSTED_SERVER__` prefix. Main's `TRUSTED_SERVER_CONFIG_*` and + `TRUSTED_SERVER_SECRET_*` names break this rule, and #1528 removes them. The + RC line adds `TS_NATIVE_GEO_PROFILE_FILE`, `TS_NATIVE_BINDINGS_TOML` and + `TS_APPCONFIG_TOKEN`, which must not be ported. +- **AX-3 Trusted IP and geo.** The client IP is taken only from trusted + CloudFront and ALB hops, for IPv4 and IPv6 ranges. Country and US region come + from CloudFront headers only when the hop is trusted. The reference + deployment enables geo, because an unknown country fails closed and issues no + EC. The code in EdgeZero #409 and #439 and Trusted Server #1532 already + accepts IPv6. The reference Terraform in #1540 trusts IPv4 ranges only and + leaves geo off by default. +- **AX-4 Record store fit for production.** EC records support create-if-absent + and a generation compare-and-swap. Writes do not all hit one item or one + partition key. Retries are bounded. Accept: a load test at the target + page-load rate with provider refreshes shows no throttling. +- **AX-5 Published production image.** The published, attested image runs the + production server, not the dev server. +- **AX-6 Reference infrastructure.** It wires the DynamoDB table, port 443 + egress for vendor APIs, a readiness check that tests dependencies, JSON logs, + and more than one task. EdgeZero #275 runs Axum on a current-thread runtime, + so a process uses one core and capacity comes from task count. #1540 already + has the 443 egress (IPv4 only). Its `/health` is static, logs are plain text, + it runs one task, and the DynamoDB module is not wired in. +- **AX-7 Ships from main.** The deployed build comes from main. It never pins + an EdgeZero RC tag (#431, #462) or a revision that exists only on an RC + branch, such as `456313675`, which #1528 pins today. Main may pin PR #275's + head, as #1506's maintainer answers allow. Pinning any other unmerged + EdgeZero revision needs its own decision. The end state is a released + EdgeZero tag. + +### ID: Server-side identity on every adapter + +- **ID-1 One EC lifecycle.** On every adapter a consented document navigation + mints the EC, creates the record and sets `ts-ec` together. A cookie is never + issued without a record. An existing EC is kept when geo is unknown. Accept: + one conformance suite runs against all four adapters. +- **ID-2 One shared record store.** The identity logic stays in core + (`KvIdentityGraph`) and becomes awaitable (#1526). It runs on one + `EcKvStore` implementation over EdgeZero's `KvStore`, not a backend per + adapter. Each platform's EdgeZero store provides a generation marker, + create-if-absent, compare-and-swap, and strongly consistent existence and + prefix reads (EZ-2). An adapter whose store cannot meet that contract + rejects identity config at startup with a clear error instead of running + without a record. Accept: no adapter crate implements `EcKvStore`. +- **ID-3 Partner APIs answered locally.** Identify, batch sync, pull sync and + admin lookup run on every adapter with a store. On an adapter without one, + identify and batch sync return a local error and never reach the publisher + origin. Admin lookup already answers 501 locally, and pull sync is outbound + only. +- **ID-4 Providers on every adapter with a store.** Lookups start at navigation + alongside the origin fetch, share one deadline, and are collected before + finalization. A page load writes the record once. Startup accepts an enabled + provider on every adapter that supports providers. +- **ID-5 After-response work.** Pull sync and guarded follow-up writes run after + the response is sent on every adapter: after send on Fastly, a task drained + on SIGTERM on Axum, `waitUntil` on Cloudflare. Spin has no mechanism; the + work either runs before the response there or is not offered. +- **ID-6 Rate limiting.** Provider lookups, batch sync, pull sync and record + creation are rate limited on every adapter. Fastly Edge Rate Limiting is the + only limiter on any branch, and record creation is limited nowhere. Batch + sync is keyed per partner. #1487 keys provider lookups on the IP-derived EC + prefix, so everyone behind one IPv4 address or IPv6 /64 shares one budget. + Choose that key on purpose. With more than one Axum task the counter must be + shared. +- **ID-7 Abuse resistance.** Record creation needs real protection on every + adapter. On Fastly the gate checks only that a JA4 fingerprint exists plus a + browser User-Agent platform, so any TLS client with a browser User-Agent + passes. The RC adds a User-Agent heuristic and request filters on Axum. + Cloudflare and Spin have nothing. +- **ID-8 Body EIDs keep being saved.** EIDs sent in the `/auction` body are + saved to the record, as on main (Fastly). The provider stack stops saving + them, and the RC's Axum lifecycle never saves them. + +### SH: Shared state passed the right way + +- **SH-1 Built once.** Settings, the partner registry, store clients and the + HTTP client are built once per process or isolate and shared. Today + Cloudflare and Spin rebuild app state per request, partner registries are + rebuilt several times per request, and Axum builds an HTTP client per + request. +- **SH-2 Per-request state in the request.** Per-request values travel in the + request context. They are never kept in a `thread_local` or in retained app + state. #1487, #1488 and #1489 keep lookups in a `thread_local`. #1527 and + #1528 keep a per-request `AtomicBool` in the Fastly `AppState`. Fastly + serves one request per sandbox, so the risk is a value carried into the next + request, not a race. +- **SH-3 No executor nesting.** No `block_on` runs inside async code. #1526 + wraps finalize in `block_on` (Fastly `main.rs:550`). #1487 calls `block_on` + inside finalize (`main.rs:699`). Combined unchanged, they panic. On main, + core pull sync and Cloudflare and Spin config reads also call `block_on`. + #1526, #1506 and the RC remove the pull-sync one, and the RC removes Spin's. + Cloudflare's remains on every branch. +- **SH-4 No first-request capture.** Cloudflare must not keep the first + request's `Env` in a set-once `thread_local`. + +### SID: SharedID handled the way Prebid defines it + +- **SID-1 Cookie name from config.** The server reads SharedID from the + cookie named by the publisher's Prebid `storage.name`, on every adapter. + Prebid has no default name; `_sharedid` and `_pubcid` are the common ones. + Trusted Server config already holds the name in + `managed_user_ids[].storage.name`, so use that rather than a second setting. + Main reads only a cookie named `sharedId` (`constants.rs:10`), which is + Prebid's module name, not a cookie name. It does so on Fastly for the + record, and on every adapter for `/_ts/admin/eids`. #1488 makes the name + configurable, on Fastly only. +- **SID-2 Opt-out.** A present `_pubcid_optout` cookie stops SharedID reads, + storage, sending and write-back. Prebid also reads the opt-out from + localStorage, which the server cannot see. #1488 honors the cookie for its + own provider only. +- **SID-3 One canonical EID.** SharedID is stored and sent as source + `pubcid.org` with atype 1. Main gets partway by configuration: a + `pubcid.org` partner with atype 1 already stores and sends the right EID + from `ts-eids` and the auction body. The `sharedId` cookie path is + hard-wired to `sharedid.org` (`ec/prebid_eids.rs:241`), and records written + from the browser default to atype 3. The outbound merge dedupes only within + one source. Deduplicate across cookie, `ts-eids`, auction body and record so + a bidder never sees the same ID twice. +- **SID-4 OpenRTB placement.** EIDs go in the field the declared protocol + defines: `user.eids` for OpenRTB 2.6. Every builder writes only + `user.ext.eids`: bidder OpenRTB (`auction/openrtb.rs:372`), Prebid Server + (`integrations/prebid.rs:2882`) and APS (`integrations/aps.rs:1131`), while + `openrtb-2.6` is the only accepted protocol. Keep `user.ext.eids` only where + a named downstream needs it. +- **SID-5 Retention.** The record keeps a SharedID for as long as the cookie is + meant to last. The stored value wins over a different browser value. Main + already does this on Fastly (`ec/kv.rs:134-174`). #1488 regresses it: it + keeps a cookie-read SharedID for 24 hours and lets the cookie replace the + stored value. That has to change before write-back can work. + +### WB: First-party write-back + +- **WB-1 Document response, every adapter.** Identity cookies are set on the + top-level HTML document response on every adapter. Main mints `ts-ec` on + navigations on Fastly only, and the RC adds Axum. The navigation permit + exists only in the provider stack, where only the Fastly adapter classifies + it. +- **WB-2 `ts-ec` lifetime.** `ts-ec` keeps `Domain=.; Path=/; Secure; +SameSite=Lax; HttpOnly; Max-Age=31536000`. Today it is set only for a new or + rotated EC, so an active reader's cookie expires a year after minting. + Decide whether returning readers get a refresh. +- **WB-3 SharedID cookie.** New; no code on any branch writes it, and #1488 + says so on purpose. Set from the record when the request has no SharedID + cookie or a different value. Use Prebid's storage name and the Domain Prebid + writes with. Not HttpOnly, Secure, `SameSite=Lax`, one year. Also keep + Prebid's `_cst` companion cookie consistent: when it is missing or + differs, Prebid rewrites the SharedID cookie from script on the next page, + and Safari caps that copy again. Expire both when consent does not allow the + EC or `_pubcid_optout` is present. Prebid's `extend` with a `pixelUrl` is + its own server-write path and must keep working alongside this. +- **WB-4 Mirror cookie.** `__Host-ts-eids-mirror` as in #1489: opt-in per + partner, dropped per vendor when TCF consent is denied, requires + `bidstream_enabled` and Purpose 4, 3,584-byte budget, host-only. Every + mirrored ID has a bounded expiry. In #1489 an ID with no stated expiry never + expires (`Expiry::Never`), and its pull-sync provider writes such IDs. + Closed #1461 capped mirrored IDs at 24 hours. +- **WB-5 Cacheable pages.** An unchanged cookie gets no `Set-Cookie`, so + returning readers' pages stay cacheable. A response that carries + `Set-Cookie` is not stored in a shared cache; main already enforces this on + every adapter (`response_privacy.rs:122-148`) and does not re-set `ts-ec`. + Main does re-set the `ts-ec-pull-complete` marker about hourly, which #1489 + retires. The template cache also bypasses any request with a Cookie header + unless the publisher configures otherwise, and its deny list names only + `sharedId`, so it must learn the configured SharedID name. +- **WB-6 Identity-only page script.** A tsjs mode captures Prebid's IDs without + the auction shim, reads the mirror cookie, and merges the IDs into Prebid. + Today the `ts-eids` capture runs only inside the shim, which also adds the + Trusted Server bidder to every ad unit. Nothing reads the mirror. + +### EZ: asks of EdgeZero + +These are behaviors Trusted Server needs from EdgeZero. No open EdgeZero PR +provides them. + +- **EZ-1** An after-response task API on all four adapters, drained on shutdown + on Axum. +- **EZ-2** `KvStore` gains what the EC record needs: reads that return a + generation marker, create-if-absent and generation compare-and-swap writes, + and strongly consistent existence checks and prefix listing. A store that + cannot meet these returns a capability error; `KvError::Unsupported` already + exists on main. #463's celld `CelldRecordStore` has all of these semantics on + a separate API, and #414's DynamoDB provider has strong existence and + listing but no compare-and-swap. The portable `KvStore` gains none of them + on any branch, and #410 and #437 rule them out today. +- **EZ-3** A portable trusted client-IP accessor. +- **EZ-4** Geo through `ctx.geo()` on Fastly, Cloudflare and Spin, with region. +- **EZ-5** A rate limiter with Fastly ERL, in-memory and shared-counter + backends. +- **EZ-6** A released tag containing the Axum production stack (#275 through + #439). + +## Out of scope + +- A narrow install with one path rule and no page proxy. It needs an EC minted + on a request that is not a navigation. +- Conformance with the SharedID Edge specification. +- An Akamai adapter. +- The open #1342 to #1423 PR stack and PRs stacked on it, such as #1448. This + plan neither depends on it nor sequences it. + +## PR sequence + +The order lands the async store and the EdgeZero cutover on main first, then +identity on top, then the EdgeZero production stack, then forward-ports of the +RC line, then new work. Each step ends in a state that can be checked. + +### Step 0: decisions and cleanup + +1. The cutover is #1506 (decided 2026-11-11). Close #1527 once #1506 has + equivalents of its native-client, response-owner and managed Fastly + delivery tests. #1528 is stacked on #1527, so it is rebased onto #1506. +2. Close #1310. #1488's ID5 provider supersedes it. +3. Drop the commits of #1545 (closed, unmerged) from #1541. + +### Step 1: Trusted Server main + +1. #1557 Sourcepoint consent. Already in the RC tag. +2. #1526 retargeted to main, with #1525 in parallel. #1526's own diff applies + cleanly to main without #1525. +3. #1516, which merges before #1506 as already agreed. +4. #1506, the EdgeZero cutover. Before merge it: + - finishes Tasks 10 to 12 + - rebases onto main with #1526 and #1516 (#1526 shares 7 of its files) + - repins to #275's head once #275 absorbs #460 (see the EdgeZero step). + That restores outbound cache tags, so #1506 can drop its rejection of + `origin_readthrough_enabled` (`config.rs:317`). It also picks up + `b97efec1`, which stops Spin sending `Content-Length: 0` on bodiless + DELETE and OPTIONS + - updates its description, which still says docs only + + Main then pins #275's head instead of `v0.0.11`. + +5. #1487 rebased onto #1526 and #1506 (13 and 23 shared files). Provider + lookups go through EdgeZero's `HttpClient`. Lookup collection becomes an + `.await` in async finalize (SH-3), and per-request lookups move into the + request (SH-2). +6. #1489, then #1488. They are siblings on #1487 and conflict in 11 files; the + second absorbs them. Vendor calls use EdgeZero's `HttpClient`. Fix the + mirror expiry (WB-4) and SharedID retention (SID-5) here. +7. New: partner routes answered locally on non-Fastly adapters (ID-3). +8. New: SharedID correctness on Fastly (SID-1 to SID-4). It removes the legacy + `sharedId` cookie and `sharedid.org` source path in the same change. +9. #1503 `ts ec show` and `ts ec eids`. +10. #1449, then #1543, image publishing. + +Landing #1506 before the provider stack means the ID5 and LiveRamp calls are +written against EdgeZero's outbound API from the start. #1506's answer to its +in-flight question allows the other order, but each PR that lands first adds +callers #1506 has to port. + +### Step 1, in parallel: EdgeZero + +1. Independent PRs, which need nothing else: #444, #466 (rebased on #444), + #452, #401, #389. +2. #275, after it absorbs the fixes to its own code that sit in later PRs: + - #460 (`251974d4`, `dc296073`): surrogate keys on `OutboundCachePolicy` + and a borrowed `poll_ready` on `OutboundPending`. #460 removes `Copy` + from a #275 public type, so it must land before #275 releases. Bring + the Cloudflare wasm clippy fix from RC-only `ebf622c2` with it. + - #406 `9f090e57`: the CLI template emits Fastly-only image code that + #275 gates behind `native_image_example`. + - One copy of the CI trigger change, so stacked PRs run CI. It exists + three times, in #406, #460 and #414. + - Optional: guard all five egress unwind sites against a panicking logger + (#409 `0769cbfd` covers three). + + Then fix placement inside the stack: + - #414 `9a985c99` (a flaky-test fix) moves to #407. + - #414 `4ea99467` (`run_production_app_with_options_and_initializer`, + which the RC's native startup calls) moves to #406. + - #463 rebases onto #422 without #439, #464 onto #439 without #454's + prototype, and #453 off its stale base. + - The RC-only merges that fit #389 (`7c07b8e7`) and #401 (`2510ecc9`, + `501a2dce`) onto #275 have to be redone in those PRs. + +3. #406, #407, #409. These give a deployable ECS service. +4. #414. +5. #422, #439, #464, #463. Retarget #463 first; it carries an old copy of + #439. If celld is not used in production, restack #439 on #414. +6. New: EZ-2 conditional KV on the DynamoDB and celld providers, generalizing + #463's celld-only record operations into `KvStore`. +7. Release a tag (EZ-6). File EZ-1 and EZ-3 to EZ-5 as issues. +8. #287 last. It shares 27 files with #275. + +Every ready EdgeZero PR currently has changes requested, and #275 has 20 of 95 +threads open. + +### Step 2: Trusted Server main on the EdgeZero production stack + +1. #1528 native startup, rebased onto #1506. It needs EdgeZero #406, #409 and + #414, which are stacked on #275 but are not in #275's head. +2. Forward-port the RC line as new PRs against main, closing each RC unit as + its port lands: + 1. New: one core `EcKvStore` over EdgeZero's `KvStore` (ID-2). Fastly + moves onto it and `adapter-fastly/src/ec_kv.rs` is deleted in the same + change. + 2. #1536 native EC lifecycle and #1537 storage selection, on that shared + backend. Their adapter-local DynamoDB and celld backends are not + ported. Spread writes across items and partitions (AX-4). #1537's + example config breaks startup on Axum, Cloudflare and Spin. + 3. #1532 CloudFront geo. It maps a US request with no region to `Unknown`, + which fails every US Cloudflare request closed. It does not trust IPv6 + CloudFront ranges. + 4. #1539 and #1538, the image, on one contract with #1543. + 5. #1540 AWS examples. +3. Close #1541, #1424 and #1499 once main reaches parity with the RC tag. + +### Step 3: new work + +1. Providers on Axum. Remove the startup refusal, register provider kinds, + classify the navigation permit, add the after-response task and a rate + limiter (ID-4 to ID-6). +2. SharedID write-back (WB-3). +3. Mirror on Axum. The adapter calls the page-load write (WB-4). +4. Identity-only tsjs mode (WB-6). +5. Partner APIs on Axum (ID-3). +6. Cloudflare and Spin: EdgeZero stores that meet EZ-2. The shared backend and + lifecycle then run unchanged (ID-1, ID-2). + +## Decisions + +Decided: + +- **Cutover** (2026-11-11). #1506. #1527 closes once its tests are covered. + +Open: + +1. **Base.** Forward-port to main, or back-port the provider stack to the RC + line for a faster first publisher. Back-porting meets 25 to 34 conflicts on + a branch that its own RC says will not merge. Recommendation: forward-port, + and keep serving the demo from the RC tag until main reaches parity. +2. **EdgeZero pin for the Axum production stack.** Wait for #406, #409 and + #414 to merge, or extend #1506's "follow #275's head" rule to that stack so + #1528 can land sooner. +3. **Store path for Axum.** Wait for EZ-2 and use the shared backend, or port + #1536's adapter-local DynamoDB backend first and delete it when EZ-2 lands. + Recommendation: wait, since the RC tag already serves the demo. +4. **Cloudflare and Spin store.** Which EdgeZero store meets EZ-2 there, or + whether identity is declared unsupported. +5. **LiveRamp Custom ID.** The current value is the IP-derived EC prefix, + shared by everyone behind one IP. +6. **SharedID retention and precedence** (SID-5), and whether the server + generates a SharedID when none exists. +7. **Mirror partners.** Which partners get `mirror_enabled`. Mirrored IDs are + readable by every script on the page. +8. **Rate limiter key and backend**, and the number of Axum tasks (ID-6, AX-6). +9. **EC EID source.** Which source name the EC uses, and whether it keeps + going out as `user.id`. +10. **`ts-ec` refresh** for returning readers (WB-2). From a3a22fe7fa23a507e34bd91110897f8c4330dd38 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:08:19 -0800 Subject: [PATCH 02/31] Restructure the identity spec and plan after review round 1 Rename the spec to a design doc and the plan to the writing-plans format. Add acceptance criteria, platform support with the Fastly, Axum, Cloudflare, Spin order, SharedID EID consent, configuration, consent, failure and rollout sections, and a review log. Rebuild the plan as ordered GitHub tasks with fixed dependencies, per-platform phases, EdgeZero releases and per-PR tie-out items. --- .../2026-11-11-identity-all-adapters-plan.md | 248 --------- ...11-11-server-side-identity-all-adapters.md | 402 ++++++++++++++ ...erver-side-identity-all-adapters-design.md | 508 ++++++++++++++++++ ...side-identity-all-adapters-requirements.md | 468 ---------------- 4 files changed, 910 insertions(+), 716 deletions(-) delete mode 100644 docs/superpowers/plans/2026-11-11-identity-all-adapters-plan.md create mode 100644 docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md create mode 100644 docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md delete mode 100644 docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-requirements.md diff --git a/docs/superpowers/plans/2026-11-11-identity-all-adapters-plan.md b/docs/superpowers/plans/2026-11-11-identity-all-adapters-plan.md deleted file mode 100644 index 8e7b6acfe..000000000 --- a/docs/superpowers/plans/2026-11-11-identity-all-adapters-plan.md +++ /dev/null @@ -1,248 +0,0 @@ -# Server-side identity on every adapter: merge order and tickets - -Status: draft plan, 2026-11-11. Requirements: -[the spec](../specs/2026-11-11-server-side-identity-all-adapters-requirements.md). -Epics: Trusted Server #1558, EdgeZero IABTechLab/edgezero#468. - -Every PR base and state below was read from GitHub on 2026-11-11. Every -"not implemented" claim comes from a five-part audit of both repositories: -main, every open PR head, the RC tags, branches with no PR, and PRs merged or -closed since 2026-10-01. Trusted Server PRs #1342 to #1423 and #1448 are out of -scope by decision. - -## Part 1: merge order - -### Is it stacked correctly? - -Trusted Server: - -| PR | Base on GitHub | Correct for the plan? | Fix | -| ----------------------------------- | -------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------ | -| #1557 | main | Yes | None | -| #1525 | main | Yes | None | -| #1526 | #1525's branch | No | Retarget to main. Its own diff applies cleanly there, so it does not wait on #1525's changes requested | -| #1516 | main | Yes | None | -| #1506 | main | Yes, but conflicting | Rebase after #1557, #1526 and #1516 land | -| #1527 | #1526's branch | Not merged | Close once #1506 covers its tests | -| #1528 | #1527's branch | No | Retarget onto main after #1506. Repin off `456313675`, a revision that exists only on EdgeZero's DO NOT MERGE #462 | -| #1487 | main | Yes, needs rebase | Rebase after #1526 and #1506 | -| #1489 | #1487's branch | Yes | None | -| #1488 | #1487's branch, old commit | No | Restack onto #1489's branch so the two form one line. It is conflicting and sits on an old #1487 commit | -| #1503 | main | Yes | None | -| #1543 | #1449's branch | Yes, but conflicting | Rebase onto #1449 | -| #1541, #1531 to #1540, #1424, #1499 | RC baselines | Not merged | Forward-port to main as new PRs; close each as its port lands | - -EdgeZero: - -| PR | Base on GitHub | Correct for the plan? | Fix | -| --------------------------------------- | -------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| #275 | main | Yes, missing fixes | Merge #460 into #275's branch first. Add #406's `9f090e57`, one copy of the CI trigger change, and #460's clippy fix from `ebf622c2` | -| #460 | #275's branch | Yes | Merge into #275's branch before #275 merges | -| #406 → #407 → #409 → #414 → #422 → #439 | linear chain | Yes, two commits misplaced | Move #414 `4ea99467` (production startup API) to #406 and #414 `9a985c99` (flaky test fix) to #407 | -| #463 | #422's branch | No | Drop the #439 commits it carries | -| #464 | #439's branch | No | Drop #454's prototype commits it carries | -| #453 | #439's branch | No | Rebase off its stale base | -| #444, #466, #452, #401, #389, #287 | main | Yes | #389 and #401 must redo the RC-only merges that fit them onto #275 (`7c07b8e7`, `2510ecc9`, `501a2dce`) | - -### Order - -Phase A runs now. Nothing in it needs new EdgeZero. - -1. TS T1, partner routes answered locally. -2. TS #1557. -3. TS #1526, retargeted to main. #1525 lands in parallel. -4. TS #1516. -5. EZ: #460 and the other #275 fixes go into #275's branch. The independent - PRs #444, #466, #452, #401 and #389 land in any order. -6. TS T3, `user.eids` placement. It is independent and can land any time. - -Phase B depends on Phase A. - -1. TS #1506, rebased, repinned to #275's head after the fold-in. -2. TS #1487, rebased onto #1526 and #1506. -3. TS #1489, then #1488 restacked onto it. -4. TS T2, SharedID correctness, removing the legacy `sharedId` path. -5. TS #1503, then #1449 and #1543. -6. EZ: #275 merges. Then #406, #407, #409, #414, #422 and #439 in order, - with the misplaced commits moved. Then #464 and #463, both rebased. Then - E1, conditional KV. Then a release tag, E6. #287 goes last. - -Phase C depends on the EdgeZero release. - -1. TS repins to the EdgeZero release tag. -2. TS #1528, retargeted and repinned. -3. TS T8, one record backend over EdgeZero, deleting the Fastly backend. -4. TS forward-ports of the RC line: native EC lifecycle (A2), geo (A4), - image (A6), AWS examples (A7). Close #1541, #1424, #1499, #1527 and the RC - units. - -Phase D is the new feature work: T9 to T14 and A3, A5, A8. - -## Part 2: tickets - -Each ticket names its requirement IDs from the spec. "Review item" means the -fix belongs in an open PR's review, not in a new issue. - -### Trusted Server epic #1558 - -Make the existing Axum self-hosting epic #1236 a sub-epic of #1558, and put the -AX tickets under it. #1236's item 2 proposes a hand-written Axum `EcKvStore` -on redb or Redis. Point it at T8 instead. - -| ID | Ticket | Reqs | Depends on | Type | -| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | --------------------------- | ------- | -| T1 | Answer identify and batch sync locally on Axum, Cloudflare and Spin | ID-3 | None | Task | -| T2 | Read SharedID from the publisher's Prebid `storage.name` (already in `managed_user_ids`), honor the `_pubcid_optout` cookie, store and send one `pubcid.org` atype 1 EID, dedupe across cookie, `ts-eids`, body and record, and delete the `sharedId` / `sharedid.org` path | SID-1 to SID-3 | #1488 | Feature | -| T3 | Write EIDs to OpenRTB 2.6 `user.eids` in the bidder, Prebid Server and APS builders (`openrtb.rs:372`, `prebid.rs:2882`, `aps.rs:1131`). Keep `user.ext.eids` only where a named downstream needs it | SID-4 | None | Feature | -| T8 | Run the EC record on one `EcKvStore` over EdgeZero's `KvStore`, and delete `adapter-fastly/src/ec_kv.rs` | ID-2 | E1, E6 | Feature | -| T9 | Run server-side providers on every adapter with a record store: remove the startup refusal, register provider kinds, classify navigations, collect after the response, rate limit | ID-4 to ID-6 | T8, E2, E3 | Feature | -| T10 | Run the EC lifecycle on Cloudflare and Spin (mint, record, `ts-ec`, identify, batch sync, pull sync) | ID-1, ID-2 | T8, E1 on those platforms | Feature | -| T11 | Write the SharedID cookie on the document response: Prebid's storage name and Domain, the `_cst` companion so Prebid does not rewrite it from script, expiry on consent denial or opt-out | WB-3 | T2, #1488 retention fix | Feature | -| T12 | Write the mirror cookie on Axum, Cloudflare and Spin | WB-4 | T9 | Feature | -| T13 | Identity-only tsjs mode: capture Prebid's IDs without the auction shim, read the mirror cookie, merge into Prebid | WB-6 | #1489 | Feature | -| T14 | Decide and implement `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | None | Task | -| T15 | Build settings, partner registry, store clients and the HTTP client once per isolate on Cloudflare and Spin. Remove Cloudflare's first-request `Env` capture and its config `block_on` | SH-1, SH-3, SH-4 | #1506 | Task | -| T16 | Protect record creation on every adapter. Off Fastly only the User-Agent is checked. On Fastly a JA4 plus a browser User-Agent passes | ID-7 | None | Feature | -| T17 | One identity conformance suite run against all four adapters. Today the EC scenarios run on Fastly only | ID-1 | None, grows with T9 and T10 | Task | - -Fixes inside open PRs (ID-8, SH-2, SH-3, SID-5, WB-4, AX-7) are listed in -Part 3, not as tickets. - -### Under #1236 (Axum deployable) - -Reuse #1142 (health and readiness), #1134 (no-geo behavior), #1141 (config -store) and #1139 (stores) where they fit. - -| ID | Ticket | Reqs | Depends on | Type | -| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ---------- | ------- | -| A1 | Serve Axum in production mode from main: native startup (#1528) on released EdgeZero, fail before listening, SIGTERM drain | AX-1, AX-7 | E6 | Feature | -| A2 | Forward-port the native EC lifecycle (#1536, #1537) onto the shared backend, without their adapter-local DynamoDB and celld code | ID-1, ID-3 | T8, A1 | Feature | -| A3 | Drain after-response tasks on SIGTERM | AX-1 | E2, A1 | Feature | -| A4 | Trusted client IP and geo behind CloudFront: forward-port #1532 without its US-no-region change that fails Cloudflare closed, trust IPv6 CloudFront ranges, enable geo by default in the reference deployment | AX-3 | A1 | Feature | -| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | A2, T9 | Task | -| A6 | Publish and attest the production image, not the dev-server image. One image contract for #1539 and #1543 | AX-5 | A1 | Feature | -| A7 | Reference infrastructure: wire DynamoDB, readiness that checks dependencies (#1142), JSON logs, more than one task | AX-6 | A1, A6 | Feature | -| A8 | Run the SIGTERM-during-record-write test in CI (`scripts/test-native-ec-image.mjs` from #1536) | AX-1 | A2 | Task | -| A9 | Use only `TRUSTED_SERVER__` environment names. The RC reads `TS_NATIVE_GEO_PROFILE_FILE`, `TS_NATIVE_BINDINGS_TOML` and `TS_APPCONFIG_TOKEN` | AX-2 | A1 | Task | - -### EdgeZero epic #468 - -Behavior and acceptance criteria only. Reuse existing issues where they fit. - -| ID | Ticket | New or reuse | -| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | -| E1 | `KvStore` conditional writes and strong reads: a read that returns a generation marker, create-if-absent, compare-and-swap on the marker, strongly consistent existence and prefix listing, `KvError::Unsupported` where a store cannot. Covers the DynamoDB provider without funnelling writes through one item or partition. #463's celld record store already has the semantics on a separate API | New. #410 and #437 rule out conditional writes today | -| E2 | Run bounded work after the response is sent on all four adapters, drained on Axum shutdown | New | -| E3 | Rate limiter: Edge Rate Limiting on Fastly, in-memory and shared-counter backends elsewhere | New | -| E4 | Trusted client IP on every adapter through one accessor | Reuse #89 with #90 to #92 and #397. New sub-issue for Spin | -| E5 | Geo with country and region on Fastly, Cloudflare and Spin through `ctx.geo()`. Main has no geo module; it exists only on the #439 stack, for Axum | New. Reuse #432 and #438 for Axum | -| E6 | Release a tag containing #275 through #439 | New, under #391, linked from #468 | - -## Part 3: fixes to existing PRs - -Fixes go to each PR's author as review findings. EdgeZero fixes are requests; -Trusted Server does not push to EdgeZero. Line references are at the PR heads -listed in Part 1. - -### Trusted Server - -- **#1506 (cutover).** - - Rebase after #1557, #1526 and #1516. It conflicts with main today. - - Repin to #275's head after #460 folds in. Then drop the - `origin_readthrough_enabled` rejection (`config.rs:317`), since cache tags - return. The repin also brings `b97efec1`, which stops Spin sending - `Content-Length: 0` on bodiless DELETE and OPTIONS. - - Port #1527's native-client, response-owner and managed Fastly delivery - tests if it has no equivalents. - - Update the description. It still says "Docs only". -- **#1526 (async EC store).** Retarget to main. Address its open review from - round 1, including the test gap for finalize under `block_on`. -- **#1525.** Address its open review from round 1. -- **#1487 (provider framework).** - - Rebase onto #1526 and #1506. Make lookup collection an `.await` inside the - now-async finalize. Today `main.rs:699` calls `block_on` inside a function - that #1526 wraps in `block_on` (`main.rs:550`), which panics. - - Move per-request lookups out of the `thread_local` into the request. - - Keep saving `/auction` body EIDs. A test at `finalize.rs:1434` asserts that - they are dropped. - - Send provider lookups through EdgeZero's `HttpClient` after #1506. - - Decide the rate-limit key. `identity:{provider}:{IP-derived prefix}` gives - everyone behind one IP one budget. -- **#1489 (mirror and pull sync).** Give every mirrored ID a bounded lifetime. - An unspecified expiry becomes `Expiry::Never` - (`coordinator/validate.rs:132-135`), and the pull-sync provider writes such - IDs (`pull_sync.rs:128`). Closed #1461 capped mirrored IDs at 24 hours. -- **#1488 (vendors).** - - Restack onto #1489's branch and resolve the conflicts. It sits on an old - #1487 commit, and the two siblings overlap in 11 files. - - Keep main's SharedID retention: the stored value wins and lives as long as - the record. Today a cookie-read SharedID expires in 24 hours and a - differing cookie replaces it. - - Read the SharedID cookie name from `managed_user_ids[].storage.name` - instead of adding a second setting, and default the source to - `pubcid.org`. - - After the restack, startup must refuse an enabled provider kind that is not - registered. The old base silently ignores it. - - Settle the LiveRamp Custom ID, which everyone behind one IP shares, and - confirm LiveRamp's response shape. The parser accepts one envelope; one - LiveRamp page shows a list. - - Address its open review from round 1. -- **#1528 (native startup).** - - Retarget onto main after #1506. - - Repin off `456313675`, which exists only on EdgeZero's DO NOT MERGE #462. - - Fix the compile failures: `AxumDevServerConfig` is missing `ingress` in a - CLI test, there is a type error, and there are two new high CodeQL alerts. - - Remove the per-request `AtomicBool` from the Fastly `AppState`. -- **#1543 (image publishing).** Rebase onto #1449; it conflicts. It publishes - the dev-server image (`STOPSIGNAL SIGINT`), so agree one production image - contract with #1539 before it ships as the release image. -- **RC units, fixed during the forward-port, not on the RC:** - - #1532: drop the core change that maps a US request with no region to - `Unknown`. It fails every US Cloudflare request closed. - - #1536: do not port the adapter-local DynamoDB backend. Its rows share one - partition key, and every insert first updates one allocator item. - - #1537: its example config breaks startup on Axum, Cloudflare and Spin. - - #1539: fix the failing Axum test. - - #1540: trust IPv6 CloudFront ranges, enable geo by default, and wire the - DynamoDB module. - - All units: replace `TS_*` environment names. - - #1541: drop the commits from closed #1545. -- **Close:** #1527 after its tests move, #1310 (superseded by #1488's ID5 - provider), and #1424, #1499 and #1541 once main matches the RC tag. - -### EdgeZero (requests to the PR authors) - -- **#275.** Absorb #460, #406's `9f090e57` (the CLI template emits - Fastly-only image code that #275 gates) and one copy of the CI trigger - change, which today sits in #406, #460 and #414. Optionally guard all five - egress unwind sites against a panicking logger; #409's `0769cbfd` covers - three. It also has changes requested, 20 of 95 threads open, and a red - watchdog fixture test. -- **#460.** Validate tags (printable ASCII, non-empty) and make Cloudflare - reject tags instead of silently using the default. Fix its red Cloudflare - wasm clippy; the fix exists only in RC commit `ebf622c2`. It removes `Copy` - from a #275 public type, so it must land before #275 releases. -- **#406.** Take `run_production_app_with_options_and_initializer` from #414 - (`4ea99467`). The Trusted Server RC's native startup calls it. -- **#407.** Take the flaky-test fix from #414 (`9a985c99`). -- **#414.** Drop those two commits once they move down. -- **#463.** Rebase onto #422 without the #439 commits it carries. -- **#464.** Rebase onto #439 without #454's KV geo prototype. -- **#453.** Rebase off its stale base. -- **#389 and #401.** Redo the RC-only merges that fit them onto #275 - (`7c07b8e7` for #389; `2510ecc9` and `501a2dce` for #401). -- **Every stack PR.** The descriptions cite CI results at heads 4 to 50 commits - old. Refresh them. - -### Outside these epics - -The #275 follow-ups belong to the cutover (Trusted Server #1505, -EdgeZero #436), not to identity: - -- Cache tags: reuse #459. #460 still lacks tag validation, and Cloudflare - silently falls back instead of rejecting. -- TLS chain-only verification: reuse #81, stating that verification is never - turned off. -- Idle bound for streamed bodies: reuse #82. -- Fire-and-forget send and a published outbound test double: new. -- Spin host override: mostly in #275; only the probe evidence is missing. diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md new file mode 100644 index 000000000..8fff4a0a1 --- /dev/null +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -0,0 +1,402 @@ +# Server-side identity on every adapter Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** File the missing tickets under #1558, #1236 and +IABTechLab/edgezero#468, and tie every existing PR to them, in an order that +keeps main shippable at each step. + +**Architecture:** This plan's tasks are GitHub operations, not code. Tasks 1 +to 4 create and link issues in dependency order, EdgeZero first because +Trusted Server tickets depend on it. Tasks 5 and 6 post the per-PR fixes in +Part C to the named PRs, and Task 7 checks coverage. Code lands later in each +ticket's own implementation PR, following the merge order in Part A, with its +own plan when it needs one. + +**Tech Stack:** `gh` CLI and GitHub GraphQL (issue types, sub-issues, project +items) against IABTechLab/trusted-server and IABTechLab/edgezero. + +**Spec:** +[server-side identity design](../specs/2026-11-11-server-side-identity-all-adapters-design.md) + +## Global Constraints + +- One-account mode: every write acts as the PR author's login. +- Platform order: Fastly, then Axum, then Cloudflare, then Spin (DEC-4). +- Issue bodies state the requirement, its acceptance and its dependencies, + and nothing about deployments or their state. +- EdgeZero references are written `IABTechLab/edgezero#N`; a bare `#N` means + Trusted Server. +- EdgeZero issue bodies state behavior and acceptance only. +- Issue types are set through GraphQL: Feature `IT_kwDOBPEB8s4A35yA`, Task + `IT_kwDOBPEB8s4A35x7`. New issues join project `PVT_kwDOBPEB8s4BFKrl`. +- An issue Part B names for reuse is reused, never duplicated. +- EdgeZero code is never changed from here; EdgeZero items are requests. +- Trusted Server PRs #1342 to #1423 and #1448 are out of scope (DEC-2). +- Issue and comment text uses no em dashes and no names of people; comments + start with `CREATOR: `. + +## Review Focus + +1. A duplicate issue for work an existing issue already tracks. Every create + step in Tasks 2 to 4 runs a search first. +2. A Part C item posted to a PR whose head has moved. Tasks 5 and 6 compare the + head with Part C before posting and re-check the item if it moved. +3. A ticket whose dependency has no issue number yet. Tasks run EdgeZero, + then #1236, then #1558, and Task 7 checks every "Depends on". +4. A bare `#N` meant for EdgeZero inside a Trusted Server issue. Task 7 scans + the posted bodies. +5. A body that goes beyond its requirement, acceptance and dependencies. Task 7 + reads every body. + +--- + +## Part A: merge order + +### Stacking, read 2026-11-12 + +Trusted Server: + +| PR | Head | Base on GitHub | Fix | +| --------------------- | ----------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| #1557 | `76e1dd5f6` | main | None | +| #1525 | `cd8977cad` | main | None | +| #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | +| #1516 | `214cd4821` | main | None | +| #1503 | `1b59bfbe7` | main | None; needs neither #1506 nor new EdgeZero | +| #1449 | `27361ec58` | main | None; needs neither #1506 nor new EdgeZero | +| #1506 | `851537045` | main | Conflicting; rebase after #1557, #1526 and #1516 | +| #1487 | `4e7a6e02b` | main | Rebase after #1526 and #1506 | +| #1489 | `29de3d446` | #1487's branch | None | +| #1488 | `25f35720b` | #1487's branch, old commit | Restack onto #1489's branch; conflicting | +| #1527 | `a020daeec` | #1526's branch | Close once #1506 covers its tests (DEC-1) | +| #1528 | `ea779fb47` | #1527's branch | Port its two commits onto #1506's outbound API; they modify #1527-only `platform/outbound.rs`; repin off `456313675` | +| #1543 | `dc1a063d8` | #1449's branch | Conflicting; rebase onto #1449; ships with A6 | +| #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | +| #1499 | `084fdbba9` | #1424's branch | Close at parity | +| #1541, #1531 to #1540 | various | RC baselines | Forward-port to main as new PRs; close each as its port lands | + +EdgeZero: + +| PR | Head | Base on GitHub | Fix | +| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| IABTechLab/edgezero#275 | `14a6ca8b` | main | Absorb IABTechLab/edgezero#460, IABTechLab/edgezero#406's `9f090e57` and one copy of the CI trigger change before merging | +| IABTechLab/edgezero#460 | `cec19886` | IABTechLab/edgezero#275's branch | Merge into that branch before IABTechLab/edgezero#275 merges | +| IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#409, IABTechLab/edgezero#414, IABTechLab/edgezero#422, IABTechLab/edgezero#439 | various | linear chain | Move IABTechLab/edgezero#414's `4ea99467` to IABTechLab/edgezero#406 and its `9a985c99` to IABTechLab/edgezero#407 | +| IABTechLab/edgezero#463 | `6f9fa8bc` | IABTechLab/edgezero#422's branch | Drop the IABTechLab/edgezero#439 commits it carries | +| IABTechLab/edgezero#464 | `6fe32531` | IABTechLab/edgezero#439's branch | Drop IABTechLab/edgezero#454's prototype commits | +| IABTechLab/edgezero#453 | `633f0552` | IABTechLab/edgezero#439's branch | Rebase off its stale base | +| IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452 | various | main | Land after IABTechLab/edgezero#275 merges; they conflict with it in 7, 6, 4 and 4 files | +| IABTechLab/edgezero#466 | `cb48c6b3` | main | Land after IABTechLab/edgezero#444; they conflict in `edgezero-cli` `config.rs` and `lib.rs` | +| IABTechLab/edgezero#287 | `d590f0eb` | main | Last; shares 52 files with IABTechLab/edgezero#275, 27 of them under `crates/` | + +### Phases + +Phase A runs now and needs no new EdgeZero. + +1. T1, partner routes answered locally. +2. #1557. +3. #1526 retargeted to main; #1525 in parallel. +4. #1516. +5. #1503 and #1449. +6. T2, `user.eids` placement, converting every EID-absence assertion. +7. EdgeZero: IABTechLab/edgezero#460, `9f090e57` and one CI trigger change go + into IABTechLab/edgezero#275's branch; the misplaced commits move; IABTechLab/edgezero#463, + IABTechLab/edgezero#464 and IABTechLab/edgezero#453 are rebased. + +Phase B follows Phase A. + +1. #1506, rebased and repinned to IABTechLab/edgezero#275's head after the + fold-in. Fallback: if the fold-in has not landed two weeks after Phase A + ends, #1506 merges pinned to IABTechLab/edgezero#275's head as it stands + (allowed by AX-7) and keeps its readthrough rejection until the repin. +2. #1487, rebased onto #1526 and #1506. +3. #1489, then #1488 restacked onto it. +4. T3 SharedID cleanup, T4 shared state, T5 conformance suite (Fastly first), + T15 identity-only tsjs. +5. EdgeZero: IABTechLab/edgezero#275 merges. Then IABTechLab/edgezero#389, + IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#466 and IABTechLab/edgezero#452 rebase onto main. Then EZI-2a. Then + IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#409, IABTechLab/edgezero#414, IABTechLab/edgezero#422, IABTechLab/edgezero#439, IABTechLab/edgezero#464 and IABTechLab/edgezero#463. Then + EZI-2b and release R1 (EZI-6). Then EZI-1 and EZI-5 for Fastly and Axum, + and EZI-3, in release R2. IABTechLab/edgezero#287 goes last. + +Phase C follows R1 and brings Axum to parity with the RC tag. + +1. Main repins to R1. +2. A1 (#1528 port) with A9. +3. T6, one record backend over EdgeZero; Fastly moves first. +4. A4 geo, then A2 EC lifecycle, then A8. +5. A6 image with #1543, then A7. +6. Close #1541, #1424, #1499, #1527 and the RC units as their ports land. + +Phase D follows R2, then R3, then R4, one platform at a time (DEC-4). + +1. Axum, after R2: A3, T10, T7, T11, T12, T13, T14, then A5. +2. Cloudflare, after R3: T8, then T10, T11, T12, T13 and T14 for Cloudflare. +3. Spin, after R4: T9, then the same tickets for Spin. +4. T16 once DEC-13 is decided. + +## Part B: tickets + +"Blocked by" names a spec decision. A ticket that spans platforms ships per +platform in DEC-4 order. + +### EdgeZero, under IABTechLab/edgezero#468 + +| ID | Ticket | Reqs | New or reuse | Phase | +| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | +| EZI-2a | `KvStore` record contract (generation read, `Add` / `Overwrite` / `IfGenerationMatch`, precondition-failed outcome, strong exists and prefix list, `KvError::Unsupported`) and the Fastly provider | EZI-2a | New; IABTechLab/edgezero#410 and IABTechLab/edgezero#437 rule out conditional writes today | B | +| EZI-2b | DynamoDB and celld providers meet the contract; DynamoDB spreads writes across partition keys with no item every insert updates | EZI-2b | New | B | +| EZI-1 | Bounded work after the response on Fastly, then Axum (drained on SIGTERM), then Cloudflare; capability error on Spin | EZI-1 | New | B, R2 / R3 | +| EZI-5 | Rate limiter: Edge Rate Limiting on Fastly, in-memory and shared counter on Axum, then Cloudflare and Spin backends per DEC-11 | EZI-5 | New | B, R2 / R3 | +| EZI-3 | Trusted client IP through one accessor on every adapter | EZI-3 | Reuse IABTechLab/edgezero#89 with IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92 and IABTechLab/edgezero#397; new sub-issue for Spin | B, R2 | +| EZI-4 | Geo with country and region through `ctx.geo()` on Fastly, then Cloudflare, then Spin | EZI-4 | New; IABTechLab/edgezero#432 and IABTechLab/edgezero#438 cover Axum | R3 / R4 | +| EZI-6 | Releases R1 to R4 as the spec's EZI-6 defines | EZI-6 | New; R1 under IABTechLab/edgezero#391 | B onward | + +### Axum deployable, under #1236 + +| ID | Ticket | Reqs | Depends on | Blocked by | Type | New or reuse | Phase | +| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | --------------- | ------------ | ------- | ------------------------- | ----- | +| A1 | Serve Axum in production mode from main: port #1528 onto #1506's outbound API, repin to R1, fail before listening, SIGTERM drain | AX-1, AX-2, AX-7 | R1, #1506 | DEC-5, DEC-6 | Feature | New; relates #1141 | C | +| A2 | Axum EC lifecycle on the shared backend: forward-port #1536 and #1537's mint, record, `ts-ec`, identify, auction and body EIDs, without their adapter stores | ID-1, ID-3, ID-8, WB-1 | T6, A1, A4 | DEC-6 | Feature | New | C | +| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | EZI-1 in R2, A1 | none | Feature | New | D | +| A4 | Trusted client IP and geo behind CloudFront: forward-port #1532 without its core change that maps a US request with no region to `Unknown` | AX-3 | A1 | DEC-6 | Feature | New; relates #1134 | C | +| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | A2, T11 | none | Task | New | D | +| A6 | Publish and attest the production image; one image contract for #1539 and #1543 | AX-5 | A1 | DEC-6 | Feature | New | C | +| A7 | Reference infrastructure: wire DynamoDB, readiness that checks dependencies, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on by default | AX-3, AX-6 | A1, A6 | DEC-11 | Feature | Reuse #1142 for readiness | C | +| A8 | Run the SIGTERM-during-record-write test (`scripts/test-native-ec-image.mjs` from #1536) in CI | AX-1 | A2 | none | Task | New | C | +| A9 | Use only `TRUSTED_SERVER__` environment names; drop the RC's `TS_NATIVE_GEO_PROFILE_FILE`, `TS_NATIVE_BINDINGS_TOML` and `TS_APPCONFIG_TOKEN` | AX-2 | A1 | none | Task | New | C | + +### Trusted Server, under #1558 + +| ID | Ticket | Reqs | Depends on | Blocked by | Type | Phase | +| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------------------------- | ------------- | ------- | ------------ | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, return 501 without one, never reach the publisher origin | ID-3 | none | none | Task | A | +| T2 | Write EIDs to OpenRTB 2.6 `user.eids` in the bidder, Prebid Server and APS builders; every EID-absence test asserts both `user.eids` and `user.ext.eids` absent; keep `user.ext.eids` only for a named downstream | SID-4 | none | none | Feature | A | +| T3 | SharedID cleanup: delete the `sharedId` cookie and `sharedid.org` path, dedupe one UUID across cookie, `ts-eids`, body and record, re-file stored `sharedid.org` entries on next write, apply the SID-6 gate, use the configured name in the template-cache deny list | SID-1, SID-3, SID-6, WB-5 | #1488 | DEC-7 | Feature | B | +| T4 | Build settings, partner registry, store clients and HTTP client once per process or isolate; remove Cloudflare's first-request `Env` capture and config `block_on`; port Spin's config `block_on` removal from RC commit `933de5921` | SH-1, SH-3, SH-4 | #1506 | none | Task | B | +| T5 | One identity conformance suite with ID-1's cases, run on every supported adapter, and an unsupported-startup test elsewhere | ID-1 | #1506 | none | Task | B | +| T6 | Run the EC record on one `EcKvStore` over EdgeZero's `KvStore`; delete `crates/trusted-server-adapter-fastly/src/ec_kv.rs` | ID-2 | EZI-2a in R1 | none | Feature | C | +| T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | A2, EZI-1, EZI-5 | none | Feature | D | +| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs | ID-1, ID-2, ID-3, ID-8, WB-1 | T6, EZI-1, EZI-2, EZI-4, EZI-5 for Cloudflare | DEC-8 | Feature | D | +| T9 | Spin EC lifecycle, the same scope as T8 | ID-1, ID-2, ID-3, ID-8, WB-1 | T8, EZI-2, EZI-4, EZI-5 for Spin | DEC-8, DEC-14 | Feature | D | +| T10 | Rate limits: batch sync per partner, pull sync, provider lookups and record creation; shared counter with more than one Axum task | ID-6 | EZI-5 | DEC-11 | Feature | D | +| T11 | Providers on every supported adapter: remove the startup refusal, register provider kinds, classify navigations, await lookups before finalization, and run only follow-up writes after the response | ID-4, ID-5, WB-1 | A2, T10, EZI-1 | none | Feature | D | +| T12 | Refuse record creation from non-browser clients on every supported adapter | ID-7 | A2 | none | Feature | D | +| T13 | SharedID cookie write-back on the document response, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | T3, #1488 retention fix, A2 | DEC-7 | Feature | D | +| T14 | Mirror cookie on Axum, then Cloudflare, then Spin | WB-4 | T11 | DEC-10 | Feature | D | +| T15 | Identity-only tsjs mode | WB-6 | #1489 | none | Feature | B | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | none | DEC-13 | Task | after DEC-13 | + +Requirements met inside open PRs, not by a ticket: ID-8 on main and SH-2 +(#1487), SID-1 to SID-3 and SID-6 for the provider, SID-5 (#1488), WB-4 +expiry (#1489), AX-7 (#1528). Part C lists them. + +## Part C: fixes to existing PRs + +Each item names the head it was checked at. Post it as a comment on that PR +(Tasks 5 and 6); once posted, the PR thread is the source of truth. + +### Trusted Server + +- **#1506 at `851537045` (AX-7, SID-4).** Rebase after #1557, #1526 and #1516. + Repin after the fold-in, or under Phase B's fallback; drop the + `origin_readthrough_enabled` rejection (`config.rs:317`) once cache tags + return. The repin brings IABTechLab/edgezero `b97efec1`, which stops Spin + sending `Content-Length: 0` on bodiless DELETE and OPTIONS. Port #1527's + native-client, response-owner and managed Fastly delivery tests if missing. + If T2 has landed, make the EID-absence assertions (`endpoints.rs:1708-1711`, + `prebid.rs:7023`) check both `user.eids` and `user.ext.eids`. +- **#1526 at `e529e5576` (ID-2, SH-3).** Retarget to main. Address its round-1 + review, including the finalize-under-`block_on` test gap. +- **#1525 at `cd8977cad`.** Address its round-1 review. +- **#1487 at `4e7a6e02b` (ID-6, ID-8, SH-2, SH-3, SID-4).** Rebase onto #1526 + and #1506 (13 and 28 shared files). Await lookup collection in the async + finalize: `main.rs:699` calls `block_on` inside a function #1526 wraps in + `block_on` (`main.rs:550`). Move per-request lookups out of the + `thread_local`. Keep saving `/auction` body EIDs; a test at + `finalize.rs:1434` asserts they are dropped. Send lookups through EdgeZero's + `HttpClient`. Key the limit per DEC-11. If T2 has landed, convert its EID + assertions as for #1506. +- **#1489 at `29de3d446` (WB-4).** Cap every mirrored ID's expiry at 30 days. + `coordinator/validate.rs:132-135` turns an unspecified expiry into + `Expiry::Never`, and + `crates/trusted-server-identity-providers/src/pull_sync.rs:128` writes such + IDs. +- **#1488 at `25f35720b` (SID-1, SID-2, SID-3, SID-5, SID-6).** Restack onto + #1489; the two overlap in 13 files and conflict in 11. Read the cookie name + from `managed_user_ids[].storage.name` and default the source to + `pubcid.org`, atype 1. Gate reads, storage and sending on EID permission + (SID-6). Keep main's retention (SID-5, DEC-7). Refuse an enabled provider + kind that is not registered. Settle the LiveRamp Custom ID (DEC-9) and + response shape. Address its round-1 review. +- **#1528 at `ea779fb47` (AX-7, SH-2).** Port its two commits onto #1506's + outbound API: a trial merge conflicts in 9 files, two of them modify/delete + on `platform/outbound.rs` and `platform/outbound_native_tests.rs`, which only + #1527 has. Repin off `456313675`. Fix the CLI test (`AxumDevServerConfig` + lacks `ingress`), the type error and the two new high CodeQL alerts. Remove + the per-request `AtomicBool` from the Fastly `AppState`. Feeds A1. +- **#1543 at `dc1a063d8` (AX-5).** Rebase onto #1449. It publishes the + dev-server image (`STOPSIGNAL SIGINT`), so it ships with A6's production + image contract. +- **RC units, fixed in their forward-ports.** #1532: drop the core change that + maps a US request with no region to `Unknown`, which fails every US + Cloudflare request closed (AX-3). #1536: do not port the adapter-local + DynamoDB store (ID-2, AX-4). #1537: its example config breaks startup on + Axum, Cloudflare and Spin. #1539: fix the failing Axum test. #1540: trust + IPv6 CloudFront ranges, turn geo on, wire DynamoDB (AX-3, AX-6). All units: + replace `TS_*` names (AX-2). #1541: drop the commits of closed #1545. +- **Proposed closures.** #1527 once its tests move (DEC-1); #1310, which + #1488's ID5 provider supersedes; #1424, #1499, #1541 and the RC units at + parity. + +### EdgeZero, requests to the PR authors + +- **IABTechLab/edgezero#275 at `14a6ca8b`.** Absorb IABTechLab/edgezero#460, + `9f090e57` from IABTechLab/edgezero#406 (the CLI template emits Fastly-only + image code that IABTechLab/edgezero#275 gates) and one copy of the CI trigger + change, which today sits in IABTechLab/edgezero#406, IABTechLab/edgezero#460 and IABTechLab/edgezero#414 (all + IABTechLab/edgezero). Optionally guard all five egress unwind sites against + a panicking logger; `0769cbfd` in IABTechLab/edgezero#409 covers three. +- **IABTechLab/edgezero#460 at `cec19886`.** Validate tags (printable ASCII, + non-empty) and make Cloudflare reject tags instead of silently using the + default. Fix the red Cloudflare wasm clippy; the fix exists only in RC + commit `ebf622c2`. It removes `Copy` from an IABTechLab/edgezero#275 public + type, so it lands before that release. +- **IABTechLab/edgezero#406 at `6d1dda16`.** Take + `run_production_app_with_options_and_initializer` from `4ea99467`; the + Trusted Server RC's native startup calls it. +- **IABTechLab/edgezero#407 at `d6f74308`.** Take the flaky-test fix + `9a985c99`. +- **IABTechLab/edgezero#414 at `3733482f`.** Drop those two commits once they + move down. +- **IABTechLab/edgezero#463 at `6f9fa8bc`, IABTechLab/edgezero#464 at `6fe32531`, IABTechLab/edgezero#453 at + `633f0552`.** Rebase as Part A's table says. +- **IABTechLab/edgezero#389 at `0653e6e3` and IABTechLab/edgezero#401 at + `1a6ae861`.** Land after IABTechLab/edgezero#275 merges, redoing the RC-only + merges `7c07b8e7` (IABTechLab/edgezero#389) and `2510ecc9` (IABTechLab/edgezero#401) and the RC-only follow-up + commit `501a2dce` (IABTechLab/edgezero#401). +- **IABTechLab/edgezero#444 at `217983c0` and IABTechLab/edgezero#466 at + `cb48c6b3`.** Land IABTechLab/edgezero#444 first, after IABTechLab/edgezero#275. +- **Every stack PR.** Refresh the CI results cited in the descriptions. + +### Cutover follow-ups outside these epics + +These belong to #1505 and IABTechLab/edgezero#436: + +- Cache-tag validation: the IABTechLab/edgezero#460 item above. +- TLS chain-only verification: reuse IABTechLab/edgezero#81, stating that + verification is never turned off. +- Idle bound for streamed bodies: reuse IABTechLab/edgezero#82. +- Fire-and-forget send and a published outbound test double: new. +- Spin host override: mostly in IABTechLab/edgezero#275; only the probe + evidence is missing. + +--- + +## Tasks + +### Task 1: Align the epics + +**Files:** none; GitHub only. + +**Interfaces:** + +- Produces: #1236 as a sub-issue of #1558. The note on #1236 saying this + design is not its spec was posted while #1559 was in review. + +- [ ] **Step 1: Add #1236 under #1558** + +Run: `gh api graphql -f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c}){issue{number}}}' -F p="$(gh issue view 1558 --json id --jq .id)" -F c="$(gh issue view 1236 --json id --jq .id)"` +Expected: `{"data":{"addSubIssue":{"issue":{"number":1558}}}}` + +- [ ] **Step 2: Verify** + +Run: `gh api graphql -f query='{repository(owner:"IABTechLab",name:"trusted-server"){issue(number:1558){subIssues(first:50){nodes{number}}}}}' --jq '[.data.repository.issue.subIssues.nodes[].number]'` +Expected: a list containing `1236`. + +### Task 2: File the EdgeZero tickets under IABTechLab/edgezero#468 + +**Files:** none; GitHub only. + +**Interfaces:** + +- Consumes: Part B's EdgeZero table and the spec's EZI requirements. +- Produces: an issue number per EZI row, recorded in a `CREATOR:` comment on + IABTechLab/edgezero#468. + +- [ ] **Step 1: Search before each row** + +For EZI-2a, EZI-2b, EZI-1, EZI-5, EZI-3, EZI-4 and EZI-6 in that order, run +`gh issue list -R IABTechLab/edgezero --state all --search ""`. +Expected: no open issue other than those the row names for reuse. + +- [ ] **Step 2: Create or reuse** + +For a new row, run +`gh issue create -R IABTechLab/edgezero --title "" --body-file `, +where the file holds three sections: Behavior (the spec's EZI text), Acceptance +(its Accept line) and Related (the reuse cell and +IABTechLab/trusted-server#1558). For a reuse row, post a `CREATOR:` comment on +the reused issue linking IABTechLab/edgezero#468 and the acceptance. +Expected: one issue URL per new row. + +- [ ] **Step 3: Type, parent and project** + +For each new issue, set type Feature through `updateIssue`, add it under +IABTechLab/edgezero#468 through `addSubIssue`, and add it to the project +through `addProjectV2ItemById`. +Expected: each mutation returns the issue number. + +- [ ] **Step 4: Record the numbers** + +Post a `CREATOR:` comment on IABTechLab/edgezero#468 mapping each EZI ID to its +issue. + +### Task 3: File the Axum tickets under #1236 + +Same steps as Task 2, against IABTechLab/trusted-server and #1236, for A1 to +A9 in table order. Each body has Summary (Ticket column), Requirements (Reqs +with a link to the spec section), Acceptance (each requirement's Accept line), +Depends on (issue numbers from Task 2 and this task) and Blocked by. A7 links +#1142; A1 links #1141; A4 links #1134. Record the map in a `CREATOR:` comment +on #1236. + +### Task 4: File the Trusted Server tickets under #1558 + +Same steps as Task 3, for T1 to T16 in table order, under #1558. + +### Task 5: Tie out the Trusted Server PRs + +- [ ] **Step 1: Check heads** + +For each Part C Trusted Server item, run +`gh pr view --json headRefOid --jq .headRefOid`. +Expected: the head Part C names. If it moved, re-check the item against the +new head before posting. + +- [ ] **Step 2: Post** + +Post one `CREATOR:` comment per PR with its Part C item and the ticket it +serves (`Part of #`). For proposed closures, ask the author; do not +close another author's PR. + +### Task 6: Tie out the EdgeZero PRs + +Same steps as Task 5, for the EdgeZero items in Part C, each linking its EZI +issue from Task 2. + +### Task 7: Verify coverage + +- [ ] **Step 1: Count sub-issues** + +Run the Task 1 Step 3 query for #1558, #1236 and IABTechLab/edgezero#468. +Expected: 16, 9 and 7 entries plus any reused issues, matching Part B. + +- [ ] **Step 2: Check dependencies and references** + +For every created issue, read its body and confirm three things: each +"Depends on" names an issue number; no Trusted Server body contains a bare +`#N` below 1000 (EdgeZero references use `IABTechLab/edgezero#N`); and the +body holds only its requirement, acceptance and dependencies. +Expected: no gaps. diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md new file mode 100644 index 000000000..31d92bfef --- /dev/null +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -0,0 +1,508 @@ +# Server-side identity on every adapter: design + +Status: draft, in review in #1559. Read at Trusted Server main `598a85170` and +EdgeZero main `e11ea308` (crates at `v0.0.11`) on 2026-11-11 and 2026-11-12. +Epics: #1558 (Trusted Server) and IABTechLab/edgezero#468 (EdgeZero). Plan: +[server-side identity plan](../plans/2026-11-11-server-side-identity-all-adapters.md). + +## Goals + +1. Axum is deployable from main as a production service on AWS, behind + CloudFront and a load balancer. +2. Server-side identity works on every adapter whose platform can meet the + record contract in [Platform support](#platform-support), delivered in the + order Fastly, Axum, Cloudflare, Spin (DEC-4). An adapter that cannot meet + it rejects identity configuration at startup with a clear error and never + runs identity without a record. +3. SharedID is read, stored and passed to bidders the way Prebid defines it, + and shared state is passed through each adapter the right way. +4. Identity is written back as first-party cookies on the HTML document + response. + +Target deployment profile: a publisher that runs client-side Prebid and wants +identity without the auction orchestrator. Its pages pass through Trusted +Server, so cookies can be set on the document response, which Safari does not +cap. + +## Relationship to other work + +- This is a program-level design across Trusted Server and EdgeZero. It is not + the design that #1236 asks for. The AX requirements feed #1236's tickets. + #1236's own open choices (key rotation, backup and restore, which Fastly-only + features stay unsupported on Axum) stay with #1236. +- Each ticket's implementation PR carries its own design notes, and a plan + when it needs one. This design fixes the requirements, the platform support + matrix and the order. +- The EdgeZero cutover is #1506 (DEC-1). +- Trusted Server PRs #1342 to #1423, and #1448 stacked on them, are out of + scope (DEC-2). + +## Where things stand + +Two lines of code carry Axum today. + +- **Main.** Axum is a dev server. It binds loopback, reads config and secrets + from env vars only, takes the client IP from the socket peer, has no geo, + and stops on Ctrl-C only. Only Fastly has server-side identity. Axum, + Cloudflare and Spin build an `EcContext` for consent alone, with no record + store and no `ts-ec` cookie. They build a partner registry per request, for + `/_ts/admin/eids` only. +- **Axum RC line.** Tag `v0.1.1-rc-axum.202611.2` is the head of draft #1541 + (`7c6e68d94`). It adds production startup, the EC record in DynamoDB and + celld, CloudFront geo, a container image and AWS examples. It branches from + main at `2dd968aee` (2026-10-20), carries 74 commits that are not on main + and is 29 commits behind it. It pins EdgeZero tag + `v0.0.12-rc-axum.202611.1`, which is IABTechLab/edgezero#431 (marked DO NOT + MERGE). It has none of the provider stack: no server-side ID5 or LiveRamp, + no SharedID write-back and no mirror cookie. + +Capability by adapter on main, and the open PR that adds each elsewhere: + +| Capability | Fastly | Axum | Cloudflare | Spin | +| ----------------------------------- | -------------- | ------------------------------------- | -------------------------------- | -------------------------------- | +| EC minted and `ts-ec` set | main | #1536 (RC line) | none | none | +| `EcKvStore` backend for the record | main (KV, CAS) | #1536 DynamoDB, #1537 celld (RC line) | none | none | +| Identify | main | #1536 (RC line) | none | none | +| Batch sync, pull sync, admin lookup | main | admin lookup answers 501 locally | admin lookup answers 501 locally | admin lookup answers 501 locally | +| Auction EIDs from the record | main | #1536 (RC line) | none | none | +| Server-side providers | #1487 | refused at startup on #1487 | refused at startup on #1487 | refused at startup on #1487 | +| Trusted client IP and geo | main | #1528, #1532 (EdgeZero RC revisions) | `cf-connecting-ip`, country only | TCP peer, no geo | +| After-response work | main | none | none | none | +| Rate limiting | main (ERL) | none | none | none | +| First-party cookie write-back | main | RC line | none | none | + +EdgeZero main provides none of these for Axum: production startup, SIGTERM +drain, trusted client IP, AWS config and secret stores, DynamoDB, geo, or +concurrent outbound HTTP with deadlines. Open PRs IABTechLab/edgezero#275 +through IABTechLab/edgezero#439 add them. No EdgeZero PR adds a portable +after-response hook, conditional KV writes, a portable client-IP accessor, geo +on Fastly, Cloudflare or Spin, or a rate limiter. EdgeZero main has no geo +module; `ctx.geo()` exists only on the IABTechLab/edgezero#439 stack, and only +Axum fills it there. + +Main has no server-side providers. The single page-load write, the navigation +permit and the startup refusal of providers off Fastly exist only on #1487 and +the PRs stacked on it. + +### How the EC record write is split + +The record logic is shared. `KvIdentityGraph` in +`crates/trusted-server-core/src/ec/kv.rs` owns the compare-and-swap retry +loops, consent tombstones and entry validation for every adapter. On #1487 it +also owns the page-load write. An adapter supplies only the `EcKvStore` trait +in `crates/trusted-server-core/src/ec/kv_backend.rs`, which has five +operations: + +- a lookup that returns a generation marker +- a strongly consistent `key_exists` +- `insert` with `Add`, `Overwrite` or `IfGenerationMatch` +- a strongly consistent prefix listing +- delete + +Each backend is written by hand because EdgeZero's `KvStore` gives none of +these guarantees. The Fastly backend calls the Fastly SDK directly +(`crates/trusted-server-adapter-fastly/src/ec_kv.rs`). The RC line adds DynamoDB +and celld backends inside the Axum adapter. EdgeZero's Fastly store drops what +the record needs: at EdgeZero main and at IABTechLab/edgezero#275's head its +read calls `lookup` without the generation, `exists` is a read, and inserts +carry no mode. So the Fastly backend can move onto EdgeZero only once EZI-2a +covers the Fastly provider. + +The target is one `EcKvStore` implementation in core over EdgeZero's +`KvStore` (DEC-3). The per-adapter backends are then deleted, and the DynamoDB +and celld conditional logic lives in EdgeZero's providers. + +## Platform support + +Identity runs on an adapter only where the record contract and geo are +supported. Elsewhere, startup rejects identity configuration (ID-2). Delivery +follows DEC-4: Fastly, Axum, Cloudflare, Spin. + +| Capability | Fastly | Axum | Cloudflare | Spin | +| ----------------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations, exposed through EdgeZero by EZI-2a | Supported. DynamoDB or celld through EZI-2b | Unsupported today. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | Unsupported today. `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing. Its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | +| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | +| Rate limiter | Supported (Edge Rate Limiting) | EZI-5, in-memory per task, shared counter with more than one task (DEC-11) | EZI-5, backend per DEC-11 | EZI-5, backend per DEC-11 | +| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | +| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | + +Cloudflare and Spin move to Supported only when EZI-2 and EZI-4 deliver for +them (DEC-8). Until then their startup rejects identity configuration, and +Goal 2 is met for them by that rejection. + +## Requirements + +Each requirement states behavior, then `Accept:` with the check that proves +it. "Supported adapter" means one where [Platform support](#platform-support) +marks the record contract and geo as supported. + +### AX: Axum deployable + +- **AX-1 Production serving.** The service binds a configured address, exits + non-zero before listening when config, secrets or a required store fails to + load, and on SIGTERM stops accepting, finishes in-flight requests and + after-response tasks, and exits within a configured grace shorter than the + ECS stop timeout. Accept: an ECS-style stop during a request with a pending + record write completes the request and the write, and CI runs that test; + startup with a missing secret exits non-zero before the port opens. State: + the RC line has startup, SIGTERM and connection drain, and + `scripts/test-native-ec-image.mjs` from #1536 passes locally but not in CI. + Draining after-response tasks needs EZI-1; IABTechLab/edgezero#406 drains + connections only. +- **AX-2 Managed config and secrets.** Config and secrets load from AWS stores + through EdgeZero store providers. Environment overrides use the full + `TRUSTED_SERVER__` prefix only. Accept: an Axum startup test loads config + and secrets through the providers in IABTechLab/edgezero#414, and no runtime + code reads a `TS_*` or `TRUSTED_SERVER_CONFIG_*` / `TRUSTED_SERVER_SECRET_*` + name. State: main reads `TRUSTED_SERVER_CONFIG_*` and + `TRUSTED_SERVER_SECRET_*`, which #1528 removes; the RC adds + `TS_NATIVE_GEO_PROFILE_FILE`, `TS_NATIVE_BINDINGS_TOML` and + `TS_APPCONFIG_TOKEN`. +- **AX-3 Trusted IP and geo.** The client IP comes only from trusted CloudFront + and load balancer hops, IPv4 and IPv6. Country and US region come from + CloudFront headers only from a trusted hop. The reference deployment enables + geo, because an unknown country fails closed and issues no EC. Accept: an + untrusted peer with forged forwarded and geo headers gets its own IP and no + geo; a trusted IPv6 CloudFront hop is accepted; the reference deployment + applies with geo on. State: IABTechLab/edgezero#409, IABTechLab/edgezero#439 and #1532 accept + IPv6; the reference Terraform in #1540 trusts IPv4 ranges only and leaves + geo off. +- **AX-4 Record store fit for production.** Writes are spread across partition + keys, with no item every insert updates, and retries are bounded. Accept: a + load test at the target page-load rate with provider refreshes (A5 sets the + rate) shows no throttling. State: #1536 puts every row under one partition + key and updates one allocator item per insert. +- **AX-5 Published production image.** The published, attested image runs the + production server. Accept: `gh attestation verify` passes for the image, and + its entrypoint starts the production server with `STOPSIGNAL SIGTERM`. State: + #1543 publishes the dev-server image (`STOPSIGNAL SIGINT`); #1539 builds the + production image without publishing it. +- **AX-6 Reference infrastructure.** The reference stack wires the DynamoDB + table, port 443 egress for vendor APIs, a readiness check that fails when a + dependency is down, JSON logs and two or more tasks. IABTechLab/edgezero#275 serves + Axum on a current-thread runtime, so capacity comes from task count. Accept: + the stack applies with those properties, and readiness fails with DynamoDB + unreachable. State: #1540 has the 443 egress (IPv4 only), a static `/health`, + plain logs, one task and no DynamoDB wiring. +- **AX-7 Ships from main.** Main never pins an EdgeZero RC tag + (IABTechLab/edgezero#431, IABTechLab/edgezero#462) or a revision that exists only on an RC + branch, such as `456313675`, which #1528 pins today. Main may pin + IABTechLab/edgezero#275's head under DEC-1; other unmerged revisions need a + recorded decision (DEC-5). Accept: `Cargo.lock` on main resolves every + EdgeZero crate to a released tag or to the revision DEC-1 or DEC-5 allows. + +### ID: Server-side identity on every adapter + +- **ID-1 One EC lifecycle.** On every supported adapter a consented document + navigation mints the EC, creates the record and sets `ts-ec` together. A + cookie is never issued without a record. An existing EC is kept when geo is + unknown. Accept: the conformance suite (T5) asserts on each supported + adapter that a consented navigation creates the record and sets `ts-ec` in + the same response, that a store failure sets no `ts-ec`, and that an + existing EC survives unknown geo; on an unsupported adapter, startup with + identity configuration fails. +- **ID-2 One shared record store.** The identity logic stays in core and + becomes awaitable (#1526). It runs on one `EcKvStore` over EdgeZero's + `KvStore` (DEC-3). A store that cannot meet the contract makes startup + reject identity configuration with a clear error. Accept: no adapter crate's + non-test code implements `EcKvStore`, and a store reporting + `KvError::Unsupported` for the contract fails startup with identity enabled. +- **ID-3 Partner APIs answered locally.** Identify, batch sync, pull sync and + admin lookup run on every adapter with a record store. Without one, identify + and batch sync return a local error and never reach the publisher origin. + Admin lookup already answers 501 locally; pull sync is outbound only. Accept: + in each adapter's tests a capturing origin receives no identify or batch-sync + request; with a store they run, without one they return 501. +- **ID-4 Providers on every supported adapter.** Lookups start at navigation + alongside the origin fetch, share one deadline, and are awaited before + finalization, so their IDs reach the record and the cookies of the same + document response. A page load writes the record once. Accept: a mock + provider that answers within the deadline has its ID in the record and in + the mirror cookie of that navigation's response; one that misses the + deadline is skipped and does not delay the response past it. +- **ID-5 After-response work.** Only pull sync and guarded follow-up writes run + after the response is sent: after send on Fastly, a task drained on SIGTERM + on Axum, `waitUntil` on Cloudflare. Spin follows DEC-14. Accept: on each + adapter a follow-up write recorded after send is in the record before the + next navigation; on Axum SIGTERM waits for it (A3). +- **ID-6 Rate limiting.** Provider lookups, batch sync, pull sync and record + creation are rate limited on every supported adapter. Batch sync is keyed + per partner; the other keys follow DEC-11. With more than one Axum task the + counter is shared. Accept: per-adapter tests hit each limit and see the + limited request refused or skipped, and two Axum tasks share one counter. + State: Fastly Edge Rate Limiting is the only limiter; record creation is + limited nowhere; #1487 keys lookups on the IP-derived EC prefix, so everyone + behind one IPv4 address or IPv6 /64 shares a budget. +- **ID-7 Abuse resistance.** Record creation is refused for clients that are + not browsers, on every supported adapter. Accept: a scripted client with a + browser User-Agent and no browser TLS fingerprint creates no record. State: + Fastly checks only that a JA4 exists plus a browser User-Agent platform; the + RC's Axum checks only the User-Agent, with request filters; Cloudflare and + Spin create no records yet. +- **ID-8 Body EIDs keep being saved.** EIDs sent in the `/auction` body are + saved to the record. Accept: on each supported adapter a body EID is in the + record after the request. State: main saves them on Fastly; the provider + stack and the RC's Axum lifecycle do not. + +### SH: Shared state passed the right way + +- **SH-1 Built once.** Settings, the partner registry, store clients and the + HTTP client are built once per process or isolate. Accept: an adapter test + shows each built once across two requests. State: Cloudflare and Spin + rebuild app state per request; Axum builds the partner registry per request + (`app.rs:515` at #1506's head) and the HTTP client per request on purpose, + because a pooled client regressed a Next.js flow (main `platform.rs:571-575`). + Keep that client choice unless a measurement shows the regression gone. +- **SH-2 Per-request state in the request.** Per-request values travel in the + request context, never in a `thread_local` or in retained app state. Accept: + no `thread_local!` or atomic in app state holds a per-request value. State: + #1487 (and #1488, #1489 on it) keeps lookups in a `thread_local`; #1527 and + #1528 keep a per-request `AtomicBool` in the Fastly `AppState`. +- **SH-3 No executor nesting.** No `block_on` runs inside async code. Accept: + the remaining `block_on` calls are only at adapter entry points. State: #1526 + wraps finalize in `block_on` (Fastly `main.rs:550`) and #1487 calls + `block_on` inside finalize (`main.rs:699`), which panic together. Core pull + sync and Cloudflare and Spin config reads also call `block_on` on main; + #1526, #1506 and the RC remove the pull-sync one, RC commit `933de5921` + removes Spin's, and Cloudflare's remains everywhere. +- **SH-4 No first-request capture.** Cloudflare does not keep the first + request's `Env` in a set-once `thread_local`. Accept: two requests with + different `Env` values each see their own. + +### SID: SharedID handled the way Prebid defines it + +- **SID-1 Cookie name from config.** The server reads SharedID from the cookie + named by the publisher's Prebid `storage.name`, held in + `managed_user_ids[].storage.name`. Prebid has no default; `_sharedid` and + `_pubcid` are common. Accept: with `storage.name = "_pubcid"` a `_pubcid` + cookie is read on every supported adapter, and a cookie named `sharedId` is + not. State: main reads only `sharedId` (`constants.rs:10`), Prebid's module + name, on Fastly for the record and on every adapter for `/_ts/admin/eids`. +- **SID-2 Opt-out.** A `_pubcid_optout` cookie stops SharedID reads, storage, + sending and write-back. Prebid also reads the opt-out from localStorage, + which the server cannot see. Accept: with `_pubcid_optout` present nothing + SharedID-related is read, stored, sent or written. +- **SID-3 One canonical EID.** SharedID is stored and sent as source + `pubcid.org`, atype 1, once. Accept: a bidder request built from cookie, + `ts-eids`, body and record that carry the same UUID has exactly one EID + `{source: "pubcid.org", atype: 1}`. State: a `pubcid.org` partner already + stores and sends the right EID from `ts-eids` and the body; the `sharedId` + cookie path is hard-wired to `sharedid.org` (`ec/prebid_eids.rs:241`); + browser-written records default to atype 3; the outbound merge dedupes only + within one source. +- **SID-4 OpenRTB placement.** EIDs go in `user.eids` for OpenRTB 2.6, and in + `user.ext.eids` only for a named downstream that needs it. Accept: the + bidder, Prebid Server and APS requests carry `user.eids`, and every test + that asserts EIDs are absent checks both fields. State: every builder writes + only `user.ext.eids` (`auction/openrtb.rs:372`, + `integrations/prebid.rs:2882`, `integrations/aps.rs:1131`). +- **SID-5 Retention.** The record keeps a SharedID for as long as the record + lives, and the stored value wins over a different browser value (DEC-7). + Accept: a stored SharedID survives a request carrying a different cookie + value. State: main does this on Fastly (`ec/kv.rs:134-174`); #1488 keeps a + cookie-read SharedID for 24 hours and lets the cookie replace it. +- **SID-6 EID consent.** Reading SharedID into the record, storing it, sending + it and writing it back each need EID permission (TCF Purposes 1 and 4, + through the consent module's EID predicate in `consent/mod.rs`), in + addition to EC permission and the SID-2 opt-out. EC permission alone + (`consent_allows_ec`, Purpose 1) is not enough. Where consent is required + but missing, permission is denied. Accept: on each supported adapter, with a + populated record, an absent or different browser cookie, Purpose 1 granted + and Purpose 4 denied, no SharedID is read, sent or written back. + +### WB: First-party write-back + +- **WB-1 Document response only.** Identity cookies are set on top-level HTML + document responses, on every supported adapter. Accept: identity + `Set-Cookie` headers appear on navigation responses and never on asset or + API responses. State: main mints `ts-ec` on Fastly navigations, and the RC + adds Axum. +- **WB-2 `ts-ec` lifetime.** `ts-ec` keeps + `Domain=.; Path=/; Secure; SameSite=Lax; HttpOnly; Max-Age=31536000`. + Whether returning readers get a refresh is DEC-13. Accept: once DEC-13 is + decided, its rule holds and a returning reader whose cookie needs no refresh + gets no `Set-Cookie`. State: set only for a new or rotated EC. +- **WB-3 SharedID cookie.** When SID-2 and SID-6 allow it, the server sets the + SharedID cookie from the record if the request has none or a different + value. It uses Prebid's storage name and the Domain Prebid writes with, is + not HttpOnly, and is `Secure; SameSite=Lax` for one year. It keeps Prebid's + `_cst` companion consistent, since a missing or different one makes + Prebid rewrite the cookie from script, which Safari caps. Prebid's `extend` + with a `pixelUrl` keeps working alongside it. Accept: record present and + cookie absent gives both cookies with those attributes; an equal cookie + gives no `Set-Cookie`; Purpose 1 denied or `_pubcid_optout` expires both; + Purpose 4 denied with Purpose 1 granted gives no `Set-Cookie` and leaves the + browser's cookie alone. State: no branch writes it. +- **WB-4 Mirror cookie.** `__Host-ts-eids-mirror` as in #1489: opt-in per + partner (DEC-10), dropped per vendor when TCF consent is denied, needs + `bidstream_enabled` and Purpose 4, 3,584-byte budget, host-only. Every + mirrored ID expires within 30 days. Accept: an ID written with no stated + expiry is mirrored with an expiry of at most 30 days. State: in #1489 such an + ID never expires (`Expiry::Never`); closed #1461 capped mirrored IDs at 24 + hours. +- **WB-5 Cacheable pages.** An unchanged cookie gets no `Set-Cookie`, and a + response carrying `Set-Cookie` is not stored in a shared cache. Accept: a + returning reader with unchanged cookies gets no `Set-Cookie`, and the + template-cache deny list uses the configured SharedID name. State: main + enforces the cache rule on every adapter (`response_privacy.rs:122-148`) and + does not re-set `ts-ec`, but re-sets the `ts-ec-pull-complete` marker about + hourly, which #1489 retires; the deny list names only `sharedId`. +- **WB-6 Identity-only page script.** A tsjs mode captures Prebid's IDs without + the auction shim, reads the mirror cookie and merges the IDs into Prebid. + Accept: in that mode Prebid receives the mirrored IDs and no Trusted Server + bidder is added to ad units. State: the `ts-eids` capture runs only inside + the shim, and nothing reads the mirror. + +### EZI: asks of EdgeZero + +Behavior and acceptance only. EZI IDs are distinct from #1506's EZ-n +requirement IDs. Each ask is delivered for Fastly and Axum first, then +Cloudflare, then Spin (DEC-4). + +- **EZI-1 After-response work.** A task registered during a request runs after + the response is delivered, bounded in time. Accept: on Fastly, Axum and + Cloudflare the task runs after delivery; on Axum SIGTERM waits for + registered tasks up to the grace; Spin returns a capability error. +- **EZI-2a Record contract and the Fastly provider.** `KvStore` gains a read + that returns a generation marker, writes in `Add`, `Overwrite` and + `IfGenerationMatch` modes with a precondition-failed outcome, and strongly + consistent existence and prefix listing, or `KvError::Unsupported`. Accept: + an EdgeZero contract suite passes against the Fastly provider under Viceroy. +- **EZI-2b DynamoDB and celld providers.** Both meet the EZI-2a contract. + DynamoDB writes are spread across partition keys with no item every insert + updates. Accept: the contract suite passes against both. State: IABTechLab/edgezero#463's + `CelldRecordStore` has the semantics on a separate API; IABTechLab/edgezero#414's DynamoDB + provider has strong existence and listing but no CAS; + IABTechLab/edgezero#410 and IABTechLab/edgezero#437 rule out conditional writes today. +- **EZI-3 Trusted client IP.** One accessor returns the trusted client IP on + every adapter. Accept: the same accessor works on all four adapters. +- **EZI-4 Geo with region.** `ctx.geo()` gives country and region on Fastly, + Cloudflare and Spin, or a capability error. Accept: a US request on Fastly + and Cloudflare reports country and region. +- **EZI-5 Rate limiter.** A limiter with Fastly Edge Rate Limiting, in-memory + and shared-counter backends. Accept: a contract test passes per backend. +- **EZI-6 Releases.** R1 contains IABTechLab/edgezero#275 through IABTechLab/edgezero#439 plus + EZI-2a and EZI-2b. R2 contains EZI-1 and EZI-5 for Fastly and Axum, and + EZI-3. R3 contains the Cloudflare parts of EZI-1, EZI-2, EZI-4 and EZI-5. R4 + contains the Spin parts. Accept: each tag exists and Trusted Server main pins + it. + +## Configuration + +- The SharedID cookie name comes from `managed_user_ids[].storage.name`; no + new setting (SID-1). +- The existing EC settings enable identity; startup rejects them where + identity is unsupported (ID-2). +- Provider `rate_limit_per_hour` and `mirror_enabled` from #1487 and #1489 + follow DEC-11 and DEC-10. +- The reference deployment turns geo on (AX-3). +- Environment overrides use the `TRUSTED_SERVER__` prefix only (AX-2). +- Record stores are EdgeZero logical stores in `edgezero.toml`, with no + adapter-local store setting (ID-2). + +## Consent and privacy + +- Creating an EC needs TCF Purpose 1 (`consent_allows_ec`). EIDs, SharedID + included, need Purposes 1 and 4 (SID-6). +- An unknown jurisdiction blocks new ECs (fail closed) and keeps an existing + one. +- The mirror cookie is readable by page scripts, so it is opt-in per partner + and filtered per vendor (WB-4, DEC-10). +- The `_pubcid_optout` cookie is honored; a localStorage opt-out is invisible + to the server (SID-2). + +## Failure handling + +- A store that fails or keeps failing a precondition after bounded retries + means no new EC and no `ts-ec` on that response; an existing `ts-ec` stays + (ID-1). +- A provider that times out, errors or is rate limited is skipped and never + delays the response past the deadline (ID-4). +- A failed after-response task is logged and retried on the next navigation, + and never holds shutdown past the grace (AX-1, ID-5). +- An unsupported capability fails startup with an error that names the + capability and the adapter (ID-2). + +## Compatibility and rollout + +- Fastly records holding a SharedID under `sharedid.org` are re-filed under + `pubcid.org`, atype 1, on their next write (T3). There is no bulk migration. +- `user.ext.eids` stays only for the downstreams T2 names. +- `ts-ec` format and attributes do not change. +- Adapters gain identity in the order Fastly, Axum, Cloudflare, Spin. Until an + adapter's tickets land, its startup rejects identity configuration. +- The RC tag keeps its current use until main reaches parity; RC units close + as their ports land. + +## Out of scope + +- A narrow install with one path rule and no page proxy. It needs an EC minted + on a request that is not a navigation. +- Conformance with the SharedID Edge specification. +- An Akamai adapter. +- Trusted Server PRs #1342 to #1423 and #1448 (DEC-2). + +## Decisions + +Decided: + +- **DEC-1 Cutover** (user, 2026-11-11). #1506 is the EdgeZero cutover. #1527 + closes once #1506 covers its tests. +- **DEC-2 Scope** (user, 2026-11-11). Trusted Server PRs #1342 to #1423, and + #1448 stacked on them, are out of scope. +- **DEC-3 Shared record store** (user, 2026-11-11). The EC record runs on one + `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. +- **DEC-4 Platform order** (user, 2026-11-12). Fastly, then Axum, then + Cloudflare, then Spin. + +Proposed and assumed by the plan until confirmed: + +- **DEC-5 EdgeZero pin for the Axum production stack.** Wait for release R1 + (proposed), or follow the stack's head under AX-7. Blocks A1. +- **DEC-6 Base and store path for Axum.** Forward-port the RC line to main and + wait for EZI-2b (proposed), rather than back-port the provider stack to the + RC line or port #1536's adapter-local store first. Applying each provider + stack to the RC tag from its main fork point conflicts in 13 to 23 files. + Blocks A1, A2, A4, A6, A7. +- **DEC-7 SharedID retention.** The stored value wins and lives as long as the + record (SID-5, proposed). Open: whether the server generates a SharedID when + none exists. Blocks T13 and the #1488 retention item. + +Open: + +- **DEC-8 Cloudflare and Spin store.** Which EdgeZero store meets the record + contract there, per [Platform support](#platform-support). Blocks T8 and T9. +- **DEC-9 LiveRamp Custom ID.** The current value is the IP-derived EC prefix, + shared by everyone behind one IP. Blocks enabling LiveRamp in #1488. +- **DEC-10 Mirror partners.** Which partners get `mirror_enabled`. Blocks T14. +- **DEC-11 Rate limiter key and backend, and the Axum task count.** Blocks T10 + and A7. +- **DEC-12 EC EID source.** Which source name the EC uses, and whether it keeps + going out as `user.id`. No ticket until decided. +- **DEC-13 `ts-ec` refresh** for returning readers. Blocks T16. +- **DEC-14 Spin after-response work.** Run it before the response, or skip it. + Blocks T9. + +## How the claims were checked + +Every state and "not implemented" claim comes from reading code at named +revisions, not from PR descriptions: + +- Code: `git grep` and `git show` across Trusted Server main, every open PR + head in both repositories, the RC tags, branches with no open PR, and PRs + merged or closed since 2026-10-01. +- Overlaps and conflicts: `git diff --name-only` between heads and + `git merge-tree --write-tree` trial merges. +- PR and issue state: `gh pr view` and `gh api graphql`, read on the dates in + the status line. +- The full CI gate list ran at this PR's first head, and the docs gates at + each later head; results are in the review threads. + +## Review log + +### Round 1, 2026-11-12 + +Reviews 5871533635 and 5871619954 at `00df85414`, from fresh agent sessions +covering factual claims, consistency and coverage, merge-order feasibility, and +repository policy. 34 findings: 8 blocking, 17 should-fix, 9 nits. This +revision addresses them; each thread holds its disposition. Round 2 is pending. diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-requirements.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-requirements.md deleted file mode 100644 index b292bc7eb..000000000 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-requirements.md +++ /dev/null @@ -1,468 +0,0 @@ -# Server-side identity on every adapter: requirements and PR sequence - -Status: draft. Read at Trusted Server main `598a85170` and EdgeZero main -`e11ea308` (crates at `v0.0.11`) on 2026-11-11. - -## Goals - -1. Axum is deployable as a production service on AWS, behind CloudFront and an - ALB. -2. Server-side identity works on every adapter: Fastly, Axum, Cloudflare and - Spin. -3. SharedID is read, stored and passed to bidders the way Prebid defines it, - and shared state is passed through each adapter the right way. -4. Identity is written back as first-party cookies on the HTML document - response. - -The first Axum publisher runs client-side Prebid and wants identity without the -auction orchestrator. Its pages pass through Trusted Server, so cookies can be -set on the document response, which Safari does not cap. - -## Where things stand - -Two lines of code carry Axum today. - -- **Main.** Axum is a dev server. It binds loopback, reads config and secrets - from env vars only, takes the client IP from the socket peer, has no geo, - and stops on Ctrl-C only. Only Fastly has server-side identity. Axum, - Cloudflare and Spin build an `EcContext` for consent alone: no store, no - `ts-ec` cookie, no partner registry. -- **Axum RC line.** Tag `v0.1.1-rc-axum.202611.2` is the head of draft #1541 - (`7c6e68d94`) and is the source of the deployed demo. It adds production - startup, the EC record in DynamoDB and celld, CloudFront geo, a container - image and AWS examples. It branches from main at `2dd968aee` (2026-10-20). - It carries 74 commits that are not on main and is 29 commits behind it. It - pins EdgeZero tag `v0.0.12-rc-axum.202611.1`, which is EdgeZero #431 ("DO - NOT MERGE"). It has none of the provider stack, so there is no server-side - ID5 or LiveRamp, no SharedID write-back and no mirror cookie. - -Capability by adapter on main, and the open PR that adds each capability -elsewhere: - -| Capability | Fastly | Axum | Cloudflare | Spin | -| ----------------------------------- | -------------- | ------------------------------------- | -------------------------------- | -------------------------------- | -| EC minted and `ts-ec` set | main | #1536 (RC line) | none | none | -| `EcKvStore` backend for the record | main (KV, CAS) | #1536 DynamoDB, #1537 celld (RC line) | none | none | -| Identify | main | #1536 (RC line) | none | none | -| Batch sync, pull sync, admin lookup | main | admin lookup answers 501 locally | admin lookup answers 501 locally | admin lookup answers 501 locally | -| Auction EIDs from the record | main | #1536 (RC line) | none | none | -| Server-side providers | #1487 | refused at startup on #1487 | refused at startup on #1487 | refused at startup on #1487 | -| Trusted client IP and geo | main | #1528, #1532 (EdgeZero RC only) | `cf-connecting-ip`, country only | TCP peer, no geo | -| After-response work | main | none | none | none | -| Rate limiting | main (ERL) | none | none | none | -| First-party cookie write-back | main | RC line | none | none | - -EdgeZero main provides none of these for Axum: production startup, SIGTERM -drain, trusted client IP, AWS config and secret stores, DynamoDB, geo, or -concurrent outbound HTTP with deadlines. Open PRs #275 through #439 add them -(see the sequence below). No EdgeZero PR adds a portable after-response hook, -conditional KV writes, a portable client-IP accessor, geo on Fastly, -Cloudflare or Spin, or a rate limiter. The `KvStore` contract on EdgeZero main -has get, put, delete, exists and list only. EdgeZero main has no geo module; -`ctx.geo()` exists only on the #439 stack, and only Axum fills it there. - -Main has no server-side providers. The single page-load write model, the -navigation permit and the startup refusal of providers off Fastly all exist -only on #1487 and the PRs stacked on it. Axum, Cloudflare and Spin do build a -partner registry, per request, for `/_ts/admin/eids`. - -### How the EC record write is split - -The record logic is already shared. `KvIdentityGraph` in -`crates/trusted-server-core/src/ec/kv.rs` owns the compare-and-swap retry -loops, consent tombstones, entry validation and the page-load write for every -adapter. An adapter supplies only the narrow `EcKvStore` trait in -`crates/trusted-server-core/src/ec/kv_backend.rs`. It has five operations: - -- a lookup that returns a generation marker -- a strongly consistent `key_exists` -- `insert` with `Add`, `Overwrite` or `IfGenerationMatch` -- a strongly consistent prefix listing -- delete - -Each backend is written by hand because EdgeZero's `KvStore` gives none of -these guarantees. The Fastly backend calls the Fastly SDK directly -(`crates/trusted-server-adapter-fastly/src/ec_kv.rs`). The RC line adds -DynamoDB and celld backends inside the Axum adapter (`ec_kv.rs`, -`celld_ec.rs`). Cloudflare already reaches EdgeZero's `KvStore` for generic -runtime KV, but that store cannot back the EC record. - -The target is one `EcKvStore` implementation in core, written over EdgeZero's -`KvStore` once EdgeZero offers conditional writes and strong reads (EZ-2). The -per-adapter backends are then deleted. The DynamoDB and celld conditional -logic belongs in EdgeZero's store providers, not in the Trusted Server Axum -adapter. - -Platform limits to confirm in the EZ-2 design: Cloudflare Workers KV is -eventually consistent and has no compare-and-swap, so a Cloudflare backend -needs a strongly consistent store such as Durable Objects or D1. Spin's -key-value interface needs checking for compare-and-swap. - -## Requirements - -Each requirement states behavior and how to accept it. The PR sequence below -maps them to work. - -### AX: Axum deployable - -- **AX-1 Production serving.** The service binds a configured address. It - exits non-zero before listening when config, secrets or a required store - fails to load. On SIGTERM it stops accepting, finishes in-flight requests and - after-response tasks, and exits within a configured grace shorter than the - ECS stop timeout. Accept: an ECS task stop during a request completes the - request and its record write, and CI runs that test. The RC line has the - startup, SIGTERM and drain parts, and the test passes locally - (`scripts/test-native-ec-image.mjs` from #1536), but CI does not run it. - Draining after-response tasks needs EZ-1; EdgeZero #406 drains connections - only. -- **AX-2 Managed config and secrets.** Config and secrets load from AWS stores - through EdgeZero store providers. Environment overrides use the full - `TRUSTED_SERVER__` prefix. Main's `TRUSTED_SERVER_CONFIG_*` and - `TRUSTED_SERVER_SECRET_*` names break this rule, and #1528 removes them. The - RC line adds `TS_NATIVE_GEO_PROFILE_FILE`, `TS_NATIVE_BINDINGS_TOML` and - `TS_APPCONFIG_TOKEN`, which must not be ported. -- **AX-3 Trusted IP and geo.** The client IP is taken only from trusted - CloudFront and ALB hops, for IPv4 and IPv6 ranges. Country and US region come - from CloudFront headers only when the hop is trusted. The reference - deployment enables geo, because an unknown country fails closed and issues no - EC. The code in EdgeZero #409 and #439 and Trusted Server #1532 already - accepts IPv6. The reference Terraform in #1540 trusts IPv4 ranges only and - leaves geo off by default. -- **AX-4 Record store fit for production.** EC records support create-if-absent - and a generation compare-and-swap. Writes do not all hit one item or one - partition key. Retries are bounded. Accept: a load test at the target - page-load rate with provider refreshes shows no throttling. -- **AX-5 Published production image.** The published, attested image runs the - production server, not the dev server. -- **AX-6 Reference infrastructure.** It wires the DynamoDB table, port 443 - egress for vendor APIs, a readiness check that tests dependencies, JSON logs, - and more than one task. EdgeZero #275 runs Axum on a current-thread runtime, - so a process uses one core and capacity comes from task count. #1540 already - has the 443 egress (IPv4 only). Its `/health` is static, logs are plain text, - it runs one task, and the DynamoDB module is not wired in. -- **AX-7 Ships from main.** The deployed build comes from main. It never pins - an EdgeZero RC tag (#431, #462) or a revision that exists only on an RC - branch, such as `456313675`, which #1528 pins today. Main may pin PR #275's - head, as #1506's maintainer answers allow. Pinning any other unmerged - EdgeZero revision needs its own decision. The end state is a released - EdgeZero tag. - -### ID: Server-side identity on every adapter - -- **ID-1 One EC lifecycle.** On every adapter a consented document navigation - mints the EC, creates the record and sets `ts-ec` together. A cookie is never - issued without a record. An existing EC is kept when geo is unknown. Accept: - one conformance suite runs against all four adapters. -- **ID-2 One shared record store.** The identity logic stays in core - (`KvIdentityGraph`) and becomes awaitable (#1526). It runs on one - `EcKvStore` implementation over EdgeZero's `KvStore`, not a backend per - adapter. Each platform's EdgeZero store provides a generation marker, - create-if-absent, compare-and-swap, and strongly consistent existence and - prefix reads (EZ-2). An adapter whose store cannot meet that contract - rejects identity config at startup with a clear error instead of running - without a record. Accept: no adapter crate implements `EcKvStore`. -- **ID-3 Partner APIs answered locally.** Identify, batch sync, pull sync and - admin lookup run on every adapter with a store. On an adapter without one, - identify and batch sync return a local error and never reach the publisher - origin. Admin lookup already answers 501 locally, and pull sync is outbound - only. -- **ID-4 Providers on every adapter with a store.** Lookups start at navigation - alongside the origin fetch, share one deadline, and are collected before - finalization. A page load writes the record once. Startup accepts an enabled - provider on every adapter that supports providers. -- **ID-5 After-response work.** Pull sync and guarded follow-up writes run after - the response is sent on every adapter: after send on Fastly, a task drained - on SIGTERM on Axum, `waitUntil` on Cloudflare. Spin has no mechanism; the - work either runs before the response there or is not offered. -- **ID-6 Rate limiting.** Provider lookups, batch sync, pull sync and record - creation are rate limited on every adapter. Fastly Edge Rate Limiting is the - only limiter on any branch, and record creation is limited nowhere. Batch - sync is keyed per partner. #1487 keys provider lookups on the IP-derived EC - prefix, so everyone behind one IPv4 address or IPv6 /64 shares one budget. - Choose that key on purpose. With more than one Axum task the counter must be - shared. -- **ID-7 Abuse resistance.** Record creation needs real protection on every - adapter. On Fastly the gate checks only that a JA4 fingerprint exists plus a - browser User-Agent platform, so any TLS client with a browser User-Agent - passes. The RC adds a User-Agent heuristic and request filters on Axum. - Cloudflare and Spin have nothing. -- **ID-8 Body EIDs keep being saved.** EIDs sent in the `/auction` body are - saved to the record, as on main (Fastly). The provider stack stops saving - them, and the RC's Axum lifecycle never saves them. - -### SH: Shared state passed the right way - -- **SH-1 Built once.** Settings, the partner registry, store clients and the - HTTP client are built once per process or isolate and shared. Today - Cloudflare and Spin rebuild app state per request, partner registries are - rebuilt several times per request, and Axum builds an HTTP client per - request. -- **SH-2 Per-request state in the request.** Per-request values travel in the - request context. They are never kept in a `thread_local` or in retained app - state. #1487, #1488 and #1489 keep lookups in a `thread_local`. #1527 and - #1528 keep a per-request `AtomicBool` in the Fastly `AppState`. Fastly - serves one request per sandbox, so the risk is a value carried into the next - request, not a race. -- **SH-3 No executor nesting.** No `block_on` runs inside async code. #1526 - wraps finalize in `block_on` (Fastly `main.rs:550`). #1487 calls `block_on` - inside finalize (`main.rs:699`). Combined unchanged, they panic. On main, - core pull sync and Cloudflare and Spin config reads also call `block_on`. - #1526, #1506 and the RC remove the pull-sync one, and the RC removes Spin's. - Cloudflare's remains on every branch. -- **SH-4 No first-request capture.** Cloudflare must not keep the first - request's `Env` in a set-once `thread_local`. - -### SID: SharedID handled the way Prebid defines it - -- **SID-1 Cookie name from config.** The server reads SharedID from the - cookie named by the publisher's Prebid `storage.name`, on every adapter. - Prebid has no default name; `_sharedid` and `_pubcid` are the common ones. - Trusted Server config already holds the name in - `managed_user_ids[].storage.name`, so use that rather than a second setting. - Main reads only a cookie named `sharedId` (`constants.rs:10`), which is - Prebid's module name, not a cookie name. It does so on Fastly for the - record, and on every adapter for `/_ts/admin/eids`. #1488 makes the name - configurable, on Fastly only. -- **SID-2 Opt-out.** A present `_pubcid_optout` cookie stops SharedID reads, - storage, sending and write-back. Prebid also reads the opt-out from - localStorage, which the server cannot see. #1488 honors the cookie for its - own provider only. -- **SID-3 One canonical EID.** SharedID is stored and sent as source - `pubcid.org` with atype 1. Main gets partway by configuration: a - `pubcid.org` partner with atype 1 already stores and sends the right EID - from `ts-eids` and the auction body. The `sharedId` cookie path is - hard-wired to `sharedid.org` (`ec/prebid_eids.rs:241`), and records written - from the browser default to atype 3. The outbound merge dedupes only within - one source. Deduplicate across cookie, `ts-eids`, auction body and record so - a bidder never sees the same ID twice. -- **SID-4 OpenRTB placement.** EIDs go in the field the declared protocol - defines: `user.eids` for OpenRTB 2.6. Every builder writes only - `user.ext.eids`: bidder OpenRTB (`auction/openrtb.rs:372`), Prebid Server - (`integrations/prebid.rs:2882`) and APS (`integrations/aps.rs:1131`), while - `openrtb-2.6` is the only accepted protocol. Keep `user.ext.eids` only where - a named downstream needs it. -- **SID-5 Retention.** The record keeps a SharedID for as long as the cookie is - meant to last. The stored value wins over a different browser value. Main - already does this on Fastly (`ec/kv.rs:134-174`). #1488 regresses it: it - keeps a cookie-read SharedID for 24 hours and lets the cookie replace the - stored value. That has to change before write-back can work. - -### WB: First-party write-back - -- **WB-1 Document response, every adapter.** Identity cookies are set on the - top-level HTML document response on every adapter. Main mints `ts-ec` on - navigations on Fastly only, and the RC adds Axum. The navigation permit - exists only in the provider stack, where only the Fastly adapter classifies - it. -- **WB-2 `ts-ec` lifetime.** `ts-ec` keeps `Domain=.; Path=/; Secure; -SameSite=Lax; HttpOnly; Max-Age=31536000`. Today it is set only for a new or - rotated EC, so an active reader's cookie expires a year after minting. - Decide whether returning readers get a refresh. -- **WB-3 SharedID cookie.** New; no code on any branch writes it, and #1488 - says so on purpose. Set from the record when the request has no SharedID - cookie or a different value. Use Prebid's storage name and the Domain Prebid - writes with. Not HttpOnly, Secure, `SameSite=Lax`, one year. Also keep - Prebid's `_cst` companion cookie consistent: when it is missing or - differs, Prebid rewrites the SharedID cookie from script on the next page, - and Safari caps that copy again. Expire both when consent does not allow the - EC or `_pubcid_optout` is present. Prebid's `extend` with a `pixelUrl` is - its own server-write path and must keep working alongside this. -- **WB-4 Mirror cookie.** `__Host-ts-eids-mirror` as in #1489: opt-in per - partner, dropped per vendor when TCF consent is denied, requires - `bidstream_enabled` and Purpose 4, 3,584-byte budget, host-only. Every - mirrored ID has a bounded expiry. In #1489 an ID with no stated expiry never - expires (`Expiry::Never`), and its pull-sync provider writes such IDs. - Closed #1461 capped mirrored IDs at 24 hours. -- **WB-5 Cacheable pages.** An unchanged cookie gets no `Set-Cookie`, so - returning readers' pages stay cacheable. A response that carries - `Set-Cookie` is not stored in a shared cache; main already enforces this on - every adapter (`response_privacy.rs:122-148`) and does not re-set `ts-ec`. - Main does re-set the `ts-ec-pull-complete` marker about hourly, which #1489 - retires. The template cache also bypasses any request with a Cookie header - unless the publisher configures otherwise, and its deny list names only - `sharedId`, so it must learn the configured SharedID name. -- **WB-6 Identity-only page script.** A tsjs mode captures Prebid's IDs without - the auction shim, reads the mirror cookie, and merges the IDs into Prebid. - Today the `ts-eids` capture runs only inside the shim, which also adds the - Trusted Server bidder to every ad unit. Nothing reads the mirror. - -### EZ: asks of EdgeZero - -These are behaviors Trusted Server needs from EdgeZero. No open EdgeZero PR -provides them. - -- **EZ-1** An after-response task API on all four adapters, drained on shutdown - on Axum. -- **EZ-2** `KvStore` gains what the EC record needs: reads that return a - generation marker, create-if-absent and generation compare-and-swap writes, - and strongly consistent existence checks and prefix listing. A store that - cannot meet these returns a capability error; `KvError::Unsupported` already - exists on main. #463's celld `CelldRecordStore` has all of these semantics on - a separate API, and #414's DynamoDB provider has strong existence and - listing but no compare-and-swap. The portable `KvStore` gains none of them - on any branch, and #410 and #437 rule them out today. -- **EZ-3** A portable trusted client-IP accessor. -- **EZ-4** Geo through `ctx.geo()` on Fastly, Cloudflare and Spin, with region. -- **EZ-5** A rate limiter with Fastly ERL, in-memory and shared-counter - backends. -- **EZ-6** A released tag containing the Axum production stack (#275 through - #439). - -## Out of scope - -- A narrow install with one path rule and no page proxy. It needs an EC minted - on a request that is not a navigation. -- Conformance with the SharedID Edge specification. -- An Akamai adapter. -- The open #1342 to #1423 PR stack and PRs stacked on it, such as #1448. This - plan neither depends on it nor sequences it. - -## PR sequence - -The order lands the async store and the EdgeZero cutover on main first, then -identity on top, then the EdgeZero production stack, then forward-ports of the -RC line, then new work. Each step ends in a state that can be checked. - -### Step 0: decisions and cleanup - -1. The cutover is #1506 (decided 2026-11-11). Close #1527 once #1506 has - equivalents of its native-client, response-owner and managed Fastly - delivery tests. #1528 is stacked on #1527, so it is rebased onto #1506. -2. Close #1310. #1488's ID5 provider supersedes it. -3. Drop the commits of #1545 (closed, unmerged) from #1541. - -### Step 1: Trusted Server main - -1. #1557 Sourcepoint consent. Already in the RC tag. -2. #1526 retargeted to main, with #1525 in parallel. #1526's own diff applies - cleanly to main without #1525. -3. #1516, which merges before #1506 as already agreed. -4. #1506, the EdgeZero cutover. Before merge it: - - finishes Tasks 10 to 12 - - rebases onto main with #1526 and #1516 (#1526 shares 7 of its files) - - repins to #275's head once #275 absorbs #460 (see the EdgeZero step). - That restores outbound cache tags, so #1506 can drop its rejection of - `origin_readthrough_enabled` (`config.rs:317`). It also picks up - `b97efec1`, which stops Spin sending `Content-Length: 0` on bodiless - DELETE and OPTIONS - - updates its description, which still says docs only - - Main then pins #275's head instead of `v0.0.11`. - -5. #1487 rebased onto #1526 and #1506 (13 and 23 shared files). Provider - lookups go through EdgeZero's `HttpClient`. Lookup collection becomes an - `.await` in async finalize (SH-3), and per-request lookups move into the - request (SH-2). -6. #1489, then #1488. They are siblings on #1487 and conflict in 11 files; the - second absorbs them. Vendor calls use EdgeZero's `HttpClient`. Fix the - mirror expiry (WB-4) and SharedID retention (SID-5) here. -7. New: partner routes answered locally on non-Fastly adapters (ID-3). -8. New: SharedID correctness on Fastly (SID-1 to SID-4). It removes the legacy - `sharedId` cookie and `sharedid.org` source path in the same change. -9. #1503 `ts ec show` and `ts ec eids`. -10. #1449, then #1543, image publishing. - -Landing #1506 before the provider stack means the ID5 and LiveRamp calls are -written against EdgeZero's outbound API from the start. #1506's answer to its -in-flight question allows the other order, but each PR that lands first adds -callers #1506 has to port. - -### Step 1, in parallel: EdgeZero - -1. Independent PRs, which need nothing else: #444, #466 (rebased on #444), - #452, #401, #389. -2. #275, after it absorbs the fixes to its own code that sit in later PRs: - - #460 (`251974d4`, `dc296073`): surrogate keys on `OutboundCachePolicy` - and a borrowed `poll_ready` on `OutboundPending`. #460 removes `Copy` - from a #275 public type, so it must land before #275 releases. Bring - the Cloudflare wasm clippy fix from RC-only `ebf622c2` with it. - - #406 `9f090e57`: the CLI template emits Fastly-only image code that - #275 gates behind `native_image_example`. - - One copy of the CI trigger change, so stacked PRs run CI. It exists - three times, in #406, #460 and #414. - - Optional: guard all five egress unwind sites against a panicking logger - (#409 `0769cbfd` covers three). - - Then fix placement inside the stack: - - #414 `9a985c99` (a flaky-test fix) moves to #407. - - #414 `4ea99467` (`run_production_app_with_options_and_initializer`, - which the RC's native startup calls) moves to #406. - - #463 rebases onto #422 without #439, #464 onto #439 without #454's - prototype, and #453 off its stale base. - - The RC-only merges that fit #389 (`7c07b8e7`) and #401 (`2510ecc9`, - `501a2dce`) onto #275 have to be redone in those PRs. - -3. #406, #407, #409. These give a deployable ECS service. -4. #414. -5. #422, #439, #464, #463. Retarget #463 first; it carries an old copy of - #439. If celld is not used in production, restack #439 on #414. -6. New: EZ-2 conditional KV on the DynamoDB and celld providers, generalizing - #463's celld-only record operations into `KvStore`. -7. Release a tag (EZ-6). File EZ-1 and EZ-3 to EZ-5 as issues. -8. #287 last. It shares 27 files with #275. - -Every ready EdgeZero PR currently has changes requested, and #275 has 20 of 95 -threads open. - -### Step 2: Trusted Server main on the EdgeZero production stack - -1. #1528 native startup, rebased onto #1506. It needs EdgeZero #406, #409 and - #414, which are stacked on #275 but are not in #275's head. -2. Forward-port the RC line as new PRs against main, closing each RC unit as - its port lands: - 1. New: one core `EcKvStore` over EdgeZero's `KvStore` (ID-2). Fastly - moves onto it and `adapter-fastly/src/ec_kv.rs` is deleted in the same - change. - 2. #1536 native EC lifecycle and #1537 storage selection, on that shared - backend. Their adapter-local DynamoDB and celld backends are not - ported. Spread writes across items and partitions (AX-4). #1537's - example config breaks startup on Axum, Cloudflare and Spin. - 3. #1532 CloudFront geo. It maps a US request with no region to `Unknown`, - which fails every US Cloudflare request closed. It does not trust IPv6 - CloudFront ranges. - 4. #1539 and #1538, the image, on one contract with #1543. - 5. #1540 AWS examples. -3. Close #1541, #1424 and #1499 once main reaches parity with the RC tag. - -### Step 3: new work - -1. Providers on Axum. Remove the startup refusal, register provider kinds, - classify the navigation permit, add the after-response task and a rate - limiter (ID-4 to ID-6). -2. SharedID write-back (WB-3). -3. Mirror on Axum. The adapter calls the page-load write (WB-4). -4. Identity-only tsjs mode (WB-6). -5. Partner APIs on Axum (ID-3). -6. Cloudflare and Spin: EdgeZero stores that meet EZ-2. The shared backend and - lifecycle then run unchanged (ID-1, ID-2). - -## Decisions - -Decided: - -- **Cutover** (2026-11-11). #1506. #1527 closes once its tests are covered. - -Open: - -1. **Base.** Forward-port to main, or back-port the provider stack to the RC - line for a faster first publisher. Back-porting meets 25 to 34 conflicts on - a branch that its own RC says will not merge. Recommendation: forward-port, - and keep serving the demo from the RC tag until main reaches parity. -2. **EdgeZero pin for the Axum production stack.** Wait for #406, #409 and - #414 to merge, or extend #1506's "follow #275's head" rule to that stack so - #1528 can land sooner. -3. **Store path for Axum.** Wait for EZ-2 and use the shared backend, or port - #1536's adapter-local DynamoDB backend first and delete it when EZ-2 lands. - Recommendation: wait, since the RC tag already serves the demo. -4. **Cloudflare and Spin store.** Which EdgeZero store meets EZ-2 there, or - whether identity is declared unsupported. -5. **LiveRamp Custom ID.** The current value is the IP-derived EC prefix, - shared by everyone behind one IP. -6. **SharedID retention and precedence** (SID-5), and whether the server - generates a SharedID when none exists. -7. **Mirror partners.** Which partners get `mirror_enabled`. Mirrored IDs are - readable by every script on the page. -8. **Rate limiter key and backend**, and the number of Axum tasks (ID-6, AX-6). -9. **EC EID source.** Which source name the EC uses, and whether it keeps - going out as `user.id`. -10. **`ts-ec` refresh** for returning readers (WB-2). From 202bd7930d471999008d77e0b97bc60315e08700 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:43:31 -0800 Subject: [PATCH 03/31] Order the identity plan by dependency waves without nesting epics Replace the phases with dependency waves that interleave Trusted Server and EdgeZero tickets, PR fixes, merges and releases. Add type, size, priority, blocked-by and blocks to every ticket, list the existing issues moved under each epic, keep the epics unnested, and add a task that links the blocked-by dependencies. --- ...11-11-server-side-identity-all-adapters.md | 451 +++++++++++------- 1 file changed, 289 insertions(+), 162 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 8fff4a0a1..37477c91e 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -2,19 +2,20 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** File the missing tickets under #1558, #1236 and -IABTechLab/edgezero#468, and tie every existing PR to them, in an order that -keeps main shippable at each step. +**Goal:** File the missing tickets, move the existing issues under their +epics (#1558, #1236 and IABTechLab/edgezero#468), link every dependency, and +tie every open PR to its ticket, in the wave order of Part A. -**Architecture:** This plan's tasks are GitHub operations, not code. Tasks 1 -to 4 create and link issues in dependency order, EdgeZero first because -Trusted Server tickets depend on it. Tasks 5 and 6 post the per-PR fixes in -Part C to the named PRs, and Task 7 checks coverage. Code lands later in each -ticket's own implementation PR, following the merge order in Part A, with its -own plan when it needs one. +**Architecture:** This plan's tasks are GitHub operations, not code. Task 1 +moves existing issues, Tasks 2 to 4 create the new tickets (EdgeZero first, +because Trusted Server tickets depend on it), Task 5 links every blocked-by +dependency, Tasks 6 and 7 post the per-PR fixes in Part C, and Task 8 checks +coverage. Code lands later in each ticket's own implementation PR, in Part A's +wave order, with its own plan when it needs one. -**Tech Stack:** `gh` CLI and GitHub GraphQL (issue types, sub-issues, project -items) against IABTechLab/trusted-server and IABTechLab/edgezero. +**Tech Stack:** `gh` CLI and GitHub GraphQL (issue types, sub-issues, +blocked-by links, project fields) against IABTechLab/trusted-server and +IABTechLab/edgezero. **Spec:** [server-side identity design](../specs/2026-11-11-server-side-identity-all-adapters-design.md) @@ -23,13 +24,23 @@ items) against IABTechLab/trusted-server and IABTechLab/edgezero. - One-account mode: every write acts as the PR author's login. - Platform order: Fastly, then Axum, then Cloudflare, then Spin (DEC-4). +- Epics are not nested. #1558 and #1236 stay separate epics that link through + their Related sections. IABTechLab/edgezero#89 stays under + IABTechLab/edgezero#88 and is linked from IABTechLab/edgezero#468. - Issue bodies state the requirement, its acceptance and its dependencies, and nothing about deployments or their state. - EdgeZero references are written `IABTechLab/edgezero#N`; a bare `#N` means Trusted Server. - EdgeZero issue bodies state behavior and acceptance only. -- Issue types are set through GraphQL: Feature `IT_kwDOBPEB8s4A35yA`, Task - `IT_kwDOBPEB8s4A35x7`. New issues join project `PVT_kwDOBPEB8s4BFKrl`. +- Issue types are set with `updateIssue`: Feature `IT_kwDOBPEB8s4A35yA`, Task + `IT_kwDOBPEB8s4A35x7`, Bug `IT_kwDOBPEB8s4A35x9`. +- Project `PVT_kwDOBPEB8s4BFKrl`. Priority field `PVTSSF_lADOBPEB8s4BFKrlzg22nQg` + (P0 `c92c6358`, P1 `dca9fbdd`, P2 `afab7bc8`, P3 `71861fe7`). Size field + `PVTSSF_lADOBPEB8s4BFKrlzg22m2Q` (XS `771e90ab`, S `01b13689`, M `084dacdb`, + L `762f39e7`, XL `27016a48`). +- Dependencies use `addBlockedBy(input:{issueId, blockingIssueId})`. GitHub + links issues only, so a PR or a decision in "Blocked by" goes in the issue + body. - An issue Part B names for reuse is reused, never duplicated. - EdgeZero code is never changed from here; EdgeZero items are requests. - Trusted Server PRs #1342 to #1423 and #1448 are out of scope (DEC-2). @@ -40,14 +51,14 @@ items) against IABTechLab/trusted-server and IABTechLab/edgezero. 1. A duplicate issue for work an existing issue already tracks. Every create step in Tasks 2 to 4 runs a search first. -2. A Part C item posted to a PR whose head has moved. Tasks 5 and 6 compare the - head with Part C before posting and re-check the item if it moved. -3. A ticket whose dependency has no issue number yet. Tasks run EdgeZero, - then #1236, then #1558, and Task 7 checks every "Depends on". -4. A bare `#N` meant for EdgeZero inside a Trusted Server issue. Task 7 scans +2. A Part C item posted to a PR whose head has moved. Tasks 6 and 7 compare + the head with Part C before posting and re-check the item if it moved. +3. A blocked-by link that GitHub refuses across repositories. Task 5 records + it in the issue body instead and lists it in its readback. +4. A bare `#N` meant for EdgeZero inside a Trusted Server issue. Task 8 scans the posted bodies. -5. A body that goes beyond its requirement, acceptance and dependencies. Task 7 - reads every body. +5. A body that goes beyond its requirement, acceptance and dependencies. Task + 8 reads every body. --- @@ -90,103 +101,180 @@ EdgeZero: | IABTechLab/edgezero#466 | `cb48c6b3` | main | Land after IABTechLab/edgezero#444; they conflict in `edgezero-cli` `config.rs` and `lib.rs` | | IABTechLab/edgezero#287 | `d590f0eb` | main | Last; shares 52 files with IABTechLab/edgezero#275, 27 of them under `crates/` | -### Phases - -Phase A runs now and needs no new EdgeZero. - -1. T1, partner routes answered locally. -2. #1557. -3. #1526 retargeted to main; #1525 in parallel. -4. #1516. -5. #1503 and #1449. -6. T2, `user.eids` placement, converting every EID-absence assertion. -7. EdgeZero: IABTechLab/edgezero#460, `9f090e57` and one CI trigger change go - into IABTechLab/edgezero#275's branch; the misplaced commits move; IABTechLab/edgezero#463, - IABTechLab/edgezero#464 and IABTechLab/edgezero#453 are rebased. - -Phase B follows Phase A. - -1. #1506, rebased and repinned to IABTechLab/edgezero#275's head after the - fold-in. Fallback: if the fold-in has not landed two weeks after Phase A - ends, #1506 merges pinned to IABTechLab/edgezero#275's head as it stands - (allowed by AX-7) and keeps its readthrough rejection until the repin. -2. #1487, rebased onto #1526 and #1506. -3. #1489, then #1488 restacked onto it. -4. T3 SharedID cleanup, T4 shared state, T5 conformance suite (Fastly first), - T15 identity-only tsjs. -5. EdgeZero: IABTechLab/edgezero#275 merges. Then IABTechLab/edgezero#389, - IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#466 and IABTechLab/edgezero#452 rebase onto main. Then EZI-2a. Then - IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#409, IABTechLab/edgezero#414, IABTechLab/edgezero#422, IABTechLab/edgezero#439, IABTechLab/edgezero#464 and IABTechLab/edgezero#463. Then - EZI-2b and release R1 (EZI-6). Then EZI-1 and EZI-5 for Fastly and Axum, - and EZI-3, in release R2. IABTechLab/edgezero#287 goes last. - -Phase C follows R1 and brings Axum to parity with the RC tag. - -1. Main repins to R1. -2. A1 (#1528 port) with A9. -3. T6, one record backend over EdgeZero; Fastly moves first. -4. A4 geo, then A2 EC lifecycle, then A8. -5. A6 image with #1543, then A7. -6. Close #1541, #1424, #1499, #1527 and the RC units as their ports land. - -Phase D follows R2, then R3, then R4, one platform at a time (DEC-4). - -1. Axum, after R2: A3, T10, T7, T11, T12, T13, T14, then A5. -2. Cloudflare, after R3: T8, then T10, T11, T12, T13 and T14 for Cloudflare. -3. Spin, after R4: T9, then the same tickets for Spin. -4. T16 once DEC-13 is decided. +### Waves + +A wave starts when everything it waits for has landed. Within a wave, rows +run in priority order, then DEC-4's platform order. TS is Trusted Server and +EZ is EdgeZero (`IABTechLab/edgezero#N`). Kinds: New (ticket), Move (existing +issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. + +| Wave | Item | Pri | Kind | Action | Waits for | +| ---- | -------------------------------------------------------------------------------------------------------------------------------- | --- | ------------------ | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | +| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | +| 0 | TS #1526 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | +| 0 | TS #1525 | P1 | PR fix | Fix its round-1 review | none | +| 0 | TS #1516 | P1 | Merge | Already approved | none | +| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | +| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | +| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464, IABTechLab/edgezero#453 | P1 | PR fix | Rebase off the commits they carry | none | +| 0 | TS T2 | P1 | New | EIDs in `user.eids`; EID-absence tests check both fields | none | +| 0 | EZ EZI-5 | P1 | New | Rate limiter | none | +| 0 | TS #1189 | P1 | Move | EID write path skips Purpose 4 gating | none | +| 0 | TS #1142 | P1 | Move | Health and startup contract | none | +| 0 | TS #1503 | P2 | Merge | `ts ec` commands; delivers #1432 | none | +| 0 | TS #1449 | P2 | PR fix | Fix its round-4 review | none | +| 0 | TS #1141, #1139, #1233 | P2 | Move | Axum config store; adapter stores; `ts-ec` to creative assets | none | +| 0 | EZ EZI-3s, TS #1192 | P3 | New, Move | Spin ClientInfo; Fastly per-request cost | none | +| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | +| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | +| 1 | TS #1543 | P2 | PR fix | Rebase onto #1449; ships with A6 | #1449 | +| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | +| 2 | EZ EZI-2a, EZI-1 | P1 | New | Record contract with Fastly; after-response work | IABTechLab/edgezero#275 | +| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order | IABTechLab/edgezero#275 | +| 2 | TS T5, T4 | P1 | New | Conformance suite, Fastly first; shared state | #1507 | +| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | +| 3 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1487 | +| 3 | TS #1553, #1549 | P1 | Move | After-response CAS retry; no `user.id` for a row-less EC | PR #1487 | +| 3 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | +| 3 | IABTechLab/edgezero#89, EZ EZI-4 | P2 | Link, New | Client-IP accessor (EZI-3); geo with region | IABTechLab/edgezero#409; IABTechLab/edgezero#439 | +| 3 | TS #1246 | P2 | Move | Lockr IDs to KV | PR #1487 | +| 4 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1489 | +| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | +| 4 | TS T15, #1551 | P2 | New, Move | Identity-only tsjs; pull sync on a full row | PR #1489 | +| 5 | EZ R1, R2 | P1 | Release | As Part B defines | EZI-2a, EZI-2b, IABTechLab/edgezero#406 to IABTechLab/edgezero#439; EZI-1, EZI-5, IABTechLab/edgezero#89 | +| 5 | TS T3 | P1 | New | SharedID cleanup | PR #1488, #1189 | +| 5 | TS #1555, #1556 | P2 | Move | Opt-out EIDs; backend certificate checks | PR #1488 (#1556 also #1507) | +| 5 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | +| 6 | TS A1 | P1 | New, PR fix | Port #1528, repin to R1 | R1, #1507, #1141 | +| 6 | TS T6, T10 | P1 | New | Shared record backend, Fastly first; rate limits | R1, #1522, #1139; R2 | +| 7 | TS #1529 (A4), A6 with #1543, A3, A9 | P1 | Move, New | CloudFront IP and geo; production image; SIGTERM drain; env names | A1 (A3 also R2) | +| 7 | TS #1527, #1523 | P2 | Close | Tests moved to #1506 | #1506, A1 | +| 8 | TS A2, A7 | P1 | New | Axum EC lifecycle; reference infrastructure | T6, A1, #1529; A1, A6, #1142 | +| 9 | TS T11, T7, T13 | P1 | New | Providers on Axum; Axum partner APIs; SharedID write-back | A2 plus their Part B blockers | +| 9 | TS T12, A8 | P2 | New | Abuse protection; SIGTERM test in CI | A2 | +| 9 | TS #1541, #1424, #1499, #1531 to #1540 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | +| 10 | TS T14, A5 | P2 | New | Mirror cookie on Axum; load test | T11, PR #1489; A2, A7, T11 | +| 11 | EZ R3, then TS #1518, then TS T8 | P2 | Release, Move, New | Cloudflare release; geo adoption check; Cloudflare EC lifecycle, then the Cloudflare parts of T10, T11, T13, T14 | Part B blockers, DEC-8 | +| 12 | EZ R4, then TS T9 | P3 | Release, New | Spin release; Spin EC lifecycle, then the Spin parts | Part B blockers | +| any | TS T16, TS #1245, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; IABTechLab/edgezero#287 last | DEC-13; DEC-9; IABTechLab/edgezero#275 | + +Fallback for wave 1: if the fold-in has not landed two weeks after wave 0 +ends, #1506 merges pinned to IABTechLab/edgezero#275's head as it stands +(allowed by AX-7) and keeps its readthrough rejection until the repin. + +The two longest chains: + +- Axum identity: IABTechLab/edgezero#460, IABTechLab/edgezero#275, IABTechLab/edgezero#406 to IABTechLab/edgezero#439, IABTechLab/edgezero#463, EZI-2b, R1, A1, + T6, A2, T11, A5. +- SharedID: IABTechLab/edgezero#460, #1506, #1487, #1489, #1488, T3, and T13 once A2 lands. ## Part B: tickets -"Blocked by" names a spec decision. A ticket that spans platforms ships per -platform in DEC-4 order. +"Blocked by" is set as a GitHub dependency between issues. A PR or a decision +there goes in the issue body. "Blocks" is the inverse and is not set +separately. A ticket that spans platforms ships per platform in DEC-4 order. ### EdgeZero, under IABTechLab/edgezero#468 -| ID | Ticket | Reqs | New or reuse | Phase | -| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | -| EZI-2a | `KvStore` record contract (generation read, `Add` / `Overwrite` / `IfGenerationMatch`, precondition-failed outcome, strong exists and prefix list, `KvError::Unsupported`) and the Fastly provider | EZI-2a | New; IABTechLab/edgezero#410 and IABTechLab/edgezero#437 rule out conditional writes today | B | -| EZI-2b | DynamoDB and celld providers meet the contract; DynamoDB spreads writes across partition keys with no item every insert updates | EZI-2b | New | B | -| EZI-1 | Bounded work after the response on Fastly, then Axum (drained on SIGTERM), then Cloudflare; capability error on Spin | EZI-1 | New | B, R2 / R3 | -| EZI-5 | Rate limiter: Edge Rate Limiting on Fastly, in-memory and shared counter on Axum, then Cloudflare and Spin backends per DEC-11 | EZI-5 | New | B, R2 / R3 | -| EZI-3 | Trusted client IP through one accessor on every adapter | EZI-3 | Reuse IABTechLab/edgezero#89 with IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92 and IABTechLab/edgezero#397; new sub-issue for Spin | B, R2 | -| EZI-4 | Geo with country and region through `ctx.geo()` on Fastly, then Cloudflare, then Spin | EZI-4 | New; IABTechLab/edgezero#432 and IABTechLab/edgezero#438 cover Axum | R3 / R4 | -| EZI-6 | Releases R1 to R4 as the spec's EZI-6 defines | EZI-6 | New; R1 under IABTechLab/edgezero#391 | B onward | +New: + +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| ------ | ------------------------------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | ---------------------------------------------------------------------- | ---------------- | +| EZI-2a | `KvStore` record contract and the Fastly provider | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, R1 | +| EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a; PRs IABTechLab/edgezero#463 and IABTechLab/edgezero#414 | R1, R3 | +| EZI-1 | Bounded work after the response: Fastly, then Axum (drained on SIGTERM), then Cloudflare; capability error on Spin | EZI-1 | Feature | L | P1 | PR IABTechLab/edgezero#275 | R2, R3 | +| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, in-memory and shared counter, then Cloudflare and Spin backends per DEC-11 | EZI-5 | Feature | M | P1 | none | R2, R3 | +| EZI-4 | Geo with country and region through `ctx.geo()`: Fastly, then Cloudflare, then Spin | EZI-4 | Feature | M | P2 | PR IABTechLab/edgezero#439 | R3, R4 | +| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | none | R4 | +| R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 plus EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1, T6 | +| R2 | Release EZI-1 and EZI-5 for Fastly and Axum, plus EZI-3 | EZI-6 | Task | S | P1 | EZI-1, EZI-5, IABTechLab/edgezero#89 | A3, T7, T10, T11 | +| R3 | Release the Cloudflare parts of EZI-1, EZI-2, EZI-4 and EZI-5 | EZI-6 | Task | S | P2 | EZI-1, EZI-2b, EZI-4, EZI-5; DEC-8 | R4, T8, #1518 | +| R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-3s, EZI-4, R3 | T9 | + +Linked from IABTechLab/edgezero#468, not moved: IABTechLab/edgezero#89 is +EZI-3 and stays under IABTechLab/edgezero#88 (Story, M, P2, blocks R2). +IABTechLab/edgezero#432 and IABTechLab/edgezero#438 relate to EZI-4, +IABTechLab/edgezero#410 and IABTechLab/edgezero#437 to EZI-2b, and +IABTechLab/edgezero#397 to EZI-3; they stay where they are. R1 is also linked +from IABTechLab/edgezero#391. ### Axum deployable, under #1236 -| ID | Ticket | Reqs | Depends on | Blocked by | Type | New or reuse | Phase | -| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------- | --------------- | ------------ | ------- | ------------------------- | ----- | -| A1 | Serve Axum in production mode from main: port #1528 onto #1506's outbound API, repin to R1, fail before listening, SIGTERM drain | AX-1, AX-2, AX-7 | R1, #1506 | DEC-5, DEC-6 | Feature | New; relates #1141 | C | -| A2 | Axum EC lifecycle on the shared backend: forward-port #1536 and #1537's mint, record, `ts-ec`, identify, auction and body EIDs, without their adapter stores | ID-1, ID-3, ID-8, WB-1 | T6, A1, A4 | DEC-6 | Feature | New | C | -| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | EZI-1 in R2, A1 | none | Feature | New | D | -| A4 | Trusted client IP and geo behind CloudFront: forward-port #1532 without its core change that maps a US request with no region to `Unknown` | AX-3 | A1 | DEC-6 | Feature | New; relates #1134 | C | -| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | A2, T11 | none | Task | New | D | -| A6 | Publish and attest the production image; one image contract for #1539 and #1543 | AX-5 | A1 | DEC-6 | Feature | New | C | -| A7 | Reference infrastructure: wire DynamoDB, readiness that checks dependencies, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on by default | AX-3, AX-6 | A1, A6 | DEC-11 | Feature | Reuse #1142 for readiness | C | -| A8 | Run the SIGTERM-during-record-write test (`scripts/test-native-ec-image.mjs` from #1536) in CI | AX-1 | A2 | none | Task | New | C | -| A9 | Use only `TRUSTED_SERVER__` environment names; drop the RC's `TS_NATIVE_GEO_PROFILE_FILE`, `TS_NATIVE_BINDINGS_TOML` and `TS_APPCONFIG_TOKEN` | AX-2 | A1 | none | Task | New | C | +New: + +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ----------------------------------------------------------------------------------------------------------- | ---------------------- | ------- | ---- | --- | ---------------- | ------------------------- | +| A1 | Serve Axum in production mode from main: port #1528 onto #1506's outbound API, repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507, #1141 | A2, A3, A6, A7, A9, #1529 | +| A2 | Axum EC lifecycle on the shared backend, forward-ported from #1536 and #1537 | ID-1, ID-3, ID-8, WB-1 | Feature | L | P1 | T6, A1, #1529 | A5, A8, T7, T11, T12, T13 | +| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | +| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P2 | A2, A7, T11 | none | +| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1 | A7 | +| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on | AX-3, AX-6 | Feature | L | P1 | A1, A6, #1142 | A5 | +| A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P2 | A2 | none | +| A9 | `TRUSTED_SERVER__` environment names only | AX-2 | Task | S | P2 | A1 | none | + +Existing, moved under #1236: + +| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | +| ----- | ------------------------------------------------------------ | ----------- | ---- | --- | ---------- | ------ | +| #1529 | Extract the protected CloudFront native Geo integration (A4) | Task (keep) | M | P1 | A1 | A2 | +| #1142 | Uniform cross-adapter health and startup-failure contract | Task (keep) | M | P1 | none | A7 | +| #1141 | Make Axum consume the local config store | Bug (keep) | S | P2 | none | A1 | +| #1139 | Implement adapter stores or retire dead store manifests | Bug (keep) | M | P2 | none | T6 | ### Trusted Server, under #1558 -| ID | Ticket | Reqs | Depends on | Blocked by | Type | Phase | -| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | --------------------------------------------- | ------------- | ------- | ------------ | -| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, return 501 without one, never reach the publisher origin | ID-3 | none | none | Task | A | -| T2 | Write EIDs to OpenRTB 2.6 `user.eids` in the bidder, Prebid Server and APS builders; every EID-absence test asserts both `user.eids` and `user.ext.eids` absent; keep `user.ext.eids` only for a named downstream | SID-4 | none | none | Feature | A | -| T3 | SharedID cleanup: delete the `sharedId` cookie and `sharedid.org` path, dedupe one UUID across cookie, `ts-eids`, body and record, re-file stored `sharedid.org` entries on next write, apply the SID-6 gate, use the configured name in the template-cache deny list | SID-1, SID-3, SID-6, WB-5 | #1488 | DEC-7 | Feature | B | -| T4 | Build settings, partner registry, store clients and HTTP client once per process or isolate; remove Cloudflare's first-request `Env` capture and config `block_on`; port Spin's config `block_on` removal from RC commit `933de5921` | SH-1, SH-3, SH-4 | #1506 | none | Task | B | -| T5 | One identity conformance suite with ID-1's cases, run on every supported adapter, and an unsupported-startup test elsewhere | ID-1 | #1506 | none | Task | B | -| T6 | Run the EC record on one `EcKvStore` over EdgeZero's `KvStore`; delete `crates/trusted-server-adapter-fastly/src/ec_kv.rs` | ID-2 | EZI-2a in R1 | none | Feature | C | -| T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | A2, EZI-1, EZI-5 | none | Feature | D | -| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs | ID-1, ID-2, ID-3, ID-8, WB-1 | T6, EZI-1, EZI-2, EZI-4, EZI-5 for Cloudflare | DEC-8 | Feature | D | -| T9 | Spin EC lifecycle, the same scope as T8 | ID-1, ID-2, ID-3, ID-8, WB-1 | T8, EZI-2, EZI-4, EZI-5 for Spin | DEC-8, DEC-14 | Feature | D | -| T10 | Rate limits: batch sync per partner, pull sync, provider lookups and record creation; shared counter with more than one Axum task | ID-6 | EZI-5 | DEC-11 | Feature | D | -| T11 | Providers on every supported adapter: remove the startup refusal, register provider kinds, classify navigations, await lookups before finalization, and run only follow-up writes after the response | ID-4, ID-5, WB-1 | A2, T10, EZI-1 | none | Feature | D | -| T12 | Refuse record creation from non-browser clients on every supported adapter | ID-7 | A2 | none | Feature | D | -| T13 | SharedID cookie write-back on the document response, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | T3, #1488 retention fix, A2 | DEC-7 | Feature | D | -| T14 | Mirror cookie on Axum, then Cloudflare, then Spin | WB-4 | T11 | DEC-10 | Feature | D | -| T15 | Identity-only tsjs mode | WB-6 | #1489 | none | Feature | B | -| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | none | DEC-13 | Task | after DEC-13 | +New: + +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------- | ---- | --- | ---------------------------- | ------- | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T7, T8 | +| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | +| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | #1189; PR #1488 | T13 | +| T4 | Build shared state once per process or isolate; Cloudflare `Env` capture and config `block_on`; Spin config `block_on` | SH-1, SH-3, SH-4 | Task | M | P2 | #1507 | none | +| T5 | Identity conformance suite, Fastly first | ID-1 | Task | M | P1 | #1507 | T8 | +| T6 | One record backend over EdgeZero's `KvStore`; delete the Fastly `ec_kv.rs` | ID-2 | Feature | L | P1 | R1, #1522, #1139 | A2, T8 | +| T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | Feature | M | P1 | A2, R2, T1 | none | +| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs | ID-1, ID-2, ID-3, ID-8, WB-1 | Feature | L | P2 | T6, R3, T1, T5, #1518 | T9 | +| T9 | Spin EC lifecycle, the same scope as T8 | ID-1, ID-2, ID-3, ID-8, WB-1 | Feature | L | P3 | T8, R4; DEC-14 | none | +| T10 | Rate limits: batch sync per partner, pull sync, provider lookups, record creation | ID-6 | Feature | M | P1 | R2; DEC-11 | T11 | +| T11 | Providers on every supported adapter, awaited before finalization; only follow-up writes after the response | ID-4, ID-5, WB-1 | Feature | L | P1 | A2, T10, R2, #1553; PR #1487 | A5, T14 | +| T12 | Refuse record creation from non-browser clients | ID-7 | Feature | M | P2 | A2 | none | +| T13 | SharedID cookie write-back, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | Feature | M | P1 | T3, A2; PR #1488; DEC-7 | none | +| T14 | Mirror cookie on Axum, then Cloudflare, then Spin | WB-4 | Feature | S | P2 | T11; PR #1489; DEC-10 | none | +| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P2 | PR #1489 | none | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | + +Existing, moved under #1558: + +| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | +| ----- | --------------------------------------------------------------------------- | ----------- | ---- | --- | --------------- | ------ | +| #1522 | Make shared EC persistence awaitable (delivered by PR #1526) | Task (keep) | M | P1 | none | T6 | +| #1189 | Ungated `ingest_*` EID write path bypasses TCF Purpose 4 gating | Bug | M | P1 | none | T3 | +| #1549 | Do not send a cookie EC with no row as `user.id` | Bug (keep) | S | P1 | PR #1487 | none | +| #1553 | Retry an after-response write that loses its CAS | Task (keep) | S | P1 | PR #1487 | T11 | +| #1551 | Stop pull sync from re-calling a partner for a row that is full | Bug (keep) | S | P2 | PR #1489 | none | +| #1555 | Client and browser EIDs for provider-owned sources after a provider opt-out | Bug (keep) | S | P2 | PR #1488 | none | +| #1556 | Certificate checks on identity backends | Bug (keep) | S | P2 | #1507; PR #1488 | none | +| #1518 | Verify EdgeZero geolocation adoption in EC and auction consent paths | Task (keep) | S | P2 | R3 | T8 | +| #1233 | Stop forwarding `ts-ec` to static creative assets | Task (keep) | S | P2 | none | none | +| #1432 | `ts ec show` and `ts ec eids` (delivered by PR #1503) | Task (keep) | S | P2 | none | none | +| #1245 | Spec for non-browser LiveRamp sync | Task | S | P2 | DEC-9 | none | +| #1246 | Write Lockr IDs to the KV store | Task (keep) | M | P2 | PR #1487 | none | +| #1192 | Reduce Fastly adapter per-request fixed cost | Task (keep) | M | P3 | none | none | + +Not moved: + +- IABTechLab/trusted-server#90 is an epic, so it is linked from #1558, not moved; it and IABTechLab/trusted-server#353 close + when PR #1488 merges. +- #1507 delivers the cutover and stays under #1505; it blocks A1, T4, T5 and + #1556. +- Proposed closures: #1523 with #1527; IABTechLab/trusted-server#353 and #1310 once PR #1488 merges. +- Out of these epics: #1484 (auction), #1140 (Cloudflare config), #1164 + (Spin tooling), IABTechLab/trusted-server#518 (cleanup). +- #1134, #1132 and #1125 overlap this work but stay out pending a decision on + whether DEC-2's exclusion covers issues. Requirements met inside open PRs, not by a ticket: ID-8 on main and SH-2 (#1487), SID-1 to SID-3 and SID-6 for the provider, SID-5 (#1488), WB-4 @@ -195,12 +283,12 @@ expiry (#1489), AX-7 (#1528). Part C lists them. ## Part C: fixes to existing PRs Each item names the head it was checked at. Post it as a comment on that PR -(Tasks 5 and 6); once posted, the PR thread is the source of truth. +(Tasks 6 and 7); once posted, the PR thread is the source of truth. ### Trusted Server - **#1506 at `851537045` (AX-7, SID-4).** Rebase after #1557, #1526 and #1516. - Repin after the fold-in, or under Phase B's fallback; drop the + Repin after the fold-in, or under wave 1's fallback; drop the `origin_readthrough_enabled` rejection (`config.rs:317`) once cache tags return. The repin brings IABTechLab/edgezero `b97efec1`, which stops Spin sending `Content-Length: 0` on bodiless DELETE and OPTIONS. Port #1527's @@ -255,9 +343,10 @@ Each item names the head it was checked at. Post it as a comment on that PR - **IABTechLab/edgezero#275 at `14a6ca8b`.** Absorb IABTechLab/edgezero#460, `9f090e57` from IABTechLab/edgezero#406 (the CLI template emits Fastly-only image code that IABTechLab/edgezero#275 gates) and one copy of the CI trigger - change, which today sits in IABTechLab/edgezero#406, IABTechLab/edgezero#460 and IABTechLab/edgezero#414 (all - IABTechLab/edgezero). Optionally guard all five egress unwind sites against - a panicking logger; `0769cbfd` in IABTechLab/edgezero#409 covers three. + change, which today sits in IABTechLab/edgezero#406, IABTechLab/edgezero#460 + and IABTechLab/edgezero#414. Optionally guard all five egress unwind sites + against a panicking logger; `0769cbfd` in IABTechLab/edgezero#409 covers + three. - **IABTechLab/edgezero#460 at `cec19886`.** Validate tags (printable ASCII, non-empty) and make Cloudflare reject tags instead of silently using the default. Fix the red Cloudflare wasm clippy; the fix exists only in RC @@ -270,14 +359,17 @@ Each item names the head it was checked at. Post it as a comment on that PR `9a985c99`. - **IABTechLab/edgezero#414 at `3733482f`.** Drop those two commits once they move down. -- **IABTechLab/edgezero#463 at `6f9fa8bc`, IABTechLab/edgezero#464 at `6fe32531`, IABTechLab/edgezero#453 at - `633f0552`.** Rebase as Part A's table says. +- **IABTechLab/edgezero#463 at `6f9fa8bc`, IABTechLab/edgezero#464 at + `6fe32531`, IABTechLab/edgezero#453 at `633f0552`.** Rebase as Part A's table + says. - **IABTechLab/edgezero#389 at `0653e6e3` and IABTechLab/edgezero#401 at `1a6ae861`.** Land after IABTechLab/edgezero#275 merges, redoing the RC-only - merges `7c07b8e7` (IABTechLab/edgezero#389) and `2510ecc9` (IABTechLab/edgezero#401) and the RC-only follow-up - commit `501a2dce` (IABTechLab/edgezero#401). + merges `7c07b8e7` (IABTechLab/edgezero#389) and `2510ecc9` + (IABTechLab/edgezero#401) and the RC-only follow-up commit `501a2dce` + (IABTechLab/edgezero#401). - **IABTechLab/edgezero#444 at `217983c0` and IABTechLab/edgezero#466 at - `cb48c6b3`.** Land IABTechLab/edgezero#444 first, after IABTechLab/edgezero#275. + `cb48c6b3`.** Land IABTechLab/edgezero#444 first, after + IABTechLab/edgezero#275. - **Every stack PR.** Refresh the CI results cited in the descriptions. ### Cutover follow-ups outside these epics @@ -296,24 +388,35 @@ These belong to #1505 and IABTechLab/edgezero#436: ## Tasks -### Task 1: Align the epics +### Task 1: Move the existing issues under their epics **Files:** none; GitHub only. **Interfaces:** -- Produces: #1236 as a sub-issue of #1558. The note on #1236 saying this - design is not its spec was posted while #1559 was in review. +- Consumes: Part B's "Existing, moved" tables and "Linked, not moved" notes. +- Produces: 4 issues under #1236 and 13 under #1558, each typed, sized and + prioritized; link comments on IABTechLab/edgezero#468. + +- [ ] **Step 1: Move each issue** -- [ ] **Step 1: Add #1236 under #1558** +For each row, run +`gh api graphql -f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c}){issue{number}}}' -F p="$(gh issue view --json id --jq .id)" -F c="$(gh issue view --json id --jq .id)"`. +Expected: the epic's number. A row whose issue already has a parent is +reported, not moved. -Run: `gh api graphql -f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c}){issue{number}}}' -F p="$(gh issue view 1558 --json id --jq .id)" -F c="$(gh issue view 1236 --json id --jq .id)"` -Expected: `{"data":{"addSubIssue":{"issue":{"number":1558}}}}` +- [ ] **Step 2: Type, Priority and Size** -- [ ] **Step 2: Verify** +Set "Type (set)" through `updateIssue` where it is not "keep". Add the issue +to the project through `addProjectV2ItemById` if it is not there, then set +Priority and Size through `updateProjectV2ItemFieldValue` with the IDs in +Global Constraints. +Expected: each mutation returns the item. -Run: `gh api graphql -f query='{repository(owner:"IABTechLab",name:"trusted-server"){issue(number:1558){subIssues(first:50){nodes{number}}}}}' --jq '[.data.repository.issue.subIssues.nodes[].number]'` -Expected: a list containing `1236`. +- [ ] **Step 3: Link without moving** + +Post a `CREATOR:` comment on IABTechLab/edgezero#468 listing the linked +issues and the EZI ID each relates to. ### Task 2: File the EdgeZero tickets under IABTechLab/edgezero#468 @@ -322,51 +425,71 @@ Expected: a list containing `1236`. **Interfaces:** - Consumes: Part B's EdgeZero table and the spec's EZI requirements. -- Produces: an issue number per EZI row, recorded in a `CREATOR:` comment on +- Produces: an issue number per row, recorded in a `CREATOR:` comment on IABTechLab/edgezero#468. - [ ] **Step 1: Search before each row** -For EZI-2a, EZI-2b, EZI-1, EZI-5, EZI-3, EZI-4 and EZI-6 in that order, run +For each row in table order, run `gh issue list -R IABTechLab/edgezero --state all --search ""`. -Expected: no open issue other than those the row names for reuse. +Expected: no open issue other than those Part B links. -- [ ] **Step 2: Create or reuse** +- [ ] **Step 2: Create** -For a new row, run -`gh issue create -R IABTechLab/edgezero --title "" --body-file `, -where the file holds three sections: Behavior (the spec's EZI text), Acceptance -(its Accept line) and Related (the reuse cell and -IABTechLab/trusted-server#1558). For a reuse row, post a `CREATOR:` comment on -the reused issue linking IABTechLab/edgezero#468 and the acceptance. -Expected: one issue URL per new row. +Run `gh issue create -R IABTechLab/edgezero --title "" --body-file `. +The body has Behavior (the spec's EZI text), Acceptance (its Accept line), +Blocked by (every PR and decision in that column, by reference) and Related +(IABTechLab/trusted-server#1558). +Expected: one issue URL per row. -- [ ] **Step 3: Type, parent and project** +- [ ] **Step 3: Type, parent, Priority and Size** -For each new issue, set type Feature through `updateIssue`, add it under -IABTechLab/edgezero#468 through `addSubIssue`, and add it to the project -through `addProjectV2ItemById`. -Expected: each mutation returns the issue number. +Set the row's Type through `updateIssue`, add the issue under +IABTechLab/edgezero#468 through `addSubIssue`, add it to the project, and set +Priority and Size. +Expected: each mutation returns the issue or item. - [ ] **Step 4: Record the numbers** -Post a `CREATOR:` comment on IABTechLab/edgezero#468 mapping each EZI ID to its -issue. +Post a `CREATOR:` comment on IABTechLab/edgezero#468 mapping each ID to its +issue, and one on IABTechLab/edgezero#391 linking R1. ### Task 3: File the Axum tickets under #1236 -Same steps as Task 2, against IABTechLab/trusted-server and #1236, for A1 to -A9 in table order. Each body has Summary (Ticket column), Requirements (Reqs -with a link to the spec section), Acceptance (each requirement's Accept line), -Depends on (issue numbers from Task 2 and this task) and Blocked by. A7 links -#1142; A1 links #1141; A4 links #1134. Record the map in a `CREATOR:` comment -on #1236. +Same steps as Task 2, against IABTechLab/trusted-server and #1236, for the +eight new rows in table order. Each body has Summary (Ticket), Requirements +(Reqs, linked to the spec section), Acceptance (each requirement's Accept +line) and Blocked by. Record the map in a `CREATOR:` comment on #1236. ### Task 4: File the Trusted Server tickets under #1558 -Same steps as Task 3, for T1 to T16 in table order, under #1558. +Same steps as Task 3, for T1 to T16 in table order, under #1558. Record the +map in a `CREATOR:` comment on #1558. + +### Task 5: Link the dependencies + +**Interfaces:** + +- Consumes: every "Blocked by" cell in Part B and the issue numbers from + Tasks 1 to 4. +- Produces: one GitHub blocked-by link per issue pair. + +- [ ] **Step 1: Link each pair** + +For every issue named in a "Blocked by" cell, run +`gh api graphql -f query='mutation($i:ID!,$b:ID!){addBlockedBy(input:{issueId:$i,blockingIssueId:$b}){issue{number}}}' -F i= -F b=`. +Expected: the blocked issue's number. If GitHub refuses a cross-repository +pair, add a "Blocked by IABTechLab/edgezero#N" line to the issue body instead +and list the pair in Step 2's readback. + +- [ ] **Step 2: Read back** + +For each issue, read its blocked-by list through GraphQL and compare it with +Part B. +Expected: every issue pair present, plus the list of pairs recorded only in +bodies. -### Task 5: Tie out the Trusted Server PRs +### Task 6: Tie out the Trusted Server PRs - [ ] **Step 1: Check heads** @@ -378,25 +501,29 @@ new head before posting. - [ ] **Step 2: Post** Post one `CREATOR:` comment per PR with its Part C item and the ticket it -serves (`Part of #`). For proposed closures, ask the author; do not -close another author's PR. +serves (`Part of #`): #1532 to #1529, #1526 to #1522, #1503 to #1432, +#1506 to #1507, #1528 to A1, #1543 to A6, #1487 to T11, #1488 to T13 and +#1489 to T14 and T15. For proposed closures, ask the author; do not close +another author's PR. -### Task 6: Tie out the EdgeZero PRs +### Task 7: Tie out the EdgeZero PRs -Same steps as Task 5, for the EdgeZero items in Part C, each linking its EZI +Same steps as Task 6, for the EdgeZero items in Part C, each linking its EZI issue from Task 2. -### Task 7: Verify coverage +### Task 8: Verify coverage - [ ] **Step 1: Count sub-issues** -Run the Task 1 Step 3 query for #1558, #1236 and IABTechLab/edgezero#468. -Expected: 16, 9 and 7 entries plus any reused issues, matching Part B. +Run `gh api graphql -f query='{repository(owner:"IABTechLab",name:""){issue(number:){subIssues(first:100){totalCount}}}}' --jq '.data.repository.issue.subIssues.totalCount'` +for #1558, #1236 and IABTechLab/edgezero#468. +Expected: 29, 12 and 10. -- [ ] **Step 2: Check dependencies and references** +- [ ] **Step 2: Check bodies** -For every created issue, read its body and confirm three things: each -"Depends on" names an issue number; no Trusted Server body contains a bare -`#N` below 1000 (EdgeZero references use `IABTechLab/edgezero#N`); and the -body holds only its requirement, acceptance and dependencies. +For every created or moved issue, read its body and confirm three things: +each "Blocked by" names an issue, PR or decision; no Trusted Server body +contains a bare `#N` below 1000 (EdgeZero references use +`IABTechLab/edgezero#N`); and a created body holds only its requirement, +acceptance and dependencies. Expected: no gaps. From 7cb8933f7ebdad099921dcad6cb8391a57e83d47 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 01:49:24 -0800 Subject: [PATCH 04/31] Let Fastly and Axum ship without Cloudflare or Spin Split every EdgeZero ask and Trusted Server ticket by platform so no Fastly or Axum item is blocked by a Cloudflare or Spin item, add Cloudflare and Spin follow-on tickets, file the Axum tickets and the moved issues under the new epics instead of nesting epics, and record the platform order with the stand-alone rule in DEC-4. --- ...11-11-server-side-identity-all-adapters.md | 246 ++++++++++-------- ...erver-side-identity-all-adapters-design.md | 52 ++-- 2 files changed, 173 insertions(+), 125 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 37477c91e..7012f5479 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -2,9 +2,9 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** File the missing tickets, move the existing issues under their -epics (#1558, #1236 and IABTechLab/edgezero#468), link every dependency, and -tie every open PR to its ticket, in the wave order of Part A. +**Goal:** File the missing tickets, move the existing issues under the new +epics (#1558 and IABTechLab/edgezero#468), link every dependency, and tie +every open PR to its ticket, in the wave order of Part A. **Architecture:** This plan's tasks are GitHub operations, not code. Task 1 moves existing issues, Tasks 2 to 4 create the new tickets (EdgeZero first, @@ -23,10 +23,15 @@ IABTechLab/edgezero. ## Global Constraints - One-account mode: every write acts as the PR author's login. -- Platform order: Fastly, then Axum, then Cloudflare, then Spin (DEC-4). -- Epics are not nested. #1558 and #1236 stay separate epics that link through - their Related sections. IABTechLab/edgezero#89 stays under - IABTechLab/edgezero#88 and is linked from IABTechLab/edgezero#468. +- Platform order: Fastly, then Axum, then Cloudflare, then Spin (DEC-4). Fastly + and Axum ship without Cloudflare or Spin: no Fastly or Axum item is blocked + by a Cloudflare or Spin item, and Cloudflare and Spin work has its own tickets. +- Only issues move, and they move under the new epics: #1558 for Trusted + Server and IABTechLab/edgezero#468 for EdgeZero. Existing epics (#1236, + #1505, IABTechLab/trusted-server#90, IABTechLab/edgezero#88, + IABTechLab/edgezero#391) are never moved under them and receive no new + issues; #1236 is linked from #1558's Related section. An issue that leaves + an old epic moves with `addSubIssue(input:{..., replaceParent: true})`. - Issue bodies state the requirement, its acceptance and its dependencies, and nothing about deployments or their state. - EdgeZero references are written `IABTechLab/edgezero#N`; a bare `#N` means @@ -108,56 +113,59 @@ run in priority order, then DEC-4's platform order. TS is Trusted Server and EZ is EdgeZero (`IABTechLab/edgezero#N`). Kinds: New (ticket), Move (existing issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. -| Wave | Item | Pri | Kind | Action | Waits for | -| ---- | -------------------------------------------------------------------------------------------------------------------------------- | --- | ------------------ | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | -| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | -| 0 | TS #1526 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | -| 0 | TS #1525 | P1 | PR fix | Fix its round-1 review | none | -| 0 | TS #1516 | P1 | Merge | Already approved | none | -| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | -| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | -| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464, IABTechLab/edgezero#453 | P1 | PR fix | Rebase off the commits they carry | none | -| 0 | TS T2 | P1 | New | EIDs in `user.eids`; EID-absence tests check both fields | none | -| 0 | EZ EZI-5 | P1 | New | Rate limiter | none | -| 0 | TS #1189 | P1 | Move | EID write path skips Purpose 4 gating | none | -| 0 | TS #1142 | P1 | Move | Health and startup contract | none | -| 0 | TS #1503 | P2 | Merge | `ts ec` commands; delivers #1432 | none | -| 0 | TS #1449 | P2 | PR fix | Fix its round-4 review | none | -| 0 | TS #1141, #1139, #1233 | P2 | Move | Axum config store; adapter stores; `ts-ec` to creative assets | none | -| 0 | EZ EZI-3s, TS #1192 | P3 | New, Move | Spin ClientInfo; Fastly per-request cost | none | -| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | -| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | -| 1 | TS #1543 | P2 | PR fix | Rebase onto #1449; ships with A6 | #1449 | -| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | -| 2 | EZ EZI-2a, EZI-1 | P1 | New | Record contract with Fastly; after-response work | IABTechLab/edgezero#275 | -| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order | IABTechLab/edgezero#275 | -| 2 | TS T5, T4 | P1 | New | Conformance suite, Fastly first; shared state | #1507 | -| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | -| 3 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1487 | -| 3 | TS #1553, #1549 | P1 | Move | After-response CAS retry; no `user.id` for a row-less EC | PR #1487 | -| 3 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | -| 3 | IABTechLab/edgezero#89, EZ EZI-4 | P2 | Link, New | Client-IP accessor (EZI-3); geo with region | IABTechLab/edgezero#409; IABTechLab/edgezero#439 | -| 3 | TS #1246 | P2 | Move | Lockr IDs to KV | PR #1487 | -| 4 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1489 | -| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | -| 4 | TS T15, #1551 | P2 | New, Move | Identity-only tsjs; pull sync on a full row | PR #1489 | -| 5 | EZ R1, R2 | P1 | Release | As Part B defines | EZI-2a, EZI-2b, IABTechLab/edgezero#406 to IABTechLab/edgezero#439; EZI-1, EZI-5, IABTechLab/edgezero#89 | -| 5 | TS T3 | P1 | New | SharedID cleanup | PR #1488, #1189 | -| 5 | TS #1555, #1556 | P2 | Move | Opt-out EIDs; backend certificate checks | PR #1488 (#1556 also #1507) | -| 5 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | -| 6 | TS A1 | P1 | New, PR fix | Port #1528, repin to R1 | R1, #1507, #1141 | -| 6 | TS T6, T10 | P1 | New | Shared record backend, Fastly first; rate limits | R1, #1522, #1139; R2 | -| 7 | TS #1529 (A4), A6 with #1543, A3, A9 | P1 | Move, New | CloudFront IP and geo; production image; SIGTERM drain; env names | A1 (A3 also R2) | -| 7 | TS #1527, #1523 | P2 | Close | Tests moved to #1506 | #1506, A1 | -| 8 | TS A2, A7 | P1 | New | Axum EC lifecycle; reference infrastructure | T6, A1, #1529; A1, A6, #1142 | -| 9 | TS T11, T7, T13 | P1 | New | Providers on Axum; Axum partner APIs; SharedID write-back | A2 plus their Part B blockers | -| 9 | TS T12, A8 | P2 | New | Abuse protection; SIGTERM test in CI | A2 | -| 9 | TS #1541, #1424, #1499, #1531 to #1540 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | -| 10 | TS T14, A5 | P2 | New | Mirror cookie on Axum; load test | T11, PR #1489; A2, A7, T11 | -| 11 | EZ R3, then TS #1518, then TS T8 | P2 | Release, Move, New | Cloudflare release; geo adoption check; Cloudflare EC lifecycle, then the Cloudflare parts of T10, T11, T13, T14 | Part B blockers, DEC-8 | -| 12 | EZ R4, then TS T9 | P3 | Release, New | Spin release; Spin EC lifecycle, then the Spin parts | Part B blockers | -| any | TS T16, TS #1245, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; IABTechLab/edgezero#287 last | DEC-13; DEC-9; IABTechLab/edgezero#275 | +| Wave | Item | Pri | Kind | Action | Waits for | +| ---- | -------------------------------------------------------------------------------------------------------------------------------- | --- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | +| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | +| 0 | TS #1526 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | +| 0 | TS #1525 | P1 | PR fix | Fix its round-1 review | none | +| 0 | TS #1516 | P1 | Merge | Already approved | none | +| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | +| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | +| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464, IABTechLab/edgezero#453 | P1 | PR fix | Rebase off the commits they carry | none | +| 0 | TS T2 | P1 | New | EIDs in `user.eids`; EID-absence tests check both fields | none | +| 0 | EZ EZI-5 | P1 | New | Rate limiter for Fastly and Axum | none | +| 0 | TS #1189 | P1 | Move | EID write path skips Purpose 4 gating | none | +| 0 | TS #1142 | P1 | Move | Health and startup contract | none | +| 0 | TS #1503 | P2 | Merge | `ts ec` commands; delivers #1432 | none | +| 0 | TS #1449 | P2 | PR fix | Fix its round-4 review | none | +| 0 | TS #1141, #1139, #1233 | P2 | Move | Axum config store; adapter stores; `ts-ec` to creative assets | none | +| 0 | TS #1192 | P3 | Move | Fastly per-request cost | none | +| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | +| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | +| 1 | TS #1543 | P2 | PR fix | Rebase onto #1449; ships with A6 | #1449 | +| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | +| 2 | EZ EZI-2a, EZI-1 | P1 | New | Record contract with Fastly; after-response work for Fastly and Axum | IABTechLab/edgezero#275 | +| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order | IABTechLab/edgezero#275 | +| 2 | TS T5, T4 | P1 | New | Conformance suite and shared state, Fastly and Axum | #1507 | +| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | +| 3 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1487 | +| 3 | TS #1553, #1549 | P1 | Move | After-response CAS retry; no `user.id` for a row-less EC | PR #1487 | +| 3 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | +| 3 | IABTechLab/edgezero#90, IABTechLab/edgezero#92 | P1 | Move | Fastly and Axum ClientInfo (EZI-3) | none; PR IABTechLab/edgezero#409 | +| 3 | TS #1246 | P2 | Move | Lockr IDs to KV | PR #1487 | +| 4 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1489 | +| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | +| 4 | TS T15, #1551 | P2 | New, Move | Identity-only tsjs; pull sync on a full row | PR #1489 | +| 5 | EZ R1, R2 | P1 | Release | As Part B defines | EZI-2a, EZI-2b, IABTechLab/edgezero#406 to IABTechLab/edgezero#439; EZI-1, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92 | +| 5 | TS T3 | P1 | New | SharedID cleanup | PR #1488, #1189 | +| 5 | TS #1555, #1556 | P2 | Move | Opt-out EIDs; backend certificate checks | PR #1488 (#1556 also #1507) | +| 5 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | +| 6 | TS A1 | P1 | New, PR fix | Port #1528, repin to R1 | R1, #1507, #1141 | +| 6 | TS T6, T10 | P1 | New | Shared record backend, Fastly first; rate limits | R1, #1522, #1139; R2 | +| 7 | TS #1529 (A4), A6 with #1543, A3, A9 | P1 | Move, New | CloudFront IP and geo; production image; SIGTERM drain; env names | A1 (A3 also R2) | +| 7 | TS #1527, #1523 | P2 | Close | Tests moved to #1506 | #1506, A1 | +| 8 | TS A2, A7 | P1 | New | Axum EC lifecycle; reference infrastructure | T6, A1, #1529; A1, A6, #1142 | +| 9 | TS T11, T7, T13 | P1 | New | Providers, Axum partner APIs and SharedID write-back on Fastly and Axum | A2 plus their Part B blockers | +| 9 | TS T12, A8 | P2 | New | Abuse protection; SIGTERM test in CI | A2 | +| 9 | TS #1541, #1424, #1499, #1531 to #1540 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | +| 10 | TS T14, A5 | P2 | New | Mirror cookie on Axum; load test | T11, PR #1489; A2, A7, T11 | +| 11 | EZ EZI-1c, EZI-2c, EZI-4c, EZI-5c and IABTechLab/edgezero#91, then R3; then TS #1518, T8, T17 | P2 | New, Move, Release | Cloudflare parts in EdgeZero; Cloudflare release; geo adoption check; Cloudflare EC lifecycle; Cloudflare providers, limits and cookies | Part B blockers, DEC-8 | +| 12 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s and EZI-5s, then R4; then TS T9, T18 | P3 | New, Release | Spin parts in EdgeZero; Spin release; Spin EC lifecycle; Spin providers, limits and cookies | Part B blockers, DEC-8, DEC-14 | +| any | TS T16, TS #1245, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; IABTechLab/edgezero#287 last | DEC-13; DEC-9; IABTechLab/edgezero#275 | + +Waves 0 to 10 complete Fastly and Axum. None of them waits on a Cloudflare or +Spin item, so Fastly and Axum ship without waves 11 and 12 (DEC-4). Fallback for wave 1: if the fold-in has not landed two weeks after wave 0 ends, #1506 merges pinned to IABTechLab/edgezero#275's head as it stands @@ -173,33 +181,52 @@ The two longest chains: "Blocked by" is set as a GitHub dependency between issues. A PR or a decision there goes in the issue body. "Blocks" is the inverse and is not set -separately. A ticket that spans platforms ships per platform in DEC-4 order. +separately. No ticket mixes Fastly or Axum work with Cloudflare or Spin work +(DEC-4). ### EdgeZero, under IABTechLab/edgezero#468 New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| ------ | ------------------------------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | ---------------------------------------------------------------------- | ---------------- | -| EZI-2a | `KvStore` record contract and the Fastly provider | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, R1 | -| EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a; PRs IABTechLab/edgezero#463 and IABTechLab/edgezero#414 | R1, R3 | -| EZI-1 | Bounded work after the response: Fastly, then Axum (drained on SIGTERM), then Cloudflare; capability error on Spin | EZI-1 | Feature | L | P1 | PR IABTechLab/edgezero#275 | R2, R3 | -| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, in-memory and shared counter, then Cloudflare and Spin backends per DEC-11 | EZI-5 | Feature | M | P1 | none | R2, R3 | -| EZI-4 | Geo with country and region through `ctx.geo()`: Fastly, then Cloudflare, then Spin | EZI-4 | Feature | M | P2 | PR IABTechLab/edgezero#439 | R3, R4 | -| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | none | R4 | -| R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 plus EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1, T6 | -| R2 | Release EZI-1 and EZI-5 for Fastly and Axum, plus EZI-3 | EZI-6 | Task | S | P1 | EZI-1, EZI-5, IABTechLab/edgezero#89 | A3, T7, T10, T11 | -| R3 | Release the Cloudflare parts of EZI-1, EZI-2, EZI-4 and EZI-5 | EZI-6 | Task | S | P2 | EZI-1, EZI-2b, EZI-4, EZI-5; DEC-8 | R4, T8, #1518 | -| R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-3s, EZI-4, R3 | T9 | - -Linked from IABTechLab/edgezero#468, not moved: IABTechLab/edgezero#89 is -EZI-3 and stays under IABTechLab/edgezero#88 (Story, M, P2, blocks R2). -IABTechLab/edgezero#432 and IABTechLab/edgezero#438 relate to EZI-4, -IABTechLab/edgezero#410 and IABTechLab/edgezero#437 to EZI-2b, and -IABTechLab/edgezero#397 to EZI-3; they stay where they are. R1 is also linked -from IABTechLab/edgezero#391. - -### Axum deployable, under #1236 +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| ------ | ----------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | ---------------------------------------------------------------------- | -------------------------- | +| EZI-2a | `KvStore` record contract and the Fastly provider | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1 | +| EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a; PRs IABTechLab/edgezero#463 and IABTechLab/edgezero#414 | R1 | +| EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PR IABTechLab/edgezero#275 | EZI-1c, EZI-1s, R2 | +| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and in-memory and shared-counter backends for Axum | EZI-5 | Feature | M | P1 | none | EZI-5c, EZI-5s, R2 | +| EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | +| EZI-2c | Cloudflare store meets the record contract, or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a; DEC-8 | R3 | +| EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | PR IABTechLab/edgezero#439 | R3 | +| EZI-5c | Rate limiter backend for Cloudflare | EZI-5 | Feature | S | P2 | EZI-5; DEC-11 | R3 | +| EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | +| EZI-2s | Spin store meets the record contract, or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a; DEC-8 | R4 | +| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | none | R4, IABTechLab/edgezero#89 | +| EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | PR IABTechLab/edgezero#439 | R4 | +| EZI-5s | Rate limiter backend for Spin | EZI-5 | Feature | S | P3 | EZI-5; DEC-11 | R4 | +| R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 plus EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1, T6 | +| R2 | Release the Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-6 | Task | S | P1 | EZI-1, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92 | A3, T7, T10, T11 | +| R3 | Release the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, EZI-5c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | +| R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s, R3 | T9, T18 | + +Existing, moved under IABTechLab/edgezero#468 from IABTechLab/edgezero#88 +(these are EZI-3's issues; the IABTechLab/edgezero#88 epic itself stays where +it is): + +| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | +| ---------------------- | ------------------------------------------------------------------- | ------------ | ---- | --- | ------------------------------------------------------------------------------ | -------------------------- | +| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | M | P2 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | +| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | none | R2, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | PR IABTechLab/edgezero#409 | R2, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | none | R3, IABTechLab/edgezero#89 | + +EZI-3s (Spin ClientInfo) also blocks IABTechLab/edgezero#89. + +Related, left in IABTechLab/edgezero#391 because they belong to that epic's +Axum production scope: IABTechLab/edgezero#432 and IABTechLab/edgezero#438 +(EZI-4), IABTechLab/edgezero#410 and IABTechLab/edgezero#437 (EZI-2b), and +IABTechLab/edgezero#397 (EZI-3). R1 is linked from IABTechLab/edgezero#391. + +### Axum deployable, under #1558 New: @@ -214,7 +241,7 @@ New: | A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P2 | A2 | none | | A9 | `TRUSTED_SERVER__` environment names only | AX-2 | Task | S | P2 | A1 | none | -Existing, moved under #1236: +Existing, moved under #1558: | Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | | ----- | ------------------------------------------------------------ | ----------- | ---- | --- | ---------- | ------ | @@ -227,24 +254,26 @@ Existing, moved under #1236: New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------- | ---- | --- | ---------------------------- | ------- | -| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T7, T8 | -| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | -| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | #1189; PR #1488 | T13 | -| T4 | Build shared state once per process or isolate; Cloudflare `Env` capture and config `block_on`; Spin config `block_on` | SH-1, SH-3, SH-4 | Task | M | P2 | #1507 | none | -| T5 | Identity conformance suite, Fastly first | ID-1 | Task | M | P1 | #1507 | T8 | -| T6 | One record backend over EdgeZero's `KvStore`; delete the Fastly `ec_kv.rs` | ID-2 | Feature | L | P1 | R1, #1522, #1139 | A2, T8 | -| T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | Feature | M | P1 | A2, R2, T1 | none | -| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs | ID-1, ID-2, ID-3, ID-8, WB-1 | Feature | L | P2 | T6, R3, T1, T5, #1518 | T9 | -| T9 | Spin EC lifecycle, the same scope as T8 | ID-1, ID-2, ID-3, ID-8, WB-1 | Feature | L | P3 | T8, R4; DEC-14 | none | -| T10 | Rate limits: batch sync per partner, pull sync, provider lookups, record creation | ID-6 | Feature | M | P1 | R2; DEC-11 | T11 | -| T11 | Providers on every supported adapter, awaited before finalization; only follow-up writes after the response | ID-4, ID-5, WB-1 | Feature | L | P1 | A2, T10, R2, #1553; PR #1487 | A5, T14 | -| T12 | Refuse record creation from non-browser clients | ID-7 | Feature | M | P2 | A2 | none | -| T13 | SharedID cookie write-back, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | Feature | M | P1 | T3, A2; PR #1488; DEC-7 | none | -| T14 | Mirror cookie on Axum, then Cloudflare, then Spin | WB-4 | Feature | S | P2 | T11; PR #1489; DEC-10 | none | -| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P2 | PR #1489 | none | -| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------- | ---- | --- | ------------------------------- | ------------ | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T7, T8 | +| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | +| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | #1189; PR #1488 | T13 | +| T4 | Build shared state once per process on Fastly and Axum: settings, partner registry (Axum builds it per request), store clients; keep Axum per-request HTTP client unless measured | SH-1, SH-3 | Task | M | P2 | #1507 | none | +| T5 | Identity conformance suite for Fastly and Axum, plus a startup-rejection test where identity is unsupported | ID-1 | Task | M | P1 | #1507 | T8 | +| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup | ID-2 | Feature | L | P1 | R1, #1522, #1139 | A2, T8 | +| T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | Feature | M | P1 | A2, R2, T1 | none | +| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3, SH-4 | Feature | L | P2 | T6, R3, T1, T5, #1518 | T9, T17 | +| T9 | Spin EC lifecycle, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3 | Feature | L | P3 | T8, R4; DEC-14 | T18 | +| T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; shared counter across Axum tasks | ID-6 | Feature | M | P1 | R2; DEC-11 | T11, T17 | +| T11 | Providers on Fastly and Axum, awaited before finalization; only follow-up writes after the response | ID-4, ID-5, WB-1 | Feature | L | P1 | A2, T10, R2, #1553; PR #1487 | A5, T14, T17 | +| T12 | Refuse record creation from non-browser clients on Fastly and Axum | ID-7 | Feature | M | P2 | A2 | T17 | +| T13 | SharedID cookie write-back on Fastly and Axum, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | Feature | M | P1 | T3, A2; PR #1488; DEC-7 | T17 | +| T14 | Mirror cookie on Axum | WB-4 | Feature | S | P2 | T11; PR #1489; DEC-10 | T17 | +| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P2 | PR #1489 | none | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | +| T17 | Cloudflare identity features: providers, rate limits, abuse protection, SharedID write-back and mirror cookie | ID-4, ID-6, ID-7, WB-3, WB-4 | Feature | L | P2 | T8, R3, T10, T11, T12, T13, T14 | T18 | +| T18 | Spin identity features, the same scope as T17 | ID-4, ID-6, ID-7, WB-3, WB-4 | Feature | L | P3 | T9, R4, T17 | none | Existing, moved under #1558: @@ -395,7 +424,7 @@ These belong to #1505 and IABTechLab/edgezero#436: **Interfaces:** - Consumes: Part B's "Existing, moved" tables and "Linked, not moved" notes. -- Produces: 4 issues under #1236 and 13 under #1558, each typed, sized and +- Produces: 17 issues under #1558 and 4 under IABTechLab/edgezero#468, each typed, sized and prioritized; link comments on IABTechLab/edgezero#468. - [ ] **Step 1: Move each issue** @@ -454,16 +483,16 @@ Expected: each mutation returns the issue or item. Post a `CREATOR:` comment on IABTechLab/edgezero#468 mapping each ID to its issue, and one on IABTechLab/edgezero#391 linking R1. -### Task 3: File the Axum tickets under #1236 +### Task 3: File the Axum tickets under #1558 -Same steps as Task 2, against IABTechLab/trusted-server and #1236, for the +Same steps as Task 2, against IABTechLab/trusted-server and #1558, for the eight new rows in table order. Each body has Summary (Ticket), Requirements (Reqs, linked to the spec section), Acceptance (each requirement's Accept -line) and Blocked by. Record the map in a `CREATOR:` comment on #1236. +line) and Blocked by. Record the map in a `CREATOR:` comment on #1558. ### Task 4: File the Trusted Server tickets under #1558 -Same steps as Task 3, for T1 to T16 in table order, under #1558. Record the +Same steps as Task 3, for T1 to T18 in table order, under #1558. Record the map in a `CREATOR:` comment on #1558. ### Task 5: Link the dependencies @@ -516,8 +545,15 @@ issue from Task 2. - [ ] **Step 1: Count sub-issues** Run `gh api graphql -f query='{repository(owner:"IABTechLab",name:""){issue(number:){subIssues(first:100){totalCount}}}}' --jq '.data.repository.issue.subIssues.totalCount'` -for #1558, #1236 and IABTechLab/edgezero#468. -Expected: 29, 12 and 10. +for #1558 and IABTechLab/edgezero#468. +Expected: 43 and 21. + +- [ ] **Step 3: Check that Fastly and Axum stand alone** + +For every issue in waves 0 to 10, read its blocked-by list and confirm that +none of its blockers is a wave 11 or 12 item (a Cloudflare or Spin ticket, +IABTechLab/edgezero#91, R3 or R4). +Expected: no such blocker (DEC-4). - [ ] **Step 2: Check bodies** diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 31d92bfef..3329a46e0 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -11,9 +11,10 @@ Epics: #1558 (Trusted Server) and IABTechLab/edgezero#468 (EdgeZero). Plan: CloudFront and a load balancer. 2. Server-side identity works on every adapter whose platform can meet the record contract in [Platform support](#platform-support), delivered in the - order Fastly, Axum, Cloudflare, Spin (DEC-4). An adapter that cannot meet - it rejects identity configuration at startup with a clear error and never - runs identity without a record. + order Fastly, Axum, Cloudflare, Spin (DEC-4). Fastly and Axum ship complete + without any Cloudflare or Spin work. An adapter that cannot meet the + contract rejects identity configuration at startup with a clear error and + never runs identity without a record. 3. SharedID is read, stored and passed to bidders the way Prebid defines it, and shared state is passed through each adapter the right way. 4. Identity is written back as first-party cookies on the HTML document @@ -356,13 +357,14 @@ marks the record contract and geo as supported. ### EZI: asks of EdgeZero Behavior and acceptance only. EZI IDs are distinct from #1506's EZ-n -requirement IDs. Each ask is delivered for Fastly and Axum first, then -Cloudflare, then Spin (DEC-4). +requirement IDs. Each ask is split by platform, in DEC-4 order: the Fastly +and Axum parts ship in R1 and R2 and never wait on a Cloudflare or Spin part. - **EZI-1 After-response work.** A task registered during a request runs after - the response is delivered, bounded in time. Accept: on Fastly, Axum and - Cloudflare the task runs after delivery; on Axum SIGTERM waits for - registered tasks up to the grace; Spin returns a capability error. + the response is delivered, bounded in time. Accept: on Fastly and Axum the + task runs after delivery, and on Axum SIGTERM waits for registered tasks up + to the grace; later, on Cloudflare the task runs after delivery, and Spin + returns a capability error. - **EZI-2a Record contract and the Fastly provider.** `KvStore` gains a read that returns a generation marker, writes in `Add`, `Overwrite` and `IfGenerationMatch` modes with a precondition-failed outcome, and strongly @@ -374,18 +376,26 @@ Cloudflare, then Spin (DEC-4). `CelldRecordStore` has the semantics on a separate API; IABTechLab/edgezero#414's DynamoDB provider has strong existence and listing but no CAS; IABTechLab/edgezero#410 and IABTechLab/edgezero#437 rule out conditional writes today. +- **EZI-2c and EZI-2s Cloudflare and Spin stores.** A store on each platform + meets the EZI-2a contract or reports `KvError::Unsupported` (DEC-8). Accept: + the contract suite passes, or startup on that platform rejects identity + configuration. - **EZI-3 Trusted client IP.** One accessor returns the trusted client IP on - every adapter. Accept: the same accessor works on all four adapters. -- **EZI-4 Geo with region.** `ctx.geo()` gives country and region on Fastly, - Cloudflare and Spin, or a capability error. Accept: a US request on Fastly - and Cloudflare reports country and region. -- **EZI-5 Rate limiter.** A limiter with Fastly Edge Rate Limiting, in-memory - and shared-counter backends. Accept: a contract test passes per backend. -- **EZI-6 Releases.** R1 contains IABTechLab/edgezero#275 through IABTechLab/edgezero#439 plus - EZI-2a and EZI-2b. R2 contains EZI-1 and EZI-5 for Fastly and Axum, and - EZI-3. R3 contains the Cloudflare parts of EZI-1, EZI-2, EZI-4 and EZI-5. R4 - contains the Spin parts. Accept: each tag exists and Trusted Server main pins - it. + every adapter, Fastly and Axum first. Accept: the same accessor works on + each adapter as its part lands. +- **EZI-4 Geo with region on Cloudflare and Spin.** `ctx.geo()` gives country + and region there, or a capability error. Fastly keeps its existing geo and + Axum uses CloudFront geo from IABTechLab/edgezero#439. Accept: a US request + on Cloudflare reports country and region. +- **EZI-5 Rate limiter.** A limiter with Fastly Edge Rate Limiting and + in-memory and shared-counter backends for Axum, then Cloudflare and Spin + backends per DEC-11. Accept: a contract test passes per backend. +- **EZI-6 Releases.** R1 contains IABTechLab/edgezero#275 through + IABTechLab/edgezero#439 plus EZI-2a and EZI-2b. R2 contains the Fastly and + Axum parts of EZI-1, EZI-3 and EZI-5. Together R1 and R2 cover Fastly and + Axum. R3 contains the Cloudflare parts of EZI-1 to EZI-5, and R4 the Spin + parts. Accept: each tag exists and Trusted Server main pins it; R1 and R2 + contain no Cloudflare or Spin part. ## Configuration @@ -453,7 +463,9 @@ Decided: - **DEC-3 Shared record store** (user, 2026-11-11). The EC record runs on one `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. - **DEC-4 Platform order** (user, 2026-11-12). Fastly, then Axum, then - Cloudflare, then Spin. + Cloudflare, then Spin. Fastly and Axum must ship complete without any + Cloudflare or Spin work, so every ticket and release is split by platform + and no Fastly or Axum item is blocked by a Cloudflare or Spin item. Proposed and assumed by the plan until confirmed: From be563274c6ef046e66b5ac593027d6021f823d0f Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 07:27:54 -0800 Subject: [PATCH 05/31] Track the EdgeZero Axum store, geo and proxy issues under the identity epic Move IABTechLab/edgezero 397, 410, 432, 437 and 438 under IABTechLab/edgezero 468 in the plan, with type, size, priority and blocked-by links, and point R1, EZI-2b, EZI-4c, EZI-4s and the Axum ClientInfo issue at them instead of at their PRs. --- ...11-11-server-side-identity-all-adapters.md | 183 +++++++++--------- 1 file changed, 94 insertions(+), 89 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 7012f5479..8b3aae628 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -113,56 +113,56 @@ run in priority order, then DEC-4's platform order. TS is Trusted Server and EZ is EdgeZero (`IABTechLab/edgezero#N`). Kinds: New (ticket), Move (existing issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. -| Wave | Item | Pri | Kind | Action | Waits for | -| ---- | -------------------------------------------------------------------------------------------------------------------------------- | --- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | -| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | -| 0 | TS #1526 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | -| 0 | TS #1525 | P1 | PR fix | Fix its round-1 review | none | -| 0 | TS #1516 | P1 | Merge | Already approved | none | -| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | -| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | -| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464, IABTechLab/edgezero#453 | P1 | PR fix | Rebase off the commits they carry | none | -| 0 | TS T2 | P1 | New | EIDs in `user.eids`; EID-absence tests check both fields | none | -| 0 | EZ EZI-5 | P1 | New | Rate limiter for Fastly and Axum | none | -| 0 | TS #1189 | P1 | Move | EID write path skips Purpose 4 gating | none | -| 0 | TS #1142 | P1 | Move | Health and startup contract | none | -| 0 | TS #1503 | P2 | Merge | `ts ec` commands; delivers #1432 | none | -| 0 | TS #1449 | P2 | PR fix | Fix its round-4 review | none | -| 0 | TS #1141, #1139, #1233 | P2 | Move | Axum config store; adapter stores; `ts-ec` to creative assets | none | -| 0 | TS #1192 | P3 | Move | Fastly per-request cost | none | -| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | -| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | -| 1 | TS #1543 | P2 | PR fix | Rebase onto #1449; ships with A6 | #1449 | -| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | -| 2 | EZ EZI-2a, EZI-1 | P1 | New | Record contract with Fastly; after-response work for Fastly and Axum | IABTechLab/edgezero#275 | -| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order | IABTechLab/edgezero#275 | -| 2 | TS T5, T4 | P1 | New | Conformance suite and shared state, Fastly and Axum | #1507 | -| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | -| 3 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1487 | -| 3 | TS #1553, #1549 | P1 | Move | After-response CAS retry; no `user.id` for a row-less EC | PR #1487 | -| 3 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | -| 3 | IABTechLab/edgezero#90, IABTechLab/edgezero#92 | P1 | Move | Fastly and Axum ClientInfo (EZI-3) | none; PR IABTechLab/edgezero#409 | -| 3 | TS #1246 | P2 | Move | Lockr IDs to KV | PR #1487 | -| 4 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1489 | -| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | -| 4 | TS T15, #1551 | P2 | New, Move | Identity-only tsjs; pull sync on a full row | PR #1489 | -| 5 | EZ R1, R2 | P1 | Release | As Part B defines | EZI-2a, EZI-2b, IABTechLab/edgezero#406 to IABTechLab/edgezero#439; EZI-1, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92 | -| 5 | TS T3 | P1 | New | SharedID cleanup | PR #1488, #1189 | -| 5 | TS #1555, #1556 | P2 | Move | Opt-out EIDs; backend certificate checks | PR #1488 (#1556 also #1507) | -| 5 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | -| 6 | TS A1 | P1 | New, PR fix | Port #1528, repin to R1 | R1, #1507, #1141 | -| 6 | TS T6, T10 | P1 | New | Shared record backend, Fastly first; rate limits | R1, #1522, #1139; R2 | -| 7 | TS #1529 (A4), A6 with #1543, A3, A9 | P1 | Move, New | CloudFront IP and geo; production image; SIGTERM drain; env names | A1 (A3 also R2) | -| 7 | TS #1527, #1523 | P2 | Close | Tests moved to #1506 | #1506, A1 | -| 8 | TS A2, A7 | P1 | New | Axum EC lifecycle; reference infrastructure | T6, A1, #1529; A1, A6, #1142 | -| 9 | TS T11, T7, T13 | P1 | New | Providers, Axum partner APIs and SharedID write-back on Fastly and Axum | A2 plus their Part B blockers | -| 9 | TS T12, A8 | P2 | New | Abuse protection; SIGTERM test in CI | A2 | -| 9 | TS #1541, #1424, #1499, #1531 to #1540 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | -| 10 | TS T14, A5 | P2 | New | Mirror cookie on Axum; load test | T11, PR #1489; A2, A7, T11 | -| 11 | EZ EZI-1c, EZI-2c, EZI-4c, EZI-5c and IABTechLab/edgezero#91, then R3; then TS #1518, T8, T17 | P2 | New, Move, Release | Cloudflare parts in EdgeZero; Cloudflare release; geo adoption check; Cloudflare EC lifecycle; Cloudflare providers, limits and cookies | Part B blockers, DEC-8 | -| 12 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s and EZI-5s, then R4; then TS T9, T18 | P3 | New, Release | Spin parts in EdgeZero; Spin release; Spin EC lifecycle; Spin providers, limits and cookies | Part B blockers, DEC-8, DEC-14 | -| any | TS T16, TS #1245, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; IABTechLab/edgezero#287 last | DEC-13; DEC-9; IABTechLab/edgezero#275 | +| Wave | Item | Pri | Kind | Action | Waits for | +| ---- | -------------------------------------------------------------------------------------------------------------------------------- | --- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | +| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | +| 0 | TS #1526 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | +| 0 | TS #1525 | P1 | PR fix | Fix its round-1 review | none | +| 0 | TS #1516 | P1 | Merge | Already approved | none | +| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | +| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | +| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464, IABTechLab/edgezero#453 | P1 | PR fix | Rebase off the commits they carry | none | +| 0 | TS T2 | P1 | New | EIDs in `user.eids`; EID-absence tests check both fields | none | +| 0 | EZ EZI-5 | P1 | New | Rate limiter for Fastly and Axum | none | +| 0 | TS #1189 | P1 | Move | EID write path skips Purpose 4 gating | none | +| 0 | TS #1142 | P1 | Move | Health and startup contract | none | +| 0 | TS #1503 | P2 | Merge | `ts ec` commands; delivers #1432 | none | +| 0 | TS #1449 | P2 | PR fix | Fix its round-4 review | none | +| 0 | TS #1141, #1139, #1233 | P2 | Move | Axum config store; adapter stores; `ts-ec` to creative assets | none | +| 0 | TS #1192 | P3 | Move | Fastly per-request cost | none | +| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | +| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | +| 1 | TS #1543 | P2 | PR fix | Rebase onto #1449; ships with A6 | #1449 | +| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | +| 2 | EZ EZI-2a, EZI-1 | P1 | New | Record contract with Fastly; after-response work for Fastly and Axum | IABTechLab/edgezero#275 | +| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | +| 2 | TS T5, T4 | P1 | New | Conformance suite and shared state, Fastly and Axum | #1507 | +| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | +| 3 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1487 | +| 3 | TS #1553, #1549 | P1 | Move | After-response CAS retry; no `user.id` for a row-less EC | PR #1487 | +| 3 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | +| 3 | IABTechLab/edgezero#90, IABTechLab/edgezero#92 | P1 | Move | Fastly and Axum ClientInfo (EZI-3) | none; PR IABTechLab/edgezero#409 | +| 3 | TS #1246 | P2 | Move | Lockr IDs to KV | PR #1487 | +| 4 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1489 | +| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | +| 4 | TS T15, #1551 | P2 | New, Move | Identity-only tsjs; pull sync on a full row | PR #1489 | +| 5 | EZ R1, R2 | P1 | Release | As Part B defines | EZI-2a, EZI-2b, IABTechLab/edgezero#406 to IABTechLab/edgezero#439; EZI-1, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92 | +| 5 | TS T3 | P1 | New | SharedID cleanup | PR #1488, #1189 | +| 5 | TS #1555, #1556 | P2 | Move | Opt-out EIDs; backend certificate checks | PR #1488 (#1556 also #1507) | +| 5 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | +| 6 | TS A1 | P1 | New, PR fix | Port #1528, repin to R1 | R1, #1507, #1141 | +| 6 | TS T6, T10 | P1 | New | Shared record backend, Fastly first; rate limits | R1, #1522, #1139; R2 | +| 7 | TS #1529 (A4), A6 with #1543, A3, A9 | P1 | Move, New | CloudFront IP and geo; production image; SIGTERM drain; env names | A1 (A3 also R2) | +| 7 | TS #1527, #1523 | P2 | Close | Tests moved to #1506 | #1506, A1 | +| 8 | TS A2, A7 | P1 | New | Axum EC lifecycle; reference infrastructure | T6, A1, #1529; A1, A6, #1142 | +| 9 | TS T11, T7, T13 | P1 | New | Providers, Axum partner APIs and SharedID write-back on Fastly and Axum | A2 plus their Part B blockers | +| 9 | TS T12, A8 | P2 | New | Abuse protection; SIGTERM test in CI | A2 | +| 9 | TS #1541, #1424, #1499, #1531 to #1540 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | +| 10 | TS T14, A5 | P2 | New | Mirror cookie on Axum; load test | T11, PR #1489; A2, A7, T11 | +| 11 | EZ EZI-1c, EZI-2c, EZI-4c, EZI-5c and IABTechLab/edgezero#91, then R3; then TS #1518, T8, T17 | P2 | New, Move, Release | Cloudflare parts in EdgeZero; Cloudflare release; geo adoption check; Cloudflare EC lifecycle; Cloudflare providers, limits and cookies | Part B blockers, DEC-8 | +| 12 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s and EZI-5s, then R4; then TS T9, T18 | P3 | New, Release | Spin parts in EdgeZero; Spin release; Spin EC lifecycle; Spin providers, limits and cookies | Part B blockers, DEC-8, DEC-14 | +| any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP with PR IABTechLab/edgezero#453; IABTechLab/edgezero#287 last | DEC-13; DEC-9; PR IABTechLab/edgezero#453; IABTechLab/edgezero#275 | Waves 0 to 10 complete Fastly and Axum. None of them waits on a Cloudflare or Spin item, so Fastly and Axum ship without waves 11 and 12 (DEC-4). @@ -188,43 +188,46 @@ separately. No ticket mixes Fastly or Axum work with Cloudflare or Spin work New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| ------ | ----------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | ---------------------------------------------------------------------- | -------------------------- | -| EZI-2a | `KvStore` record contract and the Fastly provider | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1 | -| EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a; PRs IABTechLab/edgezero#463 and IABTechLab/edgezero#414 | R1 | -| EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PR IABTechLab/edgezero#275 | EZI-1c, EZI-1s, R2 | -| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and in-memory and shared-counter backends for Axum | EZI-5 | Feature | M | P1 | none | EZI-5c, EZI-5s, R2 | -| EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | -| EZI-2c | Cloudflare store meets the record contract, or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a; DEC-8 | R3 | -| EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | PR IABTechLab/edgezero#439 | R3 | -| EZI-5c | Rate limiter backend for Cloudflare | EZI-5 | Feature | S | P2 | EZI-5; DEC-11 | R3 | -| EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | -| EZI-2s | Spin store meets the record contract, or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a; DEC-8 | R4 | -| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | none | R4, IABTechLab/edgezero#89 | -| EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | PR IABTechLab/edgezero#439 | R4 | -| EZI-5s | Rate limiter backend for Spin | EZI-5 | Feature | S | P3 | EZI-5; DEC-11 | R4 | -| R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 plus EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1, T6 | -| R2 | Release the Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-6 | Task | S | P1 | EZI-1, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92 | A3, T7, T10, T11 | -| R3 | Release the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, EZI-5c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | -| R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s, R3 | T9, T18 | - -Existing, moved under IABTechLab/edgezero#468 from IABTechLab/edgezero#88 -(these are EZI-3's issues; the IABTechLab/edgezero#88 epic itself stays where -it is): - -| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | -| ---------------------- | ------------------------------------------------------------------- | ------------ | ---- | --- | ------------------------------------------------------------------------------ | -------------------------- | -| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | M | P2 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | -| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | none | R2, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | PR IABTechLab/edgezero#409 | R2, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | none | R3, IABTechLab/edgezero#89 | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| ------ | ----------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | +| EZI-2a | `KvStore` record contract and the Fastly provider | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1 | +| EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | R1 | +| EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PR IABTechLab/edgezero#275 | EZI-1c, EZI-1s, R2 | +| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and in-memory and shared-counter backends for Axum | EZI-5 | Feature | M | P1 | none | EZI-5c, EZI-5s, R2 | +| EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | +| EZI-2c | Cloudflare store meets the record contract, or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a; DEC-8 | R3 | +| EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | IABTechLab/edgezero#432 | R3 | +| EZI-5c | Rate limiter backend for Cloudflare | EZI-5 | Feature | S | P2 | EZI-5; DEC-11 | R3 | +| EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | +| EZI-2s | Spin store meets the record contract, or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a; DEC-8 | R4 | +| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | none | R4, IABTechLab/edgezero#89 | +| EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | +| EZI-5s | Rate limiter backend for Spin | EZI-5 | Feature | S | P3 | EZI-5; DEC-11 | R4 | +| R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 plus EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b, IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1, T6 | +| R2 | Release the Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-6 | Task | S | P1 | EZI-1, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92 | A3, T7, T10, T11 | +| R3 | Release the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, EZI-5c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | +| R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s, R3 | T9, T18 | + +Existing, moved under IABTechLab/edgezero#468. IABTechLab/edgezero#89 to IABTechLab/edgezero#92 leave +IABTechLab/edgezero#88 and the last five leave IABTechLab/edgezero#391 or have +no parent; both old epics stay where they are: + +| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | +| ----------------------- | ------------------------------------------------------------------------------------------------------ | ------------ | ---- | --- | ------------------------------------------------------------------------------ | -------------------------- | +| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | M | P2 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | +| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | none | R2, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | IABTechLab/edgezero#397 | R2, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | none | R3, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#397 | Axum: safe production diagnostics and trusted-proxy handling (delivered by PR IABTechLab/edgezero#409) | Task (keep) | M | P1 | none | R1, IABTechLab/edgezero#92 | +| IABTechLab/edgezero#410 | Native AWS configuration, secret and KV store providers (PR IABTechLab/edgezero#414) | Task (keep) | L | P1 | none | EZI-2b, R1 | +| IABTechLab/edgezero#437 | Native celld configuration and KV store providers (PR IABTechLab/edgezero#422) | Task (keep) | M | P1 | none | EZI-2b, R1 | +| IABTechLab/edgezero#432 | Axum: protected CloudFront Geo metadata provider (PR IABTechLab/edgezero#439) | Story (keep) | M | P1 | none | R1, EZI-4c, EZI-4s | +| IABTechLab/edgezero#438 | Axum: explicit local GeoIP provider (PR IABTechLab/edgezero#453) | Story (keep) | M | P3 | none | none | EZI-3s (Spin ClientInfo) also blocks IABTechLab/edgezero#89. -Related, left in IABTechLab/edgezero#391 because they belong to that epic's -Axum production scope: IABTechLab/edgezero#432 and IABTechLab/edgezero#438 -(EZI-4), IABTechLab/edgezero#410 and IABTechLab/edgezero#437 (EZI-2b), and -IABTechLab/edgezero#397 (EZI-3). R1 is linked from IABTechLab/edgezero#391. +The last five rows leave IABTechLab/edgezero#391, which stays an epic in place; +R1 is linked from it. ### Axum deployable, under #1558 @@ -424,7 +427,7 @@ These belong to #1505 and IABTechLab/edgezero#436: **Interfaces:** - Consumes: Part B's "Existing, moved" tables and "Linked, not moved" notes. -- Produces: 17 issues under #1558 and 4 under IABTechLab/edgezero#468, each typed, sized and +- Produces: 17 issues under #1558 and 9 under IABTechLab/edgezero#468, each typed, sized and prioritized; link comments on IABTechLab/edgezero#468. - [ ] **Step 1: Move each issue** @@ -442,10 +445,12 @@ Priority and Size through `updateProjectV2ItemFieldValue` with the IDs in Global Constraints. Expected: each mutation returns the item. -- [ ] **Step 3: Link without moving** +- [ ] **Step 3: Update the EdgeZero epics' text** -Post a `CREATOR:` comment on IABTechLab/edgezero#468 listing the linked -issues and the EZI ID each relates to. +Edit IABTechLab/edgezero#468's Related section to say that IABTechLab/edgezero#391 stays +the Axum production epic and that the five issues moved from it or with no +parent are tracked here. Post a `CREATOR:` comment on IABTechLab/edgezero#391 and on +IABTechLab/edgezero#88 naming the issues that moved to IABTechLab/edgezero#468. ### Task 2: File the EdgeZero tickets under IABTechLab/edgezero#468 @@ -546,7 +551,7 @@ issue from Task 2. Run `gh api graphql -f query='{repository(owner:"IABTechLab",name:""){issue(number:){subIssues(first:100){totalCount}}}}' --jq '.data.repository.issue.subIssues.totalCount'` for #1558 and IABTechLab/edgezero#468. -Expected: 43 and 21. +Expected: 43 and 26. - [ ] **Step 3: Check that Fastly and Axum stand alone** From 0a579977b9089a8d23365c55e86303eb1744f4cb Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 07:36:26 -0800 Subject: [PATCH 06/31] Address review round 2 on the identity spec and plan Start the wave-1 fallback clock from the Trusted Server merges, make SH-1, ID-5 and WB-3 acceptance match their exceptions, pin the RC-unit baseline and heads, restore the decision gates in the ticket cells, re-parent moved issues in Task 1, and record round 2 in the review log. --- ...11-11-server-side-identity-all-adapters.md | 55 ++++++++++++------- ...erver-side-identity-all-adapters-design.md | 34 +++++++++--- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 8b3aae628..e5f99c9d0 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -151,7 +151,7 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 5 | TS T3 | P1 | New | SharedID cleanup | PR #1488, #1189 | | 5 | TS #1555, #1556 | P2 | Move | Opt-out EIDs; backend certificate checks | PR #1488 (#1556 also #1507) | | 5 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | -| 6 | TS A1 | P1 | New, PR fix | Port #1528, repin to R1 | R1, #1507, #1141 | +| 6 | TS A1 | P1 | New, PR fix | Port #1528, repin to R1 | R1, #1507, #1141; DEC-5, DEC-6 | | 6 | TS T6, T10 | P1 | New | Shared record backend, Fastly first; rate limits | R1, #1522, #1139; R2 | | 7 | TS #1529 (A4), A6 with #1543, A3, A9 | P1 | Move, New | CloudFront IP and geo; production image; SIGTERM drain; env names | A1 (A3 also R2) | | 7 | TS #1527, #1523 | P2 | Close | Tests moved to #1506 | #1506, A1 | @@ -167,8 +167,8 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. Waves 0 to 10 complete Fastly and Axum. None of them waits on a Cloudflare or Spin item, so Fastly and Axum ship without waves 11 and 12 (DEC-4). -Fallback for wave 1: if the fold-in has not landed two weeks after wave 0 -ends, #1506 merges pinned to IABTechLab/edgezero#275's head as it stands +Fallback for wave 1: if the fold-in has not landed two weeks after #1557, +#1526 and #1516 have all merged on main, #1506 merges pinned to IABTechLab/edgezero#275's head as it stands (allowed by AX-7) and keeps its readthrough rejection until the repin. The two longest chains: @@ -233,22 +233,22 @@ R1 is linked from it. New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ----------------------------------------------------------------------------------------------------------- | ---------------------- | ------- | ---- | --- | ---------------- | ------------------------- | -| A1 | Serve Axum in production mode from main: port #1528 onto #1506's outbound API, repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507, #1141 | A2, A3, A6, A7, A9, #1529 | -| A2 | Axum EC lifecycle on the shared backend, forward-ported from #1536 and #1537 | ID-1, ID-3, ID-8, WB-1 | Feature | L | P1 | T6, A1, #1529 | A5, A8, T7, T11, T12, T13 | -| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | -| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P2 | A2, A7, T11 | none | -| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1 | A7 | -| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on | AX-3, AX-6 | Feature | L | P1 | A1, A6, #1142 | A5 | -| A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P2 | A2 | none | -| A9 | `TRUSTED_SERVER__` environment names only | AX-2 | Task | S | P2 | A1 | none | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ----------------------------------------------------------------------------------------------------------- | ---------------------- | ------- | ---- | --- | ------------------------------ | ------------------------- | +| A1 | Serve Axum in production mode from main: port #1528 onto #1506's outbound API, repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507, #1141; DEC-5, DEC-6 | A2, A3, A6, A7, A9, #1529 | +| A2 | Axum EC lifecycle on the shared backend, forward-ported from #1536 and #1537 | ID-1, ID-3, ID-8, WB-1 | Feature | L | P1 | T6, A1, #1529; DEC-6 | A5, A8, T7, T11, T12, T13 | +| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | +| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P2 | A2, A7, T11 | none | +| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; DEC-6 | A7 | +| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on | AX-3, AX-6 | Feature | L | P1 | A1, A6, #1142; DEC-6, DEC-11 | A5 | +| A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P2 | A2 | none | +| A9 | `TRUSTED_SERVER__` environment names only | AX-2 | Task | S | P2 | A1 | none | Existing, moved under #1558: | Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | | ----- | ------------------------------------------------------------ | ----------- | ---- | --- | ---------- | ------ | -| #1529 | Extract the protected CloudFront native Geo integration (A4) | Task (keep) | M | P1 | A1 | A2 | +| #1529 | Extract the protected CloudFront native Geo integration (A4) | Task (keep) | M | P1 | A1; DEC-6 | A2 | | #1142 | Uniform cross-adapter health and startup-failure contract | Task (keep) | M | P1 | none | A7 | | #1141 | Make Axum consume the local config store | Bug (keep) | S | P2 | none | A1 | | #1139 | Implement adapter stores or retire dead store manifests | Bug (keep) | M | P2 | none | T6 | @@ -266,8 +266,8 @@ New: | T5 | Identity conformance suite for Fastly and Axum, plus a startup-rejection test where identity is unsupported | ID-1 | Task | M | P1 | #1507 | T8 | | T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup | ID-2 | Feature | L | P1 | R1, #1522, #1139 | A2, T8 | | T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | Feature | M | P1 | A2, R2, T1 | none | -| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3, SH-4 | Feature | L | P2 | T6, R3, T1, T5, #1518 | T9, T17 | -| T9 | Spin EC lifecycle, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3 | Feature | L | P3 | T8, R4; DEC-14 | T18 | +| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3, SH-4 | Feature | L | P2 | T6, R3, T1, T5, #1518; DEC-8 | T9, T17 | +| T9 | Spin EC lifecycle, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | | T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; shared counter across Axum tasks | ID-6 | Feature | M | P1 | R2; DEC-11 | T11, T17 | | T11 | Providers on Fastly and Axum, awaited before finalization; only follow-up writes after the response | ID-4, ID-5, WB-1 | Feature | L | P1 | A2, T10, R2, #1553; PR #1487 | A5, T14, T17 | | T12 | Refuse record creation from non-browser clients on Fastly and Axum | ID-7 | Feature | M | P2 | A2 | T17 | @@ -359,7 +359,14 @@ Each item names the head it was checked at. Post it as a comment on that PR - **#1543 at `dc1a063d8` (AX-5).** Rebase onto #1449. It publishes the dev-server image (`STOPSIGNAL SIGINT`), so it ships with A6's production image contract. -- **RC units, fixed in their forward-ports.** #1532: drop the core change that +- **RC units, fixed in their forward-ports.** These were checked at the RC tag + `v0.1.1-rc-axum.202611.2` (`7c6e68d94`), which contains every unit. They are + forward-port notes: each applies to the PR that ports its unit to main, and + Task 6 posts it there once that PR exists, checked at its head. On the RC unit + PRs themselves Task 6 posts a note only after re-checking it at the unit's + head, read on 2026-11-12 as #1532 `852b875fe`, #1536 `296e05405`, #1537 + `4a838272c`, #1539 `8d41cb06c`, #1540 `eb6e19ef4` and #1541 `7dd2e3056`. + #1532: drop the core change that maps a US request with no region to `Unknown`, which fails every US Cloudflare request closed (AX-3). #1536: do not port the adapter-local DynamoDB store (ID-2, AX-4). #1537: its example config breaks startup on @@ -432,10 +439,16 @@ These belong to #1505 and IABTechLab/edgezero#436: - [ ] **Step 1: Move each issue** -For each row, run -`gh api graphql -f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c}){issue{number}}}' -F p="$(gh issue view --json id --jq .id)" -F c="$(gh issue view --json id --jq .id)"`. -Expected: the epic's number. A row whose issue already has a parent is -reported, not moved. +For each row, first read the issue's current parent with +`gh api graphql -f query='{repository(owner:"",name:""){issue(number:){parent{number}}}}' --jq '.data.repository.issue.parent.number'`. +The expected parent is IABTechLab/edgezero#88 for IABTechLab/edgezero#89 to +IABTechLab/edgezero#92, IABTechLab/edgezero#391 for IABTechLab/edgezero#397, +IABTechLab/edgezero#410 and IABTechLab/edgezero#432, and none for every other +row. Stop and report a row whose parent differs. Then run +`gh api graphql -f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c,replaceParent:true}){issue{number}}}' -F p="$(gh issue view --json id --jq .id)" -F c="$(gh issue view --json id --jq .id)"`, +and read the parent again. +Expected: the new epic's number, and every moved issue's parent is now that +epic. - [ ] **Step 2: Type, Priority and Size** diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 3329a46e0..321b7ca1d 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -48,8 +48,8 @@ Two lines of code carry Axum today. Cloudflare and Spin build an `EcContext` for consent alone, with no record store and no `ts-ec` cookie. They build a partner registry per request, for `/_ts/admin/eids` only. -- **Axum RC line.** Tag `v0.1.1-rc-axum.202611.2` is the head of draft #1541 - (`7c6e68d94`). It adds production startup, the EC record in DynamoDB and +- **Axum RC line.** Tag `v0.1.1-rc-axum.202611.2` (`7c6e68d94`) was the head of + draft #1541 when read on 2026-11-11; #1541 has moved since. It adds production startup, the EC record in DynamoDB and celld, CloudFront geo, a container image and AWS examples. It branches from main at `2dd968aee` (2026-10-20), carries 74 commits that are not on main and is 29 commits behind it. It pins EdgeZero tag @@ -223,9 +223,11 @@ marks the record contract and geo as supported. deadline is skipped and does not delay the response past it. - **ID-5 After-response work.** Only pull sync and guarded follow-up writes run after the response is sent: after send on Fastly, a task drained on SIGTERM - on Axum, `waitUntil` on Cloudflare. Spin follows DEC-14. Accept: on each - adapter a follow-up write recorded after send is in the record before the - next navigation; on Axum SIGTERM waits for it (A3). + on Axum, `waitUntil` on Cloudflare. Spin follows DEC-14. Accept: on Fastly, + Axum and Cloudflare a follow-up write recorded after send is in the record + before the next navigation, and on Axum SIGTERM waits for it (A3). On Spin + the DEC-14 choice holds: either the follow-up write lands before the + response, or the work is skipped and the response is unchanged. - **ID-6 Rate limiting.** Provider lookups, batch sync, pull sync and record creation are rate limited on every supported adapter. Batch sync is keyed per partner; the other keys follow DEC-11. With more than one Axum task the @@ -249,7 +251,10 @@ marks the record contract and geo as supported. - **SH-1 Built once.** Settings, the partner registry, store clients and the HTTP client are built once per process or isolate. Accept: an adapter test - shows each built once across two requests. State: Cloudflare and Spin + shows settings, the partner registry and store clients built once across two + requests, and the HTTP client too, except Axum's, which stays per request + until a measurement shows the Next.js regression gone; after that + measurement Axum's client is held to the same rule. State: Cloudflare and Spin rebuild app state per request; Axum builds the partner registry per request (`app.rs:515` at #1506's head) and the HTTP client per request on purpose, because a pooled client regressed a Next.js flow (main `platform.rs:571-575`). @@ -331,7 +336,8 @@ marks the record contract and geo as supported. Prebid rewrite the cookie from script, which Safari caps. Prebid's `extend` with a `pixelUrl` keeps working alongside it. Accept: record present and cookie absent gives both cookies with those attributes; an equal cookie - gives no `Set-Cookie`; Purpose 1 denied or `_pubcid_optout` expires both; + with a consistent `_cst` gives no `Set-Cookie`; an equal cookie with a + missing or different `_cst` re-sets only the companion; Purpose 1 denied or `_pubcid_optout` expires both; Purpose 4 denied with Purpose 1 granted gives no `Set-Cookie` and leaves the browser's cookie alone. State: no branch writes it. - **WB-4 Mirror cookie.** `__Host-ts-eids-mirror` as in #1489: opt-in per @@ -517,4 +523,16 @@ revisions, not from PR descriptions: Reviews 5871533635 and 5871619954 at `00df85414`, from fresh agent sessions covering factual claims, consistency and coverage, merge-order feasibility, and repository policy. 34 findings: 8 blocking, 17 should-fix, 9 nits. This -revision addresses them; each thread holds its disposition. Round 2 is pending. +revision addresses them; each thread holds its disposition. + +### Round 2, 2026-11-12 + +Review 5875159165 at `7cb8933f7`. It accepted and resolved 28 round-1 +findings and left six open: the fallback clock (thread 1), the review log and +draft gate (7), SH-1's acceptance against the Axum HTTP client exception (14), +the RC-unit baseline (15), ID-5's acceptance on Spin (18) and decision gates +missing from plan cells (23). It added two should-fix findings: Task 1 must +re-parent the EdgeZero issues it moves (35), and WB-3's no-write case must +require a consistent `_cst` (36). No blocking finding. The revision after +it addresses all eight; round 3 is pending, and the PR stays in draft until a +round finds nothing blocking. From 5c3181c6b567065321030780603161cf0f94df55 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 07:40:06 -0800 Subject: [PATCH 07/31] Name Fastly and Axum as the identity MVP Record in the spec's goals, rollout and DEC-4 that Fastly and Axum are the MVP and Cloudflare and Spin follow it, and mark waves 0 to 10 and their tickets as MVP in the plan. --- ...26-11-11-server-side-identity-all-adapters.md | 10 ++++++++-- ...1-server-side-identity-all-adapters-design.md | 16 +++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index e5f99c9d0..957f07d3c 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -164,8 +164,10 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 12 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s and EZI-5s, then R4; then TS T9, T18 | P3 | New, Release | Spin parts in EdgeZero; Spin release; Spin EC lifecycle; Spin providers, limits and cookies | Part B blockers, DEC-8, DEC-14 | | any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP with PR IABTechLab/edgezero#453; IABTechLab/edgezero#287 last | DEC-13; DEC-9; PR IABTechLab/edgezero#453; IABTechLab/edgezero#275 | -Waves 0 to 10 complete Fastly and Axum. None of them waits on a Cloudflare or -Spin item, so Fastly and Axum ship without waves 11 and 12 (DEC-4). +Waves 0 to 10 are the MVP: they complete Fastly and Axum. None of them waits +on a Cloudflare or Spin item, so the MVP ships without waves 11 and 12 (DEC-4). +Wave 11 (Cloudflare), wave 12 (Spin) and the decision-gated row follow the +MVP. Fallback for wave 1: if the fold-in has not landed two weeks after #1557, #1526 and #1516 have all merged on main, #1506 merges pinned to IABTechLab/edgezero#275's head as it stands @@ -179,6 +181,10 @@ The two longest chains: ## Part B: tickets +Every ticket in waves 0 to 10 is MVP; the Cloudflare and Spin tickets (T8, +T9, T17, T18, the EZI `c` and `s` tickets, R3, R4, IABTechLab/edgezero#91, +IABTechLab/edgezero#89 and #1518) and the decision-gated row are post-MVP. + "Blocked by" is set as a GitHub dependency between issues. A PR or a decision there goes in the issue body. "Blocks" is the inverse and is not set separately. No ticket mixes Fastly or Axum work with Cloudflare or Spin work diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 321b7ca1d..6d7b55481 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -11,8 +11,8 @@ Epics: #1558 (Trusted Server) and IABTechLab/edgezero#468 (EdgeZero). Plan: CloudFront and a load balancer. 2. Server-side identity works on every adapter whose platform can meet the record contract in [Platform support](#platform-support), delivered in the - order Fastly, Axum, Cloudflare, Spin (DEC-4). Fastly and Axum ship complete - without any Cloudflare or Spin work. An adapter that cannot meet the + order Fastly, Axum, Cloudflare, Spin (DEC-4). Fastly and Axum are the MVP + and ship complete without any Cloudflare or Spin work. An adapter that cannot meet the contract rejects identity configuration at startup with a clear error and never runs identity without a record. 3. SharedID is read, stored and passed to bidders the way Prebid defines it, @@ -445,8 +445,9 @@ and Axum parts ship in R1 and R2 and never wait on a Cloudflare or Spin part. `pubcid.org`, atype 1, on their next write (T3). There is no bulk migration. - `user.ext.eids` stays only for the downstreams T2 names. - `ts-ec` format and attributes do not change. -- Adapters gain identity in the order Fastly, Axum, Cloudflare, Spin. Until an - adapter's tickets land, its startup rejects identity configuration. +- Adapters gain identity in the order Fastly, Axum, Cloudflare, Spin. The MVP + is Fastly and Axum; Cloudflare and Spin follow it. Until an adapter's + tickets land, its startup rejects identity configuration. - The RC tag keeps its current use until main reaches parity; RC units close as their ports land. @@ -469,9 +470,10 @@ Decided: - **DEC-3 Shared record store** (user, 2026-11-11). The EC record runs on one `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. - **DEC-4 Platform order** (user, 2026-11-12). Fastly, then Axum, then - Cloudflare, then Spin. Fastly and Axum must ship complete without any - Cloudflare or Spin work, so every ticket and release is split by platform - and no Fastly or Axum item is blocked by a Cloudflare or Spin item. + Cloudflare, then Spin. Fastly and Axum are the MVP and must ship complete + without any Cloudflare or Spin work, so every ticket and release is split by + platform and no Fastly or Axum item is blocked by a Cloudflare or Spin item. + Cloudflare and Spin follow the MVP. Proposed and assumed by the plan until confirmed: From fbdfbedef5d04634644dff4d9e87eb847f6f500a Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 07:45:24 -0800 Subject: [PATCH 08/31] Drop references to work outside the identity plan Remove the scope exclusion and the related issue note from the spec and plan, and reuse the DEC-2 slot for the MVP decision: Fastly and Axum are the MVP. --- .../2026-11-11-server-side-identity-all-adapters.md | 5 +---- ...-11-11-server-side-identity-all-adapters-design.md | 11 ++++------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 957f07d3c..f5f5a90a0 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -48,7 +48,6 @@ IABTechLab/edgezero. body. - An issue Part B names for reuse is reused, never duplicated. - EdgeZero code is never changed from here; EdgeZero items are requests. -- Trusted Server PRs #1342 to #1423 and #1448 are out of scope (DEC-2). - Issue and comment text uses no em dashes and no names of people; comments start with `CREATOR: `. @@ -164,7 +163,7 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 12 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s and EZI-5s, then R4; then TS T9, T18 | P3 | New, Release | Spin parts in EdgeZero; Spin release; Spin EC lifecycle; Spin providers, limits and cookies | Part B blockers, DEC-8, DEC-14 | | any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP with PR IABTechLab/edgezero#453; IABTechLab/edgezero#287 last | DEC-13; DEC-9; PR IABTechLab/edgezero#453; IABTechLab/edgezero#275 | -Waves 0 to 10 are the MVP: they complete Fastly and Axum. None of them waits +Waves 0 to 10 are the MVP (DEC-2): they complete Fastly and Axum. None of them waits on a Cloudflare or Spin item, so the MVP ships without waves 11 and 12 (DEC-4). Wave 11 (Cloudflare), wave 12 (Spin) and the decision-gated row follow the MVP. @@ -311,8 +310,6 @@ Not moved: - Proposed closures: #1523 with #1527; IABTechLab/trusted-server#353 and #1310 once PR #1488 merges. - Out of these epics: #1484 (auction), #1140 (Cloudflare config), #1164 (Spin tooling), IABTechLab/trusted-server#518 (cleanup). -- #1134, #1132 and #1125 overlap this work but stay out pending a decision on - whether DEC-2's exclusion covers issues. Requirements met inside open PRs, not by a ticket: ID-8 on main and SH-2 (#1487), SID-1 to SID-3 and SID-6 for the provider, SID-5 (#1488), WB-4 diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 6d7b55481..cb8682ed5 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -12,7 +12,7 @@ Epics: #1558 (Trusted Server) and IABTechLab/edgezero#468 (EdgeZero). Plan: 2. Server-side identity works on every adapter whose platform can meet the record contract in [Platform support](#platform-support), delivered in the order Fastly, Axum, Cloudflare, Spin (DEC-4). Fastly and Axum are the MVP - and ship complete without any Cloudflare or Spin work. An adapter that cannot meet the + (DEC-2) and ship complete without any Cloudflare or Spin work. An adapter that cannot meet the contract rejects identity configuration at startup with a clear error and never runs identity without a record. 3. SharedID is read, stored and passed to bidders the way Prebid defines it, @@ -35,8 +35,6 @@ cap. when it needs one. This design fixes the requirements, the platform support matrix and the order. - The EdgeZero cutover is #1506 (DEC-1). -- Trusted Server PRs #1342 to #1423, and #1448 stacked on them, are out of - scope (DEC-2). ## Where things stand @@ -457,7 +455,6 @@ and Axum parts ship in R1 and R2 and never wait on a Cloudflare or Spin part. on a request that is not a navigation. - Conformance with the SharedID Edge specification. - An Akamai adapter. -- Trusted Server PRs #1342 to #1423 and #1448 (DEC-2). ## Decisions @@ -465,12 +462,12 @@ Decided: - **DEC-1 Cutover** (user, 2026-11-11). #1506 is the EdgeZero cutover. #1527 closes once #1506 covers its tests. -- **DEC-2 Scope** (user, 2026-11-11). Trusted Server PRs #1342 to #1423, and - #1448 stacked on them, are out of scope. +- **DEC-2 MVP** (user, 2026-11-12). Fastly and Axum are the MVP: plan waves 0 + to 10. Cloudflare, Spin and the decision-gated work follow it. - **DEC-3 Shared record store** (user, 2026-11-11). The EC record runs on one `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. - **DEC-4 Platform order** (user, 2026-11-12). Fastly, then Axum, then - Cloudflare, then Spin. Fastly and Axum are the MVP and must ship complete + Cloudflare, then Spin. Fastly and Axum (the MVP, DEC-2) must ship complete without any Cloudflare or Spin work, so every ticket and release is split by platform and no Fastly or Axum item is blocked by a Cloudflare or Spin item. Cloudflare and Spin follow the MVP. From ba106e554f129554def15d4b26a92c5f57ea3590 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 08:12:55 -0800 Subject: [PATCH 09/31] Correct ticket order, priority, size and MVP scope in the identity plan Reverse the provider stack to #1487, #1488, #1489 and refresh the moved heads. Split the EdgeZero release so Axum serving does not wait for the record contract (R1b), add the ClientInfo contract (EZI-3) and JA4 (IABTechLab/edgezero#283) that ID-7 needs on Axum, and move the Axum side of providers, abuse protection and SharedID write-back into A10 so Fastly no longer waits on the Axum chain. Drop blockers that code at the PR heads shows are already met (#1189, #1556, #1141 before A1), move four-adapter and decision-gated issues out of the MVP, and align priorities and sizes. The MVP is now waves 0 to 9. --- ...11-11-server-side-identity-all-adapters.md | 400 ++++++++++-------- ...erver-side-identity-all-adapters-design.md | 54 ++- 2 files changed, 250 insertions(+), 204 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index f5f5a90a0..c3718435d 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -26,6 +26,9 @@ IABTechLab/edgezero. - Platform order: Fastly, then Axum, then Cloudflare, then Spin (DEC-4). Fastly and Axum ship without Cloudflare or Spin: no Fastly or Axum item is blocked by a Cloudflare or Spin item, and Cloudflare and Spin work has its own tickets. + A Fastly or Axum ticket may still make Cloudflare and Spin reject identity + configuration, answer 501 or report a capability as absent (T1, T6, + IABTechLab/edgezero#283), because that needs no Cloudflare or Spin work. - Only issues move, and they move under the new epics: #1558 for Trusted Server and IABTechLab/edgezero#468 for EdgeZero. Existing epics (#1236, #1505, IABTechLab/trusted-server#90, IABTechLab/edgezero#88, @@ -72,24 +75,24 @@ IABTechLab/edgezero. Trusted Server: -| PR | Head | Base on GitHub | Fix | -| --------------------- | ----------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------- | -| #1557 | `76e1dd5f6` | main | None | -| #1525 | `cd8977cad` | main | None | -| #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | -| #1516 | `214cd4821` | main | None | -| #1503 | `1b59bfbe7` | main | None; needs neither #1506 nor new EdgeZero | -| #1449 | `27361ec58` | main | None; needs neither #1506 nor new EdgeZero | -| #1506 | `851537045` | main | Conflicting; rebase after #1557, #1526 and #1516 | -| #1487 | `4e7a6e02b` | main | Rebase after #1526 and #1506 | -| #1489 | `29de3d446` | #1487's branch | None | -| #1488 | `25f35720b` | #1487's branch, old commit | Restack onto #1489's branch; conflicting | -| #1527 | `a020daeec` | #1526's branch | Close once #1506 covers its tests (DEC-1) | -| #1528 | `ea779fb47` | #1527's branch | Port its two commits onto #1506's outbound API; they modify #1527-only `platform/outbound.rs`; repin off `456313675` | -| #1543 | `dc1a063d8` | #1449's branch | Conflicting; rebase onto #1449; ships with A6 | -| #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | -| #1499 | `084fdbba9` | #1424's branch | Close at parity | -| #1541, #1531 to #1540 | various | RC baselines | Forward-port to main as new PRs; close each as its port lands | +| PR | Head | Base on GitHub | Fix | +| --------------------- | ----------- | -------------- | -------------------------------------------------------------------------------------------------------------------- | +| #1557 | `76e1dd5f6` | main | None | +| #1525 | `cd8977cad` | main | None; #1526 does not need it | +| #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | +| #1516 | `214cd4821` | main | None | +| #1503 | `5e10c24e4` | main | None; needs neither #1506 nor new EdgeZero | +| #1449 | `fd33f2c74` | main | None; needs neither #1506 nor new EdgeZero | +| #1506 | `ba5790b8a` | main | Rebase after #1557, #1526 and #1516 | +| #1487 | `1600781eb` | main | Rebase after #1526 and #1506 | +| #1488 | `7f55776f3` | #1487's branch | None | +| #1489 | `d3780ba13` | #1488's branch | None | +| #1527 | `a020daeec` | #1526's branch | Close once #1506 covers its tests (DEC-1) | +| #1528 | `ea779fb47` | #1527's branch | Port its two commits onto #1506's outbound API; they modify #1527-only `platform/outbound.rs`; repin off `456313675` | +| #1543 | `e5e6ea7fa` | #1449's branch | None; ships with A6 | +| #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | +| #1499 | `084fdbba9` | #1424's branch | Close at parity | +| #1541, #1531 to #1540 | various | RC baselines | Forward-port to main as new PRs; close each as its port lands | EdgeZero: @@ -112,61 +115,74 @@ run in priority order, then DEC-4's platform order. TS is Trusted Server and EZ is EdgeZero (`IABTechLab/edgezero#N`). Kinds: New (ticket), Move (existing issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. -| Wave | Item | Pri | Kind | Action | Waits for | -| ---- | -------------------------------------------------------------------------------------------------------------------------------- | --- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | -| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | -| 0 | TS #1526 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | -| 0 | TS #1525 | P1 | PR fix | Fix its round-1 review | none | -| 0 | TS #1516 | P1 | Merge | Already approved | none | -| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | -| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | -| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464, IABTechLab/edgezero#453 | P1 | PR fix | Rebase off the commits they carry | none | -| 0 | TS T2 | P1 | New | EIDs in `user.eids`; EID-absence tests check both fields | none | -| 0 | EZ EZI-5 | P1 | New | Rate limiter for Fastly and Axum | none | -| 0 | TS #1189 | P1 | Move | EID write path skips Purpose 4 gating | none | -| 0 | TS #1142 | P1 | Move | Health and startup contract | none | -| 0 | TS #1503 | P2 | Merge | `ts ec` commands; delivers #1432 | none | -| 0 | TS #1449 | P2 | PR fix | Fix its round-4 review | none | -| 0 | TS #1141, #1139, #1233 | P2 | Move | Axum config store; adapter stores; `ts-ec` to creative assets | none | -| 0 | TS #1192 | P3 | Move | Fastly per-request cost | none | -| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | -| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | -| 1 | TS #1543 | P2 | PR fix | Rebase onto #1449; ships with A6 | #1449 | -| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | -| 2 | EZ EZI-2a, EZI-1 | P1 | New | Record contract with Fastly; after-response work for Fastly and Axum | IABTechLab/edgezero#275 | -| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | -| 2 | TS T5, T4 | P1 | New | Conformance suite and shared state, Fastly and Axum | #1507 | -| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | -| 3 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1487 | -| 3 | TS #1553, #1549 | P1 | Move | After-response CAS retry; no `user.id` for a row-less EC | PR #1487 | -| 3 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | -| 3 | IABTechLab/edgezero#90, IABTechLab/edgezero#92 | P1 | Move | Fastly and Axum ClientInfo (EZI-3) | none; PR IABTechLab/edgezero#409 | -| 3 | TS #1246 | P2 | Move | Lockr IDs to KV | PR #1487 | -| 4 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1489 | -| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | -| 4 | TS T15, #1551 | P2 | New, Move | Identity-only tsjs; pull sync on a full row | PR #1489 | -| 5 | EZ R1, R2 | P1 | Release | As Part B defines | EZI-2a, EZI-2b, IABTechLab/edgezero#406 to IABTechLab/edgezero#439; EZI-1, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92 | -| 5 | TS T3 | P1 | New | SharedID cleanup | PR #1488, #1189 | -| 5 | TS #1555, #1556 | P2 | Move | Opt-out EIDs; backend certificate checks | PR #1488 (#1556 also #1507) | -| 5 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | -| 6 | TS A1 | P1 | New, PR fix | Port #1528, repin to R1 | R1, #1507, #1141; DEC-5, DEC-6 | -| 6 | TS T6, T10 | P1 | New | Shared record backend, Fastly first; rate limits | R1, #1522, #1139; R2 | -| 7 | TS #1529 (A4), A6 with #1543, A3, A9 | P1 | Move, New | CloudFront IP and geo; production image; SIGTERM drain; env names | A1 (A3 also R2) | -| 7 | TS #1527, #1523 | P2 | Close | Tests moved to #1506 | #1506, A1 | -| 8 | TS A2, A7 | P1 | New | Axum EC lifecycle; reference infrastructure | T6, A1, #1529; A1, A6, #1142 | -| 9 | TS T11, T7, T13 | P1 | New | Providers, Axum partner APIs and SharedID write-back on Fastly and Axum | A2 plus their Part B blockers | -| 9 | TS T12, A8 | P2 | New | Abuse protection; SIGTERM test in CI | A2 | -| 9 | TS #1541, #1424, #1499, #1531 to #1540 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | -| 10 | TS T14, A5 | P2 | New | Mirror cookie on Axum; load test | T11, PR #1489; A2, A7, T11 | -| 11 | EZ EZI-1c, EZI-2c, EZI-4c, EZI-5c and IABTechLab/edgezero#91, then R3; then TS #1518, T8, T17 | P2 | New, Move, Release | Cloudflare parts in EdgeZero; Cloudflare release; geo adoption check; Cloudflare EC lifecycle; Cloudflare providers, limits and cookies | Part B blockers, DEC-8 | -| 12 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s and EZI-5s, then R4; then TS T9, T18 | P3 | New, Release | Spin parts in EdgeZero; Spin release; Spin EC lifecycle; Spin providers, limits and cookies | Part B blockers, DEC-8, DEC-14 | -| any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP with PR IABTechLab/edgezero#453; IABTechLab/edgezero#287 last | DEC-13; DEC-9; PR IABTechLab/edgezero#453; IABTechLab/edgezero#275 | - -Waves 0 to 10 are the MVP (DEC-2): they complete Fastly and Axum. None of them waits -on a Cloudflare or Spin item, so the MVP ships without waves 11 and 12 (DEC-4). -Wave 11 (Cloudflare), wave 12 (Spin) and the decision-gated row follow the -MVP. +| Wave | Item | Pri | Kind | Action | Waits for | +| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | +| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | +| 0 | TS #1526, #1522 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | +| 0 | TS #1516 | P1 | Merge | Already approved | none | +| 0 | TS #1449 | P1 | PR fix, Merge | Fix its round-4 review; A6 publishes through it | none | +| 0 | TS #1189 | P1 | Close | Fixed on main by `56a19229f` (#1157), which deleted the ungated `ingest_*` functions; ask its author to close it | none | +| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | +| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | +| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464, IABTechLab/edgezero#453 | P1 | PR fix | Rebase off the commits they carry | none | +| 0 | TS #1503, #1432 | P2 | Merge | `ts ec` commands; delivers #1432 | none | +| 0 | TS #1233 | P2 | Move | `ts-ec` to creative assets | none | +| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | +| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | +| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | +| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439; IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | +| 2 | EZ EZI-2a, EZI-3, EZI-5 | P1 | New | Record contract with Fastly; ClientInfo contract; rate limiter for Fastly and Axum | IABTechLab/edgezero#275 (EZI-5 also DEC-11) | +| 2 | TS T5 | P1 | New | Conformance suite for Fastly and Axum | #1507 | +| 2 | TS T4, #1192 | P2 | New, Move | Shared state once per process: Axum (T4) and Fastly (#1192) | #1507 | +| 2 | TS #1556 | P2 | Move | Regression test that identity backends verify certificates, then close | #1507 | +| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | +| 3 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1487 | +| 3 | EZ R1 | P1 | Release | IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | wave 2's EdgeZero chain | +| 3 | EZ EZI-1 | P1 | New | After-response work for Fastly and Axum | IABTechLab/edgezero#275, IABTechLab/edgezero#406 | +| 3 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | +| 3 | IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | P1 | Move | Fastly and Axum ClientInfo; JA4 | EZI-3; IABTechLab/edgezero#397 | +| 3 | TS #1553, #1246 | P2 | Move | After-response CAS retry; Lockr IDs to KV | PR #1487 | +| 4 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1488 | +| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | +| 4 | EZ R2 | P1 | Release | Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-1, EZI-3, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | +| 4 | TS A1 | P1 | New, PR fix | Port #1528, repin to R1 | R1, #1507; DEC-5, DEC-6 | +| 4 | TS T3 | P1 | New | SharedID cleanup | PR #1488 | +| 4 | TS #1555 | P2 | Move | Opt-out EIDs | PR #1488 | +| 4 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | +| 5 | EZ R1b | P1 | Release | EZI-2a and EZI-2b | EZI-2a, EZI-2b | +| 5 | TS #1529 (A4), A6 with #1543, A3 | P1 | Move, New | CloudFront IP and geo; production image; SIGTERM drain | A1 (A3 also R2; A6 also PR #1449) | +| 5 | TS T2 | P1 | New | EIDs in `user.eids`; after the provider stack, so open PRs need no test churn | #1489 merged | +| 5 | TS T10, T12, T13 | P1 | New | Rate limits on Fastly and Axum; abuse protection and SharedID write-back on Fastly | R2; DEC-11 (T10); T3, PR #1488, DEC-7 (T13) | +| 5 | TS T15 | P1 | New | Identity-only tsjs | PR #1489 | +| 5 | TS A9, #1141 | P2 | New, Move | `TRUSTED_SERVER__` guard; local config handoff | A1 | +| 5 | TS #1551 | P2 | Move | Pull sync on a full row | PR #1489 | +| 5 | TS #1527, #1523 | P2 | Close | Tests moved to #1506 | #1506, A1 | +| 6 | TS T6 | P1 | New | Shared record backend, Fastly first | R1b, #1522 | +| 6 | TS T11 | P1 | New | Providers awaited before finalization on Fastly | T10, R2, PR #1487 | +| 6 | TS A7 | P1 | New | Reference infrastructure | A1, A6, #1529; DEC-6, DEC-11 | +| 7 | TS A2 | P1 | New | Axum EC lifecycle | T5, T6, A1, #1529; DEC-6 | +| 8 | TS A10, T7, A8 | P1 | New | Axum providers, abuse protection and SharedID write-back; Axum partner APIs; SIGTERM test in CI | A2 plus their Part B blockers | +| 8 | TS #1541, #1424, #1499, #1531 to #1540 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | +| 9 | TS A5 | P1 | New | Load test | A2, A7, A10 | +| 9 | TS T14 | P2 | New | Mirror cookie on Axum | A10, PR #1489; DEC-10 | +| 10 | EZ EZI-1c, EZI-2c, EZI-4c, EZI-5c; IABTechLab/edgezero#91 | P2 | New, Move | Cloudflare parts in EdgeZero | Part B blockers; DEC-8, DEC-11 | +| 11 | EZ R3 | P2 | Release | Cloudflare release | wave 10 | +| 12 | TS #1518, T8 | P2 | Move, New | Geo source check; Cloudflare EC lifecycle | R3 (T8 also T6, T1, T5; DEC-8) | +| 13 | TS T17 | P2 | New | Cloudflare providers, limits, abuse protection and cookies | T8, R3, T10 to T14 | +| 14 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s | P3 | New | Spin parts in EdgeZero | Part B blockers; DEC-8, DEC-11 | +| 15 | EZ R4; IABTechLab/edgezero#89 | P3 | Release, Move | Spin release; ClientInfo on every adapter | wave 14, R3 | +| 16 | TS T9 | P3 | New | Spin EC lifecycle | T8, R4; DEC-8, DEC-14 | +| 17 | TS T18 | P3 | New | Spin providers, limits, abuse protection and cookies | T9, R4, T17 | +| any | TS #1142 | P2 | Move | Health and startup contract on all four adapters; A7 carries Axum readiness | none | +| any | TS #1549 | P2 | Move | No `user.id` for a row-less EC | DEC-12; PR #1487 | +| any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP with PR IABTechLab/edgezero#453; IABTechLab/edgezero#287 last | DEC-13; DEC-9; PR IABTechLab/edgezero#453; IABTechLab/edgezero#275 | +| any | TS #1139, #1525 | P3 | Move, PR fix | Store inventory on all four adapters; HTTP auction endpoints, which #1526 does not need | none | + +Waves 0 to 9 are the MVP (DEC-2): they complete Fastly and Axum. None of them +waits on a Cloudflare or Spin item, so the MVP ships without waves 10 to 17 +(DEC-4). Waves 10 to 13 (Cloudflare), waves 14 to 17 (Spin) and the `any` rows +follow the MVP; an `any` row starts when its decision or reviewer is ready. Fallback for wave 1: if the fold-in has not landed two weeks after #1557, #1526 and #1516 have all merged on main, #1506 merges pinned to IABTechLab/edgezero#275's head as it stands @@ -174,140 +190,152 @@ Fallback for wave 1: if the fold-in has not landed two weeks after #1557, The two longest chains: -- Axum identity: IABTechLab/edgezero#460, IABTechLab/edgezero#275, IABTechLab/edgezero#406 to IABTechLab/edgezero#439, IABTechLab/edgezero#463, EZI-2b, R1, A1, - T6, A2, T11, A5. -- SharedID: IABTechLab/edgezero#460, #1506, #1487, #1489, #1488, T3, and T13 once A2 lands. +- Axum identity: IABTechLab/edgezero#460, IABTechLab/edgezero#275, IABTechLab/edgezero#406 to IABTechLab/edgezero#439, IABTechLab/edgezero#463, EZI-2b, R1b, + T6, A2, A10, A5. A1 runs beside it on R1, before the record contract lands. +- SharedID: IABTechLab/edgezero#460, #1506, #1487, #1488, T3, T13 on Fastly, then A10 on Axum. ## Part B: tickets -Every ticket in waves 0 to 10 is MVP; the Cloudflare and Spin tickets (T8, -T9, T17, T18, the EZI `c` and `s` tickets, R3, R4, IABTechLab/edgezero#91, -IABTechLab/edgezero#89 and #1518) and the decision-gated row are post-MVP. +Every ticket in waves 0 to 9 is MVP: P1 is required for it, and P2 can slip +past its first release. The Cloudflare and Spin tickets (T8, T9, T17, T18, the +EZI `c` and `s` tickets, R3, R4, IABTechLab/edgezero#91, IABTechLab/edgezero#89 and #1518) and the `any` +rows are post-MVP. "Blocked by" is set as a GitHub dependency between issues. A PR or a decision there goes in the issue body. "Blocks" is the inverse and is not set separately. No ticket mixes Fastly or Axum work with Cloudflare or Spin work -(DEC-4). +(DEC-4), except the rejection, 501 and absence paths in Global Constraints. ### EdgeZero, under IABTechLab/edgezero#468 New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| ------ | ----------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | -| EZI-2a | `KvStore` record contract and the Fastly provider | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1 | -| EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | R1 | -| EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PR IABTechLab/edgezero#275 | EZI-1c, EZI-1s, R2 | -| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and in-memory and shared-counter backends for Axum | EZI-5 | Feature | M | P1 | none | EZI-5c, EZI-5s, R2 | -| EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | -| EZI-2c | Cloudflare store meets the record contract, or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a; DEC-8 | R3 | -| EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | IABTechLab/edgezero#432 | R3 | -| EZI-5c | Rate limiter backend for Cloudflare | EZI-5 | Feature | S | P2 | EZI-5; DEC-11 | R3 | -| EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | -| EZI-2s | Spin store meets the record contract, or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a; DEC-8 | R4 | -| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | none | R4, IABTechLab/edgezero#89 | -| EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | -| EZI-5s | Rate limiter backend for Spin | EZI-5 | Feature | S | P3 | EZI-5; DEC-11 | R4 | -| R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 plus EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b, IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1, T6 | -| R2 | Release the Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-6 | Task | S | P1 | EZI-1, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92 | A3, T7, T10, T11 | -| R3 | Release the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, EZI-5c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | -| R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s, R3 | T9, T18 | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| ------ | -------------------------------------------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| EZI-2a | `KvStore` record contract and the Fastly provider | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1b | +| EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | R1b | +| EZI-3 | ClientInfo contract: portable client IP, geo and TLS metadata with explicit absence, the contract part of IABTechLab/edgezero#89 | EZI-3 | Feature | M | P1 | PR IABTechLab/edgezero#275 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, IABTechLab/edgezero#283, EZI-3s, IABTechLab/edgezero#89, R2 | +| EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PRs IABTechLab/edgezero#275, IABTechLab/edgezero#406 | EZI-1c, EZI-1s, R2 | +| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and in-memory and shared-counter backends for Axum | EZI-5 | Feature | M | P1 | PR IABTechLab/edgezero#275; DEC-11 | EZI-5c, EZI-5s, R2 | +| EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | +| EZI-2c | Cloudflare store meets the record contract, or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a; DEC-8 | R3 | +| EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | IABTechLab/edgezero#432 | R3 | +| EZI-5c | Rate limiter backend for Cloudflare | EZI-5 | Feature | S | P2 | EZI-5; DEC-11 | R3 | +| EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | +| EZI-2s | Spin store meets the record contract, or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a; DEC-8 | R4 | +| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | EZI-3 | R4, IABTechLab/edgezero#89 | +| EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | +| EZI-5s | Rate limiter backend for Spin | EZI-5 | Feature | S | P3 | EZI-5; DEC-11 | R4 | +| R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | +| R1b | Release the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | +| R2 | Release the Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T7, T10, T11, T12 | +| R3 | Release the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, EZI-5c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | +| R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s, R3 | T9, T18 | Existing, moved under IABTechLab/edgezero#468. IABTechLab/edgezero#89 to IABTechLab/edgezero#92 leave -IABTechLab/edgezero#88 and the last five leave IABTechLab/edgezero#391 or have +IABTechLab/edgezero#88 and the last six leave IABTechLab/edgezero#391 or have no parent; both old epics stay where they are: -| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | -| ----------------------- | ------------------------------------------------------------------------------------------------------ | ------------ | ---- | --- | ------------------------------------------------------------------------------ | -------------------------- | -| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | M | P2 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | -| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | none | R2, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | IABTechLab/edgezero#397 | R2, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | none | R3, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#397 | Axum: safe production diagnostics and trusted-proxy handling (delivered by PR IABTechLab/edgezero#409) | Task (keep) | M | P1 | none | R1, IABTechLab/edgezero#92 | -| IABTechLab/edgezero#410 | Native AWS configuration, secret and KV store providers (PR IABTechLab/edgezero#414) | Task (keep) | L | P1 | none | EZI-2b, R1 | -| IABTechLab/edgezero#437 | Native celld configuration and KV store providers (PR IABTechLab/edgezero#422) | Task (keep) | M | P1 | none | EZI-2b, R1 | -| IABTechLab/edgezero#432 | Axum: protected CloudFront Geo metadata provider (PR IABTechLab/edgezero#439) | Story (keep) | M | P1 | none | R1, EZI-4c, EZI-4s | -| IABTechLab/edgezero#438 | Axum: explicit local GeoIP provider (PR IABTechLab/edgezero#453) | Story (keep) | M | P3 | none | none | - -EZI-3s (Spin ClientInfo) also blocks IABTechLab/edgezero#89. - -The last five rows leave IABTechLab/edgezero#391, which stays an epic in place; -R1 is linked from it. +| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------ | ---- | --- | ------------------------------------------------------------------------------------- | --------------------------------------------------- | +| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | S | P3 | EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | +| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | EZI-3 | R2, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | EZI-3, IABTechLab/edgezero#397 | R2, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | EZI-3 | R3, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#397 | Axum: safe production diagnostics and trusted-proxy handling (delivered by PR IABTechLab/edgezero#409) | Task (keep) | M | P1 | none | R1, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | +| IABTechLab/edgezero#410 | Native AWS configuration, secret and KV store providers (PR IABTechLab/edgezero#414) | Task (keep) | L | P1 | none | EZI-2b, R1 | +| IABTechLab/edgezero#437 | Native celld configuration and KV store providers (PR IABTechLab/edgezero#422) | Task (keep) | M | P1 | none | EZI-2b, R1 | +| IABTechLab/edgezero#432 | Axum: protected CloudFront Geo metadata provider (PR IABTechLab/edgezero#439) | Story (keep) | M | P1 | none | R1, EZI-4c, EZI-4s | +| IABTechLab/edgezero#438 | Axum: explicit local GeoIP provider (PR IABTechLab/edgezero#453) | Story (keep) | M | P3 | none | none | +| IABTechLab/edgezero#283 | Expose JA4 through a standard request interface; R2 needs its Fastly and Axum parts, and other adapters report absence | Task (keep) | M | P1 | EZI-3, IABTechLab/edgezero#397 | R2 | + +IABTechLab/edgezero#89 stays the umbrella story: EZI-3 carries its contract, and it completes +with the last adapter's mapping. IABTechLab/edgezero#397, IABTechLab/edgezero#410 and IABTechLab/edgezero#432 leave +IABTechLab/edgezero#391, which stays an epic in place; R1 is linked from it. ### Axum deployable, under #1558 New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ----------------------------------------------------------------------------------------------------------- | ---------------------- | ------- | ---- | --- | ------------------------------ | ------------------------- | -| A1 | Serve Axum in production mode from main: port #1528 onto #1506's outbound API, repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507, #1141; DEC-5, DEC-6 | A2, A3, A6, A7, A9, #1529 | -| A2 | Axum EC lifecycle on the shared backend, forward-ported from #1536 and #1537 | ID-1, ID-3, ID-8, WB-1 | Feature | L | P1 | T6, A1, #1529; DEC-6 | A5, A8, T7, T11, T12, T13 | -| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | -| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P2 | A2, A7, T11 | none | -| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; DEC-6 | A7 | -| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on | AX-3, AX-6 | Feature | L | P1 | A1, A6, #1142; DEC-6, DEC-11 | A5 | -| A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P2 | A2 | none | -| A9 | `TRUSTED_SERVER__` environment names only | AX-2 | Task | S | P2 | A1 | none | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------- | ---- | --- | ---------------------------- | -------------------------------- | +| A1 | Serve Axum in production mode from main: port #1528 onto #1506's outbound API, repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507; DEC-5, DEC-6 | A2, A3, A6, A7, A9, #1529, #1141 | +| A2 | Axum EC lifecycle on the shared backend, forward-ported from #1536 and #1537, passing T5's suite | ID-1, ID-3, ID-8, WB-1 | Feature | L | P1 | T5, T6, A1, #1529; DEC-6 | A5, A8, A10, T7 | +| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | +| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | +| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; PR #1449; DEC-6 | A7 | +| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded | AX-3, AX-6, ID-7 | Feature | L | P1 | A1, A6, #1529; DEC-6, DEC-11 | A5, A10 | +| A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P1 | A2 | none | +| A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | +| A10 | Axum identity features: providers, abuse protection and SharedID write-back, on the core work of T11 to T13 | ID-4, ID-7, WB-3 | Feature | L | P1 | A2, A7, R2, T11, T12, T13 | A5, T14 | Existing, moved under #1558: | Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | | ----- | ------------------------------------------------------------ | ----------- | ---- | --- | ---------- | ------ | -| #1529 | Extract the protected CloudFront native Geo integration (A4) | Task (keep) | M | P1 | A1; DEC-6 | A2 | -| #1142 | Uniform cross-adapter health and startup-failure contract | Task (keep) | M | P1 | none | A7 | -| #1141 | Make Axum consume the local config store | Bug (keep) | S | P2 | none | A1 | -| #1139 | Implement adapter stores or retire dead store manifests | Bug (keep) | M | P2 | none | T6 | +| #1529 | Extract the protected CloudFront native Geo integration (A4) | Task (keep) | M | P1 | A1; DEC-6 | A2, A7 | +| #1141 | Make Axum consume the local config store | Bug (keep) | S | P2 | A1 | none | +| #1142 | Uniform cross-adapter health and startup-failure contract | Task (keep) | M | P2 | none | none | +| #1139 | Implement adapter stores or retire dead store manifests | Bug (keep) | M | P3 | none | none | + +A1 replaces the local configuration bridge: #1528 starts Axum from +`EDGEZERO__ADAPTER__CONFIG_DIR` and never reads the old +`TRUSTED_SERVER_CONFIG_*` names, so #1141 then points `ts config push --local` +and `ts serve` at that directory. #1142 and #1139 cover all four adapters, so +they sit outside the MVP; A7 carries Axum readiness. ### Trusted Server, under #1558 New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------- | ---- | --- | ------------------------------- | ------------ | -| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T7, T8 | -| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | -| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | #1189; PR #1488 | T13 | -| T4 | Build shared state once per process on Fastly and Axum: settings, partner registry (Axum builds it per request), store clients; keep Axum per-request HTTP client unless measured | SH-1, SH-3 | Task | M | P2 | #1507 | none | -| T5 | Identity conformance suite for Fastly and Axum, plus a startup-rejection test where identity is unsupported | ID-1 | Task | M | P1 | #1507 | T8 | -| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup | ID-2 | Feature | L | P1 | R1, #1522, #1139 | A2, T8 | -| T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | Feature | M | P1 | A2, R2, T1 | none | -| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3, SH-4 | Feature | L | P2 | T6, R3, T1, T5, #1518; DEC-8 | T9, T17 | -| T9 | Spin EC lifecycle, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | -| T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; shared counter across Axum tasks | ID-6 | Feature | M | P1 | R2; DEC-11 | T11, T17 | -| T11 | Providers on Fastly and Axum, awaited before finalization; only follow-up writes after the response | ID-4, ID-5, WB-1 | Feature | L | P1 | A2, T10, R2, #1553; PR #1487 | A5, T14, T17 | -| T12 | Refuse record creation from non-browser clients on Fastly and Axum | ID-7 | Feature | M | P2 | A2 | T17 | -| T13 | SharedID cookie write-back on Fastly and Axum, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | Feature | M | P1 | T3, A2; PR #1488; DEC-7 | T17 | -| T14 | Mirror cookie on Axum | WB-4 | Feature | S | P2 | T11; PR #1489; DEC-10 | T17 | -| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P2 | PR #1489 | none | -| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | -| T17 | Cloudflare identity features: providers, rate limits, abuse protection, SharedID write-back and mirror cookie | ID-4, ID-6, ID-7, WB-3, WB-4 | Feature | L | P2 | T8, R3, T10, T11, T12, T13, T14 | T18 | -| T18 | Spin identity features, the same scope as T17 | ID-4, ID-6, ID-7, WB-3, WB-4 | Feature | L | P3 | T9, R4, T17 | none | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- | ------- | ---- | --- | ------------------------------- | ------------ | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T7, T8 | +| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | +| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | +| T4 | Build shared state once per process on Axum: settings, partner registry (built per request today), store clients; keep the per-request HTTP client unless measured. #1192 is the Fastly half | SH-1, SH-3 | Task | M | P2 | #1507 | none | +| T5 | Identity conformance suite for Fastly and Axum | ID-1 | Task | M | P1 | #1507 | A2, T8 | +| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | +| T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | Feature | M | P1 | A2, R2, T1, T10 | none | +| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs; build state once; drop the first-request `Env` capture and config `block_on`. Split when scheduled | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3, SH-4 | Feature | XL | P2 | T6, R3, T1, T5; DEC-8 | T9, T17 | +| T9 | Spin EC lifecycle, the same scope as T8; port the config `block_on` removal from RC commit `933de5921`. Split when scheduled | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3 | Feature | XL | P3 | T8, R4; DEC-8, DEC-14 | T18 | +| T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; shared counter across Axum tasks | ID-6 | Feature | M | P1 | R2; DEC-11 | T7, T11, T17 | +| T11 | Providers on Fastly, awaited before finalization; only follow-up writes after the response | ID-4, ID-5, WB-1 | Feature | M | P1 | T10, R2; PR #1487 | A10, T17 | +| T12 | Refuse record creation from non-browser clients on Fastly, through EdgeZero's JA4 accessor | ID-7 | Feature | M | P1 | R2 | A10, T17 | +| T13 | SharedID cookie write-back on Fastly, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | Feature | M | P1 | T3; PR #1488; DEC-7 | A10, T17 | +| T14 | Mirror cookie on Axum | WB-4 | Feature | S | P2 | A10; PR #1489; DEC-10 | T17 | +| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | none | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | +| T17 | Cloudflare identity features: providers, rate limits, abuse protection, SharedID write-back and mirror cookie. Split when scheduled | ID-4, ID-6, ID-7, WB-3, WB-4 | Feature | XL | P2 | T8, R3, T10, T11, T12, T13, T14 | T18 | +| T18 | Spin identity features, the same scope as T17. Split when scheduled | ID-4, ID-6, ID-7, WB-3, WB-4 | Feature | XL | P3 | T9, R4, T17 | none | Existing, moved under #1558: -| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | -| ----- | --------------------------------------------------------------------------- | ----------- | ---- | --- | --------------- | ------ | -| #1522 | Make shared EC persistence awaitable (delivered by PR #1526) | Task (keep) | M | P1 | none | T6 | -| #1189 | Ungated `ingest_*` EID write path bypasses TCF Purpose 4 gating | Bug | M | P1 | none | T3 | -| #1549 | Do not send a cookie EC with no row as `user.id` | Bug (keep) | S | P1 | PR #1487 | none | -| #1553 | Retry an after-response write that loses its CAS | Task (keep) | S | P1 | PR #1487 | T11 | -| #1551 | Stop pull sync from re-calling a partner for a row that is full | Bug (keep) | S | P2 | PR #1489 | none | -| #1555 | Client and browser EIDs for provider-owned sources after a provider opt-out | Bug (keep) | S | P2 | PR #1488 | none | -| #1556 | Certificate checks on identity backends | Bug (keep) | S | P2 | #1507; PR #1488 | none | -| #1518 | Verify EdgeZero geolocation adoption in EC and auction consent paths | Task (keep) | S | P2 | R3 | T8 | -| #1233 | Stop forwarding `ts-ec` to static creative assets | Task (keep) | S | P2 | none | none | -| #1432 | `ts ec show` and `ts ec eids` (delivered by PR #1503) | Task (keep) | S | P2 | none | none | -| #1245 | Spec for non-browser LiveRamp sync | Task | S | P2 | DEC-9 | none | -| #1246 | Write Lockr IDs to the KV store | Task (keep) | M | P2 | PR #1487 | none | -| #1192 | Reduce Fastly adapter per-request fixed cost | Task (keep) | M | P3 | none | none | +| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | +| ----- | --------------------------------------------------------------------------- | ----------- | ---- | --- | ---------------- | ------ | +| #1522 | Make shared EC persistence awaitable (delivered by PR #1526) | Task (keep) | M | P1 | none | T6 | +| #1549 | Do not send a cookie EC with no row as `user.id` | Bug (keep) | S | P2 | PR #1487; DEC-12 | none | +| #1553 | Retry an after-response write that loses its CAS | Task (keep) | S | P2 | PR #1487 | none | +| #1551 | Stop pull sync from re-calling a partner for a row that is full | Bug (keep) | S | P2 | PR #1489 | none | +| #1555 | Client and browser EIDs for provider-owned sources after a provider opt-out | Bug (keep) | S | P2 | PR #1488 | none | +| #1556 | Certificate checks on identity backends | Bug (keep) | XS | P2 | #1507 | none | +| #1518 | Verify EdgeZero geolocation adoption in EC and auction consent paths | Task (keep) | M | P2 | R3 | none | +| #1233 | Stop forwarding `ts-ec` to static creative assets | Task (keep) | S | P2 | none | none | +| #1432 | `ts ec show` and `ts ec eids` (delivered by PR #1503) | Task (keep) | S | P2 | none | none | +| #1245 | Spec for non-browser LiveRamp sync | Task | S | P3 | DEC-9 | none | +| #1246 | Write Lockr IDs to the KV store | Task (keep) | M | P2 | PR #1487 | none | +| #1192 | Reduce Fastly adapter per-request fixed cost (the Fastly half of SH-1) | Task (keep) | M | P2 | #1507 | none | Not moved: - IABTechLab/trusted-server#90 is an epic, so it is linked from #1558, not moved; it and IABTechLab/trusted-server#353 close when PR #1488 merges. -- #1507 delivers the cutover and stays under #1505; it blocks A1, T4, T5 and - #1556. -- Proposed closures: #1523 with #1527; IABTechLab/trusted-server#353 and #1310 once PR #1488 merges. +- #1507 delivers the cutover and stays under #1505; it blocks A1, T4, T5, + #1192 and #1556. +- Proposed closures: #1523 with #1527; IABTechLab/trusted-server#353 and #1310 once PR #1488 merges; + #1189, which `56a19229f` (#1157) fixed on main. +- #1556 needs only a regression test: #1506 rejects + `proxy.certificate_check = false` and drops the setting from pull sync. - Out of these epics: #1484 (auction), #1140 (Cloudflare config), #1164 (Spin tooling), IABTechLab/trusted-server#518 (cleanup). @@ -318,7 +346,9 @@ expiry (#1489), AX-7 (#1528). Part C lists them. ## Part C: fixes to existing PRs Each item names the head it was checked at. Post it as a comment on that PR -(Tasks 6 and 7); once posted, the PR thread is the source of truth. +(Tasks 6 and 7); once posted, the PR thread is the source of truth. Since the +items were checked, #1506, #1487, #1488, #1489, #1543, #1449 and #1503 have +moved to the heads in Part A, so Task 6 re-checks each of their items first. ### Trusted Server @@ -328,8 +358,6 @@ Each item names the head it was checked at. Post it as a comment on that PR return. The repin brings IABTechLab/edgezero `b97efec1`, which stops Spin sending `Content-Length: 0` on bodiless DELETE and OPTIONS. Port #1527's native-client, response-owner and managed Fastly delivery tests if missing. - If T2 has landed, make the EID-absence assertions (`endpoints.rs:1708-1711`, - `prebid.rs:7023`) check both `user.eids` and `user.ext.eids`. - **#1526 at `e529e5576` (ID-2, SH-3).** Retarget to main. Address its round-1 review, including the finalize-under-`block_on` test gap. - **#1525 at `cd8977cad`.** Address its round-1 review. @@ -339,15 +367,13 @@ Each item names the head it was checked at. Post it as a comment on that PR `block_on` (`main.rs:550`). Move per-request lookups out of the `thread_local`. Keep saving `/auction` body EIDs; a test at `finalize.rs:1434` asserts they are dropped. Send lookups through EdgeZero's - `HttpClient`. Key the limit per DEC-11. If T2 has landed, convert its EID - assertions as for #1506. + `HttpClient`. Key the limit per DEC-11. - **#1489 at `29de3d446` (WB-4).** Cap every mirrored ID's expiry at 30 days. `coordinator/validate.rs:132-135` turns an unspecified expiry into `Expiry::Never`, and `crates/trusted-server-identity-providers/src/pull_sync.rs:128` writes such IDs. -- **#1488 at `25f35720b` (SID-1, SID-2, SID-3, SID-5, SID-6).** Restack onto - #1489; the two overlap in 13 files and conflict in 11. Read the cookie name +- **#1488 at `25f35720b` (SID-1, SID-2, SID-3, SID-5, SID-6).** Read the cookie name from `managed_user_ids[].storage.name` and default the source to `pubcid.org`, atype 1. Gate reads, storage and sending on EID permission (SID-6). Keep main's retention (SID-5, DEC-7). Refuse an enabled provider @@ -359,8 +385,7 @@ Each item names the head it was checked at. Post it as a comment on that PR #1527 has. Repin off `456313675`. Fix the CLI test (`AxumDevServerConfig` lacks `ingress`), the type error and the two new high CodeQL alerts. Remove the per-request `AtomicBool` from the Fastly `AppState`. Feeds A1. -- **#1543 at `dc1a063d8` (AX-5).** Rebase onto #1449. It publishes the - dev-server image (`STOPSIGNAL SIGINT`), so it ships with A6's production +- **#1543 at `dc1a063d8` (AX-5).** It publishes the dev-server image (`STOPSIGNAL SIGINT`), so it ships with A6's production image contract. - **RC units, fixed in their forward-ports.** These were checked at the RC tag `v0.1.1-rc-axum.202611.2` (`7c6e68d94`), which contains every unit. They are @@ -371,7 +396,8 @@ Each item names the head it was checked at. Post it as a comment on that PR `4a838272c`, #1539 `8d41cb06c`, #1540 `eb6e19ef4` and #1541 `7dd2e3056`. #1532: drop the core change that maps a US request with no region to `Unknown`, which fails every US - Cloudflare request closed (AX-3). #1536: do not port the adapter-local + Cloudflare request closed (AX-3), and show that the same trusted country + and region reach EC creation and auction consent on Axum. #1536: do not port the adapter-local DynamoDB store (ID-2, AX-4). #1537: its example config breaks startup on Axum, Cloudflare and Spin. #1539: fix the failing Axum test. #1540: trust IPv6 CloudFront ranges, turn geo on, wire DynamoDB (AX-3, AX-6). All units: @@ -437,7 +463,7 @@ These belong to #1505 and IABTechLab/edgezero#436: **Interfaces:** - Consumes: Part B's "Existing, moved" tables and "Linked, not moved" notes. -- Produces: 17 issues under #1558 and 9 under IABTechLab/edgezero#468, each typed, sized and +- Produces: 16 issues under #1558 and 10 under IABTechLab/edgezero#468, each typed, sized and prioritized; link comments on IABTechLab/edgezero#468. - [ ] **Step 1: Move each issue** @@ -464,7 +490,7 @@ Expected: each mutation returns the item. - [ ] **Step 3: Update the EdgeZero epics' text** Edit IABTechLab/edgezero#468's Related section to say that IABTechLab/edgezero#391 stays -the Axum production epic and that the five issues moved from it or with no +the Axum production epic and that the six issues moved from it or with no parent are tracked here. Post a `CREATOR:` comment on IABTechLab/edgezero#391 and on IABTechLab/edgezero#88 naming the issues that moved to IABTechLab/edgezero#468. @@ -507,7 +533,7 @@ issue, and one on IABTechLab/edgezero#391 linking R1. ### Task 3: File the Axum tickets under #1558 Same steps as Task 2, against IABTechLab/trusted-server and #1558, for the -eight new rows in table order. Each body has Summary (Ticket), Requirements +nine new rows in table order. Each body has Summary (Ticket), Requirements (Reqs, linked to the spec section), Acceptance (each requirement's Accept line) and Blocked by. Record the map in a `CREATOR:` comment on #1558. @@ -567,16 +593,16 @@ issue from Task 2. Run `gh api graphql -f query='{repository(owner:"IABTechLab",name:""){issue(number:){subIssues(first:100){totalCount}}}}' --jq '.data.repository.issue.subIssues.totalCount'` for #1558 and IABTechLab/edgezero#468. -Expected: 43 and 26. +Expected: 43 and 29. -- [ ] **Step 3: Check that Fastly and Axum stand alone** +- [ ] **Step 2: Check that Fastly and Axum stand alone** -For every issue in waves 0 to 10, read its blocked-by list and confirm that -none of its blockers is a wave 11 or 12 item (a Cloudflare or Spin ticket, -IABTechLab/edgezero#91, R3 or R4). +For every issue in waves 0 to 9, read its blocked-by list and confirm that +none of its blockers is in wave 10 or later or in an `any` row (a Cloudflare +or Spin ticket, IABTechLab/edgezero#91, R3, R4, #1139 or #1142). Expected: no such blocker (DEC-4). -- [ ] **Step 2: Check bodies** +- [ ] **Step 3: Check bodies** For every created or moved issue, read its body and confirm three things: each "Blocked by" names an issue, PR or decision; no Trusted Server body diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index cb8682ed5..396c3bae9 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -123,6 +123,7 @@ follows DEC-4: Fastly, Axum, Cloudflare, Spin. | After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | | Rate limiter | Supported (Edge Rate Limiting) | EZI-5, in-memory per task, shared counter with more than one task (DEC-11) | EZI-5, backend per DEC-11 | EZI-5, backend per DEC-11 | | Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | +| TLS fingerprint (JA4) | Supported (Fastly SDK); EZI-3 accessor | EZI-3 from a trusted CloudFront hop | EZI-3 accessor, or absent | EZI-3 accessor, or absent | | Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | Cloudflare and Spin move to Supported only when EZI-2 and EZI-4 deliver for @@ -238,8 +239,10 @@ marks the record contract and geo as supported. not browsers, on every supported adapter. Accept: a scripted client with a browser User-Agent and no browser TLS fingerprint creates no record. State: Fastly checks only that a JA4 exists plus a browser User-Agent platform; the - RC's Axum checks only the User-Agent, with request filters; Cloudflare and - Spin create no records yet. + RC's Axum checks only the User-Agent, with request filters, because it has + no trusted JA4 (EZI-3); Cloudflare and Spin create no records yet. CloudFront + sends JA4 to the origin only in the `CloudFront-Viewer-JA4-Fingerprint` + header, which an origin request policy must forward. - **ID-8 Body EIDs keep being saved.** EIDs sent in the `/auction` body are saved to the record. Accept: on each supported adapter a body EID is in the record after the request. State: main saves them on Fastly; the provider @@ -362,7 +365,8 @@ marks the record contract and geo as supported. Behavior and acceptance only. EZI IDs are distinct from #1506's EZ-n requirement IDs. Each ask is split by platform, in DEC-4 order: the Fastly -and Axum parts ship in R1 and R2 and never wait on a Cloudflare or Spin part. +and Axum parts ship in R1, R1b and R2 and never wait on a Cloudflare or Spin +part. - **EZI-1 After-response work.** A task registered during a request runs after the response is delivered, bounded in time. Accept: on Fastly and Axum the @@ -384,9 +388,12 @@ and Axum parts ship in R1 and R2 and never wait on a Cloudflare or Spin part. meets the EZI-2a contract or reports `KvError::Unsupported` (DEC-8). Accept: the contract suite passes, or startup on that platform rejects identity configuration. -- **EZI-3 Trusted client IP.** One accessor returns the trusted client IP on - every adapter, Fastly and Axum first. Accept: the same accessor works on - each adapter as its part lands. +- **EZI-3 Trusted client metadata.** One accessor returns the trusted client + IP, and one the JA4 fingerprint where a trusted provider supplies it + (IABTechLab/edgezero#283), on every adapter, Fastly and Axum first. Axum + reads JA4 only from a trusted CloudFront hop. Accept: the same accessors work + on each adapter as its part lands, and a JA4 header from an untrusted peer + reads as absent. - **EZI-4 Geo with region on Cloudflare and Spin.** `ctx.geo()` gives country and region there, or a capability error. Fastly keeps its existing geo and Axum uses CloudFront geo from IABTechLab/edgezero#439. Accept: a US request @@ -395,11 +402,12 @@ and Axum parts ship in R1 and R2 and never wait on a Cloudflare or Spin part. in-memory and shared-counter backends for Axum, then Cloudflare and Spin backends per DEC-11. Accept: a contract test passes per backend. - **EZI-6 Releases.** R1 contains IABTechLab/edgezero#275 through - IABTechLab/edgezero#439 plus EZI-2a and EZI-2b. R2 contains the Fastly and - Axum parts of EZI-1, EZI-3 and EZI-5. Together R1 and R2 cover Fastly and - Axum. R3 contains the Cloudflare parts of EZI-1 to EZI-5, and R4 the Spin - parts. Accept: each tag exists and Trusted Server main pins it; R1 and R2 - contain no Cloudflare or Spin part. + IABTechLab/edgezero#439, so Axum production serving does not wait for the + record contract. R1b contains EZI-2a and EZI-2b. R2 contains the Fastly and + Axum parts of EZI-1, EZI-3 and EZI-5. Together R1, R1b and R2 cover Fastly + and Axum. R3 contains the Cloudflare parts of EZI-1 to EZI-5, and R4 the Spin + parts. Accept: each tag exists and Trusted Server main pins it; R1, R1b and + R2 contain no Cloudflare or Spin part. ## Configuration @@ -463,7 +471,7 @@ Decided: - **DEC-1 Cutover** (user, 2026-11-11). #1506 is the EdgeZero cutover. #1527 closes once #1506 covers its tests. - **DEC-2 MVP** (user, 2026-11-12). Fastly and Axum are the MVP: plan waves 0 - to 10. Cloudflare, Spin and the decision-gated work follow it. + to 9. Cloudflare, Spin and the decision-gated work follow it. - **DEC-3 Shared record store** (user, 2026-11-11). The EC record runs on one `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. - **DEC-4 Platform order** (user, 2026-11-12). Fastly, then Axum, then @@ -488,14 +496,16 @@ Proposed and assumed by the plan until confirmed: Open: - **DEC-8 Cloudflare and Spin store.** Which EdgeZero store meets the record - contract there, per [Platform support](#platform-support). Blocks T8 and T9. + contract there, per [Platform support](#platform-support). Blocks EZI-2c, + EZI-2s, R3, T8 and T9. - **DEC-9 LiveRamp Custom ID.** The current value is the IP-derived EC prefix, - shared by everyone behind one IP. Blocks enabling LiveRamp in #1488. + shared by everyone behind one IP. Blocks enabling LiveRamp in #1488, and + #1245. - **DEC-10 Mirror partners.** Which partners get `mirror_enabled`. Blocks T14. -- **DEC-11 Rate limiter key and backend, and the Axum task count.** Blocks T10 - and A7. +- **DEC-11 Rate limiter key and backend, and the Axum task count.** Blocks + EZI-5, EZI-5c, EZI-5s, T10 and A7. - **DEC-12 EC EID source.** Which source name the EC uses, and whether it keeps - going out as `user.id`. No ticket until decided. + going out as `user.id`. Blocks #1549; no other ticket until decided. - **DEC-13 `ts-ec` refresh** for returning readers. Blocks T16. - **DEC-14 Spin after-response work.** Run it before the response, or skip it. Blocks T9. @@ -535,3 +545,13 @@ re-parent the EdgeZero issues it moves (35), and WB-3's no-write case must require a consistent `_cst` (36). No blocking finding. The revision after it addresses all eight; round 3 is pending, and the PR stays in draft until a round finds nothing blocking. + +### Ticket audit, 2026-11-12 + +At the user's request, an audit of every plan ticket's order, priority, size +and MVP need, with each blocker checked against code at the PR heads. In the +spec, EZI-3 adds JA4, because ID-7 cannot pass on Axum without it, and ID-7's +state and the platform table say so; EZI-6 moves the record contract into R1b, +so A1 no longer waits for it; DEC-2's MVP is plan waves 0 to 9; and DEC-8, +DEC-9, DEC-11 and DEC-12 list every ticket they block. The plan's changes are +listed in the PR conversation. From 5f09873c89eebfdcb48b395ec5c807cfbf909c6f Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 08:20:56 -0800 Subject: [PATCH 10/31] Record the Axum rate limiter decision in the identity spec and plan Each Axum task keeps its own in-memory counter and divides every limit by the configured task count, so the MVP needs no shared counter (DEC-11). EZI-5 no longer waits on a decision and shrinks to S. The open remainder, keys, task count and the Cloudflare and Spin backends, moves to DEC-15. --- ...11-11-server-side-identity-all-adapters.md | 22 ++++----- ...erver-side-identity-all-adapters-design.md | 45 ++++++++++++------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index c3718435d..e546e3834 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -132,7 +132,7 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | | 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | | 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439; IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | -| 2 | EZ EZI-2a, EZI-3, EZI-5 | P1 | New | Record contract with Fastly; ClientInfo contract; rate limiter for Fastly and Axum | IABTechLab/edgezero#275 (EZI-5 also DEC-11) | +| 2 | EZ EZI-2a, EZI-3, EZI-5 | P1 | New | Record contract with Fastly; ClientInfo contract; rate limiter for Fastly and Axum | IABTechLab/edgezero#275 | | 2 | TS T5 | P1 | New | Conformance suite for Fastly and Axum | #1507 | | 2 | TS T4, #1192 | P2 | New, Move | Shared state once per process: Axum (T4) and Fastly (#1192) | #1507 | | 2 | TS #1556 | P2 | Move | Regression test that identity backends verify certificates, then close | #1507 | @@ -153,24 +153,24 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 5 | EZ R1b | P1 | Release | EZI-2a and EZI-2b | EZI-2a, EZI-2b | | 5 | TS #1529 (A4), A6 with #1543, A3 | P1 | Move, New | CloudFront IP and geo; production image; SIGTERM drain | A1 (A3 also R2; A6 also PR #1449) | | 5 | TS T2 | P1 | New | EIDs in `user.eids`; after the provider stack, so open PRs need no test churn | #1489 merged | -| 5 | TS T10, T12, T13 | P1 | New | Rate limits on Fastly and Axum; abuse protection and SharedID write-back on Fastly | R2; DEC-11 (T10); T3, PR #1488, DEC-7 (T13) | +| 5 | TS T10, T12, T13 | P1 | New | Rate limits on Fastly and Axum; abuse protection and SharedID write-back on Fastly | R2; DEC-15 (T10); T3, PR #1488, DEC-7 (T13) | | 5 | TS T15 | P1 | New | Identity-only tsjs | PR #1489 | | 5 | TS A9, #1141 | P2 | New, Move | `TRUSTED_SERVER__` guard; local config handoff | A1 | | 5 | TS #1551 | P2 | Move | Pull sync on a full row | PR #1489 | | 5 | TS #1527, #1523 | P2 | Close | Tests moved to #1506 | #1506, A1 | | 6 | TS T6 | P1 | New | Shared record backend, Fastly first | R1b, #1522 | | 6 | TS T11 | P1 | New | Providers awaited before finalization on Fastly | T10, R2, PR #1487 | -| 6 | TS A7 | P1 | New | Reference infrastructure | A1, A6, #1529; DEC-6, DEC-11 | +| 6 | TS A7 | P1 | New | Reference infrastructure | A1, A6, #1529; DEC-6, DEC-15 | | 7 | TS A2 | P1 | New | Axum EC lifecycle | T5, T6, A1, #1529; DEC-6 | | 8 | TS A10, T7, A8 | P1 | New | Axum providers, abuse protection and SharedID write-back; Axum partner APIs; SIGTERM test in CI | A2 plus their Part B blockers | | 8 | TS #1541, #1424, #1499, #1531 to #1540 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | | 9 | TS A5 | P1 | New | Load test | A2, A7, A10 | | 9 | TS T14 | P2 | New | Mirror cookie on Axum | A10, PR #1489; DEC-10 | -| 10 | EZ EZI-1c, EZI-2c, EZI-4c, EZI-5c; IABTechLab/edgezero#91 | P2 | New, Move | Cloudflare parts in EdgeZero | Part B blockers; DEC-8, DEC-11 | +| 10 | EZ EZI-1c, EZI-2c, EZI-4c, EZI-5c; IABTechLab/edgezero#91 | P2 | New, Move | Cloudflare parts in EdgeZero | Part B blockers; DEC-8, DEC-15 | | 11 | EZ R3 | P2 | Release | Cloudflare release | wave 10 | | 12 | TS #1518, T8 | P2 | Move, New | Geo source check; Cloudflare EC lifecycle | R3 (T8 also T6, T1, T5; DEC-8) | | 13 | TS T17 | P2 | New | Cloudflare providers, limits, abuse protection and cookies | T8, R3, T10 to T14 | -| 14 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s | P3 | New | Spin parts in EdgeZero | Part B blockers; DEC-8, DEC-11 | +| 14 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s | P3 | New | Spin parts in EdgeZero | Part B blockers; DEC-8, DEC-15 | | 15 | EZ R4; IABTechLab/edgezero#89 | P3 | Release, Move | Spin release; ClientInfo on every adapter | wave 14, R3 | | 16 | TS T9 | P3 | New | Spin EC lifecycle | T8, R4; DEC-8, DEC-14 | | 17 | TS T18 | P3 | New | Spin providers, limits, abuse protection and cookies | T9, R4, T17 | @@ -216,16 +216,16 @@ New: | EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | R1b | | EZI-3 | ClientInfo contract: portable client IP, geo and TLS metadata with explicit absence, the contract part of IABTechLab/edgezero#89 | EZI-3 | Feature | M | P1 | PR IABTechLab/edgezero#275 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, IABTechLab/edgezero#283, EZI-3s, IABTechLab/edgezero#89, R2 | | EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PRs IABTechLab/edgezero#275, IABTechLab/edgezero#406 | EZI-1c, EZI-1s, R2 | -| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and in-memory and shared-counter backends for Axum | EZI-5 | Feature | M | P1 | PR IABTechLab/edgezero#275; DEC-11 | EZI-5c, EZI-5s, R2 | +| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and an in-memory backend for Axum | EZI-5 | Feature | S | P1 | PR IABTechLab/edgezero#275 | EZI-5c, EZI-5s, R2 | | EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | | EZI-2c | Cloudflare store meets the record contract, or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a; DEC-8 | R3 | | EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | IABTechLab/edgezero#432 | R3 | -| EZI-5c | Rate limiter backend for Cloudflare | EZI-5 | Feature | S | P2 | EZI-5; DEC-11 | R3 | +| EZI-5c | Rate limiter backend for Cloudflare | EZI-5 | Feature | S | P2 | EZI-5; DEC-15 | R3 | | EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | | EZI-2s | Spin store meets the record contract, or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a; DEC-8 | R4 | | EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | EZI-3 | R4, IABTechLab/edgezero#89 | | EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | -| EZI-5s | Rate limiter backend for Spin | EZI-5 | Feature | S | P3 | EZI-5; DEC-11 | R4 | +| EZI-5s | Rate limiter backend for Spin | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | R4 | | R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | | R1b | Release the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | | R2 | Release the Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T7, T10, T11, T12 | @@ -264,7 +264,7 @@ New: | A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | | A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | | A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; PR #1449; DEC-6 | A7 | -| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded | AX-3, AX-6, ID-7 | Feature | L | P1 | A1, A6, #1529; DEC-6, DEC-11 | A5, A10 | +| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded | AX-3, AX-6, ID-7 | Feature | L | P1 | A1, A6, #1529; DEC-6, DEC-15 | A5, A10 | | A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P1 | A2 | none | | A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | | A10 | Axum identity features: providers, abuse protection and SharedID write-back, on the core work of T11 to T13 | ID-4, ID-7, WB-3 | Feature | L | P1 | A2, A7, R2, T11, T12, T13 | A5, T14 | @@ -299,7 +299,7 @@ New: | T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | Feature | M | P1 | A2, R2, T1, T10 | none | | T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs; build state once; drop the first-request `Env` capture and config `block_on`. Split when scheduled | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3, SH-4 | Feature | XL | P2 | T6, R3, T1, T5; DEC-8 | T9, T17 | | T9 | Spin EC lifecycle, the same scope as T8; port the config `block_on` removal from RC commit `933de5921`. Split when scheduled | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3 | Feature | XL | P3 | T8, R4; DEC-8, DEC-14 | T18 | -| T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; shared counter across Axum tasks | ID-6 | Feature | M | P1 | R2; DEC-11 | T7, T11, T17 | +| T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; on Axum each limit divided by the configured task count | ID-6 | Feature | M | P1 | R2; DEC-15 | T7, T11, T17 | | T11 | Providers on Fastly, awaited before finalization; only follow-up writes after the response | ID-4, ID-5, WB-1 | Feature | M | P1 | T10, R2; PR #1487 | A10, T17 | | T12 | Refuse record creation from non-browser clients on Fastly, through EdgeZero's JA4 accessor | ID-7 | Feature | M | P1 | R2 | A10, T17 | | T13 | SharedID cookie write-back on Fastly, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | Feature | M | P1 | T3; PR #1488; DEC-7 | A10, T17 | @@ -367,7 +367,7 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. `block_on` (`main.rs:550`). Move per-request lookups out of the `thread_local`. Keep saving `/auction` body EIDs; a test at `finalize.rs:1434` asserts they are dropped. Send lookups through EdgeZero's - `HttpClient`. Key the limit per DEC-11. + `HttpClient`. Key the limit per DEC-15. - **#1489 at `29de3d446` (WB-4).** Cap every mirrored ID's expiry at 30 days. `coordinator/validate.rs:132-135` turns an unspecified expiry into `Expiry::Never`, and diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 396c3bae9..19ce19894 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -117,14 +117,14 @@ Identity runs on an adapter only where the record contract and geo are supported. Elsewhere, startup rejects identity configuration (ID-2). Delivery follows DEC-4: Fastly, Axum, Cloudflare, Spin. -| Capability | Fastly | Axum | Cloudflare | Spin | -| ----------------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations, exposed through EdgeZero by EZI-2a | Supported. DynamoDB or celld through EZI-2b | Unsupported today. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | Unsupported today. `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing. Its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | -| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | -| Rate limiter | Supported (Edge Rate Limiting) | EZI-5, in-memory per task, shared counter with more than one task (DEC-11) | EZI-5, backend per DEC-11 | EZI-5, backend per DEC-11 | -| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | -| TLS fingerprint (JA4) | Supported (Fastly SDK); EZI-3 accessor | EZI-3 from a trusted CloudFront hop | EZI-3 accessor, or absent | EZI-3 accessor, or absent | -| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | +| Capability | Fastly | Axum | Cloudflare | Spin | +| ----------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations, exposed through EdgeZero by EZI-2a | Supported. DynamoDB or celld through EZI-2b | Unsupported today. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | Unsupported today. `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing. Its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | +| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | +| Rate limiter | Supported (Edge Rate Limiting) | EZI-5, in-memory per task, each limit divided by the task count (DEC-11) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | +| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | +| TLS fingerprint (JA4) | Supported (Fastly SDK); EZI-3 accessor | EZI-3 from a trusted CloudFront hop | EZI-3 accessor, or absent | EZI-3 accessor, or absent | +| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | Cloudflare and Spin move to Supported only when EZI-2 and EZI-4 deliver for them (DEC-8). Until then their startup rejects identity configuration, and @@ -229,9 +229,11 @@ marks the record contract and geo as supported. response, or the work is skipped and the response is unchanged. - **ID-6 Rate limiting.** Provider lookups, batch sync, pull sync and record creation are rate limited on every supported adapter. Batch sync is keyed - per partner; the other keys follow DEC-11. With more than one Axum task the - counter is shared. Accept: per-adapter tests hit each limit and see the - limited request refused or skipped, and two Axum tasks share one counter. + per partner; the other keys follow DEC-15. On Axum each task keeps its own + counter and enforces each limit divided by the configured task count + (DEC-11). Accept: per-adapter tests hit each limit and see the limited + request refused or skipped, and an Axum task configured for two tasks + refuses at half the limit. State: Fastly Edge Rate Limiting is the only limiter; record creation is limited nowhere; #1487 keys lookups on the IP-derived EC prefix, so everyone behind one IPv4 address or IPv6 /64 shares a budget. @@ -399,8 +401,8 @@ part. Axum uses CloudFront geo from IABTechLab/edgezero#439. Accept: a US request on Cloudflare reports country and region. - **EZI-5 Rate limiter.** A limiter with Fastly Edge Rate Limiting and - in-memory and shared-counter backends for Axum, then Cloudflare and Spin - backends per DEC-11. Accept: a contract test passes per backend. + an in-memory backend for Axum, then Cloudflare and Spin backends per + DEC-15. Accept: a contract test passes per backend. - **EZI-6 Releases.** R1 contains IABTechLab/edgezero#275 through IABTechLab/edgezero#439, so Axum production serving does not wait for the record contract. R1b contains EZI-2a and EZI-2b. R2 contains the Fastly and @@ -416,7 +418,8 @@ part. - The existing EC settings enable identity; startup rejects them where identity is unsupported (ID-2). - Provider `rate_limit_per_hour` and `mirror_enabled` from #1487 and #1489 - follow DEC-11 and DEC-10. + follow DEC-15 and DEC-10. On Axum every limit is divided by the configured + task count (DEC-11). - The reference deployment turns geo on (AX-3). - Environment overrides use the `TRUSTED_SERVER__` prefix only (AX-2). - Record stores are EdgeZero logical stores in `edgezero.toml`, with no @@ -479,6 +482,10 @@ Decided: without any Cloudflare or Spin work, so every ticket and release is split by platform and no Fastly or Axum item is blocked by a Cloudflare or Spin item. Cloudflare and Spin follow the MVP. +- **DEC-11 Axum rate limiter backend** (user, 2026-11-12). Each Axum task + keeps its own in-memory counter and enforces each limit divided by the + configured task count, so the total across tasks stays within the limit. + The MVP has no shared counter. Proposed and assumed by the plan until confirmed: @@ -502,13 +509,14 @@ Open: shared by everyone behind one IP. Blocks enabling LiveRamp in #1488, and #1245. - **DEC-10 Mirror partners.** Which partners get `mirror_enabled`. Blocks T14. -- **DEC-11 Rate limiter key and backend, and the Axum task count.** Blocks - EZI-5, EZI-5c, EZI-5s, T10 and A7. - **DEC-12 EC EID source.** Which source name the EC uses, and whether it keeps going out as `user.id`. Blocks #1549; no other ticket until decided. - **DEC-13 `ts-ec` refresh** for returning readers. Blocks T16. - **DEC-14 Spin after-response work.** Run it before the response, or skip it. Blocks T9. +- **DEC-15 Rate limiter keys, the Axum task count, and the Cloudflare and Spin + limiter backends.** Batch sync stays keyed per partner. Blocks EZI-5c, + EZI-5s, T10, A7 and #1487's key item. ## How the claims were checked @@ -555,3 +563,8 @@ state and the platform table say so; EZI-6 moves the record contract into R1b, so A1 no longer waits for it; DEC-2's MVP is plan waves 0 to 9; and DEC-8, DEC-9, DEC-11 and DEC-12 list every ticket they block. The plan's changes are listed in the PR conversation. + +Later that day the user decided the Axum half of DEC-11: each task keeps its +own counter with every limit divided by the task count, and the MVP has no +shared counter. EZI-5 no longer waits on a decision, and the rest of the old +DEC-11 (keys, task count, Cloudflare and Spin backends) is now DEC-15. From cd5b75af1c5cd31b647cf3d56a4c2e93b7e4ac21 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 08:42:39 -0800 Subject: [PATCH 11/31] Add the EC route to the identity MVP and require the Axum mirror cookie The script Trusted Server injects into proxied pages calls a same-origin route, /_ts/api/v1/ec by default, once the CMP reports consent. The route mints the EC, writes the record, runs the providers and sets ts-ec, the SharedID cookie and the mirror cookie on its own response, answering both Prebid's pixelUrl and the script's JSON call (ID-9). ID-1 and WB-1 now include it. T19 builds it in core and on Fastly, T20 wires the script and A11 serves it on Axum. T14, the Axum mirror cookie, becomes P1 so the IDs beyond SharedID are written back on Axum in the MVP. --- ...11-11-server-side-identity-all-adapters.md | 56 ++++++++++--------- ...erver-side-identity-all-adapters-design.md | 50 ++++++++++++++--- 2 files changed, 72 insertions(+), 34 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index e546e3834..924e37c1e 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -162,10 +162,11 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 6 | TS T11 | P1 | New | Providers awaited before finalization on Fastly | T10, R2, PR #1487 | | 6 | TS A7 | P1 | New | Reference infrastructure | A1, A6, #1529; DEC-6, DEC-15 | | 7 | TS A2 | P1 | New | Axum EC lifecycle | T5, T6, A1, #1529; DEC-6 | +| 7 | TS T19 | P1 | New | EC route on Fastly: mint and write back on a same-origin call; pixel and JSON responses | T10, T11, T12, T13 | | 8 | TS A10, T7, A8 | P1 | New | Axum providers, abuse protection and SharedID write-back; Axum partner APIs; SIGTERM test in CI | A2 plus their Part B blockers | +| 8 | TS T20 | P1 | New | Injected script calls the EC route on consent and merges the IDs into Prebid | T15, T19 | | 8 | TS #1541, #1424, #1499, #1531 to #1540 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | -| 9 | TS A5 | P1 | New | Load test | A2, A7, A10 | -| 9 | TS T14 | P2 | New | Mirror cookie on Axum | A10, PR #1489; DEC-10 | +| 9 | TS A5, A11, T14 | P1 | New | Load test; EC route on Axum; mirror cookie on Axum | A2, A7, A10 (A11 also T19; T14 also PR #1489, DEC-10) | | 10 | EZ EZI-1c, EZI-2c, EZI-4c, EZI-5c; IABTechLab/edgezero#91 | P2 | New, Move | Cloudflare parts in EdgeZero | Part B blockers; DEC-8, DEC-15 | | 11 | EZ R3 | P2 | Release | Cloudflare release | wave 10 | | 12 | TS #1518, T8 | P2 | Move, New | Geo source check; Cloudflare EC lifecycle | R3 (T8 also T6, T1, T5; DEC-8) | @@ -267,7 +268,8 @@ New: | A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded | AX-3, AX-6, ID-7 | Feature | L | P1 | A1, A6, #1529; DEC-6, DEC-15 | A5, A10 | | A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P1 | A2 | none | | A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | -| A10 | Axum identity features: providers, abuse protection and SharedID write-back, on the core work of T11 to T13 | ID-4, ID-7, WB-3 | Feature | L | P1 | A2, A7, R2, T11, T12, T13 | A5, T14 | +| A10 | Axum identity features: providers, abuse protection and SharedID write-back, on the core work of T11 to T13 | ID-4, ID-7, WB-3 | Feature | L | P1 | A2, A7, R2, T11, T12, T13 | A5, A11, T14 | +| A11 | EC route on Axum, on T19's core route | ID-9, WB-1 | Feature | S | P1 | A10, T19 | none | Existing, moved under #1558: @@ -288,26 +290,28 @@ they sit outside the MVP; A7 carries Axum readiness. New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------- | ------- | ---- | --- | ------------------------------- | ------------ | -| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T7, T8 | -| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | -| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | -| T4 | Build shared state once per process on Axum: settings, partner registry (built per request today), store clients; keep the per-request HTTP client unless measured. #1192 is the Fastly half | SH-1, SH-3 | Task | M | P2 | #1507 | none | -| T5 | Identity conformance suite for Fastly and Axum | ID-1 | Task | M | P1 | #1507 | A2, T8 | -| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | -| T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | Feature | M | P1 | A2, R2, T1, T10 | none | -| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs; build state once; drop the first-request `Env` capture and config `block_on`. Split when scheduled | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3, SH-4 | Feature | XL | P2 | T6, R3, T1, T5; DEC-8 | T9, T17 | -| T9 | Spin EC lifecycle, the same scope as T8; port the config `block_on` removal from RC commit `933de5921`. Split when scheduled | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3 | Feature | XL | P3 | T8, R4; DEC-8, DEC-14 | T18 | -| T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; on Axum each limit divided by the configured task count | ID-6 | Feature | M | P1 | R2; DEC-15 | T7, T11, T17 | -| T11 | Providers on Fastly, awaited before finalization; only follow-up writes after the response | ID-4, ID-5, WB-1 | Feature | M | P1 | T10, R2; PR #1487 | A10, T17 | -| T12 | Refuse record creation from non-browser clients on Fastly, through EdgeZero's JA4 accessor | ID-7 | Feature | M | P1 | R2 | A10, T17 | -| T13 | SharedID cookie write-back on Fastly, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | Feature | M | P1 | T3; PR #1488; DEC-7 | A10, T17 | -| T14 | Mirror cookie on Axum | WB-4 | Feature | S | P2 | A10; PR #1489; DEC-10 | T17 | -| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | none | -| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | -| T17 | Cloudflare identity features: providers, rate limits, abuse protection, SharedID write-back and mirror cookie. Split when scheduled | ID-4, ID-6, ID-7, WB-3, WB-4 | Feature | XL | P2 | T8, R3, T10, T11, T12, T13, T14 | T18 | -| T18 | Spin identity features, the same scope as T17. Split when scheduled | ID-4, ID-6, ID-7, WB-3, WB-4 | Feature | XL | P3 | T9, R4, T17 | none | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------- | ---- | --- | ------------------------------------ | ----------------- | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T7, T8 | +| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | +| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | +| T4 | Build shared state once per process on Axum: settings, partner registry (built per request today), store clients; keep the per-request HTTP client unless measured. #1192 is the Fastly half | SH-1, SH-3 | Task | M | P2 | #1507 | none | +| T5 | Identity conformance suite for Fastly and Axum | ID-1 | Task | M | P1 | #1507 | A2, T8 | +| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | +| T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | Feature | M | P1 | A2, R2, T1, T10 | none | +| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs; build state once; drop the first-request `Env` capture and config `block_on`. Split when scheduled | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3, SH-4 | Feature | XL | P2 | T6, R3, T1, T5; DEC-8 | T9, T17 | +| T9 | Spin EC lifecycle, the same scope as T8; port the config `block_on` removal from RC commit `933de5921`. Split when scheduled | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3 | Feature | XL | P3 | T8, R4; DEC-8, DEC-14 | T18 | +| T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; on Axum each limit divided by the configured task count | ID-6 | Feature | M | P1 | R2; DEC-15 | T7, T11, T17, T19 | +| T11 | Providers on Fastly, awaited before finalization; only follow-up writes after the response | ID-4, ID-5, WB-1 | Feature | M | P1 | T10, R2; PR #1487 | A10, T17, T19 | +| T12 | Refuse record creation from non-browser clients on Fastly, through EdgeZero's JA4 accessor | ID-7 | Feature | M | P1 | R2 | A10, T17, T19 | +| T13 | SharedID cookie write-back on Fastly, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | Feature | M | P1 | T3; PR #1488; DEC-7 | A10, T17, T19 | +| T14 | Mirror cookie on Axum | WB-4 | Feature | S | P1 | A10; PR #1489; DEC-10 | T17 | +| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | +| T17 | Cloudflare identity features: providers, rate limits, abuse protection, SharedID write-back, mirror cookie and the EC route. Split when scheduled | ID-4, ID-6, ID-7, ID-9, WB-3, WB-4 | Feature | XL | P2 | T8, R3, T10, T11, T12, T13, T14, T19 | T18 | +| T18 | Spin identity features, the same scope as T17. Split when scheduled | ID-4, ID-6, ID-7, WB-3, WB-4 | Feature | XL | P3 | T9, R4, T17 | none | +| T19 | EC route in core and on Fastly: `/_ts/api/v1/ec` mints, records, runs the providers and sets the identity cookies on a consented same-origin call, with a pixel response for Prebid's `pixelUrl` and a JSON response with the IDs | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T10, T11, T12, T13 | A11, T17, T20 | +| T20 | The injected script calls the EC route once the CMP reports consent and merges the returned IDs into Prebid | ID-9, WB-6 | Feature | M | P1 | T15, T19 | none | Existing, moved under #1558: @@ -533,13 +537,13 @@ issue, and one on IABTechLab/edgezero#391 linking R1. ### Task 3: File the Axum tickets under #1558 Same steps as Task 2, against IABTechLab/trusted-server and #1558, for the -nine new rows in table order. Each body has Summary (Ticket), Requirements +ten new rows in table order. Each body has Summary (Ticket), Requirements (Reqs, linked to the spec section), Acceptance (each requirement's Accept line) and Blocked by. Record the map in a `CREATOR:` comment on #1558. ### Task 4: File the Trusted Server tickets under #1558 -Same steps as Task 3, for T1 to T18 in table order, under #1558. Record the +Same steps as Task 3, for T1 to T20 in table order, under #1558. Record the map in a `CREATOR:` comment on #1558. ### Task 5: Link the dependencies @@ -593,7 +597,7 @@ issue from Task 2. Run `gh api graphql -f query='{repository(owner:"IABTechLab",name:""){issue(number:){subIssues(first:100){totalCount}}}}' --jq '.data.repository.issue.subIssues.totalCount'` for #1558 and IABTechLab/edgezero#468. -Expected: 43 and 29. +Expected: 46 and 29. - [ ] **Step 2: Check that Fastly and Axum stand alone** diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 19ce19894..aa358f1d5 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -18,7 +18,8 @@ Epics: #1558 (Trusted Server) and IABTechLab/edgezero#468 (EdgeZero). Plan: 3. SharedID is read, stored and passed to bidders the way Prebid defines it, and shared state is passed through each adapter the right way. 4. Identity is written back as first-party cookies on the HTML document - response. + response, and on a same-origin EC route that the script Trusted Server + injects into the page calls once the reader consents. Target deployment profile: a publisher that runs client-side Prebid and wants identity without the auction orchestrator. Its pages pass through Trusted @@ -194,7 +195,7 @@ marks the record contract and geo as supported. ### ID: Server-side identity on every adapter - **ID-1 One EC lifecycle.** On every supported adapter a consented document - navigation mints the EC, creates the record and sets `ts-ec` together. A + navigation or EC route call (ID-9) mints the EC, creates the record and sets `ts-ec` together. A cookie is never issued without a record. An existing EC is kept when geo is unknown. Accept: the conformance suite (T5) asserts on each supported adapter that a consented navigation creates the record and sets `ts-ec` in @@ -249,6 +250,27 @@ marks the record contract and geo as supported. saved to the record. Accept: on each supported adapter a body EID is in the record after the request. State: main saves them on Fastly; the provider stack and the RC's Axum lifecycle do not. +- **ID-9 EC route.** A same-origin route, `/_ts/api/v1/ec` by default, mints + the EC when the reader has none and consent allows, creates the record, runs + the providers as a navigation does, and sets `ts-ec`, the SharedID cookie and + the mirror cookie on its own response. The script Trusted Server injects + into proxied pages calls it once the CMP reports consent, so a reader who + consents after the page loads gets an EC on that page view instead of the + next navigation. A plain `GET` answers Prebid's SharedID `pixelUrl` with an + empty response; the script's call gets JSON with the SharedID and the EIDs + the record holds, which the script merges into Prebid. The route mints + nothing for a cross-site caller, a client ID-7 refuses or a call past the + record-creation limit (ID-6), and every response carries + `Cache-Control: no-store`. It is served from the page's own host, which + WebKit's cap on cookies from non-document responses exempts when the IP + matches the page's (read from WebKit's source, not yet tested in Safari). + Accept: on Fastly and Axum, a consented same-site call with no `ts-ec` + creates the record and sets `ts-ec` and the SharedID cookie in that + response; the JSON call returns the record's IDs; a cross-site `Origin`, a + scripted client and a call past the limit mint nothing; an unchanged + returning reader gets no `Set-Cookie`. State: on main only a real-browser + navigation through the publisher proxy mints an EC, and no such route + exists. ### SH: Shared state passed the right way @@ -321,10 +343,10 @@ marks the record contract and geo as supported. ### WB: First-party write-back -- **WB-1 Document response only.** Identity cookies are set on top-level HTML - document responses, on every supported adapter. Accept: identity - `Set-Cookie` headers appear on navigation responses and never on asset or - API responses. State: main mints `ts-ec` on Fastly navigations, and the RC +- **WB-1 Document and EC route responses only.** Identity cookies are set on + top-level HTML document responses and on EC route responses (ID-9), on every + supported adapter. Accept: identity `Set-Cookie` headers appear on navigation + and EC route responses and never on asset or other API responses. State: main mints `ts-ec` on Fastly navigations, and the RC adds Axum. - **WB-2 `ts-ec` lifetime.** `ts-ec` keeps `Domain=.; Path=/; Secure; SameSite=Lax; HttpOnly; Max-Age=31536000`. @@ -424,6 +446,7 @@ part. - Environment overrides use the `TRUSTED_SERVER__` prefix only (AX-2). - Record stores are EdgeZero logical stores in `edgezero.toml`, with no adapter-local store setting (ID-2). +- The EC route path is configurable; the default is `/_ts/api/v1/ec` (ID-9). ## Consent and privacy @@ -435,6 +458,10 @@ part. and filtered per vendor (WB-4, DEC-10). - The `_pubcid_optout` cookie is honored; a localStorage opt-out is invisible to the server (SID-2). +- The EC route reads consent from the request's cookies and `Sec-GPC`, as a + navigation does, or from the TCF or GPP string the injected script reads + from the CMP and sends with the call. Both pass through the same decoding + and the same Purpose 1 rule (ID-9). ## Failure handling @@ -462,8 +489,9 @@ part. ## Out of scope -- A narrow install with one path rule and no page proxy. It needs an EC minted - on a request that is not a navigation. +- A narrow install with one path rule and no page proxy. The EC route (ID-9) + serves pages that Trusted Server proxies; that install would also need the + script served without the page proxy. - Conformance with the SharedID Edge specification. - An Akamai adapter. @@ -568,3 +596,9 @@ Later that day the user decided the Axum half of DEC-11: each task keeps its own counter with every limit divided by the task count, and the MVP has no shared counter. EZI-5 no longer waits on a decision, and the rest of the old DEC-11 (keys, task count, Cloudflare and Spin backends) is now DEC-15. + +The user then brought the EC route into the MVP (ID-9): the script Trusted +Server injects into proxied pages calls a same-origin route that mints the EC +and writes the cookies on its own response, and the route answers both +Prebid's `pixelUrl` and the script's JSON call. ID-1 and WB-1 now include that +route, and the Axum mirror cookie (T14) is P1. From 02e029ff89d7770363ba8f1c5677de6db8265dd9 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 10:20:28 -0800 Subject: [PATCH 12/31] Build identity once for every adapter and restack the Axum line on #1506 Per the user, every feature is built once and works on every adapter in platform order, with platform gaps sent to EdgeZero (DEC-16, SH-5). T21 moves the identity routes, EC lifecycle, providers and write-back into one router in core that each adapter mounts; A2, A10, T8, T9, T17 and T18 shrink to turning it on per adapter and passing the conformance suite, and T7, T14 and A11 fold into A2 and A10. EZI-2a adds EdgeZero's local store for Axum development and EZI-4 binds Axum's geo provider from the manifest. Restack the Axum RC units on #1506: #1531 carries A1 in place of #1528, #1533 and #1534 close as copies of #1525 and #1526, and the image and Terraform units sit directly on #1532 so A6 and A7 stop waiting on A2. Record #1566, refresh the provider stack's fixes at their current heads, and flag #1535 as unplanned. --- ...11-11-server-side-identity-all-adapters.md | 299 ++++++++++-------- ...erver-side-identity-all-adapters-design.md | 38 ++- 2 files changed, 208 insertions(+), 129 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 924e37c1e..6ec152af4 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -27,8 +27,12 @@ IABTechLab/edgezero. and Axum ship without Cloudflare or Spin: no Fastly or Axum item is blocked by a Cloudflare or Spin item, and Cloudflare and Spin work has its own tickets. A Fastly or Axum ticket may still make Cloudflare and Spin reject identity - configuration, answer 501 or report a capability as absent (T1, T6, - IABTechLab/edgezero#283), because that needs no Cloudflare or Spin work. + configuration, answer 501, report a capability as absent or mount the shared + app (T1, T6, T21, IABTechLab/edgezero#283), because that needs no Cloudflare + or Spin work. +- Every feature is built once in core and works on every adapter in DEC-4's + order (DEC-16, SH-5). A capability an adapter lacks becomes an EdgeZero ask; + no ticket adds identity code to one adapter crate. - Only issues move, and they move under the new epics: #1558 for Trusted Server and IABTechLab/edgezero#468 for EdgeZero. Existing epics (#1236, #1505, IABTechLab/trusted-server#90, IABTechLab/edgezero#88, @@ -75,24 +79,30 @@ IABTechLab/edgezero. Trusted Server: -| PR | Head | Base on GitHub | Fix | -| --------------------- | ----------- | -------------- | -------------------------------------------------------------------------------------------------------------------- | -| #1557 | `76e1dd5f6` | main | None | -| #1525 | `cd8977cad` | main | None; #1526 does not need it | -| #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | -| #1516 | `214cd4821` | main | None | -| #1503 | `5e10c24e4` | main | None; needs neither #1506 nor new EdgeZero | -| #1449 | `fd33f2c74` | main | None; needs neither #1506 nor new EdgeZero | -| #1506 | `ba5790b8a` | main | Rebase after #1557, #1526 and #1516 | -| #1487 | `1600781eb` | main | Rebase after #1526 and #1506 | -| #1488 | `7f55776f3` | #1487's branch | None | -| #1489 | `d3780ba13` | #1488's branch | None | -| #1527 | `a020daeec` | #1526's branch | Close once #1506 covers its tests (DEC-1) | -| #1528 | `ea779fb47` | #1527's branch | Port its two commits onto #1506's outbound API; they modify #1527-only `platform/outbound.rs`; repin off `456313675` | -| #1543 | `e5e6ea7fa` | #1449's branch | None; ships with A6 | -| #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | -| #1499 | `084fdbba9` | #1424's branch | Close at parity | -| #1541, #1531 to #1540 | various | RC baselines | Forward-port to main as new PRs; close each as its port lands | +| PR | Head | Base on GitHub | Fix | +| ------------------- | ------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| #1557 | `76e1dd5f6` | main | None | +| #1525 | `cd8977cad` | main | None; #1526 does not need it | +| #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | +| #1516 | `214cd4821` | main | None | +| #1503 | `5e10c24e4` | main | None; needs neither #1506 nor new EdgeZero | +| #1449 | `fd33f2c74` | main | None; needs neither #1506 nor new EdgeZero | +| #1506 | `6c39a56a1` | main | Rebase after #1557, #1526 and #1516; clear CodeQL alert 283 | +| #1487 | `1600781eb` | main | Rebase after #1526 and #1506: 8 and 17 files conflict | +| #1488 | `7f55776f3` | #1487's branch | Take #1566's two #1488 commits | +| #1489 | `d3780ba13` | #1488's branch | None | +| #1566 | `117a58e0e` | #1488's branch | Move its two #1488 commits down; rebuild the Axum part on T21 and EdgeZero (Part C). 1 file conflicts with #1489, 19 with #1506 | +| #1527 | `a020daeec` | #1526's branch | Close once #1506 covers its tests (DEC-1) | +| #1528 | `ea779fb47` | #1527's branch | Close once #1531 carries A1 | +| #1543 | `e5e6ea7fa` | #1449's branch | None; ships with A6 | +| #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | +| #1499 | `084fdbba9` | #1424's branch | Close at parity | +| #1531 | `88a6ba596` | #1506's branch | Retargeted without a rebase: it forks from main with its own cutover and the RC tag pin, and 13 files conflict with #1506. Rebase onto #1506; it then delivers A1 | +| #1532 | `852b875fe` | #1531's branch | Delivers #1529 (A4); restack after #1531 | +| #1533, #1534 | `82d9a0e66`, `646c39a34` | #1532's and #1533's branches | RC-baseline copies of #1525 and #1526: drop from the chain and close | +| #1536, #1535, #1537 | `296e05405`, `0666a9b4b`, `4a838272c` | #1534's, #1536's and #1535's branches | Restack on #1532. #1536 and #1537 feed A2 without their Axum-only stores; #1535 has no ticket | +| #1539, #1538, #1540 | `8d41cb06c`, `3111c2456`, `eb6e19ef4` | #1537's, #1539's and #1538's branches | Restack on #1532: they share no files with #1533 to #1537, so A6 and A7 stop waiting on A2 | +| #1541 | `7dd2e3056` | main | Close at parity | EdgeZero: @@ -106,7 +116,8 @@ EdgeZero: | IABTechLab/edgezero#453 | `633f0552` | IABTechLab/edgezero#439's branch | Rebase off its stale base | | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452 | various | main | Land after IABTechLab/edgezero#275 merges; they conflict with it in 7, 6, 4 and 4 files | | IABTechLab/edgezero#466 | `cb48c6b3` | main | Land after IABTechLab/edgezero#444; they conflict in `edgezero-cli` `config.rs` and `lib.rs` | -| IABTechLab/edgezero#287 | `d590f0eb` | main | Last; shares 52 files with IABTechLab/edgezero#275, 27 of them under `crates/` | +| IABTechLab/edgezero#469 | `1d5bf2ff` | main | JA4 interface design for IABTechLab/edgezero#283; draft | +| IABTechLab/edgezero#287 | `ce25ef5a` | main | Last; shares 52 files with IABTechLab/edgezero#275, 27 of them under `crates/` | ### Waves @@ -134,6 +145,7 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439; IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | | 2 | EZ EZI-2a, EZI-3, EZI-5 | P1 | New | Record contract with Fastly; ClientInfo contract; rate limiter for Fastly and Axum | IABTechLab/edgezero#275 | | 2 | TS T5 | P1 | New | Conformance suite for Fastly and Axum | #1507 | +| 2 | TS T21 | P1 | New | One identity app in core for every adapter | T1, #1507 | | 2 | TS T4, #1192 | P2 | New, Move | Shared state once per process: Axum (T4) and Fastly (#1192) | #1507 | | 2 | TS #1556 | P2 | Move | Regression test that identity backends verify certificates, then close | #1507 | | 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | @@ -146,35 +158,35 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 4 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1488 | | 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | | 4 | EZ R2 | P1 | Release | Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-1, EZI-3, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | -| 4 | TS A1 | P1 | New, PR fix | Port #1528, repin to R1 | R1, #1507; DEC-5, DEC-6 | +| 4 | TS A1 | P1 | New, PR fix | Rebase #1531 onto #1506, repin to R1; #1528 closes | R1, #1507; DEC-5, DEC-6 | | 4 | TS T3 | P1 | New | SharedID cleanup | PR #1488 | | 4 | TS #1555 | P2 | Move | Opt-out EIDs | PR #1488 | | 4 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | | 5 | EZ R1b | P1 | Release | EZI-2a and EZI-2b | EZI-2a, EZI-2b | -| 5 | TS #1529 (A4), A6 with #1543, A3 | P1 | Move, New | CloudFront IP and geo; production image; SIGTERM drain | A1 (A3 also R2; A6 also PR #1449) | +| 5 | TS #1529 (A4), A6 with #1543, A3 | P1 | Move, New | CloudFront IP and geo via #1532; production image via #1539 and #1538; SIGTERM drain | A1 (A3 also R2; A6 also PR #1449) | | 5 | TS T2 | P1 | New | EIDs in `user.eids`; after the provider stack, so open PRs need no test churn | #1489 merged | -| 5 | TS T10, T12, T13 | P1 | New | Rate limits on Fastly and Axum; abuse protection and SharedID write-back on Fastly | R2; DEC-15 (T10); T3, PR #1488, DEC-7 (T13) | +| 5 | TS T10, T12, T13 | P1 | New | Rate limits on Fastly and Axum; abuse protection and SharedID write-back in the shared app | R2, T21; DEC-15 (T10); T3, PR #1488, DEC-7 (T13) | | 5 | TS T15 | P1 | New | Identity-only tsjs | PR #1489 | | 5 | TS A9, #1141 | P2 | New, Move | `TRUSTED_SERVER__` guard; local config handoff | A1 | | 5 | TS #1551 | P2 | Move | Pull sync on a full row | PR #1489 | -| 5 | TS #1527, #1523 | P2 | Close | Tests moved to #1506 | #1506, A1 | +| 5 | TS #1527, #1528, #1523, #1533, #1534 | P2 | Close | Tests moved to #1506; A1 on #1531; RC copies of #1525 and #1526 | #1506, A1 | | 6 | TS T6 | P1 | New | Shared record backend, Fastly first | R1b, #1522 | -| 6 | TS T11 | P1 | New | Providers awaited before finalization on Fastly | T10, R2, PR #1487 | -| 6 | TS A7 | P1 | New | Reference infrastructure | A1, A6, #1529; DEC-6, DEC-15 | -| 7 | TS A2 | P1 | New | Axum EC lifecycle | T5, T6, A1, #1529; DEC-6 | -| 7 | TS T19 | P1 | New | EC route on Fastly: mint and write back on a same-origin call; pixel and JSON responses | T10, T11, T12, T13 | -| 8 | TS A10, T7, A8 | P1 | New | Axum providers, abuse protection and SharedID write-back; Axum partner APIs; SIGTERM test in CI | A2 plus their Part B blockers | +| 6 | TS T11 | P1 | New | Providers awaited before finalization in the shared app | T10, T21, R2, PR #1487 | +| 6 | TS A7 | P1 | New | Reference infrastructure via #1540 | A1, A6, #1529; DEC-6, DEC-15 | +| 7 | TS A2 | P1 | New | Identity on Axum through the shared app, from #1536 and #1537 | T5, T6, T21, A1, #1529; DEC-6 | +| 7 | TS T19 | P1 | New | EC route in the shared app: mint and write back on a same-origin call; pixel and JSON responses | T10, T11, T12, T13, T21 | +| 8 | TS A10, A8 | P1 | New | Identity features pass on Axum; SIGTERM test in CI | A2 plus their Part B blockers | | 8 | TS T20 | P1 | New | Injected script calls the EC route on consent and merges the IDs into Prebid | T15, T19 | -| 8 | TS #1541, #1424, #1499, #1531 to #1540 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | -| 9 | TS A5, A11, T14 | P1 | New | Load test; EC route on Axum; mirror cookie on Axum | A2, A7, A10 (A11 also T19; T14 also PR #1489, DEC-10) | +| 8 | TS #1541, #1424, #1499 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | +| 9 | TS A5 | P1 | New | Load test | A2, A7, A10 | | 10 | EZ EZI-1c, EZI-2c, EZI-4c, EZI-5c; IABTechLab/edgezero#91 | P2 | New, Move | Cloudflare parts in EdgeZero | Part B blockers; DEC-8, DEC-15 | | 11 | EZ R3 | P2 | Release | Cloudflare release | wave 10 | -| 12 | TS #1518, T8 | P2 | Move, New | Geo source check; Cloudflare EC lifecycle | R3 (T8 also T6, T1, T5; DEC-8) | -| 13 | TS T17 | P2 | New | Cloudflare providers, limits, abuse protection and cookies | T8, R3, T10 to T14 | +| 12 | TS #1518, T8 | P2 | Move, New | Geo source check; identity on Cloudflare through the shared app | R3 (T8 also T6, T1, T5, T21; DEC-8) | +| 13 | TS T17 | P2 | New | Identity features pass on Cloudflare | T8, R3, T10 to T13, T19 | | 14 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s | P3 | New | Spin parts in EdgeZero | Part B blockers; DEC-8, DEC-15 | | 15 | EZ R4; IABTechLab/edgezero#89 | P3 | Release, Move | Spin release; ClientInfo on every adapter | wave 14, R3 | -| 16 | TS T9 | P3 | New | Spin EC lifecycle | T8, R4; DEC-8, DEC-14 | -| 17 | TS T18 | P3 | New | Spin providers, limits, abuse protection and cookies | T9, R4, T17 | +| 16 | TS T9 | P3 | New | Identity on Spin through the shared app | T8, R4; DEC-8, DEC-14 | +| 17 | TS T18 | P3 | New | Identity features pass on Spin | T9, R4, T17 | | any | TS #1142 | P2 | Move | Health and startup contract on all four adapters; A7 carries Axum readiness | none | | any | TS #1549 | P2 | Move | No `user.id` for a row-less EC | DEC-12; PR #1487 | | any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP with PR IABTechLab/edgezero#453; IABTechLab/edgezero#287 last | DEC-13; DEC-9; PR IABTechLab/edgezero#453; IABTechLab/edgezero#275 | @@ -205,7 +217,8 @@ rows are post-MVP. "Blocked by" is set as a GitHub dependency between issues. A PR or a decision there goes in the issue body. "Blocks" is the inverse and is not set separately. No ticket mixes Fastly or Axum work with Cloudflare or Spin work -(DEC-4), except the rejection, 501 and absence paths in Global Constraints. +(DEC-4), except the shared app, rejection, 501 and absence paths in Global +Constraints. ### EdgeZero, under IABTechLab/edgezero#468 @@ -213,7 +226,7 @@ New: | ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | | ------ | -------------------------------------------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| EZI-2a | `KvStore` record contract and the Fastly provider | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1b | +| EZI-2a | `KvStore` record contract, the Fastly provider and the local in-memory store | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1b | | EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | R1b | | EZI-3 | ClientInfo contract: portable client IP, geo and TLS metadata with explicit absence, the contract part of IABTechLab/edgezero#89 | EZI-3 | Feature | M | P1 | PR IABTechLab/edgezero#275 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, IABTechLab/edgezero#283, EZI-3s, IABTechLab/edgezero#89, R2 | | EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PRs IABTechLab/edgezero#275, IABTechLab/edgezero#406 | EZI-1c, EZI-1s, R2 | @@ -229,7 +242,7 @@ New: | EZI-5s | Rate limiter backend for Spin | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | R4 | | R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | | R1b | Release the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | -| R2 | Release the Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T7, T10, T11, T12 | +| R2 | Release the Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T10, T11, T12 | | R3 | Release the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, EZI-5c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | | R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s, R3 | T9, T18 | @@ -237,18 +250,18 @@ Existing, moved under IABTechLab/edgezero#468. IABTechLab/edgezero#89 to IABTech IABTechLab/edgezero#88 and the last six leave IABTechLab/edgezero#391 or have no parent; both old epics stay where they are: -| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | -| ----------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------ | ---- | --- | ------------------------------------------------------------------------------------- | --------------------------------------------------- | -| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | S | P3 | EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | -| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | EZI-3 | R2, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | EZI-3, IABTechLab/edgezero#397 | R2, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | EZI-3 | R3, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#397 | Axum: safe production diagnostics and trusted-proxy handling (delivered by PR IABTechLab/edgezero#409) | Task (keep) | M | P1 | none | R1, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | -| IABTechLab/edgezero#410 | Native AWS configuration, secret and KV store providers (PR IABTechLab/edgezero#414) | Task (keep) | L | P1 | none | EZI-2b, R1 | -| IABTechLab/edgezero#437 | Native celld configuration and KV store providers (PR IABTechLab/edgezero#422) | Task (keep) | M | P1 | none | EZI-2b, R1 | -| IABTechLab/edgezero#432 | Axum: protected CloudFront Geo metadata provider (PR IABTechLab/edgezero#439) | Story (keep) | M | P1 | none | R1, EZI-4c, EZI-4s | -| IABTechLab/edgezero#438 | Axum: explicit local GeoIP provider (PR IABTechLab/edgezero#453) | Story (keep) | M | P3 | none | none | -| IABTechLab/edgezero#283 | Expose JA4 through a standard request interface; R2 needs its Fastly and Axum parts, and other adapters report absence | Task (keep) | M | P1 | EZI-3, IABTechLab/edgezero#397 | R2 | +| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---- | --- | ------------------------------------------------------------------------------------- | --------------------------------------------------- | +| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | S | P3 | EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | +| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | EZI-3 | R2, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | EZI-3, IABTechLab/edgezero#397 | R2, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | EZI-3 | R3, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#397 | Axum: safe production diagnostics and trusted-proxy handling (delivered by PR IABTechLab/edgezero#409) | Task (keep) | M | P1 | none | R1, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | +| IABTechLab/edgezero#410 | Native AWS configuration, secret and KV store providers (PR IABTechLab/edgezero#414) | Task (keep) | L | P1 | none | EZI-2b, R1 | +| IABTechLab/edgezero#437 | Native celld configuration and KV store providers (PR IABTechLab/edgezero#422) | Task (keep) | M | P1 | none | EZI-2b, R1 | +| IABTechLab/edgezero#432 | Axum: protected CloudFront Geo metadata provider (PR IABTechLab/edgezero#439) | Story (keep) | M | P1 | none | R1, EZI-4c, EZI-4s | +| IABTechLab/edgezero#438 | Axum: explicit local GeoIP provider (PR IABTechLab/edgezero#453) | Story (keep) | M | P3 | none | none | +| IABTechLab/edgezero#283 | Expose JA4 through a standard request interface (design in PR IABTechLab/edgezero#469); R2 needs its Fastly and Axum parts, and other adapters report absence | Task (keep) | M | P1 | EZI-3, IABTechLab/edgezero#397 | R2 | IABTechLab/edgezero#89 stays the umbrella story: EZI-3 carries its contract, and it completes with the last adapter's mapping. IABTechLab/edgezero#397, IABTechLab/edgezero#410 and IABTechLab/edgezero#432 leave @@ -258,18 +271,17 @@ IABTechLab/edgezero#391, which stays an epic in place; R1 is linked from it. New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------- | ---- | --- | ---------------------------- | -------------------------------- | -| A1 | Serve Axum in production mode from main: port #1528 onto #1506's outbound API, repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507; DEC-5, DEC-6 | A2, A3, A6, A7, A9, #1529, #1141 | -| A2 | Axum EC lifecycle on the shared backend, forward-ported from #1536 and #1537, passing T5's suite | ID-1, ID-3, ID-8, WB-1 | Feature | L | P1 | T5, T6, A1, #1529; DEC-6 | A5, A8, A10, T7 | -| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | -| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | -| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; PR #1449; DEC-6 | A7 | -| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded | AX-3, AX-6, ID-7 | Feature | L | P1 | A1, A6, #1529; DEC-6, DEC-15 | A5, A10 | -| A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P1 | A2 | none | -| A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | -| A10 | Axum identity features: providers, abuse protection and SharedID write-back, on the core work of T11 to T13 | ID-4, ID-7, WB-3 | Feature | L | P1 | A2, A7, R2, T11, T12, T13 | A5, A11, T14 | -| A11 | EC route on Axum, on T19's core route | ID-9, WB-1 | Feature | S | P1 | A10, T19 | none | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------- | ---- | --- | ------------------------------ | -------------------------------- | +| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507; DEC-5, DEC-6 | A2, A3, A6, A7, A9, #1529, #1141 | +| A2 | Identity on Axum through T21's shared app, from #1536 and #1537 without their Axum-only stores; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529; DEC-6 | A5, A8, A10 | +| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | +| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | +| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; PR #1449; DEC-6 | A7 | +| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded | AX-3, AX-6, ID-7 | Feature | L | P1 | A1, A6, #1529; DEC-6, DEC-15 | A5, A10 | +| A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P1 | A2 | none | +| A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | +| A10 | Identity features pass on Axum from the shared app: providers, abuse protection, SharedID write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P1 | A2, A7, R2, T11, T12, T13, T19 | A5 | Existing, moved under #1558: @@ -283,35 +295,37 @@ Existing, moved under #1558: A1 replaces the local configuration bridge: #1528 starts Axum from `EDGEZERO__ADAPTER__CONFIG_DIR` and never reads the old `TRUSTED_SERVER_CONFIG_*` names, so #1141 then points `ts config push --local` -and `ts serve` at that directory. #1142 and #1139 cover all four adapters, so -they sit outside the MVP; A7 carries Axum readiness. +and `ts serve` at that directory; #1531 starts Axum the same way. #1142 and +#1139 cover all four adapters, so they sit outside the MVP; A7 carries Axum +readiness. #1566 overlaps A2 and A10; its Axum part is rebuilt on T21 (Part C). +T7, T14 and A11 are folded into A2 and A10, because the shared app (T21) +brings partner APIs, the mirror cookie and the EC route to every adapter. ### Trusted Server, under #1558 New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------- | ---- | --- | ------------------------------------ | ----------------- | -| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T7, T8 | -| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | -| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | -| T4 | Build shared state once per process on Axum: settings, partner registry (built per request today), store clients; keep the per-request HTTP client unless measured. #1192 is the Fastly half | SH-1, SH-3 | Task | M | P2 | #1507 | none | -| T5 | Identity conformance suite for Fastly and Axum | ID-1 | Task | M | P1 | #1507 | A2, T8 | -| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | -| T7 | Axum partner APIs on the shared backend: batch sync, pull sync, admin lookup | ID-3, ID-5 | Feature | M | P1 | A2, R2, T1, T10 | none | -| T8 | Cloudflare EC lifecycle: mint, record, `ts-ec`, identify, batch sync, pull sync, admin lookup, auction and body EIDs; build state once; drop the first-request `Env` capture and config `block_on`. Split when scheduled | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3, SH-4 | Feature | XL | P2 | T6, R3, T1, T5; DEC-8 | T9, T17 | -| T9 | Spin EC lifecycle, the same scope as T8; port the config `block_on` removal from RC commit `933de5921`. Split when scheduled | ID-1, ID-2, ID-3, ID-8, WB-1, SH-1, SH-3 | Feature | XL | P3 | T8, R4; DEC-8, DEC-14 | T18 | -| T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; on Axum each limit divided by the configured task count | ID-6 | Feature | M | P1 | R2; DEC-15 | T7, T11, T17, T19 | -| T11 | Providers on Fastly, awaited before finalization; only follow-up writes after the response | ID-4, ID-5, WB-1 | Feature | M | P1 | T10, R2; PR #1487 | A10, T17, T19 | -| T12 | Refuse record creation from non-browser clients on Fastly, through EdgeZero's JA4 accessor | ID-7 | Feature | M | P1 | R2 | A10, T17, T19 | -| T13 | SharedID cookie write-back on Fastly, with `_cst`, under SID-2 and SID-6 | WB-3, SID-6 | Feature | M | P1 | T3; PR #1488; DEC-7 | A10, T17, T19 | -| T14 | Mirror cookie on Axum | WB-4 | Feature | S | P1 | A10; PR #1489; DEC-10 | T17 | -| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | -| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | -| T17 | Cloudflare identity features: providers, rate limits, abuse protection, SharedID write-back, mirror cookie and the EC route. Split when scheduled | ID-4, ID-6, ID-7, ID-9, WB-3, WB-4 | Feature | XL | P2 | T8, R3, T10, T11, T12, T13, T14, T19 | T18 | -| T18 | Spin identity features, the same scope as T17. Split when scheduled | ID-4, ID-6, ID-7, WB-3, WB-4 | Feature | XL | P3 | T9, R4, T17 | none | -| T19 | EC route in core and on Fastly: `/_ts/api/v1/ec` mints, records, runs the providers and sets the identity cookies on a consented same-origin call, with a pixel response for Prebid's `pixelUrl` and a JSON response with the IDs | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T10, T11, T12, T13 | A11, T17, T20 | -| T20 | The injected script calls the EC route once the CMP reports consent and merges the returned IDs into Prebid | ID-9, WB-6 | Feature | M | P1 | T15, T19 | none | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------- | ---- | --- | ------------------------------- | ------------------------------- | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | +| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | +| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | +| T4 | Build shared state once per process on Axum: settings, partner registry (built per request today), store clients; keep the per-request HTTP client unless measured. #1192 is the Fastly half | SH-1, SH-3 | Task | M | P2 | #1507 | none | +| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | +| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | +| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | +| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | +| T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; on Axum each limit divided by the configured task count | ID-6 | Feature | M | P1 | R2, T21; DEC-15 | T11, T17, T19 | +| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly | ID-4, ID-5, WB-1 | Feature | M | P1 | T10, T21, R2; PR #1487 | A10, T17, T19 | +| T12 | Refuse record creation from non-browser clients in the shared app, through EdgeZero's JA4 accessor; verified on Fastly | ID-7 | Feature | M | P1 | R2, T21 | A10, T17, T19 | +| T13 | SharedID cookie write-back in the shared app, with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-6 | Feature | M | P1 | T3, T21; PR #1488; DEC-7 | A10, T17, T19 | +| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | +| T17 | Identity features pass on Cloudflare from the shared app: providers, rate limits, abuse protection, SharedID write-back, the mirror cookie and the EC route | ID-4, ID-6, ID-7, ID-9, WB-3, WB-4 | Feature | M | P2 | T8, R3, T10, T11, T12, T13, T19 | T18 | +| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-6, ID-7, ID-9, WB-3, WB-4 | Feature | M | P3 | T9, R4, T17 | none | +| T19 | EC route in the shared app, verified on Fastly: `/_ts/api/v1/ec` mints, records, runs the providers and sets the identity cookies on a consented same-origin call, with a pixel response for Prebid's `pixelUrl` and a JSON response with the IDs | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T10, T11, T12, T13, T21 | A10, T17, T20 | +| T20 | The injected script calls the EC route once the CMP reports consent and merges the returned IDs into Prebid | ID-9, WB-6 | Feature | M | P1 | T15, T19 | none | +| T21 | One identity app in core for every adapter: identify, batch sync, pull sync, admin lookup, the EC route, the EC lifecycle, providers and write-back in one router each adapter mounts; adapter crates supply only platform services through EdgeZero | SH-5, ID-1, ID-3 | Feature | L | P1 | T1, #1507 | A2, T8, T10, T11, T12, T13, T19 | Existing, moved under #1558: @@ -335,7 +349,7 @@ Not moved: - IABTechLab/trusted-server#90 is an epic, so it is linked from #1558, not moved; it and IABTechLab/trusted-server#353 close when PR #1488 merges. - #1507 delivers the cutover and stays under #1505; it blocks A1, T4, T5, - #1192 and #1556. + T21, #1192 and #1556. - Proposed closures: #1523 with #1527; IABTechLab/trusted-server#353 and #1310 once PR #1488 merges; #1189, which `56a19229f` (#1157) fixed on main. - #1556 needs only a regression test: #1506 rejects @@ -356,7 +370,11 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. ### Trusted Server -- **#1506 at `851537045` (AX-7, SID-4).** Rebase after #1557, #1526 and #1516. +- **#1506 at `6c39a56a1` (AX-7, SID-4).** Rebase after #1557, #1526 and #1516. + Clear CodeQL alert 283 (`rust/cleartext-logging`, + `test_support/scripted_client.rs:557`). Keep EdgeZero pinned to + IABTechLab/edgezero#275's head: an RC-tag pin pushed to this branch on + 2026-11-12 was reverted. Repin after the fold-in, or under wave 1's fallback; drop the `origin_readthrough_enabled` rejection (`config.rs:317`) once cache tags return. The repin brings IABTechLab/edgezero `b97efec1`, which stops Spin @@ -365,33 +383,60 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. - **#1526 at `e529e5576` (ID-2, SH-3).** Retarget to main. Address its round-1 review, including the finalize-under-`block_on` test gap. - **#1525 at `cd8977cad`.** Address its round-1 review. -- **#1487 at `4e7a6e02b` (ID-6, ID-8, SH-2, SH-3, SID-4).** Rebase onto #1526 - and #1506 (13 and 28 shared files). Await lookup collection in the async - finalize: `main.rs:699` calls `block_on` inside a function #1526 wraps in - `block_on` (`main.rs:550`). Move per-request lookups out of the - `thread_local`. Keep saving `/auction` body EIDs; a test at - `finalize.rs:1434` asserts they are dropped. Send lookups through EdgeZero's - `HttpClient`. Key the limit per DEC-15. -- **#1489 at `29de3d446` (WB-4).** Cap every mirrored ID's expiry at 30 days. - `coordinator/validate.rs:132-135` turns an unspecified expiry into - `Expiry::Never`, and - `crates/trusted-server-identity-providers/src/pull_sync.rs:128` writes such - IDs. -- **#1488 at `25f35720b` (SID-1, SID-2, SID-3, SID-5, SID-6).** Read the cookie name - from `managed_user_ids[].storage.name` and default the source to - `pubcid.org`, atype 1. Gate reads, storage and sending on EID permission - (SID-6). Keep main's retention (SID-5, DEC-7). Refuse an enabled provider - kind that is not registered. Settle the LiveRamp Custom ID (DEC-9) and - response shape. Address its round-1 review. -- **#1528 at `ea779fb47` (AX-7, SH-2).** Port its two commits onto #1506's - outbound API: a trial merge conflicts in 9 files, two of them modify/delete - on `platform/outbound.rs` and `platform/outbound_native_tests.rs`, which only - #1527 has. Repin off `456313675`. Fix the CLI test (`AxumDevServerConfig` - lacks `ingress`), the type error and the two new high CodeQL alerts. Remove - the per-request `AtomicBool` from the Fastly `AppState`. Feeds A1. +- **#1487 at `1600781eb` (ID-6, ID-8, SH-2, SH-3, SID-4).** Every item from + `4e7a6e02b` is still open. Rebase onto #1526 and #1506 (8 and 17 files + conflict). Await lookup collection in the async finalize: `main.rs:699` + calls `block_on` inside a function #1526 wraps in `block_on` + (`main.rs:550`, `:577`), and `run_after_response_lookups` does the same at + `:793` and `:811`. Move per-request lookups out of the `thread_local` + (`app.rs:414-424`). Keep saving `/auction` body EIDs (ID-8): the test at + `finalize.rs:1365` asserts they are dropped and the PR's own spec now calls + that intended, so the comment argues against that spec line. Send lookups + through EdgeZero's `HttpClient`: #1506 deletes `platform/http.rs`, and its + `outbound_client().start_batch_until` has the same start-then-gather shape. + Key the limit per DEC-15. +- **#1489 at `d3780ba13` (WB-4).** Cap every mirrored ID's expiry at 30 days + where the mirror is built (`mirror.rs:236-247`), not in validation, because + SID-5 keeps SharedID `Never` in the record. An unstated expiry still becomes + `Expiry::Never` (`coordinator/validate.rs:135-138`), pull sync is its only + production source (`pull_sync.rs:145`), and such IDs reach the cookie with no + `exp` under a one-year `Max-Age`. The 30-day clamp on stated expiries comes + from #1488. Answer the open thread on `any_due` skipping cookie-read + opt-outs. +- **#1566 at `117a58e0e` (SID-1, SID-3, SID-5, ID-2, SH-5).** Move + `0a2a00aaf` (SharedID name, source and retention) and `0c13d4272` (drops the + blanket provider rejection) into #1488. Rebuild the Axum part on T21's shared + app and EdgeZero capabilities: no Axum-only `EcKvStore` (`ec_kv.rs:87`; + ID-2, DEC-3), no Trusted Server rate limiter (EZI-5), no User-Agent-only + gate (EZI-3, ID-7), and no detached `block_on` task (EZI-1). Local + development uses EdgeZero's local store (EZI-2a). It then serves A2 and A10. +- **#1488 at `7f55776f3` (SID-1, SID-3, SID-5, SID-6).** Take #1566's + `0a2a00aaf`, which reads the SharedID name from `managed_user_ids`, defaults + the source to `pubcid.org` and keeps the stored value (still open here: + `sharedid.rs:46-59`, `common.rs:94,110,129-136`). Take `0c13d4272`: the + blanket `reject_providers_without_a_kind` (`config.rs:348`) makes + `ts config validate`, `push` and `diff` reject every enabled provider. SID-6 + holds for the provider path. LiveRamp's Custom ID waits on DEC-9, which gates + enabling LiveRamp, not this merge. Answer the open round-1 thread and the + four newer ones. +- **#1531 at `88a6ba596` (AX-1, AX-2, AX-7), which carries A1 in place of + #1528.** It now targets #1506's branch but forks from main with its own + cutover rebuild (`82d7e3be8`) and the RC tag pin, so 13 files conflict. + Rebase onto #1506's head, drop that rebuild and the pin, and repin to R1 + (DEC-5). Startup, stores and the client IP come from EdgeZero + (IABTechLab/edgezero#406, IABTechLab/edgezero#414, + IABTechLab/edgezero#409). Carry #1528's open items where they apply: the CLI + test (`AxumDevServerConfig` lacks `ingress`), the type error, the CodeQL + alerts and the per-request `AtomicBool` in the Fastly `AppState`. - **#1543 at `dc1a063d8` (AX-5).** It publishes the dev-server image (`STOPSIGNAL SIGINT`), so it ships with A6's production image contract. -- **RC units, fixed in their forward-ports.** These were checked at the RC tag +- **RC units, restacked as the forward-ports.** #1531 goes on #1506, #1532 on + #1531, #1536 and #1537 on #1532 for A2, and #1539, #1538 and #1540 directly + on #1532 for A6 and A7; #1533 and #1534 close. #1535 (Sourcepoint GPP capture + and server consent reconciliation) has no ticket until the MVP's need for it + beyond #1557 is decided. #1532 takes its geo provider from EdgeZero's manifest + once IABTechLab/edgezero#439 binds it, dropping `TS_NATIVE_GEO_PROFILE_FILE`. + The notes below were checked at the RC tag `v0.1.1-rc-axum.202611.2` (`7c6e68d94`), which contains every unit. They are forward-port notes: each applies to the PR that ports its unit to main, and Task 6 posts it there once that PR exists, checked at its head. On the RC unit @@ -402,13 +447,15 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. maps a US request with no region to `Unknown`, which fails every US Cloudflare request closed (AX-3), and show that the same trusted country and region reach EC creation and auction consent on Axum. #1536: do not port the adapter-local - DynamoDB store (ID-2, AX-4). #1537: its example config breaks startup on - Axum, Cloudflare and Spin. #1539: fix the failing Axum test. #1540: trust + DynamoDB store (ID-2, AX-4). #1537: do not port its Axum celld store or + storage selection, which EdgeZero's providers and logical stores replace + (EZI-2b, DEC-16); its example config also breaks startup on Axum, Cloudflare + and Spin. #1539: fix the failing Axum test. #1540: trust IPv6 CloudFront ranges, turn geo on, wire DynamoDB (AX-3, AX-6). All units: replace `TS_*` names (AX-2). #1541: drop the commits of closed #1545. -- **Proposed closures.** #1527 once its tests move (DEC-1); #1310, which - #1488's ID5 provider supersedes; #1424, #1499, #1541 and the RC units at - parity. +- **Proposed closures.** #1527 once its tests move (DEC-1); #1528 once #1531 + carries A1; #1533 and #1534, the RC copies of #1525 and #1526; #1310, which + #1488's ID5 provider supersedes; #1424, #1499 and #1541 at parity. ### EdgeZero, requests to the PR authors @@ -431,6 +478,9 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. `9a985c99`. - **IABTechLab/edgezero#414 at `3733482f`.** Drop those two commits once they move down. +- **IABTechLab/edgezero#439 at `3a0e5eb4`.** Bind the geo provider from the + manifest, so an application reads `ctx.geo()` without its own boot profile + (EZI-4, DEC-16); #1532 composes it from a Trusted Server profile today. - **IABTechLab/edgezero#463 at `6f9fa8bc`, IABTechLab/edgezero#464 at `6fe32531`, IABTechLab/edgezero#453 at `633f0552`.** Rebase as Part A's table says. @@ -537,13 +587,14 @@ issue, and one on IABTechLab/edgezero#391 linking R1. ### Task 3: File the Axum tickets under #1558 Same steps as Task 2, against IABTechLab/trusted-server and #1558, for the -ten new rows in table order. Each body has Summary (Ticket), Requirements +nine new rows in table order. Each body has Summary (Ticket), Requirements (Reqs, linked to the spec section), Acceptance (each requirement's Accept line) and Blocked by. Record the map in a `CREATOR:` comment on #1558. ### Task 4: File the Trusted Server tickets under #1558 -Same steps as Task 3, for T1 to T20 in table order, under #1558. Record the +Same steps as Task 3, for the T rows in table order (T1 to T21; T7 and T14 +are folded into A2 and A10), under #1558. Record the map in a `CREATOR:` comment on #1558. ### Task 5: Link the dependencies @@ -582,8 +633,8 @@ new head before posting. Post one `CREATOR:` comment per PR with its Part C item and the ticket it serves (`Part of #`): #1532 to #1529, #1526 to #1522, #1503 to #1432, -#1506 to #1507, #1528 to A1, #1543 to A6, #1487 to T11, #1488 to T13 and -#1489 to T14 and T15. For proposed closures, ask the author; do not close +#1506 to #1507, #1531 to A1, #1536, #1537 and #1566 to A2, #1539, #1538 and +#1543 to A6, #1540 to A7, #1487 to T11, #1488 to T13 and #1489 to T15. For proposed closures, ask the author; do not close another author's PR. ### Task 7: Tie out the EdgeZero PRs @@ -597,7 +648,7 @@ issue from Task 2. Run `gh api graphql -f query='{repository(owner:"IABTechLab",name:""){issue(number:){subIssues(first:100){totalCount}}}}' --jq '.data.repository.issue.subIssues.totalCount'` for #1558 and IABTechLab/edgezero#468. -Expected: 46 and 29. +Expected: 44 and 29. - [ ] **Step 2: Check that Fastly and Axum stand alone** diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index aa358f1d5..c7323bf09 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -299,6 +299,17 @@ marks the record contract and geo as supported. - **SH-4 No first-request capture.** Cloudflare does not keep the first request's `Env` in a set-once `thread_local`. Accept: two requests with different `Env` values each see their own. +- **SH-5 One app for every adapter.** The identity routes (identify, batch + sync, pull sync, admin lookup and the EC route), the EC lifecycle, provider + lookups and cookie write-back run from one router built in core, which every + adapter mounts. An adapter crate supplies only platform services, and a + capability an adapter lacks is an EdgeZero ask, never code in that adapter + (DEC-16). Accept: no adapter crate's non-test code calls EC generation, + finalization or an identity handler directly, and the same conformance + suite (T5) runs on each supported adapter. State: at #1506's head only the + Fastly adapter wires identity (EC generation, finalization, identify and + batch sync are called from its `app.rs` and `main.rs`); Axum, Cloudflare and + Spin each build their own router without it. ### SID: SharedID handled the way Prebid defines it @@ -397,11 +408,13 @@ part. task runs after delivery, and on Axum SIGTERM waits for registered tasks up to the grace; later, on Cloudflare the task runs after delivery, and Spin returns a capability error. -- **EZI-2a Record contract and the Fastly provider.** `KvStore` gains a read +- **EZI-2a Record contract, the Fastly provider and the local store.** `KvStore` gains a read that returns a generation marker, writes in `Add`, `Overwrite` and `IfGenerationMatch` modes with a precondition-failed outcome, and strongly consistent existence and prefix listing, or `KvError::Unsupported`. Accept: - an EdgeZero contract suite passes against the Fastly provider under Viceroy. + an EdgeZero contract suite passes against the Fastly provider under Viceroy + and against EdgeZero's local in-memory store, which the Axum development + server uses. - **EZI-2b DynamoDB and celld providers.** Both meet the EZI-2a contract. DynamoDB writes are spread across partition keys with no item every insert updates. Accept: the contract suite passes against both. State: IABTechLab/edgezero#463's @@ -420,8 +433,10 @@ part. reads as absent. - **EZI-4 Geo with region on Cloudflare and Spin.** `ctx.geo()` gives country and region there, or a capability error. Fastly keeps its existing geo and - Axum uses CloudFront geo from IABTechLab/edgezero#439. Accept: a US request - on Cloudflare reports country and region. + Axum uses CloudFront geo from IABTechLab/edgezero#439, bound from EdgeZero's + manifest so an application reads `ctx.geo()` without its own boot profile. + Accept: a US request on Cloudflare reports country and region, and Axum's + geo loads with no Trusted Server geo setting. - **EZI-5 Rate limiter.** A limiter with Fastly Edge Rate Limiting and an in-memory backend for Axum, then Cloudflare and Spin backends per DEC-15. Accept: a contract test passes per backend. @@ -514,6 +529,10 @@ Decided: keeps its own in-memory counter and enforces each limit divided by the configured task count, so the total across tasks stays within the limit. The MVP has no shared counter. +- **DEC-16 One implementation for every adapter** (user, 2026-11-12). Every + feature is built once and works on every adapter, delivered in DEC-4's + order. Shared logic lives in Trusted Server core; a platform capability an + adapter lacks is an EdgeZero ask, never code in one adapter crate (SH-5). Proposed and assumed by the plan until confirmed: @@ -536,7 +555,8 @@ Open: - **DEC-9 LiveRamp Custom ID.** The current value is the IP-derived EC prefix, shared by everyone behind one IP. Blocks enabling LiveRamp in #1488, and #1245. -- **DEC-10 Mirror partners.** Which partners get `mirror_enabled`. Blocks T14. +- **DEC-10 Mirror partners.** Which partners get `mirror_enabled`. It gates + turning the mirror on for a partner in a deployment, not a ticket. - **DEC-12 EC EID source.** Which source name the EC uses, and whether it keeps going out as `user.id`. Blocks #1549; no other ticket until decided. - **DEC-13 `ts-ec` refresh** for returning readers. Blocks T16. @@ -602,3 +622,11 @@ Server injects into proxied pages calls a same-origin route that mints the EC and writes the cookies on its own response, and the route answers both Prebid's `pixelUrl` and the script's JSON call. ID-1 and WB-1 now include that route, and the Axum mirror cookie (T14) is P1. + +The user then required every feature to be built once and work on every +adapter, with platform gaps sent to EdgeZero (DEC-16). SH-5 adds one identity +app in core that every adapter mounts (T21). The per-adapter identity tickets +shrink to turning that app on with each adapter's EdgeZero capabilities and +passing the conformance suite, and T7, T14 and A11 fold into A2 and A10. +EZI-2a adds EdgeZero's local store for Axum development, and EZI-4 binds +Axum's geo provider from EdgeZero's manifest. From 7c8aff024822d73b48980cca9b8a5560e83ba3ae Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 10:52:13 -0800 Subject: [PATCH 13/31] Turn Axum rate limiting off by default Per the user, every Axum limit, inbound and outbound, is off by default because deployers rate-limit at their own load balancer (DEC-11). A limit turned on in configuration still runs per task, divided by the configured task count. The reference stack (A7, AX-6) adds an example rate-based rule at CloudFront or the load balancer, and A7 no longer waits on DEC-15. --- ...11-11-server-side-identity-all-adapters.md | 33 +++++------ ...erver-side-identity-all-adapters-design.md | 57 +++++++++++-------- 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 6ec152af4..886e82f8d 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -165,14 +165,14 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 5 | EZ R1b | P1 | Release | EZI-2a and EZI-2b | EZI-2a, EZI-2b | | 5 | TS #1529 (A4), A6 with #1543, A3 | P1 | Move, New | CloudFront IP and geo via #1532; production image via #1539 and #1538; SIGTERM drain | A1 (A3 also R2; A6 also PR #1449) | | 5 | TS T2 | P1 | New | EIDs in `user.eids`; after the provider stack, so open PRs need no test churn | #1489 merged | -| 5 | TS T10, T12, T13 | P1 | New | Rate limits on Fastly and Axum; abuse protection and SharedID write-back in the shared app | R2, T21; DEC-15 (T10); T3, PR #1488, DEC-7 (T13) | +| 5 | TS T10, T12, T13 | P1 | New | Rate limits on Fastly, off by default on Axum; abuse protection and SharedID write-back in the shared app | R2, T21; DEC-15 (T10); T3, PR #1488, DEC-7 (T13) | | 5 | TS T15 | P1 | New | Identity-only tsjs | PR #1489 | | 5 | TS A9, #1141 | P2 | New, Move | `TRUSTED_SERVER__` guard; local config handoff | A1 | | 5 | TS #1551 | P2 | Move | Pull sync on a full row | PR #1489 | | 5 | TS #1527, #1528, #1523, #1533, #1534 | P2 | Close | Tests moved to #1506; A1 on #1531; RC copies of #1525 and #1526 | #1506, A1 | | 6 | TS T6 | P1 | New | Shared record backend, Fastly first | R1b, #1522 | | 6 | TS T11 | P1 | New | Providers awaited before finalization in the shared app | T10, T21, R2, PR #1487 | -| 6 | TS A7 | P1 | New | Reference infrastructure via #1540 | A1, A6, #1529; DEC-6, DEC-15 | +| 6 | TS A7 | P1 | New | Reference infrastructure via #1540, with an example rate rule | A1, A6, #1529; DEC-6 | | 7 | TS A2 | P1 | New | Identity on Axum through the shared app, from #1536 and #1537 | T5, T6, T21, A1, #1529; DEC-6 | | 7 | TS T19 | P1 | New | EC route in the shared app: mint and write back on a same-origin call; pixel and JSON responses | T10, T11, T12, T13, T21 | | 8 | TS A10, A8 | P1 | New | Identity features pass on Axum; SIGTERM test in CI | A2 plus their Part B blockers | @@ -271,17 +271,17 @@ IABTechLab/edgezero#391, which stays an epic in place; R1 is linked from it. New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------- | ---- | --- | ------------------------------ | -------------------------------- | -| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507; DEC-5, DEC-6 | A2, A3, A6, A7, A9, #1529, #1141 | -| A2 | Identity on Axum through T21's shared app, from #1536 and #1537 without their Axum-only stores; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529; DEC-6 | A5, A8, A10 | -| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | -| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | -| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; PR #1449; DEC-6 | A7 | -| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded | AX-3, AX-6, ID-7 | Feature | L | P1 | A1, A6, #1529; DEC-6, DEC-15 | A5, A10 | -| A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P1 | A2 | none | -| A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | -| A10 | Identity features pass on Axum from the shared app: providers, abuse protection, SharedID write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P1 | A2, A7, R2, T11, T12, T13, T19 | A5 | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------- | ---- | --- | ------------------------------ | -------------------------------- | +| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507; DEC-5, DEC-6 | A2, A3, A6, A7, A9, #1529, #1141 | +| A2 | Identity on Axum through T21's shared app, from #1536 and #1537 without their Axum-only stores; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529; DEC-6 | A5, A8, A10 | +| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | +| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | +| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; PR #1449; DEC-6 | A7 | +| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded, and an example rate-based rule at CloudFront or the load balancer | AX-3, AX-6, ID-6, ID-7 | Feature | L | P1 | A1, A6, #1529; DEC-6 | A5, A10 | +| A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P1 | A2 | none | +| A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | +| A10 | Identity features pass on Axum from the shared app: providers, abuse protection, SharedID write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P1 | A2, A7, R2, T11, T12, T13, T19 | A5 | Existing, moved under #1558: @@ -315,7 +315,7 @@ New: | T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | | T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | | T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | -| T10 | Rate limits on Fastly and Axum: batch sync per partner, pull sync, provider lookups, record creation; on Axum each limit divided by the configured task count | ID-6 | Feature | M | P1 | R2, T21; DEC-15 | T11, T17, T19 | +| T10 | Rate limits: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum every limit off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P1 | R2, T21; DEC-15 | T11, T17, T19 | | T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly | ID-4, ID-5, WB-1 | Feature | M | P1 | T10, T21, R2; PR #1487 | A10, T17, T19 | | T12 | Refuse record creation from non-browser clients in the shared app, through EdgeZero's JA4 accessor; verified on Fastly | ID-7 | Feature | M | P1 | R2, T21 | A10, T17, T19 | | T13 | SharedID cookie write-back in the shared app, with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-6 | Feature | M | P1 | T3, T21; PR #1488; DEC-7 | A10, T17, T19 | @@ -407,7 +407,7 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. `0a2a00aaf` (SharedID name, source and retention) and `0c13d4272` (drops the blanket provider rejection) into #1488. Rebuild the Axum part on T21's shared app and EdgeZero capabilities: no Axum-only `EcKvStore` (`ec_kv.rs:87`; - ID-2, DEC-3), no Trusted Server rate limiter (EZI-5), no User-Agent-only + ID-2, DEC-3), no Trusted Server rate limiter (EZI-5's, off by default on Axum, DEC-11), no User-Agent-only gate (EZI-3, ID-7), and no detached `block_on` task (EZI-1). Local development uses EdgeZero's local store (EZI-2a). It then serves A2 and A10. - **#1488 at `7f55776f3` (SID-1, SID-3, SID-5, SID-6).** Take #1566's @@ -451,7 +451,8 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. storage selection, which EdgeZero's providers and logical stores replace (EZI-2b, DEC-16); its example config also breaks startup on Axum, Cloudflare and Spin. #1539: fix the failing Axum test. #1540: trust - IPv6 CloudFront ranges, turn geo on, wire DynamoDB (AX-3, AX-6). All units: + IPv6 CloudFront ranges, turn geo on, wire DynamoDB and add an example + rate-based rule (AX-3, AX-6, DEC-11). All units: replace `TS_*` names (AX-2). #1541: drop the commits of closed #1545. - **Proposed closures.** #1527 once its tests move (DEC-1); #1528 once #1531 carries A1; #1533 and #1534, the RC copies of #1525 and #1526; #1310, which diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index c7323bf09..7e4d5384f 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -118,14 +118,14 @@ Identity runs on an adapter only where the record contract and geo are supported. Elsewhere, startup rejects identity configuration (ID-2). Delivery follows DEC-4: Fastly, Axum, Cloudflare, Spin. -| Capability | Fastly | Axum | Cloudflare | Spin | -| ----------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations, exposed through EdgeZero by EZI-2a | Supported. DynamoDB or celld through EZI-2b | Unsupported today. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | Unsupported today. `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing. Its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | -| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | -| Rate limiter | Supported (Edge Rate Limiting) | EZI-5, in-memory per task, each limit divided by the task count (DEC-11) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | -| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | -| TLS fingerprint (JA4) | Supported (Fastly SDK); EZI-3 accessor | EZI-3 from a trusted CloudFront hop | EZI-3 accessor, or absent | EZI-3 accessor, or absent | -| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | +| Capability | Fastly | Axum | Cloudflare | Spin | +| ----------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations, exposed through EdgeZero by EZI-2a | Supported. DynamoDB or celld through EZI-2b | Unsupported today. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | Unsupported today. `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing. Its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | +| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | +| Rate limiter | Supported (Edge Rate Limiting) | Off by default, the load balancer limits; when turned on, EZI-5 in memory per task (DEC-11) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | +| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | +| TLS fingerprint (JA4) | Supported (Fastly SDK); EZI-3 accessor | EZI-3 from a trusted CloudFront hop | EZI-3 accessor, or absent | EZI-3 accessor, or absent | +| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | Cloudflare and Spin move to Supported only when EZI-2 and EZI-4 deliver for them (DEC-8). Until then their startup rejects identity configuration, and @@ -180,10 +180,12 @@ marks the record contract and geo as supported. production image without publishing it. - **AX-6 Reference infrastructure.** The reference stack wires the DynamoDB table, port 443 egress for vendor APIs, a readiness check that fails when a - dependency is down, JSON logs and two or more tasks. IABTechLab/edgezero#275 serves + dependency is down, JSON logs, two or more tasks and an example rate-based + rule at CloudFront or the load balancer, since Axum's own limits are off by + default (DEC-11). IABTechLab/edgezero#275 serves Axum on a current-thread runtime, so capacity comes from task count. Accept: - the stack applies with those properties, and readiness fails with DynamoDB - unreachable. State: #1540 has the 443 egress (IPv4 only), a static `/health`, + the stack applies with those properties, the rate rule refuses traffic over + its threshold, and readiness fails with DynamoDB unreachable. State: #1540 has the 443 egress (IPv4 only), a static `/health`, plain logs, one task and no DynamoDB wiring. - **AX-7 Ships from main.** Main never pins an EdgeZero RC tag (IABTechLab/edgezero#431, IABTechLab/edgezero#462) or a revision that exists only on an RC @@ -229,11 +231,13 @@ marks the record contract and geo as supported. the DEC-14 choice holds: either the follow-up write lands before the response, or the work is skipped and the response is unchanged. - **ID-6 Rate limiting.** Provider lookups, batch sync, pull sync and record - creation are rate limited on every supported adapter. Batch sync is keyed - per partner; the other keys follow DEC-15. On Axum each task keeps its own - counter and enforces each limit divided by the configured task count - (DEC-11). Accept: per-adapter tests hit each limit and see the limited - request refused or skipped, and an Axum task configured for two tasks + creation are rate limited on every supported adapter except Axum, where + every limit is off by default because the deployer's load balancer limits + traffic (DEC-11). Batch sync is keyed per partner; the other keys follow + DEC-15. An Axum limit turned on in configuration runs per task and enforces + the limit divided by the configured task count. Accept: per-adapter tests hit + each limit and see the limited request refused or skipped; Axum's default + configuration applies no limit; and an Axum limit turned on for two tasks refuses at half the limit. State: Fastly Edge Rate Limiting is the only limiter; record creation is limited nowhere; #1487 keys lookups on the IP-derived EC prefix, so everyone @@ -455,8 +459,8 @@ part. - The existing EC settings enable identity; startup rejects them where identity is unsupported (ID-2). - Provider `rate_limit_per_hour` and `mirror_enabled` from #1487 and #1489 - follow DEC-15 and DEC-10. On Axum every limit is divided by the configured - task count (DEC-11). + follow DEC-15 and DEC-10. On Axum every limit is off by default and, when + turned on, divided by the configured task count (DEC-11). - The reference deployment turns geo on (AX-3). - Environment overrides use the `TRUSTED_SERVER__` prefix only (AX-2). - Record stores are EdgeZero logical stores in `edgezero.toml`, with no @@ -525,10 +529,12 @@ Decided: without any Cloudflare or Spin work, so every ticket and release is split by platform and no Fastly or Axum item is blocked by a Cloudflare or Spin item. Cloudflare and Spin follow the MVP. -- **DEC-11 Axum rate limiter backend** (user, 2026-11-12). Each Axum task - keeps its own in-memory counter and enforces each limit divided by the - configured task count, so the total across tasks stays within the limit. - The MVP has no shared counter. +- **DEC-11 Axum rate limiting** (user, 2026-11-12). Every limit is off on + Axum by default, inbound and outbound, because deployers rate-limit at their + own load balancer; the reference stack shows one rule (AX-6). A limit a + deployer turns on keeps its own in-memory counter in each task and enforces + the limit divided by the configured task count. The MVP has no shared + counter. - **DEC-16 One implementation for every adapter** (user, 2026-11-12). Every feature is built once and works on every adapter, delivered in DEC-4's order. Shared logic lives in Trusted Server core; a platform capability an @@ -564,7 +570,7 @@ Open: Blocks T9. - **DEC-15 Rate limiter keys, the Axum task count, and the Cloudflare and Spin limiter backends.** Batch sync stays keyed per partner. Blocks EZI-5c, - EZI-5s, T10, A7 and #1487's key item. + EZI-5s, T10 and #1487's key item. ## How the claims were checked @@ -630,3 +636,8 @@ shrink to turning that app on with each adapter's EdgeZero capabilities and passing the conformance suite, and T7, T14 and A11 fold into A2 and A10. EZI-2a adds EdgeZero's local store for Axum development, and EZI-4 binds Axum's geo provider from EdgeZero's manifest. + +The user then turned every Axum limit off by default, inbound and outbound, +because deployers rate-limit at their own load balancer (DEC-11), and asked +for an example rate rule in the reference stack (AX-6). A7 no longer waits on +DEC-15. From fce5239aa39acaf3e91c7d115739c6123a38020b Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:01:09 -0800 Subject: [PATCH 14/31] Punt rate limiting past the MVP and create SharedID on the server Per the user, rate limiting waits until after the MVP (DEC-15): EZI-5, its Cloudflare and Spin parts and T10 move to an unscheduled row at P3, R2 to R4 and T11, T17 and T19 no longer wait on them, and Fastly keeps main's limits. The EC route relies on the same-site and browser checks until then. The user also decided DEC-7: when neither the record nor the request has a SharedID, the server creates one in Prebid's format, stores it and sets it, because a browser may block Prebid.js from loading at all. WB-3 and SID-5 gain that behavior and T13 delivers it. --- ...11-11-server-side-identity-all-adapters.md | 188 +++++++++--------- ...erver-side-identity-all-adapters-design.md | 100 ++++++---- 2 files changed, 152 insertions(+), 136 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 886e82f8d..04b3fd9d7 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -126,71 +126,72 @@ run in priority order, then DEC-4's platform order. TS is Trusted Server and EZ is EdgeZero (`IABTechLab/edgezero#N`). Kinds: New (ticket), Move (existing issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. -| Wave | Item | Pri | Kind | Action | Waits for | -| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | -| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | -| 0 | TS #1526, #1522 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | -| 0 | TS #1516 | P1 | Merge | Already approved | none | -| 0 | TS #1449 | P1 | PR fix, Merge | Fix its round-4 review; A6 publishes through it | none | -| 0 | TS #1189 | P1 | Close | Fixed on main by `56a19229f` (#1157), which deleted the ungated `ingest_*` functions; ask its author to close it | none | -| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | -| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | -| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464, IABTechLab/edgezero#453 | P1 | PR fix | Rebase off the commits they carry | none | -| 0 | TS #1503, #1432 | P2 | Merge | `ts ec` commands; delivers #1432 | none | -| 0 | TS #1233 | P2 | Move | `ts-ec` to creative assets | none | -| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | -| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | -| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | -| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439; IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | -| 2 | EZ EZI-2a, EZI-3, EZI-5 | P1 | New | Record contract with Fastly; ClientInfo contract; rate limiter for Fastly and Axum | IABTechLab/edgezero#275 | -| 2 | TS T5 | P1 | New | Conformance suite for Fastly and Axum | #1507 | -| 2 | TS T21 | P1 | New | One identity app in core for every adapter | T1, #1507 | -| 2 | TS T4, #1192 | P2 | New, Move | Shared state once per process: Axum (T4) and Fastly (#1192) | #1507 | -| 2 | TS #1556 | P2 | Move | Regression test that identity backends verify certificates, then close | #1507 | -| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | -| 3 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1487 | -| 3 | EZ R1 | P1 | Release | IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | wave 2's EdgeZero chain | -| 3 | EZ EZI-1 | P1 | New | After-response work for Fastly and Axum | IABTechLab/edgezero#275, IABTechLab/edgezero#406 | -| 3 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | -| 3 | IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | P1 | Move | Fastly and Axum ClientInfo; JA4 | EZI-3; IABTechLab/edgezero#397 | -| 3 | TS #1553, #1246 | P2 | Move | After-response CAS retry; Lockr IDs to KV | PR #1487 | -| 4 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1488 | -| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | -| 4 | EZ R2 | P1 | Release | Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-1, EZI-3, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | -| 4 | TS A1 | P1 | New, PR fix | Rebase #1531 onto #1506, repin to R1; #1528 closes | R1, #1507; DEC-5, DEC-6 | -| 4 | TS T3 | P1 | New | SharedID cleanup | PR #1488 | -| 4 | TS #1555 | P2 | Move | Opt-out EIDs | PR #1488 | -| 4 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | -| 5 | EZ R1b | P1 | Release | EZI-2a and EZI-2b | EZI-2a, EZI-2b | -| 5 | TS #1529 (A4), A6 with #1543, A3 | P1 | Move, New | CloudFront IP and geo via #1532; production image via #1539 and #1538; SIGTERM drain | A1 (A3 also R2; A6 also PR #1449) | -| 5 | TS T2 | P1 | New | EIDs in `user.eids`; after the provider stack, so open PRs need no test churn | #1489 merged | -| 5 | TS T10, T12, T13 | P1 | New | Rate limits on Fastly, off by default on Axum; abuse protection and SharedID write-back in the shared app | R2, T21; DEC-15 (T10); T3, PR #1488, DEC-7 (T13) | -| 5 | TS T15 | P1 | New | Identity-only tsjs | PR #1489 | -| 5 | TS A9, #1141 | P2 | New, Move | `TRUSTED_SERVER__` guard; local config handoff | A1 | -| 5 | TS #1551 | P2 | Move | Pull sync on a full row | PR #1489 | -| 5 | TS #1527, #1528, #1523, #1533, #1534 | P2 | Close | Tests moved to #1506; A1 on #1531; RC copies of #1525 and #1526 | #1506, A1 | -| 6 | TS T6 | P1 | New | Shared record backend, Fastly first | R1b, #1522 | -| 6 | TS T11 | P1 | New | Providers awaited before finalization in the shared app | T10, T21, R2, PR #1487 | -| 6 | TS A7 | P1 | New | Reference infrastructure via #1540, with an example rate rule | A1, A6, #1529; DEC-6 | -| 7 | TS A2 | P1 | New | Identity on Axum through the shared app, from #1536 and #1537 | T5, T6, T21, A1, #1529; DEC-6 | -| 7 | TS T19 | P1 | New | EC route in the shared app: mint and write back on a same-origin call; pixel and JSON responses | T10, T11, T12, T13, T21 | -| 8 | TS A10, A8 | P1 | New | Identity features pass on Axum; SIGTERM test in CI | A2 plus their Part B blockers | -| 8 | TS T20 | P1 | New | Injected script calls the EC route on consent and merges the IDs into Prebid | T15, T19 | -| 8 | TS #1541, #1424, #1499 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | -| 9 | TS A5 | P1 | New | Load test | A2, A7, A10 | -| 10 | EZ EZI-1c, EZI-2c, EZI-4c, EZI-5c; IABTechLab/edgezero#91 | P2 | New, Move | Cloudflare parts in EdgeZero | Part B blockers; DEC-8, DEC-15 | -| 11 | EZ R3 | P2 | Release | Cloudflare release | wave 10 | -| 12 | TS #1518, T8 | P2 | Move, New | Geo source check; identity on Cloudflare through the shared app | R3 (T8 also T6, T1, T5, T21; DEC-8) | -| 13 | TS T17 | P2 | New | Identity features pass on Cloudflare | T8, R3, T10 to T13, T19 | -| 14 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s | P3 | New | Spin parts in EdgeZero | Part B blockers; DEC-8, DEC-15 | -| 15 | EZ R4; IABTechLab/edgezero#89 | P3 | Release, Move | Spin release; ClientInfo on every adapter | wave 14, R3 | -| 16 | TS T9 | P3 | New | Identity on Spin through the shared app | T8, R4; DEC-8, DEC-14 | -| 17 | TS T18 | P3 | New | Identity features pass on Spin | T9, R4, T17 | -| any | TS #1142 | P2 | Move | Health and startup contract on all four adapters; A7 carries Axum readiness | none | -| any | TS #1549 | P2 | Move | No `user.id` for a row-less EC | DEC-12; PR #1487 | -| any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP with PR IABTechLab/edgezero#453; IABTechLab/edgezero#287 last | DEC-13; DEC-9; PR IABTechLab/edgezero#453; IABTechLab/edgezero#275 | -| any | TS #1139, #1525 | P3 | Move, PR fix | Store inventory on all four adapters; HTTP auction endpoints, which #1526 does not need | none | +| Wave | Item | Pri | Kind | Action | Waits for | +| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | +| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | +| 0 | TS #1526, #1522 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | +| 0 | TS #1516 | P1 | Merge | Already approved | none | +| 0 | TS #1449 | P1 | PR fix, Merge | Fix its round-4 review; A6 publishes through it | none | +| 0 | TS #1189 | P1 | Close | Fixed on main by `56a19229f` (#1157), which deleted the ungated `ingest_*` functions; ask its author to close it | none | +| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | +| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | +| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464, IABTechLab/edgezero#453 | P1 | PR fix | Rebase off the commits they carry | none | +| 0 | TS #1503, #1432 | P2 | Merge | `ts ec` commands; delivers #1432 | none | +| 0 | TS #1233 | P2 | Move | `ts-ec` to creative assets | none | +| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | +| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | +| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | +| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439; IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | +| 2 | EZ EZI-2a, EZI-3 | P1 | New | Record contract with Fastly; ClientInfo contract | IABTechLab/edgezero#275 | +| 2 | TS T5 | P1 | New | Conformance suite for Fastly and Axum | #1507 | +| 2 | TS T21 | P1 | New | One identity app in core for every adapter | T1, #1507 | +| 2 | TS T4, #1192 | P2 | New, Move | Shared state once per process: Axum (T4) and Fastly (#1192) | #1507 | +| 2 | TS #1556 | P2 | Move | Regression test that identity backends verify certificates, then close | #1507 | +| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | +| 3 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1487 | +| 3 | EZ R1 | P1 | Release | IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | wave 2's EdgeZero chain | +| 3 | EZ EZI-1 | P1 | New | After-response work for Fastly and Axum | IABTechLab/edgezero#275, IABTechLab/edgezero#406 | +| 3 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | +| 3 | IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | P1 | Move | Fastly and Axum ClientInfo; JA4 | EZI-3; IABTechLab/edgezero#397 | +| 3 | TS #1553, #1246 | P2 | Move | After-response CAS retry; Lockr IDs to KV | PR #1487 | +| 4 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1488 | +| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | +| 4 | EZ R2 | P1 | Release | Fastly and Axum parts of EZI-1 and EZI-3 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | +| 4 | TS A1 | P1 | New, PR fix | Rebase #1531 onto #1506, repin to R1; #1528 closes | R1, #1507; DEC-5, DEC-6 | +| 4 | TS T3 | P1 | New | SharedID cleanup | PR #1488 | +| 4 | TS #1555 | P2 | Move | Opt-out EIDs | PR #1488 | +| 4 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | +| 5 | EZ R1b | P1 | Release | EZI-2a and EZI-2b | EZI-2a, EZI-2b | +| 5 | TS #1529 (A4), A6 with #1543, A3 | P1 | Move, New | CloudFront IP and geo via #1532; production image via #1539 and #1538; SIGTERM drain | A1 (A3 also R2; A6 also PR #1449) | +| 5 | TS T2 | P1 | New | EIDs in `user.eids`; after the provider stack, so open PRs need no test churn | #1489 merged | +| 5 | TS T12, T13 | P1 | New | Abuse protection; SharedID creation and write-back in the shared app | R2, T21 (T12); T3, T21, PR #1488 (T13) | +| 5 | TS T15 | P1 | New | Identity-only tsjs | PR #1489 | +| 5 | TS A9, #1141 | P2 | New, Move | `TRUSTED_SERVER__` guard; local config handoff | A1 | +| 5 | TS #1551 | P2 | Move | Pull sync on a full row | PR #1489 | +| 5 | TS #1527, #1528, #1523, #1533, #1534 | P2 | Close | Tests moved to #1506; A1 on #1531; RC copies of #1525 and #1526 | #1506, A1 | +| 6 | TS T6 | P1 | New | Shared record backend, Fastly first | R1b, #1522 | +| 6 | TS T11 | P1 | New | Providers awaited before finalization in the shared app | T21, R2, PR #1487 | +| 6 | TS A7 | P1 | New | Reference infrastructure via #1540, with an example rate rule | A1, A6, #1529; DEC-6 | +| 7 | TS A2 | P1 | New | Identity on Axum through the shared app, from #1536 and #1537 | T5, T6, T21, A1, #1529; DEC-6 | +| 7 | TS T19 | P1 | New | EC route in the shared app: mint and write back on a same-origin call; pixel and JSON responses | T11, T12, T13, T21 | +| 8 | TS A10, A8 | P1 | New | Identity features pass on Axum; SIGTERM test in CI | A2 plus their Part B blockers | +| 8 | TS T20 | P1 | New | Injected script calls the EC route on consent and merges the IDs into Prebid | T15, T19 | +| 8 | TS #1541, #1424, #1499 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | +| 9 | TS A5 | P1 | New | Load test | A2, A7, A10 | +| 10 | EZ EZI-1c, EZI-2c, EZI-4c; IABTechLab/edgezero#91 | P2 | New, Move | Cloudflare parts in EdgeZero | Part B blockers; DEC-8 | +| 11 | EZ R3 | P2 | Release | Cloudflare release | wave 10 | +| 12 | TS #1518, T8 | P2 | Move, New | Geo source check; identity on Cloudflare through the shared app | R3 (T8 also T6, T1, T5, T21; DEC-8) | +| 13 | TS T17 | P2 | New | Identity features pass on Cloudflare | T8, R3, T11 to T13, T19 | +| 14 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s | P3 | New | Spin parts in EdgeZero | Part B blockers; DEC-8 | +| 15 | EZ R4; IABTechLab/edgezero#89 | P3 | Release, Move | Spin release; ClientInfo on every adapter | wave 14, R3 | +| 16 | TS T9 | P3 | New | Identity on Spin through the shared app | T8, R4; DEC-8, DEC-14 | +| 17 | TS T18 | P3 | New | Identity features pass on Spin | T9, R4, T17 | +| any | TS #1142 | P2 | Move | Health and startup contract on all four adapters; A7 carries Axum readiness | none | +| any | TS #1549 | P2 | Move | No `user.id` for a row-less EC | DEC-12; PR #1487 | +| any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP with PR IABTechLab/edgezero#453; IABTechLab/edgezero#287 last | DEC-13; DEC-9; PR IABTechLab/edgezero#453; IABTechLab/edgezero#275 | +| any | TS #1139, #1525 | P3 | Move, PR fix | Store inventory on all four adapters; HTTP auction endpoints, which #1526 does not need | none | +| any | EZ EZI-5, EZI-5c, EZI-5s; TS T10 | P3 | New | Rate limiting, punted past the MVP | DEC-15 | Waves 0 to 9 are the MVP (DEC-2): they complete Fastly and Axum. None of them waits on a Cloudflare or Spin item, so the MVP ships without waves 10 to 17 @@ -230,21 +231,21 @@ New: | EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | R1b | | EZI-3 | ClientInfo contract: portable client IP, geo and TLS metadata with explicit absence, the contract part of IABTechLab/edgezero#89 | EZI-3 | Feature | M | P1 | PR IABTechLab/edgezero#275 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, IABTechLab/edgezero#283, EZI-3s, IABTechLab/edgezero#89, R2 | | EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PRs IABTechLab/edgezero#275, IABTechLab/edgezero#406 | EZI-1c, EZI-1s, R2 | -| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and an in-memory backend for Axum | EZI-5 | Feature | S | P1 | PR IABTechLab/edgezero#275 | EZI-5c, EZI-5s, R2 | +| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and an in-memory backend for Axum (after the MVP) | EZI-5 | Feature | S | P3 | PR IABTechLab/edgezero#275; DEC-15 | EZI-5c, EZI-5s, T10 | | EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | | EZI-2c | Cloudflare store meets the record contract, or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a; DEC-8 | R3 | | EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | IABTechLab/edgezero#432 | R3 | -| EZI-5c | Rate limiter backend for Cloudflare | EZI-5 | Feature | S | P2 | EZI-5; DEC-15 | R3 | +| EZI-5c | Rate limiter backend for Cloudflare (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | | EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | | EZI-2s | Spin store meets the record contract, or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a; DEC-8 | R4 | | EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | EZI-3 | R4, IABTechLab/edgezero#89 | | EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | -| EZI-5s | Rate limiter backend for Spin | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | R4 | +| EZI-5s | Rate limiter backend for Spin (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | | R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | | R1b | Release the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | -| R2 | Release the Fastly and Axum parts of EZI-1, EZI-3 and EZI-5 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, EZI-5, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T10, T11, T12 | -| R3 | Release the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, EZI-5c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | -| R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, EZI-5s, R3 | T9, T18 | +| R2 | Release the Fastly and Axum parts of EZI-1 and EZI-3 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T11, T12 | +| R3 | Release the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | +| R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, R3 | T9, T18 | Existing, moved under IABTechLab/edgezero#468. IABTechLab/edgezero#89 to IABTechLab/edgezero#92 leave IABTechLab/edgezero#88 and the last six leave IABTechLab/edgezero#391 or have @@ -305,27 +306,27 @@ brings partner APIs, the mirror cookie and the EC route to every adapter. New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------- | ---- | --- | ------------------------------- | ------------------------------- | -| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | -| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | -| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | -| T4 | Build shared state once per process on Axum: settings, partner registry (built per request today), store clients; keep the per-request HTTP client unless measured. #1192 is the Fastly half | SH-1, SH-3 | Task | M | P2 | #1507 | none | -| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | -| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | -| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | -| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | -| T10 | Rate limits: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum every limit off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P1 | R2, T21; DEC-15 | T11, T17, T19 | -| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly | ID-4, ID-5, WB-1 | Feature | M | P1 | T10, T21, R2; PR #1487 | A10, T17, T19 | -| T12 | Refuse record creation from non-browser clients in the shared app, through EdgeZero's JA4 accessor; verified on Fastly | ID-7 | Feature | M | P1 | R2, T21 | A10, T17, T19 | -| T13 | SharedID cookie write-back in the shared app, with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-6 | Feature | M | P1 | T3, T21; PR #1488; DEC-7 | A10, T17, T19 | -| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | -| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | -| T17 | Identity features pass on Cloudflare from the shared app: providers, rate limits, abuse protection, SharedID write-back, the mirror cookie and the EC route | ID-4, ID-6, ID-7, ID-9, WB-3, WB-4 | Feature | M | P2 | T8, R3, T10, T11, T12, T13, T19 | T18 | -| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-6, ID-7, ID-9, WB-3, WB-4 | Feature | M | P3 | T9, R4, T17 | none | -| T19 | EC route in the shared app, verified on Fastly: `/_ts/api/v1/ec` mints, records, runs the providers and sets the identity cookies on a consented same-origin call, with a pixel response for Prebid's `pixelUrl` and a JSON response with the IDs | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T10, T11, T12, T13, T21 | A10, T17, T20 | -| T20 | The injected script calls the EC route once the CMP reports consent and merges the returned IDs into Prebid | ID-9, WB-6 | Feature | M | P1 | T15, T19 | none | -| T21 | One identity app in core for every adapter: identify, batch sync, pull sync, admin lookup, the EC route, the EC lifecycle, providers and write-back in one router each adapter mounts; adapter crates supply only platform services through EdgeZero | SH-5, ID-1, ID-3 | Feature | L | P1 | T1, #1507 | A2, T8, T10, T11, T12, T13, T19 | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------- | ---- | --- | -------------------------- | ------------------------------- | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | +| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | +| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | +| T4 | Build shared state once per process on Axum: settings, partner registry (built per request today), store clients; keep the per-request HTTP client unless measured. #1192 is the Fastly half | SH-1, SH-3 | Task | M | P2 | #1507 | none | +| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | +| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | +| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | +| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | +| T10 | Rate limits after the MVP: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P3 | EZI-5, T21; DEC-15 | none | +| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly | ID-4, ID-5, WB-1 | Feature | M | P1 | T21, R2; PR #1487 | A10, T17, T19 | +| T12 | Refuse record creation from non-browser clients in the shared app, through EdgeZero's JA4 accessor; verified on Fastly | ID-7 | Feature | M | P1 | R2, T21 | A10, T17, T19 | +| T13 | SharedID in the shared app: create one when the reader has none, and write it back with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-5, SID-6 | Feature | M | P1 | T3, T21; PR #1488 | A10, T17, T19 | +| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | +| T17 | Identity features pass on Cloudflare from the shared app: providers, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P2 | T8, R3, T11, T12, T13, T19 | T18 | +| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P3 | T9, R4, T17 | none | +| T19 | EC route in the shared app, verified on Fastly: `/_ts/api/v1/ec` mints, records, runs the providers and sets the identity cookies on a consented same-origin call, with a pixel response for Prebid's `pixelUrl` and a JSON response with the IDs | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T11, T12, T13, T21 | A10, T17, T20 | +| T20 | The injected script calls the EC route once the CMP reports consent and merges the returned IDs into Prebid | ID-9, WB-6 | Feature | M | P1 | T15, T19 | none | +| T21 | One identity app in core for every adapter: identify, batch sync, pull sync, admin lookup, the EC route, the EC lifecycle, providers and write-back in one router each adapter mounts; adapter crates supply only platform services through EdgeZero | SH-5, ID-1, ID-3 | Feature | L | P1 | T1, #1507 | A2, T8, T10, T11, T12, T13, T19 | Existing, moved under #1558: @@ -394,7 +395,6 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. that intended, so the comment argues against that spec line. Send lookups through EdgeZero's `HttpClient`: #1506 deletes `platform/http.rs`, and its `outbound_client().start_batch_until` has the same start-then-gather shape. - Key the limit per DEC-15. - **#1489 at `d3780ba13` (WB-4).** Cap every mirrored ID's expiry at 30 days where the mirror is built (`mirror.rs:236-247`), not in validation, because SID-5 keeps SharedID `Never` in the record. An unstated expiry still becomes @@ -407,7 +407,7 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. `0a2a00aaf` (SharedID name, source and retention) and `0c13d4272` (drops the blanket provider rejection) into #1488. Rebuild the Axum part on T21's shared app and EdgeZero capabilities: no Axum-only `EcKvStore` (`ec_kv.rs:87`; - ID-2, DEC-3), no Trusted Server rate limiter (EZI-5's, off by default on Axum, DEC-11), no User-Agent-only + ID-2, DEC-3), no Trusted Server rate limiter (none on Axum, DEC-11), no User-Agent-only gate (EZI-3, ID-7), and no detached `block_on` task (EZI-1). Local development uses EdgeZero's local store (EZI-2a). It then serves A2 and A10. - **#1488 at `7f55776f3` (SID-1, SID-3, SID-5, SID-6).** Take #1566's diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 7e4d5384f..00bbf70b0 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -118,14 +118,14 @@ Identity runs on an adapter only where the record contract and geo are supported. Elsewhere, startup rejects identity configuration (ID-2). Delivery follows DEC-4: Fastly, Axum, Cloudflare, Spin. -| Capability | Fastly | Axum | Cloudflare | Spin | -| ----------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations, exposed through EdgeZero by EZI-2a | Supported. DynamoDB or celld through EZI-2b | Unsupported today. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | Unsupported today. `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing. Its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | -| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | -| Rate limiter | Supported (Edge Rate Limiting) | Off by default, the load balancer limits; when turned on, EZI-5 in memory per task (DEC-11) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | -| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | -| TLS fingerprint (JA4) | Supported (Fastly SDK); EZI-3 accessor | EZI-3 from a trusted CloudFront hop | EZI-3 accessor, or absent | EZI-3 accessor, or absent | -| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | +| Capability | Fastly | Axum | Cloudflare | Spin | +| ----------------------------------------------------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations, exposed through EdgeZero by EZI-2a | Supported. DynamoDB or celld through EZI-2b | Unsupported today. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | Unsupported today. `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing. Its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | +| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | +| Rate limiter | Supported (Edge Rate Limiting) | None in the MVP; the load balancer limits (DEC-11, DEC-15) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | +| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | +| TLS fingerprint (JA4) | Supported (Fastly SDK); EZI-3 accessor | EZI-3 from a trusted CloudFront hop | EZI-3 accessor, or absent | EZI-3 accessor, or absent | +| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | Cloudflare and Spin move to Supported only when EZI-2 and EZI-4 deliver for them (DEC-8). Until then their startup rejects identity configuration, and @@ -230,18 +230,20 @@ marks the record contract and geo as supported. before the next navigation, and on Axum SIGTERM waits for it (A3). On Spin the DEC-14 choice holds: either the follow-up write lands before the response, or the work is skipped and the response is unchanged. -- **ID-6 Rate limiting.** Provider lookups, batch sync, pull sync and record - creation are rate limited on every supported adapter except Axum, where - every limit is off by default because the deployer's load balancer limits - traffic (DEC-11). Batch sync is keyed per partner; the other keys follow - DEC-15. An Axum limit turned on in configuration runs per task and enforces - the limit divided by the configured task count. Accept: per-adapter tests hit - each limit and see the limited request refused or skipped; Axum's default - configuration applies no limit; and an Axum limit turned on for two tasks - refuses at half the limit. - State: Fastly Edge Rate Limiting is the only limiter; record creation is - limited nowhere; #1487 keys lookups on the IP-derived EC prefix, so everyone - behind one IPv4 address or IPv6 /64 shares a budget. +- **ID-6 Rate limiting (after the MVP, DEC-15).** In the MVP, Fastly keeps + main's limits on batch sync and pull sync and #1487's per-provider lookup + limit, and Axum has none because the deployer's load balancer limits + traffic (DEC-11). After the MVP, provider lookups, batch sync, pull sync and + record creation are rate limited on every supported adapter except Axum, + where every limit stays off by default; batch sync is keyed per partner and + the other keys follow DEC-15, and an Axum limit turned on in configuration + runs per task, divided by the configured task count. Accept: in the MVP, + Fastly's existing limits still refuse or skip over-limit requests and Axum's + default configuration applies no limit; after the MVP, per-adapter tests hit + each limit, and an Axum limit turned on for two tasks refuses at half the + limit. State: Fastly Edge Rate Limiting is the only limiter; record creation + is limited nowhere; #1487 keys lookups on the IP-derived EC prefix, so + everyone behind one IPv4 address or IPv6 /64 shares a budget. - **ID-7 Abuse resistance.** Record creation is refused for clients that are not browsers, on every supported adapter. Accept: a scripted client with a browser User-Agent and no browser TLS fingerprint creates no record. State: @@ -263,15 +265,15 @@ marks the record contract and geo as supported. next navigation. A plain `GET` answers Prebid's SharedID `pixelUrl` with an empty response; the script's call gets JSON with the SharedID and the EIDs the record holds, which the script merges into Prebid. The route mints - nothing for a cross-site caller, a client ID-7 refuses or a call past the - record-creation limit (ID-6), and every response carries - `Cache-Control: no-store`. It is served from the page's own host, which + nothing for a cross-site caller or a client ID-7 refuses, and every response + carries `Cache-Control: no-store`; a rate limit on it waits with ID-6 + (DEC-15). It is served from the page's own host, which WebKit's cap on cookies from non-document responses exempts when the IP matches the page's (read from WebKit's source, not yet tested in Safari). Accept: on Fastly and Axum, a consented same-site call with no `ts-ec` creates the record and sets `ts-ec` and the SharedID cookie in that - response; the JSON call returns the record's IDs; a cross-site `Origin`, a - scripted client and a call past the limit mint nothing; an unchanged + response; the JSON call returns the record's IDs; a cross-site `Origin` and a + scripted client mint nothing; an unchanged returning reader gets no `Set-Cookie`. State: on main only a real-browser navigation through the publisher proxy mints an EC, and no such route exists. @@ -343,7 +345,8 @@ marks the record contract and geo as supported. only `user.ext.eids` (`auction/openrtb.rs:372`, `integrations/prebid.rs:2882`, `integrations/aps.rs:1131`). - **SID-5 Retention.** The record keeps a SharedID for as long as the record - lives, and the stored value wins over a different browser value (DEC-7). + lives, and the stored value wins over a different browser value. When neither + has one, the server creates it (DEC-7, WB-3). Accept: a stored SharedID survives a request carrying a different cookie value. State: main does this on Fastly (`ec/kv.rs:134-174`); #1488 keeps a cookie-read SharedID for 24 hours and lets the cookie replace it. @@ -370,12 +373,17 @@ marks the record contract and geo as supported. gets no `Set-Cookie`. State: set only for a new or rotated EC. - **WB-3 SharedID cookie.** When SID-2 and SID-6 allow it, the server sets the SharedID cookie from the record if the request has none or a different - value. It uses Prebid's storage name and the Domain Prebid writes with, is + value. When neither the record nor the request has one, the server creates + a SharedID in the format Prebid writes (a UUID v4), stores it in the record + and sets it the same way, because a browser may block Prebid.js from loading + at all (DEC-7). It uses Prebid's storage name and the Domain Prebid writes with, is not HttpOnly, and is `Secure; SameSite=Lax` for one year. It keeps Prebid's `_cst` companion consistent, since a missing or different one makes Prebid rewrite the cookie from script, which Safari caps. Prebid's `extend` with a `pixelUrl` keeps working alongside it. Accept: record present and - cookie absent gives both cookies with those attributes; an equal cookie + cookie absent gives both cookies with those attributes; a consented first + visit with no SharedID anywhere gets one created, stored and set, and a + reader with `_pubcid_optout` gets none; an equal cookie with a consistent `_cst` gives no `Set-Cookie`; an equal cookie with a missing or different `_cst` re-sets only the companion; Purpose 1 denied or `_pubcid_optout` expires both; Purpose 4 denied with Purpose 1 granted gives no `Set-Cookie` and leaves the @@ -441,15 +449,16 @@ part. manifest so an application reads `ctx.geo()` without its own boot profile. Accept: a US request on Cloudflare reports country and region, and Axum's geo loads with no Trusted Server geo setting. -- **EZI-5 Rate limiter.** A limiter with Fastly Edge Rate Limiting and +- **EZI-5 Rate limiter (after the MVP, DEC-15).** A limiter with Fastly Edge + Rate Limiting and an in-memory backend for Axum, then Cloudflare and Spin backends per DEC-15. Accept: a contract test passes per backend. - **EZI-6 Releases.** R1 contains IABTechLab/edgezero#275 through IABTechLab/edgezero#439, so Axum production serving does not wait for the record contract. R1b contains EZI-2a and EZI-2b. R2 contains the Fastly and - Axum parts of EZI-1, EZI-3 and EZI-5. Together R1, R1b and R2 cover Fastly - and Axum. R3 contains the Cloudflare parts of EZI-1 to EZI-5, and R4 the Spin - parts. Accept: each tag exists and Trusted Server main pins it; R1, R1b and + Axum parts of EZI-1 and EZI-3. Together R1, R1b and R2 cover Fastly and Axum. + R3 contains the Cloudflare parts of EZI-1 to EZI-4, and R4 the Spin parts. + EZI-5 and its parts ship in a release after the MVP (DEC-15). Accept: each tag exists and Trusted Server main pins it; R1, R1b and R2 contain no Cloudflare or Spin part. ## Configuration @@ -459,8 +468,7 @@ part. - The existing EC settings enable identity; startup rejects them where identity is unsupported (ID-2). - Provider `rate_limit_per_hour` and `mirror_enabled` from #1487 and #1489 - follow DEC-15 and DEC-10. On Axum every limit is off by default and, when - turned on, divided by the configured task count (DEC-11). + follow DEC-15 and DEC-10; on Axum no limit applies by default (DEC-11). - The reference deployment turns geo on (AX-3). - Environment overrides use the `TRUSTED_SERVER__` prefix only (AX-2). - Record stores are EdgeZero logical stores in `edgezero.toml`, with no @@ -531,10 +539,14 @@ Decided: Cloudflare and Spin follow the MVP. - **DEC-11 Axum rate limiting** (user, 2026-11-12). Every limit is off on Axum by default, inbound and outbound, because deployers rate-limit at their - own load balancer; the reference stack shows one rule (AX-6). A limit a - deployer turns on keeps its own in-memory counter in each task and enforces + own load balancer; the reference stack shows one rule (AX-6). Once rate + limiting returns (DEC-15), a limit a deployer turns on keeps its own in-memory counter in each task and enforces the limit divided by the configured task count. The MVP has no shared counter. +- **DEC-7 SharedID retention and creation** (user, 2026-11-12). The stored + value wins and lives as long as the record (SID-5). When neither the record + nor the request has a SharedID, the server creates one, because a browser may + block Prebid.js from loading at all (WB-3). - **DEC-16 One implementation for every adapter** (user, 2026-11-12). Every feature is built once and works on every adapter, delivered in DEC-4's order. Shared logic lives in Trusted Server core; a platform capability an @@ -549,9 +561,6 @@ Proposed and assumed by the plan until confirmed: RC line or port #1536's adapter-local store first. Applying each provider stack to the RC tag from its main fork point conflicts in 13 to 23 files. Blocks A1, A2, A4, A6, A7. -- **DEC-7 SharedID retention.** The stored value wins and lives as long as the - record (SID-5, proposed). Open: whether the server generates a SharedID when - none exists. Blocks T13 and the #1488 retention item. Open: @@ -568,9 +577,10 @@ Open: - **DEC-13 `ts-ec` refresh** for returning readers. Blocks T16. - **DEC-14 Spin after-response work.** Run it before the response, or skip it. Blocks T9. -- **DEC-15 Rate limiter keys, the Axum task count, and the Cloudflare and Spin - limiter backends.** Batch sync stays keyed per partner. Blocks EZI-5c, - EZI-5s, T10 and #1487's key item. +- **DEC-15 Rate limiting after the MVP** (user, 2026-11-12, deferred). Rate + limiting is punted past the MVP: the keys (batch sync stays per partner), + EdgeZero's limiter with its Cloudflare and Spin backends, and new limits on + record creation and on Axum. Blocks EZI-5, EZI-5c, EZI-5s and T10. ## How the claims were checked @@ -641,3 +651,9 @@ The user then turned every Axum limit off by default, inbound and outbound, because deployers rate-limit at their own load balancer (DEC-11), and asked for an example rate rule in the reference stack (AX-6). A7 no longer waits on DEC-15. + +The user then punted rate limiting past the MVP (DEC-15): EZI-5 and its +Cloudflare and Spin parts and T10 move to a later row, and Fastly keeps main's +limits. The user also decided DEC-7: the server creates a SharedID when the +reader has none, because a browser may block Prebid.js from loading (WB-3, +T13). From ea09206279677c1d646b034cde2f8af6a0bc66f5 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:08:58 -0800 Subject: [PATCH 15/31] Pin Trusted Server to an EdgeZero integration branch Per the user, EdgeZero keeps an integration branch for the unreleased work headed for its main (#275, the #406 to #439 chain, the EZI tickets), and Trusted Server main pins its revisions instead of waiting for a release, repinning once EdgeZero publishes one (DEC-5). AX-7 allows those revisions, EZI-6 and R1 to R4 become integration points on that branch, and A1 no longer waits on a decision for its pin. --- ...11-11-server-side-identity-all-adapters.md | 26 ++++++++++------- ...erver-side-identity-all-adapters-design.md | 29 ++++++++++++++----- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 04b3fd9d7..65c6b993a 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -124,7 +124,9 @@ EdgeZero: A wave starts when everything it waits for has landed. Within a wave, rows run in priority order, then DEC-4's platform order. TS is Trusted Server and EZ is EdgeZero (`IABTechLab/edgezero#N`). Kinds: New (ticket), Move (existing -issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. +issue under its epic), PR fix (changes the PR needs), Merge, Release (a +revision on EdgeZero's integration branch that Trusted Server pins, DEC-5), +Close. | Wave | Item | Pri | Kind | Action | Waits for | | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | @@ -158,7 +160,7 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 4 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1488 | | 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | | 4 | EZ R2 | P1 | Release | Fastly and Axum parts of EZI-1 and EZI-3 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | -| 4 | TS A1 | P1 | New, PR fix | Rebase #1531 onto #1506, repin to R1; #1528 closes | R1, #1507; DEC-5, DEC-6 | +| 4 | TS A1 | P1 | New, PR fix | Rebase #1531 onto #1506, repin to R1; #1528 closes | R1, #1507; DEC-6 | | 4 | TS T3 | P1 | New | SharedID cleanup | PR #1488 | | 4 | TS #1555 | P2 | Move | Opt-out EIDs | PR #1488 | | 4 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | @@ -180,11 +182,11 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release, Close. | 8 | TS #1541, #1424, #1499 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | | 9 | TS A5 | P1 | New | Load test | A2, A7, A10 | | 10 | EZ EZI-1c, EZI-2c, EZI-4c; IABTechLab/edgezero#91 | P2 | New, Move | Cloudflare parts in EdgeZero | Part B blockers; DEC-8 | -| 11 | EZ R3 | P2 | Release | Cloudflare release | wave 10 | +| 11 | EZ R3 | P2 | Release | Cloudflare integration point | wave 10 | | 12 | TS #1518, T8 | P2 | Move, New | Geo source check; identity on Cloudflare through the shared app | R3 (T8 also T6, T1, T5, T21; DEC-8) | | 13 | TS T17 | P2 | New | Identity features pass on Cloudflare | T8, R3, T11 to T13, T19 | | 14 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s | P3 | New | Spin parts in EdgeZero | Part B blockers; DEC-8 | -| 15 | EZ R4; IABTechLab/edgezero#89 | P3 | Release, Move | Spin release; ClientInfo on every adapter | wave 14, R3 | +| 15 | EZ R4; IABTechLab/edgezero#89 | P3 | Release, Move | Spin integration point; ClientInfo on every adapter | wave 14, R3 | | 16 | TS T9 | P3 | New | Identity on Spin through the shared app | T8, R4; DEC-8, DEC-14 | | 17 | TS T18 | P3 | New | Identity features pass on Spin | T9, R4, T17 | | any | TS #1142 | P2 | Move | Health and startup contract on all four adapters; A7 carries Axum readiness | none | @@ -223,6 +225,10 @@ Constraints. ### EdgeZero, under IABTechLab/edgezero#468 +The integration branch is itself an EdgeZero ask (DEC-5): it carries only +work headed for EdgeZero main, merged in Part A's order, and R1 to R4 are +revisions on it that Trusted Server pins. + New: | ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | @@ -241,11 +247,11 @@ New: | EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | EZI-3 | R4, IABTechLab/edgezero#89 | | EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | | EZI-5s | Rate limiter backend for Spin (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | -| R1 | Release IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | -| R1b | Release the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | -| R2 | Release the Fastly and Axum parts of EZI-1 and EZI-3 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T11, T12 | -| R3 | Release the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | -| R4 | Release the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, R3 | T9, T18 | +| R1 | Integration-branch revision with IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | +| R1b | Integration-branch revision with the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | +| R2 | Integration-branch revision with the Fastly and Axum parts of EZI-1 and EZI-3 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T11, T12 | +| R3 | Integration-branch revision with the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | +| R4 | Integration-branch revision with the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, R3 | T9, T18 | Existing, moved under IABTechLab/edgezero#468. IABTechLab/edgezero#89 to IABTechLab/edgezero#92 leave IABTechLab/edgezero#88 and the last six leave IABTechLab/edgezero#391 or have @@ -274,7 +280,7 @@ New: | ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------- | ---- | --- | ------------------------------ | -------------------------------- | -| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507; DEC-5, DEC-6 | A2, A3, A6, A7, A9, #1529, #1141 | +| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507; DEC-6 | A2, A3, A6, A7, A9, #1529, #1141 | | A2 | Identity on Axum through T21's shared app, from #1536 and #1537 without their Axum-only stores; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529; DEC-6 | A5, A8, A10 | | A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | | A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 00bbf70b0..9c6e205f8 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -190,9 +190,11 @@ marks the record contract and geo as supported. - **AX-7 Ships from main.** Main never pins an EdgeZero RC tag (IABTechLab/edgezero#431, IABTechLab/edgezero#462) or a revision that exists only on an RC branch, such as `456313675`, which #1528 pins today. Main may pin - IABTechLab/edgezero#275's head under DEC-1; other unmerged revisions need a - recorded decision (DEC-5). Accept: `Cargo.lock` on main resolves every - EdgeZero crate to a released tag or to the revision DEC-1 or DEC-5 allows. + IABTechLab/edgezero#275's head under DEC-1, or a revision of EdgeZero's + integration branch under DEC-5, which carries only work headed for EdgeZero + main. Accept: `Cargo.lock` on main resolves every EdgeZero crate to a + released tag, IABTechLab/edgezero#275's head or an integration-branch + revision. ### ID: Server-side identity on every adapter @@ -453,13 +455,15 @@ part. Rate Limiting and an in-memory backend for Axum, then Cloudflare and Spin backends per DEC-15. Accept: a contract test passes per backend. -- **EZI-6 Releases.** R1 contains IABTechLab/edgezero#275 through +- **EZI-6 Integration points.** EdgeZero keeps an integration branch for this + work (DEC-5), and R1 to R4 are revisions on it that Trusted Server main pins + until EdgeZero releases them. R1 contains IABTechLab/edgezero#275 through IABTechLab/edgezero#439, so Axum production serving does not wait for the record contract. R1b contains EZI-2a and EZI-2b. R2 contains the Fastly and Axum parts of EZI-1 and EZI-3. Together R1, R1b and R2 cover Fastly and Axum. R3 contains the Cloudflare parts of EZI-1 to EZI-4, and R4 the Spin parts. - EZI-5 and its parts ship in a release after the MVP (DEC-15). Accept: each tag exists and Trusted Server main pins it; R1, R1b and - R2 contain no Cloudflare or Spin part. + EZI-5 and its parts ship in a release after the MVP (DEC-15). Accept: each revision exists on the integration branch and Trusted Server + main pins it; R1, R1b and R2 contain no Cloudflare or Spin part. ## Configuration @@ -547,6 +551,12 @@ Decided: value wins and lives as long as the record (SID-5). When neither the record nor the request has a SharedID, the server creates one, because a browser may block Prebid.js from loading at all (WB-3). +- **DEC-5 EdgeZero integration branch** (user, 2026-11-12). EdgeZero keeps an + integration branch that carries the unreleased work headed for EdgeZero + main: IABTechLab/edgezero#275, the IABTechLab/edgezero#406 to + IABTechLab/edgezero#439 chain and the EZI tickets. Trusted Server main pins + its revisions (R1, R1b, R2) instead of waiting for an EdgeZero release, and + repins to a released tag once EdgeZero publishes one. - **DEC-16 One implementation for every adapter** (user, 2026-11-12). Every feature is built once and works on every adapter, delivered in DEC-4's order. Shared logic lives in Trusted Server core; a platform capability an @@ -554,8 +564,6 @@ Decided: Proposed and assumed by the plan until confirmed: -- **DEC-5 EdgeZero pin for the Axum production stack.** Wait for release R1 - (proposed), or follow the stack's head under AX-7. Blocks A1. - **DEC-6 Base and store path for Axum.** Forward-port the RC line to main and wait for EZI-2b (proposed), rather than back-port the provider stack to the RC line or port #1536's adapter-local store first. Applying each provider @@ -657,3 +665,8 @@ Cloudflare and Spin parts and T10 move to a later row, and Fastly keeps main's limits. The user also decided DEC-7: the server creates a SharedID when the reader has none, because a browser may block Prebid.js from loading (WB-3, T13). + +The user then decided DEC-5: EdgeZero keeps an integration branch for the +unreleased work, and Trusted Server main pins its revisions instead of waiting +for a release. AX-7 allows those revisions, EZI-6 calls R1 to R4 integration +points, and A1 no longer waits on a decision for its pin. From 2f0cb7d7e63317bcd0e9ccd8ea7ead5876e2eddb Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:16:15 -0800 Subject: [PATCH 16/31] Keep celld, mirror ID5 and LiveRamp, and fold #1535 into the EC route Per the user, celld stays in the MVP (DEC-17), ID5 and LiveRamp are the mirrored identity providers the MVP's checks use (DEC-10), which makes the LiveRamp Custom ID (DEC-9) a launch need, and #1535's Sourcepoint consent sync is required. ID-9's EC route now also takes later consent changes, so a withdrawal clears the identity cookies and tombstones the record at once; #1535's separate per-adapter /_ts/consent route folds into T19 and its GPP capture into T20. --- ...11-11-server-side-identity-all-adapters.md | 53 ++++++++++--------- ...erver-side-identity-all-adapters-design.md | 29 +++++++--- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 65c6b993a..2ef7dca7c 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -100,7 +100,7 @@ Trusted Server: | #1531 | `88a6ba596` | #1506's branch | Retargeted without a rebase: it forks from main with its own cutover and the RC tag pin, and 13 files conflict with #1506. Rebase onto #1506; it then delivers A1 | | #1532 | `852b875fe` | #1531's branch | Delivers #1529 (A4); restack after #1531 | | #1533, #1534 | `82d9a0e66`, `646c39a34` | #1532's and #1533's branches | RC-baseline copies of #1525 and #1526: drop from the chain and close | -| #1536, #1535, #1537 | `296e05405`, `0666a9b4b`, `4a838272c` | #1534's, #1536's and #1535's branches | Restack on #1532. #1536 and #1537 feed A2 without their Axum-only stores; #1535 has no ticket | +| #1536, #1535, #1537 | `296e05405`, `0666a9b4b`, `4a838272c` | #1534's, #1536's and #1535's branches | Restack on #1532. #1536 and #1537 feed A2 without their Axum-only stores; #1535 feeds T19 and T20 | | #1539, #1538, #1540 | `8d41cb06c`, `3111c2456`, `eb6e19ef4` | #1537's, #1539's and #1538's branches | Restack on #1532: they share no files with #1533 to #1537, so A6 and A7 stop waiting on A2 | | #1541 | `7dd2e3056` | main | Close at parity | @@ -312,27 +312,27 @@ brings partner APIs, the mirror cookie and the EC route to every adapter. New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------- | ---- | --- | -------------------------- | ------------------------------- | -| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | -| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | -| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | -| T4 | Build shared state once per process on Axum: settings, partner registry (built per request today), store clients; keep the per-request HTTP client unless measured. #1192 is the Fastly half | SH-1, SH-3 | Task | M | P2 | #1507 | none | -| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | -| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | -| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | -| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | -| T10 | Rate limits after the MVP: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P3 | EZI-5, T21; DEC-15 | none | -| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly | ID-4, ID-5, WB-1 | Feature | M | P1 | T21, R2; PR #1487 | A10, T17, T19 | -| T12 | Refuse record creation from non-browser clients in the shared app, through EdgeZero's JA4 accessor; verified on Fastly | ID-7 | Feature | M | P1 | R2, T21 | A10, T17, T19 | -| T13 | SharedID in the shared app: create one when the reader has none, and write it back with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-5, SID-6 | Feature | M | P1 | T3, T21; PR #1488 | A10, T17, T19 | -| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | -| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | -| T17 | Identity features pass on Cloudflare from the shared app: providers, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P2 | T8, R3, T11, T12, T13, T19 | T18 | -| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P3 | T9, R4, T17 | none | -| T19 | EC route in the shared app, verified on Fastly: `/_ts/api/v1/ec` mints, records, runs the providers and sets the identity cookies on a consented same-origin call, with a pixel response for Prebid's `pixelUrl` and a JSON response with the IDs | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T11, T12, T13, T21 | A10, T17, T20 | -| T20 | The injected script calls the EC route once the CMP reports consent and merges the returned IDs into Prebid | ID-9, WB-6 | Feature | M | P1 | T15, T19 | none | -| T21 | One identity app in core for every adapter: identify, batch sync, pull sync, admin lookup, the EC route, the EC lifecycle, providers and write-back in one router each adapter mounts; adapter crates supply only platform services through EdgeZero | SH-5, ID-1, ID-3 | Feature | L | P1 | T1, #1507 | A2, T8, T10, T11, T12, T13, T19 | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------- | ---- | --- | -------------------------- | ------------------------------- | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | +| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | +| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | +| T4 | Build shared state once per process on Axum: settings, partner registry (built per request today), store clients; keep the per-request HTTP client unless measured. #1192 is the Fastly half | SH-1, SH-3 | Task | M | P2 | #1507 | none | +| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | +| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | +| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | +| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | +| T10 | Rate limits after the MVP: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P3 | EZI-5, T21; DEC-15 | none | +| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly | ID-4, ID-5, WB-1 | Feature | M | P1 | T21, R2; PR #1487 | A10, T17, T19 | +| T12 | Refuse record creation from non-browser clients in the shared app, through EdgeZero's JA4 accessor; verified on Fastly | ID-7 | Feature | M | P1 | R2, T21 | A10, T17, T19 | +| T13 | SharedID in the shared app: create one when the reader has none, and write it back with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-5, SID-6 | Feature | M | P1 | T3, T21; PR #1488 | A10, T17, T19 | +| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | +| T17 | Identity features pass on Cloudflare from the shared app: providers, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P2 | T8, R3, T11, T12, T13, T19 | T18 | +| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P3 | T9, R4, T17 | none | +| T19 | EC route in the shared app, verified on Fastly: `/_ts/api/v1/ec` mints, records, runs the providers and sets the identity cookies on a consented same-origin call, and on a withdrawal expires them and tombstones the record (reconciliation from #1535), with a pixel response for Prebid's `pixelUrl` and a JSON response with the IDs | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T11, T12, T13, T21 | A10, T17, T20 | +| T20 | The injected script calls the EC route when the CMP reports consent and on every later change, sending Sourcepoint's GPP string (capture from #1535), and merges the returned IDs into Prebid | ID-9, WB-6 | Feature | M | P1 | T15, T19 | none | +| T21 | One identity app in core for every adapter: identify, batch sync, pull sync, admin lookup, the EC route, the EC lifecycle, providers and write-back in one router each adapter mounts; adapter crates supply only platform services through EdgeZero | SH-5, ID-1, ID-3 | Feature | L | P1 | T1, #1507 | A2, T8, T10, T11, T12, T13, T19 | Existing, moved under #1558: @@ -438,9 +438,10 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. image contract. - **RC units, restacked as the forward-ports.** #1531 goes on #1506, #1532 on #1531, #1536 and #1537 on #1532 for A2, and #1539, #1538 and #1540 directly - on #1532 for A6 and A7; #1533 and #1534 close. #1535 (Sourcepoint GPP capture - and server consent reconciliation) has no ticket until the MVP's need for it - beyond #1557 is decided. #1532 takes its geo provider from EdgeZero's manifest + on #1532 for A6 and A7; #1533 and #1534 close. #1535 (Sourcepoint GPP capture and server consent reconciliation) + feeds T19 and T20: its reconciliation runs in the EC route and its GPP + capture in the injected script, in place of a separate `/_ts/consent` route + wired into each adapter (DEC-16). #1532 takes its geo provider from EdgeZero's manifest once IABTechLab/edgezero#439 binds it, dropping `TS_NATIVE_GEO_PROFILE_FILE`. The notes below were checked at the RC tag `v0.1.1-rc-axum.202611.2` (`7c6e68d94`), which contains every unit. They are @@ -640,7 +641,7 @@ new head before posting. Post one `CREATOR:` comment per PR with its Part C item and the ticket it serves (`Part of #`): #1532 to #1529, #1526 to #1522, #1503 to #1432, -#1506 to #1507, #1531 to A1, #1536, #1537 and #1566 to A2, #1539, #1538 and +#1506 to #1507, #1531 to A1, #1536, #1537 and #1566 to A2, #1535 to T19, #1539, #1538 and #1543 to A6, #1540 to A7, #1487 to T11, #1488 to T13 and #1489 to T15. For proposed closures, ask the author; do not close another author's PR. diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 9c6e205f8..8e42b7cda 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -266,7 +266,11 @@ marks the record contract and geo as supported. consents after the page loads gets an EC on that page view instead of the next navigation. A plain `GET` answers Prebid's SharedID `pixelUrl` with an empty response; the script's call gets JSON with the SharedID and the EIDs - the record holds, which the script merges into Prebid. The route mints + the record holds, which the script merges into Prebid. The script also calls + it on every later CMP consent change, sending Sourcepoint's GPP string where + the page uses Sourcepoint, so a withdrawal expires `ts-ec`, the SharedID + cookie and the mirror cookie and tombstones the record in that response + instead of on the next navigation. The route mints nothing for a cross-site caller or a client ID-7 refuses, and every response carries `Cache-Control: no-store`; a rate limit on it waits with ID-6 (DEC-15). It is served from the page's own host, which @@ -275,10 +279,12 @@ marks the record contract and geo as supported. Accept: on Fastly and Axum, a consented same-site call with no `ts-ec` creates the record and sets `ts-ec` and the SharedID cookie in that response; the JSON call returns the record's IDs; a cross-site `Origin` and a - scripted client mint nothing; an unchanged - returning reader gets no `Set-Cookie`. State: on main only a real-browser + scripted client mint nothing; a withdrawal sent to it expires the + identity cookies and tombstones the record; an unchanged returning reader gets + no `Set-Cookie`. State: on main only a real-browser navigation through the publisher proxy mints an EC, and no such route - exists. + exists. #1535 (RC line) posts Sourcepoint's GPP string to a separate + `/_ts/consent` route, wired into each adapter's router. ### SH: Shared state passed the right way @@ -557,6 +563,11 @@ Decided: IABTechLab/edgezero#439 chain and the EZI tickets. Trusted Server main pins its revisions (R1, R1b, R2) instead of waiting for an EdgeZero release, and repins to a released tag once EdgeZero publishes one. +- **DEC-10 Mirror partners** (user, 2026-11-12). ID5 and LiveRamp, the + important identity providers, get `mirror_enabled`, and the MVP's provider + and mirror checks (T11, A10) use them. +- **DEC-17 celld in the MVP** (user, 2026-11-12). EZI-2b delivers both the + DynamoDB and the celld provider before R1b. - **DEC-16 One implementation for every adapter** (user, 2026-11-12). Every feature is built once and works on every adapter, delivered in DEC-4's order. Shared logic lives in Trusted Server core; a platform capability an @@ -577,9 +588,7 @@ Open: EZI-2s, R3, T8 and T9. - **DEC-9 LiveRamp Custom ID.** The current value is the IP-derived EC prefix, shared by everyone behind one IP. Blocks enabling LiveRamp in #1488, and - #1245. -- **DEC-10 Mirror partners.** Which partners get `mirror_enabled`. It gates - turning the mirror on for a partner in a deployment, not a ticket. + #1245. LiveRamp is in the MVP (DEC-10), so this is needed before launch. - **DEC-12 EC EID source.** Which source name the EC uses, and whether it keeps going out as `user.id`. Blocks #1549; no other ticket until decided. - **DEC-13 `ts-ec` refresh** for returning readers. Blocks T16. @@ -670,3 +679,9 @@ The user then decided DEC-5: EdgeZero keeps an integration branch for the unreleased work, and Trusted Server main pins its revisions instead of waiting for a release. AX-7 allows those revisions, EZI-6 calls R1 to R4 integration points, and A1 no longer waits on a decision for its pin. + +The user then kept celld in the MVP (DEC-17), named ID5 and LiveRamp as the +mirrored providers (DEC-10), which makes DEC-9 a launch need, and required +#1535's Sourcepoint consent sync. ID-9's route now also takes later consent +changes, so #1535's separate per-adapter `/_ts/consent` route folds into it +(T19, T20). From 52fd60b2f77a46c1f4f2443a51f2933073d500ca Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:22:40 -0800 Subject: [PATCH 17/31] Reaffirm pluggable KvStore adapters for the Axum record Per the user, Axum's record uses EdgeZero's pluggable KvStore adapters (DynamoDB and celld through EZI-2b), never an Axum-only store (DEC-3). DEC-6 now covers only the base, restacking the RC units on #1506, and DEC-9 waits on a LiveRamp test setup the user is getting. --- ...erver-side-identity-all-adapters-design.md | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 8e42b7cda..15c3cea5b 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -541,7 +541,10 @@ Decided: - **DEC-2 MVP** (user, 2026-11-12). Fastly and Axum are the MVP: plan waves 0 to 9. Cloudflare, Spin and the decision-gated work follow it. - **DEC-3 Shared record store** (user, 2026-11-11). The EC record runs on one - `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. + `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. The + user reaffirmed it on 2026-11-12: Axum's record uses EdgeZero's pluggable + `KvStore` adapters (DynamoDB and celld through EZI-2b), never an Axum-only + store such as #1536's. - **DEC-4 Platform order** (user, 2026-11-12). Fastly, then Axum, then Cloudflare, then Spin. Fastly and Axum (the MVP, DEC-2) must ship complete without any Cloudflare or Spin work, so every ticket and release is split by @@ -575,10 +578,10 @@ Decided: Proposed and assumed by the plan until confirmed: -- **DEC-6 Base and store path for Axum.** Forward-port the RC line to main and - wait for EZI-2b (proposed), rather than back-port the provider stack to the - RC line or port #1536's adapter-local store first. Applying each provider - stack to the RC tag from its main fork point conflicts in 13 to 23 files. +- **DEC-6 Base for the Axum line.** Forward-port the RC line to main by + restacking its units on #1506 (proposed), rather than back-port the provider + stack to the RC line. Applying each provider stack to the RC tag from its + main fork point conflicts in 13 to 23 files. The store path follows DEC-3. Blocks A1, A2, A4, A6, A7. Open: @@ -588,7 +591,8 @@ Open: EZI-2s, R3, T8 and T9. - **DEC-9 LiveRamp Custom ID.** The current value is the IP-derived EC prefix, shared by everyone behind one IP. Blocks enabling LiveRamp in #1488, and - #1245. LiveRamp is in the MVP (DEC-10), so this is needed before launch. + #1245. LiveRamp is in the MVP (DEC-10), so this is needed before launch; + the user is getting a LiveRamp test setup to settle it. - **DEC-12 EC EID source.** Which source name the EC uses, and whether it keeps going out as `user.id`. Blocks #1549; no other ticket until decided. - **DEC-13 `ts-ec` refresh** for returning readers. Blocks T16. @@ -685,3 +689,7 @@ mirrored providers (DEC-10), which makes DEC-9 a launch need, and required #1535's Sourcepoint consent sync. ID-9's route now also takes later consent changes, so #1535's separate per-adapter `/_ts/consent` route folds into it (T19, T20). + +The user then reaffirmed DEC-3 for Axum: the record uses EdgeZero's pluggable +`KvStore` adapters. DEC-6 now covers only the base, restacking the RC units on +#1506, and DEC-9 waits on a LiveRamp test setup. From bf9ff9159baecfab93ca047fcc1eafded3325f41 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:24:54 -0800 Subject: [PATCH 18/31] Make the DynamoDB and celld KvStore adapters available on every adapter Per the user, the DynamoDB and celld adapters work on Fastly, Axum, Cloudflare and Spin, while the native stores (Fastly KV, Cloudflare KV, Spin KV) stay on their own platform (DEC-3). EZI-2b builds both over EdgeZero's outbound HTTP, because edgezero-store-aws and edgezero-store-celld compile only for non-wasm targets today, and grows to XL. EZI-2c and EZI-2s run them on Cloudflare and Spin, and DEC-8 now picks each of those platforms' default store. --- ...11-11-server-side-identity-all-adapters.md | 44 ++++++++-------- ...erver-side-identity-all-adapters-design.md | 52 ++++++++++++------- 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 2ef7dca7c..c3dfe3d70 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -158,7 +158,7 @@ Close. | 3 | IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | P1 | Move | Fastly and Axum ClientInfo; JA4 | EZI-3; IABTechLab/edgezero#397 | | 3 | TS #1553, #1246 | P2 | Move | After-response CAS retry; Lockr IDs to KV | PR #1487 | | 4 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1488 | -| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | +| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld adapters for every platform | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | | 4 | EZ R2 | P1 | Release | Fastly and Axum parts of EZI-1 and EZI-3 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | | 4 | TS A1 | P1 | New, PR fix | Rebase #1531 onto #1506, repin to R1; #1528 closes | R1, #1507; DEC-6 | | 4 | TS T3 | P1 | New | SharedID cleanup | PR #1488 | @@ -231,27 +231,27 @@ revisions on it that Trusted Server pins. New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| ------ | -------------------------------------------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| EZI-2a | `KvStore` record contract, the Fastly provider and the local in-memory store | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1b | -| EZI-2b | DynamoDB and celld providers meet the record contract, with writes spread across partition keys | EZI-2b | Feature | L | P1 | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | R1b | -| EZI-3 | ClientInfo contract: portable client IP, geo and TLS metadata with explicit absence, the contract part of IABTechLab/edgezero#89 | EZI-3 | Feature | M | P1 | PR IABTechLab/edgezero#275 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, IABTechLab/edgezero#283, EZI-3s, IABTechLab/edgezero#89, R2 | -| EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PRs IABTechLab/edgezero#275, IABTechLab/edgezero#406 | EZI-1c, EZI-1s, R2 | -| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and an in-memory backend for Axum (after the MVP) | EZI-5 | Feature | S | P3 | PR IABTechLab/edgezero#275; DEC-15 | EZI-5c, EZI-5s, T10 | -| EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | -| EZI-2c | Cloudflare store meets the record contract, or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a; DEC-8 | R3 | -| EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | IABTechLab/edgezero#432 | R3 | -| EZI-5c | Rate limiter backend for Cloudflare (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | -| EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | -| EZI-2s | Spin store meets the record contract, or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a; DEC-8 | R4 | -| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | EZI-3 | R4, IABTechLab/edgezero#89 | -| EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | -| EZI-5s | Rate limiter backend for Spin (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | -| R1 | Integration-branch revision with IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | -| R1b | Integration-branch revision with the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | -| R2 | Integration-branch revision with the Fastly and Axum parts of EZI-1 and EZI-3 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T11, T12 | -| R3 | Integration-branch revision with the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | -| R4 | Integration-branch revision with the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, R3 | T9, T18 | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| EZI-2a | `KvStore` record contract, the Fastly provider and the local in-memory store | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1b | +| EZI-2b | DynamoDB and celld `KvStore` adapters that run on every adapter over EdgeZero's outbound HTTP and meet the record contract, with DynamoDB writes spread across partition keys; verified on Fastly and Axum | EZI-2b | Feature | XL | P1 | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | R1b, EZI-2c, EZI-2s | +| EZI-3 | ClientInfo contract: portable client IP, geo and TLS metadata with explicit absence, the contract part of IABTechLab/edgezero#89 | EZI-3 | Feature | M | P1 | PR IABTechLab/edgezero#275 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, IABTechLab/edgezero#283, EZI-3s, IABTechLab/edgezero#89, R2 | +| EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PRs IABTechLab/edgezero#275, IABTechLab/edgezero#406 | EZI-1c, EZI-1s, R2 | +| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and an in-memory backend for Axum (after the MVP) | EZI-5 | Feature | S | P3 | PR IABTechLab/edgezero#275; DEC-15 | EZI-5c, EZI-5s, T10 | +| EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | +| EZI-2c | On Cloudflare, the DynamoDB and celld adapters pass the record contract, and Workers KV meets it or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a, EZI-2b; DEC-8 | R3 | +| EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | IABTechLab/edgezero#432 | R3 | +| EZI-5c | Rate limiter backend for Cloudflare (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | +| EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | +| EZI-2s | On Spin, the DynamoDB and celld adapters pass the record contract, and Spin's KV meets it or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a, EZI-2b; DEC-8 | R4 | +| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | EZI-3 | R4, IABTechLab/edgezero#89 | +| EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | +| EZI-5s | Rate limiter backend for Spin (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | +| R1 | Integration-branch revision with IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | +| R1b | Integration-branch revision with the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | +| R2 | Integration-branch revision with the Fastly and Axum parts of EZI-1 and EZI-3 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T11, T12 | +| R3 | Integration-branch revision with the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | +| R4 | Integration-branch revision with the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, R3 | T9, T18 | Existing, moved under IABTechLab/edgezero#468. IABTechLab/edgezero#89 to IABTechLab/edgezero#92 leave IABTechLab/edgezero#88 and the last six leave IABTechLab/edgezero#391 or have diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 15c3cea5b..6aeaeca38 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -118,14 +118,14 @@ Identity runs on an adapter only where the record contract and geo are supported. Elsewhere, startup rejects identity configuration (ID-2). Delivery follows DEC-4: Fastly, Axum, Cloudflare, Spin. -| Capability | Fastly | Axum | Cloudflare | Spin | -| ----------------------------------------------------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations, exposed through EdgeZero by EZI-2a | Supported. DynamoDB or celld through EZI-2b | Unsupported today. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | Unsupported today. `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing. Its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | -| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | -| Rate limiter | Supported (Edge Rate Limiting) | None in the MVP; the load balancer limits (DEC-11, DEC-15) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | -| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | -| TLS fingerprint (JA4) | Supported (Fastly SDK); EZI-3 accessor | EZI-3 from a trusted CloudFront hop | EZI-3 accessor, or absent | EZI-3 accessor, or absent | -| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | +| Capability | Fastly | Axum | Cloudflare | Spin | +| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations through EZI-2a, or DynamoDB or celld through EZI-2b | Supported. DynamoDB or celld through EZI-2b | DynamoDB or celld through EZI-2b and EZI-2c. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | DynamoDB or celld through EZI-2b and EZI-2s. Spin's own `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing; its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | +| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | +| Rate limiter | Supported (Edge Rate Limiting) | None in the MVP; the load balancer limits (DEC-11, DEC-15) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | +| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | +| TLS fingerprint (JA4) | Supported (Fastly SDK); EZI-3 accessor | EZI-3 from a trusted CloudFront hop | EZI-3 accessor, or absent | EZI-3 accessor, or absent | +| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | Cloudflare and Spin move to Supported only when EZI-2 and EZI-4 deliver for them (DEC-8). Until then their startup rejects identity configuration, and @@ -435,16 +435,22 @@ part. an EdgeZero contract suite passes against the Fastly provider under Viceroy and against EdgeZero's local in-memory store, which the Axum development server uses. -- **EZI-2b DynamoDB and celld providers.** Both meet the EZI-2a contract. - DynamoDB writes are spread across partition keys with no item every insert - updates. Accept: the contract suite passes against both. State: IABTechLab/edgezero#463's - `CelldRecordStore` has the semantics on a separate API; IABTechLab/edgezero#414's DynamoDB +- **EZI-2b DynamoDB and celld adapters for every platform.** Both meet the + EZI-2a contract and run on every adapter over EdgeZero's outbound HTTP, while + the native stores (Fastly KV, Cloudflare KV, Spin KV) stay on their own + platform (DEC-3). DynamoDB writes are spread across partition keys with no + item every insert updates. Accept: the contract suite passes against both on + Fastly and Axum; EZI-2c and EZI-2s run it on Cloudflare and Spin. State: + the IABTechLab/edgezero#414 and IABTechLab/edgezero#422 providers are + native-only. IABTechLab/edgezero#463's `CelldRecordStore` has the semantics on a separate API; IABTechLab/edgezero#414's DynamoDB provider has strong existence and listing but no CAS; IABTechLab/edgezero#410 and IABTechLab/edgezero#437 rule out conditional writes today. -- **EZI-2c and EZI-2s Cloudflare and Spin stores.** A store on each platform - meets the EZI-2a contract or reports `KvError::Unsupported` (DEC-8). Accept: - the contract suite passes, or startup on that platform rejects identity - configuration. +- **EZI-2c and EZI-2s Cloudflare and Spin stores.** On each platform the + DynamoDB and celld adapters pass the EZI-2a contract, and the native store + meets it or reports `KvError::Unsupported`; DEC-8 picks the default. Accept: + the contract suite passes for every store offered there, and a native store + that cannot meet it reports `Unsupported`, so identity on it is rejected at + startup. - **EZI-3 Trusted client metadata.** One accessor returns the trusted client IP, and one the JA4 fingerprint where a trusted provider supplies it (IABTechLab/edgezero#283), on every adapter, Fastly and Axum first. Axum @@ -544,7 +550,9 @@ Decided: `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. The user reaffirmed it on 2026-11-12: Axum's record uses EdgeZero's pluggable `KvStore` adapters (DynamoDB and celld through EZI-2b), never an Axum-only - store such as #1536's. + store such as #1536's. The DynamoDB and celld adapters are available to every + adapter; the native stores (Fastly KV, Cloudflare KV, Spin KV) only to their + own platform. - **DEC-4 Platform order** (user, 2026-11-12). Fastly, then Axum, then Cloudflare, then Spin. Fastly and Axum (the MVP, DEC-2) must ship complete without any Cloudflare or Spin work, so every ticket and release is split by @@ -586,8 +594,9 @@ Proposed and assumed by the plan until confirmed: Open: -- **DEC-8 Cloudflare and Spin store.** Which EdgeZero store meets the record - contract there, per [Platform support](#platform-support). Blocks EZI-2c, +- **DEC-8 Cloudflare and Spin store.** Which store Cloudflare and Spin use by + default for the record: the DynamoDB or celld adapter, or a native store that + meets the contract, per [Platform support](#platform-support). Blocks EZI-2c, EZI-2s, R3, T8 and T9. - **DEC-9 LiveRamp Custom ID.** The current value is the IP-derived EC prefix, shared by everyone behind one IP. Blocks enabling LiveRamp in #1488, and @@ -693,3 +702,8 @@ changes, so #1535's separate per-adapter `/_ts/consent` route folds into it The user then reaffirmed DEC-3 for Axum: the record uses EdgeZero's pluggable `KvStore` adapters. DEC-6 now covers only the base, restacking the RC units on #1506, and DEC-9 waits on a LiveRamp test setup. + +The user then made the DynamoDB and celld adapters available to every adapter, +with native stores only on their own platform: EZI-2b builds them over +EdgeZero's outbound HTTP, EZI-2c and EZI-2s run them on Cloudflare and Spin, +and DEC-8 now picks each of those platforms' default store. From dca7e59da1bbdb86f06bfe99bd00de46898c6064 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:29:36 -0800 Subject: [PATCH 19/31] Decide DEC-6 and correct the Axum restack Per the user, the RC line is forward-ported to main (DEC-6). Rechecking the proposed stack against DEC-3 and DEC-16: #1536 and #1537 are mostly Axum-only stores, store selection and wiring that EZI-2b, T6 and T21 replace, so A2 becomes a new pull request that takes their tests, and both close with #1535 once their code moves. The image and Terraform units restack on #1543, which publishes the image and shares .dockerignore with #1539. #1531 goes on #1506 for A1 and #1532 on #1531 for #1529. --- ...11-11-server-side-identity-all-adapters.md | 86 ++++++++++--------- ...erver-side-identity-all-adapters-design.md | 21 +++-- 2 files changed, 59 insertions(+), 48 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index c3dfe3d70..ee386c423 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -79,30 +79,30 @@ IABTechLab/edgezero. Trusted Server: -| PR | Head | Base on GitHub | Fix | -| ------------------- | ------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| #1557 | `76e1dd5f6` | main | None | -| #1525 | `cd8977cad` | main | None; #1526 does not need it | -| #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | -| #1516 | `214cd4821` | main | None | -| #1503 | `5e10c24e4` | main | None; needs neither #1506 nor new EdgeZero | -| #1449 | `fd33f2c74` | main | None; needs neither #1506 nor new EdgeZero | -| #1506 | `6c39a56a1` | main | Rebase after #1557, #1526 and #1516; clear CodeQL alert 283 | -| #1487 | `1600781eb` | main | Rebase after #1526 and #1506: 8 and 17 files conflict | -| #1488 | `7f55776f3` | #1487's branch | Take #1566's two #1488 commits | -| #1489 | `d3780ba13` | #1488's branch | None | -| #1566 | `117a58e0e` | #1488's branch | Move its two #1488 commits down; rebuild the Axum part on T21 and EdgeZero (Part C). 1 file conflicts with #1489, 19 with #1506 | -| #1527 | `a020daeec` | #1526's branch | Close once #1506 covers its tests (DEC-1) | -| #1528 | `ea779fb47` | #1527's branch | Close once #1531 carries A1 | -| #1543 | `e5e6ea7fa` | #1449's branch | None; ships with A6 | -| #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | -| #1499 | `084fdbba9` | #1424's branch | Close at parity | -| #1531 | `88a6ba596` | #1506's branch | Retargeted without a rebase: it forks from main with its own cutover and the RC tag pin, and 13 files conflict with #1506. Rebase onto #1506; it then delivers A1 | -| #1532 | `852b875fe` | #1531's branch | Delivers #1529 (A4); restack after #1531 | -| #1533, #1534 | `82d9a0e66`, `646c39a34` | #1532's and #1533's branches | RC-baseline copies of #1525 and #1526: drop from the chain and close | -| #1536, #1535, #1537 | `296e05405`, `0666a9b4b`, `4a838272c` | #1534's, #1536's and #1535's branches | Restack on #1532. #1536 and #1537 feed A2 without their Axum-only stores; #1535 feeds T19 and T20 | -| #1539, #1538, #1540 | `8d41cb06c`, `3111c2456`, `eb6e19ef4` | #1537's, #1539's and #1538's branches | Restack on #1532: they share no files with #1533 to #1537, so A6 and A7 stop waiting on A2 | -| #1541 | `7dd2e3056` | main | Close at parity | +| PR | Head | Base on GitHub | Fix | +| ------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| #1557 | `76e1dd5f6` | main | None | +| #1525 | `cd8977cad` | main | None; #1526 does not need it | +| #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | +| #1516 | `214cd4821` | main | None | +| #1503 | `5e10c24e4` | main | None; needs neither #1506 nor new EdgeZero | +| #1449 | `fd33f2c74` | main | None; needs neither #1506 nor new EdgeZero | +| #1506 | `6c39a56a1` | main | Rebase after #1557, #1526 and #1516; clear CodeQL alert 283 | +| #1487 | `1600781eb` | main | Rebase after #1526 and #1506: 8 and 17 files conflict | +| #1488 | `7f55776f3` | #1487's branch | Take #1566's two #1488 commits | +| #1489 | `d3780ba13` | #1488's branch | None | +| #1566 | `117a58e0e` | #1488's branch | Move its two #1488 commits down; rebuild the Axum part on T21 and EdgeZero (Part C). 1 file conflicts with #1489, 19 with #1506 | +| #1527 | `a020daeec` | #1526's branch | Close once #1506 covers its tests (DEC-1) | +| #1528 | `ea779fb47` | #1527's branch | Close once #1531 carries A1 | +| #1543 | `e5e6ea7fa` | #1449's branch | Base for #1539, #1538 and #1540; ships with A6 | +| #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | +| #1499 | `084fdbba9` | #1424's branch | Close at parity | +| #1531 | `88a6ba596` | #1506's branch | Retargeted without a rebase: it forks from main with its own cutover and the RC tag pin, and 13 files conflict with #1506. Rebase onto #1506; it then delivers A1 | +| #1532 | `852b875fe` | #1531's branch | Delivers #1529 (A4); restack after #1531 | +| #1533, #1534 | `82d9a0e66`, `646c39a34` | #1532's and #1533's branches | RC-baseline copies of #1525 and #1526: drop from the chain and close | +| #1536, #1535, #1537 | `296e05405`, `0666a9b4b`, `4a838272c` | #1534's, #1536's and #1535's branches | Not restacked: mostly Axum-only stores, store selection and wiring that EZI-2b, T6 and T21 replace. Their tests go to A2, A8 and T5 and #1535's code to T19 and T20; each closes when those land | +| #1539, #1538, #1540 | `8d41cb06c`, `3111c2456`, `eb6e19ef4` | #1537's, #1539's and #1538's branches | Restack on #1543, which publishes the image (A6); #1539 and #1543 both add `.dockerignore`. Merge after A1 | +| #1541 | `7dd2e3056` | main | Close at parity | EdgeZero: @@ -160,7 +160,7 @@ Close. | 4 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1488 | | 4 | EZ EZI-2b | P1 | New | DynamoDB and celld adapters for every platform | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | | 4 | EZ R2 | P1 | Release | Fastly and Axum parts of EZI-1 and EZI-3 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | -| 4 | TS A1 | P1 | New, PR fix | Rebase #1531 onto #1506, repin to R1; #1528 closes | R1, #1507; DEC-6 | +| 4 | TS A1 | P1 | New, PR fix | Rebase #1531 onto #1506, repin to R1; #1528 closes | R1, #1507 | | 4 | TS T3 | P1 | New | SharedID cleanup | PR #1488 | | 4 | TS #1555 | P2 | Move | Opt-out EIDs | PR #1488 | | 4 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | @@ -174,13 +174,14 @@ Close. | 5 | TS #1527, #1528, #1523, #1533, #1534 | P2 | Close | Tests moved to #1506; A1 on #1531; RC copies of #1525 and #1526 | #1506, A1 | | 6 | TS T6 | P1 | New | Shared record backend, Fastly first | R1b, #1522 | | 6 | TS T11 | P1 | New | Providers awaited before finalization in the shared app | T21, R2, PR #1487 | -| 6 | TS A7 | P1 | New | Reference infrastructure via #1540, with an example rate rule | A1, A6, #1529; DEC-6 | -| 7 | TS A2 | P1 | New | Identity on Axum through the shared app, from #1536 and #1537 | T5, T6, T21, A1, #1529; DEC-6 | +| 6 | TS A7 | P1 | New | Reference infrastructure via #1540, with an example rate rule | A1, A6, #1529 | +| 7 | TS A2 | P1 | New | Identity on Axum through the shared app, a new PR with #1536's and #1537's tests | T5, T6, T21, A1, #1529 | | 7 | TS T19 | P1 | New | EC route in the shared app: mint and write back on a same-origin call; pixel and JSON responses | T11, T12, T13, T21 | | 8 | TS A10, A8 | P1 | New | Identity features pass on Axum; SIGTERM test in CI | A2 plus their Part B blockers | | 8 | TS T20 | P1 | New | Injected script calls the EC route on consent and merges the IDs into Prebid | T15, T19 | | 8 | TS #1541, #1424, #1499 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | | 9 | TS A5 | P1 | New | Load test | A2, A7, A10 | +| 9 | TS #1535, #1536, #1537 | P2 | Close | Their code and tests now in T19, T20, A2 and A8 | A2, A8, T19, T20 | | 10 | EZ EZI-1c, EZI-2c, EZI-4c; IABTechLab/edgezero#91 | P2 | New, Move | Cloudflare parts in EdgeZero | Part B blockers; DEC-8 | | 11 | EZ R3 | P2 | Release | Cloudflare integration point | wave 10 | | 12 | TS #1518, T8 | P2 | Move, New | Geo source check; identity on Cloudflare through the shared app | R3 (T8 also T6, T1, T5, T21; DEC-8) | @@ -280,13 +281,13 @@ New: | ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------- | ---- | --- | ------------------------------ | -------------------------------- | -| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507; DEC-6 | A2, A3, A6, A7, A9, #1529, #1141 | -| A2 | Identity on Axum through T21's shared app, from #1536 and #1537 without their Axum-only stores; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529; DEC-6 | A5, A8, A10 | +| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507 | A2, A3, A6, A7, A9, #1529, #1141 | +| A2 | Identity on Axum through T21's shared app: a new PR that takes #1536's and #1537's tests but not their Axum-only stores, store selection or wiring; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529 | A5, A8, A10 | | A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | | A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | -| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; PR #1449; DEC-6 | A7 | -| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded, and an example rate-based rule at CloudFront or the load balancer | AX-3, AX-6, ID-6, ID-7 | Feature | L | P1 | A1, A6, #1529; DEC-6 | A5, A10 | -| A8 | Run the SIGTERM-during-record-write test in CI | AX-1 | Task | S | P1 | A2 | none | +| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; PR #1449 | A7 | +| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded, and an example rate-based rule at CloudFront or the load balancer | AX-3, AX-6, ID-6, ID-7 | Feature | L | P1 | A1, A6, #1529 | A5, A10 | +| A8 | Run the SIGTERM-during-record-write test in CI (`scripts/test-native-ec-image.mjs` from #1536) | AX-1 | Task | S | P1 | A2 | none | | A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | | A10 | Identity features pass on Axum from the shared app: providers, abuse protection, SharedID write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P1 | A2, A7, R2, T11, T12, T13, T19 | A5 | @@ -294,7 +295,7 @@ Existing, moved under #1558: | Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | | ----- | ------------------------------------------------------------ | ----------- | ---- | --- | ---------- | ------ | -| #1529 | Extract the protected CloudFront native Geo integration (A4) | Task (keep) | M | P1 | A1; DEC-6 | A2, A7 | +| #1529 | Extract the protected CloudFront native Geo integration (A4) | Task (keep) | M | P1 | A1 | A2, A7 | | #1141 | Make Axum consume the local config store | Bug (keep) | S | P2 | A1 | none | | #1142 | Uniform cross-adapter health and startup-failure contract | Task (keep) | M | P2 | none | none | | #1139 | Implement adapter stores or retire dead store manifests | Bug (keep) | M | P3 | none | none | @@ -436,12 +437,16 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. alerts and the per-request `AtomicBool` in the Fastly `AppState`. - **#1543 at `dc1a063d8` (AX-5).** It publishes the dev-server image (`STOPSIGNAL SIGINT`), so it ships with A6's production image contract. -- **RC units, restacked as the forward-ports.** #1531 goes on #1506, #1532 on - #1531, #1536 and #1537 on #1532 for A2, and #1539, #1538 and #1540 directly - on #1532 for A6 and A7; #1533 and #1534 close. #1535 (Sourcepoint GPP capture and server consent reconciliation) - feeds T19 and T20: its reconciliation runs in the EC route and its GPP - capture in the injected script, in place of a separate `/_ts/consent` route - wired into each adapter (DEC-16). #1532 takes its geo provider from EdgeZero's manifest +- **RC units, forward-ported (DEC-6).** #1531 goes on #1506 for A1 and #1532 on + #1531 for #1529. #1539, #1538 and #1540 go on #1543 for A6 and A7, resolving + the `.dockerignore` that #1539 and #1543 both add, and merge after A1. #1533 + and #1534 close as copies of #1525 and #1526. #1536 and #1537 are not + restacked: their Axum-only stores, store selection and wiring give way to + EZI-2b, T6 and T21, and their tests move into A2, A8 and T5. #1535 (Sourcepoint + GPP capture and server consent reconciliation) feeds T19 and T20: its + reconciliation runs in the EC route and its GPP capture in the injected + script, in place of a separate `/_ts/consent` route wired into each adapter + (DEC-16). #1532 takes its geo provider from EdgeZero's manifest once IABTechLab/edgezero#439 binds it, dropping `TS_NATIVE_GEO_PROFILE_FILE`. The notes below were checked at the RC tag `v0.1.1-rc-axum.202611.2` (`7c6e68d94`), which contains every unit. They are @@ -462,7 +467,8 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. rate-based rule (AX-3, AX-6, DEC-11). All units: replace `TS_*` names (AX-2). #1541: drop the commits of closed #1545. - **Proposed closures.** #1527 once its tests move (DEC-1); #1528 once #1531 - carries A1; #1533 and #1534, the RC copies of #1525 and #1526; #1310, which + carries A1; #1533 and #1534, the RC copies of #1525 and #1526; #1535, #1536 + and #1537 once T19, T20, A2 and A8 carry their code and tests; #1310, which #1488's ID5 provider supersedes; #1424, #1499 and #1541 at parity. ### EdgeZero, requests to the PR authors diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 6aeaeca38..e84119fde 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -574,6 +574,14 @@ Decided: IABTechLab/edgezero#439 chain and the EZI tickets. Trusted Server main pins its revisions (R1, R1b, R2) instead of waiting for an EdgeZero release, and repins to a released tag once EdgeZero publishes one. +- **DEC-6 Base for the Axum line** (user, 2026-11-12). Forward-port the RC + line to main rather than back-port the provider stack to it, which conflicts + in 13 to 23 files. #1531 restacks on #1506 for A1 and #1532 on #1531 for + #1529. The image and Terraform units (#1539, #1538, #1540) restack on #1543, + which publishes the image. #1536 and #1537 are not restacked: their + Axum-only stores, store selection and wiring give way to EZI-2b, T6 and + T21, and their tests move into A2, A8 and T5. #1535 goes into the EC route + (T19, T20), and #1533 and #1534 close as copies of #1525 and #1526. - **DEC-10 Mirror partners** (user, 2026-11-12). ID5 and LiveRamp, the important identity providers, get `mirror_enabled`, and the MVP's provider and mirror checks (T11, A10) use them. @@ -584,14 +592,6 @@ Decided: order. Shared logic lives in Trusted Server core; a platform capability an adapter lacks is an EdgeZero ask, never code in one adapter crate (SH-5). -Proposed and assumed by the plan until confirmed: - -- **DEC-6 Base for the Axum line.** Forward-port the RC line to main by - restacking its units on #1506 (proposed), rather than back-port the provider - stack to the RC line. Applying each provider stack to the RC tag from its - main fork point conflicts in 13 to 23 files. The store path follows DEC-3. - Blocks A1, A2, A4, A6, A7. - Open: - **DEC-8 Cloudflare and Spin store.** Which store Cloudflare and Spin use by @@ -707,3 +707,8 @@ The user then made the DynamoDB and celld adapters available to every adapter, with native stores only on their own platform: EZI-2b builds them over EdgeZero's outbound HTTP, EZI-2c and EZI-2s run them on Cloudflare and Spin, and DEC-8 now picks each of those platforms' default store. + +The user then decided DEC-6. Rechecking the restack against DEC-3 and DEC-16 +showed that #1536 and #1537 are mostly Axum-only stores and wiring, so A2 is a +new pull request on T21 and T6 that takes their tests, and that the image unit +belongs on #1543, with which it shares `.dockerignore`. From 91525086799ca724b37eca1f4693cfcd17ff3f44 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:36:15 -0800 Subject: [PATCH 20/31] Rely on JA4 where available and make it an EdgeZero abstraction Per the user, ID-7 relies on a trusted JA4 where the adapter supplies one and otherwise does not block record creation (DEC-18), and JA4 is an EdgeZero abstraction, ctx.ja4(), filled per adapter: Fastly from runtime TLS, Cloudflare from Bot Management, Axum from a trusted proxy header such as CloudFront's, Spin never. Trusted Server reads JA4 only through it, so T12 deletes the direct Fastly read. IABTechLab/edgezero#469's draft leaves Axum NotExposed and defers the trusted proxy header to its section 13.1; the plan asks for that section in R2. --- ...11-11-server-side-identity-all-adapters.md | 31 ++++++----- ...erver-side-identity-all-adapters-design.md | 53 ++++++++++++------- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index ee386c423..65fedceb0 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -258,18 +258,18 @@ Existing, moved under IABTechLab/edgezero#468. IABTechLab/edgezero#89 to IABTech IABTechLab/edgezero#88 and the last six leave IABTechLab/edgezero#391 or have no parent; both old epics stay where they are: -| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---- | --- | ------------------------------------------------------------------------------------- | --------------------------------------------------- | -| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | S | P3 | EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | -| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | EZI-3 | R2, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | EZI-3, IABTechLab/edgezero#397 | R2, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | EZI-3 | R3, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#397 | Axum: safe production diagnostics and trusted-proxy handling (delivered by PR IABTechLab/edgezero#409) | Task (keep) | M | P1 | none | R1, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | -| IABTechLab/edgezero#410 | Native AWS configuration, secret and KV store providers (PR IABTechLab/edgezero#414) | Task (keep) | L | P1 | none | EZI-2b, R1 | -| IABTechLab/edgezero#437 | Native celld configuration and KV store providers (PR IABTechLab/edgezero#422) | Task (keep) | M | P1 | none | EZI-2b, R1 | -| IABTechLab/edgezero#432 | Axum: protected CloudFront Geo metadata provider (PR IABTechLab/edgezero#439) | Story (keep) | M | P1 | none | R1, EZI-4c, EZI-4s | -| IABTechLab/edgezero#438 | Axum: explicit local GeoIP provider (PR IABTechLab/edgezero#453) | Story (keep) | M | P3 | none | none | -| IABTechLab/edgezero#283 | Expose JA4 through a standard request interface (design in PR IABTechLab/edgezero#469); R2 needs its Fastly and Axum parts, and other adapters report absence | Task (keep) | M | P1 | EZI-3, IABTechLab/edgezero#397 | R2 | +| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---- | --- | ------------------------------------------------------------------------------------- | --------------------------------------------------- | +| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | S | P3 | EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | +| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | EZI-3 | R2, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | EZI-3, IABTechLab/edgezero#397 | R2, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | EZI-3 | R3, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#397 | Axum: safe production diagnostics and trusted-proxy handling (delivered by PR IABTechLab/edgezero#409) | Task (keep) | M | P1 | none | R1, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | +| IABTechLab/edgezero#410 | Native AWS configuration, secret and KV store providers (PR IABTechLab/edgezero#414) | Task (keep) | L | P1 | none | EZI-2b, R1 | +| IABTechLab/edgezero#437 | Native celld configuration and KV store providers (PR IABTechLab/edgezero#422) | Task (keep) | M | P1 | none | EZI-2b, R1 | +| IABTechLab/edgezero#432 | Axum: protected CloudFront Geo metadata provider (PR IABTechLab/edgezero#439) | Story (keep) | M | P1 | none | R1, EZI-4c, EZI-4s | +| IABTechLab/edgezero#438 | Axum: explicit local GeoIP provider (PR IABTechLab/edgezero#453) | Story (keep) | M | P3 | none | none | +| IABTechLab/edgezero#283 | Expose JA4 through `ctx.ja4()` (design in PR IABTechLab/edgezero#469); R2 needs Fastly's runtime JA4 and Axum's JA4 from a trusted proxy header (the design's section 13.1); Cloudflare and Spin follow | Task (keep) | M | P1 | EZI-3, IABTechLab/edgezero#397 | R2 | IABTechLab/edgezero#89 stays the umbrella story: EZI-3 carries its contract, and it completes with the last adapter's mapping. IABTechLab/edgezero#397, IABTechLab/edgezero#410 and IABTechLab/edgezero#432 leave @@ -325,7 +325,7 @@ New: | T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | | T10 | Rate limits after the MVP: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P3 | EZI-5, T21; DEC-15 | none | | T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly | ID-4, ID-5, WB-1 | Feature | M | P1 | T21, R2; PR #1487 | A10, T17, T19 | -| T12 | Refuse record creation from non-browser clients in the shared app, through EdgeZero's JA4 accessor; verified on Fastly | ID-7 | Feature | M | P1 | R2, T21 | A10, T17, T19 | +| T12 | In the shared app, refuse record creation when a trusted JA4 from `ctx.ja4()` and the User-Agent are not a browser's, and do not block when no trusted JA4 exists; delete the direct Fastly JA4 read; verified on Fastly | ID-7 | Feature | M | P1 | R2, T21 | A10, T17, T19 | | T13 | SharedID in the shared app: create one when the reader has none, and write it back with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-5, SID-6 | Feature | M | P1 | T3, T21; PR #1488 | A10, T17, T19 | | T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | | T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | @@ -492,6 +492,11 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. `9a985c99`. - **IABTechLab/edgezero#414 at `3733482f`.** Drop those two commits once they move down. +- **IABTechLab/edgezero#469 at `1d5bf2ff`.** Bring section 13.1, JA4 from a trusted proxy + header recorded as `Ja4Source::TrustedProxyHeader`, into the work that ships + in R2. Axum's ID-7 needs JA4 from a hop that IABTechLab/edgezero#409 or IABTechLab/edgezero#439 checks, + such as CloudFront's `CloudFront-Viewer-JA4-Fingerprint`; an unchecked peer + stays `NotExposed` (EZI-3, DEC-18). - **IABTechLab/edgezero#439 at `3a0e5eb4`.** Bind the geo provider from the manifest, so an application reads `ctx.geo()` without its own boot profile (EZI-4, DEC-16); #1532 composes it from a Trusted Server profile today. diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index e84119fde..8fa45b8b9 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -118,14 +118,14 @@ Identity runs on an adapter only where the record contract and geo are supported. Elsewhere, startup rejects identity configuration (ID-2). Delivery follows DEC-4: Fastly, Axum, Cloudflare, Spin. -| Capability | Fastly | Axum | Cloudflare | Spin | -| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations through EZI-2a, or DynamoDB or celld through EZI-2b | Supported. DynamoDB or celld through EZI-2b | DynamoDB or celld through EZI-2b and EZI-2c. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | DynamoDB or celld through EZI-2b and EZI-2s. Spin's own `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing; its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | -| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | -| Rate limiter | Supported (Edge Rate Limiting) | None in the MVP; the load balancer limits (DEC-11, DEC-15) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | -| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | -| TLS fingerprint (JA4) | Supported (Fastly SDK); EZI-3 accessor | EZI-3 from a trusted CloudFront hop | EZI-3 accessor, or absent | EZI-3 accessor, or absent | -| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | +| Capability | Fastly | Axum | Cloudflare | Spin | +| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations through EZI-2a, or DynamoDB or celld through EZI-2b | Supported. DynamoDB or celld through EZI-2b | DynamoDB or celld through EZI-2b and EZI-2c. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | DynamoDB or celld through EZI-2b and EZI-2s. Spin's own `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing; its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | +| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | +| Rate limiter | Supported (Edge Rate Limiting) | None in the MVP; the load balancer limits (DEC-11, DEC-15) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | +| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | +| TLS fingerprint (JA4) | Runtime TLS through EdgeZero's `ctx.ja4()` (EZI-3) | `ctx.ja4()` from a trusted proxy header such as CloudFront's (EZI-3) | `ctx.ja4()` from Bot Management, otherwise absent | Absent (`NotExposed`) | +| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | Cloudflare and Spin move to Supported only when EZI-2 and EZI-4 deliver for them (DEC-8). Until then their startup rejects identity configuration, and @@ -246,14 +246,20 @@ marks the record contract and geo as supported. limit. State: Fastly Edge Rate Limiting is the only limiter; record creation is limited nowhere; #1487 keys lookups on the IP-derived EC prefix, so everyone behind one IPv4 address or IPv6 /64 shares a budget. -- **ID-7 Abuse resistance.** Record creation is refused for clients that are - not browsers, on every supported adapter. Accept: a scripted client with a - browser User-Agent and no browser TLS fingerprint creates no record. State: +- **ID-7 Abuse resistance.** Where a trusted JA4 is available, record creation + is refused for clients whose JA4 and User-Agent are not a browser's; where + none is available, record creation is not blocked on its account (DEC-18). + JA4 comes only from EdgeZero's `ctx.ja4()` (EZI-3). Accept: on each supported + adapter a scripted client with a browser User-Agent and a non-browser JA4 + creates no record, and a request with no trusted JA4 is not refused for + lacking one. State: Fastly checks only that a JA4 exists plus a browser User-Agent platform; the RC's Axum checks only the User-Agent, with request filters, because it has no trusted JA4 (EZI-3); Cloudflare and Spin create no records yet. CloudFront sends JA4 to the origin only in the `CloudFront-Viewer-JA4-Fingerprint` - header, which an origin request policy must forward. + header, which an origin request policy must forward. IABTechLab/edgezero#469's + draft design reports Axum as `NotExposed` and leaves the trusted proxy header + to its section 13.1. - **ID-8 Body EIDs keep being saved.** EIDs sent in the `/auction` body are saved to the record. Accept: on each supported adapter a body EID is in the record after the request. State: main saves them on Fastly; the provider @@ -453,10 +459,13 @@ part. startup. - **EZI-3 Trusted client metadata.** One accessor returns the trusted client IP, and one the JA4 fingerprint where a trusted provider supplies it - (IABTechLab/edgezero#283), on every adapter, Fastly and Axum first. Axum - reads JA4 only from a trusted CloudFront hop. Accept: the same accessors work - on each adapter as its part lands, and a JA4 header from an untrusted peer - reads as absent. + (IABTechLab/edgezero#283), on every adapter, Fastly and Axum first. JA4 is an EdgeZero abstraction, `ctx.ja4()`, that each adapter fills + from its own source: Fastly from runtime TLS, Cloudflare from Bot Management, + Axum from a trusted proxy header such as CloudFront's + `CloudFront-Viewer-JA4-Fingerprint` set by a hop EdgeZero's checked ingress + trusts, and Spin never (`NotExposed`). Trusted Server reads JA4 only through + it. Accept: the same accessors work on each adapter as its part lands, and a + JA4 header from an untrusted peer reads as absent. - **EZI-4 Geo with region on Cloudflare and Spin.** `ctx.geo()` gives country and region there, or a capability error. Fastly keeps its existing geo and Axum uses CloudFront geo from IABTechLab/edgezero#439, bound from EdgeZero's @@ -580,8 +589,11 @@ Decided: #1529. The image and Terraform units (#1539, #1538, #1540) restack on #1543, which publishes the image. #1536 and #1537 are not restacked: their Axum-only stores, store selection and wiring give way to EZI-2b, T6 and - T21, and their tests move into A2, A8 and T5. #1535 goes into the EC route - (T19, T20), and #1533 and #1534 close as copies of #1525 and #1526. + T21, and their tests move into A2, A8 and T5. #1535 goes into the EC route (T19, T20), and #1533 and #1534 close as copies of #1525 and #1526. +- **DEC-18 JA4 when available** (user, 2026-11-12). ID-7 relies on a trusted + JA4 where the adapter supplies one; without one, record creation is not + blocked. JA4 is EdgeZero's abstraction (EZI-3), never read by Trusted + Server from a header or a platform SDK. - **DEC-10 Mirror partners** (user, 2026-11-12). ID5 and LiveRamp, the important identity providers, get `mirror_enabled`, and the MVP's provider and mirror checks (T11, A10) use them. @@ -712,3 +724,8 @@ The user then decided DEC-6. Rechecking the restack against DEC-3 and DEC-16 showed that #1536 and #1537 are mostly Axum-only stores and wiring, so A2 is a new pull request on T21 and T6 that takes their tests, and that the image unit belongs on #1543, with which it shares `.dockerignore`. + +The user then decided DEC-18: ID-7 relies on a trusted JA4 where one exists and +otherwise does not block, and JA4 is an EdgeZero abstraction. Axum's JA4 +therefore needs the trusted proxy header that IABTechLab/edgezero#469's draft +defers to its section 13.1, which the plan asks to ship in R2. From c4417b7f80ef975894c8da25110f7e2fa27db899 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 11:47:02 -0800 Subject: [PATCH 21/31] Exclude attestation and packaging from the identity plan Per the user, binary attestation and package publishing (#1449, #1463, #1543) are not part of this work and carry lower priority (DEC-19). AX-5 keeps only the production image built from source, A6 shrinks to building it and no longer waits on #1449, and the image and Terraform units restack on #1531, where they apply cleanly, instead of #1543. --- ...11-11-server-side-identity-all-adapters.md | 24 +++++++++---------- ...erver-side-identity-all-adapters-design.md | 24 +++++++++++++------ 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 65fedceb0..30231da56 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -86,7 +86,7 @@ Trusted Server: | #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | | #1516 | `214cd4821` | main | None | | #1503 | `5e10c24e4` | main | None; needs neither #1506 nor new EdgeZero | -| #1449 | `fd33f2c74` | main | None; needs neither #1506 nor new EdgeZero | +| #1449 | `fd33f2c74` | main | Outside this plan (DEC-19) | | #1506 | `6c39a56a1` | main | Rebase after #1557, #1526 and #1516; clear CodeQL alert 283 | | #1487 | `1600781eb` | main | Rebase after #1526 and #1506: 8 and 17 files conflict | | #1488 | `7f55776f3` | #1487's branch | Take #1566's two #1488 commits | @@ -94,14 +94,14 @@ Trusted Server: | #1566 | `117a58e0e` | #1488's branch | Move its two #1488 commits down; rebuild the Axum part on T21 and EdgeZero (Part C). 1 file conflicts with #1489, 19 with #1506 | | #1527 | `a020daeec` | #1526's branch | Close once #1506 covers its tests (DEC-1) | | #1528 | `ea779fb47` | #1527's branch | Close once #1531 carries A1 | -| #1543 | `e5e6ea7fa` | #1449's branch | Base for #1539, #1538 and #1540; ships with A6 | +| #1543 | `e5e6ea7fa` | #1449's branch | Outside this plan (DEC-19) | | #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | | #1499 | `084fdbba9` | #1424's branch | Close at parity | | #1531 | `88a6ba596` | #1506's branch | Retargeted without a rebase: it forks from main with its own cutover and the RC tag pin, and 13 files conflict with #1506. Rebase onto #1506; it then delivers A1 | | #1532 | `852b875fe` | #1531's branch | Delivers #1529 (A4); restack after #1531 | | #1533, #1534 | `82d9a0e66`, `646c39a34` | #1532's and #1533's branches | RC-baseline copies of #1525 and #1526: drop from the chain and close | | #1536, #1535, #1537 | `296e05405`, `0666a9b4b`, `4a838272c` | #1534's, #1536's and #1535's branches | Not restacked: mostly Axum-only stores, store selection and wiring that EZI-2b, T6 and T21 replace. Their tests go to A2, A8 and T5 and #1535's code to T19 and T20; each closes when those land | -| #1539, #1538, #1540 | `8d41cb06c`, `3111c2456`, `eb6e19ef4` | #1537's, #1539's and #1538's branches | Restack on #1543, which publishes the image (A6); #1539 and #1543 both add `.dockerignore`. Merge after A1 | +| #1539, #1538, #1540 | `8d41cb06c`, `3111c2456`, `eb6e19ef4` | #1537's, #1539's and #1538's branches | Restack on #1531 (A1), where they apply cleanly. #1539 and #1543 both add `.dockerignore`, so whichever lands second reconciles it | | #1541 | `7dd2e3056` | main | Close at parity | EdgeZero: @@ -134,7 +134,6 @@ Close. | 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | | 0 | TS #1526, #1522 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | | 0 | TS #1516 | P1 | Merge | Already approved | none | -| 0 | TS #1449 | P1 | PR fix, Merge | Fix its round-4 review; A6 publishes through it | none | | 0 | TS #1189 | P1 | Close | Fixed on main by `56a19229f` (#1157), which deleted the ungated `ingest_*` functions; ask its author to close it | none | | 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | | 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | @@ -165,7 +164,7 @@ Close. | 4 | TS #1555 | P2 | Move | Opt-out EIDs | PR #1488 | | 4 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | | 5 | EZ R1b | P1 | Release | EZI-2a and EZI-2b | EZI-2a, EZI-2b | -| 5 | TS #1529 (A4), A6 with #1543, A3 | P1 | Move, New | CloudFront IP and geo via #1532; production image via #1539 and #1538; SIGTERM drain | A1 (A3 also R2; A6 also PR #1449) | +| 5 | TS #1529 (A4), A6, A3 | P1 | Move, New | CloudFront IP and geo via #1532; production image via #1539 and #1538; SIGTERM drain | A1 (A3 also R2) | | 5 | TS T2 | P1 | New | EIDs in `user.eids`; after the provider stack, so open PRs need no test churn | #1489 merged | | 5 | TS T12, T13 | P1 | New | Abuse protection; SharedID creation and write-back in the shared app | R2, T21 (T12); T3, T21, PR #1488 (T13) | | 5 | TS T15 | P1 | New | Identity-only tsjs | PR #1489 | @@ -285,7 +284,7 @@ New: | A2 | Identity on Axum through T21's shared app: a new PR that takes #1536's and #1537's tests but not their Axum-only stores, store selection or wiring; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529 | A5, A8, A10 | | A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | | A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | -| A6 | Publish and attest the production image; one contract for #1539 and #1543 | AX-5 | Feature | M | P1 | A1; PR #1449 | A7 | +| A6 | Build the production image from source (#1539, #1538): its entrypoint starts the production server with `STOPSIGNAL SIGTERM`; no publishing or attestation | AX-5 | Feature | S | P1 | A1 | A7 | | A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded, and an example rate-based rule at CloudFront or the load balancer | AX-3, AX-6, ID-6, ID-7 | Feature | L | P1 | A1, A6, #1529 | A5, A10 | | A8 | Run the SIGTERM-during-record-write test in CI (`scripts/test-native-ec-image.mjs` from #1536) | AX-1 | Task | S | P1 | A2 | none | | A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | @@ -364,6 +363,8 @@ Not moved: `proxy.certificate_check = false` and drops the setting from pull sync. - Out of these epics: #1484 (auction), #1140 (Cloudflare config), #1164 (Spin tooling), IABTechLab/trusted-server#518 (cleanup). +- Outside this plan at lower priority (DEC-19): binary attestation and package + publishing, #1449, #1463 and #1543. Requirements met inside open PRs, not by a ticket: ID-8 on main and SH-2 (#1487), SID-1 to SID-3 and SID-6 for the provider, SID-5 (#1488), WB-4 @@ -373,7 +374,7 @@ expiry (#1489), AX-7 (#1528). Part C lists them. Each item names the head it was checked at. Post it as a comment on that PR (Tasks 6 and 7); once posted, the PR thread is the source of truth. Since the -items were checked, #1506, #1487, #1488, #1489, #1543, #1449 and #1503 have +items were checked, #1506, #1487, #1488, #1489 and #1503 have moved to the heads in Part A, so Task 6 re-checks each of their items first. ### Trusted Server @@ -435,11 +436,9 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. IABTechLab/edgezero#409). Carry #1528's open items where they apply: the CLI test (`AxumDevServerConfig` lacks `ingress`), the type error, the CodeQL alerts and the per-request `AtomicBool` in the Fastly `AppState`. -- **#1543 at `dc1a063d8` (AX-5).** It publishes the dev-server image (`STOPSIGNAL SIGINT`), so it ships with A6's production - image contract. - **RC units, forward-ported (DEC-6).** #1531 goes on #1506 for A1 and #1532 on - #1531 for #1529. #1539, #1538 and #1540 go on #1543 for A6 and A7, resolving - the `.dockerignore` that #1539 and #1543 both add, and merge after A1. #1533 + #1531 for #1529. #1539, #1538 and #1540 go on #1531 for A6 and A7, where they + apply cleanly. #1533 and #1534 close as copies of #1525 and #1526. #1536 and #1537 are not restacked: their Axum-only stores, store selection and wiring give way to EZI-2b, T6 and T21, and their tests move into A2, A8 and T5. #1535 (Sourcepoint @@ -652,8 +651,7 @@ new head before posting. Post one `CREATOR:` comment per PR with its Part C item and the ticket it serves (`Part of #`): #1532 to #1529, #1526 to #1522, #1503 to #1432, -#1506 to #1507, #1531 to A1, #1536, #1537 and #1566 to A2, #1535 to T19, #1539, #1538 and -#1543 to A6, #1540 to A7, #1487 to T11, #1488 to T13 and #1489 to T15. For proposed closures, ask the author; do not close +#1506 to #1507, #1531 to A1, #1536, #1537 and #1566 to A2, #1535 to T19, #1539 and #1538 to A6, #1540 to A7, #1487 to T11, #1488 to T13 and #1489 to T15. For proposed closures, ask the author; do not close another author's PR. ### Task 7: Tie out the EdgeZero PRs diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 8fa45b8b9..e9979f3a9 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -173,11 +173,11 @@ marks the record contract and geo as supported. load test at the target page-load rate with provider refreshes (A5 sets the rate) shows no throttling. State: #1536 puts every row under one partition key and updates one allocator item per insert. -- **AX-5 Published production image.** The published, attested image runs the - production server. Accept: `gh attestation verify` passes for the image, and - its entrypoint starts the production server with `STOPSIGNAL SIGTERM`. State: - #1543 publishes the dev-server image (`STOPSIGNAL SIGINT`); #1539 builds the - production image without publishing it. +- **AX-5 Production image.** An image built from source runs the production + server. Accept: its entrypoint starts the production server with + `STOPSIGNAL SIGTERM`. Publishing and attesting the image are outside this + design (DEC-19). State: #1539 builds the production image; #1543 publishes + the dev-server image (`STOPSIGNAL SIGINT`). - **AX-6 Reference infrastructure.** The reference stack wires the DynamoDB table, port 443 egress for vendor APIs, a readiness check that fails when a dependency is down, JSON logs, two or more tasks and an example rate-based @@ -545,6 +545,8 @@ part. serves pages that Trusted Server proxies; that install would also need the script served without the page proxy. - Conformance with the SharedID Edge specification. +- Binary attestation and package publishing (#1449, #1463, #1543). They + continue on their own track at lower priority (DEC-19). - An Akamai adapter. ## Decisions @@ -586,14 +588,18 @@ Decided: - **DEC-6 Base for the Axum line** (user, 2026-11-12). Forward-port the RC line to main rather than back-port the provider stack to it, which conflicts in 13 to 23 files. #1531 restacks on #1506 for A1 and #1532 on #1531 for - #1529. The image and Terraform units (#1539, #1538, #1540) restack on #1543, - which publishes the image. #1536 and #1537 are not restacked: their + #1529. The image and Terraform units (#1539, #1538, #1540) restack on #1531, + where they apply cleanly. #1536 and #1537 are not restacked: their Axum-only stores, store selection and wiring give way to EZI-2b, T6 and T21, and their tests move into A2, A8 and T5. #1535 goes into the EC route (T19, T20), and #1533 and #1534 close as copies of #1525 and #1526. - **DEC-18 JA4 when available** (user, 2026-11-12). ID-7 relies on a trusted JA4 where the adapter supplies one; without one, record creation is not blocked. JA4 is EdgeZero's abstraction (EZI-3), never read by Trusted Server from a header or a platform SDK. +- **DEC-19 Attestation and packaging excluded** (user, 2026-11-12). Binary + attestation and package publishing (#1449, #1463, #1543) are not part of + this work and carry lower priority. The MVP builds the production image + from source (AX-5) without publishing or attesting it. - **DEC-10 Mirror partners** (user, 2026-11-12). ID5 and LiveRamp, the important identity providers, get `mirror_enabled`, and the MVP's provider and mirror checks (T11, A10) use them. @@ -729,3 +735,7 @@ The user then decided DEC-18: ID-7 relies on a trusted JA4 where one exists and otherwise does not block, and JA4 is an EdgeZero abstraction. Axum's JA4 therefore needs the trusted proxy header that IABTechLab/edgezero#469's draft defers to its section 13.1, which the plan asks to ship in R2. + +The user then excluded attestation and packaging (DEC-19): AX-5 keeps only the +source-built production image, #1449 and #1543 leave the plan, and the image +and Terraform units restack on #1531 instead of #1543. From d2523f444a7e458f959f68321617cb0e5f046251 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 12:44:44 -0800 Subject: [PATCH 22/31] Address the round-4 review of the identity spec and plan Split EZI-2b into native DynamoDB and celld providers on Axum for the MVP and a portable EZI-2p after it, and make prefix listing strong within a partition. Split R2 into R2 (after-response work) and R2b (ClientInfo, JA4 and the new EZI-4a Axum geo binding), and make R1 to R4 tags on the protected integration branch. Rewrite the EC route as a same-origin POST that mints and a GET pixel that only re-sets an existing SharedID, with one call in flight. Add T22 for LiveRamp under DEC-9, keep Fastly's limiter and geo bridges in T21, and let A7 report degraded identity instead of failing readiness. Correct the stack heads, conflict counts and wave order, add Files and Interfaces to Tasks 3 to 8, qualify every gh call with --repo, and set the expected sub-issue counts to 45 and 32. --- ...11-11-server-side-identity-all-adapters.md | 439 ++++++++++-------- ...erver-side-identity-all-adapters-design.md | 426 ++++++++++------- 2 files changed, 502 insertions(+), 363 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 30231da56..4473f0d7c 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -84,10 +84,10 @@ Trusted Server: | #1557 | `76e1dd5f6` | main | None | | #1525 | `cd8977cad` | main | None; #1526 does not need it | | #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | -| #1516 | `214cd4821` | main | None | +| #1516 | `214cd4821` | main | Rebase onto main: `CHANGELOG.md` conflicts | | #1503 | `5e10c24e4` | main | None; needs neither #1506 nor new EdgeZero | | #1449 | `fd33f2c74` | main | Outside this plan (DEC-19) | -| #1506 | `6c39a56a1` | main | Rebase after #1557, #1526 and #1516; clear CodeQL alert 283 | +| #1506 | `9ec4711d3` | main | Rebase after #1557, #1526 and #1516 | | #1487 | `1600781eb` | main | Rebase after #1526 and #1506: 8 and 17 files conflict | | #1488 | `7f55776f3` | #1487's branch | Take #1566's two #1488 commits | | #1489 | `d3780ba13` | #1488's branch | None | @@ -97,7 +97,7 @@ Trusted Server: | #1543 | `e5e6ea7fa` | #1449's branch | Outside this plan (DEC-19) | | #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | | #1499 | `084fdbba9` | #1424's branch | Close at parity | -| #1531 | `88a6ba596` | #1506's branch | Retargeted without a rebase: it forks from main with its own cutover and the RC tag pin, and 13 files conflict with #1506. Rebase onto #1506; it then delivers A1 | +| #1531 | `88a6ba596` | #1506's branch | Retargeted without a rebase: it forks from main with its own cutover and the RC tag pin, and 15 files conflict with #1506 at `9ec4711d3`. Rebase onto #1506; it then delivers A1 | | #1532 | `852b875fe` | #1531's branch | Delivers #1529 (A4); restack after #1531 | | #1533, #1534 | `82d9a0e66`, `646c39a34` | #1532's and #1533's branches | RC-baseline copies of #1525 and #1526: drop from the chain and close | | #1536, #1535, #1537 | `296e05405`, `0666a9b4b`, `4a838272c` | #1534's, #1536's and #1535's branches | Not restacked: mostly Axum-only stores, store selection and wiring that EZI-2b, T6 and T21 replace. Their tests go to A2, A8 and T5 and #1535's code to T19 and T20; each closes when those land | @@ -113,11 +113,10 @@ EdgeZero: | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#409, IABTechLab/edgezero#414, IABTechLab/edgezero#422, IABTechLab/edgezero#439 | various | linear chain | Move IABTechLab/edgezero#414's `4ea99467` to IABTechLab/edgezero#406 and its `9a985c99` to IABTechLab/edgezero#407 | | IABTechLab/edgezero#463 | `6f9fa8bc` | IABTechLab/edgezero#422's branch | Drop the IABTechLab/edgezero#439 commits it carries | | IABTechLab/edgezero#464 | `6fe32531` | IABTechLab/edgezero#439's branch | Drop IABTechLab/edgezero#454's prototype commits | -| IABTechLab/edgezero#453 | `633f0552` | IABTechLab/edgezero#439's branch | Rebase off its stale base | -| IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452 | various | main | Land after IABTechLab/edgezero#275 merges; they conflict with it in 7, 6, 4 and 4 files | -| IABTechLab/edgezero#466 | `cb48c6b3` | main | Land after IABTechLab/edgezero#444; they conflict in `edgezero-cli` `config.rs` and `lib.rs` | +| IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452 | various | main | Land after IABTechLab/edgezero#275 merges; they conflict with it in 9, 6, 4 and 6 files, modify/delete included | +| IABTechLab/edgezero#466 | `214dd4fa` | main | Land after IABTechLab/edgezero#444; they conflict in `edgezero-cli` `config.rs` and `lib.rs` | | IABTechLab/edgezero#469 | `1d5bf2ff` | main | JA4 interface design for IABTechLab/edgezero#283; draft | -| IABTechLab/edgezero#287 | `ce25ef5a` | main | Last; shares 52 files with IABTechLab/edgezero#275, 27 of them under `crates/` | +| IABTechLab/edgezero#287 | `ce25ef5a` | main | Last; shares 51 files with IABTechLab/edgezero#275, 26 of them under `crates/` | ### Waves @@ -128,76 +127,77 @@ issue under its epic), PR fix (changes the PR needs), Merge, Release (a revision on EdgeZero's integration branch that Trusted Server pins, DEC-5), Close. -| Wave | Item | Pri | Kind | Action | Waits for | -| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | -| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | -| 0 | TS #1526, #1522 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | -| 0 | TS #1516 | P1 | Merge | Already approved | none | -| 0 | TS #1189 | P1 | Close | Fixed on main by `56a19229f` (#1157), which deleted the ungated `ingest_*` functions; ask its author to close it | none | -| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | -| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | -| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464, IABTechLab/edgezero#453 | P1 | PR fix | Rebase off the commits they carry | none | -| 0 | TS #1503, #1432 | P2 | Merge | `ts ec` commands; delivers #1432 | none | -| 0 | TS #1233 | P2 | Move | `ts-ec` to creative assets | none | -| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | -| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | -| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | -| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439; IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | -| 2 | EZ EZI-2a, EZI-3 | P1 | New | Record contract with Fastly; ClientInfo contract | IABTechLab/edgezero#275 | -| 2 | TS T5 | P1 | New | Conformance suite for Fastly and Axum | #1507 | -| 2 | TS T21 | P1 | New | One identity app in core for every adapter | T1, #1507 | -| 2 | TS T4, #1192 | P2 | New, Move | Shared state once per process: Axum (T4) and Fastly (#1192) | #1507 | -| 2 | TS #1556 | P2 | Move | Regression test that identity backends verify certificates, then close | #1507 | -| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | -| 3 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1487 | -| 3 | EZ R1 | P1 | Release | IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | wave 2's EdgeZero chain | -| 3 | EZ EZI-1 | P1 | New | After-response work for Fastly and Axum | IABTechLab/edgezero#275, IABTechLab/edgezero#406 | -| 3 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | -| 3 | IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | P1 | Move | Fastly and Axum ClientInfo; JA4 | EZI-3; IABTechLab/edgezero#397 | -| 3 | TS #1553, #1246 | P2 | Move | After-response CAS retry; Lockr IDs to KV | PR #1487 | -| 4 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1488 | -| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld adapters for every platform | EZI-2a, IABTechLab/edgezero#463, IABTechLab/edgezero#414 | -| 4 | EZ R2 | P1 | Release | Fastly and Axum parts of EZI-1 and EZI-3 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | -| 4 | TS A1 | P1 | New, PR fix | Rebase #1531 onto #1506, repin to R1; #1528 closes | R1, #1507 | -| 4 | TS T3 | P1 | New | SharedID cleanup | PR #1488 | -| 4 | TS #1555 | P2 | Move | Opt-out EIDs | PR #1488 | -| 4 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | -| 5 | EZ R1b | P1 | Release | EZI-2a and EZI-2b | EZI-2a, EZI-2b | -| 5 | TS #1529 (A4), A6, A3 | P1 | Move, New | CloudFront IP and geo via #1532; production image via #1539 and #1538; SIGTERM drain | A1 (A3 also R2) | -| 5 | TS T2 | P1 | New | EIDs in `user.eids`; after the provider stack, so open PRs need no test churn | #1489 merged | -| 5 | TS T12, T13 | P1 | New | Abuse protection; SharedID creation and write-back in the shared app | R2, T21 (T12); T3, T21, PR #1488 (T13) | -| 5 | TS T15 | P1 | New | Identity-only tsjs | PR #1489 | -| 5 | TS A9, #1141 | P2 | New, Move | `TRUSTED_SERVER__` guard; local config handoff | A1 | -| 5 | TS #1551 | P2 | Move | Pull sync on a full row | PR #1489 | -| 5 | TS #1527, #1528, #1523, #1533, #1534 | P2 | Close | Tests moved to #1506; A1 on #1531; RC copies of #1525 and #1526 | #1506, A1 | -| 6 | TS T6 | P1 | New | Shared record backend, Fastly first | R1b, #1522 | -| 6 | TS T11 | P1 | New | Providers awaited before finalization in the shared app | T21, R2, PR #1487 | -| 6 | TS A7 | P1 | New | Reference infrastructure via #1540, with an example rate rule | A1, A6, #1529 | -| 7 | TS A2 | P1 | New | Identity on Axum through the shared app, a new PR with #1536's and #1537's tests | T5, T6, T21, A1, #1529 | -| 7 | TS T19 | P1 | New | EC route in the shared app: mint and write back on a same-origin call; pixel and JSON responses | T11, T12, T13, T21 | -| 8 | TS A10, A8 | P1 | New | Identity features pass on Axum; SIGTERM test in CI | A2 plus their Part B blockers | -| 8 | TS T20 | P1 | New | Injected script calls the EC route on consent and merges the IDs into Prebid | T15, T19 | -| 8 | TS #1541, #1424, #1499 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | -| 9 | TS A5 | P1 | New | Load test | A2, A7, A10 | -| 9 | TS #1535, #1536, #1537 | P2 | Close | Their code and tests now in T19, T20, A2 and A8 | A2, A8, T19, T20 | -| 10 | EZ EZI-1c, EZI-2c, EZI-4c; IABTechLab/edgezero#91 | P2 | New, Move | Cloudflare parts in EdgeZero | Part B blockers; DEC-8 | -| 11 | EZ R3 | P2 | Release | Cloudflare integration point | wave 10 | -| 12 | TS #1518, T8 | P2 | Move, New | Geo source check; identity on Cloudflare through the shared app | R3 (T8 also T6, T1, T5, T21; DEC-8) | -| 13 | TS T17 | P2 | New | Identity features pass on Cloudflare | T8, R3, T11 to T13, T19 | -| 14 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s | P3 | New | Spin parts in EdgeZero | Part B blockers; DEC-8 | -| 15 | EZ R4; IABTechLab/edgezero#89 | P3 | Release, Move | Spin integration point; ClientInfo on every adapter | wave 14, R3 | -| 16 | TS T9 | P3 | New | Identity on Spin through the shared app | T8, R4; DEC-8, DEC-14 | -| 17 | TS T18 | P3 | New | Identity features pass on Spin | T9, R4, T17 | -| any | TS #1142 | P2 | Move | Health and startup contract on all four adapters; A7 carries Axum readiness | none | -| any | TS #1549 | P2 | Move | No `user.id` for a row-less EC | DEC-12; PR #1487 | -| any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP with PR IABTechLab/edgezero#453; IABTechLab/edgezero#287 last | DEC-13; DEC-9; PR IABTechLab/edgezero#453; IABTechLab/edgezero#275 | -| any | TS #1139, #1525 | P3 | Move, PR fix | Store inventory on all four adapters; HTTP auction endpoints, which #1526 does not need | none | -| any | EZ EZI-5, EZI-5c, EZI-5s; TS T10 | P3 | New | Rate limiting, punted past the MVP | DEC-15 | +| Wave | Item | Pri | Kind | Action | Waits for | +| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | +| 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | +| 0 | TS #1526, #1522 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | +| 0 | TS #1516 | P1 | Merge | Already approved | none | +| 0 | TS #1189 | P1 | Close | Fixed on main by `56a19229f` (#1157), which deleted the ungated `ingest_*` functions; ask its author to close it | none | +| 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | +| 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | +| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | PR fix | Rebase off the commits they carry | none | +| 0 | TS #1503, #1432 | P2 | Merge | `ts ec` commands; delivers #1432 | none | +| 0 | TS #1233 | P2 | Move | `ts-ec` to creative assets | none | +| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | +| 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | +| 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | +| 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439; IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | +| 2 | EZ EZI-2a, EZI-3 | P1 | New | Record contract with Fastly; ClientInfo contract | IABTechLab/edgezero#275 | +| 2 | TS T5 | P1 | New | Conformance suite for Fastly and Axum | #1507 | +| 2 | TS T4, #1192 | P2 | New, Move | Shared state once per process: Axum (T4) and Fastly (#1192) | #1507 | +| 2 | TS #1556 | P2 | Move | Regression test that identity backends verify certificates, then close | #1507 | +| 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | +| 3 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1487 | +| 3 | EZ R1 | P1 | Release | IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | wave 2's EdgeZero chain | +| 3 | EZ EZI-1 | P1 | New | After-response work for Fastly and Axum | IABTechLab/edgezero#275, IABTechLab/edgezero#406 | +| 3 | IABTechLab/edgezero#463 | P1 | Merge | Rebased | IABTechLab/edgezero#422, IABTechLab/edgezero#439 | +| 3 | EZ EZI-4a | P1 | New | Axum geo bound from EdgeZero's manifest | PR IABTechLab/edgezero#439 | +| 3 | IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | P1 | Move | Fastly and Axum ClientInfo; JA4 | EZI-3; IABTechLab/edgezero#397 | +| 3 | TS #1553, #1246 | P2 | Move | After-response CAS retry; Lockr IDs to KV | PR #1487 | +| 4 | TS #1489 | P1 | PR fix, Merge | Cap mirrored-ID expiry | #1488 | +| 4 | EZ EZI-2b | P1 | New | DynamoDB and celld providers on Axum | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | +| 4 | EZ R2, R2b | P1 | Release | R2: EZI-1 for Fastly and Axum; R2b: EZI-3, ClientInfo, JA4 and Axum's geo binding | EZI-1 (R2); EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283, EZI-4a (R2b) | +| 4 | TS A1 | P1 | New, PR fix | Rebase #1531 onto #1506, repin to R1; #1528 closes | R1, #1507 | +| 4 | TS T3 | P1 | New | SharedID cleanup | PR #1488 | +| 4 | TS #1555 | P2 | Move | Opt-out EIDs | PR #1488 | +| 4 | TS #1310, IABTechLab/trusted-server#90, IABTechLab/trusted-server#353 | P2 | Close | Superseded by #1488's ID5 provider | #1488 merged | +| 5 | EZ R1b | P1 | Release | EZI-2a and EZI-2b | EZI-2a, EZI-2b | +| 5 | TS #1529 (A4), A6, A3 | P1 | Move, New | CloudFront IP and geo via #1532; production image via #1539 and #1538; SIGTERM drain | A1 (A3 also R2; #1529 also R2b) | +| 5 | TS T2 | P1 | New | EIDs in `user.eids`; after the provider stack, so open PRs need no test churn | #1489 merged | +| 5 | TS T21 | P1 | New | One identity app in core for every adapter | T1, #1507, PR #1489 | +| 5 | TS T15 | P1 | New | Identity-only tsjs | PR #1489 | +| 5 | TS A9, #1141 | P2 | New, Move | `TRUSTED_SERVER__` guard; local config handoff | A1 | +| 5 | TS #1551 | P2 | Move | Pull sync on a full row | PR #1489 | +| 5 | TS #1527, #1528, #1523, #1533, #1534 | P2 | Close | Tests moved to #1506; A1 on #1531; RC copies of #1525 and #1526 | #1506, A1 | +| 6 | TS T6 | P1 | New | Shared record backend, Fastly first | R1b, #1522 | +| 6 | TS T11, T12, T13 | P1 | New | Providers checked with ID5; abuse protection; SharedID creation and write-back, all in the shared app | T21, R2 (T11 also PR #1487); R2b (T12); T3, PR #1488 (T13) | +| 7 | TS A2 | P1 | New | Identity on Axum through the shared app, a new PR with #1536's and #1537's tests | T5, T6, T21, A1, #1529 | +| 7 | TS T19 | P1 | New | EC route in the shared app: mint and write back on a same-origin call; pixel and JSON responses | T11, T12, T13, T21 | +| 8 | TS A10, A7, A8 | P1 | New | Identity features pass on Axum; reference infrastructure; SIGTERM test in CI | A2 plus their Part B blockers | +| 8 | TS T20 | P1 | New | Injected script calls the EC route on consent and merges the IDs into Prebid | T15, T19 | +| 9 | TS A5, T22 | P1 | New | Load test; LiveRamp per DEC-9 | A2, A7, A10 (T22: T11, A10, PR #1488, DEC-9) | +| 9 | TS #1535, #1536, #1537 | P2 | Close | Their code and tests now in T19, T20, A2 and A8 | A2, A8, T19, T20 | +| 9 | TS #1541, #1424, #1499 | P2 | Close | The RC line is on main | A1, A2, A4, A6, A7 | +| 10 | EZ EZI-2p, EZI-1c, EZI-4c; IABTechLab/edgezero#91 | P2 | New, Move | Portable DynamoDB and celld providers; Cloudflare parts in EdgeZero | Part B blockers | +| 11 | EZ EZI-2c | P2 | New | Cloudflare record store | EZI-2a, EZI-2p; DEC-8 | +| 12 | EZ R3 | P2 | Release | Cloudflare integration point | waves 10 and 11 | +| 13 | TS #1518, T8 | P2 | Move, New | Geo source check; identity on Cloudflare through the shared app | R3 (T8 also T6, T1, T5, T21; DEC-8) | +| 14 | TS T17 | P2 | New | Identity features pass on Cloudflare | T8, R3, T11 to T13, T19 | +| 15 | EZ EZI-1s, EZI-2s, EZI-3s, EZI-4s | P3 | New | Spin parts in EdgeZero | Part B blockers; DEC-8 for EZI-2s | +| 16 | EZ R4; IABTechLab/edgezero#89 | P3 | Release, Move | Spin integration point; ClientInfo on every adapter | wave 15, R3 | +| 17 | TS T9 | P3 | New | Identity on Spin through the shared app | T8, R4; DEC-8, DEC-14 | +| 18 | TS T18 | P3 | New | Identity features pass on Spin | T9, R4, T17 | +| any | TS #1142 | P2 | Move | Health and startup contract on all four adapters; A7 carries Axum's readiness signal | none | +| any | TS #1549 | P2 | Move | No `user.id` for a row-less EC | DEC-12; PR #1487 | +| any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP (no open PR); IABTechLab/edgezero#287 last | DEC-13; DEC-9; none; IABTechLab/edgezero#275 | +| any | TS #1139, #1525 | P3 | Move, PR fix | Store inventory on all four adapters; HTTP auction endpoints, which #1526 does not need | none | +| any | IABTechLab/edgezero#464 | P3 | Merge | Merge when its review closes; no MVP ticket needs it | IABTechLab/edgezero#439 | +| any | EZ EZI-5, EZI-5c, EZI-5s; TS T10 | P3 | New | Rate limiting, punted past the MVP | DEC-15 | Waves 0 to 9 are the MVP (DEC-2): they complete Fastly and Axum. None of them -waits on a Cloudflare or Spin item, so the MVP ships without waves 10 to 17 -(DEC-4). Waves 10 to 13 (Cloudflare), waves 14 to 17 (Spin) and the `any` rows +waits on a Cloudflare or Spin item, so the MVP ships without waves 10 to 18 +(DEC-4). Waves 10 to 14 (Cloudflare), waves 15 to 18 (Spin) and the `any` rows follow the MVP; an `any` row starts when its decision or reviewer is ready. Fallback for wave 1: if the fold-in has not landed two weeks after #1557, @@ -209,6 +209,8 @@ The two longest chains: - Axum identity: IABTechLab/edgezero#460, IABTechLab/edgezero#275, IABTechLab/edgezero#406 to IABTechLab/edgezero#439, IABTechLab/edgezero#463, EZI-2b, R1b, T6, A2, A10, A5. A1 runs beside it on R1, before the record contract lands. - SharedID: IABTechLab/edgezero#460, #1506, #1487, #1488, T3, T13 on Fastly, then A10 on Axum. +- JA4: IABTechLab/edgezero#275, EZI-3, IABTechLab/edgezero#283 (section 13.1 of IABTechLab/edgezero#469), R2b, T12, T19, A10. +- LiveRamp: T22 waits on DEC-9 as well as T11 and A10. ## Part B: tickets @@ -226,49 +228,53 @@ Constraints. ### EdgeZero, under IABTechLab/edgezero#468 The integration branch is itself an EdgeZero ask (DEC-5): it carries only -work headed for EdgeZero main, merged in Part A's order, and R1 to R4 are -revisions on it that Trusted Server pins. +work headed for EdgeZero main, merged in Part A's order. R1 creates it, +protected against force-push and deletion, and R1 to R4 are tags on it that +Trusted Server pins. New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| EZI-2a | `KvStore` record contract, the Fastly provider and the local in-memory store | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1b | -| EZI-2b | DynamoDB and celld `KvStore` adapters that run on every adapter over EdgeZero's outbound HTTP and meet the record contract, with DynamoDB writes spread across partition keys; verified on Fastly and Axum | EZI-2b | Feature | XL | P1 | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | R1b, EZI-2c, EZI-2s | -| EZI-3 | ClientInfo contract: portable client IP, geo and TLS metadata with explicit absence, the contract part of IABTechLab/edgezero#89 | EZI-3 | Feature | M | P1 | PR IABTechLab/edgezero#275 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, IABTechLab/edgezero#283, EZI-3s, IABTechLab/edgezero#89, R2 | -| EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PRs IABTechLab/edgezero#275, IABTechLab/edgezero#406 | EZI-1c, EZI-1s, R2 | -| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and an in-memory backend for Axum (after the MVP) | EZI-5 | Feature | S | P3 | PR IABTechLab/edgezero#275; DEC-15 | EZI-5c, EZI-5s, T10 | -| EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | -| EZI-2c | On Cloudflare, the DynamoDB and celld adapters pass the record contract, and Workers KV meets it or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a, EZI-2b; DEC-8 | R3 | -| EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | IABTechLab/edgezero#432 | R3 | -| EZI-5c | Rate limiter backend for Cloudflare (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | -| EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | -| EZI-2s | On Spin, the DynamoDB and celld adapters pass the record contract, and Spin's KV meets it or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a, EZI-2b; DEC-8 | R4 | -| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | EZI-3 | R4, IABTechLab/edgezero#89 | -| EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | -| EZI-5s | Rate limiter backend for Spin (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | -| R1 | Integration-branch revision with IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | -| R1b | Integration-branch revision with the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | -| R2 | Integration-branch revision with the Fastly and Axum parts of EZI-1 and EZI-3 | EZI-6 | Task | S | P1 | EZI-1, EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | A3, A10, T11, T12 | -| R3 | Integration-branch revision with the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | -| R4 | Integration-branch revision with the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, R3 | T9, T18 | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------- | ---- | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| EZI-2a | `KvStore` record contract with listing strong within a partition, the Fastly provider and the local persistent store (`PersistentKvStore`) | EZI-2a | Feature | L | P1 | PR IABTechLab/edgezero#275 | EZI-2b, EZI-2c, EZI-2s, R1b | +| EZI-2b | DynamoDB and celld providers on Axum meet the record contract, each key's partition being its DynamoDB partition key | EZI-2b | Feature | L | P1 | EZI-2a, IABTechLab/edgezero#410, IABTechLab/edgezero#437; PR IABTechLab/edgezero#463 | R1b, EZI-2p | +| EZI-2p | Portable DynamoDB and celld providers over EdgeZero's outbound HTTP, verified on Fastly, with the WASM credential source and a conditional-write latency budget stated | EZI-2p | Feature | XL | P2 | EZI-2b; PR IABTechLab/edgezero#275 | EZI-2c, EZI-2s | +| EZI-3 | ClientInfo contract: portable client IP and TLS metadata with explicit absence, the contract part of IABTechLab/edgezero#89 | EZI-3 | Feature | M | P1 | PR IABTechLab/edgezero#275 | IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, IABTechLab/edgezero#283, EZI-3s, IABTechLab/edgezero#89, R2b | +| EZI-1 | Bounded work after the response on Fastly and Axum, drained on Axum SIGTERM | EZI-1 | Feature | M | P1 | PRs IABTechLab/edgezero#275, IABTechLab/edgezero#406 | EZI-1c, EZI-1s, R2 | +| EZI-5 | Rate limiter: Fastly Edge Rate Limiting, and an in-memory backend for Axum (after the MVP) | EZI-5 | Feature | S | P3 | PR IABTechLab/edgezero#275; DEC-15 | EZI-5c, EZI-5s, T10 | +| EZI-1c | After-response work on Cloudflare (`waitUntil`) | EZI-1 | Feature | S | P2 | EZI-1 | R3 | +| EZI-2c | On Cloudflare, the portable DynamoDB and celld providers pass the record contract, and Workers KV meets it or reports `KvError::Unsupported` | EZI-2c | Feature | L | P2 | EZI-2a, EZI-2p; DEC-8 | R3 | +| EZI-4c | Geo with country and region through `ctx.geo()` on Cloudflare | EZI-4 | Feature | S | P2 | IABTechLab/edgezero#432 | R3 | +| EZI-5c | Rate limiter backend for Cloudflare (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | +| EZI-4a | Axum geo bound from EdgeZero's manifest, so `ctx.geo()` needs no application boot profile | EZI-4 | Feature | S | P1 | PR IABTechLab/edgezero#439 | R2b | +| EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | +| EZI-2s | On Spin, the portable DynamoDB and celld providers pass the record contract, and Spin's KV meets it or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a, EZI-2p; DEC-8 | R4 | +| EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | EZI-3 | R4, IABTechLab/edgezero#89 | +| EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | +| EZI-5s | Rate limiter backend for Spin (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | +| R1 | Create the protected integration branch and tag IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | +| R1b | Integration-branch tag with the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | +| R2 | Integration-branch tag with the Fastly and Axum parts of EZI-1 | EZI-6 | Task | S | P1 | EZI-1 | A3, A10, T11 | +| R2b | Integration-branch tag with EZI-3, the Fastly and Axum ClientInfo, the JA4 accessor and Axum's geo binding | EZI-6 | Task | S | P1 | EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#92, IABTechLab/edgezero#283, EZI-4a | A10, T12, #1529 | +| R3 | Integration-branch tag with the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | +| R4 | Integration-branch tag with the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, R3 | T9, T18 | Existing, moved under IABTechLab/edgezero#468. IABTechLab/edgezero#89 to IABTechLab/edgezero#92 leave IABTechLab/edgezero#88 and the last six leave IABTechLab/edgezero#391 or have no parent; both old epics stay where they are: -| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---- | --- | ------------------------------------------------------------------------------------- | --------------------------------------------------- | -| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | S | P3 | EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | -| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | EZI-3 | R2, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | EZI-3, IABTechLab/edgezero#397 | R2, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | EZI-3 | R3, IABTechLab/edgezero#89 | -| IABTechLab/edgezero#397 | Axum: safe production diagnostics and trusted-proxy handling (delivered by PR IABTechLab/edgezero#409) | Task (keep) | M | P1 | none | R1, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | -| IABTechLab/edgezero#410 | Native AWS configuration, secret and KV store providers (PR IABTechLab/edgezero#414) | Task (keep) | L | P1 | none | EZI-2b, R1 | -| IABTechLab/edgezero#437 | Native celld configuration and KV store providers (PR IABTechLab/edgezero#422) | Task (keep) | M | P1 | none | EZI-2b, R1 | -| IABTechLab/edgezero#432 | Axum: protected CloudFront Geo metadata provider (PR IABTechLab/edgezero#439) | Story (keep) | M | P1 | none | R1, EZI-4c, EZI-4s | -| IABTechLab/edgezero#438 | Axum: explicit local GeoIP provider (PR IABTechLab/edgezero#453) | Story (keep) | M | P3 | none | none | -| IABTechLab/edgezero#283 | Expose JA4 through `ctx.ja4()` (design in PR IABTechLab/edgezero#469); R2 needs Fastly's runtime JA4 and Axum's JA4 from a trusted proxy header (the design's section 13.1); Cloudflare and Spin follow | Task (keep) | M | P1 | EZI-3, IABTechLab/edgezero#397 | R2 | +| Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---- | --- | ------------------------------------------------------------------------------------- | --------------------------------------------------- | +| IABTechLab/edgezero#89 | As a developer I want to access client metadata in handlers (EZI-3) | Story (keep) | S | P3 | EZI-3, IABTechLab/edgezero#90, IABTechLab/edgezero#91, IABTechLab/edgezero#92, EZI-3s | none | +| IABTechLab/edgezero#90 | Fastly ClientInfo | Task (keep) | S | P1 | EZI-3 | R2b, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#92 | Axum ClientInfo | Task (keep) | S | P1 | EZI-3, IABTechLab/edgezero#397 | R2b, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#91 | Cloudflare ClientInfo | Task (keep) | S | P2 | EZI-3 | R3, IABTechLab/edgezero#89 | +| IABTechLab/edgezero#397 | Axum: safe production diagnostics and trusted-proxy handling (delivered by PR IABTechLab/edgezero#409) | Task (keep) | M | P1 | none | R1, IABTechLab/edgezero#92, IABTechLab/edgezero#283 | +| IABTechLab/edgezero#410 | Native AWS configuration, secret and KV store providers (PR IABTechLab/edgezero#414) | Task (keep) | L | P1 | none | EZI-2b, R1 | +| IABTechLab/edgezero#437 | Native celld configuration and KV store providers (PR IABTechLab/edgezero#422) | Task (keep) | M | P1 | none | EZI-2b, R1 | +| IABTechLab/edgezero#432 | Axum: protected CloudFront Geo metadata provider (PR IABTechLab/edgezero#439) | Story (keep) | M | P1 | none | R1, EZI-4c, EZI-4s | +| IABTechLab/edgezero#438 | Axum: explicit local GeoIP provider (no open PR) | Story (keep) | M | P3 | none | none | +| IABTechLab/edgezero#283 | Expose JA4 through `ctx.ja4()` (design in PR IABTechLab/edgezero#469); R2b needs Fastly's runtime JA4 and Axum's JA4 from a trusted proxy header (the design's section 13.1); IABTechLab/edgezero#91 carries Cloudflare's Bot Management JA4 and EZI-3s Spin's absence; closes with R2b | Task (keep) | M | P1 | EZI-3, IABTechLab/edgezero#397 | R2b | IABTechLab/edgezero#89 stays the umbrella story: EZI-3 carries its contract, and it completes with the last adapter's mapping. IABTechLab/edgezero#397, IABTechLab/edgezero#410 and IABTechLab/edgezero#432 leave @@ -278,23 +284,23 @@ IABTechLab/edgezero#391, which stays an epic in place; R1 is linked from it. New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------- | ---- | --- | ------------------------------ | -------------------------------- | -| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507 | A2, A3, A6, A7, A9, #1529, #1141 | -| A2 | Identity on Axum through T21's shared app: a new PR that takes #1536's and #1537's tests but not their Axum-only stores, store selection or wiring; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529 | A5, A8, A10 | -| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | -| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | -| A6 | Build the production image from source (#1539, #1538): its entrypoint starts the production server with `STOPSIGNAL SIGTERM`; no publishing or attestation | AX-5 | Feature | S | P1 | A1 | A7 | -| A7 | Reference infrastructure: DynamoDB, readiness, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded, and an example rate-based rule at CloudFront or the load balancer | AX-3, AX-6, ID-6, ID-7 | Feature | L | P1 | A1, A6, #1529 | A5, A10 | -| A8 | Run the SIGTERM-during-record-write test in CI (`scripts/test-native-ec-image.mjs` from #1536) | AX-1 | Task | S | P1 | A2 | none | -| A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | -| A10 | Identity features pass on Axum from the shared app: providers, abuse protection, SharedID write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P1 | A2, A7, R2, T11, T12, T13, T19 | A5 | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------- | ---- | --- | ------------------------------- | -------------------------------- | +| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507 | A2, A3, A6, A7, A9, #1529, #1141 | +| A2 | Identity on Axum through T21's shared app: a new PR that takes #1536's and #1537's tests but not their Axum-only stores, store selection or wiring; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529 | A5, A7, A8, A10 | +| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | +| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | +| A6 | Build the production image from source (#1539, #1538): its entrypoint starts the production server with `STOPSIGNAL SIGTERM`; no publishing or attestation | AX-5 | Feature | S | P1 | A1 | A7 | +| A7 | Reference infrastructure: DynamoDB, a readiness signal that reports degraded identity without failing the load balancer health check, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded, and an example rate-based rule at CloudFront or the load balancer | AX-3, AX-6, ID-6, ID-7 | Feature | L | P1 | A1, A2, A6, #1529 | A5 | +| A8 | Run the SIGTERM-during-record-write test in CI (`scripts/test-native-ec-image.mjs` from #1536) | AX-1 | Task | S | P1 | A2 | none | +| A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | +| A10 | Identity features pass on Axum from the shared app: providers checked with ID5, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route, including Safari's cookie lifetime | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P1 | A2, R2, R2b, T11, T12, T13, T19 | A5, T22 | Existing, moved under #1558: | Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | | ----- | ------------------------------------------------------------ | ----------- | ---- | --- | ---------- | ------ | -| #1529 | Extract the protected CloudFront native Geo integration (A4) | Task (keep) | M | P1 | A1 | A2, A7 | +| #1529 | Extract the protected CloudFront native Geo integration (A4) | Task (keep) | M | P1 | A1, R2b | A2, A7 | | #1141 | Make Axum consume the local config store | Bug (keep) | S | P2 | A1 | none | | #1142 | Uniform cross-adapter health and startup-failure contract | Task (keep) | M | P2 | none | none | | #1139 | Implement adapter stores or retire dead store manifests | Bug (keep) | M | P3 | none | none | @@ -303,8 +309,8 @@ A1 replaces the local configuration bridge: #1528 starts Axum from `EDGEZERO__ADAPTER__CONFIG_DIR` and never reads the old `TRUSTED_SERVER_CONFIG_*` names, so #1141 then points `ts config push --local` and `ts serve` at that directory; #1531 starts Axum the same way. #1142 and -#1139 cover all four adapters, so they sit outside the MVP; A7 carries Axum -readiness. #1566 overlaps A2 and A10; its Axum part is rebuilt on T21 (Part C). +#1139 cover all four adapters, so they sit outside the MVP; A7 carries Axum's +readiness signal. #1566 overlaps A2 and A10; its Axum part is rebuilt on T21 (Part C). T7, T14 and A11 are folded into A2 and A10, because the shared app (T21) brings partner APIs, the mirror cookie and the EC route to every adapter. @@ -312,27 +318,28 @@ brings partner APIs, the mirror cookie and the EC route to every adapter. New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------- | ---- | --- | -------------------------- | ------------------------------- | -| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | -| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | none | none | -| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | -| T4 | Build shared state once per process on Axum: settings, partner registry (built per request today), store clients; keep the per-request HTTP client unless measured. #1192 is the Fastly half | SH-1, SH-3 | Task | M | P2 | #1507 | none | -| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | -| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | -| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | -| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | -| T10 | Rate limits after the MVP: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P3 | EZI-5, T21; DEC-15 | none | -| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly | ID-4, ID-5, WB-1 | Feature | M | P1 | T21, R2; PR #1487 | A10, T17, T19 | -| T12 | In the shared app, refuse record creation when a trusted JA4 from `ctx.ja4()` and the User-Agent are not a browser's, and do not block when no trusted JA4 exists; delete the direct Fastly JA4 read; verified on Fastly | ID-7 | Feature | M | P1 | R2, T21 | A10, T17, T19 | -| T13 | SharedID in the shared app: create one when the reader has none, and write it back with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-5, SID-6 | Feature | M | P1 | T3, T21; PR #1488 | A10, T17, T19 | -| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | -| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | -| T17 | Identity features pass on Cloudflare from the shared app: providers, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P2 | T8, R3, T11, T12, T13, T19 | T18 | -| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-7, ID-9, WB-3, WB-4 | Feature | M | P3 | T9, R4, T17 | none | -| T19 | EC route in the shared app, verified on Fastly: `/_ts/api/v1/ec` mints, records, runs the providers and sets the identity cookies on a consented same-origin call, and on a withdrawal expires them and tombstones the record (reconciliation from #1535), with a pixel response for Prebid's `pixelUrl` and a JSON response with the IDs | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T11, T12, T13, T21 | A10, T17, T20 | -| T20 | The injected script calls the EC route when the CMP reports consent and on every later change, sending Sourcepoint's GPP string (capture from #1535), and merges the returned IDs into Prebid | ID-9, WB-6 | Feature | M | P1 | T15, T19 | none | -| T21 | One identity app in core for every adapter: identify, batch sync, pull sync, admin lookup, the EC route, the EC lifecycle, providers and write-back in one router each adapter mounts; adapter crates supply only platform services through EdgeZero | SH-5, ID-1, ID-3 | Feature | L | P1 | T1, #1507 | A2, T8, T10, T11, T12, T13, T19 | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------- | ---- | --- | -------------------------- | ------------------------------- | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | +| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | PR #1489 | none | +| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-2, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | +| T4 | Measure Axum's per-request HTTP client against a pooled one, and keep it per request unless the measurement shows the Next.js regression gone (SH-1's exception) | SH-1 | Task | S | P2 | #1507 | none | +| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | +| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | +| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | +| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | +| T10 | Rate limits after the MVP: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P3 | EZI-5, T21; DEC-15 | none | +| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly with ID5 | ID-4, ID-5, WB-1 | Feature | M | P1 | T21, R2; PR #1487 | A10, T17, T19, T22 | +| T12 | In the shared app, refuse record creation when a trusted JA4 from `ctx.ja4()` contradicts the User-Agent's browser family, allow an unknown or missing JA4, and own the family rule's data; delete the direct Fastly JA4 read; verified on Fastly | ID-7 | Feature | M | P1 | R2b, T21 | A10, T17, T19 | +| T13 | SharedID in the shared app: create one when the reader has none and `storage.type` is `cookie`, and write it back with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-2, SID-5, SID-6 | Feature | M | P1 | T3, T21; PR #1488 | A10, T17, T19 | +| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | +| T17 | Identity features pass on Cloudflare from the shared app: providers, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P2 | T8, R3, T11, T12, T13, T19 | T18 | +| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P3 | T9, R4, T17 | none | +| T19 | EC route in the shared app, verified on Fastly: a same-origin `POST` to `/_ts/api/v1/ec` mints, records, runs the providers, takes the posted consent and withdrawals, and answers with the mirror's view as JSON; a `GET` answers Prebid's `pixelUrl` with a 204 and only re-sets an existing SharedID (reconciliation from #1535) | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T11, T12, T13, T21 | A10, T17, T20 | +| T20 | The injected script calls the EC route with a same-origin `POST` when the CMP reports consent and on every later change, keeps one call in flight and sends the latest consent after it settles, sends the TCF string and the GPP string (Sourcepoint's capture from #1535), copies a localStorage `_pubcid_optout` into the cookie, and merges the returned IDs into Prebid | ID-9, WB-6, SID-2 | Feature | M | P1 | T15, T19 | none | +| T21 | One identity app in core for every adapter: mounts identify, batch sync, admin lookup and the EC route and runs the EC lifecycle in one router each adapter mounts, building its state once (SH-1); adapter crates supply platform services through EdgeZero where it offers them, and Fastly's limiter and geo stay their existing bridges until their asks land | SH-5, SH-1, ID-1, ID-3, ID-6 | Feature | L | P1 | T1, #1507; PR #1489 | A2, T8, T10, T11, T12, T13, T19 | +| T22 | LiveRamp per DEC-9: set the Custom ID, enable LiveRamp, and pass the provider and mirror checks with it on Fastly and Axum | ID-4, WB-4 | Feature | S | P1 | T11, A10; PR #1488; DEC-9 | none | Existing, moved under #1558: @@ -349,11 +356,11 @@ Existing, moved under #1558: | #1432 | `ts ec show` and `ts ec eids` (delivered by PR #1503) | Task (keep) | S | P2 | none | none | | #1245 | Spec for non-browser LiveRamp sync | Task | S | P3 | DEC-9 | none | | #1246 | Write Lockr IDs to the KV store | Task (keep) | M | P2 | PR #1487 | none | -| #1192 | Reduce Fastly adapter per-request fixed cost (the Fastly half of SH-1) | Task (keep) | M | P2 | #1507 | none | +| #1192 | Reduce Fastly adapter per-request fixed cost | Task (keep) | M | P2 | #1507 | none | Not moved: -- IABTechLab/trusted-server#90 is an epic, so it is linked from #1558, not moved; it and IABTechLab/trusted-server#353 close +- IABTechLab/trusted-server#90 is an epic, so Task 1 Step 3 links it from #1558 and it is not moved; it and IABTechLab/trusted-server#353 close when PR #1488 merges. - #1507 delivers the cutover and stays under #1505; it blocks A1, T4, T5, T21, #1192 and #1556. @@ -368,31 +375,28 @@ Not moved: Requirements met inside open PRs, not by a ticket: ID-8 on main and SH-2 (#1487), SID-1 to SID-3 and SID-6 for the provider, SID-5 (#1488), WB-4 -expiry (#1489), AX-7 (#1528). Part C lists them. +expiry (#1489). Part C lists them. ## Part C: fixes to existing PRs Each item names the head it was checked at. Post it as a comment on that PR -(Tasks 6 and 7); once posted, the PR thread is the source of truth. Since the -items were checked, #1506, #1487, #1488, #1489 and #1503 have -moved to the heads in Part A, so Task 6 re-checks each of their items first. +(Tasks 6 and 7); once posted, the PR thread is the source of truth. Task 6 re-checks each item at the PR's current head before posting. ### Trusted Server -- **#1506 at `6c39a56a1` (AX-7, SID-4).** Rebase after #1557, #1526 and #1516. - Clear CodeQL alert 283 (`rust/cleartext-logging`, - `test_support/scripted_client.rs:557`). Keep EdgeZero pinned to - IABTechLab/edgezero#275's head: an RC-tag pin pushed to this branch on +- **#1506 at `9ec4711d3` (AX-7).** Rebase after #1557, #1526 and #1516. Keep + EdgeZero pinned to IABTechLab/edgezero#275's head or an integration-branch tag + (DEC-5): an RC-tag pin pushed to this branch on 2026-11-12 was reverted. Repin after the fold-in, or under wave 1's fallback; drop the - `origin_readthrough_enabled` rejection (`config.rs:317`) once cache tags - return. The repin brings IABTechLab/edgezero `b97efec1`, which stops Spin - sending `Content-Length: 0` on bodiless DELETE and OPTIONS. Port #1527's + `origin_readthrough_enabled` rejection in `config.rs` once cache tags return. + The current pin already carries IABTechLab/edgezero `b97efec1`, which stops + Spin sending `Content-Length: 0` on bodiless DELETE and OPTIONS. Port #1527's native-client, response-owner and managed Fastly delivery tests if missing. - **#1526 at `e529e5576` (ID-2, SH-3).** Retarget to main. Address its round-1 review, including the finalize-under-`block_on` test gap. - **#1525 at `cd8977cad`.** Address its round-1 review. -- **#1487 at `1600781eb` (ID-6, ID-8, SH-2, SH-3, SID-4).** Every item from +- **#1487 at `1600781eb` (ID-8, SH-2, SH-3).** Every item from `4e7a6e02b` is still open. Rebase onto #1526 and #1506 (8 and 17 files conflict). Await lookup collection in the async finalize: `main.rs:699` calls `block_on` inside a function #1526 wraps in `block_on` @@ -409,7 +413,7 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. `Expiry::Never` (`coordinator/validate.rs:135-138`), pull sync is its only production source (`pull_sync.rs:145`), and such IDs reach the cookie with no `exp` under a one-year `Max-Age`. The 30-day clamp on stated expiries comes - from #1488. Answer the open thread on `any_due` skipping cookie-read + from #1488. Answer its open threads, including the one on `any_due` skipping cookie-read opt-outs. - **#1566 at `117a58e0e` (SID-1, SID-3, SID-5, ID-2, SH-5).** Move `0a2a00aaf` (SharedID name, source and retention) and `0c13d4272` (drops the @@ -418,18 +422,17 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. ID-2, DEC-3), no Trusted Server rate limiter (none on Axum, DEC-11), no User-Agent-only gate (EZI-3, ID-7), and no detached `block_on` task (EZI-1). Local development uses EdgeZero's local store (EZI-2a). It then serves A2 and A10. -- **#1488 at `7f55776f3` (SID-1, SID-3, SID-5, SID-6).** Take #1566's +- **#1488 at `7f55776f3` (SID-1, SID-2, SID-3, SID-5, SID-6).** Take #1566's `0a2a00aaf`, which reads the SharedID name from `managed_user_ids`, defaults the source to `pubcid.org` and keeps the stored value (still open here: `sharedid.rs:46-59`, `common.rs:94,110,129-136`). Take `0c13d4272`: the blanket `reject_providers_without_a_kind` (`config.rs:348`) makes `ts config validate`, `push` and `diff` reject every enabled provider. SID-6 holds for the provider path. LiveRamp's Custom ID waits on DEC-9, which gates - enabling LiveRamp, not this merge. Answer the open round-1 thread and the - four newer ones. + enabling LiveRamp, not this merge. Answer its open threads. - **#1531 at `88a6ba596` (AX-1, AX-2, AX-7), which carries A1 in place of #1528.** It now targets #1506's branch but forks from main with its own - cutover rebuild (`82d7e3be8`) and the RC tag pin, so 13 files conflict. + cutover rebuild (`82d7e3be8`) and the RC tag pin, so 15 files conflict with #1506 at `9ec4711d3`. Rebase onto #1506's head, drop that rebuild and the pin, and repin to R1 (DEC-5). Startup, stores and the client IP come from EdgeZero (IABTechLab/edgezero#406, IABTechLab/edgezero#414, @@ -483,7 +486,7 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. non-empty) and make Cloudflare reject tags instead of silently using the default. Fix the red Cloudflare wasm clippy; the fix exists only in RC commit `ebf622c2`. It removes `Copy` from an IABTechLab/edgezero#275 public - type, so it lands before that release. + type, so it lands before R1. - **IABTechLab/edgezero#406 at `6d1dda16`.** Take `run_production_app_with_options_and_initializer` from `4ea99467`; the Trusted Server RC's native startup calls it. @@ -492,16 +495,14 @@ moved to the heads in Part A, so Task 6 re-checks each of their items first. - **IABTechLab/edgezero#414 at `3733482f`.** Drop those two commits once they move down. - **IABTechLab/edgezero#469 at `1d5bf2ff`.** Bring section 13.1, JA4 from a trusted proxy - header recorded as `Ja4Source::TrustedProxyHeader`, into the work that ships - in R2. Axum's ID-7 needs JA4 from a hop that IABTechLab/edgezero#409 or IABTechLab/edgezero#439 checks, + header recorded as `Ja4Source::TrustedProxyHeader`, into the work that ships in R2b. Axum's ID-7 needs JA4 from a hop that IABTechLab/edgezero#409 or IABTechLab/edgezero#439 checks, such as CloudFront's `CloudFront-Viewer-JA4-Fingerprint`; an unchecked peer stays `NotExposed` (EZI-3, DEC-18). - **IABTechLab/edgezero#439 at `3a0e5eb4`.** Bind the geo provider from the manifest, so an application reads `ctx.geo()` without its own boot profile - (EZI-4, DEC-16); #1532 composes it from a Trusted Server profile today. -- **IABTechLab/edgezero#463 at `6f9fa8bc`, IABTechLab/edgezero#464 at - `6fe32531`, IABTechLab/edgezero#453 at `633f0552`.** Rebase as Part A's table - says. + (EZI-4a, DEC-16); #1532 composes it from a Trusted Server profile today. +- **IABTechLab/edgezero#463 at `6f9fa8bc` and IABTechLab/edgezero#464 at `6fe32531`.** Rebase as Part A's + table says. - **IABTechLab/edgezero#389 at `0653e6e3` and IABTechLab/edgezero#401 at `1a6ae861`.** Land after IABTechLab/edgezero#275 merges, redoing the RC-only merges `7c07b8e7` (IABTechLab/edgezero#389) and `2510ecc9` @@ -534,9 +535,10 @@ These belong to #1505 and IABTechLab/edgezero#436: **Interfaces:** -- Consumes: Part B's "Existing, moved" tables and "Linked, not moved" notes. +- Consumes: Part B's "Existing, moved" tables and "Not moved" notes. - Produces: 16 issues under #1558 and 10 under IABTechLab/edgezero#468, each typed, sized and - prioritized; link comments on IABTechLab/edgezero#468. + prioritized; the Related sections of IABTechLab/edgezero#468 and #1558 + updated; comments on IABTechLab/edgezero#391 and IABTechLab/edgezero#88. - [ ] **Step 1: Move each issue** @@ -546,7 +548,7 @@ The expected parent is IABTechLab/edgezero#88 for IABTechLab/edgezero#89 to IABTechLab/edgezero#92, IABTechLab/edgezero#391 for IABTechLab/edgezero#397, IABTechLab/edgezero#410 and IABTechLab/edgezero#432, and none for every other row. Stop and report a row whose parent differs. Then run -`gh api graphql -f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c,replaceParent:true}){issue{number}}}' -F p="$(gh issue view --json id --jq .id)" -F c="$(gh issue view --json id --jq .id)"`, +`gh api graphql -f query='mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c,replaceParent:true}){issue{number}}}' -F p="$(gh issue view --repo / --json id --jq .id)" -F c="$(gh issue view --repo / --json id --jq .id)"`, and read the parent again. Expected: the new epic's number, and every moved issue's parent is now that epic. @@ -563,8 +565,14 @@ Expected: each mutation returns the item. Edit IABTechLab/edgezero#468's Related section to say that IABTechLab/edgezero#391 stays the Axum production epic and that the six issues moved from it or with no -parent are tracked here. Post a `CREATOR:` comment on IABTechLab/edgezero#391 and on -IABTechLab/edgezero#88 naming the issues that moved to IABTechLab/edgezero#468. +parent are tracked here, and add IABTechLab/trusted-server#90 to #1558's +Related section, each with +`gh issue edit --repo / --body-file `. Post a +`CREATOR:` comment on IABTechLab/edgezero#391 and on IABTechLab/edgezero#88 +naming the issues that moved to IABTechLab/edgezero#468, with +`gh issue comment --repo IABTechLab/edgezero --body-file `. +Expected: each command prints the issue or comment URL, and +`gh issue view --repo /` shows the new Related text. ### Task 2: File the EdgeZero tickets under IABTechLab/edgezero#468 @@ -600,10 +608,20 @@ Expected: each mutation returns the issue or item. - [ ] **Step 4: Record the numbers** Post a `CREATOR:` comment on IABTechLab/edgezero#468 mapping each ID to its -issue, and one on IABTechLab/edgezero#391 linking R1. +issue, and one on IABTechLab/edgezero#391 linking R1, each with +`gh issue comment --repo IABTechLab/edgezero --body-file `. +Expected: two comment URLs. ### Task 3: File the Axum tickets under #1558 +**Files:** none; GitHub only. + +**Interfaces:** + +- Consumes: Part B's Axum table and the spec's requirements. +- Produces: an issue number per A row, recorded in a `CREATOR:` comment on + #1558, which Task 5 consumes. + Same steps as Task 2, against IABTechLab/trusted-server and #1558, for the nine new rows in table order. Each body has Summary (Ticket), Requirements (Reqs, linked to the spec section), Acceptance (each requirement's Accept @@ -611,12 +629,22 @@ line) and Blocked by. Record the map in a `CREATOR:` comment on #1558. ### Task 4: File the Trusted Server tickets under #1558 -Same steps as Task 3, for the T rows in table order (T1 to T21; T7 and T14 +**Files:** none; GitHub only. + +**Interfaces:** + +- Consumes: Part B's Trusted Server table and the spec's requirements. +- Produces: an issue number per T row, recorded in a `CREATOR:` comment on + #1558, which Task 5 consumes. + +Same steps as Task 3, for the T rows in table order (T1 to T22; T7 and T14 are folded into A2 and A10), under #1558. Record the map in a `CREATOR:` comment on #1558. ### Task 5: Link the dependencies +**Files:** none; GitHub only. + **Interfaces:** - Consumes: every "Blocked by" cell in Part B and the issue numbers from @@ -640,43 +668,78 @@ bodies. ### Task 6: Tie out the Trusted Server PRs +**Files:** none; GitHub only. + +**Interfaces:** + +- Consumes: Part C's Trusted Server items and the issue numbers from Tasks 1 + to 4. +- Produces: one `CREATOR:` comment per mapped PR. + - [ ] **Step 1: Check heads** For each Part C Trusted Server item, run -`gh pr view --json headRefOid --jq .headRefOid`. +`gh pr view --repo IABTechLab/trusted-server --json headRefOid --jq .headRefOid`. Expected: the head Part C names. If it moved, re-check the item against the new head before posting. - [ ] **Step 2: Post** -Post one `CREATOR:` comment per PR with its Part C item and the ticket it -serves (`Part of #`): #1532 to #1529, #1526 to #1522, #1503 to #1432, -#1506 to #1507, #1531 to A1, #1536, #1537 and #1566 to A2, #1535 to T19, #1539 and #1538 to A6, #1540 to A7, #1487 to T11, #1488 to T13 and #1489 to T15. For proposed closures, ask the author; do not close -another author's PR. +Post one `CREATOR:` comment per PR with its Part C item and its ticket, with +`gh pr comment --repo IABTechLab/trusted-server --body-file `. Use +`Part of #` for #1532 to #1529, #1526 to #1522, #1506 to #1507, #1531 +to A1, #1536, #1537 and #1566 to A2, #1535 to T19 and T20, #1539 and #1538 to +A6, and #1540 to A7. Use `Blocks #` for the provider stack: #1487 +blocks T11, #1488 blocks T3, T13 and T22, and #1489 blocks T15, T19 and A10. +#1525 and #1541 get their Part C item with no ticket; #1503 has no item. For +proposed closures, ask the author; do not close another author's PR. +Expected: one comment URL per PR. ### Task 7: Tie out the EdgeZero PRs -Same steps as Task 6, for the EdgeZero items in Part C, each linking its EZI -issue from Task 2. +**Files:** none; GitHub only. + +**Interfaces:** + +- Consumes: Part C's EdgeZero items and the issue numbers from Task 2. +- Produces: one `CREATOR:` comment per mapped PR. + +Same steps as Task 6, against IABTechLab/edgezero (`gh pr view --repo +IABTechLab/edgezero` and `gh pr comment --repo IABTechLab/edgezero`), for +the EdgeZero items in Part C. Link #406 to EZI-1, #463 to EZI-2b, #469 to +IABTechLab/edgezero#283, #439 to IABTechLab/edgezero#432 and EZI-4a, and #275, +#460 and the other chain fixes to R1. #389, #401, #444, #466 and #464 get their +Part C item with no ticket. ### Task 8: Verify coverage +**Files:** none; GitHub only. + +**Interfaces:** + +- Consumes: the results of Tasks 1 to 5. +- Produces: a pass, or a list of gaps posted as a `CREATOR:` comment on #1558. + - [ ] **Step 1: Count sub-issues** Run `gh api graphql -f query='{repository(owner:"IABTechLab",name:""){issue(number:){subIssues(first:100){totalCount}}}}' --jq '.data.repository.issue.subIssues.totalCount'` for #1558 and IABTechLab/edgezero#468. -Expected: 44 and 29. +Expected: 45 and 32. - [ ] **Step 2: Check that Fastly and Axum stand alone** For every issue in waves 0 to 9, read its blocked-by list and confirm that none of its blockers is in wave 10 or later or in an `any` row (a Cloudflare -or Spin ticket, IABTechLab/edgezero#91, R3, R4, #1139 or #1142). +or Spin ticket, IABTechLab/edgezero#91, R3, R4, EZI-2p, EZI-5, EZI-5c, EZI-5s, T10, T16, +#1139, #1142, #1245, #1549, IABTechLab/edgezero#438 or IABTechLab/edgezero#464), reading the list as Task 5 +Step 2 does. Expected: no such blocker (DEC-4). - [ ] **Step 3: Check bodies** -For every created or moved issue, read its body and confirm three things: +For every created or moved issue, read its body with +`gh issue view --repo / --json body --jq .body` and confirm +three things: each "Blocked by" names an issue, PR or decision; no Trusted Server body contains a bare `#N` below 1000 (EdgeZero references use `IABTechLab/edgezero#N`); and a created body holds only its requirement, diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index e9979f3a9..1aeaaeaee 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -29,7 +29,7 @@ cap. ## Relationship to other work - This is a program-level design across Trusted Server and EdgeZero. It is not - the design that #1236 asks for. The AX requirements feed #1236's tickets. + the design that #1236 asks for. #1236 keeps its own scope; A1 to A10 under #1558 deliver the AX requirements. #1236's own open choices (key rotation, backup and restore, which Fastly-only features stay unsupported on Axum) stay with #1236. - Each ticket's implementation PR carries its own design notes, and a plan @@ -75,7 +75,9 @@ EdgeZero main provides none of these for Axum: production startup, SIGTERM drain, trusted client IP, AWS config and secret stores, DynamoDB, geo, or concurrent outbound HTTP with deadlines. Open PRs IABTechLab/edgezero#275 through IABTechLab/edgezero#439 add them. No EdgeZero PR adds a portable -after-response hook, conditional KV writes, a portable client-IP accessor, geo +after-response hook, conditional writes on the `KvStore` trait +(IABTechLab/edgezero#463's celld store has them on a separate API), a portable +client-IP accessor, geo on Fastly, Cloudflare or Spin, or a rate limiter. EdgeZero main has no geo module; `ctx.geo()` exists only on the IABTechLab/edgezero#439 stack, and only Axum fills it there. @@ -106,7 +108,12 @@ and celld backends inside the Axum adapter. EdgeZero's Fastly store drops what the record needs: at EdgeZero main and at IABTechLab/edgezero#275's head its read calls `lookup` without the generation, `exists` is a read, and inserts carry no mode. So the Fastly backend can move onto EdgeZero only once EZI-2a -covers the Fastly provider. +covers the Fastly provider. IABTechLab/edgezero#463's `CelldRecordStore` has +the semantics on a separate API; IABTechLab/edgezero#414's DynamoDB provider +has strong existence and listing but no CAS and keeps every key in one +partition; IABTechLab/edgezero#410 and IABTechLab/edgezero#437 rule out +conditional writes today; and the `edgezero-store-aws` and +`edgezero-store-celld` crates compile only for native targets. The target is one `EcKvStore` implementation in core over EdgeZero's `KvStore` (DEC-3). The per-adapter backends are then deleted, and the DynamoDB @@ -118,17 +125,17 @@ Identity runs on an adapter only where the record contract and geo are supported. Elsewhere, startup rejects identity configuration (ID-2). Delivery follows DEC-4: Fastly, Axum, Cloudflare, Spin. -| Capability | Fastly | Axum | Cloudflare | Spin | -| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations through EZI-2a, or DynamoDB or celld through EZI-2b | Supported. DynamoDB or celld through EZI-2b | DynamoDB or celld through EZI-2b and EZI-2c. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | DynamoDB or celld through EZI-2b and EZI-2s. Spin's own `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing; its bundled `wasi:keyvalue/atomics` interface defines a CAS resource that the SDK does not wrap | -| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | -| Rate limiter | Supported (Edge Rate Limiting) | None in the MVP; the load balancer limits (DEC-11, DEC-15) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | -| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | -| TLS fingerprint (JA4) | Runtime TLS through EdgeZero's `ctx.ja4()` (EZI-3) | `ctx.ja4()` from a trusted proxy header such as CloudFront's (EZI-3) | `ctx.ja4()` from Bot Management, otherwise absent | Absent (`NotExposed`) | -| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | - -Cloudflare and Spin move to Supported only when EZI-2 and EZI-4 deliver for -them (DEC-8). Until then their startup rejects identity configuration, and +| Capability | Fastly | Axum | Cloudflare | Spin | +| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations through EZI-2a, or DynamoDB or celld through EZI-2b | Supported. DynamoDB or celld through EZI-2b | DynamoDB or celld through EZI-2b and EZI-2c. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | DynamoDB or celld through EZI-2b and EZI-2s. Spin's own `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing; the SDK exposes `wasi:keyvalue/atomics`, which defines a CAS resource, only as raw bindings in its doc-hidden `experimental` module | +| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | +| Rate limiter | Supported (Edge Rate Limiting) | None in the MVP; the load balancer limits (DEC-11, DEC-15) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | +| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | +| TLS fingerprint (JA4) | Runtime TLS through EdgeZero's `ctx.ja4()` (EZI-3) | `ctx.ja4()` from a trusted proxy header such as CloudFront's (EZI-3) | `ctx.ja4()` from Bot Management, otherwise absent | Absent (`NotExposed`) | +| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | + +Cloudflare and Spin move to Supported only when EZI-2c, EZI-2s, EZI-4c and +EZI-4s deliver for them (DEC-8 picks their store). Until then their startup rejects identity configuration, and Goal 2 is met for them by that rejection. ## Requirements @@ -169,32 +176,38 @@ marks the record contract and geo as supported. IPv6; the reference Terraform in #1540 trusts IPv4 ranges only and leaves geo off. - **AX-4 Record store fit for production.** Writes are spread across partition - keys, with no item every insert updates, and retries are bounded. Accept: a - load test at the target page-load rate with provider refreshes (A5 sets the - rate) shows no throttling. State: #1536 puts every row under one partition - key and updates one allocator item per insert. + keys by listing prefix: an EC row's partition is its IP hash, and a + withdrawal marker's is the marker prefix and its EC ID, so no item is updated + by every insert and listing stays strong within a partition (EZI-2a). + Retries are bounded. Accept: a load test at the target page-load rate with + provider refreshes (A5 sets the rate) shows no throttling, and rows from + different IP hashes land in different partitions. State: #1536 puts every + row under one partition key and updates one allocator item per insert, and + IABTechLab/edgezero#414's DynamoDB store keeps every key in one partition. - **AX-5 Production image.** An image built from source runs the production server. Accept: its entrypoint starts the production server with `STOPSIGNAL SIGTERM`. Publishing and attesting the image are outside this design (DEC-19). State: #1539 builds the production image; #1543 publishes the dev-server image (`STOPSIGNAL SIGINT`). - **AX-6 Reference infrastructure.** The reference stack wires the DynamoDB - table, port 443 egress for vendor APIs, a readiness check that fails when a - dependency is down, JSON logs, two or more tasks and an example rate-based - rule at CloudFront or the load balancer, since Axum's own limits are off by - default (DEC-11). IABTechLab/edgezero#275 serves - Axum on a current-thread runtime, so capacity comes from task count. Accept: - the stack applies with those properties, the rate rule refuses traffic over - its threshold, and readiness fails with DynamoDB unreachable. State: #1540 has the 443 egress (IPv4 only), a static `/health`, - plain logs, one task and no DynamoDB wiring. + table, port 443 egress for vendor APIs, a readiness signal, JSON logs, two or + more tasks and an example rate-based rule at CloudFront or the load balancer, + since Axum's own limits are off by default (DEC-11). A store outage only + degrades identity (Failure handling), so the load balancer health check + stays green while identity reports itself degraded. + IABTechLab/edgezero#275 serves Axum on a current-thread runtime, so capacity + comes from task count. Accept: the stack applies with those properties, the + rate rule refuses traffic over its threshold, and with DynamoDB unreachable + pages keep serving while identity reports degraded. State: #1540 has the 443 + egress (IPv4 only), a static `/health`, plain logs, one task and no DynamoDB + wiring; EdgeZero's readiness probe does not poll dependencies. - **AX-7 Ships from main.** Main never pins an EdgeZero RC tag - (IABTechLab/edgezero#431, IABTechLab/edgezero#462) or a revision that exists only on an RC - branch, such as `456313675`, which #1528 pins today. Main may pin - IABTechLab/edgezero#275's head under DEC-1, or a revision of EdgeZero's - integration branch under DEC-5, which carries only work headed for EdgeZero - main. Accept: `Cargo.lock` on main resolves every EdgeZero crate to a - released tag, IABTechLab/edgezero#275's head or an integration-branch - revision. + (IABTechLab/edgezero#431, IABTechLab/edgezero#462) or a revision that exists + only on an RC branch, such as `456313675`, which #1528 pins today. Main may + pin IABTechLab/edgezero#275's head under DEC-1, or a tag on EdgeZero's + protected integration branch under DEC-5, which carries only work headed for + EdgeZero main. Accept: `Cargo.lock` on main resolves every EdgeZero crate to + a released tag, IABTechLab/edgezero#275's head or an integration-branch tag. ### ID: Server-side identity on every adapter @@ -227,70 +240,85 @@ marks the record contract and geo as supported. deadline is skipped and does not delay the response past it. - **ID-5 After-response work.** Only pull sync and guarded follow-up writes run after the response is sent: after send on Fastly, a task drained on SIGTERM - on Axum, `waitUntil` on Cloudflare. Spin follows DEC-14. Accept: on Fastly, - Axum and Cloudflare a follow-up write recorded after send is in the record - before the next navigation, and on Axum SIGTERM waits for it (A3). On Spin - the DEC-14 choice holds: either the follow-up write lands before the - response, or the work is skipped and the response is unchanged. + on Axum, `waitUntil` on Cloudflare. On Spin the work runs before the response + (DEC-14; DEC-16 rules out skipping it). Accept: on Fastly, Axum and Cloudflare + a follow-up write recorded after send is in the record before the next + navigation, and on Axum SIGTERM waits for it (A3). On Spin the follow-up write + lands before the response. - **ID-6 Rate limiting (after the MVP, DEC-15).** In the MVP, Fastly keeps main's limits on batch sync and pull sync and #1487's per-provider lookup limit, and Axum has none because the deployer's load balancer limits - traffic (DEC-11). After the MVP, provider lookups, batch sync, pull sync and - record creation are rate limited on every supported adapter except Axum, - where every limit stays off by default; batch sync is keyed per partner and - the other keys follow DEC-15, and an Axum limit turned on in configuration - runs per task, divided by the configured task count. Accept: in the MVP, - Fastly's existing limits still refuse or skip over-limit requests and Axum's - default configuration applies no limit; after the MVP, per-adapter tests hit - each limit, and an Axum limit turned on for two tasks refuses at half the - limit. State: Fastly Edge Rate Limiting is the only limiter; record creation - is limited nowhere; #1487 keys lookups on the IP-derived EC prefix, so - everyone behind one IPv4 address or IPv6 /64 shares a budget. + traffic (DEC-11). Until EZI-5, Fastly's limiter stays the existing + `fastly::erl` bridge in the Fastly adapter, and T21 keeps it when it moves + the handlers into core. After the MVP, provider lookups, batch sync, pull + sync and record creation are rate limited on every supported adapter except + Axum, where every limit stays off by default; batch sync is keyed per partner + and the other keys follow DEC-15, and an Axum limit turned on in + configuration runs per task, divided by the configured task count. Accept: in + the MVP, Fastly's existing limits still refuse or skip over-limit requests + after T21 and Axum's default configuration applies no limit; after the MVP, + per-adapter tests hit each limit, and an Axum limit turned on for two tasks + refuses at half the limit. State: Fastly Edge Rate Limiting is the only + limiter; record creation is limited nowhere; #1487 keys lookups on the + IP-derived EC prefix, so everyone behind one IPv4 address or IPv6 /64 shares + a budget. - **ID-7 Abuse resistance.** Where a trusted JA4 is available, record creation - is refused for clients whose JA4 and User-Agent are not a browser's; where - none is available, record creation is not blocked on its account (DEC-18). - JA4 comes only from EdgeZero's `ctx.ja4()` (EZI-3). Accept: on each supported - adapter a scripted client with a browser User-Agent and a non-browser JA4 - creates no record, and a request with no trusted JA4 is not refused for - lacking one. State: - Fastly checks only that a JA4 exists plus a browser User-Agent platform; the - RC's Axum checks only the User-Agent, with request filters, because it has - no trusted JA4 (EZI-3); Cloudflare and Spin create no records yet. CloudFront - sends JA4 to the origin only in the `CloudFront-Viewer-JA4-Fingerprint` - header, which an origin request policy must forward. IABTechLab/edgezero#469's - draft design reports Axum as `NotExposed` and leaves the trusted proxy header - to its section 13.1. + is refused when the JA4's TLS client family contradicts the User-Agent's + browser family; a JA4 the rule does not recognize, or no trusted JA4 at all, + does not block (DEC-18). The family rule and its data live in core with T12, + which owns keeping them current; JA4 comes only from EdgeZero's `ctx.ja4()` + (EZI-3). Accept: on each supported adapter a scripted client with a browser + User-Agent and a non-browser JA4 creates no record, a current Chrome, Safari + and Firefox JA4 that no list names still creates one, and a request with no + trusted JA4 is not refused for lacking one. State: Fastly checks only that a + JA4 exists plus a browser User-Agent platform; main's known-browser table + (`ec/device.rs`) feeds analytics only and needs an HTTP/2 fingerprint that + Axum behind CloudFront lacks; the RC's Axum checks only the User-Agent, with + request filters, because it has no trusted JA4 (EZI-3); Cloudflare and Spin + create no records yet. CloudFront sends JA4 to the origin only in the + `CloudFront-Viewer-JA4-Fingerprint` header, which an origin request policy + must forward. IABTechLab/edgezero#469's draft design reports Axum as + `NotExposed` and leaves the trusted proxy header to its section 13.1. - **ID-8 Body EIDs keep being saved.** EIDs sent in the `/auction` body are saved to the record. Accept: on each supported adapter a body EID is in the record after the request. State: main saves them on Fastly; the provider stack and the RC's Axum lifecycle do not. -- **ID-9 EC route.** A same-origin route, `/_ts/api/v1/ec` by default, mints - the EC when the reader has none and consent allows, creates the record, runs - the providers as a navigation does, and sets `ts-ec`, the SharedID cookie and - the mirror cookie on its own response. The script Trusted Server injects - into proxied pages calls it once the CMP reports consent, so a reader who - consents after the page loads gets an EC on that page view instead of the - next navigation. A plain `GET` answers Prebid's SharedID `pixelUrl` with an - empty response; the script's call gets JSON with the SharedID and the EIDs - the record holds, which the script merges into Prebid. The script also calls - it on every later CMP consent change, sending Sourcepoint's GPP string where - the page uses Sourcepoint, so a withdrawal expires `ts-ec`, the SharedID - cookie and the mirror cookie and tombstones the record in that response - instead of on the next navigation. The route mints - nothing for a cross-site caller or a client ID-7 refuses, and every response - carries `Cache-Control: no-store`; a rate limit on it waits with ID-6 - (DEC-15). It is served from the page's own host, which - WebKit's cap on cookies from non-document responses exempts when the IP - matches the page's (read from WebKit's source, not yet tested in Safari). - Accept: on Fastly and Axum, a consented same-site call with no `ts-ec` +- **ID-9 EC route.** A same-origin route, `/_ts/api/v1/ec` by default, lets the + script Trusted Server injects into proxied pages report consent and receive + identity, so a reader who consents after the page loads gets an EC on that + page view instead of the next navigation. A `POST` whose `Origin` matches + the page mints the EC when the reader has none and consent allows, creates + the record, runs the providers as a navigation does, and sets `ts-ec`, the + SharedID cookie and the mirror cookie on its own response. It carries the + consent strings the script reads from the CMP, the TCF string from + `__tcfapi` and the GPP string from `__gpp` (Sourcepoint's capture from #1535 + where present), which replace the consent cookies for that request; a + withdrawal expires `ts-ec`, the SharedID cookie and the mirror cookie and + tombstones the record in that response. The `POST` answers with JSON holding + exactly what WB-4 would put in the mirror cookie for that request, and the + SharedID only under SID-2 and SID-6; the script merges it into Prebid, and + nothing else in the record reaches the page. A plain `GET` answers Prebid's + SharedID `pixelUrl` with an empty 204: it never mints, calls a provider or + changes consent, and only re-sets an existing SharedID cookie from the record + (WB-3). The script keeps at most one call in flight and sends the latest + consent after it settles, so a withdrawal always lands after any grant in + progress. Every response carries `Cache-Control: no-store`; a rate limit on + the route waits with ID-6 (DEC-15). The route is served from the page's own + host, which WebKit's cap on cookies from non-document responses exempts when + the IP matches the page's (read from WebKit's source; the Accept tests it). + Accept: on Fastly and Axum, a consented same-origin `POST` with no `ts-ec` creates the record and sets `ts-ec` and the SharedID cookie in that - response; the JSON call returns the record's IDs; a cross-site `Origin` and a - scripted client mint nothing; a withdrawal sent to it expires the - identity cookies and tombstones the record; an unchanged returning reader gets - no `Set-Cookie`. State: on main only a real-browser - navigation through the publisher proxy mints an EC, and no such route - exists. #1535 (RC line) posts Sourcepoint's GPP string to a separate - `/_ts/consent` route, wired into each adapter's router. + response; its JSON omits a non-mirrored partner's ID, an ID whose vendor's + TCF consent is denied and an expired ID; a withdrawal issued while a grant + call with no EC and delayed providers is in flight leaves no identity cookie + and no live record; a cross-site `Origin`, a `GET` and a client ID-7 + refuses mint nothing and call no provider; an unchanged returning reader gets + no `Set-Cookie`; and in Safari, `ts-ec` and the SharedID cookie set by the + route keep their one-year `Max-Age` on Fastly and on Axum behind CloudFront. + State: on main only a real-browser navigation through the publisher proxy + mints an EC, and no such route exists. #1535 (RC line) posts Sourcepoint's + GPP string to a separate, non-minting `/_ts/consent` route, wired into each + adapter's router. ### SH: Shared state passed the right way @@ -301,8 +329,8 @@ marks the record contract and geo as supported. until a measurement shows the Next.js regression gone; after that measurement Axum's client is held to the same rule. State: Cloudflare and Spin rebuild app state per request; Axum builds the partner registry per request - (`app.rs:515` at #1506's head) and the HTTP client per request on purpose, - because a pooled client regressed a Next.js flow (main `platform.rs:571-575`). + (`app.rs:495` at #1506's head `9ec4711d3`) and the HTTP client per request on purpose, + because a pooled client regressed a Next.js flow (main `platform.rs:620-623`). Keep that client choice unless a measurement shows the regression gone. - **SH-2 Per-request state in the request.** Per-request values travel in the request context, never in a `thread_local` or in retained app state. Accept: @@ -320,16 +348,19 @@ marks the record contract and geo as supported. request's `Env` in a set-once `thread_local`. Accept: two requests with different `Env` values each see their own. - **SH-5 One app for every adapter.** The identity routes (identify, batch - sync, pull sync, admin lookup and the EC route), the EC lifecycle, provider - lookups and cookie write-back run from one router built in core, which every - adapter mounts. An adapter crate supplies only platform services, and a - capability an adapter lacks is an EdgeZero ask, never code in that adapter - (DEC-16). Accept: no adapter crate's non-test code calls EC generation, - finalization or an identity handler directly, and the same conformance - suite (T5) runs on each supported adapter. State: at #1506's head only the - Fastly adapter wires identity (EC generation, finalization, identify and - batch sync are called from its `app.rs` and `main.rs`); Axum, Cloudflare and - Spin each build their own router without it. + sync, admin lookup and the EC route), pull sync, the EC lifecycle, provider + lookups and cookie write-back run from one app built in core, which every + adapter mounts and which builds its state once (SH-1). An adapter crate + supplies only platform services: through EdgeZero where it offers them, and + otherwise through the adapter's existing platform bridge until the EdgeZero + ask lands (today Fastly's limiter, ID-6, and Fastly's geo, EZI-4). A + capability an adapter lacks is an EdgeZero ask, never new code in that + adapter (DEC-16). Accept: no adapter crate's non-test code calls EC + generation, finalization or an identity handler directly, and the same + conformance suite (T5) runs on each supported adapter. State: at #1506's head + only the Fastly adapter wires identity (EC generation and finalization are + called from its `app.rs` and `main.rs`); Axum, Cloudflare and Spin each + build their own router without it. ### SID: SharedID handled the way Prebid defines it @@ -341,9 +372,12 @@ marks the record contract and geo as supported. not. State: main reads only `sharedId` (`constants.rs:10`), Prebid's module name, on Fastly for the record and on every adapter for `/_ts/admin/eids`. - **SID-2 Opt-out.** A `_pubcid_optout` cookie stops SharedID reads, storage, - sending and write-back. Prebid also reads the opt-out from localStorage, - which the server cannot see. Accept: with `_pubcid_optout` present nothing - SharedID-related is read, stored, sent or written. + sending, creation and write-back. Prebid also reads the opt-out from + localStorage, which the server cannot see, so the injected script copies a + localStorage `_pubcid_optout` into the cookie (T20). Accept: with + `_pubcid_optout` present nothing SharedID-related is read, stored, sent, + created or written, and a localStorage-only opt-out stops creation from the + next request on. - **SID-3 One canonical EID.** SharedID is stored and sent as source `pubcid.org`, atype 1, once. Accept: a bidder request built from cookie, `ts-eids`, body and record that carry the same UUID has exactly one EID @@ -390,14 +424,18 @@ marks the record contract and geo as supported. value. When neither the record nor the request has one, the server creates a SharedID in the format Prebid writes (a UUID v4), stores it in the record and sets it the same way, because a browser may block Prebid.js from loading - at all (DEC-7). It uses Prebid's storage name and the Domain Prebid writes with, is + at all (DEC-7). It creates and writes back a SharedID only when the + configured `storage.type` is `cookie` (Prebid's `cookie&html5` counts as + `cookie`): with `html5` Prebid never reads the cookie, so a server-made + SharedID would split the reader's identity (SID-3). It uses Prebid's storage name and the Domain Prebid writes with, is not HttpOnly, and is `Secure; SameSite=Lax` for one year. It keeps Prebid's `_cst` companion consistent, since a missing or different one makes Prebid rewrite the cookie from script, which Safari caps. Prebid's `extend` with a `pixelUrl` keeps working alongside it. Accept: record present and cookie absent gives both cookies with those attributes; a consented first visit with no SharedID anywhere gets one created, stored and set, and a - reader with `_pubcid_optout` gets none; an equal cookie + reader with `_pubcid_optout` gets none; with `storage.type = html5` the server + neither creates nor sets a SharedID; an equal cookie with a consistent `_cst` gives no `Set-Cookie`; an equal cookie with a missing or different `_cst` re-sets only the companion; Purpose 1 denied or `_pubcid_optout` expires both; Purpose 4 denied with Purpose 1 granted gives no `Set-Cookie` and leaves the @@ -426,33 +464,38 @@ marks the record contract and geo as supported. Behavior and acceptance only. EZI IDs are distinct from #1506's EZ-n requirement IDs. Each ask is split by platform, in DEC-4 order: the Fastly -and Axum parts ship in R1, R1b and R2 and never wait on a Cloudflare or Spin -part. +and Axum parts ship in R1, R1b, R2 and R2b, except EZI-2p and EZI-5, and never +wait on a Cloudflare or Spin part. - **EZI-1 After-response work.** A task registered during a request runs after the response is delivered, bounded in time. Accept: on Fastly and Axum the task runs after delivery, and on Axum SIGTERM waits for registered tasks up to the grace; later, on Cloudflare the task runs after delivery, and Spin returns a capability error. -- **EZI-2a Record contract, the Fastly provider and the local store.** `KvStore` gains a read - that returns a generation marker, writes in `Add`, `Overwrite` and - `IfGenerationMatch` modes with a precondition-failed outcome, and strongly - consistent existence and prefix listing, or `KvError::Unsupported`. Accept: - an EdgeZero contract suite passes against the Fastly provider under Viceroy - and against EdgeZero's local in-memory store, which the Axum development - server uses. -- **EZI-2b DynamoDB and celld adapters for every platform.** Both meet the - EZI-2a contract and run on every adapter over EdgeZero's outbound HTTP, while - the native stores (Fastly KV, Cloudflare KV, Spin KV) stay on their own - platform (DEC-3). DynamoDB writes are spread across partition keys with no - item every insert updates. Accept: the contract suite passes against both on - Fastly and Axum; EZI-2c and EZI-2s run it on Cloudflare and Spin. State: - the IABTechLab/edgezero#414 and IABTechLab/edgezero#422 providers are - native-only. IABTechLab/edgezero#463's `CelldRecordStore` has the semantics on a separate API; IABTechLab/edgezero#414's DynamoDB - provider has strong existence and listing but no CAS; - IABTechLab/edgezero#410 and IABTechLab/edgezero#437 rule out conditional writes today. +- **EZI-2a Record contract, the Fastly provider and the local store.** `KvStore` + gains a read that returns a generation marker, writes in `Add`, `Overwrite` + and `IfGenerationMatch` modes with a precondition-failed outcome, strongly + consistent existence, and prefix listing that is strongly consistent within + a partition: the caller declares how a key maps to its partition, and a + listing whose prefix is a whole partition reads strongly. A store that cannot + meet this reports `KvError::Unsupported`. Accept: an EdgeZero contract suite + passes against the Fastly provider under Viceroy and against EdgeZero's local + persistent store (`PersistentKvStore`), which the Axum development server + uses. +- **EZI-2b DynamoDB and celld providers on Axum.** Both meet the EZI-2a + contract on Axum. DynamoDB uses each key's partition as its partition key, so + writes spread across partitions and no item is updated by every insert + (AX-4). Accept: the contract suite passes against both on Axum, listing + within a partition is strong, and keys in different partitions land in + different DynamoDB partitions. +- **EZI-2p Portable DynamoDB and celld providers.** The same providers run on + every adapter over EdgeZero's outbound HTTP, while the native stores (Fastly + KV, Cloudflare KV, Spin KV) stay on their own platform (DEC-3). Each states + the credential source it uses on WASM targets and a latency budget for a + conditional write. Accept: the contract suite passes against both on Fastly; + EZI-2c and EZI-2s run it on Cloudflare and Spin. - **EZI-2c and EZI-2s Cloudflare and Spin stores.** On each platform the - DynamoDB and celld adapters pass the EZI-2a contract, and the native store + portable DynamoDB and celld providers (EZI-2p) pass the EZI-2a contract, and the native store meets it or reports `KvError::Unsupported`; DEC-8 picks the default. Accept: the contract suite passes for every store offered there, and a native store that cannot meet it reports `Unsupported`, so identity on it is rejected at @@ -466,25 +509,32 @@ part. trusts, and Spin never (`NotExposed`). Trusted Server reads JA4 only through it. Accept: the same accessors work on each adapter as its part lands, and a JA4 header from an untrusted peer reads as absent. -- **EZI-4 Geo with region on Cloudflare and Spin.** `ctx.geo()` gives country - and region there, or a capability error. Fastly keeps its existing geo and - Axum uses CloudFront geo from IABTechLab/edgezero#439, bound from EdgeZero's - manifest so an application reads `ctx.geo()` without its own boot profile. - Accept: a US request on Cloudflare reports country and region, and Axum's - geo loads with no Trusted Server geo setting. +- **EZI-4 Geo with region on every adapter.** `ctx.geo()` gives country and + region. Fastly keeps its existing geo. Axum uses CloudFront geo from + IABTechLab/edgezero#439, bound from EdgeZero's manifest so an application + reads `ctx.geo()` without its own boot profile (EZI-4a). Cloudflare reads its + request metadata (EZI-4c). Spin, whose requests carry no geo, reads a trusted + ingress header the platform sets or a KV- or API-backed provider (EZI-4s, as + #1518 describes for WASM targets). Accept: a US request on Cloudflare and on + Spin reports country and region, and Axum's geo loads with no Trusted Server + geo setting. - **EZI-5 Rate limiter (after the MVP, DEC-15).** A limiter with Fastly Edge Rate Limiting and an in-memory backend for Axum, then Cloudflare and Spin backends per DEC-15. Accept: a contract test passes per backend. -- **EZI-6 Integration points.** EdgeZero keeps an integration branch for this - work (DEC-5), and R1 to R4 are revisions on it that Trusted Server main pins - until EdgeZero releases them. R1 contains IABTechLab/edgezero#275 through - IABTechLab/edgezero#439, so Axum production serving does not wait for the - record contract. R1b contains EZI-2a and EZI-2b. R2 contains the Fastly and - Axum parts of EZI-1 and EZI-3. Together R1, R1b and R2 cover Fastly and Axum. - R3 contains the Cloudflare parts of EZI-1 to EZI-4, and R4 the Spin parts. - EZI-5 and its parts ship in a release after the MVP (DEC-15). Accept: each revision exists on the integration branch and Trusted Server - main pins it; R1, R1b and R2 contain no Cloudflare or Spin part. +- **EZI-6 Integration points.** EdgeZero keeps a protected integration branch + for this work (DEC-5), and each integration point is a tag on it that Trusted + Server main pins until EdgeZero releases the work. R1 creates the branch and + tags IABTechLab/edgezero#275 through IABTechLab/edgezero#439, so Axum + production serving does not wait for the record contract. R1b adds EZI-2a and + EZI-2b. R2 adds the Fastly and Axum parts of EZI-1. R2b adds EZI-3, + IABTechLab/edgezero#90, IABTechLab/edgezero#92, the JA4 accessor + (IABTechLab/edgezero#283) and Axum's geo binding (EZI-4a), so providers never + wait on JA4. Together R1, R1b, R2 and R2b cover Fastly and Axum. R3 adds the + Cloudflare parts of EZI-1 to EZI-4, and R4 the Spin parts. EZI-2p and EZI-5 + follow the MVP (DEC-15). Accept: each tag exists on the protected integration + branch and Trusted Server main pins it; R1, R1b, R2 and R2b contain no + Cloudflare or Spin part. ## Configuration @@ -556,7 +606,7 @@ Decided: - **DEC-1 Cutover** (user, 2026-11-11). #1506 is the EdgeZero cutover. #1527 closes once #1506 covers its tests. - **DEC-2 MVP** (user, 2026-11-12). Fastly and Axum are the MVP: plan waves 0 - to 9. Cloudflare, Spin and the decision-gated work follow it. + to 9. The one open decision the MVP waits on is DEC-9, through T22. Cloudflare, Spin and the decision-gated work follow it. - **DEC-3 Shared record store** (user, 2026-11-11). The EC record runs on one `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. The user reaffirmed it on 2026-11-12: Axum's record uses EdgeZero's pluggable @@ -565,33 +615,46 @@ Decided: adapter; the native stores (Fastly KV, Cloudflare KV, Spin KV) only to their own platform. - **DEC-4 Platform order** (user, 2026-11-12). Fastly, then Axum, then - Cloudflare, then Spin. Fastly and Axum (the MVP, DEC-2) must ship complete - without any Cloudflare or Spin work, so every ticket and release is split by - platform and no Fastly or Axum item is blocked by a Cloudflare or Spin item. - Cloudflare and Spin follow the MVP. -- **DEC-11 Axum rate limiting** (user, 2026-11-12). Every limit is off on - Axum by default, inbound and outbound, because deployers rate-limit at their - own load balancer; the reference stack shows one rule (AX-6). Once rate - limiting returns (DEC-15), a limit a deployer turns on keeps its own in-memory counter in each task and enforces - the limit divided by the configured task count. The MVP has no shared - counter. -- **DEC-7 SharedID retention and creation** (user, 2026-11-12). The stored - value wins and lives as long as the record (SID-5). When neither the record - nor the request has a SharedID, the server creates one, because a browser may - block Prebid.js from loading at all (WB-3). + Cloudflare, then Spin. Fastly and Axum (the MVP, DEC-2) must ship without any + Cloudflare or Spin work: no Fastly or Axum item is blocked by a Cloudflare or + Spin item. Shared work may touch every adapter (the shared app and the + rejection, 501 and absence paths in the plan's Global Constraints). Cloudflare + and Spin follow the MVP. - **DEC-5 EdgeZero integration branch** (user, 2026-11-12). EdgeZero keeps an integration branch that carries the unreleased work headed for EdgeZero main: IABTechLab/edgezero#275, the IABTechLab/edgezero#406 to IABTechLab/edgezero#439 chain and the EZI tickets. Trusted Server main pins - its revisions (R1, R1b, R2) instead of waiting for an EdgeZero release, and + its tags (R1, R1b, R2 and R2b in the MVP) instead of waiting for an EdgeZero release, and repins to a released tag once EdgeZero publishes one. - **DEC-6 Base for the Axum line** (user, 2026-11-12). Forward-port the RC - line to main rather than back-port the provider stack to it, which conflicts - in 13 to 23 files. #1531 restacks on #1506 for A1 and #1532 on #1531 for + line to main rather than back-port the provider stack to it, which conflicted + in 13 to 23 files at the heads first measured and 21 to 24 at the round-4 + heads. #1531 restacks on #1506 for A1 and #1532 on #1531 for #1529. The image and Terraform units (#1539, #1538, #1540) restack on #1531, where they apply cleanly. #1536 and #1537 are not restacked: their Axum-only stores, store selection and wiring give way to EZI-2b, T6 and T21, and their tests move into A2, A8 and T5. #1535 goes into the EC route (T19, T20), and #1533 and #1534 close as copies of #1525 and #1526. +- **DEC-7 SharedID creation and retention.** Creation (user, 2026-11-12): when + neither the record nor the request has a SharedID, the server creates one, + because a browser may block Prebid.js from loading at all (WB-3). Retention + (proposed, assumed by the plan): the stored value wins and lives as long as + the record (SID-5). +- **DEC-10 Mirror partners** (user, 2026-11-12). ID5 and LiveRamp, the + important identity providers, get `mirror_enabled`, and the MVP's provider + and mirror checks (T11, A10) use them. +- **DEC-11 Axum rate limiting** (user, 2026-11-12). Every limit is off on + Axum by default, inbound and outbound, because deployers rate-limit at their + own load balancer; the reference stack shows one rule (AX-6). Once rate + limiting returns (DEC-15), a limit a deployer turns on keeps its own in-memory counter in each task and enforces + the limit divided by the configured task count. No shared counter is + planned. +- **DEC-16 One implementation for every adapter** (user, 2026-11-12). Every + feature is built once and works on every adapter, delivered in DEC-4's + order. Shared logic lives in Trusted Server core; a platform capability an + adapter lacks is an EdgeZero ask, never code in one adapter crate (SH-5). + SH-5 records the existing platform bridges that stay until their asks land. +- **DEC-17 celld in the MVP** (user, 2026-11-12). EZI-2b delivers both the + DynamoDB and the celld provider before R1b. - **DEC-18 JA4 when available** (user, 2026-11-12). ID-7 relies on a trusted JA4 where the adapter supplies one; without one, record creation is not blocked. JA4 is EdgeZero's abstraction (EZI-3), never read by Trusted @@ -600,15 +663,6 @@ Decided: attestation and package publishing (#1449, #1463, #1543) are not part of this work and carry lower priority. The MVP builds the production image from source (AX-5) without publishing or attesting it. -- **DEC-10 Mirror partners** (user, 2026-11-12). ID5 and LiveRamp, the - important identity providers, get `mirror_enabled`, and the MVP's provider - and mirror checks (T11, A10) use them. -- **DEC-17 celld in the MVP** (user, 2026-11-12). EZI-2b delivers both the - DynamoDB and the celld provider before R1b. -- **DEC-16 One implementation for every adapter** (user, 2026-11-12). Every - feature is built once and works on every adapter, delivered in DEC-4's - order. Shared logic lives in Trusted Server core; a platform capability an - adapter lacks is an EdgeZero ask, never code in one adapter crate (SH-5). Open: @@ -617,14 +671,15 @@ Open: meets the contract, per [Platform support](#platform-support). Blocks EZI-2c, EZI-2s, R3, T8 and T9. - **DEC-9 LiveRamp Custom ID.** The current value is the IP-derived EC prefix, - shared by everyone behind one IP. Blocks enabling LiveRamp in #1488, and - #1245. LiveRamp is in the MVP (DEC-10), so this is needed before launch; - the user is getting a LiveRamp test setup to settle it. + shared by everyone behind one IP. Blocks T22, which sets the Custom ID, + enables LiveRamp and checks it before launch because LiveRamp is in the MVP + (DEC-10), and #1245. The user is getting a LiveRamp test setup to settle it. - **DEC-12 EC EID source.** Which source name the EC uses, and whether it keeps going out as `user.id`. Blocks #1549; no other ticket until decided. - **DEC-13 `ts-ec` refresh** for returning readers. Blocks T16. -- **DEC-14 Spin after-response work.** Run it before the response, or skip it. - Blocks T9. +- **DEC-14 Spin after-response work.** The work runs before the response, + because DEC-16 rules out skipping it. Open: the latency it may add and its + bound. Blocks T9. - **DEC-15 Rate limiting after the MVP** (user, 2026-11-12, deferred). Rate limiting is punted past the MVP: the keys (batch sync stays per partner), EdgeZero's limiter with its Cloudflare and Spin backends, and new limits on @@ -643,7 +698,8 @@ revisions, not from PR descriptions: - PR and issue state: `gh pr view` and `gh api graphql`, read on the dates in the status line. - The full CI gate list ran at this PR's first head, and the docs gates at - each later head; results are in the review threads. + each later head; results are in the review threads and the PR's head-move + comments. ## Review log @@ -663,8 +719,16 @@ the RC-unit baseline (15), ID-5's acceptance on Spin (18) and decision gates missing from plan cells (23). It added two should-fix findings: Task 1 must re-parent the EdgeZero issues it moves (35), and WB-3's no-write case must require a consistent `_cst` (36). No blocking finding. The revision after -it addresses all eight; round 3 is pending, and the PR stays in draft until a -round finds nothing blocking. +it addresses all eight. The PR stays in draft until a round leaves no open +finding. + +### Round 3, 2026-11-12 + +Review 5877498110 at `fbdfbedef` accepted and resolved threads 1, 7, 14, 15, +18, 23, 35 and 36, found nothing blocking and opened one should-fix, thread 37: +the plan's `gh issue view` and `gh pr view` calls did not name the repository. +The decision commits that followed (`ba106e554` to `c4417b7f8`) are recorded +below. ### Ticket audit, 2026-11-12 @@ -739,3 +803,15 @@ defers to its section 13.1, which the plan asks to ship in R2. The user then excluded attestation and packaging (DEC-19): AX-5 keeps only the source-built production image, #1449 and #1543 leave the plan, and the image and Terraform units restack on #1531 instead of #1543. + +### Round 4, 2026-11-12 + +Reviews 5881124556 and 5881450878 at `c4417b7f8` reviewed those decisions and +opened 66 threads; four of the second duplicate the first (the LiveRamp gate, +Fastly's limiter under T21, withdrawal ordering and the EC route's JSON). Three +were blocking: LiveRamp had no MVP ticket (T22 now checks it, gated by DEC-9), +the record contract asked DynamoDB for strong listing across partitions +(EZI-2a now lists strongly within a partition, and AX-4 partitions by listing +prefix), and the EC route's JSON exposed every EID (it now carries only the +mirror's view). The revision after it addresses every open thread, including +thread 37; each thread holds its disposition. From 4b82198fd9bf75c5e2b4ca3cba41489ca860dec1 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 12:50:12 -0800 Subject: [PATCH 23/31] Route non-Axum record stores through EZI-2p and reflow long lines The record-contract row and DEC-3 still sent Fastly, Cloudflare and Spin through EZI-2b, which is now Axum-only. Point them at the portable EZI-2p after the MVP. Keep the example rate rule at the load balancer, as chosen, and have A2 switch the production image's store features to EZI-2b's providers. Give Task 5's readback a command, and describe T4 as a measurement in wave 2. Reflow prose lines over 80 columns without changing any words. --- ...11-11-server-side-identity-all-adapters.md | 204 +++++++++--------- ...erver-side-identity-all-adapters-design.md | 204 +++++++++--------- 2 files changed, 213 insertions(+), 195 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 4473f0d7c..234f438e4 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -1,6 +1,9 @@ # Server-side identity on every adapter Implementation Plan -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> superpowers:subagent-driven-development (recommended) or +> superpowers:executing-plans to implement this plan task-by-task. Steps use +> checkbox (`- [ ]`) syntax for tracking. **Goal:** File the missing tickets, move the existing issues under the new epics (#1558 and IABTechLab/edgezero#468), link every dependency, and tie @@ -17,16 +20,16 @@ wave order, with its own plan when it needs one. blocked-by links, project fields) against IABTechLab/trusted-server and IABTechLab/edgezero. -**Spec:** -[server-side identity design](../specs/2026-11-11-server-side-identity-all-adapters-design.md) +**Spec:** [server-side identity +design](../specs/2026-11-11-server-side-identity-all-adapters-design.md) ## Global Constraints - One-account mode: every write acts as the PR author's login. - Platform order: Fastly, then Axum, then Cloudflare, then Spin (DEC-4). Fastly - and Axum ship without Cloudflare or Spin: no Fastly or Axum item is blocked - by a Cloudflare or Spin item, and Cloudflare and Spin work has its own tickets. - A Fastly or Axum ticket may still make Cloudflare and Spin reject identity + and Axum ship without Cloudflare or Spin: no Fastly or Axum item is blocked by + a Cloudflare or Spin item, and Cloudflare and Spin work has its own tickets. A + Fastly or Axum ticket may still make Cloudflare and Spin reject identity configuration, answer 501, report a capability as absent or mount the shared app (T1, T6, T21, IABTechLab/edgezero#283), because that needs no Cloudflare or Spin work. @@ -46,10 +49,10 @@ IABTechLab/edgezero. - EdgeZero issue bodies state behavior and acceptance only. - Issue types are set with `updateIssue`: Feature `IT_kwDOBPEB8s4A35yA`, Task `IT_kwDOBPEB8s4A35x7`, Bug `IT_kwDOBPEB8s4A35x9`. -- Project `PVT_kwDOBPEB8s4BFKrl`. Priority field `PVTSSF_lADOBPEB8s4BFKrlzg22nQg` - (P0 `c92c6358`, P1 `dca9fbdd`, P2 `afab7bc8`, P3 `71861fe7`). Size field - `PVTSSF_lADOBPEB8s4BFKrlzg22m2Q` (XS `771e90ab`, S `01b13689`, M `084dacdb`, - L `762f39e7`, XL `27016a48`). +- Project `PVT_kwDOBPEB8s4BFKrl`. Priority field + `PVTSSF_lADOBPEB8s4BFKrlzg22nQg` (P0 `c92c6358`, P1 `dca9fbdd`, P2 `afab7bc8`, + P3 `71861fe7`). Size field `PVTSSF_lADOBPEB8s4BFKrlzg22m2Q` (XS `771e90ab`, S + `01b13689`, M `084dacdb`, L `762f39e7`, XL `27016a48`). - Dependencies use `addBlockedBy(input:{issueId, blockingIssueId})`. GitHub links issues only, so a PR or a decision in "Blocked by" goes in the issue body. @@ -145,7 +148,7 @@ Close. | 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439; IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | | 2 | EZ EZI-2a, EZI-3 | P1 | New | Record contract with Fastly; ClientInfo contract | IABTechLab/edgezero#275 | | 2 | TS T5 | P1 | New | Conformance suite for Fastly and Axum | #1507 | -| 2 | TS T4, #1192 | P2 | New, Move | Shared state once per process: Axum (T4) and Fastly (#1192) | #1507 | +| 2 | TS T4, #1192 | P2 | New, Move | Measure Axum's per-request HTTP client (T4); Fastly per-request cost (#1192) | #1507 | | 2 | TS #1556 | P2 | Move | Regression test that identity backends verify certificates, then close | #1507 | | 2 | IABTechLab/edgezero#389, IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#452, then IABTechLab/edgezero#466 | P2 | PR fix | Rebase onto main and redo the RC-only merges | IABTechLab/edgezero#275 (IABTechLab/edgezero#466 also IABTechLab/edgezero#444) | | 3 | TS #1488 | P1 | PR fix, Merge | Part C fixes | #1487 | @@ -206,18 +209,22 @@ Fallback for wave 1: if the fold-in has not landed two weeks after #1557, The two longest chains: -- Axum identity: IABTechLab/edgezero#460, IABTechLab/edgezero#275, IABTechLab/edgezero#406 to IABTechLab/edgezero#439, IABTechLab/edgezero#463, EZI-2b, R1b, - T6, A2, A10, A5. A1 runs beside it on R1, before the record contract lands. -- SharedID: IABTechLab/edgezero#460, #1506, #1487, #1488, T3, T13 on Fastly, then A10 on Axum. -- JA4: IABTechLab/edgezero#275, EZI-3, IABTechLab/edgezero#283 (section 13.1 of IABTechLab/edgezero#469), R2b, T12, T19, A10. +- Axum identity: IABTechLab/edgezero#460, IABTechLab/edgezero#275, + IABTechLab/edgezero#406 to IABTechLab/edgezero#439, IABTechLab/edgezero#463, + EZI-2b, R1b, T6, A2, A10, A5. A1 runs beside it on R1, before the record + contract lands. +- SharedID: IABTechLab/edgezero#460, #1506, #1487, #1488, T3, T13 on Fastly, + then A10 on Axum. +- JA4: IABTechLab/edgezero#275, EZI-3, IABTechLab/edgezero#283 (section 13.1 of + IABTechLab/edgezero#469), R2b, T12, T19, A10. - LiveRamp: T22 waits on DEC-9 as well as T11 and A10. ## Part B: tickets -Every ticket in waves 0 to 9 is MVP: P1 is required for it, and P2 can slip -past its first release. The Cloudflare and Spin tickets (T8, T9, T17, T18, the -EZI `c` and `s` tickets, R3, R4, IABTechLab/edgezero#91, IABTechLab/edgezero#89 and #1518) and the `any` -rows are post-MVP. +Every ticket in waves 0 to 9 is MVP: P1 is required for it, and P2 can slip past +its first release. The Cloudflare and Spin tickets (T8, T9, T17, T18, the EZI +`c` and `s` tickets, R3, R4, IABTechLab/edgezero#91, IABTechLab/edgezero#89 and +#1518) and the `any` rows are post-MVP. "Blocked by" is set as a GitHub dependency between issues. A PR or a decision there goes in the issue body. "Blocks" is the inverse and is not set @@ -259,9 +266,9 @@ New: | R3 | Integration-branch tag with the Cloudflare parts | EZI-6 | Task | S | P2 | EZI-1c, EZI-2c, EZI-4c, IABTechLab/edgezero#91; DEC-8 | R4, T8, T17, #1518 | | R4 | Integration-branch tag with the Spin parts | EZI-6 | Task | S | P3 | EZI-1s, EZI-2s, EZI-3s, EZI-4s, R3 | T9, T18 | -Existing, moved under IABTechLab/edgezero#468. IABTechLab/edgezero#89 to IABTechLab/edgezero#92 leave -IABTechLab/edgezero#88 and the last six leave IABTechLab/edgezero#391 or have -no parent; both old epics stay where they are: +Existing, moved under IABTechLab/edgezero#468. IABTechLab/edgezero#89 to +IABTechLab/edgezero#92 leave IABTechLab/edgezero#88 and the last six leave +IABTechLab/edgezero#391 or have no parent; both old epics stay where they are: | Issue | Title | Type (set) | Size | Pri | Blocked by | Blocks | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---- | --- | ------------------------------------------------------------------------------------- | --------------------------------------------------- | @@ -276,25 +283,26 @@ no parent; both old epics stay where they are: | IABTechLab/edgezero#438 | Axum: explicit local GeoIP provider (no open PR) | Story (keep) | M | P3 | none | none | | IABTechLab/edgezero#283 | Expose JA4 through `ctx.ja4()` (design in PR IABTechLab/edgezero#469); R2b needs Fastly's runtime JA4 and Axum's JA4 from a trusted proxy header (the design's section 13.1); IABTechLab/edgezero#91 carries Cloudflare's Bot Management JA4 and EZI-3s Spin's absence; closes with R2b | Task (keep) | M | P1 | EZI-3, IABTechLab/edgezero#397 | R2b | -IABTechLab/edgezero#89 stays the umbrella story: EZI-3 carries its contract, and it completes -with the last adapter's mapping. IABTechLab/edgezero#397, IABTechLab/edgezero#410 and IABTechLab/edgezero#432 leave +IABTechLab/edgezero#89 stays the umbrella story: EZI-3 carries its contract, and +it completes with the last adapter's mapping. IABTechLab/edgezero#397, +IABTechLab/edgezero#410 and IABTechLab/edgezero#432 leave IABTechLab/edgezero#391, which stays an epic in place; R1 is linked from it. ### Axum deployable, under #1558 New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------- | ---- | --- | ------------------------------- | -------------------------------- | -| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507 | A2, A3, A6, A7, A9, #1529, #1141 | -| A2 | Identity on Axum through T21's shared app: a new PR that takes #1536's and #1537's tests but not their Axum-only stores, store selection or wiring; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529 | A5, A7, A8, A10 | -| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | -| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | -| A6 | Build the production image from source (#1539, #1538): its entrypoint starts the production server with `STOPSIGNAL SIGTERM`; no publishing or attestation | AX-5 | Feature | S | P1 | A1 | A7 | -| A7 | Reference infrastructure: DynamoDB, a readiness signal that reports degraded identity without failing the load balancer health check, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded, and an example rate-based rule at CloudFront or the load balancer | AX-3, AX-6, ID-6, ID-7 | Feature | L | P1 | A1, A2, A6, #1529 | A5 | -| A8 | Run the SIGTERM-during-record-write test in CI (`scripts/test-native-ec-image.mjs` from #1536) | AX-1 | Task | S | P1 | A2 | none | -| A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | -| A10 | Identity features pass on Axum from the shared app: providers checked with ID5, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route, including Safari's cookie lifetime | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P1 | A2, R2, R2b, T11, T12, T13, T19 | A5, T22 | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | ------- | ---- | --- | ------------------------------- | -------------------------------- | +| A1 | Serve Axum in production mode from main: rebase #1531 onto #1506 (#1528 closes), with startup, stores and client IP from EdgeZero; repin to R1 | AX-1, AX-2, AX-7 | Feature | L | P1 | R1, #1507 | A2, A3, A6, A7, A9, #1529, #1141 | +| A2 | Identity on Axum through T21's shared app: a new PR that takes #1536's and #1537's tests but not their Axum-only stores, store selection or wiring, and switches the production image's store features (`aws-dynamodb-ec`, `celld-ec` in #1539) to EZI-2b's providers; partner APIs included; passes T5's suite | ID-1, ID-2, ID-3, ID-8, WB-1, SH-5 | Feature | M | P1 | T5, T6, T21, A1, #1529 | A5, A7, A8, A10 | +| A3 | Drain after-response tasks on SIGTERM | AX-1, ID-5 | Feature | S | P1 | R2, A1 | none | +| A5 | Load-test the record store at the target page-load rate with provider refreshes | AX-4 | Task | M | P1 | A2, A7, A10 | none | +| A6 | Build the production image from source (#1539, #1538): its entrypoint starts the production server with `STOPSIGNAL SIGTERM`; no publishing or attestation | AX-5 | Feature | S | P1 | A1 | A7 | +| A7 | Reference infrastructure: DynamoDB, a readiness signal that reports degraded identity without failing the load balancer health check, JSON logs, two or more tasks, IPv6 CloudFront ranges, geo on, CloudFront's JA4 header forwarded, and an example rate-based rule at the load balancer | AX-3, AX-6, ID-6, ID-7 | Feature | L | P1 | A1, A2, A6, #1529 | A5 | +| A8 | Run the SIGTERM-during-record-write test in CI (`scripts/test-native-ec-image.mjs` from #1536) | AX-1 | Task | S | P1 | A2 | none | +| A9 | CI guard: runtime code reads only `TRUSTED_SERVER__` environment names | AX-2 | Task | XS | P2 | A1 | none | +| A10 | Identity features pass on Axum from the shared app: providers checked with ID5, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route, including Safari's cookie lifetime | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P1 | A2, R2, R2b, T11, T12, T13, T19 | A5, T22 | Existing, moved under #1558: @@ -310,9 +318,9 @@ A1 replaces the local configuration bridge: #1528 starts Axum from `TRUSTED_SERVER_CONFIG_*` names, so #1141 then points `ts config push --local` and `ts serve` at that directory; #1531 starts Axum the same way. #1142 and #1139 cover all four adapters, so they sit outside the MVP; A7 carries Axum's -readiness signal. #1566 overlaps A2 and A10; its Axum part is rebuilt on T21 (Part C). -T7, T14 and A11 are folded into A2 and A10, because the shared app (T21) -brings partner APIs, the mirror cookie and the EC route to every adapter. +readiness signal. #1566 overlaps A2 and A10; its Axum part is rebuilt on T21 +(Part C). T7, T14 and A11 are folded into A2 and A10, because the shared app +(T21) brings partner APIs, the mirror cookie and the EC route to every adapter. ### Trusted Server, under #1558 @@ -360,12 +368,13 @@ Existing, moved under #1558: Not moved: -- IABTechLab/trusted-server#90 is an epic, so Task 1 Step 3 links it from #1558 and it is not moved; it and IABTechLab/trusted-server#353 close - when PR #1488 merges. +- IABTechLab/trusted-server#90 is an epic, so Task 1 Step 3 links it from #1558 + and it is not moved; it and IABTechLab/trusted-server#353 close when PR #1488 + merges. - #1507 delivers the cutover and stays under #1505; it blocks A1, T4, T5, T21, #1192 and #1556. -- Proposed closures: #1523 with #1527; IABTechLab/trusted-server#353 and #1310 once PR #1488 merges; - #1189, which `56a19229f` (#1157) fixed on main. +- Proposed closures: #1523 with #1527; IABTechLab/trusted-server#353 and #1310 + once PR #1488 merges; #1189, which `56a19229f` (#1157) fixed on main. - #1556 needs only a regression test: #1506 rejects `proxy.certificate_check = false` and drops the setting from pull sync. - Out of these epics: #1484 (auction), #1140 (Cloudflare config), #1164 @@ -380,7 +389,8 @@ expiry (#1489). Part C lists them. ## Part C: fixes to existing PRs Each item names the head it was checked at. Post it as a comment on that PR -(Tasks 6 and 7); once posted, the PR thread is the source of truth. Task 6 re-checks each item at the PR's current head before posting. +(Tasks 6 and 7); once posted, the PR thread is the source of truth. Task 6 +re-checks each item at the PR's current head before posting. ### Trusted Server @@ -413,15 +423,15 @@ Each item names the head it was checked at. Post it as a comment on that PR `Expiry::Never` (`coordinator/validate.rs:135-138`), pull sync is its only production source (`pull_sync.rs:145`), and such IDs reach the cookie with no `exp` under a one-year `Max-Age`. The 30-day clamp on stated expiries comes - from #1488. Answer its open threads, including the one on `any_due` skipping cookie-read - opt-outs. -- **#1566 at `117a58e0e` (SID-1, SID-3, SID-5, ID-2, SH-5).** Move - `0a2a00aaf` (SharedID name, source and retention) and `0c13d4272` (drops the - blanket provider rejection) into #1488. Rebuild the Axum part on T21's shared - app and EdgeZero capabilities: no Axum-only `EcKvStore` (`ec_kv.rs:87`; - ID-2, DEC-3), no Trusted Server rate limiter (none on Axum, DEC-11), no User-Agent-only - gate (EZI-3, ID-7), and no detached `block_on` task (EZI-1). Local - development uses EdgeZero's local store (EZI-2a). It then serves A2 and A10. + from #1488. Answer its open threads, including the one on `any_due` skipping + cookie-read opt-outs. +- **#1566 at `117a58e0e` (SID-1, SID-3, SID-5, ID-2, SH-5).** Move `0a2a00aaf` + (SharedID name, source and retention) and `0c13d4272` (drops the blanket + provider rejection) into #1488. Rebuild the Axum part on T21's shared app and + EdgeZero capabilities: no Axum-only `EcKvStore` (`ec_kv.rs:87`; ID-2, DEC-3), + no Trusted Server rate limiter (none on Axum, DEC-11), no User-Agent-only gate + (EZI-3, ID-7), and no detached `block_on` task (EZI-1). Local development uses + EdgeZero's local store (EZI-2a). It then serves A2 and A10. - **#1488 at `7f55776f3` (SID-1, SID-2, SID-3, SID-5, SID-6).** Take #1566's `0a2a00aaf`, which reads the SharedID name from `managed_user_ids`, defaults the source to `pubcid.org` and keeps the stored value (still open here: @@ -432,42 +442,40 @@ Each item names the head it was checked at. Post it as a comment on that PR enabling LiveRamp, not this merge. Answer its open threads. - **#1531 at `88a6ba596` (AX-1, AX-2, AX-7), which carries A1 in place of #1528.** It now targets #1506's branch but forks from main with its own - cutover rebuild (`82d7e3be8`) and the RC tag pin, so 15 files conflict with #1506 at `9ec4711d3`. - Rebase onto #1506's head, drop that rebuild and the pin, and repin to R1 - (DEC-5). Startup, stores and the client IP come from EdgeZero - (IABTechLab/edgezero#406, IABTechLab/edgezero#414, - IABTechLab/edgezero#409). Carry #1528's open items where they apply: the CLI - test (`AxumDevServerConfig` lacks `ingress`), the type error, the CodeQL - alerts and the per-request `AtomicBool` in the Fastly `AppState`. + cutover rebuild (`82d7e3be8`) and the RC tag pin, so 15 files conflict with + #1506 at `9ec4711d3`. Rebase onto #1506's head, drop that rebuild and the pin, + and repin to R1 (DEC-5). Startup, stores and the client IP come from EdgeZero + (IABTechLab/edgezero#406, IABTechLab/edgezero#414, IABTechLab/edgezero#409). + Carry #1528's open items where they apply: the CLI test (`AxumDevServerConfig` + lacks `ingress`), the type error, the CodeQL alerts and the per-request + `AtomicBool` in the Fastly `AppState`. - **RC units, forward-ported (DEC-6).** #1531 goes on #1506 for A1 and #1532 on #1531 for #1529. #1539, #1538 and #1540 go on #1531 for A6 and A7, where they - apply cleanly. #1533 - and #1534 close as copies of #1525 and #1526. #1536 and #1537 are not - restacked: their Axum-only stores, store selection and wiring give way to - EZI-2b, T6 and T21, and their tests move into A2, A8 and T5. #1535 (Sourcepoint - GPP capture and server consent reconciliation) feeds T19 and T20: its - reconciliation runs in the EC route and its GPP capture in the injected + apply cleanly. #1533 and #1534 close as copies of #1525 and #1526. #1536 and + #1537 are not restacked: their Axum-only stores, store selection and wiring + give way to EZI-2b, T6 and T21, and their tests move into A2, A8 and T5. #1535 + (Sourcepoint GPP capture and server consent reconciliation) feeds T19 and T20: + its reconciliation runs in the EC route and its GPP capture in the injected script, in place of a separate `/_ts/consent` route wired into each adapter - (DEC-16). #1532 takes its geo provider from EdgeZero's manifest - once IABTechLab/edgezero#439 binds it, dropping `TS_NATIVE_GEO_PROFILE_FILE`. - The notes below were checked at the RC tag - `v0.1.1-rc-axum.202611.2` (`7c6e68d94`), which contains every unit. They are - forward-port notes: each applies to the PR that ports its unit to main, and - Task 6 posts it there once that PR exists, checked at its head. On the RC unit - PRs themselves Task 6 posts a note only after re-checking it at the unit's - head, read on 2026-11-12 as #1532 `852b875fe`, #1536 `296e05405`, #1537 - `4a838272c`, #1539 `8d41cb06c`, #1540 `eb6e19ef4` and #1541 `7dd2e3056`. - #1532: drop the core change that - maps a US request with no region to `Unknown`, which fails every US - Cloudflare request closed (AX-3), and show that the same trusted country - and region reach EC creation and auction consent on Axum. #1536: do not port the adapter-local + (DEC-16). #1532 takes its geo provider from EdgeZero's manifest once + IABTechLab/edgezero#439 binds it, dropping `TS_NATIVE_GEO_PROFILE_FILE`. The + notes below were checked at the RC tag `v0.1.1-rc-axum.202611.2` + (`7c6e68d94`), which contains every unit. They are forward-port notes: each + applies to the PR that ports its unit to main, and Task 6 posts it there once + that PR exists, checked at its head. On the RC unit PRs themselves Task 6 + posts a note only after re-checking it at the unit's head, read on 2026-11-12 + as #1532 `852b875fe`, #1536 `296e05405`, #1537 `4a838272c`, #1539 `8d41cb06c`, + #1540 `eb6e19ef4` and #1541 `7dd2e3056`. #1532: drop the core change that maps + a US request with no region to `Unknown`, which fails every US Cloudflare + request closed (AX-3), and show that the same trusted country and region reach + EC creation and auction consent on Axum. #1536: do not port the adapter-local DynamoDB store (ID-2, AX-4). #1537: do not port its Axum celld store or storage selection, which EdgeZero's providers and logical stores replace (EZI-2b, DEC-16); its example config also breaks startup on Axum, Cloudflare - and Spin. #1539: fix the failing Axum test. #1540: trust - IPv6 CloudFront ranges, turn geo on, wire DynamoDB and add an example - rate-based rule (AX-3, AX-6, DEC-11). All units: - replace `TS_*` names (AX-2). #1541: drop the commits of closed #1545. + and Spin. #1539: fix the failing Axum test. #1540: trust IPv6 CloudFront + ranges, turn geo on, wire DynamoDB and add an example rate-based rule (AX-3, + AX-6, DEC-11). All units: replace `TS_*` names (AX-2). #1541: drop the commits + of closed #1545. - **Proposed closures.** #1527 once its tests move (DEC-1); #1528 once #1531 carries A1; #1533 and #1534, the RC copies of #1525 and #1526; #1535, #1536 and #1537 once T19, T20, A2 and A8 carry their code and tests; #1310, which @@ -494,15 +502,17 @@ Each item names the head it was checked at. Post it as a comment on that PR `9a985c99`. - **IABTechLab/edgezero#414 at `3733482f`.** Drop those two commits once they move down. -- **IABTechLab/edgezero#469 at `1d5bf2ff`.** Bring section 13.1, JA4 from a trusted proxy - header recorded as `Ja4Source::TrustedProxyHeader`, into the work that ships in R2b. Axum's ID-7 needs JA4 from a hop that IABTechLab/edgezero#409 or IABTechLab/edgezero#439 checks, - such as CloudFront's `CloudFront-Viewer-JA4-Fingerprint`; an unchecked peer - stays `NotExposed` (EZI-3, DEC-18). +- **IABTechLab/edgezero#469 at `1d5bf2ff`.** Bring section 13.1, JA4 from a + trusted proxy header recorded as `Ja4Source::TrustedProxyHeader`, into the + work that ships in R2b. Axum's ID-7 needs JA4 from a hop that + IABTechLab/edgezero#409 or IABTechLab/edgezero#439 checks, such as + CloudFront's `CloudFront-Viewer-JA4-Fingerprint`; an unchecked peer stays + `NotExposed` (EZI-3, DEC-18). - **IABTechLab/edgezero#439 at `3a0e5eb4`.** Bind the geo provider from the manifest, so an application reads `ctx.geo()` without its own boot profile (EZI-4a, DEC-16); #1532 composes it from a Trusted Server profile today. -- **IABTechLab/edgezero#463 at `6f9fa8bc` and IABTechLab/edgezero#464 at `6fe32531`.** Rebase as Part A's - table says. +- **IABTechLab/edgezero#463 at `6f9fa8bc` and IABTechLab/edgezero#464 at + `6fe32531`.** Rebase as Part A's table says. - **IABTechLab/edgezero#389 at `0653e6e3` and IABTechLab/edgezero#401 at `1a6ae861`.** Land after IABTechLab/edgezero#275 merges, redoing the RC-only merges `7c07b8e7` (IABTechLab/edgezero#389) and `2510ecc9` @@ -536,9 +546,10 @@ These belong to #1505 and IABTechLab/edgezero#436: **Interfaces:** - Consumes: Part B's "Existing, moved" tables and "Not moved" notes. -- Produces: 16 issues under #1558 and 10 under IABTechLab/edgezero#468, each typed, sized and - prioritized; the Related sections of IABTechLab/edgezero#468 and #1558 - updated; comments on IABTechLab/edgezero#391 and IABTechLab/edgezero#88. +- Produces: 16 issues under #1558 and 10 under IABTechLab/edgezero#468, each + typed, sized and prioritized; the Related sections of IABTechLab/edgezero#468 + and #1558 updated; comments on IABTechLab/edgezero#391 and + IABTechLab/edgezero#88. - [ ] **Step 1: Move each issue** @@ -661,8 +672,9 @@ and list the pair in Step 2's readback. - [ ] **Step 2: Read back** -For each issue, read its blocked-by list through GraphQL and compare it with -Part B. +For each issue, run +`gh api graphql -f query='{repository(owner:"",name:""){issue(number:){blockedBy(first:50){nodes{number repository{nameWithOwner}}}}}}' --jq '.data.repository.issue.blockedBy.nodes'` +and compare the list with Part B. Expected: every issue pair present, plus the list of pairs recorded only in bodies. @@ -728,9 +740,9 @@ Expected: 45 and 32. - [ ] **Step 2: Check that Fastly and Axum stand alone** -For every issue in waves 0 to 9, read its blocked-by list and confirm that -none of its blockers is in wave 10 or later or in an `any` row (a Cloudflare -or Spin ticket, IABTechLab/edgezero#91, R3, R4, EZI-2p, EZI-5, EZI-5c, EZI-5s, T10, T16, +For every issue in waves 0 to 9, read its blocked-by list and confirm that none +of its blockers is in wave 10 or later or in an `any` row (a Cloudflare or Spin +ticket, IABTechLab/edgezero#91, R3, R4, EZI-2p, EZI-5, EZI-5c, EZI-5s, T10, T16, #1139, #1142, #1245, #1549, IABTechLab/edgezero#438 or IABTechLab/edgezero#464), reading the list as Task 5 Step 2 does. Expected: no such blocker (DEC-4). diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 1aeaaeaee..64e5fc89c 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -3,7 +3,8 @@ Status: draft, in review in #1559. Read at Trusted Server main `598a85170` and EdgeZero main `e11ea308` (crates at `v0.0.11`) on 2026-11-11 and 2026-11-12. Epics: #1558 (Trusted Server) and IABTechLab/edgezero#468 (EdgeZero). Plan: -[server-side identity plan](../plans/2026-11-11-server-side-identity-all-adapters.md). +[server-side identity +plan](../plans/2026-11-11-server-side-identity-all-adapters.md). ## Goals @@ -12,9 +13,9 @@ Epics: #1558 (Trusted Server) and IABTechLab/edgezero#468 (EdgeZero). Plan: 2. Server-side identity works on every adapter whose platform can meet the record contract in [Platform support](#platform-support), delivered in the order Fastly, Axum, Cloudflare, Spin (DEC-4). Fastly and Axum are the MVP - (DEC-2) and ship complete without any Cloudflare or Spin work. An adapter that cannot meet the - contract rejects identity configuration at startup with a clear error and - never runs identity without a record. + (DEC-2) and ship complete without any Cloudflare or Spin work. An adapter + that cannot meet the contract rejects identity configuration at startup with + a clear error and never runs identity without a record. 3. SharedID is read, stored and passed to bidders the way Prebid defines it, and shared state is passed through each adapter the right way. 4. Identity is written back as first-party cookies on the HTML document @@ -29,9 +30,10 @@ cap. ## Relationship to other work - This is a program-level design across Trusted Server and EdgeZero. It is not - the design that #1236 asks for. #1236 keeps its own scope; A1 to A10 under #1558 deliver the AX requirements. - #1236's own open choices (key rotation, backup and restore, which Fastly-only - features stay unsupported on Axum) stay with #1236. + the design that #1236 asks for. #1236 keeps its own scope; A1 to A10 under + #1558 deliver the AX requirements. #1236's own open choices (key rotation, + backup and restore, which Fastly-only features stay unsupported on Axum) stay + with #1236. - Each ticket's implementation PR carries its own design notes, and a plan when it needs one. This design fixes the requirements, the platform support matrix and the order. @@ -48,13 +50,13 @@ Two lines of code carry Axum today. store and no `ts-ec` cookie. They build a partner registry per request, for `/_ts/admin/eids` only. - **Axum RC line.** Tag `v0.1.1-rc-axum.202611.2` (`7c6e68d94`) was the head of - draft #1541 when read on 2026-11-11; #1541 has moved since. It adds production startup, the EC record in DynamoDB and - celld, CloudFront geo, a container image and AWS examples. It branches from - main at `2dd968aee` (2026-10-20), carries 74 commits that are not on main - and is 29 commits behind it. It pins EdgeZero tag - `v0.0.12-rc-axum.202611.1`, which is IABTechLab/edgezero#431 (marked DO NOT - MERGE). It has none of the provider stack: no server-side ID5 or LiveRamp, - no SharedID write-back and no mirror cookie. + draft #1541 when read on 2026-11-11; #1541 has moved since. It adds production + startup, the EC record in DynamoDB and celld, CloudFront geo, a container + image and AWS examples. It branches from main at `2dd968aee` (2026-10-20), + carries 74 commits that are not on main and is 29 commits behind it. It pins + EdgeZero tag `v0.0.12-rc-axum.202611.1`, which is IABTechLab/edgezero#431 + (marked DO NOT MERGE). It has none of the provider stack: no server-side ID5 + or LiveRamp, no SharedID write-back and no mirror cookie. Capability by adapter on main, and the open PR that adds each elsewhere: @@ -125,18 +127,18 @@ Identity runs on an adapter only where the record contract and geo are supported. Elsewhere, startup rejects identity configuration (ID-2). Delivery follows DEC-4: Fastly, Axum, Cloudflare, Spin. -| Capability | Fastly | Axum | Cloudflare | Spin | -| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations through EZI-2a, or DynamoDB or celld through EZI-2b | Supported. DynamoDB or celld through EZI-2b | DynamoDB or celld through EZI-2b and EZI-2c. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | DynamoDB or celld through EZI-2b and EZI-2s. Spin's own `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing; the SDK exposes `wasi:keyvalue/atomics`, which defines a CAS resource, only as raw bindings in its doc-hidden `experimental` module | -| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | -| Rate limiter | Supported (Edge Rate Limiting) | None in the MVP; the load balancer limits (DEC-11, DEC-15) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | -| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | -| TLS fingerprint (JA4) | Runtime TLS through EdgeZero's `ctx.ja4()` (EZI-3) | `ctx.ja4()` from a trusted proxy header such as CloudFront's (EZI-3) | `ctx.ja4()` from Bot Management, otherwise absent | Absent (`NotExposed`) | -| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | +| Capability | Fastly | Axum | Cloudflare | Spin | +| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations through EZI-2a; DynamoDB or celld through EZI-2p after the MVP | Supported. DynamoDB or celld through EZI-2b | DynamoDB or celld through EZI-2p and EZI-2c. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | DynamoDB or celld through EZI-2p and EZI-2s. Spin's own `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing; the SDK exposes `wasi:keyvalue/atomics`, which defines a CAS resource, only as raw bindings in its doc-hidden `experimental` module | +| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | +| Rate limiter | Supported (Edge Rate Limiting) | None in the MVP; the load balancer limits (DEC-11, DEC-15) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | +| Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | +| TLS fingerprint (JA4) | Runtime TLS through EdgeZero's `ctx.ja4()` (EZI-3) | `ctx.ja4()` from a trusted proxy header such as CloudFront's (EZI-3) | `ctx.ja4()` from Bot Management, otherwise absent | Absent (`NotExposed`) | +| Trusted client IP | Supported | IABTechLab/edgezero#409 | `cf-connecting-ip`; EZI-3 accessor | TCP peer; EZI-3 accessor | Cloudflare and Spin move to Supported only when EZI-2c, EZI-2s, EZI-4c and -EZI-4s deliver for them (DEC-8 picks their store). Until then their startup rejects identity configuration, and -Goal 2 is met for them by that rejection. +EZI-4s deliver for them (DEC-8 picks their store). Until then their startup +rejects identity configuration, and Goal 2 is met for them by that rejection. ## Requirements @@ -172,9 +174,9 @@ marks the record contract and geo as supported. geo, because an unknown country fails closed and issues no EC. Accept: an untrusted peer with forged forwarded and geo headers gets its own IP and no geo; a trusted IPv6 CloudFront hop is accepted; the reference deployment - applies with geo on. State: IABTechLab/edgezero#409, IABTechLab/edgezero#439 and #1532 accept - IPv6; the reference Terraform in #1540 trusts IPv4 ranges only and leaves - geo off. + applies with geo on. State: IABTechLab/edgezero#409, IABTechLab/edgezero#439 + and #1532 accept IPv6; the reference Terraform in #1540 trusts IPv4 ranges + only and leaves geo off. - **AX-4 Record store fit for production.** Writes are spread across partition keys by listing prefix: an EC row's partition is its IP hash, and a withdrawal marker's is the marker prefix and its EC ID, so no item is updated @@ -191,7 +193,7 @@ marks the record contract and geo as supported. the dev-server image (`STOPSIGNAL SIGINT`). - **AX-6 Reference infrastructure.** The reference stack wires the DynamoDB table, port 443 egress for vendor APIs, a readiness signal, JSON logs, two or - more tasks and an example rate-based rule at CloudFront or the load balancer, + more tasks and an example rate-based rule at the load balancer, since Axum's own limits are off by default (DEC-11). A store outage only degrades identity (Failure handling), so the load balancer health check stays green while identity reports itself degraded. @@ -212,12 +214,12 @@ marks the record contract and geo as supported. ### ID: Server-side identity on every adapter - **ID-1 One EC lifecycle.** On every supported adapter a consented document - navigation or EC route call (ID-9) mints the EC, creates the record and sets `ts-ec` together. A - cookie is never issued without a record. An existing EC is kept when geo is - unknown. Accept: the conformance suite (T5) asserts on each supported - adapter that a consented navigation creates the record and sets `ts-ec` in - the same response, that a store failure sets no `ts-ec`, and that an - existing EC survives unknown geo; on an unsupported adapter, startup with + navigation or EC route call (ID-9) mints the EC, creates the record and sets + `ts-ec` together. A cookie is never issued without a record. An existing EC is + kept when geo is unknown. Accept: the conformance suite (T5) asserts on each + supported adapter that a consented navigation creates the record and sets + `ts-ec` in the same response, that a store failure sets no `ts-ec`, and that + an existing EC survives unknown geo; on an unsupported adapter, startup with identity configuration fails. - **ID-2 One shared record store.** The identity logic stays in core and becomes awaitable (#1526). It runs on one `EcKvStore` over EdgeZero's @@ -326,10 +328,10 @@ marks the record contract and geo as supported. HTTP client are built once per process or isolate. Accept: an adapter test shows settings, the partner registry and store clients built once across two requests, and the HTTP client too, except Axum's, which stays per request - until a measurement shows the Next.js regression gone; after that - measurement Axum's client is held to the same rule. State: Cloudflare and Spin - rebuild app state per request; Axum builds the partner registry per request - (`app.rs:495` at #1506's head `9ec4711d3`) and the HTTP client per request on purpose, + until a measurement shows the Next.js regression gone; after that measurement + Axum's client is held to the same rule. State: Cloudflare and Spin rebuild app + state per request; Axum builds the partner registry per request (`app.rs:495` + at #1506's head `9ec4711d3`) and the HTTP client per request on purpose, because a pooled client regressed a Next.js flow (main `platform.rs:620-623`). Keep that client choice unless a measurement shows the regression gone. - **SH-2 Per-request state in the request.** Per-request values travel in the @@ -412,34 +414,35 @@ marks the record contract and geo as supported. - **WB-1 Document and EC route responses only.** Identity cookies are set on top-level HTML document responses and on EC route responses (ID-9), on every supported adapter. Accept: identity `Set-Cookie` headers appear on navigation - and EC route responses and never on asset or other API responses. State: main mints `ts-ec` on Fastly navigations, and the RC - adds Axum. + and EC route responses and never on asset or other API responses. State: main + mints `ts-ec` on Fastly navigations, and the RC adds Axum. - **WB-2 `ts-ec` lifetime.** `ts-ec` keeps `Domain=.; Path=/; Secure; SameSite=Lax; HttpOnly; Max-Age=31536000`. Whether returning readers get a refresh is DEC-13. Accept: once DEC-13 is decided, its rule holds and a returning reader whose cookie needs no refresh gets no `Set-Cookie`. State: set only for a new or rotated EC. - **WB-3 SharedID cookie.** When SID-2 and SID-6 allow it, the server sets the - SharedID cookie from the record if the request has none or a different - value. When neither the record nor the request has one, the server creates - a SharedID in the format Prebid writes (a UUID v4), stores it in the record - and sets it the same way, because a browser may block Prebid.js from loading - at all (DEC-7). It creates and writes back a SharedID only when the - configured `storage.type` is `cookie` (Prebid's `cookie&html5` counts as - `cookie`): with `html5` Prebid never reads the cookie, so a server-made - SharedID would split the reader's identity (SID-3). It uses Prebid's storage name and the Domain Prebid writes with, is - not HttpOnly, and is `Secure; SameSite=Lax` for one year. It keeps Prebid's - `_cst` companion consistent, since a missing or different one makes - Prebid rewrite the cookie from script, which Safari caps. Prebid's `extend` - with a `pixelUrl` keeps working alongside it. Accept: record present and - cookie absent gives both cookies with those attributes; a consented first - visit with no SharedID anywhere gets one created, stored and set, and a - reader with `_pubcid_optout` gets none; with `storage.type = html5` the server - neither creates nor sets a SharedID; an equal cookie - with a consistent `_cst` gives no `Set-Cookie`; an equal cookie with a - missing or different `_cst` re-sets only the companion; Purpose 1 denied or `_pubcid_optout` expires both; - Purpose 4 denied with Purpose 1 granted gives no `Set-Cookie` and leaves the - browser's cookie alone. State: no branch writes it. + SharedID cookie from the record if the request has none or a different value. + When neither the record nor the request has one, the server creates a SharedID + in the format Prebid writes (a UUID v4), stores it in the record and sets it + the same way, because a browser may block Prebid.js from loading at all + (DEC-7). It creates and writes back a SharedID only when the configured + `storage.type` is `cookie` (Prebid's `cookie&html5` counts as `cookie`): with + `html5` Prebid never reads the cookie, so a server-made SharedID would split + the reader's identity (SID-3). It uses Prebid's storage name and the Domain + Prebid writes with, is not HttpOnly, and is `Secure; SameSite=Lax` for one + year. It keeps Prebid's `_cst` companion consistent, since a missing or + different one makes Prebid rewrite the cookie from script, which Safari caps. + Prebid's `extend` with a `pixelUrl` keeps working alongside it. Accept: record + present and cookie absent gives both cookies with those attributes; a + consented first visit with no SharedID anywhere gets one created, stored and + set, and a reader with `_pubcid_optout` gets none; with `storage.type = html5` + the server neither creates nor sets a SharedID; an equal cookie with a + consistent `_cst` gives no `Set-Cookie`; an equal cookie with a missing + or different `_cst` re-sets only the companion; Purpose 1 denied or + `_pubcid_optout` expires both; Purpose 4 denied with Purpose 1 granted gives + no `Set-Cookie` and leaves the browser's cookie alone. State: no branch writes + it. - **WB-4 Mirror cookie.** `__Host-ts-eids-mirror` as in #1489: opt-in per partner (DEC-10), dropped per vendor when TCF consent is denied, needs `bidstream_enabled` and Purpose 4, 3,584-byte budget, host-only. Every @@ -495,20 +498,21 @@ wait on a Cloudflare or Spin part. conditional write. Accept: the contract suite passes against both on Fastly; EZI-2c and EZI-2s run it on Cloudflare and Spin. - **EZI-2c and EZI-2s Cloudflare and Spin stores.** On each platform the - portable DynamoDB and celld providers (EZI-2p) pass the EZI-2a contract, and the native store - meets it or reports `KvError::Unsupported`; DEC-8 picks the default. Accept: - the contract suite passes for every store offered there, and a native store - that cannot meet it reports `Unsupported`, so identity on it is rejected at - startup. -- **EZI-3 Trusted client metadata.** One accessor returns the trusted client - IP, and one the JA4 fingerprint where a trusted provider supplies it - (IABTechLab/edgezero#283), on every adapter, Fastly and Axum first. JA4 is an EdgeZero abstraction, `ctx.ja4()`, that each adapter fills - from its own source: Fastly from runtime TLS, Cloudflare from Bot Management, - Axum from a trusted proxy header such as CloudFront's - `CloudFront-Viewer-JA4-Fingerprint` set by a hop EdgeZero's checked ingress - trusts, and Spin never (`NotExposed`). Trusted Server reads JA4 only through - it. Accept: the same accessors work on each adapter as its part lands, and a - JA4 header from an untrusted peer reads as absent. + portable DynamoDB and celld providers (EZI-2p) pass the EZI-2a contract, and + the native store meets it or reports `KvError::Unsupported`; DEC-8 picks the + default. Accept: the contract suite passes for every store offered there, and + a native store that cannot meet it reports `Unsupported`, so identity on it is + rejected at startup. +- **EZI-3 Trusted client metadata.** One accessor returns the trusted client IP, + and one the JA4 fingerprint where a trusted provider supplies it + (IABTechLab/edgezero#283), on every adapter, Fastly and Axum first. JA4 is an + EdgeZero abstraction, `ctx.ja4()`, that each adapter fills from its own + source: Fastly from runtime TLS, Cloudflare from Bot Management, Axum from a + trusted proxy header such as CloudFront's `CloudFront-Viewer-JA4-Fingerprint` + set by a hop EdgeZero's checked ingress trusts, and Spin never (`NotExposed`). + Trusted Server reads JA4 only through it. Accept: the same accessors work on + each adapter as its part lands, and a JA4 header from an untrusted peer reads + as absent. - **EZI-4 Geo with region on every adapter.** `ctx.geo()` gives country and region. Fastly keeps its existing geo. Axum uses CloudFront geo from IABTechLab/edgezero#439, bound from EdgeZero's manifest so an application @@ -605,15 +609,16 @@ Decided: - **DEC-1 Cutover** (user, 2026-11-11). #1506 is the EdgeZero cutover. #1527 closes once #1506 covers its tests. -- **DEC-2 MVP** (user, 2026-11-12). Fastly and Axum are the MVP: plan waves 0 - to 9. The one open decision the MVP waits on is DEC-9, through T22. Cloudflare, Spin and the decision-gated work follow it. +- **DEC-2 MVP** (user, 2026-11-12). Fastly and Axum are the MVP: plan waves 0 to 9. + The one open decision the MVP waits on is DEC-9, through T22. Cloudflare, Spin + and the decision-gated work follow it. - **DEC-3 Shared record store** (user, 2026-11-11). The EC record runs on one - `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. The - user reaffirmed it on 2026-11-12: Axum's record uses EdgeZero's pluggable - `KvStore` adapters (DynamoDB and celld through EZI-2b), never an Axum-only + `EcKvStore` over EdgeZero's `KvStore`, with no per-adapter backends. The user + reaffirmed it on 2026-11-12: Axum's record uses EdgeZero's pluggable `KvStore` + adapters (DynamoDB and celld through EZI-2b in the MVP), never an Axum-only store such as #1536's. The DynamoDB and celld adapters are available to every - adapter; the native stores (Fastly KV, Cloudflare KV, Spin KV) only to their - own platform. + adapter (EZI-2p, after the MVP); the native stores (Fastly KV, Cloudflare KV, + Spin KV) only to their own platform. - **DEC-4 Platform order** (user, 2026-11-12). Fastly, then Axum, then Cloudflare, then Spin. Fastly and Axum (the MVP, DEC-2) must ship without any Cloudflare or Spin work: no Fastly or Axum item is blocked by a Cloudflare or @@ -621,19 +626,20 @@ Decided: rejection, 501 and absence paths in the plan's Global Constraints). Cloudflare and Spin follow the MVP. - **DEC-5 EdgeZero integration branch** (user, 2026-11-12). EdgeZero keeps an - integration branch that carries the unreleased work headed for EdgeZero - main: IABTechLab/edgezero#275, the IABTechLab/edgezero#406 to + integration branch that carries the unreleased work headed for EdgeZero main: + IABTechLab/edgezero#275, the IABTechLab/edgezero#406 to IABTechLab/edgezero#439 chain and the EZI tickets. Trusted Server main pins - its tags (R1, R1b, R2 and R2b in the MVP) instead of waiting for an EdgeZero release, and - repins to a released tag once EdgeZero publishes one. -- **DEC-6 Base for the Axum line** (user, 2026-11-12). Forward-port the RC - line to main rather than back-port the provider stack to it, which conflicted - in 13 to 23 files at the heads first measured and 21 to 24 at the round-4 - heads. #1531 restacks on #1506 for A1 and #1532 on #1531 for - #1529. The image and Terraform units (#1539, #1538, #1540) restack on #1531, - where they apply cleanly. #1536 and #1537 are not restacked: their - Axum-only stores, store selection and wiring give way to EZI-2b, T6 and - T21, and their tests move into A2, A8 and T5. #1535 goes into the EC route (T19, T20), and #1533 and #1534 close as copies of #1525 and #1526. + its tags (R1, R1b, R2 and R2b in the MVP) instead of waiting for an EdgeZero + release, and repins to a released tag once EdgeZero publishes one. +- **DEC-6 Base for the Axum line** (user, 2026-11-12). Forward-port the RC line + to main rather than back-port the provider stack to it, which conflicted in 13 + to 23 files at the heads first measured and 21 to 24 at the round-4 heads. + #1531 restacks on #1506 for A1 and #1532 on #1531 for #1529. The image and + Terraform units (#1539, #1538, #1540) restack on #1531, where they apply + cleanly. #1536 and #1537 are not restacked: their Axum-only stores, store + selection and wiring give way to EZI-2b, T6 and T21, and their tests move into + A2, A8 and T5. #1535 goes into the EC route (T19, T20), and #1533 and #1534 + close as copies of #1525 and #1526. - **DEC-7 SharedID creation and retention.** Creation (user, 2026-11-12): when neither the record nor the request has a SharedID, the server creates one, because a browser may block Prebid.js from loading at all (WB-3). Retention @@ -642,12 +648,12 @@ Decided: - **DEC-10 Mirror partners** (user, 2026-11-12). ID5 and LiveRamp, the important identity providers, get `mirror_enabled`, and the MVP's provider and mirror checks (T11, A10) use them. -- **DEC-11 Axum rate limiting** (user, 2026-11-12). Every limit is off on - Axum by default, inbound and outbound, because deployers rate-limit at their - own load balancer; the reference stack shows one rule (AX-6). Once rate - limiting returns (DEC-15), a limit a deployer turns on keeps its own in-memory counter in each task and enforces - the limit divided by the configured task count. No shared counter is - planned. +- **DEC-11 Axum rate limiting** (user, 2026-11-12). Every limit is off on Axum + by default, inbound and outbound, because deployers rate-limit at their own + load balancer; the reference stack shows one rule (AX-6). Once rate limiting + returns (DEC-15), a limit a deployer turns on keeps its own in-memory counter + in each task and enforces the limit divided by the configured task count. No + shared counter is planned. - **DEC-16 One implementation for every adapter** (user, 2026-11-12). Every feature is built once and works on every adapter, delivered in DEC-4's order. Shared logic lives in Trusted Server core; a platform capability an From 5017eaf65f2bf0d0aa358a2a7fad0332117b882e Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 12:52:15 -0800 Subject: [PATCH 24/31] Match Task 6's provider-stack map to Part B's blockers --- .../plans/2026-11-11-server-side-identity-all-adapters.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 234f438e4..a362c83fb 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -701,8 +701,9 @@ Post one `CREATOR:` comment per PR with its Part C item and its ticket, with `gh pr comment --repo IABTechLab/trusted-server --body-file `. Use `Part of #` for #1532 to #1529, #1526 to #1522, #1506 to #1507, #1531 to A1, #1536, #1537 and #1566 to A2, #1535 to T19 and T20, #1539 and #1538 to -A6, and #1540 to A7. Use `Blocks #` for the provider stack: #1487 -blocks T11, #1488 blocks T3, T13 and T22, and #1489 blocks T15, T19 and A10. +A6, and #1540 to A7. Use `Blocks #` for the provider stack, as +Part B's Blocked by cells say: #1487 blocks T11, #1246, #1549 and #1553; +#1488 blocks T3, T13, T22 and #1555; and #1489 blocks T2, T15, T21 and #1551. #1525 and #1541 get their Part C item with no ticket; #1503 has no item. For proposed closures, ask the author; do not close another author's PR. Expected: one comment URL per PR. From 8f7e6b2aee13c8b455351cce5e243e3c16a5a95a Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 12:53:35 -0800 Subject: [PATCH 25/31] Qualify Task 7's EdgeZero pull request numbers --- .../2026-11-11-server-side-identity-all-adapters.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index a362c83fb..1002fa601 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -719,10 +719,13 @@ Expected: one comment URL per PR. Same steps as Task 6, against IABTechLab/edgezero (`gh pr view --repo IABTechLab/edgezero` and `gh pr comment --repo IABTechLab/edgezero`), for -the EdgeZero items in Part C. Link #406 to EZI-1, #463 to EZI-2b, #469 to -IABTechLab/edgezero#283, #439 to IABTechLab/edgezero#432 and EZI-4a, and #275, -#460 and the other chain fixes to R1. #389, #401, #444, #466 and #464 get their -Part C item with no ticket. +the EdgeZero items in Part C. Link IABTechLab/edgezero#406 to EZI-1, +IABTechLab/edgezero#463 to EZI-2b, IABTechLab/edgezero#469 to IABTechLab/edgezero#283, +IABTechLab/edgezero#439 to IABTechLab/edgezero#432 and EZI-4a, and +IABTechLab/edgezero#275, IABTechLab/edgezero#460, IABTechLab/edgezero#407 and +IABTechLab/edgezero#414 to R1. IABTechLab/edgezero#389, +IABTechLab/edgezero#401, IABTechLab/edgezero#444, IABTechLab/edgezero#466 and +IABTechLab/edgezero#464 get their Part C item with no ticket. ### Task 8: Verify coverage From 2033ec722786740355fdab058e9f97e66ec954e4 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 12:55:48 -0800 Subject: [PATCH 26/31] Keep a stale grant result out of Prebid after a withdrawal One call in flight orders the requests, but a grant's JSON can still arrive after the reader withdrew. The script now discards a result overtaken by a newer consent change and removes the IDs it merged on a withdrawal, and ID-9's Accept checks that no ID reaches Prebid. --- ...11-11-server-side-identity-all-adapters.md | 44 +++++------ ...erver-side-identity-all-adapters-design.md | 77 ++++++++++--------- 2 files changed, 62 insertions(+), 59 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 1002fa601..53b578a6c 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -326,28 +326,28 @@ readiness signal. #1566 overlaps A2 and A10; its Axum part is rebuilt on T21 New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------- | ---- | --- | -------------------------- | ------------------------------- | -| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | -| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | PR #1489 | none | -| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-2, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | -| T4 | Measure Axum's per-request HTTP client against a pooled one, and keep it per request unless the measurement shows the Next.js regression gone (SH-1's exception) | SH-1 | Task | S | P2 | #1507 | none | -| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | -| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | -| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | -| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | -| T10 | Rate limits after the MVP: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P3 | EZI-5, T21; DEC-15 | none | -| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly with ID5 | ID-4, ID-5, WB-1 | Feature | M | P1 | T21, R2; PR #1487 | A10, T17, T19, T22 | -| T12 | In the shared app, refuse record creation when a trusted JA4 from `ctx.ja4()` contradicts the User-Agent's browser family, allow an unknown or missing JA4, and own the family rule's data; delete the direct Fastly JA4 read; verified on Fastly | ID-7 | Feature | M | P1 | R2b, T21 | A10, T17, T19 | -| T13 | SharedID in the shared app: create one when the reader has none and `storage.type` is `cookie`, and write it back with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-2, SID-5, SID-6 | Feature | M | P1 | T3, T21; PR #1488 | A10, T17, T19 | -| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | -| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | -| T17 | Identity features pass on Cloudflare from the shared app: providers, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P2 | T8, R3, T11, T12, T13, T19 | T18 | -| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P3 | T9, R4, T17 | none | -| T19 | EC route in the shared app, verified on Fastly: a same-origin `POST` to `/_ts/api/v1/ec` mints, records, runs the providers, takes the posted consent and withdrawals, and answers with the mirror's view as JSON; a `GET` answers Prebid's `pixelUrl` with a 204 and only re-sets an existing SharedID (reconciliation from #1535) | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T11, T12, T13, T21 | A10, T17, T20 | -| T20 | The injected script calls the EC route with a same-origin `POST` when the CMP reports consent and on every later change, keeps one call in flight and sends the latest consent after it settles, sends the TCF string and the GPP string (Sourcepoint's capture from #1535), copies a localStorage `_pubcid_optout` into the cookie, and merges the returned IDs into Prebid | ID-9, WB-6, SID-2 | Feature | M | P1 | T15, T19 | none | -| T21 | One identity app in core for every adapter: mounts identify, batch sync, admin lookup and the EC route and runs the EC lifecycle in one router each adapter mounts, building its state once (SH-1); adapter crates supply platform services through EdgeZero where it offers them, and Fastly's limiter and geo stay their existing bridges until their asks land | SH-5, SH-1, ID-1, ID-3, ID-6 | Feature | L | P1 | T1, #1507; PR #1489 | A2, T8, T10, T11, T12, T13, T19 | -| T22 | LiveRamp per DEC-9: set the Custom ID, enable LiveRamp, and pass the provider and mirror checks with it on Fastly and Axum | ID-4, WB-4 | Feature | S | P1 | T11, A10; PR #1488; DEC-9 | none | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | ------- | ---- | --- | -------------------------- | ------------------------------- | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | +| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | PR #1489 | none | +| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-2, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | +| T4 | Measure Axum's per-request HTTP client against a pooled one, and keep it per request unless the measurement shows the Next.js regression gone (SH-1's exception) | SH-1 | Task | S | P2 | #1507 | none | +| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | +| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | +| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | +| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | +| T10 | Rate limits after the MVP: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P3 | EZI-5, T21; DEC-15 | none | +| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly with ID5 | ID-4, ID-5, WB-1 | Feature | M | P1 | T21, R2; PR #1487 | A10, T17, T19, T22 | +| T12 | In the shared app, refuse record creation when a trusted JA4 from `ctx.ja4()` contradicts the User-Agent's browser family, allow an unknown or missing JA4, and own the family rule's data; delete the direct Fastly JA4 read; verified on Fastly | ID-7 | Feature | M | P1 | R2b, T21 | A10, T17, T19 | +| T13 | SharedID in the shared app: create one when the reader has none and `storage.type` is `cookie`, and write it back with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-2, SID-5, SID-6 | Feature | M | P1 | T3, T21; PR #1488 | A10, T17, T19 | +| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | +| T17 | Identity features pass on Cloudflare from the shared app: providers, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P2 | T8, R3, T11, T12, T13, T19 | T18 | +| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P3 | T9, R4, T17 | none | +| T19 | EC route in the shared app, verified on Fastly: a same-origin `POST` to `/_ts/api/v1/ec` mints, records, runs the providers, takes the posted consent and withdrawals, and answers with the mirror's view as JSON; a `GET` answers Prebid's `pixelUrl` with a 204 and only re-sets an existing SharedID (reconciliation from #1535) | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T11, T12, T13, T21 | A10, T17, T20 | +| T20 | The injected script calls the EC route with a same-origin `POST` when the CMP reports consent and on every later change, keeps one call in flight and sends the latest consent after it settles, discards a result overtaken by a newer consent change, removes the IDs it merged on a withdrawal, sends the TCF string and the GPP string (Sourcepoint's capture from #1535), copies a localStorage `_pubcid_optout` into the cookie, and merges the returned IDs into Prebid | ID-9, WB-6, SID-2 | Feature | M | P1 | T15, T19 | none | +| T21 | One identity app in core for every adapter: mounts identify, batch sync, admin lookup and the EC route and runs the EC lifecycle in one router each adapter mounts, building its state once (SH-1); adapter crates supply platform services through EdgeZero where it offers them, and Fastly's limiter and geo stay their existing bridges until their asks land | SH-5, SH-1, ID-1, ID-3, ID-6 | Feature | L | P1 | T1, #1507; PR #1489 | A2, T8, T10, T11, T12, T13, T19 | +| T22 | LiveRamp per DEC-9: set the Custom ID, enable LiveRamp, and pass the provider and mirror checks with it on Fastly and Axum | ID-4, WB-4 | Feature | S | P1 | T11, A10; PR #1488; DEC-9 | none | Existing, moved under #1558: diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 64e5fc89c..8dfc466eb 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -288,39 +288,40 @@ marks the record contract and geo as supported. - **ID-9 EC route.** A same-origin route, `/_ts/api/v1/ec` by default, lets the script Trusted Server injects into proxied pages report consent and receive identity, so a reader who consents after the page loads gets an EC on that - page view instead of the next navigation. A `POST` whose `Origin` matches - the page mints the EC when the reader has none and consent allows, creates - the record, runs the providers as a navigation does, and sets `ts-ec`, the + page view instead of the next navigation. A `POST` whose `Origin` matches the + page mints the EC when the reader has none and consent allows, creates the + record, runs the providers as a navigation does, and sets `ts-ec`, the SharedID cookie and the mirror cookie on its own response. It carries the - consent strings the script reads from the CMP, the TCF string from - `__tcfapi` and the GPP string from `__gpp` (Sourcepoint's capture from #1535 - where present), which replace the consent cookies for that request; a - withdrawal expires `ts-ec`, the SharedID cookie and the mirror cookie and - tombstones the record in that response. The `POST` answers with JSON holding - exactly what WB-4 would put in the mirror cookie for that request, and the - SharedID only under SID-2 and SID-6; the script merges it into Prebid, and - nothing else in the record reaches the page. A plain `GET` answers Prebid's - SharedID `pixelUrl` with an empty 204: it never mints, calls a provider or - changes consent, and only re-sets an existing SharedID cookie from the record - (WB-3). The script keeps at most one call in flight and sends the latest - consent after it settles, so a withdrawal always lands after any grant in - progress. Every response carries `Cache-Control: no-store`; a rate limit on - the route waits with ID-6 (DEC-15). The route is served from the page's own - host, which WebKit's cap on cookies from non-document responses exempts when - the IP matches the page's (read from WebKit's source; the Accept tests it). - Accept: on Fastly and Axum, a consented same-origin `POST` with no `ts-ec` - creates the record and sets `ts-ec` and the SharedID cookie in that - response; its JSON omits a non-mirrored partner's ID, an ID whose vendor's - TCF consent is denied and an expired ID; a withdrawal issued while a grant - call with no EC and delayed providers is in flight leaves no identity cookie - and no live record; a cross-site `Origin`, a `GET` and a client ID-7 - refuses mint nothing and call no provider; an unchanged returning reader gets - no `Set-Cookie`; and in Safari, `ts-ec` and the SharedID cookie set by the - route keep their one-year `Max-Age` on Fastly and on Axum behind CloudFront. - State: on main only a real-browser navigation through the publisher proxy - mints an EC, and no such route exists. #1535 (RC line) posts Sourcepoint's - GPP string to a separate, non-minting `/_ts/consent` route, wired into each - adapter's router. + consent strings the script reads from the CMP, the TCF string from `__tcfapi` + and the GPP string from `__gpp` (Sourcepoint's capture from #1535 where + present), which replace the consent cookies for that request; a withdrawal + expires `ts-ec`, the SharedID cookie and the mirror cookie and tombstones the + record in that response. The `POST` answers with JSON holding exactly what + WB-4 would put in the mirror cookie for that request, and the SharedID only + under SID-2 and SID-6; the script merges it into Prebid, and nothing else in + the record reaches the page. A plain `GET` answers Prebid's SharedID + `pixelUrl` with an empty 204: it never mints, calls a provider or changes + consent, and only re-sets an existing SharedID cookie from the record (WB-3). + The script keeps at most one call in flight and sends the latest consent after + it settles, so a withdrawal always lands after any grant in progress; it + discards a result that arrives while a newer consent change is waiting, and a + withdrawal removes the IDs it merged into Prebid. Every response carries + `Cache-Control: no-store`; a rate limit on the route waits with ID-6 (DEC-15). + The route is served from the page's own host, which WebKit's cap on cookies + from non-document responses exempts when the IP matches the page's (read from + WebKit's source; the Accept tests it). Accept: on Fastly and Axum, a consented + same-origin `POST` with no `ts-ec` creates the record and sets `ts-ec` and the + SharedID cookie in that response; its JSON omits a non-mirrored partner's ID, + an ID whose vendor's TCF consent is denied and an expired ID; a withdrawal + issued while a grant call with no EC and delayed providers is in flight leaves + no identity cookie, no live record and no ID in Prebid; a cross-site `Origin`, + a `GET` and a client ID-7 refuses mint nothing and call no provider; an + unchanged returning reader gets no `Set-Cookie`; and in Safari, `ts-ec` and + the SharedID cookie set by the route keep their one-year `Max-Age` on Fastly + and on Axum behind CloudFront. State: on main only a real-browser navigation + through the publisher proxy mints an EC, and no such route exists. #1535 (RC + line) posts Sourcepoint's GPP string to a separate, non-minting `/_ts/consent` + route, wired into each adapter's router. ### SH: Shared state passed the right way @@ -816,8 +817,10 @@ Reviews 5881124556 and 5881450878 at `c4417b7f8` reviewed those decisions and opened 66 threads; four of the second duplicate the first (the LiveRamp gate, Fastly's limiter under T21, withdrawal ordering and the EC route's JSON). Three were blocking: LiveRamp had no MVP ticket (T22 now checks it, gated by DEC-9), -the record contract asked DynamoDB for strong listing across partitions -(EZI-2a now lists strongly within a partition, and AX-4 partitions by listing -prefix), and the EC route's JSON exposed every EID (it now carries only the -mirror's view). The revision after it addresses every open thread, including -thread 37; each thread holds its disposition. +the record contract asked DynamoDB for strong listing across partitions (EZI-2a +now lists strongly within a partition, and AX-4 partitions by listing prefix), +and the EC route's JSON exposed every EID (it now carries only the mirror's +view). EZI-2b now covers Axum only, with the portable providers in EZI-2p after +the MVP, and R2 splits so providers no longer wait on JA4 (R2b). The revision +after it addresses every open thread, including thread 37; each thread holds its +disposition. From 71f8aa1fdc00c1a829d245ffc782a04533a8c79b Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 12:58:38 -0800 Subject: [PATCH 27/31] Align Spin and consent leftovers with the round-4 decisions The platform table still offered skipping Spin's after-response work, which DEC-16 rules out, and EZI-4s still allowed a capability error instead of the Spin geo source EZI-4 now names. The consent bullet now says the posted TCF and GPP strings replace the cookies, as ID-9 does. --- .../2026-11-11-server-side-identity-all-adapters.md | 2 +- ...026-11-11-server-side-identity-all-adapters-design.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 53b578a6c..6c04a08c7 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -257,7 +257,7 @@ New: | EZI-1s | After-response capability error on Spin | EZI-1 | Task | XS | P3 | EZI-1 | R4 | | EZI-2s | On Spin, the portable DynamoDB and celld providers pass the record contract, and Spin's KV meets it or reports `KvError::Unsupported` | EZI-2s | Feature | M | P3 | EZI-2a, EZI-2p; DEC-8 | R4 | | EZI-3s | Spin ClientInfo, the Spin part of EZI-3 | EZI-3 | Task | S | P3 | EZI-3 | R4, IABTechLab/edgezero#89 | -| EZI-4s | Geo through `ctx.geo()` on Spin, or a capability error | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | +| EZI-4s | Geo with country and region through `ctx.geo()` on Spin, from a trusted ingress header or a KV- or API-backed provider | EZI-4 | Feature | S | P3 | IABTechLab/edgezero#432 | R4 | | EZI-5s | Rate limiter backend for Spin (after the MVP) | EZI-5 | Feature | S | P3 | EZI-5; DEC-15 | none | | R1 | Create the protected integration branch and tag IABTechLab/edgezero#275 through IABTechLab/edgezero#439 | EZI-6 | Task | S | P1 | IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432; PRs IABTechLab/edgezero#275 to IABTechLab/edgezero#439 | A1 | | R1b | Integration-branch tag with the record contract: EZI-2a and EZI-2b | EZI-6 | Task | S | P1 | EZI-2a, EZI-2b | T6 | diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 8dfc466eb..4938e64f2 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -130,7 +130,7 @@ follows DEC-4: Fastly, Axum, Cloudflare, Spin. | Capability | Fastly | Axum | Cloudflare | Spin | | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Record contract (generation read, create-if-absent, CAS, strong exists, list) | Supported. Fastly KV generations through EZI-2a; DynamoDB or celld through EZI-2p after the MVP | Supported. DynamoDB or celld through EZI-2b | DynamoDB or celld through EZI-2p and EZI-2c. Workers KV is eventually consistent with no CAS, and EdgeZero has no Durable Objects or D1 store on main, IABTechLab/edgezero#275 or the RC tags | DynamoDB or celld through EZI-2p and EZI-2s. Spin's own `spin-sdk` 7.0.0 `key_value` has get, set, delete, exists and get_keys only, with no prefix listing; the SDK exposes `wasi:keyvalue/atomics`, which defines a CAS resource, only as raw bindings in its doc-hidden `experimental` module | -| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported. DEC-14 picks before-response or skip | +| After-response work | Supported (after send) | EZI-1, drained on SIGTERM | EZI-1 over `waitUntil` | Unsupported; the work runs before the response (DEC-14) | | Rate limiter | Supported (Edge Rate Limiting) | None in the MVP; the load balancer limits (DEC-11, DEC-15) | EZI-5, backend per DEC-15 | EZI-5, backend per DEC-15 | | Geo with region | Supported | CloudFront headers (A4, IABTechLab/edgezero#439) | Country only today; region needs EZI-4 | None; needs EZI-4 | | TLS fingerprint (JA4) | Runtime TLS through EdgeZero's `ctx.ja4()` (EZI-3) | `ctx.ja4()` from a trusted proxy header such as CloudFront's (EZI-3) | `ctx.ja4()` from Bot Management, otherwise absent | Absent (`NotExposed`) | @@ -566,9 +566,10 @@ wait on a Cloudflare or Spin part. - The `_pubcid_optout` cookie is honored; a localStorage opt-out is invisible to the server (SID-2). - The EC route reads consent from the request's cookies and `Sec-GPC`, as a - navigation does, or from the TCF or GPP string the injected script reads - from the CMP and sends with the call. Both pass through the same decoding - and the same Purpose 1 rule (ID-9). + navigation does, or from the TCF and GPP strings the injected script reads + from the CMP and sends with the call, which replace the consent cookies for + that request. Both pass through the same decoding and the same Purpose 1 rule + (ID-9). ## Failure handling From 8e564f24c32fb2e315d463d62cd2d40dfa78cef6 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 13:01:14 -0800 Subject: [PATCH 28/31] Move IABTechLab/edgezero#464's rebase out of the MVP waves --- .../plans/2026-11-11-server-side-identity-all-adapters.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 6c04a08c7..2490d4b91 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -139,7 +139,7 @@ Close. | 0 | TS #1189 | P1 | Close | Fixed on main by `56a19229f` (#1157), which deleted the ungated `ingest_*` functions; ask its author to close it | none | | 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | | 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | -| 0 | IABTechLab/edgezero#463, IABTechLab/edgezero#464 | P1 | PR fix | Rebase off the commits they carry | none | +| 0 | IABTechLab/edgezero#463 | P1 | PR fix | Rebase off the commits it carries | none | | 0 | TS #1503, #1432 | P2 | Merge | `ts ec` commands; delivers #1432 | none | | 0 | TS #1233 | P2 | Move | `ts-ec` to creative assets | none | | 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | @@ -195,7 +195,7 @@ Close. | any | TS #1549 | P2 | Move | No `user.id` for a row-less EC | DEC-12; PR #1487 | | any | TS T16, TS #1245, IABTechLab/edgezero#438, IABTechLab/edgezero#287 | P3 | New, Move, Merge | `ts-ec` refresh; LiveRamp sync spec; local GeoIP (no open PR); IABTechLab/edgezero#287 last | DEC-13; DEC-9; none; IABTechLab/edgezero#275 | | any | TS #1139, #1525 | P3 | Move, PR fix | Store inventory on all four adapters; HTTP auction endpoints, which #1526 does not need | none | -| any | IABTechLab/edgezero#464 | P3 | Merge | Merge when its review closes; no MVP ticket needs it | IABTechLab/edgezero#439 | +| any | IABTechLab/edgezero#464 | P3 | PR fix, Merge | Rebase off the commits it carries, then merge when its review closes; no MVP ticket needs it | IABTechLab/edgezero#439 | | any | EZ EZI-5, EZI-5c, EZI-5s; TS T10 | P3 | New | Rate limiting, punted past the MVP | DEC-15 | Waves 0 to 9 are the MVP (DEC-2): they complete Fastly and Axum. None of them From 3bbd297ed875ed31a87212199c2f391ee72d3bce Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 19:13:57 -0800 Subject: [PATCH 29/31] Keep a waiting withdrawal ahead of later consent changes Sending only the latest waiting consent could drop a withdrawal made between a grant in flight and a later regrant, so the record would never be tombstoned. The script now lets a newer change replace a waiting one except a waiting withdrawal, which later changes wait behind, and ID-9's Accept covers the grant, withdrawal and regrant case. Record round 5 in the review log. --- ...11-11-server-side-identity-all-adapters.md | 44 ++++++------- ...erver-side-identity-all-adapters-design.md | 62 ++++++++++++------- 2 files changed, 60 insertions(+), 46 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 2490d4b91..45f1cd23d 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -326,28 +326,28 @@ readiness signal. #1566 overlaps A2 and A10; its Axum part is rebuilt on T21 New: -| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | -| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | ------- | ---- | --- | -------------------------- | ------------------------------- | -| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | -| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | PR #1489 | none | -| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-2, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | -| T4 | Measure Axum's per-request HTTP client against a pooled one, and keep it per request unless the measurement shows the Next.js regression gone (SH-1's exception) | SH-1 | Task | S | P2 | #1507 | none | -| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | -| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | -| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | -| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | -| T10 | Rate limits after the MVP: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P3 | EZI-5, T21; DEC-15 | none | -| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly with ID5 | ID-4, ID-5, WB-1 | Feature | M | P1 | T21, R2; PR #1487 | A10, T17, T19, T22 | -| T12 | In the shared app, refuse record creation when a trusted JA4 from `ctx.ja4()` contradicts the User-Agent's browser family, allow an unknown or missing JA4, and own the family rule's data; delete the direct Fastly JA4 read; verified on Fastly | ID-7 | Feature | M | P1 | R2b, T21 | A10, T17, T19 | -| T13 | SharedID in the shared app: create one when the reader has none and `storage.type` is `cookie`, and write it back with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-2, SID-5, SID-6 | Feature | M | P1 | T3, T21; PR #1488 | A10, T17, T19 | -| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | -| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | -| T17 | Identity features pass on Cloudflare from the shared app: providers, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P2 | T8, R3, T11, T12, T13, T19 | T18 | -| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P3 | T9, R4, T17 | none | -| T19 | EC route in the shared app, verified on Fastly: a same-origin `POST` to `/_ts/api/v1/ec` mints, records, runs the providers, takes the posted consent and withdrawals, and answers with the mirror's view as JSON; a `GET` answers Prebid's `pixelUrl` with a 204 and only re-sets an existing SharedID (reconciliation from #1535) | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T11, T12, T13, T21 | A10, T17, T20 | -| T20 | The injected script calls the EC route with a same-origin `POST` when the CMP reports consent and on every later change, keeps one call in flight and sends the latest consent after it settles, discards a result overtaken by a newer consent change, removes the IDs it merged on a withdrawal, sends the TCF string and the GPP string (Sourcepoint's capture from #1535), copies a localStorage `_pubcid_optout` into the cookie, and merges the returned IDs into Prebid | ID-9, WB-6, SID-2 | Feature | M | P1 | T15, T19 | none | -| T21 | One identity app in core for every adapter: mounts identify, batch sync, admin lookup and the EC route and runs the EC lifecycle in one router each adapter mounts, building its state once (SH-1); adapter crates supply platform services through EdgeZero where it offers them, and Fastly's limiter and geo stay their existing bridges until their asks land | SH-5, SH-1, ID-1, ID-3, ID-6 | Feature | L | P1 | T1, #1507; PR #1489 | A2, T8, T10, T11, T12, T13, T19 | -| T22 | LiveRamp per DEC-9: set the Custom ID, enable LiveRamp, and pass the provider and mirror checks with it on Fastly and Axum | ID-4, WB-4 | Feature | S | P1 | T11, A10; PR #1488; DEC-9 | none | +| ID | Ticket | Reqs | Type | Size | Pri | Blocked by | Blocks | +| --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------- | ---- | --- | -------------------------- | ------------------------------- | +| T1 | Answer identify and batch sync locally on every adapter: run them with a record store, 501 without one | ID-3 | Task | S | P0 | none | T8, T21 | +| T2 | EIDs in OpenRTB 2.6 `user.eids`; every EID-absence test checks both `user.eids` and `user.ext.eids` | SID-4 | Feature | M | P1 | PR #1489 | none | +| T3 | SharedID cleanup: legacy path, cross-source dedupe, re-filing, SID-6 gate, template-cache deny list | SID-1, SID-2, SID-3, SID-6, WB-5 | Feature | M | P1 | PR #1488 | T13 | +| T4 | Measure Axum's per-request HTTP client against a pooled one, and keep it per request unless the measurement shows the Next.js regression gone (SH-1's exception) | SH-1 | Task | S | P2 | #1507 | none | +| T5 | Identity conformance suite that runs against each adapter, Fastly and Axum first | ID-1, SH-5 | Task | M | P1 | #1507 | A2, T8 | +| T6 | One record backend over EdgeZero `KvStore`; delete the Fastly `ec_kv.rs`; adapters without a supported store reject identity configuration at startup, with a test | ID-2 | Feature | L | P1 | R1b, #1522 | A2, T8 | +| T8 | Identity on Cloudflare through T21's shared app with EZI-2c's store, passing T5's suite; build state once; drop the first-request `Env` capture and config `block_on` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-4, SH-5 | Feature | L | P2 | T6, R3, T1, T5, T21; DEC-8 | T9, T17 | +| T9 | Identity on Spin, the same scope as T8; port the config `block_on` removal from RC commit `933de5921` | ID-1, ID-2, ID-3, ID-5, ID-8, WB-1, SH-1, SH-3, SH-5 | Feature | L | P3 | T8, R4; DEC-8, DEC-14 | T18 | +| T10 | Rate limits after the MVP: batch sync per partner, pull sync, provider lookups and record creation on Fastly; on Axum off by default and, when turned on, per task divided by the configured task count | ID-6 | Feature | M | P3 | EZI-5, T21; DEC-15 | none | +| T11 | Providers in the shared app, awaited before finalization; only follow-up writes after the response; verified on Fastly with ID5 | ID-4, ID-5, WB-1 | Feature | M | P1 | T21, R2; PR #1487 | A10, T17, T19, T22 | +| T12 | In the shared app, refuse record creation when a trusted JA4 from `ctx.ja4()` contradicts the User-Agent's browser family, allow an unknown or missing JA4, and own the family rule's data; delete the direct Fastly JA4 read; verified on Fastly | ID-7 | Feature | M | P1 | R2b, T21 | A10, T17, T19 | +| T13 | SharedID in the shared app: create one when the reader has none and `storage.type` is `cookie`, and write it back with `_cst`, under SID-2 and SID-6; verified on Fastly | WB-3, SID-2, SID-5, SID-6 | Feature | M | P1 | T3, T21; PR #1488 | A10, T17, T19 | +| T15 | Identity-only tsjs mode | WB-6 | Feature | M | P1 | PR #1489 | T20 | +| T16 | `ts-ec` refresh for returning readers without breaking cacheability | WB-2, WB-5 | Task | S | P3 | DEC-13 | none | +| T17 | Identity features pass on Cloudflare from the shared app: providers, abuse protection, SharedID creation and write-back, the mirror cookie and the EC route | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P2 | T8, R3, T11, T12, T13, T19 | T18 | +| T18 | Identity features pass on Spin, the same scope as T17 | ID-4, ID-7, ID-9, WB-3, WB-4, SID-1, SID-2, SID-6 | Feature | M | P3 | T9, R4, T17 | none | +| T19 | EC route in the shared app, verified on Fastly: a same-origin `POST` to `/_ts/api/v1/ec` mints, records, runs the providers, takes the posted consent and withdrawals, and answers with the mirror's view as JSON; a `GET` answers Prebid's `pixelUrl` with a 204 and only re-sets an existing SharedID (reconciliation from #1535) | ID-9, WB-1, WB-3, WB-4 | Feature | M | P1 | T11, T12, T13, T21 | A10, T17, T20 | +| T20 | The injected script calls the EC route with a same-origin `POST` when the CMP reports consent and on every later change, sends consent changes in order with one call in flight, letting a newer change replace a waiting one but never a waiting withdrawal, which later changes wait behind, discards a result overtaken by a newer consent change, removes the IDs it merged on a withdrawal, sends the TCF string and the GPP string (Sourcepoint's capture from #1535), copies a localStorage `_pubcid_optout` into the cookie, and merges the returned IDs into Prebid | ID-9, WB-6, SID-2 | Feature | M | P1 | T15, T19 | none | +| T21 | One identity app in core for every adapter: mounts identify, batch sync, admin lookup and the EC route and runs the EC lifecycle in one router each adapter mounts, building its state once (SH-1); adapter crates supply platform services through EdgeZero where it offers them, and Fastly's limiter and geo stay their existing bridges until their asks land | SH-5, SH-1, ID-1, ID-3, ID-6 | Feature | L | P1 | T1, #1507; PR #1489 | A2, T8, T10, T11, T12, T13, T19 | +| T22 | LiveRamp per DEC-9: set the Custom ID, enable LiveRamp, and pass the provider and mirror checks with it on Fastly and Axum | ID-4, WB-4 | Feature | S | P1 | T11, A10; PR #1488; DEC-9 | none | Existing, moved under #1558: diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 4938e64f2..52339eb84 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -302,21 +302,26 @@ marks the record contract and geo as supported. the record reaches the page. A plain `GET` answers Prebid's SharedID `pixelUrl` with an empty 204: it never mints, calls a provider or changes consent, and only re-sets an existing SharedID cookie from the record (WB-3). - The script keeps at most one call in flight and sends the latest consent after - it settles, so a withdrawal always lands after any grant in progress; it - discards a result that arrives while a newer consent change is waiting, and a - withdrawal removes the IDs it merged into Prebid. Every response carries - `Cache-Control: no-store`; a rate limit on the route waits with ID-6 (DEC-15). - The route is served from the page's own host, which WebKit's cap on cookies - from non-document responses exempts when the IP matches the page's (read from - WebKit's source; the Accept tests it). Accept: on Fastly and Axum, a consented - same-origin `POST` with no `ts-ec` creates the record and sets `ts-ec` and the - SharedID cookie in that response; its JSON omits a non-mirrored partner's ID, - an ID whose vendor's TCF consent is denied and an expired ID; a withdrawal - issued while a grant call with no EC and delayed providers is in flight leaves - no identity cookie, no live record and no ID in Prebid; a cross-site `Origin`, - a `GET` and a client ID-7 refuses mint nothing and call no provider; an - unchanged returning reader gets no `Set-Cookie`; and in Safari, `ts-ec` and + The script sends consent changes in order with at most one call in flight. + While a call is in flight, a newer change replaces a waiting one, except a + waiting withdrawal, which is never replaced: later changes wait behind it. So + every withdrawal reaches the server, after any grant in progress and before + any later grant. The script discards a result that arrives while a newer + consent change is waiting, and a withdrawal removes the IDs it merged into + Prebid. Every response carries `Cache-Control: no-store`; a rate limit on the + route waits with ID-6 (DEC-15). The route is served from the page's own host, + which WebKit's cap on cookies from non-document responses exempts when the IP + matches the page's (read from WebKit's source; the Accept tests it). Accept: + on Fastly and Axum, a consented same-origin `POST` with no `ts-ec` creates the + record and sets `ts-ec` and the SharedID cookie in that response; its JSON + omits a non-mirrored partner's ID, an ID whose vendor's TCF consent is denied + and an expired ID; a withdrawal issued while a grant call with no EC and + delayed providers is in flight leaves no identity cookie, no live record and + no ID in Prebid; with grant A in flight, a withdrawal B and then a grant C + reach the server as A, B, C, so B expires the identity cookies and tombstones + A's record and C is handled as a first consent with no `ts-ec`; a cross-site + `Origin`, a `GET` and a client ID-7 refuses mint nothing and call no provider; + an unchanged returning reader gets no `Set-Cookie`; and in Safari, `ts-ec` and the SharedID cookie set by the route keep their one-year `Max-Age` on Fastly and on Axum behind CloudFront. State: on main only a real-browser navigation through the publisher proxy mints an EC, and no such route exists. #1535 (RC @@ -720,15 +725,15 @@ revision addresses them; each thread holds its disposition. ### Round 2, 2026-11-12 -Review 5875159165 at `7cb8933f7`. It accepted and resolved 28 round-1 -findings and left six open: the fallback clock (thread 1), the review log and -draft gate (7), SH-1's acceptance against the Axum HTTP client exception (14), -the RC-unit baseline (15), ID-5's acceptance on Spin (18) and decision gates -missing from plan cells (23). It added two should-fix findings: Task 1 must -re-parent the EdgeZero issues it moves (35), and WB-3's no-write case must -require a consistent `_cst` (36). No blocking finding. The revision after -it addresses all eight. The PR stays in draft until a round leaves no open -finding. +Review 5875159165 at `7cb8933f7`. It accepted and resolved 28 round-1 findings +and left six open: the fallback clock (thread 1), the review log and draft gate +(7), SH-1's acceptance against the Axum HTTP client exception (14), the RC-unit +baseline (15), ID-5's acceptance on Spin (18) and decision gates missing from +plan cells (23). It added two should-fix findings: Task 1 must re-parent the +EdgeZero issues it moves (35), and WB-3's no-write case must require a +consistent `_cst` (36). No blocking finding. The revision after it +addresses all eight. The PR stays in draft while known changes remain and is +marked ready for each review round, as CONTRIBUTING.md asks. ### Round 3, 2026-11-12 @@ -825,3 +830,12 @@ view). EZI-2b now covers Axum only, with the portable providers in EZI-2p after the MVP, and R2 splits so providers no longer wait on JA4 (R2b). The revision after it addresses every open thread, including thread 37; each thread holds its disposition. + +### Round 5, 2026-11-13 + +Review 5884927046 at `8e564f24c` accepted and resolved 65 of the 67 round-4 +threads, 101 in all, and opened nothing new. Threads 40 and 58 stayed open on +one issue: sending only the latest waiting consent could drop a withdrawal made +between a grant in flight and a later regrant. ID-9 and T20 now keep a waiting +withdrawal as a barrier that later changes wait behind, and ID-9's Accept adds +the grant, withdrawal and regrant case. From 403a62a5faeec1a38824820add05f85a8f13c4c6 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 19:57:51 -0800 Subject: [PATCH 30/31] Record #1516's merge and the moved RC units #1516 merged on main, so it leaves #1506's wait list and the wave-1 fallback clock. #1540 and #1541 moved: #1540 now wires a single-partition DynamoDB table and an opt-in CloudFront front door, and no longer restacks cleanly on #1531, so AX-6's state, DEC-6 and Part C say so. --- ...11-11-server-side-identity-all-adapters.md | 132 +++++++++--------- ...erver-side-identity-all-adapters-design.md | 35 ++--- 2 files changed, 87 insertions(+), 80 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index 45f1cd23d..c2deaf842 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -82,30 +82,30 @@ design](../specs/2026-11-11-server-side-identity-all-adapters-design.md) Trusted Server: -| PR | Head | Base on GitHub | Fix | -| ------------------- | ------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| #1557 | `76e1dd5f6` | main | None | -| #1525 | `cd8977cad` | main | None; #1526 does not need it | -| #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | -| #1516 | `214cd4821` | main | Rebase onto main: `CHANGELOG.md` conflicts | -| #1503 | `5e10c24e4` | main | None; needs neither #1506 nor new EdgeZero | -| #1449 | `fd33f2c74` | main | Outside this plan (DEC-19) | -| #1506 | `9ec4711d3` | main | Rebase after #1557, #1526 and #1516 | -| #1487 | `1600781eb` | main | Rebase after #1526 and #1506: 8 and 17 files conflict | -| #1488 | `7f55776f3` | #1487's branch | Take #1566's two #1488 commits | -| #1489 | `d3780ba13` | #1488's branch | None | -| #1566 | `117a58e0e` | #1488's branch | Move its two #1488 commits down; rebuild the Axum part on T21 and EdgeZero (Part C). 1 file conflicts with #1489, 19 with #1506 | -| #1527 | `a020daeec` | #1526's branch | Close once #1506 covers its tests (DEC-1) | -| #1528 | `ea779fb47` | #1527's branch | Close once #1531 carries A1 | -| #1543 | `e5e6ea7fa` | #1449's branch | Outside this plan (DEC-19) | -| #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | -| #1499 | `084fdbba9` | #1424's branch | Close at parity | -| #1531 | `88a6ba596` | #1506's branch | Retargeted without a rebase: it forks from main with its own cutover and the RC tag pin, and 15 files conflict with #1506 at `9ec4711d3`. Rebase onto #1506; it then delivers A1 | -| #1532 | `852b875fe` | #1531's branch | Delivers #1529 (A4); restack after #1531 | -| #1533, #1534 | `82d9a0e66`, `646c39a34` | #1532's and #1533's branches | RC-baseline copies of #1525 and #1526: drop from the chain and close | -| #1536, #1535, #1537 | `296e05405`, `0666a9b4b`, `4a838272c` | #1534's, #1536's and #1535's branches | Not restacked: mostly Axum-only stores, store selection and wiring that EZI-2b, T6 and T21 replace. Their tests go to A2, A8 and T5 and #1535's code to T19 and T20; each closes when those land | -| #1539, #1538, #1540 | `8d41cb06c`, `3111c2456`, `eb6e19ef4` | #1537's, #1539's and #1538's branches | Restack on #1531 (A1), where they apply cleanly. #1539 and #1543 both add `.dockerignore`, so whichever lands second reconciles it | -| #1541 | `7dd2e3056` | main | Close at parity | +| PR | Head | Base on GitHub | Fix | +| ------------------- | ------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| #1557 | `76e1dd5f6` | main | None | +| #1525 | `cd8977cad` | main | None; #1526 does not need it | +| #1526 | `e529e5576` | #1525's branch | Retarget to main; its own diff applies cleanly there | +| #1516 | `8b9c45d5e` | main | Merged on main as `1810c763a` on 2026-11-12 | +| #1503 | `5e10c24e4` | main | None; needs neither #1506 nor new EdgeZero | +| #1449 | `fd33f2c74` | main | Outside this plan (DEC-19) | +| #1506 | `9ec4711d3` | main | Rebase after #1557 and #1526; #1516 has merged | +| #1487 | `1600781eb` | main | Rebase after #1526 and #1506: 8 and 17 files conflict | +| #1488 | `7f55776f3` | #1487's branch | Take #1566's two #1488 commits | +| #1489 | `d3780ba13` | #1488's branch | None | +| #1566 | `117a58e0e` | #1488's branch | Move its two #1488 commits down; rebuild the Axum part on T21 and EdgeZero (Part C). 1 file conflicts with #1489, 19 with #1506 | +| #1527 | `a020daeec` | #1526's branch | Close once #1506 covers its tests (DEC-1) | +| #1528 | `ea779fb47` | #1527's branch | Close once #1531 carries A1 | +| #1543 | `e5e6ea7fa` | #1449's branch | Outside this plan (DEC-19) | +| #1424 | `2423a2ed2` | main | Conflicting, head `rc/edgezero-431-demo`; close at parity | +| #1499 | `084fdbba9` | #1424's branch | Close at parity | +| #1531 | `88a6ba596` | #1506's branch | Retargeted without a rebase: it forks from main with its own cutover and the RC tag pin, and 15 files conflict with #1506 at `9ec4711d3`. Rebase onto #1506; it then delivers A1 | +| #1532 | `852b875fe` | #1531's branch | Delivers #1529 (A4); restack after #1531 | +| #1533, #1534 | `82d9a0e66`, `646c39a34` | #1532's and #1533's branches | RC-baseline copies of #1525 and #1526: drop from the chain and close | +| #1536, #1535, #1537 | `296e05405`, `0666a9b4b`, `4a838272c` | #1534's, #1536's and #1535's branches | Not restacked: mostly Axum-only stores, store selection and wiring that EZI-2b, T6 and T21 replace. Their tests go to A2, A8 and T5 and #1535's code to T19 and T20; each closes when those land | +| #1539, #1538, #1540 | `8d41cb06c`, `3111c2456`, `db1ba337a` | #1537's, #1539's and #1538's branches | Restack on #1531 (A1). #1539 and #1538 apply cleanly; #1540 conflicts in `docs/guide/native-ec-persistence.md` since its `db1ba337a` merged the AWS examples. #1539 and #1543 both add `.dockerignore`, so whichever lands second reconciles it | +| #1541 | `86797e0b4` | main | Close at parity | EdgeZero: @@ -135,14 +135,14 @@ Close. | 0 | TS T1 | P0 | New | Answer identify and batch sync locally on every adapter | none | | 0 | TS #1557 | P1 | Merge | Sourcepoint consent | none | | 0 | TS #1526, #1522 | P1 | PR fix, Merge | Retarget to main, fix its round-1 review; delivers #1522 | none | -| 0 | TS #1516 | P1 | Merge | Already approved | none | +| 0 | TS #1516 | P1 | Merged | Merged on main as `1810c763a` | none | | 0 | TS #1189 | P1 | Close | Fixed on main by `56a19229f` (#1157), which deleted the ungated `ingest_*` functions; ask its author to close it | none | | 0 | IABTechLab/edgezero#460 | P1 | PR fix | Part C fixes, then fold into IABTechLab/edgezero#275's branch with `9f090e57` and one CI trigger change | none | | 0 | IABTechLab/edgezero#406, IABTechLab/edgezero#407, IABTechLab/edgezero#414 | P1 | PR fix | Move `4ea99467` to IABTechLab/edgezero#406 and `9a985c99` to IABTechLab/edgezero#407 | none | | 0 | IABTechLab/edgezero#463 | P1 | PR fix | Rebase off the commits it carries | none | | 0 | TS #1503, #1432 | P2 | Merge | `ts ec` commands; delivers #1432 | none | | 0 | TS #1233 | P2 | Move | `ts-ec` to creative assets | none | -| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, #1516, IABTechLab/edgezero#460 fold-in | +| 1 | TS #1506 | P1 | PR fix, Merge | Rebase; repin after the fold-in, or the fallback below; delivers #1507 | #1557, #1526, IABTechLab/edgezero#460 fold-in | | 1 | IABTechLab/edgezero#275 | P1 | PR fix, Merge | Resolve its review, then merge | IABTechLab/edgezero#460 fold-in | | 2 | TS #1487 | P1 | PR fix, Merge | Part C fixes | #1526, #1506 | | 2 | IABTechLab/edgezero#406 to IABTechLab/edgezero#439; IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437, IABTechLab/edgezero#432 | P1 | PR fix, Merge | Resolve reviews, retarget to main, merge in order; delivers IABTechLab/edgezero#397, IABTechLab/edgezero#410, IABTechLab/edgezero#437 and IABTechLab/edgezero#432 | IABTechLab/edgezero#275 | @@ -203,9 +203,10 @@ waits on a Cloudflare or Spin item, so the MVP ships without waves 10 to 18 (DEC-4). Waves 10 to 14 (Cloudflare), waves 15 to 18 (Spin) and the `any` rows follow the MVP; an `any` row starts when its decision or reviewer is ready. -Fallback for wave 1: if the fold-in has not landed two weeks after #1557, -#1526 and #1516 have all merged on main, #1506 merges pinned to IABTechLab/edgezero#275's head as it stands -(allowed by AX-7) and keeps its readthrough rejection until the repin. +Fallback for wave 1: if the fold-in has not landed two weeks after #1557 and +#1526 have both merged on main (#1516 merged on 2026-11-12), #1506 merges pinned +to IABTechLab/edgezero#275's head as it stands (allowed by AX-7) and keeps its +readthrough rejection until the repin. The two longest chains: @@ -394,15 +395,15 @@ re-checks each item at the PR's current head before posting. ### Trusted Server -- **#1506 at `9ec4711d3` (AX-7).** Rebase after #1557, #1526 and #1516. Keep - EdgeZero pinned to IABTechLab/edgezero#275's head or an integration-branch tag - (DEC-5): an RC-tag pin pushed to this branch on - 2026-11-12 was reverted. - Repin after the fold-in, or under wave 1's fallback; drop the - `origin_readthrough_enabled` rejection in `config.rs` once cache tags return. - The current pin already carries IABTechLab/edgezero `b97efec1`, which stops - Spin sending `Content-Length: 0` on bodiless DELETE and OPTIONS. Port #1527's - native-client, response-owner and managed Fastly delivery tests if missing. +- **#1506 at `9ec4711d3` (AX-7).** Rebase after #1557 and #1526; #1516 has + merged. Keep EdgeZero pinned to IABTechLab/edgezero#275's head or an + integration-branch tag (DEC-5): an RC-tag pin pushed to this branch on + 2026-11-12 was reverted. Repin after the fold-in, or under wave 1's fallback; + drop the `origin_readthrough_enabled` rejection in `config.rs` once cache tags + return. The current pin already carries IABTechLab/edgezero `b97efec1`, which + stops Spin sending `Content-Length: 0` on bodiless DELETE and OPTIONS. Port + #1527's native-client, response-owner and managed Fastly delivery tests if + missing. - **#1526 at `e529e5576` (ID-2, SH-3).** Retarget to main. Address its round-1 review, including the finalize-under-`block_on` test gap. - **#1525 at `cd8977cad`.** Address its round-1 review. @@ -450,32 +451,35 @@ re-checks each item at the PR's current head before posting. lacks `ingress`), the type error, the CodeQL alerts and the per-request `AtomicBool` in the Fastly `AppState`. - **RC units, forward-ported (DEC-6).** #1531 goes on #1506 for A1 and #1532 on - #1531 for #1529. #1539, #1538 and #1540 go on #1531 for A6 and A7, where they - apply cleanly. #1533 and #1534 close as copies of #1525 and #1526. #1536 and - #1537 are not restacked: their Axum-only stores, store selection and wiring - give way to EZI-2b, T6 and T21, and their tests move into A2, A8 and T5. #1535 - (Sourcepoint GPP capture and server consent reconciliation) feeds T19 and T20: - its reconciliation runs in the EC route and its GPP capture in the injected - script, in place of a separate `/_ts/consent` route wired into each adapter - (DEC-16). #1532 takes its geo provider from EdgeZero's manifest once - IABTechLab/edgezero#439 binds it, dropping `TS_NATIVE_GEO_PROFILE_FILE`. The - notes below were checked at the RC tag `v0.1.1-rc-axum.202611.2` - (`7c6e68d94`), which contains every unit. They are forward-port notes: each - applies to the PR that ports its unit to main, and Task 6 posts it there once - that PR exists, checked at its head. On the RC unit PRs themselves Task 6 - posts a note only after re-checking it at the unit's head, read on 2026-11-12 - as #1532 `852b875fe`, #1536 `296e05405`, #1537 `4a838272c`, #1539 `8d41cb06c`, - #1540 `eb6e19ef4` and #1541 `7dd2e3056`. #1532: drop the core change that maps - a US request with no region to `Unknown`, which fails every US Cloudflare - request closed (AX-3), and show that the same trusted country and region reach - EC creation and auction consent on Axum. #1536: do not port the adapter-local - DynamoDB store (ID-2, AX-4). #1537: do not port its Axum celld store or - storage selection, which EdgeZero's providers and logical stores replace - (EZI-2b, DEC-16); its example config also breaks startup on Axum, Cloudflare - and Spin. #1539: fix the failing Axum test. #1540: trust IPv6 CloudFront - ranges, turn geo on, wire DynamoDB and add an example rate-based rule (AX-3, - AX-6, DEC-11). All units: replace `TS_*` names (AX-2). #1541: drop the commits - of closed #1545. + #1531 for #1529. #1539, #1538 and #1540 go on #1531 for A6 and A7: #1539 and + #1538 apply cleanly, and #1540 at `db1ba337a` conflicts in + `docs/guide/native-ec-persistence.md`. #1533 and #1534 close as copies of + #1525 and #1526. #1536 and #1537 are not restacked: their Axum-only stores, + store selection and wiring give way to EZI-2b, T6 and T21, and their tests + move into A2, A8 and T5. #1535 (Sourcepoint GPP capture and server consent + reconciliation) feeds T19 and T20: its reconciliation runs in the EC route and + its GPP capture in the injected script, in place of a separate `/_ts/consent` + route wired into each adapter (DEC-16). #1532 takes its geo provider from + EdgeZero's manifest once IABTechLab/edgezero#439 binds it, dropping + `TS_NATIVE_GEO_PROFILE_FILE`. The notes below were checked at the RC tag + `v0.1.1-rc-axum.202611.2` (`7c6e68d94`), which contains every unit. They are + forward-port notes: each applies to the PR that ports its unit to main, and + Task 6 posts it there once that PR exists, checked at its head. On the RC unit + PRs themselves Task 6 posts a note only after re-checking it at the unit's + head, read on 2026-11-12 as #1532 `852b875fe`, #1536 `296e05405`, #1537 + `4a838272c` and #1539 `8d41cb06c`, and on 2026-11-13 as #1540 `db1ba337a` and + #1541 `86797e0b4`. #1532: drop the core change that maps a US request with no + region to `Unknown`, which fails every US Cloudflare request closed (AX-3), + and show that the same trusted country and region reach EC creation and + auction consent on Axum. #1536: do not port the adapter-local DynamoDB store + (ID-2, AX-4). #1537: do not port its Axum celld store or storage selection, + which EdgeZero's providers and logical stores replace (EZI-2b, DEC-16); its + example config also breaks startup on Axum, Cloudflare and Spin. #1539: fix + the failing Axum test. #1540: trust IPv6 CloudFront ranges, turn geo on, key + the DynamoDB table by partition instead of the one partition key and one + allocator item its IAM policy allows (AX-4), and add an example rate-based + rule at the load balancer (AX-3, AX-6, DEC-11). All units: replace `TS_*` + names (AX-2). #1541: drop the commits of closed #1545. - **Proposed closures.** #1527 once its tests move (DEC-1); #1528 once #1531 carries A1; #1533 and #1534, the RC copies of #1525 and #1526; #1535, #1536 and #1537 once T19, T20, A2 and A8 carry their code and tests; #1310, which diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index 52339eb84..a69781e09 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -193,16 +193,17 @@ marks the record contract and geo as supported. the dev-server image (`STOPSIGNAL SIGINT`). - **AX-6 Reference infrastructure.** The reference stack wires the DynamoDB table, port 443 egress for vendor APIs, a readiness signal, JSON logs, two or - more tasks and an example rate-based rule at the load balancer, - since Axum's own limits are off by default (DEC-11). A store outage only - degrades identity (Failure handling), so the load balancer health check - stays green while identity reports itself degraded. - IABTechLab/edgezero#275 serves Axum on a current-thread runtime, so capacity - comes from task count. Accept: the stack applies with those properties, the - rate rule refuses traffic over its threshold, and with DynamoDB unreachable - pages keep serving while identity reports degraded. State: #1540 has the 443 - egress (IPv4 only), a static `/health`, plain logs, one task and no DynamoDB - wiring; EdgeZero's readiness probe does not poll dependencies. + more tasks and an example rate-based rule at the load balancer, since Axum's + own limits are off by default (DEC-11). A store outage only degrades identity + (Failure handling), so the load balancer health check stays green while + identity reports itself degraded. IABTechLab/edgezero#275 serves Axum on a + current-thread runtime, so capacity comes from task count. Accept: the stack + applies with those properties, the rate rule refuses traffic over its + threshold, and with DynamoDB unreachable pages keep serving while identity + reports degraded. State: #1540 at `db1ba337a` has the 443 egress (IPv4 only), + a `/health` check, one task, an opt-in CloudFront front door for geo, and a + DynamoDB table whose IAM policy allows one partition key and one allocator + item (AX-4); EdgeZero's readiness probe does not poll dependencies. - **AX-7 Ships from main.** Main never pins an EdgeZero RC tag (IABTechLab/edgezero#431, IABTechLab/edgezero#462) or a revision that exists only on an RC branch, such as `456313675`, which #1528 pins today. Main may @@ -642,11 +643,12 @@ Decided: to main rather than back-port the provider stack to it, which conflicted in 13 to 23 files at the heads first measured and 21 to 24 at the round-4 heads. #1531 restacks on #1506 for A1 and #1532 on #1531 for #1529. The image and - Terraform units (#1539, #1538, #1540) restack on #1531, where they apply - cleanly. #1536 and #1537 are not restacked: their Axum-only stores, store - selection and wiring give way to EZI-2b, T6 and T21, and their tests move into - A2, A8 and T5. #1535 goes into the EC route (T19, T20), and #1533 and #1534 - close as copies of #1525 and #1526. + Terraform units (#1539, #1538, #1540) restack on #1531: #1539 and #1538 apply + cleanly, and #1540 at `db1ba337a` conflicts in one file, + `docs/guide/native-ec-persistence.md`. #1536 and #1537 are not restacked: + their Axum-only stores, store selection and wiring give way to EZI-2b, T6 and + T21, and their tests move into A2, A8 and T5. #1535 goes into the EC route + (T19, T20), and #1533 and #1534 close as copies of #1525 and #1526. - **DEC-7 SharedID creation and retention.** Creation (user, 2026-11-12): when neither the record nor the request has a SharedID, the server creates one, because a browser may block Prebid.js from loading at all (WB-3). Retention @@ -838,4 +840,5 @@ threads, 101 in all, and opened nothing new. Threads 40 and 58 stayed open on one issue: sending only the latest waiting consent could drop a withdrawal made between a grant in flight and a later regrant. ID-9 and T20 now keep a waiting withdrawal as a barrier that later changes wait behind, and ID-9's Accept adds -the grant, withdrawal and regrant case. +the grant, withdrawal and regrant case. After it, #1516 merged on main and #1540 +and #1541 moved, so Part A, Part C, AX-6 and DEC-6 now record their new state. From c7dc9de77d31692412aec2768ae3f11fe0d52bbb Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Thu, 12 Nov 2026 20:27:04 -0800 Subject: [PATCH 31/31] Describe #1540's selected geo and log rounds 6 and 7 At db1ba337a #1540's example inputs already select a CloudFront front door with geo, fed through TS_NATIVE_GEO_PROFILE_TOML. AX-3 and AX-6 now say so, and Part C asks #1540 to bind geo through EdgeZero's manifest (EZI-4a) instead of turning it on. --- ...11-11-server-side-identity-all-adapters.md | 12 +++++---- ...erver-side-identity-all-adapters-design.md | 26 ++++++++++++++++--- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md index c2deaf842..c42e33237 100644 --- a/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md +++ b/docs/superpowers/plans/2026-11-11-server-side-identity-all-adapters.md @@ -475,11 +475,13 @@ re-checks each item at the PR's current head before posting. (ID-2, AX-4). #1537: do not port its Axum celld store or storage selection, which EdgeZero's providers and logical stores replace (EZI-2b, DEC-16); its example config also breaks startup on Axum, Cloudflare and Spin. #1539: fix - the failing Axum test. #1540: trust IPv6 CloudFront ranges, turn geo on, key - the DynamoDB table by partition instead of the one partition key and one - allocator item its IAM policy allows (AX-4), and add an example rate-based - rule at the load balancer (AX-3, AX-6, DEC-11). All units: replace `TS_*` - names (AX-2). #1541: drop the commits of closed #1545. + the failing Axum test. #1540: trust IPv6 CloudFront ranges, keep the geo its + example inputs select but bind it through EdgeZero's manifest (EZI-4a) instead + of `TS_NATIVE_GEO_PROFILE_TOML`, key the DynamoDB table by partition instead + of the one partition key and one allocator item its IAM policy allows (AX-4), + and add an example rate-based rule at the load balancer (AX-3, AX-6, DEC-11). + All units: replace `TS_*` names (AX-2). #1541: drop the commits of closed + #1545. - **Proposed closures.** #1527 once its tests move (DEC-1); #1528 once #1531 carries A1; #1533 and #1534, the RC copies of #1525 and #1526; #1535, #1536 and #1537 once T19, T20, A2 and A8 carry their code and tests; #1310, which diff --git a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md index a69781e09..b00dd2f38 100644 --- a/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md +++ b/docs/superpowers/specs/2026-11-11-server-side-identity-all-adapters-design.md @@ -176,7 +176,9 @@ marks the record contract and geo as supported. geo; a trusted IPv6 CloudFront hop is accepted; the reference deployment applies with geo on. State: IABTechLab/edgezero#409, IABTechLab/edgezero#439 and #1532 accept IPv6; the reference Terraform in #1540 trusts IPv4 ranges - only and leaves geo off. + only. At `db1ba337a` its example inputs turn on a CloudFront front door with + geo, which reaches the application through `TS_NATIVE_GEO_PROFILE_TOML` rather + than EdgeZero's geo binding (EZI-4a). - **AX-4 Record store fit for production.** Writes are spread across partition keys by listing prefix: an EC row's partition is its IP hash, and a withdrawal marker's is the marker prefix and its EC ID, so no item is updated @@ -201,9 +203,10 @@ marks the record contract and geo as supported. applies with those properties, the rate rule refuses traffic over its threshold, and with DynamoDB unreachable pages keep serving while identity reports degraded. State: #1540 at `db1ba337a` has the 443 egress (IPv4 only), - a `/health` check, one task, an opt-in CloudFront front door for geo, and a - DynamoDB table whose IAM policy allows one partition key and one allocator - item (AX-4); EdgeZero's readiness probe does not poll dependencies. + a `/health` check, one task, a CloudFront front door for geo that its example + inputs select, and a DynamoDB table whose IAM policy allows one partition key + and one allocator item (AX-4); EdgeZero's readiness probe does not poll + dependencies. - **AX-7 Ships from main.** Main never pins an EdgeZero RC tag (IABTechLab/edgezero#431, IABTechLab/edgezero#462) or a revision that exists only on an RC branch, such as `456313675`, which #1528 pins today. Main may @@ -842,3 +845,18 @@ between a grant in flight and a later regrant. ID-9 and T20 now keep a waiting withdrawal as a barrier that later changes wait behind, and ID-9's Accept adds the grant, withdrawal and regrant case. After it, #1516 merged on main and #1540 and #1541 moved, so Part A, Part C, AX-6 and DEC-6 now record their new state. + +### Round 6, 2026-11-13 + +Review 5885768720 at `de7b2268c`, a merge of main onto `3bbd297ed`, accepted +and resolved threads 40 and 58, so all 103 were resolved, found nothing new +and gave an APPROVE verdict. `403a62a5f` then recorded #1516's merge and the +moved #1540 and #1541, which moved the head past that verdict. + +### Round 7, 2026-11-13 + +Review 5885982220 at `403a62a5f` kept all 103 resolutions and opened one +should-fix, thread 104: at its new head #1540's example inputs already select +CloudFront geo, so AX-3's State and Part C's "turn geo on" were stale. Both +now describe that head, and Part C asks #1540 to bind geo through EdgeZero's +manifest (EZI-4a) instead of `TS_NATIVE_GEO_PROFILE_TOML`.