diff --git a/.lane/reports/fix-ads-conversion-placeholder-rereverify-2026-08-15.md b/.lane/reports/fix-ads-conversion-placeholder-rereverify-2026-08-15.md new file mode 100644 index 00000000..444417a5 --- /dev/null +++ b/.lane/reports/fix-ads-conversion-placeholder-rereverify-2026-08-15.md @@ -0,0 +1,59 @@ +# Lane 1 — Google Ads conversion placeholder tag re-verification (2026-08-15) + +Item: `d2c8a852ff` — "[unreviewed-by-opus] The funnel's only Google Ads +conversion measurement is dead by construction: placeholder tag" + +Branch: `fix/ads-conversion-placeholder-rereverify-2026-08-15-lane1` +Base: `origin/main` at `c62211c` (2026-08-15) + +## Verdict + +**Finding stays closed — no code change required.** The dead-by-construction +Google Ads conversion tag was removed by PR #172 (`60d045c`, merged +2026-08-13): the worker now generates the tag at request time from +`GOOGLE_ADS_CONVERSION_ID` / `GOOGLE_ADS_CONVERSION_LABEL`, emits it only on +`/brief-requested` when both are configured and well-formed, and emits +nothing otherwise. The CI guard in `scripts/check-site.mjs` makes the +placeholder shape impossible to return. The fix remains intact on both +current main (`c62211c`) and the live deployment. + +## Changes since the 2026-08-15 #227/#228 re-verification (base 23e24d6) + +No commits since `23e24d6` (#227/#228) touched the ads-conversion surface: +the diff range `23e24d6..HEAD` is empty for `src/worker.js`, +`public/brief-requested.html`, `public/brief-requested.js`, +`scripts/check-site.mjs` and `specs/003-wellness-clinic-launch/tracking-setup.md`. + +## Source verification (head c62211c) + +- `git merge-base --is-ancestor 60d045c HEAD` → yes. +- `src/worker.js` lines 1350-1383: `googleAdsConversion()` returns `null` + unless both env values are present and match `/^AW-\d{6,15}$/` and + `/^[A-Za-z0-9_-]{10,50}$/`; injection confined to `/brief-requested` + and `/brief-requested.js`. +- `public/brief-requested.html` / `public/brief-requested.js`: no loader, + no placeholder — only the explanatory comment; the script is a static + no-op the worker replaces when configured. +- `npm run check` passes (including the Google Ads guard section). +- `npm test` passes end to end: check-site, headings 6/6, sitemap 7/7, + worker 80/80 (incl. the three ads-tag tests), agent-UI 16/16, + product-contract 8/8, first-viewport 4/4, narrow-viewport — zero + failures across all suites. + +## Live verification (2026-08-15) + +- `GET https://tinystudio.io/brief-requested.html` → 307 → + `/brief-requested` → 200, strict CSP, zero `googletagmanager` / `AW-` + references (single "gtag" hit is the explanatory comment). +- `GET https://tinystudio.io/brief-requested.js` → 200, static no-op, no + `gtag(` / `dataLayer`. + +With the secrets unset, the funnel ships zero tracking rather than a dead +tag — exactly the designed behavior — and the tag activates from env with +no code change the moment the real conversion ID and label are set via +`wrangler secret put`. + +## Outcome + +Closed, no code change. Full evidence: +`docs/evidence/ads-conversion-placeholder-rereverify-2026-08-15-lane1.md`. diff --git a/docs/evidence/ads-conversion-placeholder-rereverify-2026-08-15-lane1.md b/docs/evidence/ads-conversion-placeholder-rereverify-2026-08-15-lane1.md new file mode 100644 index 00000000..6cf1bb1d --- /dev/null +++ b/docs/evidence/ads-conversion-placeholder-rereverify-2026-08-15-lane1.md @@ -0,0 +1,124 @@ +# Google Ads conversion placeholder tag — re-verification 2026-08-15 (lane 1) + +Item: `d2c8a852ff` — "[unreviewed-by-opus] The funnel's only Google Ads +conversion measurement is dead by construction: placeholder tag" + +## Verdict + +**Already fixed on main and live — re-verified, stays closed. No code change +was needed.** The dead-by-construction shape was removed by PR #172 +(`60d045c`, merged 2026-08-13); the close-out has been recorded by #215 +(`0e483ca`, 2026-08-14), #216 (`76ee0e7`, 2026-08-15) and #227/#228 +(`23e24d6`, 2026-08-15). This lane (1) re-verified the fix against the +current `origin/main` head (`c62211c`) and the live deployment on +2026-08-15. + +## What was dead (pre-#172) + +- `public/brief-requested.html` hardcoded the gtag loader with placeholder + `AW-XXXXXXXXX` (from the page's first commit `c90c8a4` / PR #14). +- `public/brief-requested.js` fired the conversion event to the same + placeholder (`AW-XXXXXXXXX/YYYYYYYYYYYYYYYYYYY`). +- The production CSP (`script-src 'self' + https://static.cloudflareinsights.com`) blocked `googletagmanager.com` + entirely, so even a real id pasted in could never load or record. + +## The merged fix (PR #172, `60d045c`) + +- The conversion tag is generated by the worker at request time from + `GOOGLE_ADS_CONVERSION_ID` / `GOOGLE_ADS_CONVERSION_LABEL`, emitted only + on `/brief-requested` when both are configured and well-formed + (`GOOGLE_ADS_ID_PATTERN` `/^AW-\d{6,15}$/`, `GOOGLE_ADS_LABEL_PATTERN` + `/^[A-Za-z0-9_-]{10,50}$/` — strict patterns, safe to interpolate). +- With either value missing or malformed, the page ships with **no tag at + all** — a dead tag is never served. +- gtag's CSP allowances (`GOOGLE_ADS_CSP`) are scoped to that one noindex + page's response; every other page keeps the strict CSP. +- `public/brief-requested.js` is a documented static no-op; the worker + serves the generated conversion script in its place when configured. +- CI (`scripts/check-site.mjs` "Google Ads conversion tag" guard) refuses + any placeholder or hardcoded gtag in `public/` or `src/worker.js`, and + requires the env-driven wiring to stay wired — the dead-by-construction + shape cannot return. +- `specs/003-wellness-clinic-launch/tracking-setup.md` documents the + `wrangler secret put` steps that activate the tag once the console-side + conversion action exists. + +## Source checks on the current head (c62211c) + +1. `git merge-base --is-ancestor 60d045c HEAD` → yes, the fix commit is in + the current head. No commit since the #227/#228 close-out + (`23e24d6`, 2026-08-15) touched `src/worker.js`, + `public/brief-requested.html`, `public/brief-requested.js`, + `scripts/check-site.mjs` or `specs/003-wellness-clinic-launch/tracking-setup.md` + (empty diff range `23e24d6..HEAD` for those files). +2. `src/worker.js` lines 1350-1383: `googleAdsConversion()` returns `null` + (emits nothing) unless both env values are present and well-formed; the + loader/script injection is confined to `/brief-requested` and + `/brief-requested.js`. +3. `public/brief-requested.html` carries only the explanatory comment that + the worker injects the tag; no loader, no placeholder. +4. `public/brief-requested.js` is a documented static no-op (8 lines). +5. `npm run check` passes — including the "Google Ads conversion tag" guard + section (placeholder scan, no hardcoded gtag in public files, worker + wiring present). +6. `npm test` passes end to end on this head: check-site, headings 6/6, + sitemap 7/7, worker 80/80 (including the three ads-tag tests: no tag + + strict CSP while unconfigured; injected loader + generated script + + page-scoped CSP when configured; nothing emitted for partial or + malformed config), agent-UI 16/16, product-contract 8/8, first-viewport + 4/4, narrow-viewport — zero failures across all suites. + +## Live re-verification 2026-08-15 + +Plain-fetch probes against the live `https://tinystudio.io` (current +deployment of current main): + +``` +GET https://tinystudio.io/brief-requested.html → HTTP 307 → /brief-requested +GET https://tinystudio.io/brief-requested → HTTP/2 200 + content-security-policy: default-src 'self'; img-src 'self' data:; + style-src 'self' https://fonts.googleapis.com; font-src 'self' + https://fonts.gstatic.com; script-src 'self' + https://static.cloudflareinsights.com; connect-src 'self' + https://cloudflareinsights.com; base-uri 'self'; frame-ancestors + 'none'; form-action 'self' + body: zero googletagmanager / AW- references; the single "gtag" hit is + the explanatory HTML comment (line 18) — no loader, no event +GET https://tinystudio.io/brief-requested.js → 200, static no-op, no + gtag( / dataLayer +``` + +The served `/brief-requested` contains no loader and no conversion event, +and carries the strict CSP — the page-scoped `GOOGLE_ADS_CSP` is not +applied because the env values are unconfigured, so no tag is emitted. +This is exactly the designed behavior: with the secrets unset, the funnel +ships zero tracking rather than a dead tag, and the tag activates the +moment the real conversion id/label are set via `wrangler secret put` +(per `specs/003-wellness-clinic-launch/tracking-setup.md` §3) — without +any code change. + +## Repro steps + +1. Source guard: `npm run check` — the "Google Ads conversion tag" section + fails if any placeholder or hardcoded gtag reappears in `public/` or + `src/worker.js`, or if the env-driven wiring is removed. +2. Worker behavior: `node --test scripts/test-agent-worker.mjs` — the ads + tests cover unconfigured / configured / partial-malformed config. +3. Live probe: + `curl -s https://tinystudio.io/brief-requested | grep -c googletagmanager` + → 0, and the response carries the strict CSP (no `googleadservices` / + `googletagmanager` allowances) while the secrets are unset. + +## Close-out + +The item as stated — "The funnel's only Google Ads conversion measurement +is dead by construction: placeholder tag" — is **closed against current +main and live**: the dead-by-construction shape was removed by PR #172 +(`60d045c`), the CI guards in `scripts/check-site.mjs` make the placeholder +shape impossible to return, `npm run check` and `npm test` pass on the +current head (`c62211c`, zero failures), and the live site serves +`/brief-requested` with no tag, the static no-op script, and the strict +CSP while the env values are unconfigured — nothing dead is shipped, and +the tag activates from env with no code change the moment the real +conversion ID and label are set.