<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>GA4 Server-Side Relay Blog</title>
        <link>https://ga4-server-side-relay.vercel.app/blog</link>
        <description>GA4 Server-Side Relay Blog</description>
        <lastBuildDate>Mon, 24 Aug 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[M0.3 — Google-native first-party mode check]]></title>
            <link>https://ga4-server-side-relay.vercel.app/blog/first-party-mode</link>
            <guid>https://ga4-server-side-relay.vercel.app/blog/first-party-mode</guid>
            <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Status: blocked in this environment. Requires a live Vercel deployment and a real gtag-instrumented site to verify whether Google's own first-party mode for Google tags is usable on Vercel deployments. No deployed environment is available here.]]></description>
            <content:encoded><![CDATA[<p><strong>Status: blocked in this environment.</strong> Requires a live Vercel deployment and a real <code>gtag</code>-instrumented site to verify whether Google's own first-party mode for Google tags is usable on Vercel deployments. No deployed environment is available here.</p>
<p><strong>What this blocks:</strong> scoping M3.1 (script-loading/proxying for <code>gtag.js</code>) around a supported mechanism instead of building parallel plumbing. Per the plan (1.7), this spike has <strong>no bearing</strong> on the Edge-vs-Node runtime choice for the collect handler — that's decided independently on latency/library-compatibility grounds, which US-007 does without needing this spike.</p>
<p><strong>Default assumption taken in its absence:</strong> M3.1 (script proxying) stays out of v1 scope, as the plan already specifies — nothing in M1/M2 implementation depends on this spike's outcome.</p>
<p><strong>To unblock:</strong> deploy <code>apps/demo</code> to a real Vercel project and test <code>gtag('config', ..., { server_container_url: ... })</code> against it.</p>]]></content:encoded>
            <category>verification</category>
            <category>spike</category>
        </item>
        <item>
            <title><![CDATA[M0.1 — MP v2 fidelity spike]]></title>
            <link>https://ga4-server-side-relay.vercel.app/blog/mp-fidelity</link>
            <guid>https://ga4-server-side-relay.vercel.app/blog/mp-fidelity</guid>
            <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Status: partially unblocked (2026-08-24) — live-verified against a real GA4 property.]]></description>
            <content:encoded><![CDATA[<p><strong>Status: partially unblocked (2026-08-24) — live-verified against a real GA4 property.</strong></p>
<p>Verified live against <code>catesworks.dev</code> (GA4 property 551179302, measurement ID <code>G-YC0JKCQVXY</code>),
using the package's own <code>buildMpPayload</code>/<code>collectUrl</code>/<code>debugCollectUrl</code> shape sent directly at
<code>/debug/mp/collect</code> and <code>/mp/collect</code>:</p>
<ul>
<li class=""><strong>Real bug found and fixed</strong>: <code>buildMpPayload</code> sent both a manual <code>session_id</code> param <em>and</em>
<code>ga_session_id</code>. GA4's live debug endpoint flagged this as <code>NAME_DUPLICATED</code> — GA4 canonicalizes
<code>ga_session_id</code> to the same internal session field, so the redundant <code>session_id</code> collided
with it instead of adding information. Fixed in <code>src/server/mp-client.ts</code> (only <code>ga_session_id</code>/
<code>ga_session_number</code>/<code>engagement_time_msec</code> are sent now); see <code>mp-client.test.ts</code> for the
regression test and the package CHANGELOG for the release note.</li>
<li class=""><strong>Realtime visibility confirmed</strong>: after the fix, a <code>POST /mp/collect</code> with only
<code>ga_session_id</code>/<code>ga_session_number</code>/<code>engagement_time_msec</code> (no <code>session_start</code>/<code>first_visit</code>,
no gtag.js involved) returned <code>204</code> and the event (<code>mp_relay_spike_verification</code>) appeared in
GA4's Realtime "Event count by Event name" report within ~30-60s, with "Active users in last
30 minutes" incrementing. This confirms the M0.1 finding (a) assumption baked into
<code>buildMpPayload</code> was directionally correct, modulo the duplicate-param bug above.</li>
<li class=""><strong><code>ip_override</code> (AC30): inconclusive, not a negative result.</strong> Two follow-up events sent with
<code>ip_override: "8.8.8.8"</code> (Google Public DNS — a well-known non-residential/datacenter IP) never
appeared in Realtime, despite both returning <code>204</code>. The most likely explanation is GA4's bot/
spam filtering excluding traffic from a widely-recognized datacenter IP, not a failure of
<code>ip_override</code> itself — real customer traffic comes from residential/mobile IPs, so this
artifact is specific to the synthetic test IP chosen, not the relay's implementation. Re-running
this check with a real residential/mobile IP (or accepting that this can only be confirmed from
genuine end-user traffic post-launch) would close this out fully.</li>
<li class=""><strong>Device/browser dimension fidelity from a forwarded user-agent header alone</strong>: not conclusively
checked in this pass — the Realtime "Tech" report wasn't reachable via direct URL navigation in
the time available, and chasing this further had diminishing returns relative to the two
confirmed findings above. Still open.</li>
</ul>
<p><strong>Still blocked / out of scope for a single pass</strong>: Traffic-acquisition and Engagement report
settling can take up to 48h, which doesn't fit a single automated verification session. Whether
synthesizing <code>session_start</code>/<code>first_visit</code> from the relay's session cookie would further close
the fidelity gap vs. real sGTM is also still untested — the events sent in this spike were sent
without those synthesized events, on purpose, to isolate the base MP v2 fidelity question first.</p>
<p><strong>Default assumption from the original plan (still holds as the baseline)</strong>: MP v2 has a fidelity
gap vs. real sGTM (no automatic <code>session_start</code>/<code>first_visit</code>, no Google Signals, no automatic
attribution) — accepted as a known, documented limitation of v1, not something implementation
blocks on.</p>
<p><strong>To fully close this spike</strong>: re-run the <code>ip_override</code> check from a real residential/mobile IP
(or wait for genuine end-user traffic), check the Realtime Tech report for device/browser
dimension population, and let a Traffic-acquisition/Engagement report settle for 48h+ on a
property receiving steady synthetic or real traffic.</p>]]></content:encoded>
            <category>verification</category>
            <category>spike</category>
        </item>
        <item>
            <title><![CDATA[Playwright browser e2e — unblocked, all 3 ACs browser-confirmed]]></title>
            <link>https://ga4-server-side-relay.vercel.app/blog/playwright-browser-e2e</link>
            <guid>https://ga4-server-side-relay.vercel.app/blog/playwright-browser-e2e</guid>
            <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Status: RESOLVED (2026-08-24). All three ACs this spike targeted (AC14, AC16, AC27) are]]></description>
            <content:encoded><![CDATA[<p><strong>Status: RESOLVED (2026-08-24).</strong> All three ACs this spike targeted (AC14, AC16, AC27) are
now real-browser-confirmed. <code>pnpm exec playwright test --project=chromium</code> passes 4/4.</p>
<p>The original blocker — no Playwright browser binaries available — is resolved.
<code>pnpm exec playwright install chromium</code> succeeded (Chrome for Testing 151.0.7922.34,
<code>/Volumes/dev-ssd/caches/playwright/chromium-1234</code>). Firefox/webkit are present in the cache
but at stale versions vs. what this Playwright wants, so <code>chromium</code> is the only project
currently runnable here.</p>
<h2 class="anchor anchorTargetStickyNavbar_AuBu" id="what-was-added">What was added<a href="https://ga4-server-side-relay.vercel.app/blog/playwright-browser-e2e#what-was-added" class="hash-link" aria-label="Direct link to What was added" title="Direct link to What was added" translate="no">​</a></h2>
<ul>
<li class=""><code>packages/ga4-relay/e2e/helpers.ts</code> — shared helpers wrapping the real <code>navigator.serviceWorker</code>
API (<code>waitForRegistrationActive</code>, <code>getRegistrationSummaries</code>, <code>registerAndWaitForActive</code>,
<code>unregisterAllServiceWorkers</code>). No mocking — every call goes through <code>page.evaluate</code> into the
actual browser API.</li>
<li class=""><code>packages/ga4-relay/e2e/ac27-scope.spec.ts</code> — AC27: <code>registration.scope</code> resolves to the
configured nested <code>/ga4-relay/</code> scope, both via <code>ga4-init.tsx</code>'s real auto-registration and via
an explicit <code>navigator.serviceWorker.register()</code> call.</li>
<li class=""><code>packages/ga4-relay/e2e/ac14-coexistence.spec.ts</code> — AC14: a trivial no-op host SW at root scope
(<code>apps/demo/public/test-sw.js</code>) plus the resilience SW at its nested scope both remain
registered and active, including after a reload.</li>
<li class=""><code>packages/ga4-relay/e2e/ac16-kill-switch.spec.ts</code> — AC16: real <code>registration.unregister()</code> on
the resilience SW leaves the site loading/rendering with zero uncaught page errors.</li>
<li class=""><code>apps/demo/public/test-sw.js</code> — trivial no-op SW used only by the AC14 spec.</li>
<li class=""><code>apps/demo/.env.local</code> (gitignored) — placeholder <code>GA4_*</code>/<code>UPSTASH_*</code> values so the demo app
can boot without real credentials.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_AuBu" id="bug-1-real-product-bug-fixed-sw-route-500s-in-both-dev-and-prod">Bug #1 (real product bug, fixed): SW route 500s in both dev and prod<a href="https://ga4-server-side-relay.vercel.app/blog/playwright-browser-e2e#bug-1-real-product-bug-fixed-sw-route-500s-in-both-dev-and-prod" class="hash-link" aria-label="Direct link to Bug #1 (real product bug, fixed): SW route 500s in both dev and prod" title="Direct link to Bug #1 (real product bug, fixed): SW route 500s in both dev and prod" translate="no">​</a></h2>
<p><code>GET /ga4-relay/ga4-sw.js</code> returned <code>500</code> in both <code>next dev</code> and <code>next build &amp;&amp; next start</code>.
Root cause: <code>apps/demo/app/ga4-relay/ga4-sw.js/route.ts</code>'s <code>loadScriptSource()</code> did
<code>createRequire(import.meta.url).resolve("@gtmss/ga4-relay/sw-script")</code> — inside a Next.js Route
Handler, webpack intercepts <code>require.resolve</code> and returns a bundler-internal identifier instead
of a real filesystem path (<code>"(rsc)/../../packages/ga4-relay/dist/sw/index.js"</code> in dev, a numeric
module id in prod), so <code>readFile()</code> on the result always threw. This affected every real consumer
following the documented pattern, not just the demo.</p>
<p><strong>Fix</strong>: moved script-loading inside the package itself. <code>createServiceWorkerHandler</code>'s
<code>loadScriptSource</code> stays a required, explicit parameter (unchanged signature), but the package now
ships <code>loadBundledSwScript</code> from a new <code>@gtmss/ga4-relay/server/sw-script-loader</code> subpath —
consumers pass that instead of hand-rolling their own loader:</p>
<div class="language-ts codeBlockContainer_L68l theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_gMLv"><pre tabindex="0" class="prism-code language-ts codeBlock_OHXo thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_Fp9H"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> createServiceWorkerHandler </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@gtmss/ga4-relay/server"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> loadBundledSwScript </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"@gtmss/ga4-relay/server/sw-script-loader"</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">export</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">const</span><span class="token plain"> </span><span class="token constant" style="color:#36acaa">GET</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">createServiceWorkerHandler</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> scope</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/ga4-relay/"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> loadScriptSource</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> loadBundledSwScript </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p><code>loadBundledSwScript</code> reads <code>dist/sw/index.js</code> relative to its own module's <code>import.meta.url</code> —
resolved inside the package's own code (which <code>next.config.mjs</code>'s <code>serverExternalPackages</code> keeps
un-bundled), not inside app-bundled route-handler code, so it's never subject to the interception
above. It's built as a <strong>separate tsup entry/output file</strong> from <code>./server</code> (see <code>tsup.config.ts</code>)
specifically so its <code>node:fs</code> import never lands in the same bundle as <code>withGa4Token</code>, which Edge
middleware imports from <code>./server</code> — confirmed live: without this split, a build produced
<code>A Node.js module is loaded ('url' at line 899) which is not supported in the Edge Runtime</code> for
<code>apps/demo/middleware.ts</code>.</p>
<p>A second, subtler bug surfaced fixing this: <code>fileURLToPath(new URL("../sw/index.js", import.meta.url))</code>
threw <code>TypeError: The "path" argument must be of type string or an instance of URL. Received an instance of URL</code> at runtime under Next's <code>serverExternalPackages</code> interop — a cross-realm
<code>instanceof URL</code> mismatch between whatever constructs <code>import.meta.url</code>'s URL-like value in that
interop layer and Node's own <code>fileURLToPath</code>. Fixed by avoiding the <code>URL</code> class entirely: plain
string manipulation on the <code>file://</code>-prefixed <code>import.meta.url</code> string
(<code>packages/ga4-relay/src/server/sw-script-loader.ts</code>).</p>
<p>Verified live via direct <code>curl</code> against both <code>next start</code> (prod) and <code>next dev</code>: <code>200</code>,
<code>content-type: text/javascript</code>, <code>cache-control: no-cache</code>, <code>service-worker-allowed: /ga4-relay/</code>,
real SW script bytes.</p>
<h2 class="anchor anchorTargetStickyNavbar_AuBu" id="bug-2-test-environment-artifact-not-a-product-bug-dev-mode-auto-reload-race">Bug #2 (test-environment artifact, not a product bug): dev-mode auto-reload race<a href="https://ga4-server-side-relay.vercel.app/blog/playwright-browser-e2e#bug-2-test-environment-artifact-not-a-product-bug-dev-mode-auto-reload-race" class="hash-link" aria-label="Direct link to Bug #2 (test-environment artifact, not a product bug): dev-mode auto-reload race" title="Direct link to Bug #2 (test-environment artifact, not a product bug): dev-mode auto-reload race" translate="no">​</a></h2>
<p>With bug #1 fixed, <code>next dev</code> still flaked on the auto-registration specs (AC14, and AC27's
auto-registration case). Live-instrumented (<code>page.on("framenavigated")</code>) reproduction showed an
<strong>unexpected second navigation</strong> firing ~20-30ms after the initial <code>load</code> — Next.js dev mode's
on-demand route compilation (the SW route hadn't been hit yet, so Next compiles it lazily,
~700ms) triggers a client-side reload the first time a not-yet-compiled route is requested. That
reload raced the resilience SW's registration, non-deterministically resetting it mid-activation.
A production build never does this. <strong>Fixed by pointing <code>playwright.config.ts</code>'s <code>webServer</code> at a
production build (<code>next build &amp;&amp; next start</code>)</strong> instead of <code>next dev</code> — also the more correct
target for validating real browser SW behavior, not dev-server hot-reload quirks.</p>
<p>One further flake remained even under production (isolated to the auto-registration spec, not
reproducible via manual live instrumentation with <code>register()</code> call-counting, byte-identical
script confirmation, or full <code>updatefound</code>/<code>statechange</code> event logging — root cause not
conclusively isolated, possibly Chromium-internal SW bookkeeping settling around the same tick as
the demo's own <code>track()</code> call firing a <code>fetch</code> to <code>/api/ga4/collect</code>). Hardened
<code>ac27-scope.spec.ts</code>'s assertion with <code>expect.poll</code> + a 250ms settle-and-reconfirm instead of a
single instantaneous read — the correct way to assert "reaches a <em>stable</em> active state" for async
browser state regardless. Stable across repeated runs since.</p>
<h2 class="anchor anchorTargetStickyNavbar_AuBu" id="also-fixed-while-here-dev-mode-double-registration-hazard-in-the-reference-pattern">Also fixed while here: dev-mode double-registration hazard in the reference pattern<a href="https://ga4-server-side-relay.vercel.app/blog/playwright-browser-e2e#also-fixed-while-here-dev-mode-double-registration-hazard-in-the-reference-pattern" class="hash-link" aria-label="Direct link to Also fixed while here: dev-mode double-registration hazard in the reference pattern" title="Direct link to Also fixed while here: dev-mode double-registration hazard in the reference pattern" translate="no">​</a></h2>
<p>Unrelated to the two bugs above, but found live while investigating: React's App Router dev mode
double-invokes effects, which would call <code>Ga4Init</code>'s <code>createGa4Client()</code> (and thus
<code>registerServiceWorker()</code>) twice per mount. Guarded with a module-level flag (survives the
dev-mode remount, unlike a <code>useRef</code>) in both <code>apps/demo/app/ga4-init.tsx</code> and the root README's
client usage example, since any real consumer copying that exact snippet would hit it too.</p>
<h2 class="anchor anchorTargetStickyNavbar_AuBu" id="net-status-vs-the-original-three-acs">Net status vs. the original three ACs<a href="https://ga4-server-side-relay.vercel.app/blog/playwright-browser-e2e#net-status-vs-the-original-three-acs" class="hash-link" aria-label="Direct link to Net status vs. the original three ACs" title="Direct link to Net status vs. the original three ACs" translate="no">​</a></h2>
<ul>
<li class=""><strong>AC27</strong> (nested scope): browser-confirmed, both auto-registration and explicit <code>register()</code>.</li>
<li class=""><strong>AC14</strong> (coexistence): browser-confirmed — both SWs remain registered and active, including
across a reload.</li>
<li class=""><strong>AC16</strong> (kill-switch): browser-confirmed — <code>unregister()</code> leaves the site fully functional.</li>
</ul>
<p>The SW beacon-capture spike remains a separate, still-open question (unload-time beacon
capture) — not attempted in this pass, see that spike's own post.</p>]]></content:encoded>
            <category>verification</category>
            <category>spike</category>
        </item>
        <item>
            <title><![CDATA[M0.4 — Safari ITP cookie-longevity observation]]></title>
            <link>https://ga4-server-side-relay.vercel.app/blog/safari-itp-observation</link>
            <guid>https://ga4-server-side-relay.vercel.app/blog/safari-itp-observation</guid>
            <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Status: blocked in this environment. Requires an 8+ day real-Safari observation against a deployed, genuinely same-origin site — not reproducible in a single automated pass, and no Safari/WebKit device with persistent state across days is available here.]]></description>
            <content:encoded><![CDATA[<p><strong>Status: blocked in this environment.</strong> Requires an 8+ day real-Safari observation against a deployed, genuinely same-origin site — not reproducible in a single automated pass, and no Safari/WebKit device with persistent state across days is available here.</p>
<p><strong>What this blocks:</strong> empirically confirming the ADR's claim that a genuinely same-origin <code>Set-Cookie</code> is not subject to ITP's 7-day <code>document.cookie</code> cap (the cap applies to script-set/CNAME-cloaked cookies, not server-set same-origin ones, by ITP's own documented design).</p>
<p><strong>Default assumption taken in its absence</strong> (per the plan's ADR): the cookie-longevity advantage over CNAME-based vendors is stated as <strong>mechanism-derived</strong> but <strong>not yet empirically validated</strong> — this qualifier stays in the ADR and in this repo until a real 8-day observation reports. Implementation is not blocked on this: <code>cookies.ts</code> (US-002) sets a standard <code>Max-Age=63072000</code> server-side cookie regardless of the outcome, since that's the correct implementation either way — the spike only confirms how long it actually survives in Safari specifically.</p>
<p><strong>To unblock:</strong> deploy <code>apps/demo</code> to a real same-origin domain and track the <code>client_id</code> cookie's survival in Safari over 8+ days.</p>]]></content:encoded>
            <category>verification</category>
            <category>spike</category>
        </item>
        <item>
            <title><![CDATA[M0.2 — SW beacon-capture spike]]></title>
            <link>https://ga4-server-side-relay.vercel.app/blog/sw-beacon-capture</link>
            <guid>https://ga4-server-side-relay.vercel.app/blog/sw-beacon-capture</guid>
            <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Status: blocked in this environment. Requires launching real cross-browser instances (Chromium/Firefox/WebKit) with a live service worker and asserting whether the SW's fetch handler observes an outgoing fetch(keepalive)/sendBeacon call during page unload. This environment has no Playwright browser binaries installed and no ability to reliably simulate real unload timing across three engines in this pass.]]></description>
            <content:encoded><![CDATA[<p><strong>Status: blocked in this environment.</strong> Requires launching real cross-browser instances (Chromium/Firefox/WebKit) with a live service worker and asserting whether the SW's <code>fetch</code> handler observes an outgoing <code>fetch(keepalive)</code>/<code>sendBeacon</code> call during page unload. This environment has no Playwright browser binaries installed and no ability to reliably simulate real unload timing across three engines in this pass.</p>
<p><strong>What this blocks:</strong> deciding whether the service worker can be promoted from retry-only to a primary capture layer (Pre-mortem #1).</p>
<p><strong>Default assumption taken in its absence</strong> (per plan Pre-mortem #1 and Requirements Summary): the SW's role <strong>defaults to retry-only</strong> — queued from the main thread on <code>fetch</code>/<code>sendBeacon</code> failure or offline detection, never assumed to reliably intercept unload-time beacons. This is the safe default and is what US-016 implements. Promotion to a capture role remains gated behind this spike passing, not assumed.</p>
<p><strong>To unblock:</strong> install Playwright browser binaries (<code>pnpm exec playwright install</code>) and run the standing regression test referenced in the plan's Verification Steps once a target environment supports launching real browsers.</p>
<p><strong>Update:</strong> Chromium browser binaries are now installed and launchable in this environment (see the Playwright browser e2e spike, updated in the same pass as this note). That spike's own AC14/AC16/AC27 e2e specs are a different question (SW registration/coexistence/kill-switch) and were not extended to cover beacon/fetch(keepalive) capture during unload — this spike's own "To unblock" steps (a dedicated beacon-capture regression test, ideally cross-browser) were not attempted here. Verdict below is unchanged.</p>]]></content:encoded>
            <category>verification</category>
            <category>spike</category>
        </item>
    </channel>
</rss>