Skip to main content

Identity Stability Execution Plan

Status: ⚠️ CRITICAL FIXES REQUIRED
Priority: BLOCKING for semantic view production use

Quick Reference: Guarantee Statements

Business ID Stability (Post-Fix)

MERGE Key: (tenant_id, normalized_name)
Immutable Field: business_id
Generation: MD5 hash of normalized_name (first 16 hex chars)
Guarantee: Same (tenant_id, normalized_name) → same business_id forever

Agent Identity Stability (Post-Migration)

MERGE Key: (tenant_id, agent_key)
Immutable Field: agent_key
Generation: SHA256 hash of normalized display_name (prefix: "AGENT:" + 16 hex chars)
Guarantee: Same (tenant_id, normalized_name) → same agent_key forever

Phase 1: Business ID Fixes (IMMEDIATE)

Step 1.1: Update Schema

File: integration/bigquery/schemas/dim_business_mapping_schema.json
Change: Add tenant_id as first field ✅ DONE

Step 1.2: Create MERGE SQL

File: integration/bigquery/sql/tables/dim_business_mapping.sqlCREATED MERGE Key: (tenant_id, normalized_name)
Immutable: business_id (never updated)

Step 1.3: Migration Script

File: integration/bigquery/sql/migrations/add_tenant_id_to_business_mapping.sql (CREATE)
-- Migration: Add tenant_id to dim_business_mapping
-- Run this BEFORE deploying new MERGE logic

-- Step 1: Add tenant_id column (nullable initially)
ALTER TABLE `payroll-bi-gauntlet.payroll_analytics.dim_business_mapping`
ADD COLUMN IF NOT EXISTS tenant_id STRING;

-- Step 2: Backfill tenant_id for existing rows
UPDATE `payroll-bi-gauntlet.payroll_analytics.dim_business_mapping`
SET tenant_id = 'creative_benefit_strategies'
WHERE tenant_id IS NULL;

-- Step 3: Verify no NULLs remain
SELECT COUNT(*) AS null_count
FROM `payroll-bi-gauntlet.payroll_analytics.dim_business_mapping`
WHERE tenant_id IS NULL;
-- Expected: 0

-- Step 4: Add clustering (optional but recommended)
ALTER TABLE `payroll-bi-gauntlet.payroll_analytics.dim_business_mapping`
SET OPTIONS(
  clustering_fields = ['tenant_id', 'normalized_name', 'business_id']
);

Step 1.4: Update BusinessIDGenerator

File: utils/business_id_generator.py
Changes:
  1. Add tenant_id parameter to __init__() and all methods
  2. Include tenant_id in CSV schema
  3. Update _save_mapping() to include tenant_id

Step 1.5: Update Loader

File: integration/bigquery/scripts/load_reference_tables.py
Change: Replace WRITE_TRUNCATE with MERGE statement (use SQL from dim_business_mapping.sql)

Phase 2: Agent Identity Migration (REQUIRED)

Step 2.1: Create Schema

File: integration/bigquery/schemas/dim_agent_identity_schema.jsonCREATED

Step 2.2: Create MERGE SQL

File: integration/bigquery/sql/tables/dim_agent_identity.sqlCREATED MERGE Key: (tenant_id, agent_key)
Immutable: agent_key (never updated)

Step 2.3: Initial Population Script

File: integration/bigquery/sql/migrations/populate_agent_identity.sql (CREATE)
-- Migration: Populate dim_agent_identity from existing data
-- Generates agent_key for all agents found in stage3_snapshots

INSERT INTO `payroll-bi-gauntlet.payroll_analytics.dim_agent_identity`
SELECT DISTINCT
  tenant_id,
  -- Generate agent_key (deterministic hash)
  CONCAT('AGENT:', TO_HEX(SHA256(UPPER(TRIM(agent_name))))[:16]) AS agent_key,
  UPPER(TRIM(agent_name)) AS display_name,
  [UPPER(TRIM(agent_name))] AS aliases,  -- Start with canonical name
  agent_id AS upstream_agent_id,
  DATE('2024-01-01') AS effective_start_date,
  NULL AS effective_end_date,
  TRUE AS is_active,
  CURRENT_TIMESTAMP() AS created_at,
  CURRENT_TIMESTAMP() AS updated_at
FROM `payroll-bi-gauntlet.payroll_processed.stage3_snapshots`
WHERE period_label >= DATE('2024-01-01')
  AND agent_name IS NOT NULL
GROUP BY tenant_id, agent_name, agent_id
-- Avoid duplicates
QUALIFY ROW_NUMBER() OVER (
  PARTITION BY tenant_id, UPPER(TRIM(agent_name))
  ORDER BY period_label DESC
) = 1;

Step 2.4: Update Semantic View (Backward Compatible)

File: integration/bigquery/sql/views/semantic_view_commission_traceability.sql Changes:
  1. Add agent_identity CTE (join to dim_agent_identity)
  2. Update stage3_component_id hash to use agent_key (fallback to agent_id)
  3. Keep agent_id in final SELECT for backward compatibility
  4. Add agent_key to final SELECT
Key Change (line 65):
-- BEFORE:
CAST(agent_id AS STRING), '|',

-- AFTER:
CAST(COALESCE(ai.agent_key, s3.agent_id, 'UNKNOWN') AS STRING), '|',

Phase 3: QA Invariants

Step 3.1: Add Business ID Churn Check

File: tests/test_semantic_view_qa_invariants.sql Add Invariant #7 (see IDENTITY_STABILITY_GUARANTEES.md section 4.1)

Step 3.2: Add Agent Key Stability Check

File: tests/test_semantic_view_qa_invariants.sql Add Invariant #8 (see IDENTITY_STABILITY_GUARANTEES.md section 4.2)

Step 3.3: Update Tenant Isolation Tests

File: tests/test_semantic_view_qa_invariants.sql Update Invariant #6 Check A to use agent_key joins (see IDENTITY_STABILITY_GUARANTEES.md section 2.4)

Validation Checklist

Before marking as complete:
  • dim_business_mapping has tenant_id column
  • MERGE SQL uses (tenant_id, normalized_name) key
  • business_id is never updated in MERGE (only inserted)
  • BusinessIDGenerator includes tenant_id in all operations
  • Loader uses MERGE instead of WRITE_TRUNCATE
  • dim_agent_identity table created and populated
  • Semantic view uses agent_key in stage3_component_id hash
  • QA invariants pass (0 business_id churn, 0 agent_key churn)
  • Tenant isolation tests updated for agent_key

Rollout Order

  1. Week 1: Business ID fixes (Phase 1)
    • Deploy schema update
    • Run migration script
    • Update generator and loader
    • Validate with QA invariants
  2. Week 2: Agent identity migration (Phase 2)
    • Create dim_agent_identity table
    • Populate from existing data
    • Update semantic view (backward compatible)
    • Validate with QA invariants
  3. Week 3: QA and validation (Phase 3)
    • Run all invariants
    • Verify no ID churn
    • Test tenant isolation
    • Document results

Risk Mitigation

Backward Compatibility

  • Keep agent_id in semantic view SELECT (add agent_key alongside)
  • Existing queries continue to work
  • Gradual migration path

Data Loss Prevention

  • MERGE operations are idempotent (safe to rerun)
  • business_id and agent_key are immutable (never updated)
  • QA invariants detect churn before production

Rollback Plan

  • Keep old loader code in git history
  • MERGE statements can be reverted to INSERT-only
  • Schema changes are additive (can drop tenant_id if needed)

Next Steps: Execute Phase 1 fixes immediately, then proceed with Phase 2.