spine-framework 0.3.17 → 0.3.19

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.
Files changed (270) hide show
  1. package/.framework/cli/bin.mjs +16 -0
  2. package/.framework/cli/commands/create-app.ts +1 -1
  3. package/.framework/cli/commands/dev.ts +215 -33
  4. package/.framework/cli/index.ts +37 -75
  5. package/.framework/functions/_shared/app-manifest.ts +8 -15
  6. package/.framework/functions/_shared/db.ts +11 -15
  7. package/.framework/functions/_shared/index.ts +7 -16
  8. package/.framework/functions/_shared/principal.ts +4 -62
  9. package/.framework/functions/_shared/resolve-ids.ts +6 -6
  10. package/.framework/functions/_shared/webhook-registry.ts +43 -1
  11. package/.framework/functions/admin-funnel.ts +104 -0
  12. package/.framework/functions/admin-install-funnel.ts +225 -0
  13. package/.framework/functions/admin-signals.ts +241 -0
  14. package/.framework/functions/integration-routes.ts +4 -8
  15. package/.framework/migrations/001_schema.sql +406 -0
  16. package/.framework/migrations/002_rls_and_functions.sql +710 -0
  17. package/.framework/migrations/003_system_data.sql +520 -0
  18. package/.framework/migrations/004_enhancements.sql +43 -0
  19. package/.framework/public/_redirects +3 -0
  20. package/.framework/public/spine-logo.jpg +0 -0
  21. package/.framework/public/spine-logo.svg +3 -0
  22. package/{scripts → .framework/scripts}/assemble-frontend.sh +8 -5
  23. package/{scripts → .framework/scripts}/assemble-functions.sh +4 -47
  24. package/{scripts → .framework/scripts}/assemble.sh +2 -8
  25. package/{scripts → .framework/scripts}/boundary-check.sh +5 -9
  26. package/{scripts → .framework/scripts}/check-core-integrity.sh +0 -0
  27. package/.framework/seeds/link_types/account_cli_instances.json +9 -0
  28. package/.framework/seeds/types/funnel_signal_use.json +98 -0
  29. package/.framework/seeds/types/spine_instance.json +109 -0
  30. package/.framework/src/App.tsx +11 -21
  31. package/.framework/src/components/CustomAppLoader.tsx +2 -19
  32. package/.framework/src/components/ui/accordion.tsx +1 -1
  33. package/.framework/src/components/ui/alert-dialog.tsx +1 -1
  34. package/.framework/src/components/ui/aspect-ratio.tsx +1 -1
  35. package/.framework/src/components/ui/avatar.tsx +1 -1
  36. package/.framework/src/components/ui/badge.tsx +2 -2
  37. package/.framework/src/components/ui/breadcrumb.tsx +2 -2
  38. package/.framework/src/components/ui/button-group.tsx +2 -2
  39. package/.framework/src/components/ui/button.tsx +2 -2
  40. package/.framework/src/components/ui/chart.tsx +6 -1
  41. package/.framework/src/components/ui/checkbox.tsx +1 -1
  42. package/.framework/src/components/ui/collapsible.tsx +1 -1
  43. package/.framework/src/components/ui/context-menu.tsx +1 -1
  44. package/.framework/src/components/ui/dialog.tsx +1 -1
  45. package/.framework/src/components/ui/direction.tsx +17 -1
  46. package/.framework/src/components/ui/dropdown-menu.tsx +1 -1
  47. package/.framework/src/components/ui/hover-card.tsx +1 -1
  48. package/.framework/src/components/ui/item.tsx +2 -2
  49. package/.framework/src/components/ui/label.tsx +1 -1
  50. package/.framework/src/components/ui/menubar.tsx +1 -1
  51. package/.framework/src/components/ui/navigation-menu.tsx +1 -1
  52. package/.framework/src/components/ui/popover.tsx +1 -1
  53. package/.framework/src/components/ui/progress.tsx +1 -1
  54. package/.framework/src/components/ui/radio-group.tsx +1 -1
  55. package/.framework/src/components/ui/scroll-area.tsx +1 -1
  56. package/.framework/src/components/ui/select.tsx +1 -1
  57. package/.framework/src/components/ui/separator.tsx +1 -1
  58. package/.framework/src/components/ui/sheet.tsx +1 -1
  59. package/.framework/src/components/ui/sidebar.tsx +6 -6
  60. package/.framework/src/components/ui/slider.tsx +1 -1
  61. package/.framework/src/components/ui/switch.tsx +1 -1
  62. package/.framework/src/components/ui/tabs.tsx +1 -1
  63. package/.framework/src/components/ui/toggle-group.tsx +1 -1
  64. package/.framework/src/components/ui/toggle.tsx +1 -1
  65. package/.framework/src/components/ui/tooltip.tsx +1 -1
  66. package/.framework/src/pages/auth/LoginPage.tsx +10 -9
  67. package/LICENSE.md +63 -93
  68. package/README.md +254 -129
  69. package/custom/.devin/skills/install-app/SKILL.md +39 -0
  70. package/custom/.devin/workflows/app-install.yaml +83 -0
  71. package/custom/README.md +79 -0
  72. package/docs/APPS.md +232 -0
  73. package/docs/ARCHITECTURE.md +246 -0
  74. package/docs/INSTALL.md +155 -0
  75. package/docs/README.md +170 -0
  76. package/docs/design-schema-spec.md +1837 -0
  77. package/docs/dev-guides/01-create-an-app.md +183 -0
  78. package/docs/dev-guides/02-create-a-function.md +178 -0
  79. package/docs/dev-guides/03-create-a-component.md +178 -0
  80. package/docs/dev-guides/04-webhook-handlers.md +129 -0
  81. package/docs/dev-guides/05-testing.md +168 -0
  82. package/docs/dev-guides/README.md +40 -0
  83. package/docs/npm-packages-plan.md +288 -0
  84. package/docs/spine-core--api-reference.md +822 -0
  85. package/package.json +79 -150
  86. package/.framework/cli/bin.cjs +0 -14
  87. package/.framework/cli/commands/claim.ts +0 -430
  88. package/.framework/cli/commands/init.ts +0 -219
  89. package/.framework/cli/commands/install-app.ts +0 -685
  90. package/.framework/cli/commands/migrate.ts +0 -178
  91. package/.framework/cli/commands/publish.ts +0 -191
  92. package/.framework/cli/commands/status.ts +0 -268
  93. package/.framework/cli/commands/uninstall-app.ts +0 -166
  94. package/.framework/cli/commands/update-app.ts +0 -397
  95. package/.framework/cli/welcome.cjs +0 -45
  96. package/.framework/config/signal-client.ts +0 -147
  97. package/.framework/config/signals.ts +0 -70
  98. package/.framework/docs/STABILITY.md +0 -52
  99. package/.framework/docs/examples/custom-app/README.md +0 -77
  100. package/.framework/docs/examples/custom-function/README.md +0 -27
  101. package/.framework/docs/examples/custom-function/handler.ts +0 -48
  102. package/.framework/docs/examples/custom-webhook/README.md +0 -68
  103. package/.framework/functions/handler-registry.ts +0 -22
  104. package/.framework/functions/signal.ts +0 -168
  105. package/.framework/migrations/000_foundation.sql +0 -1302
  106. package/.framework/migrations/001_seed.sql +0 -163
  107. package/.framework/migrations/002_seed_constraints.sql +0 -19
  108. package/.framework/migrations/003_auth_user_trigger.sql +0 -67
  109. package/.framework/migrations/004_app_version_tracking.sql +0 -8
  110. package/.framework/migrations/005_pipelines_slug_stages.sql +0 -21
  111. package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
  112. package/CHANGELOG.md +0 -71
  113. package/STRUCTURE.md +0 -150
  114. package/bin/spine-framework.cjs +0 -62
  115. package/bin/welcome.cjs +0 -58
  116. package/bin/ws-shim.ts +0 -10
  117. package/config/components.json +0 -25
  118. package/config/deno.lock +0 -108
  119. package/config/package-lock.json +0 -17183
  120. package/config/postcss.config.cjs +0 -10
  121. package/config/tailwind.config.cjs +0 -78
  122. package/config/tsconfig.build.json +0 -32
  123. package/config/tsconfig.cli.json +0 -18
  124. package/config/tsconfig.json +0 -41
  125. package/config/tsconfig.node.json +0 -17
  126. package/config/tsconfig.node.tsbuildinfo +0 -1
  127. package/config/tsconfig.tsbuildinfo +0 -1
  128. package/config/typedoc.json +0 -16
  129. package/config/vite.config.d.ts +0 -2
  130. package/config/vite.config.ts +0 -71
  131. package/dist/cli/commands/agents.d.ts +0 -39
  132. package/dist/cli/commands/agents.d.ts.map +0 -1
  133. package/dist/cli/commands/auth.d.ts +0 -36
  134. package/dist/cli/commands/auth.d.ts.map +0 -1
  135. package/dist/cli/commands/claim.d.ts +0 -38
  136. package/dist/cli/commands/claim.d.ts.map +0 -1
  137. package/dist/cli/commands/create-app.d.ts +0 -23
  138. package/dist/cli/commands/create-app.d.ts.map +0 -1
  139. package/dist/cli/commands/dev.d.ts +0 -24
  140. package/dist/cli/commands/dev.d.ts.map +0 -1
  141. package/dist/cli/commands/doctor.d.ts +0 -42
  142. package/dist/cli/commands/doctor.d.ts.map +0 -1
  143. package/dist/cli/commands/generate.d.ts +0 -36
  144. package/dist/cli/commands/generate.d.ts.map +0 -1
  145. package/dist/cli/commands/init.d.ts +0 -20
  146. package/dist/cli/commands/init.d.ts.map +0 -1
  147. package/dist/cli/commands/install-app.d.ts +0 -30
  148. package/dist/cli/commands/install-app.d.ts.map +0 -1
  149. package/dist/cli/commands/items.d.ts +0 -45
  150. package/dist/cli/commands/items.d.ts.map +0 -1
  151. package/dist/cli/commands/migrate.d.ts +0 -21
  152. package/dist/cli/commands/migrate.d.ts.map +0 -1
  153. package/dist/cli/commands/migrations.d.ts +0 -41
  154. package/dist/cli/commands/migrations.d.ts.map +0 -1
  155. package/dist/cli/commands/pipelines.d.ts +0 -40
  156. package/dist/cli/commands/pipelines.d.ts.map +0 -1
  157. package/dist/cli/commands/publish.d.ts +0 -23
  158. package/dist/cli/commands/publish.d.ts.map +0 -1
  159. package/dist/cli/commands/status.d.ts +0 -23
  160. package/dist/cli/commands/status.d.ts.map +0 -1
  161. package/dist/cli/commands/system.d.ts +0 -29
  162. package/dist/cli/commands/system.d.ts.map +0 -1
  163. package/dist/cli/commands/test.d.ts +0 -46
  164. package/dist/cli/commands/test.d.ts.map +0 -1
  165. package/dist/cli/commands/uninstall-app.d.ts +0 -23
  166. package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
  167. package/dist/cli/commands/update-app.d.ts +0 -24
  168. package/dist/cli/commands/update-app.d.ts.map +0 -1
  169. package/dist/cli/context.d.ts +0 -88
  170. package/dist/cli/context.d.ts.map +0 -1
  171. package/dist/cli/env-loader.d.ts +0 -14
  172. package/dist/cli/env-loader.d.ts.map +0 -1
  173. package/dist/cli/index.d.ts +0 -44
  174. package/dist/cli/index.d.ts.map +0 -1
  175. package/dist/config/signals.d.ts +0 -82
  176. package/dist/config/signals.d.ts.map +0 -1
  177. package/dist/functions/_shared/agent-runner.d.ts +0 -156
  178. package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
  179. package/dist/functions/_shared/app-manifest.d.ts +0 -68
  180. package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
  181. package/dist/functions/_shared/audit.d.ts +0 -91
  182. package/dist/functions/_shared/audit.d.ts.map +0 -1
  183. package/dist/functions/_shared/db.d.ts +0 -125
  184. package/dist/functions/_shared/db.d.ts.map +0 -1
  185. package/dist/functions/_shared/index.d.ts +0 -299
  186. package/dist/functions/_shared/index.d.ts.map +0 -1
  187. package/dist/functions/_shared/middleware.d.ts +0 -315
  188. package/dist/functions/_shared/middleware.d.ts.map +0 -1
  189. package/dist/functions/_shared/permissions.d.ts +0 -626
  190. package/dist/functions/_shared/permissions.d.ts.map +0 -1
  191. package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
  192. package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
  193. package/dist/functions/_shared/principal.d.ts +0 -284
  194. package/dist/functions/_shared/principal.d.ts.map +0 -1
  195. package/dist/functions/_shared/resolve-ids.d.ts +0 -10
  196. package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
  197. package/dist/functions/_shared/schema-utils.d.ts +0 -181
  198. package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
  199. package/dist/functions/_shared/testing.d.ts +0 -172
  200. package/dist/functions/_shared/testing.d.ts.map +0 -1
  201. package/dist/functions/_shared/trigger-engine.d.ts +0 -140
  202. package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
  203. package/dist/functions/_shared/webhook-registration.d.ts +0 -81
  204. package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
  205. package/dist/functions/_shared/webhook-registry.d.ts +0 -41
  206. package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
  207. package/dist/functions/account-nodes.d.ts +0 -48
  208. package/dist/functions/account-nodes.d.ts.map +0 -1
  209. package/dist/functions/admin-data.d.ts +0 -178
  210. package/dist/functions/admin-data.d.ts.map +0 -1
  211. package/dist/functions/ai-agents.d.ts +0 -125
  212. package/dist/functions/ai-agents.d.ts.map +0 -1
  213. package/dist/functions/api-keys.d.ts +0 -140
  214. package/dist/functions/api-keys.d.ts.map +0 -1
  215. package/dist/functions/apps.d.ts +0 -163
  216. package/dist/functions/apps.d.ts.map +0 -1
  217. package/dist/functions/auth.d.ts +0 -74
  218. package/dist/functions/auth.d.ts.map +0 -1
  219. package/dist/functions/debug-auth.d.ts +0 -33
  220. package/dist/functions/debug-auth.d.ts.map +0 -1
  221. package/dist/functions/embeddings.d.ts +0 -205
  222. package/dist/functions/embeddings.d.ts.map +0 -1
  223. package/dist/functions/handler-registry.d.ts +0 -22
  224. package/dist/functions/handler-registry.d.ts.map +0 -1
  225. package/dist/functions/integration-routes.d.ts +0 -45
  226. package/dist/functions/integration-routes.d.ts.map +0 -1
  227. package/dist/functions/integrations.d.ts +0 -124
  228. package/dist/functions/integrations.d.ts.map +0 -1
  229. package/dist/functions/item-progress.d.ts +0 -41
  230. package/dist/functions/item-progress.d.ts.map +0 -1
  231. package/dist/functions/logs.d.ts +0 -162
  232. package/dist/functions/logs.d.ts.map +0 -1
  233. package/dist/functions/observability.d.ts +0 -123
  234. package/dist/functions/observability.d.ts.map +0 -1
  235. package/dist/functions/pipeline-executions.d.ts +0 -190
  236. package/dist/functions/pipeline-executions.d.ts.map +0 -1
  237. package/dist/functions/pipelines.d.ts +0 -171
  238. package/dist/functions/pipelines.d.ts.map +0 -1
  239. package/dist/functions/prompt-configs.d.ts +0 -125
  240. package/dist/functions/prompt-configs.d.ts.map +0 -1
  241. package/dist/functions/roles.d.ts +0 -118
  242. package/dist/functions/roles.d.ts.map +0 -1
  243. package/dist/functions/signal.d.ts +0 -73
  244. package/dist/functions/signal.d.ts.map +0 -1
  245. package/dist/functions/system-cron.d.ts +0 -65
  246. package/dist/functions/system-cron.d.ts.map +0 -1
  247. package/dist/functions/system.d.ts +0 -29
  248. package/dist/functions/system.d.ts.map +0 -1
  249. package/dist/functions/tests.d.ts +0 -28
  250. package/dist/functions/tests.d.ts.map +0 -1
  251. package/dist/functions/timers.d.ts +0 -139
  252. package/dist/functions/timers.d.ts.map +0 -1
  253. package/dist/functions/triggers.d.ts +0 -203
  254. package/dist/functions/triggers.d.ts.map +0 -1
  255. package/dist/functions/types.d.ts +0 -151
  256. package/dist/functions/types.d.ts.map +0 -1
  257. package/dist/src/types/types.d.ts +0 -364
  258. package/dist/src/types/types.d.ts.map +0 -1
  259. package/index.html +0 -13
  260. package/netlify.toml +0 -36
  261. package/package-project.json +0 -71
  262. package/scripts/ingest-chunks.cjs +0 -202
  263. package/scripts/kb-chunk-parser.cjs +0 -312
  264. package/scripts/kb-chunk-parser.ts +0 -330
  265. package/scripts/load-test-app-install.ts +0 -484
  266. package/scripts/netlify-dev-wrapper.sh +0 -22
  267. package/vitest.config.ts +0 -45
  268. /package/{scripts → .framework/scripts}/app-install-cli.ts +0 -0
  269. /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
  270. /package/{scripts → .framework/scripts}/verify-integrity.sh +0 -0
@@ -0,0 +1,241 @@
1
+ /**
2
+ * @module admin-signals
3
+ * @audience system-admin
4
+ * @layer api-handler
5
+ * @stability stable
6
+ *
7
+ * Admin API for signal analytics and audit functionality
8
+ */
9
+
10
+ import { createHandler } from './_shared/middleware'
11
+ import { adminDb } from './_shared/db'
12
+
13
+ // ============================================
14
+ // SIGNAL METRICS
15
+ // ============================================
16
+
17
+ export const metrics = createHandler(async (ctx, body) => {
18
+ const now = new Date()
19
+ const today = now.toISOString().split('T')[0]
20
+ const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString()
21
+
22
+ // Get funnel_signal type ID
23
+ const { data: signalType } = await adminDb
24
+ .from('item_type_registry')
25
+ .select('id')
26
+ .eq('slug', 'funnel_signal')
27
+ .single()
28
+
29
+ if (!signalType) {
30
+ throw new Error('funnel_signal type not found')
31
+ }
32
+
33
+ // Total signals
34
+ const { count: totalSignals } = await adminDb
35
+ .from('items')
36
+ .select('*', { count: 'exact', head: true })
37
+ .eq('type_id', signalType.id)
38
+
39
+ // Signals today
40
+ const { count: signalsToday } = await adminDb
41
+ .from('items')
42
+ .select('*', { count: 'exact', head: true })
43
+ .eq('type_id', signalType.id)
44
+ .gte('created_at', today)
45
+
46
+ // Signals this week
47
+ const { count: signalsThisWeek } = await adminDb
48
+ .from('items')
49
+ .select('*', { count: 'exact', head: true })
50
+ .eq('type_id', signalType.id)
51
+ .gte('created_at', weekAgo)
52
+
53
+ // Top sources
54
+ const { data: sourceData } = await adminDb
55
+ .from('items')
56
+ .select('data->>source')
57
+ .eq('type_id', signalType.id)
58
+ .gte('created_at', weekAgo)
59
+
60
+ const sourceCounts = sourceData?.reduce((acc, item) => {
61
+ const source = item.data?.source || 'unknown'
62
+ acc[source] = (acc[source] || 0) + 1
63
+ return acc
64
+ }, {} as Record<string, number>)
65
+
66
+ const topSources = Object.entries(sourceCounts)
67
+ .sort(([, a], [, b]) => b - a)
68
+ .slice(0, 10)
69
+ .map(([source, count]) => ({ source, count }))
70
+
71
+ // Recent signals with account info
72
+ const { data: recentSignals } = await adminDb
73
+ .from('items')
74
+ .select(`
75
+ id,
76
+ data->>action_type,
77
+ data->>source,
78
+ data->>stage,
79
+ created_at,
80
+ data->>rating,
81
+ account_id,
82
+ accounts(name)
83
+ `)
84
+ .eq('type_id', signalType.id)
85
+ .order('created_at', { ascending: false })
86
+ .limit(20)
87
+
88
+ const formattedSignals = recentSignals?.map(signal => ({
89
+ id: signal.id,
90
+ action_type: signal.data?.action_type || 'unknown',
91
+ source: signal.data?.source || 'unknown',
92
+ stage: signal.data?.stage || 'unknown',
93
+ created_at: signal.created_at,
94
+ account_name: signal.accounts?.name,
95
+ rating: parseInt(signal.data?.rating || '1')
96
+ }))
97
+
98
+ // Calculate conversion rate (simplified - signals with rating >= 4)
99
+ const { count: highValueSignals } = await adminDb
100
+ .from('items')
101
+ .select('*', { count: 'exact', head: true })
102
+ .eq('type_id', signalType.id)
103
+ .gte('data->>rating', '4')
104
+ .gte('created_at', weekAgo)
105
+
106
+ const conversionRate = signalsThisWeek > 0 ? (highValueSignals / signalsThisWeek) * 100 : 0
107
+
108
+ return {
109
+ total_signals: totalSignals || 0,
110
+ signals_today: signalsToday || 0,
111
+ signals_this_week: signalsThisWeek || 0,
112
+ conversion_rate: conversionRate,
113
+ top_sources: topSources,
114
+ recent_signals: formattedSignals || []
115
+ }
116
+ })
117
+
118
+ // ============================================
119
+ // SIGNAL AUDIT
120
+ // ============================================
121
+
122
+ export const audit = createHandler(async (ctx, body) => {
123
+ const {
124
+ page = '1',
125
+ limit = '50',
126
+ date_from,
127
+ date_to,
128
+ source,
129
+ stage,
130
+ action_type,
131
+ min_rating,
132
+ account_id,
133
+ search,
134
+ export: exportData
135
+ } = ctx.query || {}
136
+
137
+ // Get funnel_signal type ID
138
+ const { data: signalType } = await adminDb
139
+ .from('item_type_registry')
140
+ .select('id')
141
+ .eq('slug', 'funnel_signal')
142
+ .single()
143
+
144
+ if (!signalType) {
145
+ throw new Error('funnel_signal type not found')
146
+ }
147
+
148
+ // Build query
149
+ let query = adminDb
150
+ .from('items')
151
+ .select(`
152
+ id,
153
+ data->>action_type,
154
+ data->>action_value,
155
+ data->>stage,
156
+ data->>source,
157
+ created_at,
158
+ data->>rating,
159
+ data->>raw_score,
160
+ data->identity->>anonymous_id,
161
+ data->identity->>session_id,
162
+ data->attribution->>first_touch_referrer_domain,
163
+ data->attribution->>utm_source,
164
+ data->attribution->>utm_medium,
165
+ data->attribution->>utm_campaign,
166
+ account_id,
167
+ people(email),
168
+ accounts(name)
169
+ `, { count: 'exact' })
170
+ .eq('type_id', signalType.id)
171
+
172
+ // Apply filters
173
+ if (date_from) {
174
+ query = query.gte('created_at', date_from)
175
+ }
176
+ if (date_to) {
177
+ query = query.lte('created_at', date_to)
178
+ }
179
+ if (source) {
180
+ query = query.eq('data->>source', source)
181
+ }
182
+ if (stage) {
183
+ query = query.eq('data->>stage', stage)
184
+ }
185
+ if (action_type) {
186
+ query = query.eq('data->>action_type', action_type)
187
+ }
188
+ if (min_rating) {
189
+ query = query.gte('data->>rating', min_rating)
190
+ }
191
+ if (account_id) {
192
+ query = query.eq('account_id', account_id)
193
+ }
194
+ if (search) {
195
+ query = query.or(`accounts.name.ilike.%${search}%,people.email.ilike.%${search}%,data->attribution->>first_touch_referrer_domain.ilike.%${search}%`)
196
+ }
197
+
198
+ // Order and paginate
199
+ const offset = (parseInt(page) - 1) * parseInt(limit)
200
+ query = query.order('created_at', { ascending: false })
201
+
202
+ if (!exportData) {
203
+ query = query.range(offset, offset + parseInt(limit) - 1)
204
+ }
205
+
206
+ const { data, error, count } = await query
207
+
208
+ if (error) {
209
+ throw new Error(`Failed to fetch signals: ${error.message}`)
210
+ }
211
+
212
+ const formattedSignals = data?.map(signal => ({
213
+ id: signal.id,
214
+ action_type: signal.data?.action_type || 'unknown',
215
+ action_value: parseInt(signal.data?.action_value || '1'),
216
+ stage: signal.data?.stage || 'unknown',
217
+ source: signal.data?.source || 'unknown',
218
+ created_at: signal.created_at,
219
+ account_name: signal.accounts?.name,
220
+ person_email: signal.people?.email,
221
+ anonymous_id: signal.data?.identity?.anonymous_id,
222
+ session_id: signal.data?.identity?.session_id,
223
+ rating: parseInt(signal.data?.rating || '1'),
224
+ raw_score: parseFloat(signal.data?.raw_score || '0'),
225
+ referrer_domain: signal.data?.attribution?.first_touch_referrer_domain,
226
+ utm_source: signal.data?.attribution?.utm_source,
227
+ utm_medium: signal.data?.attribution?.utm_medium,
228
+ utm_campaign: signal.data?.attribution?.utm_campaign
229
+ }))
230
+
231
+ if (exportData) {
232
+ return formattedSignals
233
+ }
234
+
235
+ return {
236
+ signals: formattedSignals,
237
+ total: count || 0,
238
+ page: parseInt(page),
239
+ limit: parseInt(limit)
240
+ }
241
+ })
@@ -37,8 +37,7 @@
37
37
 
38
38
  import { adminDb } from './_shared/db'
39
39
  import { resolvePrincipal } from './_shared/principal'
40
- import { listRegisteredHandlers } from './_shared/webhook-registry'
41
- import { HANDLER_REGISTRY } from './handler-registry'
40
+ import { resolveHandler, listRegisteredHandlers } from './_shared/webhook-registry'
42
41
 
43
42
  // ─── TYPES ────────────────────────────────────────────────────────────────
44
43
 
@@ -72,7 +71,7 @@ async function resolveIntegration(slug: string, type: string): Promise<any> {
72
71
  const { data, error } = await adminDb
73
72
  .from('integrations')
74
73
  .select('*')
75
- .eq('slug', slug)
74
+ .eq('name', slug)
76
75
  .eq('integration_type', type)
77
76
  .eq('is_active', true)
78
77
  .single()
@@ -129,7 +128,7 @@ function validateSchema(data: any, schema: any, requestId: string): ValidationRe
129
128
  // Validate data type
130
129
  if (config.type) {
131
130
  const typeValid = validateType(value, config.type, fieldName)
132
- if (!typeValid.valid && typeValid.error) {
131
+ if (!typeValid.valid) {
133
132
  errors.push(typeValid.error)
134
133
  }
135
134
  }
@@ -256,10 +255,7 @@ function sanitizeData(data: any, rules: string = 'strict'): any {
256
255
  * @param handlerName - The handler key from integration config (e.g. "webhook-handler")
257
256
  */
258
257
  async function loadCustomScript(handlerName: string): Promise<Function | null> {
259
- const mod = HANDLER_REGISTRY[handlerName]
260
- if (!mod) return null
261
- const fn = mod.handler || mod.default
262
- return typeof fn === 'function' ? fn : null
258
+ return resolveHandler(handlerName)
263
259
  }
264
260
 
265
261
  // ─── DIAGNOSTIC HANDLER (temporary — full trace in response) ──────────────
@@ -0,0 +1,406 @@
1
+ -- Migration 001: Schema
2
+ -- Spine v2 consolidated day-zero schema
3
+ -- Contains all CREATE TABLE statements for the public schema
4
+
5
+ COMMENT ON SCHEMA public IS 'Spine v2 consolidated schema - runtime entities foundation';
6
+
7
+ -- ============================================
8
+ -- EXTENSIONS
9
+ -- ============================================
10
+
11
+ CREATE EXTENSION IF NOT EXISTS vector;
12
+
13
+ -- ============================================
14
+ -- IDENTITY TABLES (accounts, people)
15
+ -- ============================================
16
+
17
+ CREATE TABLE public.accounts (
18
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
19
+ parent_id uuid, type_id uuid NOT NULL, slug text NOT NULL UNIQUE,
20
+ display_name text NOT NULL, description text,
21
+ data jsonb DEFAULT '{}', is_active boolean DEFAULT true,
22
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
23
+ design_schema jsonb DEFAULT '{}', validation_schema jsonb DEFAULT '{}',
24
+ app_id uuid, created_by uuid, updated_by uuid
25
+ );
26
+
27
+ CREATE TABLE public.people (
28
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
29
+ auth_uid uuid UNIQUE, email text UNIQUE NOT NULL, full_name text NOT NULL,
30
+ avatar_url text, phone text, status text DEFAULT 'active',
31
+ data jsonb DEFAULT '{}', is_active boolean DEFAULT true, account_id uuid NOT NULL,
32
+ app_id uuid, role_id uuid, type_id uuid NOT NULL,
33
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
34
+ design_schema jsonb DEFAULT '{}', validation_schema jsonb DEFAULT '{}',
35
+ created_by uuid
36
+ );
37
+
38
+ -- ============================================
39
+ -- TYPES, APPS, ROLES
40
+ -- ============================================
41
+
42
+ CREATE TABLE public.types (
43
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
44
+ app_id uuid, kind text NOT NULL, slug text NOT NULL, name text NOT NULL,
45
+ description text, icon text, color text,
46
+ design_schema jsonb DEFAULT '{}', validation_schema jsonb DEFAULT '{}',
47
+ ownership text DEFAULT 'tenant', is_active boolean DEFAULT true,
48
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
49
+ UNIQUE(app_id, kind, slug)
50
+ );
51
+
52
+ CREATE TABLE public.apps (
53
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
54
+ slug text NOT NULL UNIQUE, name text NOT NULL, description text,
55
+ icon text, color text, version text DEFAULT '1.0.0',
56
+ app_type text DEFAULT 'system', source text DEFAULT 'builtin',
57
+ config jsonb DEFAULT '{}', nav_items jsonb DEFAULT '[]',
58
+ min_role text, integration_deps jsonb DEFAULT '[]', metadata jsonb DEFAULT '{}',
59
+ is_active boolean DEFAULT true, is_system boolean DEFAULT false,
60
+ account_id uuid, owner_account_id uuid,
61
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
62
+ route_prefix text,
63
+ renderer text NOT NULL DEFAULT 'generic',
64
+ CONSTRAINT chk_apps_renderer CHECK (renderer IN ('generic', 'custom', 'none'))
65
+ );
66
+
67
+ CREATE TABLE public.roles (
68
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
69
+ slug text NOT NULL, name text NOT NULL, description text,
70
+ permissions jsonb DEFAULT '[]', is_system boolean DEFAULT false,
71
+ is_active boolean DEFAULT true, is_protected boolean DEFAULT false,
72
+ app_id uuid, account_id uuid,
73
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
74
+ UNIQUE(app_id, slug)
75
+ );
76
+
77
+ -- ============================================
78
+ -- RUNTIME ENTITIES
79
+ -- ============================================
80
+
81
+ CREATE TABLE public.items (
82
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
83
+ type_id uuid NOT NULL, account_id uuid NOT NULL, app_id uuid,
84
+ title text, description text, status text DEFAULT 'active',
85
+ data jsonb DEFAULT '{}', is_active boolean DEFAULT true,
86
+ design_schema jsonb DEFAULT '{}', validation_schema jsonb DEFAULT '{}',
87
+ created_by uuid, updated_by uuid,
88
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
89
+ );
90
+
91
+ CREATE TABLE public.threads (
92
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
93
+ type_id uuid NOT NULL, account_id uuid NOT NULL, app_id uuid,
94
+ title text, target_type text NOT NULL, target_id uuid NOT NULL,
95
+ visibility text DEFAULT 'internal', status text DEFAULT 'open',
96
+ data jsonb DEFAULT '{}', is_active boolean DEFAULT true,
97
+ design_schema jsonb DEFAULT '{}', validation_schema jsonb DEFAULT '{}',
98
+ created_by uuid, updated_by uuid,
99
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
100
+ CONSTRAINT threads_unique_target_visibility UNIQUE (target_type, target_id, visibility)
101
+ );
102
+
103
+ CREATE TABLE public.messages (
104
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
105
+ type_id uuid NOT NULL, thread_id uuid NOT NULL,
106
+ content text NOT NULL, direction text DEFAULT 'outbound',
107
+ sequence integer NOT NULL, visibility text DEFAULT 'internal',
108
+ data jsonb DEFAULT '{}', is_active boolean DEFAULT true,
109
+ design_schema jsonb DEFAULT '{}', validation_schema jsonb DEFAULT '{}',
110
+ person_id uuid, created_by uuid,
111
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
112
+ UNIQUE(thread_id, sequence)
113
+ );
114
+
115
+ CREATE TABLE public.links (
116
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
117
+ type_id uuid NOT NULL, account_id uuid NOT NULL, link_type_id uuid,
118
+ source_type text NOT NULL, source_id uuid NOT NULL,
119
+ target_type text NOT NULL, target_id uuid NOT NULL,
120
+ link_type text, metadata jsonb DEFAULT '{}',
121
+ data jsonb DEFAULT '{}', design_schema jsonb DEFAULT '{}', validation_schema jsonb DEFAULT '{}',
122
+ created_by uuid, updated_by uuid,
123
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
124
+ UNIQUE(source_type, source_id, target_type, target_id, link_type_id)
125
+ );
126
+
127
+ CREATE TABLE public.attachments (
128
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
129
+ type_id uuid NOT NULL, account_id uuid NOT NULL,
130
+ filename text NOT NULL, file_size integer, mime_type text,
131
+ storage_path text, storage_provider text DEFAULT 'supabase',
132
+ metadata jsonb DEFAULT '{}', data jsonb DEFAULT '{}',
133
+ design_schema jsonb DEFAULT '{}', validation_schema jsonb DEFAULT '{}',
134
+ uploaded_by uuid, created_by uuid,
135
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
136
+ );
137
+
138
+ CREATE TABLE public.watchers (
139
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
140
+ type_id uuid NOT NULL, account_id uuid,
141
+ target_type text NOT NULL, target_id uuid NOT NULL, person_id uuid NOT NULL,
142
+ watch_type text DEFAULT 'all', notification_level text DEFAULT 'all',
143
+ metadata jsonb DEFAULT '{}', data jsonb DEFAULT '{}',
144
+ design_schema jsonb DEFAULT '{}', validation_schema jsonb DEFAULT '{}',
145
+ created_by uuid, created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
146
+ UNIQUE(target_type, target_id, person_id)
147
+ );
148
+
149
+ -- ============================================
150
+ -- ADMIN/AUTOMATION TABLES
151
+ -- ============================================
152
+
153
+ CREATE TABLE public.link_types (
154
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
155
+ app_id uuid, slug text NOT NULL, name text NOT NULL,
156
+ description text, icon text, color text,
157
+ config jsonb DEFAULT '{}', is_active boolean DEFAULT true,
158
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now(),
159
+ UNIQUE(app_id, slug)
160
+ );
161
+
162
+ CREATE TABLE public.pipelines (
163
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
164
+ app_id uuid, account_id uuid, name text NOT NULL,
165
+ description text, trigger_type text, config jsonb DEFAULT '{}',
166
+ stages jsonb DEFAULT '[]', metadata jsonb DEFAULT '{}',
167
+ ownership text DEFAULT 'tenant', is_system boolean DEFAULT false,
168
+ is_active boolean DEFAULT true, created_by uuid,
169
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
170
+ );
171
+
172
+ CREATE TABLE public.pipeline_executions (
173
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
174
+ pipeline_id uuid NOT NULL, account_id uuid NOT NULL,
175
+ status text DEFAULT 'pending', trigger_data jsonb DEFAULT '{}',
176
+ result jsonb DEFAULT '{}', error_message text,
177
+ started_at timestamptz, completed_at timestamptz, duration_ms integer,
178
+ created_by uuid, created_at timestamptz DEFAULT now()
179
+ );
180
+
181
+ CREATE TABLE public.triggers (
182
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
183
+ app_id uuid, account_id uuid, name text NOT NULL,
184
+ description text, trigger_type text NOT NULL, event_type text,
185
+ config jsonb DEFAULT '{}', pipeline_id uuid, metadata jsonb DEFAULT '{}',
186
+ ownership text DEFAULT 'tenant', is_system boolean DEFAULT false,
187
+ is_active boolean DEFAULT true, trigger_count integer DEFAULT 0,
188
+ last_triggered timestamptz, created_by uuid,
189
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
190
+ );
191
+
192
+ CREATE TABLE public.trigger_executions (
193
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
194
+ trigger_id uuid NOT NULL, status text DEFAULT 'pending',
195
+ triggered_at timestamptz DEFAULT now(), started_at timestamptz,
196
+ completed_at timestamptz, trigger_data jsonb DEFAULT '{}',
197
+ result jsonb DEFAULT '{}', error_message text, duration_ms integer,
198
+ created_at timestamptz DEFAULT now()
199
+ );
200
+
201
+ CREATE TABLE public.timers (
202
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
203
+ app_id uuid, account_id uuid, name text NOT NULL,
204
+ description text, timer_type text NOT NULL, config jsonb DEFAULT '{}',
205
+ pipeline_id uuid, metadata jsonb DEFAULT '{}',
206
+ ownership text DEFAULT 'tenant', is_system boolean DEFAULT false,
207
+ is_active boolean DEFAULT true, last_execution timestamptz, next_execution timestamptz,
208
+ execution_count integer DEFAULT 0, success_count integer DEFAULT 0, failure_count integer DEFAULT 0,
209
+ created_by uuid, created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
210
+ );
211
+
212
+ -- ============================================
213
+ -- SYSTEM TABLES (AI, Integrations, Logs, API Keys)
214
+ -- ============================================
215
+
216
+ CREATE TABLE public.ai_agents (
217
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
218
+ app_id uuid, account_id uuid, name text NOT NULL,
219
+ description text, agent_type text NOT NULL, model_config jsonb DEFAULT '{}',
220
+ system_prompt text, tools jsonb DEFAULT '[]', capabilities jsonb DEFAULT '[]',
221
+ constraints jsonb DEFAULT '{}', metadata jsonb DEFAULT '{}',
222
+ ownership text DEFAULT 'tenant', is_system boolean DEFAULT false,
223
+ is_active boolean DEFAULT true, created_by uuid,
224
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
225
+ );
226
+
227
+ CREATE TABLE public.embeddings (
228
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
229
+ account_id uuid NOT NULL, model_id text NOT NULL, document_id text NOT NULL,
230
+ chunk_index integer NOT NULL, content text NOT NULL, embedding vector(1536),
231
+ metadata jsonb DEFAULT '{}', created_at timestamptz DEFAULT now(),
232
+ UNIQUE(model_id, document_id, chunk_index)
233
+ );
234
+
235
+ CREATE TABLE public.integrations (
236
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
237
+ app_id uuid, account_id uuid, name text NOT NULL,
238
+ description text, integration_type text NOT NULL, provider text,
239
+ version text, config jsonb DEFAULT '{}', credentials jsonb DEFAULT '{}',
240
+ metadata jsonb DEFAULT '{}', is_active boolean DEFAULT true,
241
+ ownership text DEFAULT 'tenant', is_system boolean DEFAULT false,
242
+ is_configured boolean DEFAULT false, created_by uuid,
243
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
244
+ );
245
+
246
+ CREATE TABLE public.prompt_configs (
247
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
248
+ app_id uuid, account_id uuid, name text NOT NULL, slug text NOT NULL,
249
+ system_prompt text, context_template text, model text, temperature numeric,
250
+ max_tokens integer, is_multi_turn boolean DEFAULT false, max_history_messages integer,
251
+ confidence_threshold numeric, escalation_action text, escalation_target text,
252
+ output_mode text, output_field text, requires_review boolean DEFAULT false,
253
+ knowledge_sources jsonb DEFAULT '[]', available_tools jsonb DEFAULT '[]',
254
+ tool_constraints jsonb DEFAULT '{}', metadata jsonb DEFAULT '{}',
255
+ ownership text DEFAULT 'tenant', is_system boolean DEFAULT false,
256
+ is_active boolean DEFAULT true, created_by uuid,
257
+ created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
258
+ );
259
+
260
+ CREATE TABLE public.api_keys (
261
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
262
+ integration_id uuid, account_id uuid, name text NOT NULL,
263
+ key_value text UNIQUE NOT NULL, key_prefix text NOT NULL,
264
+ key_type text NOT NULL, permissions jsonb DEFAULT '[]',
265
+ rate_limit integer DEFAULT 1000, is_active boolean DEFAULT true,
266
+ expires_at timestamptz, metadata jsonb DEFAULT '{}',
267
+ ownership text DEFAULT 'tenant', is_system boolean DEFAULT false,
268
+ machine_type text, is_internal boolean DEFAULT false, scopes text[] DEFAULT '{}',
269
+ created_by uuid, created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
270
+ );
271
+
272
+ CREATE TABLE public.api_key_usage_logs (
273
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
274
+ api_key_id uuid NOT NULL, account_id uuid NOT NULL,
275
+ request_method text, request_path text, request_ip text, user_agent text,
276
+ response_status integer, response_size integer, duration_ms integer,
277
+ success boolean DEFAULT true, error_message text, metadata jsonb DEFAULT '{}',
278
+ created_at timestamptz DEFAULT now()
279
+ );
280
+
281
+ CREATE TABLE public.logs (
282
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
283
+ level text NOT NULL, message text NOT NULL, context jsonb DEFAULT '{}',
284
+ source text, source_type text, source_id uuid, person_id uuid, account_id uuid,
285
+ metadata jsonb DEFAULT '{}', created_at timestamptz DEFAULT now()
286
+ );
287
+
288
+ CREATE TABLE public.actions (
289
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
290
+ account_id uuid, name text NOT NULL, slug text NOT NULL,
291
+ description text, handler text NOT NULL, handler_module text DEFAULT 'functions',
292
+ config jsonb DEFAULT '{}', input_schema jsonb DEFAULT '{}', output_schema jsonb DEFAULT '{}',
293
+ ownership text DEFAULT 'tenant', is_system boolean DEFAULT false,
294
+ default_machine_principal_id uuid, required_scopes text[] DEFAULT '{}',
295
+ is_active boolean DEFAULT true, timeout_seconds integer DEFAULT 300,
296
+ retry_count integer DEFAULT 3, created_by uuid, created_at timestamptz DEFAULT now()
297
+ );
298
+
299
+ CREATE TABLE public.schedules (
300
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
301
+ account_id uuid, action_id uuid NOT NULL, name text NOT NULL,
302
+ schedule_type text NOT NULL, cron_expression text,
303
+ next_run_at timestamptz, last_run_at timestamptz,
304
+ ownership text DEFAULT 'tenant', is_system boolean DEFAULT false,
305
+ is_active boolean DEFAULT true, machine_principal_id uuid,
306
+ created_by uuid, created_at timestamptz DEFAULT now(), updated_at timestamptz DEFAULT now()
307
+ );
308
+
309
+ CREATE TABLE public.schedule_executions (
310
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
311
+ schedule_id uuid NOT NULL, account_id uuid NOT NULL, machine_principal_id uuid,
312
+ status text DEFAULT 'pending', input_params jsonb DEFAULT '{}',
313
+ output_result jsonb DEFAULT '{}', error_message text, duration_ms integer,
314
+ created_at timestamptz DEFAULT now()
315
+ );
316
+
317
+ -- ============================================
318
+ -- TEST TABLES
319
+ -- ============================================
320
+
321
+ CREATE TABLE public.test_runs (
322
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
323
+ suite text NOT NULL,
324
+ status text NOT NULL,
325
+ started_at timestamptz NOT NULL DEFAULT now(),
326
+ finished_at timestamptz,
327
+ duration_ms integer,
328
+ total integer,
329
+ passed integer,
330
+ failed integer,
331
+ skipped integer,
332
+ triggered_by text DEFAULT 'agent',
333
+ account_id uuid REFERENCES public.accounts(id)
334
+ );
335
+
336
+ CREATE TABLE public.test_results (
337
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
338
+ run_id uuid NOT NULL REFERENCES public.test_runs(id) ON DELETE CASCADE,
339
+ suite text NOT NULL,
340
+ file text,
341
+ describe text,
342
+ name text NOT NULL,
343
+ status text NOT NULL,
344
+ duration_ms integer,
345
+ error text,
346
+ created_at timestamptz NOT NULL DEFAULT now()
347
+ );
348
+
349
+ -- ============================================
350
+ -- ITEM PROGRESS
351
+ -- ============================================
352
+
353
+ CREATE TABLE public.item_progress (
354
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
355
+ type_id uuid NOT NULL,
356
+ account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
357
+ app_id uuid REFERENCES public.apps(id) ON DELETE SET NULL,
358
+ person_id uuid NOT NULL REFERENCES public.people(id) ON DELETE CASCADE,
359
+ item_id uuid NOT NULL REFERENCES public.items(id) ON DELETE CASCADE,
360
+ title text,
361
+ description text,
362
+ status text NOT NULL DEFAULT 'not_started',
363
+ score integer CHECK (score IS NULL OR (score >= 0 AND score <= 100)),
364
+ data jsonb NOT NULL DEFAULT '{}',
365
+ is_active boolean NOT NULL DEFAULT true,
366
+ design_schema jsonb NOT NULL DEFAULT '{}',
367
+ validation_schema jsonb NOT NULL DEFAULT '{}',
368
+ created_by uuid REFERENCES public.people(id) ON DELETE SET NULL,
369
+ updated_by uuid REFERENCES public.people(id) ON DELETE SET NULL,
370
+ created_at timestamptz NOT NULL DEFAULT now(),
371
+ updated_at timestamptz NOT NULL DEFAULT now(),
372
+ UNIQUE (person_id, item_id)
373
+ );
374
+
375
+ -- ============================================
376
+ -- WEBHOOK HANDLERS
377
+ -- ============================================
378
+
379
+ CREATE TABLE public.webhook_handlers (
380
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
381
+ name VARCHAR(255) NOT NULL UNIQUE,
382
+ function_name VARCHAR(255) NOT NULL,
383
+ description TEXT,
384
+ events JSONB DEFAULT '[]'::jsonb,
385
+ account_id UUID REFERENCES public.accounts(id) ON DELETE CASCADE,
386
+ is_active BOOLEAN DEFAULT true,
387
+ created_at TIMESTAMPTZ DEFAULT now(),
388
+ updated_at TIMESTAMPTZ DEFAULT now(),
389
+ is_deleted BOOLEAN DEFAULT false,
390
+ deleted_at TIMESTAMPTZ
391
+ );
392
+
393
+ -- ============================================
394
+ -- ACCOUNT HIERARCHY (from 005_functions.sql)
395
+ -- ============================================
396
+
397
+ CREATE TABLE public.account_paths (
398
+ ancestor_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
399
+ descendant_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
400
+ depth integer NOT NULL,
401
+ PRIMARY KEY (ancestor_id, descendant_id),
402
+ CHECK (ancestor_id != descendant_id),
403
+ CHECK (depth >= 1)
404
+ );
405
+
406
+ COMMENT ON TABLE public.account_paths IS 'Stores ancestor-descendant relationships for account hierarchy traversal';