Appendix F·Appendix
Implementation Timeline
This appendix records the reconstruction of the {COMPANY} GA4 property (534525683, measurement ID G-WH410Z73V1) in the order it happened, and explains why each step preceded the next. The sequence is not arbitrary. It follows one governing rule: fix correctness before you build architecture, build architecture before you activate it, and activate before you optimize. A reader who inverts that order pays for it. Registering a custom dimension against an event that never fires wastes the registration. Linking Google Ads before the server conversion reaches GA4 imports a broken signal into a bidding system. Raising data retention after a month of thin collection preserves nothing worth keeping. Every step below earns its position because the step after it depends on the state the earlier step leaves behind.
The sequencing principle
Correctness first, because a measurement system that reports the wrong number is worse than one that reports nothing: the wrong number invites action. Architecture second, because durable structure (event taxonomy, custom dimensions, centralized page_view) is only worth building once the data flowing through it is trustworthy. Activation third, because the offline-revenue loop and the reporting surface consume the corrected, structured data; they cannot precede it. Optimization last, and in this case deliberately deferred, because at roughly 46 paid clicks and 500 sessions per month the property sits below the volume where Google data-driven attribution and value-based Smart Bidding stabilize (Chapter 16, "Lessons, Limitations, Future Work"). Building the loop still earns its cost-per-booked-job visibility even where the automated bidding will not fully engage.
Stage A: the two-layer audit
Step 1. Programmatic inventory via the Admin and Data APIs. The reconstruction began with measurement, not change. A service account, id-01-{company}@gen-lang-client-0444184725.iam.gserviceaccount.com, reached through impersonation (gcloud auth print-access-token --impersonate-service-account), read the property state end to end: accountSummaries, the property, dataStreams, customDimensions, keyEvents, dataRetentionSettings, enhancedMeasurementSettings, and googleSignalsSettings. This came first because you cannot prioritize defects you have not enumerated. The inventory established the baseline (2,328 events, 401 active users, 527 sessions over 30 days; retention at the TWO_MONTHS floor; eight custom dimensions; six key events including a purchase that never fired) and surfaced the traffic-source paradox: google/cpc spend with no Google Ads link. Chapter 4, "Auditing Methodology," documents the endpoints and scopes.
Step 2. The 18-agent adversarial review. The programmatic pass tells you what the configuration is; it does not tell you what it means or where it lies. A workflow of 18 subagents then researched nine measurement pillars and adversarially fact-checked each, roughly 943,000 tokens over about 18 minutes. The order matters: the machine inventory fed the agents ground truth so their claims attached to real values, and the verify pass then corrected several plausible but wrong conclusions before any of them reached a change ticket. That pass reclassified Consent Mode v2 as an EEA/UK Google Ads requirement rather than a US legal mandate, confirmed the Measurement Protocol payload carried no PII, reframed Google Signals as a reporting-threshold harm at this volume, flagged the 2026-06-15 block on the legacy Google Ads offline-upload path, and caught a collision the inventory alone missed: Enhanced Measurement form interactions auto-fires GA4's own form_start and form_submit, duplicating the site's custom events. Catching that in audit, not in production, is the return on the two-layer design.
Stage B: Phase 0, zero-risk Admin API changes
These came before any code deploy because they carry no rollback risk and start clocks that later steps depend on.
Step 3. Raise event data retention from TWO_MONTHS to FOURTEEN_MONTHS. A single dataRetentionSettings PATCH. The change is not retroactive and takes about 24 hours to apply, so doing it first maximizes the window of by-service and by-county seasonal history available to later Explorations. Standard aggregate reports retain indefinitely; the 14-month cap bites only funnels and Explorations that use event- or user-scoped custom dimensions, which is exactly the seasonal analysis this pest-control business needs.
Step 4. Delete the cta_click key event. Removed to stop an interaction-level event inflating the conversion count.
Step 5. Confirm Google Signals off. Verified already disabled, so no action, but recorded so the later attribution work does not reintroduce the reporting-threshold problem. The purchase key event could not be deleted (the API returns "The event cannot be deleted", INVALID_ARGUMENT); it stays inert because the site has no ecommerce.
Stage C: Phase 1, the correctness deploy
Step 6. Fix the Measurement Protocol host and fields. This is the keystone. Both api/contact/route.ts line 384 and api/careers/route.ts line 288 posted to https://www.google.com/mp/collect, which returns HTTP 404; the documented https://www.google-analytics.com/mp/collect returns 204. Live curl against both hosts confirmed 404 versus 204. The server generate_lead had therefore never reached GA4 across the property lifetime. The rewrite corrected the host; sent only when a real ga_client_id is present (dropping the server.<timestamp> fallback that minted phantom users); added session_id parsed from the _ga_<container> cookie in both GS1 dot and GS2 dollar formats; set engagement_time_msec to 1; set ip_override to the request IP so the conversion geolocates to the user; replaced the flat value 150 with a provisional internal per-service map (termite 400, fumigation 500, commercial 400, bed-bug 250, rodent 180, general and pest 150, ant and wasp-bee 100, other 120); renamed form_name to form_source; dropped bare source/medium/campaign, which GA4 misreads as manual traffic signals; and logged non-200 responses instead of swallowing them. The careers server event was removed, not renamed, because the client career_application_submit key event already covers that low-volume form.
Step 7. Register the missing custom dimensions. service_type, cta_label, and error_message registered only now, after step 6 guaranteed the events carrying them actually fire. error_message is safe because the code sends only bounded codes such as server_error and network_error, never raw user text.
Step 8. Promote generate_lead to a key event. Done after, not before, the host fix, because promoting an event that returns zero data would have manufactured a false conversion of record.
Step 9. Centralize page_view. The config set send_page_view:false; a single RouteAnalytics client component now fires one page_view per route with page_type and the relevant slug from usePathname; the GA load strategy moved from lazyOnload to afterInteractive so the tag sets the _ga cookie before a fast visitor submits; the duplicate trackPageView call was removed.
Step 10. Resolve the Enhanced Measurement collisions. pageChangesEnabled and formInteractionsEnabled switched off, because they double-fired page_view on client navigation and duplicated the custom form events (the collision the verify pass caught in step 2).
Step 11. Add Consent Mode defaults. Two gtag('consent','default',...) calls before config: a global default of granted so US analytics is unaffected, then a region-scoped denied for 32 EEA, UK, and Switzerland codes with wait_for_update 500. The dataLayer was inspected to confirm the global default resolved to granted.
Verification of Phase 1
Step 12. The payload validated against https://www.google-analytics.com/debug/mp/collect and returned validationMessages: []. A production generate_lead to the live endpoint returned HTTP 204 and appeared in GA4 Realtime as a registered key event within about 20 seconds. Headless Chrome read the dataLayer to confirm page_view centralization: the homepage pushed {page_type: home}, /services/termite-control pushed {page_type: service, service_slug: termite-control}, /locations/salinas pushed {page_type: location, location_slug: salinas}, one push per route. The database gave the honest capture rate: of 29 leads, 20 carried a ga_client_id and 9 did not, 69 percent, with the missing 31 percent attributable to ad-block and privacy-browser loss rather than timing (the _ga cookie persists two years once set). Row id 32, a real submission through the header quote panel on a clean Windows Chrome user-agent, landed with an empty ga_client_id, confirming the browser sent no _ga cookie and that the code correctly skipped the Measurement Protocol send. Two constraints shaped this: headless Chrome could not reliably load gtag.js from the CDN, and Cloudflare Turnstile blocked automated submissions, so a human submission was the only full-path test.
Stage D: Phase 2, foundation without the Ads link
Step 13. With correctness verified, the loop's data plumbing went in ahead of any Ads link. The site now captures wbraid and gbraid alongside gclid, generates a lead_id UUID per lead, and gained stage, job_value, and closed_at columns via a Drizzle change plus a psql ALTER TABLE on production. An auth-guarded admin editor sets stage (new, contacted, quoted, won, lost) and booked value through a Server Action that calls requireAdmin() to verify the signed admin_session cookie, because Server Actions must guard themselves. A native /admin/insights dashboard, driven entirely by the property's own PostgreSQL data, renders KPI tiles, a pipeline by stage, and leads by service, source, and month, with honest empty states. This replaced a blocked Looker Studio task. MEASUREMENT_PLAN.md was committed as the living taxonomy contract (Chapter 8).
Stage E: BigQuery and the permission wall
Step 14. The GA4-to-BigQuery daily export, free at roughly 5 MB of events per month against the 10 GB / 1 TB free tier. bigQueryLinks.create returned HTTP 403 for the service account even after GA4 Administrator and roles/bigquery.admin, because the link flow makes GA grant its own export service account project-level IAM, which the automation account should not hold. The owner created the link in the GA4 UI (US location, daily); the Admin API then confirmed dailyExportEnabled true and streaming off.
Stage F: access-model confirmation
Step 15. The reconstruction closed by recording what each role permits: Editor covers custom dimensions, key events, retention, and Enhanced Measurement; Administrator is required for product links and user management; reading accessBindings needs the analytics.manage.users scope (an analytics.edit token returns ACCESS_TOKEN_SCOPE_INSUFFICIENT). The service account holds predefinedRoles/admin. Chapter 15, "Access and Permissions Model," carries the full analysis.