@foundrynorth/flux-schema 1.19.4 → 1.19.6

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.
package/dist/schema.js CHANGED
@@ -186,6 +186,8 @@ export const fluxUsers = pgTable("flux_users", {
186
186
  primaryRole: fluxPrimaryRoleEnum("primary_role").notNull().default("strategist"),
187
187
  isAdmin: boolean("is_admin").default(false).notNull(),
188
188
  hasSentinelAccess: boolean("has_sentinel_access").notNull().default(false),
189
+ hasVarsityAccess: boolean("has_varsity_access").notNull().default(false),
190
+ hasOpinionAccess: boolean("has_opinion_access").notNull().default(false),
189
191
  hasDashboardAccess: boolean("has_dashboard_access").notNull().default(true),
190
192
  privacyKeywords: jsonb("privacy_keywords").default([]).$type(),
191
193
  /** HubSpot owner ID for reverse lookup (Flux user → HubSpot owner) */
@@ -5649,6 +5651,20 @@ export const fluxSemCampaigns = pgTable("flux_sem_campaigns", {
5649
5651
  latestPreresearchRunId: text("latest_preresearch_run_id"),
5650
5652
  /** Mirrors latest run status for dashboard queries without joining */
5651
5653
  preresearchStatus: text("preresearch_status"),
5654
+ // -- External visibility provider identifiers (D1 / D2 adapters) --
5655
+ /**
5656
+ * Google Search Console property URL (e.g. "sc-domain:example.com" or
5657
+ * "https://www.example.com/") used by the D1 visibility adapter to scope
5658
+ * Search Analytics queries. Null when GSC is not configured for this
5659
+ * campaign — the adapter no-ops in that case.
5660
+ */
5661
+ gscPropertyUrl: text("gsc_property_url"),
5662
+ /**
5663
+ * Google Analytics 4 property ID (numeric, e.g. "123456789") used by the
5664
+ * D2 visibility adapter to scope GA4 Data API queries. Null when GA4 is
5665
+ * not configured for this campaign — the adapter no-ops in that case.
5666
+ */
5667
+ ga4PropertyId: text("ga4_property_id"),
5652
5668
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
5653
5669
  updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
5654
5670
  /**
@@ -5865,6 +5881,25 @@ export const fluxSemChangePacks = pgTable("flux_sem_change_packs", {
5865
5881
  }, (table) => [
5866
5882
  index("flux_sem_change_packs_campaign_idx").on(table.semCampaignId, table.createdAt),
5867
5883
  index("flux_sem_change_packs_status_idx").on(table.status, table.priority),
5884
+ /**
5885
+ * Partial unique index closing a non-transactional check-then-insert race
5886
+ * across writers that dedup by (semCampaignId, recommendationKey):
5887
+ * sem-visibility-actions and sem-ingest-search-terms. Concurrent runs of
5888
+ * either could otherwise insert duplicates.
5889
+ *
5890
+ * Note: sem-propose-pacing-adjustments is NOT covered by this index — it
5891
+ * leaves recommendationKey NULL and dedups via an `approvalContext->>'signal'`
5892
+ * + `dateWindow` JSONB extract, which falls outside this partial predicate.
5893
+ * Its own dedup race is tracked separately (out of scope for this index).
5894
+ *
5895
+ * Scoped with `WHERE recommendation_key IS NOT NULL` so manual change
5896
+ * packs (no recommendationKey) remain unconstrained. PostgreSQL treats
5897
+ * NULLs as distinct in a unique index anyway, but the partial predicate
5898
+ * makes the intent explicit and avoids ambiguity.
5899
+ */
5900
+ uniqueIndex("flux_sem_change_packs_campaign_recommendation_key_uniq")
5901
+ .on(table.semCampaignId, table.recommendationKey)
5902
+ .where(sql `${table.recommendationKey} IS NOT NULL`),
5868
5903
  ]);
5869
5904
  /**
5870
5905
  * flux_sem_preresearch_runs — Automated campaign prep results.