mcp-scraper 0.68.0 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -908,9 +908,26 @@ function optionalString(record, ...keys) {
908
908
  }
909
909
  return void 0;
910
910
  }
911
+ function providerReceiptRecord(receipt) {
912
+ const record = recordValue(receipt);
913
+ const structured = recordValue(record.structuredContent);
914
+ if (Object.keys(structured).length > 0) return structured;
915
+ const content = Array.isArray(record.content) ? record.content : [];
916
+ for (const item of content) {
917
+ const block = recordValue(item);
918
+ if (block.type !== "text" || typeof block.text !== "string" || block.text.length > 1e6) continue;
919
+ try {
920
+ const parsed = JSON.parse(block.text);
921
+ const parsedRecord = recordValue(parsed);
922
+ if (Object.keys(parsedRecord).length > 0) return parsedRecord;
923
+ } catch {
924
+ }
925
+ }
926
+ return record;
927
+ }
911
928
  function normalizeProviderReceipt(platform, receipt, stableEventId) {
912
929
  void getAnalyticsProviderDefinition(platform);
913
- const record = receipt && typeof receipt === "object" ? receipt : {};
930
+ const record = providerReceiptRecord(receipt);
914
931
  const warnings = [record.warnings, record.messages, record.fieldWarnings].flatMap((value) => Array.isArray(value) ? value : []).map((value) => {
915
932
  if (typeof value === "string") return value;
916
933
  const warning = recordValue(value);
@@ -1748,6 +1765,600 @@ async function migrateAnalytics() {
1748
1765
  await client.query(
1749
1766
  `ALTER TABLE analytics_connections ADD COLUMN IF NOT EXISTS last_verified_receipt_id uuid`
1750
1767
  );
1768
+ await client.query(
1769
+ `ALTER TABLE analytics_sessions ADD COLUMN IF NOT EXISTS public_ref text`
1770
+ );
1771
+ await client.query(
1772
+ `ALTER TABLE analytics_people ADD COLUMN IF NOT EXISTS public_ref text`
1773
+ );
1774
+ await client.query(
1775
+ `ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS event_definition_id uuid REFERENCES analytics_event_definitions(id) ON DELETE SET NULL`
1776
+ );
1777
+ await client.query(
1778
+ `ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS event_definition_version integer`
1779
+ );
1780
+ await client.query(
1781
+ `ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS value_source text NOT NULL DEFAULT 'default_zero' CHECK (value_source IN ('absent','default_zero','fixed','dynamic','conditional_override','provider','verified_revenue','restatement'))`
1782
+ );
1783
+ await client.query(
1784
+ `ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS value_provenance jsonb NOT NULL DEFAULT '{}'::jsonb`
1785
+ );
1786
+ await client.query(
1787
+ `ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS touch_kind text NOT NULL DEFAULT 'click' CHECK (touch_kind IN ('click','view','direct','organic','referral','email','sms','call','other'))`
1788
+ );
1789
+ await client.query(
1790
+ `ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS platform text`
1791
+ );
1792
+ await client.query(
1793
+ `ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS account_id text`
1794
+ );
1795
+ await client.query(
1796
+ `ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS campaign_id text`
1797
+ );
1798
+ await client.query(
1799
+ `ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS ad_set_id text`
1800
+ );
1801
+ await client.query(
1802
+ `ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS ad_id text`
1803
+ );
1804
+ await client.query(
1805
+ `ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS creative_id text`
1806
+ );
1807
+ await client.query(
1808
+ `ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS placement text`
1809
+ );
1810
+ await client.query(`
1811
+ CREATE TABLE IF NOT EXISTS analytics_onboarding_presentations (
1812
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
1813
+ user_id bigint NOT NULL,
1814
+ preferences jsonb NOT NULL DEFAULT '{}'::jsonb,
1815
+ last_presented_step text,
1816
+ dismissed_help jsonb NOT NULL DEFAULT '[]'::jsonb,
1817
+ created_at timestamptz NOT NULL DEFAULT now(),
1818
+ updated_at timestamptz NOT NULL DEFAULT now(),
1819
+ PRIMARY KEY(site_id, user_id)
1820
+ )
1821
+ `);
1822
+ await client.query(`
1823
+ CREATE TABLE IF NOT EXISTS analytics_funnel_definitions (
1824
+ id uuid PRIMARY KEY,
1825
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
1826
+ name text NOT NULL,
1827
+ stages jsonb NOT NULL CHECK (jsonb_typeof(stages) = 'array'),
1828
+ version integer NOT NULL DEFAULT 1 CHECK (version > 0),
1829
+ status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled','archived')),
1830
+ is_default boolean NOT NULL DEFAULT false,
1831
+ created_by_user_id bigint NOT NULL,
1832
+ created_at timestamptz NOT NULL DEFAULT now(),
1833
+ updated_at timestamptz NOT NULL DEFAULT now(),
1834
+ UNIQUE(site_id, name, version)
1835
+ )
1836
+ `);
1837
+ await client.query(`
1838
+ CREATE TABLE IF NOT EXISTS analytics_saved_views (
1839
+ id uuid PRIMARY KEY,
1840
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
1841
+ owner_user_id bigint NOT NULL,
1842
+ name text NOT NULL,
1843
+ report_type text NOT NULL,
1844
+ schema_version integer NOT NULL DEFAULT 1 CHECK (schema_version > 0),
1845
+ settings jsonb NOT NULL DEFAULT '{}'::jsonb,
1846
+ is_default boolean NOT NULL DEFAULT false,
1847
+ created_at timestamptz NOT NULL DEFAULT now(),
1848
+ updated_at timestamptz NOT NULL DEFAULT now(),
1849
+ UNIQUE(site_id, owner_user_id, report_type, name)
1850
+ )
1851
+ `);
1852
+ await client.query(`
1853
+ CREATE TABLE IF NOT EXISTS analytics_product_milestones (
1854
+ id uuid PRIMARY KEY,
1855
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
1856
+ user_id bigint NOT NULL,
1857
+ milestone text NOT NULL,
1858
+ evidence_kind text NOT NULL,
1859
+ evidence_ref text,
1860
+ occurred_at timestamptz NOT NULL DEFAULT now(),
1861
+ safe_metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
1862
+ UNIQUE(site_id, user_id, milestone)
1863
+ )
1864
+ `);
1865
+ await client.query(`
1866
+ CREATE TABLE IF NOT EXISTS analytics_purpose_policies (
1867
+ id uuid PRIMARY KEY,
1868
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
1869
+ purpose text NOT NULL CHECK (purpose IN ('necessary','site_analytics','enhanced_matching','advertising_measurement')),
1870
+ policy_version integer NOT NULL CHECK (policy_version > 0),
1871
+ jurisdiction_profile text NOT NULL,
1872
+ retention_days integer CHECK (retention_days IS NULL OR retention_days > 0),
1873
+ enabled boolean NOT NULL DEFAULT true,
1874
+ policy jsonb NOT NULL DEFAULT '{}'::jsonb,
1875
+ created_by_user_id bigint NOT NULL,
1876
+ created_at timestamptz NOT NULL DEFAULT now(),
1877
+ retired_at timestamptz,
1878
+ UNIQUE(site_id, purpose, policy_version, jurisdiction_profile)
1879
+ )
1880
+ `);
1881
+ await client.query(`
1882
+ CREATE TABLE IF NOT EXISTS analytics_consent_receipts (
1883
+ id uuid PRIMARY KEY,
1884
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
1885
+ pixel_id uuid REFERENCES analytics_pixels(id) ON DELETE SET NULL,
1886
+ subject_ref text NOT NULL,
1887
+ policy_version integer NOT NULL CHECK (policy_version > 0),
1888
+ jurisdiction_profile text NOT NULL,
1889
+ necessary boolean NOT NULL DEFAULT true CHECK (necessary),
1890
+ site_analytics boolean NOT NULL DEFAULT false,
1891
+ enhanced_matching boolean NOT NULL DEFAULT false,
1892
+ advertising_measurement boolean NOT NULL DEFAULT false,
1893
+ gpc boolean NOT NULL DEFAULT false,
1894
+ action text NOT NULL CHECK (action IN ('accept_all','reject_all','save_choices','withdraw','gpc')),
1895
+ source text NOT NULL CHECK (source IN ('gateway','api','gpc','system')),
1896
+ presented_at timestamptz,
1897
+ occurred_at timestamptz NOT NULL,
1898
+ expires_at timestamptz,
1899
+ supersedes_id uuid REFERENCES analytics_consent_receipts(id) ON DELETE SET NULL,
1900
+ created_at timestamptz NOT NULL DEFAULT now()
1901
+ )
1902
+ `);
1903
+ await client.query(
1904
+ `ALTER TABLE analytics_consent_receipts ADD COLUMN IF NOT EXISTS presented_at timestamptz`
1905
+ );
1906
+ await client.query(`
1907
+ DO $$
1908
+ BEGIN
1909
+ IF NOT EXISTS (
1910
+ SELECT 1 FROM pg_constraint
1911
+ WHERE conrelid = 'analytics_consent_receipts'::regclass
1912
+ AND conname = 'analytics_consent_receipts_presentation_order_check'
1913
+ ) THEN
1914
+ ALTER TABLE analytics_consent_receipts
1915
+ ADD CONSTRAINT analytics_consent_receipts_presentation_order_check
1916
+ CHECK (presented_at IS NULL OR presented_at <= occurred_at);
1917
+ END IF;
1918
+ END $$
1919
+ `);
1920
+ await client.query(`
1921
+ CREATE TABLE IF NOT EXISTS analytics_consent_current_state (
1922
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
1923
+ subject_ref text NOT NULL,
1924
+ latest_receipt_id uuid NOT NULL REFERENCES analytics_consent_receipts(id) ON DELETE CASCADE,
1925
+ policy_version integer NOT NULL CHECK (policy_version > 0),
1926
+ jurisdiction_profile text NOT NULL,
1927
+ necessary boolean NOT NULL DEFAULT true CHECK (necessary),
1928
+ site_analytics boolean NOT NULL DEFAULT false,
1929
+ enhanced_matching boolean NOT NULL DEFAULT false,
1930
+ advertising_measurement boolean NOT NULL DEFAULT false,
1931
+ gpc boolean NOT NULL DEFAULT false,
1932
+ effective_at timestamptz NOT NULL,
1933
+ expires_at timestamptz,
1934
+ updated_at timestamptz NOT NULL DEFAULT now(),
1935
+ PRIMARY KEY(site_id, subject_ref)
1936
+ )
1937
+ `);
1938
+ await client.query(`
1939
+ CREATE TABLE IF NOT EXISTS analytics_identity_namespaces (
1940
+ id uuid PRIMARY KEY,
1941
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
1942
+ name text NOT NULL,
1943
+ status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','approved','disabled','archived')),
1944
+ approved_hosts jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(approved_hosts) = 'array'),
1945
+ approved_sources jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(approved_sources) = 'array'),
1946
+ version integer NOT NULL DEFAULT 1 CHECK (version > 0),
1947
+ approved_by_user_id bigint,
1948
+ approved_at timestamptz,
1949
+ created_at timestamptz NOT NULL DEFAULT now(),
1950
+ updated_at timestamptz NOT NULL DEFAULT now(),
1951
+ UNIQUE(site_id, name, version),
1952
+ UNIQUE(id, site_id)
1953
+ )
1954
+ `);
1955
+ await client.query(`
1956
+ CREATE TABLE IF NOT EXISTS analytics_identity_sources (
1957
+ id uuid PRIMARY KEY,
1958
+ namespace_id uuid NOT NULL,
1959
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
1960
+ source_kind text NOT NULL CHECK (source_kind IN ('pixel','host','form','crm','call','server','import')),
1961
+ source_ref text NOT NULL,
1962
+ status text NOT NULL DEFAULT 'approved' CHECK (status IN ('pending','approved','revoked')),
1963
+ created_at timestamptz NOT NULL DEFAULT now(),
1964
+ revoked_at timestamptz,
1965
+ FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
1966
+ UNIQUE(namespace_id, source_kind, source_ref)
1967
+ )
1968
+ `);
1969
+ await client.query(
1970
+ `ALTER TABLE analytics_people ADD COLUMN IF NOT EXISTS namespace_id uuid REFERENCES analytics_identity_namespaces(id) ON DELETE RESTRICT`
1971
+ );
1972
+ await client.query(
1973
+ `ALTER TABLE analytics_identity_nodes ADD COLUMN IF NOT EXISTS namespace_id uuid REFERENCES analytics_identity_namespaces(id) ON DELETE RESTRICT`
1974
+ );
1975
+ await client.query(
1976
+ `ALTER TABLE analytics_identity_edges ADD COLUMN IF NOT EXISTS namespace_id uuid REFERENCES analytics_identity_namespaces(id) ON DELETE RESTRICT`
1977
+ );
1978
+ await client.query(
1979
+ "CREATE UNIQUE INDEX IF NOT EXISTS analytics_identity_nodes_namespace_ref ON analytics_identity_nodes(id, site_id, namespace_id)"
1980
+ );
1981
+ await client.query(
1982
+ "CREATE UNIQUE INDEX IF NOT EXISTS analytics_people_namespace_ref ON analytics_people(id, site_id, namespace_id)"
1983
+ );
1984
+ await client.query(`
1985
+ CREATE TABLE IF NOT EXISTS analytics_candidate_associations (
1986
+ id uuid PRIMARY KEY,
1987
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
1988
+ namespace_id uuid NOT NULL,
1989
+ left_identity_node_id uuid NOT NULL,
1990
+ right_identity_node_id uuid NOT NULL,
1991
+ confidence numeric(5,4) NOT NULL CHECK (confidence >= 0 AND confidence <= 1),
1992
+ confidence_band text NOT NULL CHECK (confidence_band IN ('high','medium','low','ineligible')),
1993
+ status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','expired','rejected','promoted','deleted')),
1994
+ consent_receipt_id uuid NOT NULL REFERENCES analytics_consent_receipts(id) ON DELETE RESTRICT,
1995
+ model_version text NOT NULL,
1996
+ first_observed_at timestamptz NOT NULL,
1997
+ last_observed_at timestamptz NOT NULL,
1998
+ expires_at timestamptz NOT NULL,
1999
+ promoted_person_id uuid,
2000
+ promoted_at timestamptz,
2001
+ created_at timestamptz NOT NULL DEFAULT now(),
2002
+ updated_at timestamptz NOT NULL DEFAULT now(),
2003
+ CHECK (left_identity_node_id <> right_identity_node_id),
2004
+ FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
2005
+ FOREIGN KEY(left_identity_node_id, site_id, namespace_id) REFERENCES analytics_identity_nodes(id, site_id, namespace_id) ON DELETE CASCADE,
2006
+ FOREIGN KEY(right_identity_node_id, site_id, namespace_id) REFERENCES analytics_identity_nodes(id, site_id, namespace_id) ON DELETE CASCADE,
2007
+ FOREIGN KEY(promoted_person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id) ON DELETE RESTRICT,
2008
+ UNIQUE(namespace_id, left_identity_node_id, right_identity_node_id, model_version),
2009
+ UNIQUE(id, namespace_id)
2010
+ )
2011
+ `);
2012
+ await client.query(`
2013
+ CREATE TABLE IF NOT EXISTS analytics_candidate_evidence (
2014
+ id uuid PRIMARY KEY,
2015
+ association_id uuid NOT NULL,
2016
+ namespace_id uuid NOT NULL,
2017
+ feature_kind text NOT NULL CHECK (feature_kind IN ('device_bucket','browser_bucket','coarse_geo','network_bucket','temporal_pattern','journey_pattern')),
2018
+ feature_hmac text NOT NULL,
2019
+ contribution numeric(6,5) NOT NULL CHECK (contribution >= -1 AND contribution <= 1),
2020
+ observed_at timestamptz NOT NULL,
2021
+ expires_at timestamptz NOT NULL,
2022
+ created_at timestamptz NOT NULL DEFAULT now(),
2023
+ FOREIGN KEY(association_id, namespace_id) REFERENCES analytics_candidate_associations(id, namespace_id) ON DELETE CASCADE,
2024
+ UNIQUE(association_id, feature_kind, feature_hmac)
2025
+ )
2026
+ `);
2027
+ await client.query(`
2028
+ CREATE TABLE IF NOT EXISTS analytics_candidate_promotions (
2029
+ id uuid PRIMARY KEY,
2030
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2031
+ namespace_id uuid NOT NULL,
2032
+ association_id uuid NOT NULL,
2033
+ person_id uuid NOT NULL,
2034
+ evidence_kind text NOT NULL CHECK (evidence_kind IN ('verified_email','verified_phone','crm_contact','signed_linker','authenticated_user')),
2035
+ evidence_ref_hmac text NOT NULL,
2036
+ promoted_by text NOT NULL CHECK (promoted_by IN ('system','user')),
2037
+ promoted_by_user_id bigint,
2038
+ promoted_at timestamptz NOT NULL DEFAULT now(),
2039
+ FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
2040
+ FOREIGN KEY(association_id, namespace_id) REFERENCES analytics_candidate_associations(id, namespace_id) ON DELETE CASCADE,
2041
+ FOREIGN KEY(person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id) ON DELETE CASCADE,
2042
+ UNIQUE(association_id, person_id)
2043
+ )
2044
+ `);
2045
+ await client.query(
2046
+ "CREATE UNIQUE INDEX IF NOT EXISTS analytics_touches_site_ref ON analytics_attribution_touches(id, site_id)"
2047
+ );
2048
+ await client.query(`
2049
+ CREATE TABLE IF NOT EXISTS analytics_candidate_attribution_touches (
2050
+ id uuid PRIMARY KEY,
2051
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2052
+ namespace_id uuid NOT NULL,
2053
+ association_id uuid NOT NULL,
2054
+ touch_id uuid NOT NULL,
2055
+ eligibility text NOT NULL DEFAULT 'overlay_only' CHECK (eligibility IN ('overlay_only','review','ineligible','expired')),
2056
+ association_confidence numeric(5,4) NOT NULL CHECK (association_confidence >= 0 AND association_confidence <= 1),
2057
+ model_version text NOT NULL,
2058
+ linked_at timestamptz NOT NULL DEFAULT now(),
2059
+ expires_at timestamptz NOT NULL,
2060
+ FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
2061
+ FOREIGN KEY(association_id, namespace_id) REFERENCES analytics_candidate_associations(id, namespace_id) ON DELETE CASCADE,
2062
+ FOREIGN KEY(touch_id, site_id) REFERENCES analytics_attribution_touches(id, site_id) ON DELETE CASCADE,
2063
+ UNIQUE(association_id, touch_id, model_version)
2064
+ )
2065
+ `);
2066
+ await client.query(`
2067
+ CREATE TABLE IF NOT EXISTS analytics_event_value_definitions (
2068
+ id uuid PRIMARY KEY,
2069
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2070
+ event_definition_id uuid NOT NULL REFERENCES analytics_event_definitions(id) ON DELETE CASCADE,
2071
+ version integer NOT NULL CHECK (version > 0),
2072
+ base_mode text NOT NULL DEFAULT 'none' CHECK (base_mode IN ('none','fixed','dynamic')),
2073
+ fixed_value_minor bigint,
2074
+ currency text NOT NULL DEFAULT 'USD',
2075
+ dynamic_source jsonb,
2076
+ conditional_event_definition_id uuid REFERENCES analytics_event_definitions(id) ON DELETE SET NULL,
2077
+ conditional_window_days integer CHECK (conditional_window_days IS NULL OR conditional_window_days > 0),
2078
+ conditional_value_minor bigint,
2079
+ status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled','archived')),
2080
+ created_by_user_id bigint NOT NULL,
2081
+ created_at timestamptz NOT NULL DEFAULT now(),
2082
+ updated_at timestamptz NOT NULL DEFAULT now(),
2083
+ CHECK ((base_mode = 'fixed') = (fixed_value_minor IS NOT NULL)),
2084
+ CHECK ((conditional_event_definition_id IS NULL) = (conditional_value_minor IS NULL)),
2085
+ UNIQUE(site_id, event_definition_id, version)
2086
+ )
2087
+ `);
2088
+ await client.query(`
2089
+ CREATE TABLE IF NOT EXISTS analytics_event_value_evaluations (
2090
+ id uuid PRIMARY KEY,
2091
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2092
+ source_event_id uuid NOT NULL REFERENCES analytics_events(id) ON DELETE CASCADE,
2093
+ definition_id uuid REFERENCES analytics_event_value_definitions(id) ON DELETE SET NULL,
2094
+ definition_version integer,
2095
+ value_minor bigint,
2096
+ currency text,
2097
+ value_source text NOT NULL CHECK (value_source IN ('absent','default_zero','fixed','dynamic','conditional_override','provider','verified_revenue','restatement')),
2098
+ matched_event_id uuid REFERENCES analytics_events(id) ON DELETE SET NULL,
2099
+ provenance jsonb NOT NULL DEFAULT '{}'::jsonb,
2100
+ evaluated_at timestamptz NOT NULL DEFAULT now(),
2101
+ UNIQUE(site_id, source_event_id, definition_version)
2102
+ )
2103
+ `);
2104
+ await client.query(`
2105
+ CREATE TABLE IF NOT EXISTS analytics_data_class_policies (
2106
+ id uuid PRIMARY KEY,
2107
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2108
+ data_class text NOT NULL,
2109
+ purpose text NOT NULL,
2110
+ jurisdiction_profile text NOT NULL,
2111
+ retention_days integer CHECK (retention_days IS NULL OR retention_days > 0),
2112
+ storage_kind text NOT NULL CHECK (storage_kind IN ('ordinary','pseudonymous','encrypted_restricted','ephemeral')),
2113
+ withdrawal_action text NOT NULL CHECK (withdrawal_action IN ('stop_future','expire','delete','retain_necessary')),
2114
+ deletion_action text NOT NULL CHECK (deletion_action IN ('delete','anonymize','retain_legal')),
2115
+ read_surface text NOT NULL,
2116
+ version integer NOT NULL DEFAULT 1 CHECK (version > 0),
2117
+ created_at timestamptz NOT NULL DEFAULT now(),
2118
+ UNIQUE(site_id, data_class, jurisdiction_profile, version)
2119
+ )
2120
+ `);
2121
+ await client.query(`
2122
+ CREATE TABLE IF NOT EXISTS analytics_privacy_audits (
2123
+ id uuid PRIMARY KEY,
2124
+ site_id uuid REFERENCES analytics_sites(id) ON DELETE SET NULL,
2125
+ namespace_ref text,
2126
+ subject_ref text,
2127
+ action text NOT NULL CHECK (action IN ('export_requested','export_completed','deletion_requested','deletion_completed','withdrawal_applied','retention_expired','candidate_promoted')),
2128
+ idempotency_key text NOT NULL,
2129
+ actor_user_id bigint,
2130
+ result_code text NOT NULL,
2131
+ affected_counts jsonb NOT NULL DEFAULT '{}'::jsonb,
2132
+ created_at timestamptz NOT NULL DEFAULT now(),
2133
+ completed_at timestamptz,
2134
+ UNIQUE(site_id, idempotency_key)
2135
+ )
2136
+ `);
2137
+ await client.query("ALTER TABLE analytics_privacy_audits ADD COLUMN IF NOT EXISTS request_fingerprint text");
2138
+ await client.query(`
2139
+ CREATE TABLE IF NOT EXISTS analytics_crm_provider_capabilities (
2140
+ provider text NOT NULL CHECK (provider IN ('hubspot','salesforce','highlevel','zoho','pipedrive','keap')),
2141
+ contract_version integer NOT NULL CHECK (contract_version > 0),
2142
+ capabilities jsonb NOT NULL DEFAULT '{}'::jsonb,
2143
+ documentation_refs jsonb NOT NULL DEFAULT '[]'::jsonb,
2144
+ created_at timestamptz NOT NULL DEFAULT now(),
2145
+ retired_at timestamptz,
2146
+ PRIMARY KEY(provider, contract_version)
2147
+ )
2148
+ `);
2149
+ await client.query(`
2150
+ CREATE TABLE IF NOT EXISTS analytics_crm_provisioning_plans (
2151
+ id uuid PRIMARY KEY,
2152
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2153
+ connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
2154
+ provider text NOT NULL CHECK (provider IN ('hubspot','salesforce','highlevel','zoho','pipedrive','keap')),
2155
+ contract_version integer NOT NULL CHECK (contract_version > 0),
2156
+ tenant_schema_version text,
2157
+ plan jsonb NOT NULL,
2158
+ status text NOT NULL DEFAULT 'preview' CHECK (status IN ('preview','approved','applying','verified','failed','reverted')),
2159
+ approved_by_user_id bigint,
2160
+ approved_at timestamptz,
2161
+ created_by_user_id bigint NOT NULL,
2162
+ created_at timestamptz NOT NULL DEFAULT now(),
2163
+ updated_at timestamptz NOT NULL DEFAULT now()
2164
+ )
2165
+ `);
2166
+ await client.query(`
2167
+ CREATE TABLE IF NOT EXISTS analytics_crm_provisioning_receipts (
2168
+ id uuid PRIMARY KEY,
2169
+ plan_id uuid NOT NULL REFERENCES analytics_crm_provisioning_plans(id) ON DELETE CASCADE,
2170
+ operation text NOT NULL CHECK (operation IN ('describe','discover','create_field','test_write','revert_test','verify')),
2171
+ status text NOT NULL CHECK (status IN ('accepted','rejected','failed','reverted')),
2172
+ external_object_ref text,
2173
+ safe_error_code text,
2174
+ warnings jsonb NOT NULL DEFAULT '[]'::jsonb,
2175
+ created_at timestamptz NOT NULL DEFAULT now()
2176
+ )
2177
+ `);
2178
+ await client.query(`
2179
+ CREATE TABLE IF NOT EXISTS analytics_crm_person_mappings (
2180
+ id uuid PRIMARY KEY,
2181
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2182
+ namespace_id uuid NOT NULL,
2183
+ connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
2184
+ person_id uuid NOT NULL,
2185
+ provider_person_ref text NOT NULL,
2186
+ mapping_version integer NOT NULL DEFAULT 1 CHECK (mapping_version > 0),
2187
+ last_synced_at timestamptz,
2188
+ created_at timestamptz NOT NULL DEFAULT now(),
2189
+ updated_at timestamptz NOT NULL DEFAULT now(),
2190
+ FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
2191
+ FOREIGN KEY(person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id) ON DELETE CASCADE,
2192
+ UNIQUE(connection_id, person_id),
2193
+ UNIQUE(connection_id, provider_person_ref)
2194
+ )
2195
+ `);
2196
+ await client.query(`
2197
+ CREATE TABLE IF NOT EXISTS analytics_crm_pipeline_mappings (
2198
+ id uuid PRIMARY KEY,
2199
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2200
+ connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
2201
+ funnel_definition_id uuid REFERENCES analytics_funnel_definitions(id) ON DELETE SET NULL,
2202
+ version integer NOT NULL DEFAULT 1 CHECK (version > 0),
2203
+ mapping jsonb NOT NULL,
2204
+ status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','active','disabled','archived')),
2205
+ created_by_user_id bigint NOT NULL,
2206
+ created_at timestamptz NOT NULL DEFAULT now(),
2207
+ updated_at timestamptz NOT NULL DEFAULT now(),
2208
+ UNIQUE(connection_id, version)
2209
+ )
2210
+ `);
2211
+ await client.query(`
2212
+ CREATE TABLE IF NOT EXISTS analytics_crm_sync_state (
2213
+ connection_id uuid PRIMARY KEY REFERENCES analytics_connections(id) ON DELETE CASCADE,
2214
+ provisioning_plan_id uuid REFERENCES analytics_crm_provisioning_plans(id) ON DELETE SET NULL,
2215
+ cursor jsonb,
2216
+ direction text NOT NULL DEFAULT 'bidirectional' CHECK (direction IN ('inbound','outbound','bidirectional','webhook_only')),
2217
+ status text NOT NULL DEFAULT 'idle' CHECK (status IN ('idle','pending','running','healthy','degraded','disabled')),
2218
+ last_started_at timestamptz,
2219
+ last_completed_at timestamptz,
2220
+ last_success_at timestamptz,
2221
+ last_error_code text,
2222
+ next_sync_at timestamptz,
2223
+ updated_at timestamptz NOT NULL DEFAULT now()
2224
+ )
2225
+ `);
2226
+ await client.query(`
2227
+ CREATE TABLE IF NOT EXISTS analytics_crm_sync_receipts (
2228
+ id uuid PRIMARY KEY,
2229
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2230
+ connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
2231
+ direction text NOT NULL CHECK (direction IN ('inbound','outbound')),
2232
+ operation text NOT NULL CHECK (operation IN ('person_upsert','activity_append','pipeline_update','schema_read','historical_pull')),
2233
+ subject_ref text,
2234
+ external_receipt_id text,
2235
+ status text NOT NULL CHECK (status IN ('accepted','rejected','failed','retrying')),
2236
+ safe_error_code text,
2237
+ occurred_at timestamptz NOT NULL DEFAULT now()
2238
+ )
2239
+ `);
2240
+ await client.query(`
2241
+ CREATE TABLE IF NOT EXISTS analytics_score_definitions (
2242
+ id uuid PRIMARY KEY,
2243
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2244
+ name text NOT NULL,
2245
+ score_kind text NOT NULL CHECK (score_kind IN ('fit','engagement','combined')),
2246
+ rules jsonb NOT NULL CHECK (jsonb_typeof(rules) = 'array'),
2247
+ thresholds jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(thresholds) = 'array'),
2248
+ minimum_score integer NOT NULL DEFAULT 0,
2249
+ maximum_score integer NOT NULL DEFAULT 100 CHECK (maximum_score >= minimum_score),
2250
+ version integer NOT NULL DEFAULT 1 CHECK (version > 0),
2251
+ status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled','archived')),
2252
+ created_by_user_id bigint NOT NULL,
2253
+ created_at timestamptz NOT NULL DEFAULT now(),
2254
+ updated_at timestamptz NOT NULL DEFAULT now(),
2255
+ UNIQUE(site_id, name, version)
2256
+ )
2257
+ `);
2258
+ await client.query(`
2259
+ CREATE TABLE IF NOT EXISTS analytics_score_evaluations (
2260
+ id uuid PRIMARY KEY,
2261
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2262
+ namespace_id uuid NOT NULL,
2263
+ definition_id uuid NOT NULL REFERENCES analytics_score_definitions(id) ON DELETE CASCADE,
2264
+ definition_version integer NOT NULL CHECK (definition_version > 0),
2265
+ person_id uuid,
2266
+ candidate_association_id uuid,
2267
+ score integer NOT NULL,
2268
+ band text,
2269
+ contributions jsonb NOT NULL DEFAULT '[]'::jsonb,
2270
+ evaluated_at timestamptz NOT NULL DEFAULT now(),
2271
+ FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
2272
+ FOREIGN KEY(person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id) ON DELETE CASCADE,
2273
+ FOREIGN KEY(candidate_association_id, namespace_id) REFERENCES analytics_candidate_associations(id, namespace_id) ON DELETE CASCADE,
2274
+ CHECK ((person_id IS NOT NULL)::integer + (candidate_association_id IS NOT NULL)::integer = 1)
2275
+ )
2276
+ `);
2277
+ await client.query(`
2278
+ CREATE TABLE IF NOT EXISTS analytics_prediction_targets (
2279
+ id uuid PRIMARY KEY,
2280
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2281
+ namespace_id uuid NOT NULL,
2282
+ name text NOT NULL,
2283
+ target_event_definition_id uuid NOT NULL REFERENCES analytics_event_definitions(id) ON DELETE RESTRICT,
2284
+ horizon_days integer NOT NULL CHECK (horizon_days > 0),
2285
+ minimum_confirmed_people integer NOT NULL CHECK (minimum_confirmed_people > 0),
2286
+ minimum_positive_outcomes integer NOT NULL CHECK (minimum_positive_outcomes > 0),
2287
+ status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','eligible','training','active','degraded','disabled')),
2288
+ created_by_user_id bigint NOT NULL,
2289
+ created_at timestamptz NOT NULL DEFAULT now(),
2290
+ updated_at timestamptz NOT NULL DEFAULT now(),
2291
+ FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
2292
+ UNIQUE(id, site_id, namespace_id),
2293
+ UNIQUE(namespace_id, name)
2294
+ )
2295
+ `);
2296
+ await client.query(`
2297
+ CREATE TABLE IF NOT EXISTS analytics_prediction_manifests (
2298
+ id uuid PRIMARY KEY,
2299
+ target_id uuid NOT NULL REFERENCES analytics_prediction_targets(id) ON DELETE CASCADE,
2300
+ model_version text NOT NULL,
2301
+ training_window_start timestamptz NOT NULL,
2302
+ training_window_end timestamptz NOT NULL,
2303
+ confirmed_people_count integer NOT NULL CHECK (confirmed_people_count >= 0),
2304
+ positive_outcome_count integer NOT NULL CHECK (positive_outcome_count >= 0),
2305
+ calibration jsonb NOT NULL DEFAULT '{}'::jsonb,
2306
+ feature_manifest jsonb NOT NULL DEFAULT '{}'::jsonb,
2307
+ status text NOT NULL CHECK (status IN ('ineligible','training','validated','active','degraded','retired')),
2308
+ trained_at timestamptz,
2309
+ activated_at timestamptz,
2310
+ retired_at timestamptz,
2311
+ created_at timestamptz NOT NULL DEFAULT now(),
2312
+ UNIQUE(target_id, model_version),
2313
+ UNIQUE(id, target_id)
2314
+ )
2315
+ `);
2316
+ await client.query(`
2317
+ CREATE TABLE IF NOT EXISTS analytics_prediction_results (
2318
+ id uuid PRIMARY KEY,
2319
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2320
+ namespace_id uuid NOT NULL,
2321
+ target_id uuid NOT NULL,
2322
+ manifest_id uuid NOT NULL,
2323
+ person_id uuid NOT NULL,
2324
+ probability numeric(7,6) NOT NULL CHECK (probability >= 0 AND probability <= 1),
2325
+ expected_value_minor bigint,
2326
+ currency text,
2327
+ status text NOT NULL DEFAULT 'available' CHECK (status IN ('available','stale','superseded','deleted')),
2328
+ evaluated_at timestamptz NOT NULL DEFAULT now(),
2329
+ expires_at timestamptz NOT NULL,
2330
+ FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
2331
+ FOREIGN KEY(target_id, site_id, namespace_id) REFERENCES analytics_prediction_targets(id, site_id, namespace_id) ON DELETE CASCADE,
2332
+ FOREIGN KEY(manifest_id, target_id) REFERENCES analytics_prediction_manifests(id, target_id) ON DELETE CASCADE,
2333
+ FOREIGN KEY(person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id) ON DELETE CASCADE,
2334
+ UNIQUE(manifest_id, person_id)
2335
+ )
2336
+ `);
2337
+ await client.query(`
2338
+ CREATE TABLE IF NOT EXISTS analytics_ad_spend_facts (
2339
+ id uuid PRIMARY KEY,
2340
+ site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
2341
+ connection_id uuid REFERENCES analytics_connections(id) ON DELETE SET NULL,
2342
+ bucket date NOT NULL,
2343
+ platform text NOT NULL,
2344
+ account_id text,
2345
+ campaign_id text,
2346
+ campaign_name text,
2347
+ ad_set_id text,
2348
+ ad_set_name text,
2349
+ ad_id text,
2350
+ ad_name text,
2351
+ creative_id text,
2352
+ placement text,
2353
+ spend_minor bigint NOT NULL CHECK (spend_minor >= 0),
2354
+ currency text NOT NULL,
2355
+ source_ref text NOT NULL,
2356
+ source_updated_at timestamptz,
2357
+ created_at timestamptz NOT NULL DEFAULT now(),
2358
+ updated_at timestamptz NOT NULL DEFAULT now(),
2359
+ UNIQUE(site_id, platform, bucket, source_ref)
2360
+ )
2361
+ `);
1751
2362
  await client.query(`CREATE UNIQUE INDEX IF NOT EXISTS analytics_exports_owner_idempotency
1752
2363
  ON analytics_exports(requested_by_user_id, idempotency_key) WHERE idempotency_key IS NOT NULL`);
1753
2364
  await client.query(
@@ -1798,9 +2409,93 @@ async function migrateAnalytics() {
1798
2409
  await client.query(
1799
2410
  "CREATE INDEX IF NOT EXISTS analytics_linker_tokens_expiry ON analytics_linker_tokens(expires_at) WHERE consumed_at IS NULL"
1800
2411
  );
2412
+ await client.query(
2413
+ "CREATE UNIQUE INDEX IF NOT EXISTS analytics_sessions_site_public_ref ON analytics_sessions(site_id, public_ref) WHERE public_ref IS NOT NULL"
2414
+ );
2415
+ await client.query(
2416
+ "CREATE UNIQUE INDEX IF NOT EXISTS analytics_people_site_public_ref ON analytics_people(site_id, public_ref) WHERE public_ref IS NOT NULL"
2417
+ );
2418
+ await client.query(
2419
+ "CREATE INDEX IF NOT EXISTS analytics_sessions_keyset ON analytics_sessions(site_id, started_at DESC, id DESC)"
2420
+ );
2421
+ await client.query(
2422
+ "CREATE INDEX IF NOT EXISTS analytics_events_keyset ON analytics_events(site_id, occurred_at DESC, id DESC)"
2423
+ );
2424
+ await client.query(
2425
+ "CREATE UNIQUE INDEX IF NOT EXISTS analytics_funnels_one_default ON analytics_funnel_definitions(site_id) WHERE is_default AND status = 'active'"
2426
+ );
2427
+ await client.query(
2428
+ "CREATE UNIQUE INDEX IF NOT EXISTS analytics_saved_views_one_default ON analytics_saved_views(site_id, owner_user_id, report_type) WHERE is_default"
2429
+ );
2430
+ await client.query(
2431
+ "CREATE INDEX IF NOT EXISTS analytics_product_milestones_site_time ON analytics_product_milestones(site_id, occurred_at DESC)"
2432
+ );
2433
+ await client.query(
2434
+ "CREATE INDEX IF NOT EXISTS analytics_consent_receipts_subject_time ON analytics_consent_receipts(site_id, subject_ref, occurred_at DESC)"
2435
+ );
2436
+ await client.query(
2437
+ "CREATE INDEX IF NOT EXISTS analytics_consent_receipts_expiry ON analytics_consent_receipts(expires_at) WHERE expires_at IS NOT NULL"
2438
+ );
2439
+ await client.query(
2440
+ "CREATE INDEX IF NOT EXISTS analytics_consent_current_expiry ON analytics_consent_current_state(expires_at) WHERE expires_at IS NOT NULL"
2441
+ );
2442
+ await client.query(
2443
+ "CREATE INDEX IF NOT EXISTS analytics_identity_sources_site ON analytics_identity_sources(site_id, namespace_id, status)"
2444
+ );
2445
+ await client.query(
2446
+ "CREATE INDEX IF NOT EXISTS analytics_candidates_active_expiry ON analytics_candidate_associations(namespace_id, expires_at) WHERE status = 'active'"
2447
+ );
2448
+ await client.query(
2449
+ "CREATE INDEX IF NOT EXISTS analytics_candidates_review ON analytics_candidate_associations(namespace_id, confidence_band, updated_at DESC) WHERE status = 'active'"
2450
+ );
2451
+ await client.query(
2452
+ "CREATE INDEX IF NOT EXISTS analytics_candidate_evidence_expiry ON analytics_candidate_evidence(expires_at)"
2453
+ );
2454
+ await client.query(
2455
+ "CREATE INDEX IF NOT EXISTS analytics_candidate_promotions_person_time ON analytics_candidate_promotions(site_id, person_id, promoted_at DESC)"
2456
+ );
2457
+ await client.query(
2458
+ "CREATE INDEX IF NOT EXISTS analytics_candidate_touches_overlay ON analytics_candidate_attribution_touches(site_id, namespace_id, linked_at DESC) WHERE eligibility = 'overlay_only'"
2459
+ );
2460
+ await client.query(
2461
+ "CREATE INDEX IF NOT EXISTS analytics_candidate_touches_expiry ON analytics_candidate_attribution_touches(expires_at) WHERE eligibility IN ('overlay_only','review')"
2462
+ );
2463
+ await client.query(
2464
+ "CREATE INDEX IF NOT EXISTS analytics_value_evaluations_event ON analytics_event_value_evaluations(site_id, source_event_id)"
2465
+ );
2466
+ await client.query(
2467
+ "CREATE INDEX IF NOT EXISTS analytics_privacy_audits_site_time ON analytics_privacy_audits(site_id, created_at DESC)"
2468
+ );
2469
+ await client.query(
2470
+ "CREATE INDEX IF NOT EXISTS analytics_crm_provisioning_connection_time ON analytics_crm_provisioning_plans(connection_id, created_at DESC)"
2471
+ );
2472
+ await client.query(
2473
+ "CREATE INDEX IF NOT EXISTS analytics_crm_sync_due ON analytics_crm_sync_state(status, next_sync_at) WHERE next_sync_at IS NOT NULL"
2474
+ );
2475
+ await client.query(
2476
+ "CREATE INDEX IF NOT EXISTS analytics_crm_sync_receipts_connection_time ON analytics_crm_sync_receipts(connection_id, occurred_at DESC)"
2477
+ );
2478
+ await client.query(
2479
+ "CREATE INDEX IF NOT EXISTS analytics_scores_person_time ON analytics_score_evaluations(site_id, person_id, evaluated_at DESC) WHERE person_id IS NOT NULL"
2480
+ );
2481
+ await client.query(
2482
+ "CREATE INDEX IF NOT EXISTS analytics_scores_candidate_time ON analytics_score_evaluations(site_id, candidate_association_id, evaluated_at DESC) WHERE candidate_association_id IS NOT NULL"
2483
+ );
2484
+ await client.query(
2485
+ "CREATE INDEX IF NOT EXISTS analytics_prediction_results_person_time ON analytics_prediction_results(site_id, person_id, evaluated_at DESC)"
2486
+ );
2487
+ await client.query(
2488
+ "CREATE INDEX IF NOT EXISTS analytics_prediction_results_expiry ON analytics_prediction_results(expires_at) WHERE status = 'available'"
2489
+ );
2490
+ await client.query(
2491
+ "CREATE INDEX IF NOT EXISTS analytics_touches_hierarchy ON analytics_attribution_touches(site_id, platform, account_id, campaign_id, ad_set_id, ad_id, occurred_at DESC)"
2492
+ );
2493
+ await client.query(
2494
+ "CREATE INDEX IF NOT EXISTS analytics_spend_facts_hierarchy ON analytics_ad_spend_facts(site_id, platform, account_id, campaign_id, ad_set_id, ad_id, bucket DESC)"
2495
+ );
1801
2496
  await client.query(`
1802
2497
  INSERT INTO analytics_schema_migrations(version)
1803
- VALUES ('2026-08-05.1'), ('2026-08-05.2'), ('2026-08-05.3'), ('2026-08-05.4'), ('2026-08-05.5'), ('2026-08-05.6'), ('2026-08-05.7'), ('2026-08-05.8'), ('2026-08-05.9'), ('2026-08-05.10'), ('2026-08-05.11'), ('2026-08-05.12'), ('2026-08-05.13'), ('2026-08-05.14'), ('2026-08-26.1'), ('2026-08-26.2'), ('2026-08-26.3'), ('2026-08-26.4')
2498
+ VALUES ('2026-08-05.1'), ('2026-08-05.2'), ('2026-08-05.3'), ('2026-08-05.4'), ('2026-08-05.5'), ('2026-08-05.6'), ('2026-08-05.7'), ('2026-08-05.8'), ('2026-08-05.9'), ('2026-08-05.10'), ('2026-08-05.11'), ('2026-08-05.12'), ('2026-08-05.13'), ('2026-08-05.14'), ('2026-08-26.1'), ('2026-08-26.2'), ('2026-08-26.3'), ('2026-08-26.4'), ('2026-08-27.1')
1804
2499
  ON CONFLICT (version) DO NOTHING
1805
2500
  `);
1806
2501
  await client.query("COMMIT");
@@ -4301,14 +4996,14 @@ async function setAnalyticsActivationReadiness(input) {
4301
4996
  );
4302
4997
  }
4303
4998
  const evidencePredicate = input.evidenceKind === "provider_test" ? `EXISTS (SELECT 1 FROM analytics_activation_destination_tests t
4304
- WHERE t.id=$5 AND t.destination_id=d.id AND t.accepted=true)` : input.evidenceKind === "real_delivery" ? `EXISTS (SELECT 1 FROM analytics_activation_delivery_attempts a
4305
- WHERE a.id=$5 AND a.destination_id=d.id AND a.accepted=true)` : `EXISTS (SELECT 1 FROM analytics_activation_destination_tests t
4306
- WHERE t.id=$5 AND t.destination_id=d.id AND t.safe_error_category IS NOT NULL)`;
4999
+ WHERE t.id=$5::uuid AND t.destination_id=d.id AND t.accepted=true)` : input.evidenceKind === "real_delivery" ? `EXISTS (SELECT 1 FROM analytics_activation_delivery_attempts a
5000
+ WHERE a.id=$5::uuid AND a.destination_id=d.id AND a.accepted=true)` : `EXISTS (SELECT 1 FROM analytics_activation_destination_tests t
5001
+ WHERE t.id=$5::uuid AND t.destination_id=d.id AND t.safe_error_category IS NOT NULL)`;
4307
5002
  const result = await getAnalyticsPool().query(
4308
5003
  `UPDATE analytics_activation_destinations d
4309
5004
  SET readiness=$3, updated_at=now(),
4310
5005
  last_tested_at=CASE WHEN $3='verified' THEN now() ELSE last_tested_at END,
4311
- last_receipt_id=CASE WHEN $3 IN ('verified','live') THEN $5 ELSE last_receipt_id END
5006
+ last_receipt_id=CASE WHEN $3 IN ('verified','live') THEN $5::text ELSE last_receipt_id END
4312
5007
  WHERE d.id=$1 AND d.site_id=$2 AND d.readiness = ANY($4::text[])
4313
5008
  AND ${evidencePredicate}
4314
5009
  RETURNING d.readiness`,