Unified Analytics Platform — Simplified HLD

Status: Target Architecture
Audience: Product, Engineering, Tagging Teams
Date: 2026-06-15


What We Have Today

Multiple Tagging Teams
├── Team A: Uses satellite.track("event_name", data)
├── Team B: Uses window.digitaldata = {...} (server-side injection)
├── Team C: Uses window.digitaldata = {...} (client-side mutation)
└── Team D: Custom event helpers

All paths lead to → Adobe Launch → Adobe Analytics

Problem: Same event tracked 4 different ways. Regressions happen when shared code changes.


What We Want Tomorrow

One Unified Contract Layer
  ↓
App-Level Analytics Builders (business mapping + digitaldata shape)
  ↓
One Thin Shared SDK (core only)
  ├── Validate payload
  ├── Pick dispatch path
  └── Call satellite or dataLayer adapter
    ↓
One Dispatch Path to Adobe Launch
    ↓
Production Validation and Monitoring

The Three Rules

  1. Contract First
    • Before any code, define the event shape in a schema.
  1. Business Logic in App Layer
  1. Thin Core SDK Only

Hard Boundary Rule (Non-Negotiable)

  1. Shared analytics packages must be fully app-agnostic.
  2. Shared SDK must never import from any app path.
  3. Shared SDK must never contain feature names, page names, or app-specific branching.
  4. App layer can import shared SDK; shared SDK cannot import app layer.
  5. Shared SDK is runtime-only and must not own app/domain TypeScript types.
[Diagram]

Allowed vs Not Allowed

Area Allowed in App Layer Allowed in Shared SDK
Business event meaning Yes No
Payload field mapping Yes No
Feature/page conditions Yes No
App/domain TypeScript types Yes No
Contract validation No Yes
Dispatch routing No Yes
satellite/dataLayer call Optional (legacy only) Yes

Simple Architecture

[Diagram]

Step by Step How It Works

Step 1: Product/Tagging Team Defines Contract (App-Owned)

File: apps/@nvc/search/analytics/contracts/search-filter.ts

import { z } from "zod";

export const SEARCH_FILTER_APPLIED = {
  name: "SEARCH_FILTER_APPLIED",
  version: "1.0.0",
  schema: z.object({
    filterId: z.string(),
    filterValue: z.string(),
    vehicleType: z.enum(["sedan", "suv", "truck"]),
    market: z.enum(["us-en", "ca-en", "za-en"]),
    timestamp: z.number(),
  }),
};

Step 2: Developer Builds Event at App Level

File: apps/@nvc/search/components/filterPanel.tsx

import { analytics } from "@analytics/sdk";
import { SEARCH_FILTER_APPLIED } from "../analytics/contracts/search-filter";

export function FilterPanel() {
  const handleFilterChange = (filter) => {
    // App owns business mapping and payload shape
    const eventPayload = {
      filterId: filter.id,
      filterValue: filter.value,
      vehicleType: "sedan",
      market: "za-en",
      timestamp: Date.now(),
    };

    analytics.track(SEARCH_FILTER_APPLIED.name, eventPayload);
  };

  return <div data-removed={handleFilterChange}>...</div>;
}

Step 3: SDK Only Does Core Logic

File: packages/@analytics/sdk/index.ts

export function track(eventName: string, payload: any) {
  // Validate against contract
  const contract = getContract(eventName);
  const validated = contract.schema.parse(payload); // throws if invalid

  // Route by policy and dispatch
  // No business mapping here
  const enriched = applyDispatchMetadata(validated);
  const policy = getDispatchPolicy(eventName);
  if (policy === "satellite") {
    window._satellite?.track(eventName, enriched);
  } else if (policy === "dataLayer") {
    window.digitaldata.push({
      event: eventName,
      data: enriched,
    });
  }

  // Log for observability
  logEvent(eventName, enriched);
}

What the Agent Automates

Your Prompt

Add analytics for search filter events in South Africa market. Events: filter-apply, filter-clear, filter-reset. Include filter ID, value, and result count.

What Agent Does (No Manual Coding)

  1. Creates Contract
  1. Updates Market Policy

    • Adds ZA-specific mapping rules
  2. Generates SDK Usage in App

    • Imports contract
    • Calls analytics.track() at right points
  3. Generates Tests

    • Unit test: contract validation
    • E2E test: payload snapshot vs expected
  4. Opens PR

    • Contract diff
    • Test evidence
    • Rollback note

You approve. Done.


Governance Layers

Layer 1: Contract Validation

Layer 2: Policy Routing

Layer 3: Market Overlay

Layer 4: Production Monitoring


Versioning and Fail-Safe Rules

  1. Semantic versioning per app-owned event contract.
  1. Runtime compatibility guarantee.
  1. Dispatch fail-safe behavior.
  1. Safe rollout for every change.
  1. No silent breakage.
[Diagram]

Migration Path (Realistic)

Week 1-2: Foundation

Week 3-4: Consolidate

Week 5-6: Agent Setup

Week 7+: Autonomous


Realistic Timeline for Autonomy

Phase Effort Timeline You Get
Foundation Med 2 weeks Contract + SDK framework
Consolidation Med 2 weeks Teams migrated to one SDK
Agent MVP High 4 weeks Prompt → PR for new events
Production-Ready Med 2 weeks Drift detection + auto-merge policy
Total 10 weeks Full autonomous delivery

What Doesn't Change

  1. Adobe Launch still receives events
  2. Satellite.track() still works (called by SDK)
  3. window.digitaldata still works (shape can be built in app layer)
  4. Your existing Adobe rules and tags still apply
  5. No rip-and-replace

What Changes

  1. All events come through one SDK entry point
  2. Contracts are app-owned, versioned, and tracked in git
  3. Business mapping lives in app code, not shared SDK
  4. Agent generates boilerplate code and tests
  5. Production validation detects drift automatically

Success Looks Like

Metric Before After
Time to add market to event 1-2 days 1 PR (2 hours)
Analytics regression rate 2-3 per month < 1 per month
Regression root cause identified 3-5 days Auto-detected in 1 hour
Cross-team event consistency 60% 99%
Manual coding per feature event 4-6 hours 0 hours

Next: Agent System Prompt

Once foundation is live, the agent works like this:

User: "Add tracking for loan calculator in US and ZA"
Agent: "I found calculator in nvc/financing. Need clarification:
  1. Should we track open, input-change, and submit? (yes/no)
  2. Include loan amount and term as payload? (yes/no)"
User: "yes, yes"
Agent: ✓ Generates contracts ✓ Updates policies ✓ Generates code
       ✓ Generates tests ✓ Opens PR
You: Review (2 mins) → Approve
System: Merge + Deploy + Monitor

This Architecture Supports

  1. ✓ Adobe Launch (current)
  2. ✓ GA4 (future - add adapter)
  3. ✓ Segment (future - add adapter)
  4. ✓ Custom webhook (future - add adapter)
  5. ✓ Server-side tagging (current - via dataLayer injection)
  6. ✓ Client-side tagging (current - via satellite.track)

One SDK, many dispatch options.


Questions for Your Tagging Team

  1. Which teams use satellite.track today? List them.
  2. Which teams use dataLayer injection today? List them.
  3. Are there custom event helpers? Where are they?
  4. Adobe Launch tags — any custom JavaScript transforms?

(Answers help agent understand existing patterns and generate compatible code.)


Questions for Your Engineering Team

  1. How do you currently test analytics events? (Manual? E2E? None?)
  2. How often do cross-app regression bugs happen?
  3. Which markets need analytics support in next 6 months?

(Answers help prioritize agent features.)


One Page Positioning

Today: Multiple teams, multiple approaches, fragile shared code, regressions when shared code changes.

Tomorrow: One contract, one SDK, one entry point. Agent generates code, validates with tests, delivers PR. Teams never manually code analytics again. Market rollouts are config changes, not code reviews.

Result: Faster, more reliable, fewer regressions, less manual work.