@foundrynorth/compass-schema 1.0.22 → 1.0.24
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.d.ts +1197 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +174 -1
- package/dist/schema.js.map +1 -1
- package/dist/types/activity-feed.d.ts +2 -2
- package/dist/types/activity-feed.d.ts.map +1 -1
- package/dist/types/activity-feed.js +8 -0
- package/dist/types/activity-feed.js.map +1 -1
- package/package.json +1 -1
- package/src/schema.ts +239 -1
- package/src/types/activity-feed.ts +8 -0
package/dist/schema.js
CHANGED
|
@@ -6168,7 +6168,15 @@ export const semCampaigns = pgTable("sem_campaigns", {
|
|
|
6168
6168
|
index("sem_campaigns_status_idx").on(table.status),
|
|
6169
6169
|
]);
|
|
6170
6170
|
// =============================================================================
|
|
6171
|
-
// COMPASS EVENT OUTBOX (
|
|
6171
|
+
// COMPASS EVENT OUTBOX (durable outbox for cross-system writes)
|
|
6172
|
+
//
|
|
6173
|
+
// Generic claim/retry/DLQ pattern used by multiple producers. Events are
|
|
6174
|
+
// namespaced by `eventType` prefix so multiple workers can drain the same
|
|
6175
|
+
// table without contention:
|
|
6176
|
+
// • "flux.*" — fn-flux webhook delivery to external consumers
|
|
6177
|
+
// • "hubspot.*" — HubSpot ticket / Campaign Task writes from fn-legacy
|
|
6178
|
+
// (drained by fn-v2 hubspot-outbox-worker)
|
|
6179
|
+
// Workers filter on `event_type LIKE '{namespace}.%'` in their claim query.
|
|
6172
6180
|
// =============================================================================
|
|
6173
6181
|
export const compassEventOutboxStatusEnum = pgEnum("compass_event_outbox_status", [
|
|
6174
6182
|
"pending",
|
|
@@ -6277,4 +6285,169 @@ export const userMilestones = pgTable("user_milestones", {
|
|
|
6277
6285
|
index("user_milestones_user_id_idx").on(table.userId),
|
|
6278
6286
|
unique("user_milestones_user_key_unique").on(table.userId, table.milestoneKey),
|
|
6279
6287
|
]);
|
|
6288
|
+
// ─── Mid-Campaign Change Management ────────────────────────────────────────
|
|
6289
|
+
//
|
|
6290
|
+
// Finance-grade in-place edits to live media orders. Distinct from
|
|
6291
|
+
// AmendOrderWizard (which takes orders active→draft→sent). A mid-campaign
|
|
6292
|
+
// change keeps the order `active`, touches a surgical set of line items,
|
|
6293
|
+
// and routes through the existing rate-exception-engine if it would alter
|
|
6294
|
+
// rate rules or margin. HubSpot push is the canonical output — HubSpot's
|
|
6295
|
+
// own workflow pushes deltas to Naviga.
|
|
6296
|
+
//
|
|
6297
|
+
// State machine:
|
|
6298
|
+
// draft → (rate violation?) ─┬─ pending_rate_approval ─┬─ applied → hubspot_syncing → hubspot_synced
|
|
6299
|
+
// │ └─ rejected (terminal, line items rolled back)
|
|
6300
|
+
// └─ applied → hubspot_syncing ─┬─ hubspot_synced
|
|
6301
|
+
// └─ hubspot_failed (retryable)
|
|
6302
|
+
export const midCampaignChangeRequests = pgTable("mid_campaign_change_requests", {
|
|
6303
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6304
|
+
mediaOrderId: varchar("media_order_id")
|
|
6305
|
+
.notNull()
|
|
6306
|
+
.references(() => mediaOrders.id),
|
|
6307
|
+
reason: amendmentReasonEnum("reason").notNull(),
|
|
6308
|
+
notes: text("notes").notNull(),
|
|
6309
|
+
requestedByUserId: varchar("requested_by_user_id").notNull(),
|
|
6310
|
+
requestedByEmail: varchar("requested_by_email"),
|
|
6311
|
+
requestedByName: varchar("requested_by_name"),
|
|
6312
|
+
requestedAt: timestamp("requested_at").notNull().defaultNow(),
|
|
6313
|
+
appliedAt: timestamp("applied_at"),
|
|
6314
|
+
status: varchar("status").notNull().default("draft"),
|
|
6315
|
+
// draft | pending_rate_approval | applied | rejected | hubspot_syncing | hubspot_synced | hubspot_failed | verified
|
|
6316
|
+
// `verified` is terminal — set when taskStateJson.allComplete && hubspotTicketStage is terminal.
|
|
6317
|
+
// Denormalized summary (so finance reports never join into ops)
|
|
6318
|
+
totalInvestmentDelta: numeric("total_investment_delta", { precision: 12, scale: 2 }).notNull().default("0"),
|
|
6319
|
+
affectedLineItemCount: integer("affected_line_item_count").notNull().default(0),
|
|
6320
|
+
marginDeltaPercent: numeric("margin_delta_percent", { precision: 7, scale: 4 }),
|
|
6321
|
+
marginDeltaDollars: numeric("margin_delta_dollars", { precision: 12, scale: 2 }),
|
|
6322
|
+
// Approval state (nullable — only set when rate engine returned pending_approval for any op)
|
|
6323
|
+
rateApprovalCompletedAt: timestamp("rate_approval_completed_at"),
|
|
6324
|
+
rejectionReason: text("rejection_reason"),
|
|
6325
|
+
// HubSpot ticket (fulfillment pipeline, CHANGE_REQUIRED stage)
|
|
6326
|
+
hubspotTicketId: text("hubspot_ticket_id"),
|
|
6327
|
+
hubspotCampaignTaskIds: jsonb("hubspot_campaign_task_ids").$type().default(sql `'[]'::jsonb`),
|
|
6328
|
+
// HubSpot line-item sync (via fn-v2 hubspot-order-sync)
|
|
6329
|
+
hubspotSyncRunId: text("hubspot_sync_run_id"),
|
|
6330
|
+
hubspotSyncStartedAt: timestamp("hubspot_sync_started_at"),
|
|
6331
|
+
hubspotSyncCompletedAt: timestamp("hubspot_sync_completed_at"),
|
|
6332
|
+
hubspotSyncError: text("hubspot_sync_error"),
|
|
6333
|
+
// Closed-loop projection from HubSpot Campaign Task webhooks
|
|
6334
|
+
// (updated by handleCampaignTaskChange in hubspot-mid-campaign-writeback.ts)
|
|
6335
|
+
hubspotTicketStage: varchar("hubspot_ticket_stage", { length: 40 }),
|
|
6336
|
+
// Current ticket stage as seen in the most recent webhook delivery.
|
|
6337
|
+
// Values: CHANGE_REQUIRED | BILLING_CHANGE_REQUIRED | CANCELLED | COMPLETED | etc.
|
|
6338
|
+
taskStateJson: jsonb("task_state_json").$type(),
|
|
6339
|
+
taskStateUpdatedAt: timestamp("task_state_updated_at"),
|
|
6340
|
+
verifiedAt: timestamp("verified_at"),
|
|
6341
|
+
// Slack alert (thread follow-up replies on approval/sync completion)
|
|
6342
|
+
slackMessageTs: text("slack_message_ts"),
|
|
6343
|
+
slackChannelId: text("slack_channel_id"),
|
|
6344
|
+
// Snapshot for rollback on rate-exception rejection
|
|
6345
|
+
rollbackSnapshotVersion: integer("rollback_snapshot_version"),
|
|
6346
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
6347
|
+
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
|
6348
|
+
}, (table) => [
|
|
6349
|
+
index("mcc_requests_order_idx").on(table.mediaOrderId),
|
|
6350
|
+
index("mcc_requests_status_idx").on(table.status),
|
|
6351
|
+
index("mcc_requests_requested_at_idx").on(table.requestedAt),
|
|
6352
|
+
index("mcc_requests_order_status_idx").on(table.mediaOrderId, table.status),
|
|
6353
|
+
]);
|
|
6354
|
+
/**
|
|
6355
|
+
* One row per line-item operation inside a change request. This is the
|
|
6356
|
+
* finance report source — one row per field changed per line item.
|
|
6357
|
+
*/
|
|
6358
|
+
export const midCampaignChangeOps = pgTable("mid_campaign_change_ops", {
|
|
6359
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6360
|
+
changeRequestId: uuid("change_request_id")
|
|
6361
|
+
.notNull()
|
|
6362
|
+
.references(() => midCampaignChangeRequests.id, { onDelete: "cascade" }),
|
|
6363
|
+
opType: varchar("op_type").notNull(), // update | cancel | create
|
|
6364
|
+
lineItemId: varchar("line_item_id")
|
|
6365
|
+
.notNull()
|
|
6366
|
+
.references(() => mediaOrderLineItems.id),
|
|
6367
|
+
// For cancel+create pairs (date-shift across month boundary, product swap)
|
|
6368
|
+
supersedesLineItemId: varchar("supersedes_line_item_id").references(() => mediaOrderLineItems.id),
|
|
6369
|
+
// Denormalized for reporting (so the report query is one table scan)
|
|
6370
|
+
periodLabel: text("period_label"), // "Aug 2026"
|
|
6371
|
+
productCode: text("product_code"),
|
|
6372
|
+
productName: text("product_name"),
|
|
6373
|
+
placement: text("placement"),
|
|
6374
|
+
// The per-field diff — this is the atom finance sees
|
|
6375
|
+
fieldChanges: jsonb("field_changes")
|
|
6376
|
+
.notNull()
|
|
6377
|
+
.$type(),
|
|
6378
|
+
// Per-op investment delta (sums to totalInvestmentDelta on the request)
|
|
6379
|
+
investmentDelta: numeric("investment_delta", { precision: 12, scale: 2 }).notNull().default("0"),
|
|
6380
|
+
// Tracks which HubSpot action this op triggered when synced
|
|
6381
|
+
hubspotOp: varchar("hubspot_op"), // update | cancel | create | skipped
|
|
6382
|
+
hubspotLineItemIdBefore: text("hubspot_line_item_id_before"),
|
|
6383
|
+
hubspotLineItemIdAfter: text("hubspot_line_item_id_after"),
|
|
6384
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
6385
|
+
}, (table) => [
|
|
6386
|
+
index("mcc_ops_change_request_idx").on(table.changeRequestId),
|
|
6387
|
+
index("mcc_ops_line_item_idx").on(table.lineItemId),
|
|
6388
|
+
index("mcc_ops_period_idx").on(table.periodLabel),
|
|
6389
|
+
uniqueIndex("mcc_ops_change_request_line_item_unique").on(table.changeRequestId, table.lineItemId),
|
|
6390
|
+
]);
|
|
6391
|
+
/**
|
|
6392
|
+
* Join table: change requests ↔ rate exception requests. A single change
|
|
6393
|
+
* request may trigger N rate exceptions (one per line item that violates
|
|
6394
|
+
* rate rules). The change request blocks in `pending_rate_approval` until
|
|
6395
|
+
* every linked exception is decided; if any reject, the change rolls back.
|
|
6396
|
+
*/
|
|
6397
|
+
export const midCampaignChangeRateExceptions = pgTable("mid_campaign_change_rate_exceptions", {
|
|
6398
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
6399
|
+
changeRequestId: uuid("change_request_id")
|
|
6400
|
+
.notNull()
|
|
6401
|
+
.references(() => midCampaignChangeRequests.id, { onDelete: "cascade" }),
|
|
6402
|
+
rateExceptionRequestId: uuid("rate_exception_request_id")
|
|
6403
|
+
.notNull()
|
|
6404
|
+
.references(() => rateExceptionRequests.id, { onDelete: "restrict" }),
|
|
6405
|
+
// Which op on the change request this exception corresponds to
|
|
6406
|
+
changeOpId: uuid("change_op_id").references(() => midCampaignChangeOps.id, { onDelete: "cascade" }),
|
|
6407
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
6408
|
+
}, (table) => [
|
|
6409
|
+
index("mcc_rate_exc_change_request_idx").on(table.changeRequestId),
|
|
6410
|
+
index("mcc_rate_exc_exception_idx").on(table.rateExceptionRequestId),
|
|
6411
|
+
uniqueIndex("mcc_rate_exc_change_request_exception_unique").on(table.changeRequestId, table.rateExceptionRequestId),
|
|
6412
|
+
]);
|
|
6413
|
+
/**
|
|
6414
|
+
* Campaign Task templates for mid-campaign changes. Hybrid storage — config
|
|
6415
|
+
* in DB (queue, offset, priority, is_active, display_order) so admins can
|
|
6416
|
+
* hot-edit without deploys; predicate logic stays in a code registry
|
|
6417
|
+
* (mid-campaign-template-predicates.ts) keyed by `predicateKey`.
|
|
6418
|
+
*
|
|
6419
|
+
* CI validator asserts every row's `queue` value is in the live HubSpot
|
|
6420
|
+
* Campaign Task `queue` enum. Prevents the "wrong queue ships to HubSpot and
|
|
6421
|
+
* silently fails enum validation" bug.
|
|
6422
|
+
*/
|
|
6423
|
+
export const midCampaignTaskTemplates = pgTable("mid_campaign_task_templates", {
|
|
6424
|
+
// Stable key (e.g. "review_change_request", "verify_billing_impact").
|
|
6425
|
+
// Used as the templateId in the task plan and for deduplication.
|
|
6426
|
+
id: varchar("id", { length: 64 }).primaryKey(),
|
|
6427
|
+
// Human-readable name — sent as campaign_task_name to HubSpot.
|
|
6428
|
+
name: text("name").notNull(),
|
|
6429
|
+
// Portal Campaign Task queue enum value. CI validator enforces.
|
|
6430
|
+
queue: varchar("queue", { length: 40 }).notNull(),
|
|
6431
|
+
// Days from applied-at when the task is due. Negative = before apply.
|
|
6432
|
+
offsetDays: integer("offset_days").notNull().default(0),
|
|
6433
|
+
// Written to HubSpot priority: high | medium | low.
|
|
6434
|
+
priority: varchar("priority", { length: 16 }).notNull().default("medium"),
|
|
6435
|
+
// Key into the PREDICATES registry in code. Must resolve at runtime.
|
|
6436
|
+
// Valid: always, has_financial_change, has_digital_update_or_cancel,
|
|
6437
|
+
// has_digital_create, has_print_change, touches_dates
|
|
6438
|
+
predicateKey: varchar("predicate_key", { length: 64 }).notNull(),
|
|
6439
|
+
// Inactive templates are skipped by buildMidCampaignChangeTaskPlan.
|
|
6440
|
+
isActive: boolean("is_active").notNull().default(true),
|
|
6441
|
+
// Display/evaluation order. Lower = higher up in the Compass task list.
|
|
6442
|
+
displayOrder: integer("display_order").notNull().default(0),
|
|
6443
|
+
// Optional human description for the admin UI.
|
|
6444
|
+
description: text("description"),
|
|
6445
|
+
// Audit — who last edited and when.
|
|
6446
|
+
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
|
6447
|
+
updatedBy: varchar("updated_by"),
|
|
6448
|
+
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
6449
|
+
}, (table) => [
|
|
6450
|
+
index("mid_campaign_task_templates_active_idx").on(table.isActive),
|
|
6451
|
+
index("mid_campaign_task_templates_order_idx").on(table.displayOrder),
|
|
6452
|
+
]);
|
|
6280
6453
|
//# sourceMappingURL=schema.js.map
|