@foundrynorth/compass-schema 1.0.21 → 1.0.23

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.
@@ -3,13 +3,13 @@
3
3
  * post-submission visibility (contract alerts, fulfillment status,
4
4
  * creative status, amendments, rate exception resolution).
5
5
  */
6
- export declare const ACTIVITY_EVENT_TYPES: readonly ["fulfillment_stage_changed", "creative_status_changed", "creative_delivered", "signing_progress", "drawdown_alert", "renewal_reminder", "amendment_submitted", "rate_exception_resolved", "sla_hold_set", "sla_hold_cleared", "sla_milestone_at_risk", "sla_milestone_breached", "sla_evaluated"];
6
+ export declare const ACTIVITY_EVENT_TYPES: readonly ["fulfillment_stage_changed", "creative_status_changed", "creative_delivered", "signing_progress", "drawdown_alert", "renewal_reminder", "amendment_submitted", "rate_exception_resolved", "sla_hold_set", "sla_hold_cleared", "sla_milestone_at_risk", "sla_milestone_breached", "sla_evaluated", "mid_campaign_change_applied", "mid_campaign_task_state_changed", "mid_campaign_change_verified"];
7
7
  export type ActivityEventType = (typeof ACTIVITY_EVENT_TYPES)[number];
8
8
  export declare const ACTIVITY_SEVERITIES: readonly ["info", "success", "warning", "action_needed"];
9
9
  export type ActivitySeverity = (typeof ACTIVITY_SEVERITIES)[number];
10
10
  export declare const ACTIVITY_SOURCE_SYSTEMS: readonly ["flux", "forge", "hubspot", "compass"];
11
11
  export type ActivitySourceSystem = (typeof ACTIVITY_SOURCE_SYSTEMS)[number];
12
- export declare const PENDING_ACTION_TYPES: readonly ["creative_revision_needed", "creative_rejected", "fulfillment_blocked", "rate_exception_pending", "sla_hold_active"];
12
+ export declare const PENDING_ACTION_TYPES: readonly ["creative_revision_needed", "creative_rejected", "fulfillment_blocked", "rate_exception_pending", "sla_hold_active", "mid_campaign_tasks_pending"];
13
13
  export type PendingActionType = (typeof PENDING_ACTION_TYPES)[number];
14
14
  /** The event envelope fn-flux sends to fn-legacy */
15
15
  export interface CompassStatusEvent {
@@ -1 +1 @@
1
- {"version":3,"file":"activity-feed.d.ts","sourceRoot":"","sources":["../../src/types/activity-feed.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,oBAAoB,6SAcvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,eAAO,MAAM,mBAAmB,0DAKtB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,eAAO,MAAM,uBAAuB,kDAK1B,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,eAAO,MAAM,oBAAoB,gIAMvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,QAAQ,EAAE,gBAAgB,CAAC;QAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;KAC3C,CAAC;CACH;AAED,mEAAmE;AACnE,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAChD,iBAAiB,EACjB;IAAE,mBAAmB,EAAE,iBAAiB,CAAC;IAAC,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAAE,CAsBzE,CAAC"}
1
+ {"version":3,"file":"activity-feed.d.ts","sourceRoot":"","sources":["../../src/types/activity-feed.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,eAAO,MAAM,oBAAoB,+YAiBvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,eAAO,MAAM,mBAAmB,0DAKtB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,eAAO,MAAM,uBAAuB,kDAK1B,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,eAAO,MAAM,oBAAoB,8JAOvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,QAAQ,EAAE,gBAAgB,CAAC;QAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;KAC3C,CAAC;CACH;AAED,mEAAmE;AACnE,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAChD,iBAAiB,EACjB;IAAE,mBAAmB,EAAE,iBAAiB,CAAC;IAAC,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAAE,CA0BzE,CAAC"}
@@ -17,6 +17,9 @@ export const ACTIVITY_EVENT_TYPES = [
17
17
  "sla_milestone_at_risk",
18
18
  "sla_milestone_breached",
19
19
  "sla_evaluated",
20
+ "mid_campaign_change_applied",
21
+ "mid_campaign_task_state_changed",
22
+ "mid_campaign_change_verified",
20
23
  ];
21
24
  export const ACTIVITY_SEVERITIES = [
22
25
  "info",
@@ -36,6 +39,7 @@ export const PENDING_ACTION_TYPES = [
36
39
  "fulfillment_blocked",
37
40
  "rate_exception_pending",
38
41
  "sla_hold_active",
42
+ "mid_campaign_tasks_pending",
39
43
  ];
40
44
  /** Resolution mapping: which events clear which pending actions */
41
45
  export const PENDING_ACTION_RESOLUTION_MAP = {
@@ -59,5 +63,9 @@ export const PENDING_ACTION_RESOLUTION_MAP = {
59
63
  resolvedByEventType: "sla_hold_cleared",
60
64
  resolvedByStatuses: ["clear"],
61
65
  },
66
+ mid_campaign_tasks_pending: {
67
+ resolvedByEventType: "mid_campaign_change_verified",
68
+ resolvedByStatuses: ["verified"],
69
+ },
62
70
  };
63
71
  //# sourceMappingURL=activity-feed.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"activity-feed.js","sourceRoot":"","sources":["../../src/types/activity-feed.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,2BAA2B;IAC3B,yBAAyB;IACzB,oBAAoB;IACpB,kBAAkB;IAClB,gBAAgB;IAChB,kBAAkB;IAClB,qBAAqB;IACrB,yBAAyB;IACzB,cAAc;IACd,kBAAkB;IAClB,uBAAuB;IACvB,wBAAwB;IACxB,eAAe;CACP,CAAC;AAIX,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,MAAM;IACN,SAAS;IACT,SAAS;IACT,eAAe;CACP,CAAC;AAIX,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,MAAM;IACN,OAAO;IACP,SAAS;IACT,SAAS;CACD,CAAC;AAIX,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,0BAA0B;IAC1B,mBAAmB;IACnB,qBAAqB;IACrB,wBAAwB;IACxB,iBAAiB;CACT,CAAC;AAuBX,mEAAmE;AACnE,MAAM,CAAC,MAAM,6BAA6B,GAGtC;IACF,wBAAwB,EAAE;QACxB,mBAAmB,EAAE,yBAAyB;QAC9C,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC;KAC9C;IACD,iBAAiB,EAAE;QACjB,mBAAmB,EAAE,yBAAyB;QAC9C,kBAAkB,EAAE,CAAC,UAAU,CAAC;KACjC;IACD,mBAAmB,EAAE;QACnB,mBAAmB,EAAE,2BAA2B;QAChD,kBAAkB,EAAE,CAAC,qBAAqB,EAAE,SAAS,EAAE,WAAW,CAAC;KACpE;IACD,sBAAsB,EAAE;QACtB,mBAAmB,EAAE,yBAAyB;QAC9C,kBAAkB,EAAE,CAAC,UAAU,EAAE,eAAe,EAAE,UAAU,CAAC;KAC9D;IACD,eAAe,EAAE;QACf,mBAAmB,EAAE,kBAAkB;QACvC,kBAAkB,EAAE,CAAC,OAAO,CAAC;KAC9B;CACF,CAAC"}
1
+ {"version":3,"file":"activity-feed.js","sourceRoot":"","sources":["../../src/types/activity-feed.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,2BAA2B;IAC3B,yBAAyB;IACzB,oBAAoB;IACpB,kBAAkB;IAClB,gBAAgB;IAChB,kBAAkB;IAClB,qBAAqB;IACrB,yBAAyB;IACzB,cAAc;IACd,kBAAkB;IAClB,uBAAuB;IACvB,wBAAwB;IACxB,eAAe;IACf,6BAA6B;IAC7B,iCAAiC;IACjC,8BAA8B;CACtB,CAAC;AAIX,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,MAAM;IACN,SAAS;IACT,SAAS;IACT,eAAe;CACP,CAAC;AAIX,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,MAAM;IACN,OAAO;IACP,SAAS;IACT,SAAS;CACD,CAAC;AAIX,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,0BAA0B;IAC1B,mBAAmB;IACnB,qBAAqB;IACrB,wBAAwB;IACxB,iBAAiB;IACjB,4BAA4B;CACpB,CAAC;AAuBX,mEAAmE;AACnE,MAAM,CAAC,MAAM,6BAA6B,GAGtC;IACF,wBAAwB,EAAE;QACxB,mBAAmB,EAAE,yBAAyB;QAC9C,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC;KAC9C;IACD,iBAAiB,EAAE;QACjB,mBAAmB,EAAE,yBAAyB;QAC9C,kBAAkB,EAAE,CAAC,UAAU,CAAC;KACjC;IACD,mBAAmB,EAAE;QACnB,mBAAmB,EAAE,2BAA2B;QAChD,kBAAkB,EAAE,CAAC,qBAAqB,EAAE,SAAS,EAAE,WAAW,CAAC;KACpE;IACD,sBAAsB,EAAE;QACtB,mBAAmB,EAAE,yBAAyB;QAC9C,kBAAkB,EAAE,CAAC,UAAU,EAAE,eAAe,EAAE,UAAU,CAAC;KAC9D;IACD,eAAe,EAAE;QACf,mBAAmB,EAAE,kBAAkB;QACvC,kBAAkB,EAAE,CAAC,OAAO,CAAC;KAC9B;IACD,0BAA0B,EAAE;QAC1B,mBAAmB,EAAE,8BAA8B;QACnD,kBAAkB,EAAE,CAAC,UAAU,CAAC;KACjC;CACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foundrynorth/compass-schema",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "Canonical Drizzle ORM schema for Foundry Compass (rough-waterfall database)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/schema.ts CHANGED
@@ -598,6 +598,35 @@ export const competitorSchema = z.object({
598
598
  lng: z.number(),
599
599
  })
600
600
  .optional(), // Detailed location information
601
+
602
+ // Ad library data from Apify deep enrichment
603
+ ads: z
604
+ .object({
605
+ facebook: z
606
+ .array(
607
+ z.object({
608
+ adImage: z.string().optional(),
609
+ headline: z.string().optional(),
610
+ adCopy: z.string().optional(),
611
+ callToAction: z.string().optional(),
612
+ publisherPlatforms: z.array(z.string()).optional(),
613
+ startDate: z.string().optional(),
614
+ })
615
+ )
616
+ .optional(),
617
+ google: z
618
+ .array(
619
+ z.object({
620
+ headline: z.string().optional(),
621
+ description: z.string().optional(),
622
+ landingPage: z.string().optional(),
623
+ adType: z.string().optional(),
624
+ imageUrl: z.string().optional(),
625
+ })
626
+ )
627
+ .optional(),
628
+ })
629
+ .optional(),
601
630
  });
602
631
 
603
632
  export type Competitor = z.infer<typeof competitorSchema>;
@@ -1244,6 +1273,7 @@ export const plans = pgTable("plans", {
1244
1273
  defaultTargeting: text("default_targeting"),
1245
1274
  defaultAudience: text("default_audience"),
1246
1275
  defaultPromoting: text("default_promoting"),
1276
+ defaultCreativeSource: varchar("default_creative_source"),
1247
1277
  defaultFlightStart: date("default_flight_start"),
1248
1278
  defaultFlightEnd: date("default_flight_end"),
1249
1279
 
@@ -1315,8 +1345,12 @@ export const proposals = pgTable(
1315
1345
  .default(sql`gen_random_uuid()`),
1316
1346
 
1317
1347
  // Optional plan reference (nullable — can be standalone)
1318
- planId: varchar("plan_id"),
1319
- engagementId: varchar("engagement_id"), // FK → engagements.id (nullable during transition)
1348
+ planId: varchar("plan_id").references(() => plans.id, {
1349
+ onDelete: "set null",
1350
+ }),
1351
+ engagementId: varchar("engagement_id").references(() => engagements.id, {
1352
+ onDelete: "set null",
1353
+ }), // nullable during transition
1320
1354
 
1321
1355
  // Configuration references (which plan_configurations to include)
1322
1356
  configurationIds: jsonb("configuration_ids").$type<string[]>(),
@@ -1387,6 +1421,41 @@ export const insertPlanSchema = createInsertSchema(plans).omit({
1387
1421
  createdAt: true,
1388
1422
  } as any);
1389
1423
 
1424
+ // ============================================================================
1425
+ // Proposal Engagement Tracking
1426
+ // ============================================================================
1427
+
1428
+ export const proposalEngagement = pgTable(
1429
+ "proposal_engagement",
1430
+ {
1431
+ id: varchar("id")
1432
+ .primaryKey()
1433
+ .default(sql`gen_random_uuid()`),
1434
+ proposalId: varchar("proposal_id"), // FK → proposals.id (nullable for legacy plan-based shares)
1435
+ planId: varchar("plan_id"), // FK → plans.id (fallback for legacy shares)
1436
+ shareToken: text("share_token").notNull(), // The share token used to access
1437
+ sessionId: text("session_id").notNull(), // Anonymous session ID (crypto.randomUUID on client)
1438
+ // Engagement data
1439
+ events: jsonb("events").notNull().default(sql`'[]'::jsonb`), // Array of engagement events
1440
+ engagementScore: integer("engagement_score").default(0), // Computed 0-100 score
1441
+ summary: text("summary"), // AI-generated follow-up brief
1442
+ // Device context
1443
+ device: text("device"), // "desktop" | "mobile" | "tablet"
1444
+ referrer: text("referrer"), // HTTP referrer
1445
+ userAgent: text("user_agent"),
1446
+ // Timestamps
1447
+ firstSeenAt: timestamp("first_seen_at").notNull().defaultNow(),
1448
+ lastSeenAt: timestamp("last_seen_at").notNull().defaultNow(),
1449
+ createdAt: timestamp("created_at").notNull().defaultNow(),
1450
+ },
1451
+ (table) => [
1452
+ index("proposal_engagement_proposal_id_idx").on(table.proposalId),
1453
+ index("proposal_engagement_plan_id_idx").on(table.planId),
1454
+ index("proposal_engagement_share_token_idx").on(table.shareToken),
1455
+ index("proposal_engagement_token_session_idx").on(table.shareToken, table.sessionId),
1456
+ ]
1457
+ );
1458
+
1390
1459
  export type InsertPlan = z.infer<typeof insertPlanSchema>;
1391
1460
  export type Plan = typeof plans.$inferSelect;
1392
1461
 
@@ -4805,22 +4874,134 @@ export type SalesInitiative = typeof salesInitiatives.$inferSelect;
4805
4874
  export type InsertSalesInitiative = z.infer<typeof insertSalesInitiativeSchema>;
4806
4875
  export type UpdateSalesInitiative = z.infer<typeof updateSalesInitiativeSchema>;
4807
4876
 
4877
+ export const salesPackages = pgTable(
4878
+ "sales_packages",
4879
+ {
4880
+ id: uuid("id").primaryKey().defaultRandom(),
4881
+ code: varchar("code").notNull().unique(),
4882
+ name: varchar("name").notNull(),
4883
+ description: text("description"),
4884
+ initiativeCode: varchar("initiative_code"),
4885
+ sellerSummary: text("seller_summary"),
4886
+ packagePrice: numeric("package_price", { precision: 12, scale: 2 }),
4887
+ discountPercent: numeric("discount_percent", { precision: 5, scale: 2 }),
4888
+ addedValueAmount: numeric("added_value_amount", { precision: 12, scale: 2 }),
4889
+ addedValueDescription: varchar("added_value_description", { length: 255 }),
4890
+ hubspotPackageValue: varchar("hubspot_package_value").notNull().unique(),
4891
+ startsAt: date("starts_at"),
4892
+ endsAt: date("ends_at"),
4893
+ status: varchar("status").notNull().default("draft"),
4894
+ recommendationMode: varchar("recommendation_mode").notNull().default("both"),
4895
+ recommendationPriority: integer("recommendation_priority").notNull().default(0),
4896
+ eligibilityRules: jsonb("eligibility_rules").$type<{
4897
+ goals?: string[];
4898
+ industries?: string[];
4899
+ geographyTags?: string[];
4900
+ minBudget?: number | null;
4901
+ maxBudget?: number | null;
4902
+ requireActiveWindow?: boolean;
4903
+ minProductOverlap?: number | null;
4904
+ allowSubsetMatch?: boolean;
4905
+ allowNearMatch?: boolean;
4906
+ compareSignals?: string[];
4907
+ }>(),
4908
+ assistantHints: jsonb("assistant_hints").$type<{
4909
+ recommendedWhen?: string[];
4910
+ upsellMessage?: string | null;
4911
+ compareHeadline?: string | null;
4912
+ compareBody?: string | null;
4913
+ }>(),
4914
+ taskTemplateMode: varchar("task_template_mode").notNull().default("standard"),
4915
+ displayOrder: integer("display_order").notNull().default(0),
4916
+ isActive: boolean("is_active").notNull().default(true),
4917
+ createdAt: timestamp("created_at").notNull().defaultNow(),
4918
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
4919
+ },
4920
+ (table) => [
4921
+ index("sales_packages_active_idx").on(table.isActive),
4922
+ index("sales_packages_status_idx").on(table.status),
4923
+ index("sales_packages_display_order_idx").on(table.displayOrder),
4924
+ index("sales_packages_initiative_code_idx").on(table.initiativeCode),
4925
+ ],
4926
+ );
4927
+
4928
+ export const insertSalesPackageSchema = createInsertSchema(salesPackages).omit({
4929
+ id: true,
4930
+ createdAt: true,
4931
+ updatedAt: true,
4932
+ });
4933
+ export const updateSalesPackageSchema = insertSalesPackageSchema.partial();
4934
+ export type SalesPackage = typeof salesPackages.$inferSelect;
4935
+ export type InsertSalesPackage = z.infer<typeof insertSalesPackageSchema>;
4936
+ export type UpdateSalesPackage = z.infer<typeof updateSalesPackageSchema>;
4937
+
4938
+ export const salesPackageProducts = pgTable(
4939
+ "sales_package_products",
4940
+ {
4941
+ id: uuid("id").primaryKey().defaultRandom(),
4942
+ salesPackageCode: varchar("sales_package_code").notNull(),
4943
+ stribProductId: uuid("strib_product_id")
4944
+ .notNull()
4945
+ .references(() => stribProducts.id, { onDelete: "cascade" }),
4946
+ displayOrder: integer("display_order").notNull().default(0),
4947
+ approvedUnitRate: numeric("approved_unit_rate", { precision: 12, scale: 4 }),
4948
+ approvedRateType: varchar("approved_rate_type"),
4949
+ allocationMode: varchar("allocation_mode").notNull().default("percent"),
4950
+ allocationValue: numeric("allocation_value", { precision: 12, scale: 4 }),
4951
+ required: boolean("required").notNull().default(true),
4952
+ defaultFulfillmentDetails: jsonb("default_fulfillment_details"),
4953
+ createdAt: timestamp("created_at").notNull().defaultNow(),
4954
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
4955
+ },
4956
+ (table) => [
4957
+ uniqueIndex("sales_package_products_unique_idx").on(
4958
+ table.salesPackageCode,
4959
+ table.stribProductId,
4960
+ ),
4961
+ index("sales_package_products_code_idx").on(table.salesPackageCode),
4962
+ index("sales_package_products_product_idx").on(table.stribProductId),
4963
+ ],
4964
+ );
4965
+
4966
+ export type SalesPackageProduct = typeof salesPackageProducts.$inferSelect;
4967
+
4808
4968
  export const mediaOrders = pgTable(
4809
4969
  "media_orders",
4810
4970
  {
4811
4971
  id: varchar("id")
4812
4972
  .primaryKey()
4813
4973
  .default(sql`gen_random_uuid()`),
4814
- planId: varchar("plan_id"), // FK → plans.id (nullable — can exist without a plan)
4815
- engagementId: varchar("engagement_id"), // FK → engagements.id (nullable during transition)
4974
+ planId: varchar("plan_id").references(() => plans.id, {
4975
+ onDelete: "set null",
4976
+ }), // nullable — can exist without a plan
4977
+ engagementId: varchar("engagement_id").references(() => engagements.id, {
4978
+ onDelete: "set null",
4979
+ }), // nullable during transition
4816
4980
  configurationId: varchar("configuration_id"), // FK → plan_configurations.id (named product mix)
4817
4981
  optionId: varchar("option_id"), // FK → plan_options.id (canonical selected option)
4818
- contractId: uuid("contract_id"), // FK → contracts.id (nullable — links to annual contract)
4982
+ contractId: uuid("contract_id").references(() => contracts.id, {
4983
+ onDelete: "set null",
4984
+ }), // nullable — links to annual contract
4819
4985
  partnerId: varchar("partner_id"),
4820
4986
  hubspotDealId: text("hubspot_deal_id"),
4821
4987
  hubspotCompanyId: text("hubspot_company_id"),
4822
4988
  parentHubspotCompanyId: text("parent_hubspot_company_id"),
4823
4989
  initiativeCode: varchar("initiative_code"),
4990
+ salesPackageCode: varchar("sales_package_code"),
4991
+ salesPackageSnapshot: jsonb("sales_package_snapshot").$type<Array<{
4992
+ code: string;
4993
+ name: string;
4994
+ packagePrice: number | null;
4995
+ initiativeCode: string | null;
4996
+ componentCount: number;
4997
+ appliedAt: string;
4998
+ packageSource: "direct_package" | "upgrade_from_selection" | "manual_products";
4999
+ compareSignals?: string[];
5000
+ sellerSummary?: string | null;
5001
+ discountPercent?: number | null;
5002
+ addedValueAmount?: number | null;
5003
+ addedValueDescription?: string | null;
5004
+ }>>(),
4824
5005
 
4825
5006
  // Client header
4826
5007
  clientName: text("client_name").notNull(),
@@ -4944,6 +5125,7 @@ export const mediaOrders = pgTable(
4944
5125
  index("media_orders_parent_company_idx").on(table.parentHubspotCompanyId),
4945
5126
  index("media_orders_contract_id_idx").on(table.contractId),
4946
5127
  index("media_orders_initiative_code_idx").on(table.initiativeCode),
5128
+ index("media_orders_sales_package_code_idx").on(table.salesPackageCode),
4947
5129
  ]
4948
5130
  );
4949
5131
 
@@ -5073,6 +5255,10 @@ export const mediaOrderLineItems = pgTable(
5073
5255
  fulfillmentDetails: jsonb("fulfillment_details"),
5074
5256
  creativeSource: varchar("creative_source"),
5075
5257
  initiativeCode: varchar("initiative_code"),
5258
+ salesPackageCode: varchar("sales_package_code"),
5259
+ salesPackageComponentRef: varchar("sales_package_component_ref"),
5260
+ isPackageDerived: boolean("is_package_derived").notNull().default(false),
5261
+ packagePricingSource: varchar("package_pricing_source"),
5076
5262
 
5077
5263
  // Ad trafficking reference key (auto-generated naming convention)
5078
5264
  trafficKey: text("traffic_key"),
@@ -5099,6 +5285,7 @@ export const mediaOrderLineItems = pgTable(
5099
5285
  index("media_order_line_items_location_id_idx").on(table.locationId),
5100
5286
  index("media_order_line_items_tracking_key_idx").on(table.trackingKey),
5101
5287
  index("media_order_line_items_initiative_code_idx").on(table.initiativeCode),
5288
+ index("media_order_line_items_sales_package_code_idx").on(table.salesPackageCode),
5102
5289
  ]
5103
5290
  );
5104
5291
 
@@ -8316,11 +8503,20 @@ export type SemCampaignRecord = typeof semCampaigns.$inferSelect;
8316
8503
  export type InsertSemCampaign = typeof semCampaigns.$inferInsert;
8317
8504
 
8318
8505
  // =============================================================================
8319
- // COMPASS EVENT OUTBOX (dead-letter queue for fn-flux webhook events)
8506
+ // COMPASS EVENT OUTBOX (durable outbox for cross-system writes)
8507
+ //
8508
+ // Generic claim/retry/DLQ pattern used by multiple producers. Events are
8509
+ // namespaced by `eventType` prefix so multiple workers can drain the same
8510
+ // table without contention:
8511
+ // • "flux.*" — fn-flux webhook delivery to external consumers
8512
+ // • "hubspot.*" — HubSpot ticket / Campaign Task writes from fn-legacy
8513
+ // (drained by fn-v2 hubspot-outbox-worker)
8514
+ // Workers filter on `event_type LIKE '{namespace}.%'` in their claim query.
8320
8515
  // =============================================================================
8321
8516
 
8322
8517
  export const compassEventOutboxStatusEnum = pgEnum("compass_event_outbox_status", [
8323
8518
  "pending",
8519
+ "processing",
8324
8520
  "failed",
8325
8521
  "delivered",
8326
8522
  "dead_letter",
@@ -8422,6 +8618,8 @@ export const pendingActions = pgTable(
8422
8618
  },
8423
8619
  (table) => [
8424
8620
  index("pending_actions_order_idx").on(table.mediaOrderId),
8621
+ index("pending_actions_line_item_idx").on(table.lineItemId),
8622
+ index("pending_actions_activity_feed_idx").on(table.activityFeedId),
8425
8623
  index("pending_actions_unresolved_idx").on(table.mediaOrderId).where(
8426
8624
  sql`resolved_at IS NULL`,
8427
8625
  ),
@@ -18,6 +18,9 @@ export const ACTIVITY_EVENT_TYPES = [
18
18
  "sla_milestone_at_risk",
19
19
  "sla_milestone_breached",
20
20
  "sla_evaluated",
21
+ "mid_campaign_change_applied",
22
+ "mid_campaign_task_state_changed",
23
+ "mid_campaign_change_verified",
21
24
  ] as const;
22
25
 
23
26
  export type ActivityEventType = (typeof ACTIVITY_EVENT_TYPES)[number];
@@ -46,6 +49,7 @@ export const PENDING_ACTION_TYPES = [
46
49
  "fulfillment_blocked",
47
50
  "rate_exception_pending",
48
51
  "sla_hold_active",
52
+ "mid_campaign_tasks_pending",
49
53
  ] as const;
50
54
 
51
55
  export type PendingActionType = (typeof PENDING_ACTION_TYPES)[number];
@@ -94,4 +98,8 @@ export const PENDING_ACTION_RESOLUTION_MAP: Record<
94
98
  resolvedByEventType: "sla_hold_cleared",
95
99
  resolvedByStatuses: ["clear"],
96
100
  },
101
+ mid_campaign_tasks_pending: {
102
+ resolvedByEventType: "mid_campaign_change_verified",
103
+ resolvedByStatuses: ["verified"],
104
+ },
97
105
  };