spine-framework 0.3.17 → 0.3.18

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 (269) hide show
  1. package/.framework/cli/commands/create-app.ts +1 -1
  2. package/.framework/cli/commands/dev.ts +215 -33
  3. package/.framework/cli/index.ts +37 -75
  4. package/.framework/functions/_shared/app-manifest.ts +8 -15
  5. package/.framework/functions/_shared/db.ts +11 -15
  6. package/.framework/functions/_shared/index.ts +7 -16
  7. package/.framework/functions/_shared/principal.ts +4 -62
  8. package/.framework/functions/_shared/resolve-ids.ts +6 -6
  9. package/.framework/functions/_shared/webhook-registry.ts +43 -1
  10. package/.framework/functions/admin-funnel.ts +104 -0
  11. package/.framework/functions/admin-install-funnel.ts +225 -0
  12. package/.framework/functions/admin-signals.ts +241 -0
  13. package/.framework/functions/integration-routes.ts +4 -8
  14. package/.framework/migrations/001_schema.sql +406 -0
  15. package/.framework/migrations/002_rls_and_functions.sql +710 -0
  16. package/.framework/migrations/003_system_data.sql +520 -0
  17. package/.framework/migrations/004_enhancements.sql +43 -0
  18. package/.framework/public/_redirects +3 -0
  19. package/.framework/public/spine-logo.jpg +0 -0
  20. package/.framework/public/spine-logo.svg +3 -0
  21. package/{scripts → .framework/scripts}/assemble-frontend.sh +8 -5
  22. package/{scripts → .framework/scripts}/assemble-functions.sh +4 -47
  23. package/{scripts → .framework/scripts}/assemble.sh +2 -8
  24. package/{scripts → .framework/scripts}/boundary-check.sh +5 -9
  25. package/{scripts → .framework/scripts}/check-core-integrity.sh +0 -0
  26. package/.framework/seeds/link_types/account_cli_instances.json +9 -0
  27. package/.framework/seeds/types/funnel_signal_use.json +98 -0
  28. package/.framework/seeds/types/spine_instance.json +109 -0
  29. package/.framework/src/App.tsx +11 -21
  30. package/.framework/src/components/CustomAppLoader.tsx +2 -19
  31. package/.framework/src/components/ui/accordion.tsx +1 -1
  32. package/.framework/src/components/ui/alert-dialog.tsx +1 -1
  33. package/.framework/src/components/ui/aspect-ratio.tsx +1 -1
  34. package/.framework/src/components/ui/avatar.tsx +1 -1
  35. package/.framework/src/components/ui/badge.tsx +2 -2
  36. package/.framework/src/components/ui/breadcrumb.tsx +2 -2
  37. package/.framework/src/components/ui/button-group.tsx +2 -2
  38. package/.framework/src/components/ui/button.tsx +2 -2
  39. package/.framework/src/components/ui/chart.tsx +6 -1
  40. package/.framework/src/components/ui/checkbox.tsx +1 -1
  41. package/.framework/src/components/ui/collapsible.tsx +1 -1
  42. package/.framework/src/components/ui/context-menu.tsx +1 -1
  43. package/.framework/src/components/ui/dialog.tsx +1 -1
  44. package/.framework/src/components/ui/direction.tsx +17 -1
  45. package/.framework/src/components/ui/dropdown-menu.tsx +1 -1
  46. package/.framework/src/components/ui/hover-card.tsx +1 -1
  47. package/.framework/src/components/ui/item.tsx +2 -2
  48. package/.framework/src/components/ui/label.tsx +1 -1
  49. package/.framework/src/components/ui/menubar.tsx +1 -1
  50. package/.framework/src/components/ui/navigation-menu.tsx +1 -1
  51. package/.framework/src/components/ui/popover.tsx +1 -1
  52. package/.framework/src/components/ui/progress.tsx +1 -1
  53. package/.framework/src/components/ui/radio-group.tsx +1 -1
  54. package/.framework/src/components/ui/scroll-area.tsx +1 -1
  55. package/.framework/src/components/ui/select.tsx +1 -1
  56. package/.framework/src/components/ui/separator.tsx +1 -1
  57. package/.framework/src/components/ui/sheet.tsx +1 -1
  58. package/.framework/src/components/ui/sidebar.tsx +6 -6
  59. package/.framework/src/components/ui/slider.tsx +1 -1
  60. package/.framework/src/components/ui/switch.tsx +1 -1
  61. package/.framework/src/components/ui/tabs.tsx +1 -1
  62. package/.framework/src/components/ui/toggle-group.tsx +1 -1
  63. package/.framework/src/components/ui/toggle.tsx +1 -1
  64. package/.framework/src/components/ui/tooltip.tsx +1 -1
  65. package/.framework/src/pages/auth/LoginPage.tsx +10 -9
  66. package/LICENSE.md +63 -93
  67. package/README.md +254 -129
  68. package/custom/.devin/skills/install-app/SKILL.md +39 -0
  69. package/custom/.devin/workflows/app-install.yaml +83 -0
  70. package/custom/README.md +79 -0
  71. package/docs/APPS.md +232 -0
  72. package/docs/ARCHITECTURE.md +246 -0
  73. package/docs/INSTALL.md +155 -0
  74. package/docs/README.md +170 -0
  75. package/docs/design-schema-spec.md +1837 -0
  76. package/docs/dev-guides/01-create-an-app.md +183 -0
  77. package/docs/dev-guides/02-create-a-function.md +178 -0
  78. package/docs/dev-guides/03-create-a-component.md +178 -0
  79. package/docs/dev-guides/04-webhook-handlers.md +129 -0
  80. package/docs/dev-guides/05-testing.md +168 -0
  81. package/docs/dev-guides/README.md +40 -0
  82. package/docs/npm-packages-plan.md +288 -0
  83. package/docs/spine-core--api-reference.md +822 -0
  84. package/package.json +78 -150
  85. package/.framework/cli/bin.cjs +0 -14
  86. package/.framework/cli/commands/claim.ts +0 -430
  87. package/.framework/cli/commands/init.ts +0 -219
  88. package/.framework/cli/commands/install-app.ts +0 -685
  89. package/.framework/cli/commands/migrate.ts +0 -178
  90. package/.framework/cli/commands/publish.ts +0 -191
  91. package/.framework/cli/commands/status.ts +0 -268
  92. package/.framework/cli/commands/uninstall-app.ts +0 -166
  93. package/.framework/cli/commands/update-app.ts +0 -397
  94. package/.framework/cli/welcome.cjs +0 -45
  95. package/.framework/config/signal-client.ts +0 -147
  96. package/.framework/config/signals.ts +0 -70
  97. package/.framework/docs/STABILITY.md +0 -52
  98. package/.framework/docs/examples/custom-app/README.md +0 -77
  99. package/.framework/docs/examples/custom-function/README.md +0 -27
  100. package/.framework/docs/examples/custom-function/handler.ts +0 -48
  101. package/.framework/docs/examples/custom-webhook/README.md +0 -68
  102. package/.framework/functions/handler-registry.ts +0 -22
  103. package/.framework/functions/signal.ts +0 -168
  104. package/.framework/migrations/000_foundation.sql +0 -1302
  105. package/.framework/migrations/001_seed.sql +0 -163
  106. package/.framework/migrations/002_seed_constraints.sql +0 -19
  107. package/.framework/migrations/003_auth_user_trigger.sql +0 -67
  108. package/.framework/migrations/004_app_version_tracking.sql +0 -8
  109. package/.framework/migrations/005_pipelines_slug_stages.sql +0 -21
  110. package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
  111. package/CHANGELOG.md +0 -71
  112. package/STRUCTURE.md +0 -150
  113. package/bin/spine-framework.cjs +0 -62
  114. package/bin/welcome.cjs +0 -58
  115. package/bin/ws-shim.ts +0 -10
  116. package/config/components.json +0 -25
  117. package/config/deno.lock +0 -108
  118. package/config/package-lock.json +0 -17183
  119. package/config/postcss.config.cjs +0 -10
  120. package/config/tailwind.config.cjs +0 -78
  121. package/config/tsconfig.build.json +0 -32
  122. package/config/tsconfig.cli.json +0 -18
  123. package/config/tsconfig.json +0 -41
  124. package/config/tsconfig.node.json +0 -17
  125. package/config/tsconfig.node.tsbuildinfo +0 -1
  126. package/config/tsconfig.tsbuildinfo +0 -1
  127. package/config/typedoc.json +0 -16
  128. package/config/vite.config.d.ts +0 -2
  129. package/config/vite.config.ts +0 -71
  130. package/dist/cli/commands/agents.d.ts +0 -39
  131. package/dist/cli/commands/agents.d.ts.map +0 -1
  132. package/dist/cli/commands/auth.d.ts +0 -36
  133. package/dist/cli/commands/auth.d.ts.map +0 -1
  134. package/dist/cli/commands/claim.d.ts +0 -38
  135. package/dist/cli/commands/claim.d.ts.map +0 -1
  136. package/dist/cli/commands/create-app.d.ts +0 -23
  137. package/dist/cli/commands/create-app.d.ts.map +0 -1
  138. package/dist/cli/commands/dev.d.ts +0 -24
  139. package/dist/cli/commands/dev.d.ts.map +0 -1
  140. package/dist/cli/commands/doctor.d.ts +0 -42
  141. package/dist/cli/commands/doctor.d.ts.map +0 -1
  142. package/dist/cli/commands/generate.d.ts +0 -36
  143. package/dist/cli/commands/generate.d.ts.map +0 -1
  144. package/dist/cli/commands/init.d.ts +0 -20
  145. package/dist/cli/commands/init.d.ts.map +0 -1
  146. package/dist/cli/commands/install-app.d.ts +0 -30
  147. package/dist/cli/commands/install-app.d.ts.map +0 -1
  148. package/dist/cli/commands/items.d.ts +0 -45
  149. package/dist/cli/commands/items.d.ts.map +0 -1
  150. package/dist/cli/commands/migrate.d.ts +0 -21
  151. package/dist/cli/commands/migrate.d.ts.map +0 -1
  152. package/dist/cli/commands/migrations.d.ts +0 -41
  153. package/dist/cli/commands/migrations.d.ts.map +0 -1
  154. package/dist/cli/commands/pipelines.d.ts +0 -40
  155. package/dist/cli/commands/pipelines.d.ts.map +0 -1
  156. package/dist/cli/commands/publish.d.ts +0 -23
  157. package/dist/cli/commands/publish.d.ts.map +0 -1
  158. package/dist/cli/commands/status.d.ts +0 -23
  159. package/dist/cli/commands/status.d.ts.map +0 -1
  160. package/dist/cli/commands/system.d.ts +0 -29
  161. package/dist/cli/commands/system.d.ts.map +0 -1
  162. package/dist/cli/commands/test.d.ts +0 -46
  163. package/dist/cli/commands/test.d.ts.map +0 -1
  164. package/dist/cli/commands/uninstall-app.d.ts +0 -23
  165. package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
  166. package/dist/cli/commands/update-app.d.ts +0 -24
  167. package/dist/cli/commands/update-app.d.ts.map +0 -1
  168. package/dist/cli/context.d.ts +0 -88
  169. package/dist/cli/context.d.ts.map +0 -1
  170. package/dist/cli/env-loader.d.ts +0 -14
  171. package/dist/cli/env-loader.d.ts.map +0 -1
  172. package/dist/cli/index.d.ts +0 -44
  173. package/dist/cli/index.d.ts.map +0 -1
  174. package/dist/config/signals.d.ts +0 -82
  175. package/dist/config/signals.d.ts.map +0 -1
  176. package/dist/functions/_shared/agent-runner.d.ts +0 -156
  177. package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
  178. package/dist/functions/_shared/app-manifest.d.ts +0 -68
  179. package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
  180. package/dist/functions/_shared/audit.d.ts +0 -91
  181. package/dist/functions/_shared/audit.d.ts.map +0 -1
  182. package/dist/functions/_shared/db.d.ts +0 -125
  183. package/dist/functions/_shared/db.d.ts.map +0 -1
  184. package/dist/functions/_shared/index.d.ts +0 -299
  185. package/dist/functions/_shared/index.d.ts.map +0 -1
  186. package/dist/functions/_shared/middleware.d.ts +0 -315
  187. package/dist/functions/_shared/middleware.d.ts.map +0 -1
  188. package/dist/functions/_shared/permissions.d.ts +0 -626
  189. package/dist/functions/_shared/permissions.d.ts.map +0 -1
  190. package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
  191. package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
  192. package/dist/functions/_shared/principal.d.ts +0 -284
  193. package/dist/functions/_shared/principal.d.ts.map +0 -1
  194. package/dist/functions/_shared/resolve-ids.d.ts +0 -10
  195. package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
  196. package/dist/functions/_shared/schema-utils.d.ts +0 -181
  197. package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
  198. package/dist/functions/_shared/testing.d.ts +0 -172
  199. package/dist/functions/_shared/testing.d.ts.map +0 -1
  200. package/dist/functions/_shared/trigger-engine.d.ts +0 -140
  201. package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
  202. package/dist/functions/_shared/webhook-registration.d.ts +0 -81
  203. package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
  204. package/dist/functions/_shared/webhook-registry.d.ts +0 -41
  205. package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
  206. package/dist/functions/account-nodes.d.ts +0 -48
  207. package/dist/functions/account-nodes.d.ts.map +0 -1
  208. package/dist/functions/admin-data.d.ts +0 -178
  209. package/dist/functions/admin-data.d.ts.map +0 -1
  210. package/dist/functions/ai-agents.d.ts +0 -125
  211. package/dist/functions/ai-agents.d.ts.map +0 -1
  212. package/dist/functions/api-keys.d.ts +0 -140
  213. package/dist/functions/api-keys.d.ts.map +0 -1
  214. package/dist/functions/apps.d.ts +0 -163
  215. package/dist/functions/apps.d.ts.map +0 -1
  216. package/dist/functions/auth.d.ts +0 -74
  217. package/dist/functions/auth.d.ts.map +0 -1
  218. package/dist/functions/debug-auth.d.ts +0 -33
  219. package/dist/functions/debug-auth.d.ts.map +0 -1
  220. package/dist/functions/embeddings.d.ts +0 -205
  221. package/dist/functions/embeddings.d.ts.map +0 -1
  222. package/dist/functions/handler-registry.d.ts +0 -22
  223. package/dist/functions/handler-registry.d.ts.map +0 -1
  224. package/dist/functions/integration-routes.d.ts +0 -45
  225. package/dist/functions/integration-routes.d.ts.map +0 -1
  226. package/dist/functions/integrations.d.ts +0 -124
  227. package/dist/functions/integrations.d.ts.map +0 -1
  228. package/dist/functions/item-progress.d.ts +0 -41
  229. package/dist/functions/item-progress.d.ts.map +0 -1
  230. package/dist/functions/logs.d.ts +0 -162
  231. package/dist/functions/logs.d.ts.map +0 -1
  232. package/dist/functions/observability.d.ts +0 -123
  233. package/dist/functions/observability.d.ts.map +0 -1
  234. package/dist/functions/pipeline-executions.d.ts +0 -190
  235. package/dist/functions/pipeline-executions.d.ts.map +0 -1
  236. package/dist/functions/pipelines.d.ts +0 -171
  237. package/dist/functions/pipelines.d.ts.map +0 -1
  238. package/dist/functions/prompt-configs.d.ts +0 -125
  239. package/dist/functions/prompt-configs.d.ts.map +0 -1
  240. package/dist/functions/roles.d.ts +0 -118
  241. package/dist/functions/roles.d.ts.map +0 -1
  242. package/dist/functions/signal.d.ts +0 -73
  243. package/dist/functions/signal.d.ts.map +0 -1
  244. package/dist/functions/system-cron.d.ts +0 -65
  245. package/dist/functions/system-cron.d.ts.map +0 -1
  246. package/dist/functions/system.d.ts +0 -29
  247. package/dist/functions/system.d.ts.map +0 -1
  248. package/dist/functions/tests.d.ts +0 -28
  249. package/dist/functions/tests.d.ts.map +0 -1
  250. package/dist/functions/timers.d.ts +0 -139
  251. package/dist/functions/timers.d.ts.map +0 -1
  252. package/dist/functions/triggers.d.ts +0 -203
  253. package/dist/functions/triggers.d.ts.map +0 -1
  254. package/dist/functions/types.d.ts +0 -151
  255. package/dist/functions/types.d.ts.map +0 -1
  256. package/dist/src/types/types.d.ts +0 -364
  257. package/dist/src/types/types.d.ts.map +0 -1
  258. package/index.html +0 -13
  259. package/netlify.toml +0 -36
  260. package/package-project.json +0 -71
  261. package/scripts/ingest-chunks.cjs +0 -202
  262. package/scripts/kb-chunk-parser.cjs +0 -312
  263. package/scripts/kb-chunk-parser.ts +0 -330
  264. package/scripts/load-test-app-install.ts +0 -484
  265. package/scripts/netlify-dev-wrapper.sh +0 -22
  266. package/vitest.config.ts +0 -45
  267. /package/{scripts → .framework/scripts}/app-install-cli.ts +0 -0
  268. /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
  269. /package/{scripts → .framework/scripts}/verify-integrity.sh +0 -0
@@ -1,147 +0,0 @@
1
- /**
2
- * @module config/signal-client
3
- * @audience installer
4
- * @layer cli
5
- * @stability stable
6
- *
7
- * Universal funnel signal client — browser, Node.js, and Deno compatible.
8
- * Sends signals to the public `/api/signal` endpoint (no auth required).
9
- *
10
- * Usage (marketing site):
11
- * ```ts
12
- * import { SpineSignal } from 'spine-framework/config/signal-client'
13
- * SpineSignal.init({ endpoint: 'https://cortex.yourcompany.com' })
14
- * SpineSignal.send({ stage: 'anonymous', source: 'mar', action_type: 'page_view', action_value: 1, session_id: sid })
15
- * ```
16
- *
17
- * Usage (pixel — email tracking):
18
- * ```html
19
- * <img src="https://cortex.yourcompany.com/api/signal?format=pixel&stage=identified&source=mar&action_type=email_open&action_value=1&person_id=UUID" width="1" height="1" />
20
- * ```
21
- *
22
- * Usage (CLI / Node.js):
23
- * ```ts
24
- * import { SpineSignal } from 'spine-framework/config/signal-client'
25
- * await SpineSignal.send({ stage: 'installed', source: 'use', action_type: 'framework_init', action_value: 2, instance_id: serial })
26
- * ```
27
- */
28
-
29
- export interface SignalPayload {
30
- stage: 'anonymous' | 'identified' | 'installed'
31
- source: 'mar' | 'int' | 'use' | 'manual'
32
- action_type: string
33
- action_value: 1 | 2 | 5
34
- pipeline_slug?: string
35
- anonymous_id?: string
36
- person_id?: string
37
- account_id?: string
38
- session_id?: string
39
- action_description?: string
40
- occurred_at?: string
41
- url?: string
42
- path?: string
43
- referrer?: string
44
- user_agent?: string
45
- utm_source?: string
46
- utm_medium?: string
47
- utm_campaign?: string
48
- instance_id?: string
49
- environment?: 'dev' | 'staging' | 'production'
50
- }
51
-
52
- export interface SignalClientConfig {
53
- endpoint: string
54
- timeout?: number
55
- }
56
-
57
- export interface SignalResult {
58
- ok: boolean
59
- signal_id?: string
60
- rating?: number
61
- error?: string
62
- }
63
-
64
- let _config: SignalClientConfig = {
65
- endpoint: 'https://cortex.spine-framework.com',
66
- timeout: 5000,
67
- }
68
-
69
- /**
70
- * Initialize the signal client with a custom endpoint.
71
- * Call once at app startup before any `send()` calls.
72
- */
73
- function init(config: SignalClientConfig): void {
74
- _config = { ..._config, ...config }
75
- }
76
-
77
- /**
78
- * Send a funnel signal. Fire-and-forget safe — never throws.
79
- * Returns a result object indicating success or failure.
80
- */
81
- async function send(payload: SignalPayload): Promise<SignalResult> {
82
- try {
83
- const controller = new AbortController()
84
- const timeout = setTimeout(() => controller.abort(), _config.timeout ?? 5000)
85
-
86
- const response = await fetch(`${_config.endpoint}/api/signal`, {
87
- method: 'POST',
88
- headers: { 'Content-Type': 'application/json' },
89
- body: JSON.stringify(payload),
90
- signal: controller.signal,
91
- }).finally(() => clearTimeout(timeout))
92
-
93
- if (!response.ok) {
94
- const body = await response.json().catch(() => ({}))
95
- return { ok: false, error: body?.error || `HTTP ${response.status}` }
96
- }
97
-
98
- const data = await response.json()
99
- return {
100
- ok: true,
101
- signal_id: data?.data?.signal_id,
102
- rating: data?.data?.rating,
103
- }
104
- } catch {
105
- return { ok: false, error: 'Signal send failed (network or timeout)' }
106
- }
107
- }
108
-
109
- /**
110
- * Build a tracking pixel URL for email templates.
111
- * Embed as: `<img src={SpineSignal.pixelUrl({...})} width="1" height="1" />`
112
- */
113
- function pixelUrl(payload: Omit<SignalPayload, 'session_id'>): string {
114
- const params = new URLSearchParams({ format: 'pixel' })
115
- for (const [key, value] of Object.entries(payload)) {
116
- if (value !== undefined && value !== null) {
117
- params.set(key, String(value))
118
- }
119
- }
120
- return `${_config.endpoint}/api/signal?${params.toString()}`
121
- }
122
-
123
- /**
124
- * Minimal browser session ID — stable for the tab session.
125
- * Suitable for `session_id` on marketing source signals.
126
- * Returns a consistent ID stored in sessionStorage (browser only).
127
- */
128
- function getSessionId(): string {
129
- if (typeof sessionStorage !== 'undefined') {
130
- const key = '_spine_sid'
131
- let sid = sessionStorage.getItem(key)
132
- if (!sid) {
133
- sid = `sess_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`
134
- sessionStorage.setItem(key, sid)
135
- }
136
- return sid
137
- }
138
- // Node.js / non-browser: generate ephemeral ID
139
- return `sess_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`
140
- }
141
-
142
- export const SpineSignal = {
143
- init,
144
- send,
145
- pixelUrl,
146
- getSessionId,
147
- }
@@ -1,70 +0,0 @@
1
- /// <reference types="node" />
2
- /**
3
- * @module config/signals
4
- * @audience installer
5
- * @layer cli
6
- * @stability stable
7
- *
8
- * Signal endpoint configuration for framework install tracking and engagement.
9
- *
10
- * The public framework sends anonymous signals to a configurable endpoint.
11
- * Private Cortex instances receive and process these signals.
12
- */
13
-
14
- export interface SignalConfig {
15
- endpoint: string
16
- timeout: number
17
- retryAttempts: number
18
- batchSize: number
19
- }
20
-
21
- export const signalConfig: SignalConfig = {
22
- endpoint: process.env.SPINE_SIGNAL_ENDPOINT || 'https://cortex.spine-framework.com',
23
- timeout: 5000,
24
- retryAttempts: 3,
25
- batchSize: 10,
26
- }
27
-
28
- // Engagement signal types with scores
29
- export const engagementSignals = {
30
- framework_installed: { score: 1, description: 'Framework npm install completion' },
31
- framework_initialized: { score: 3, description: 'First successful startup' },
32
- framework_connected_db: { score: 5, description: 'Database connection success' },
33
- app_install: { score: 4, description: 'App installation' },
34
- app_initialized: { score: 4, description: 'App first load' },
35
- app_first_action: { score: 6, description: 'First feature usage' },
36
- account_created: { score: 8, description: 'Anonymous → identified migration' },
37
- first_deploy: { score: 10, description: 'Production deployment' },
38
- team_collaboration: { score: 12, description: 'Multi-user usage' },
39
- } as const
40
-
41
- export type EngagementSignalType = keyof typeof engagementSignals
42
-
43
- // Claim trigger thresholds
44
- export const claimTriggers = {
45
- immediate: 0, // User explicitly runs `spine-framework claim`
46
- gentle_nudge: 3, // After first successful startup
47
- value_realized: 5, // After database connection
48
- ecosystem_engaged: 8, // After app installation
49
- power_user: 10, // After regular usage patterns
50
- } as const
51
-
52
- export interface EngagementEntry {
53
- type: EngagementSignalType
54
- timestamp: string
55
- score: number
56
- }
57
-
58
- export interface EngagementData {
59
- framework_install_serial: string
60
- engagement_score: number
61
- signals: EngagementEntry[]
62
- claim_status: 'anonymous' | 'claimed' | 'pending'
63
- last_claim_prompt?: string
64
- account_id?: string
65
- }
66
-
67
- export interface ClaimPrompt {
68
- timing: keyof typeof claimTriggers
69
- message: string
70
- }
@@ -1,52 +0,0 @@
1
- # Stability Policy
2
-
3
- > Version target: **0.x** — breaking changes are allowed until 1.0.
4
-
5
- ## Stability Levels
6
-
7
- Every exported symbol in `_shared/index.ts` is annotated with one of these levels:
8
-
9
- | Level | Meaning | Breaking change policy |
10
- |-------|---------|----------------------|
11
- | **stable** | Safe for custom code to depend on | Minimum 1 minor version deprecation notice before removal |
12
- | **evolving** | API may change between minor versions | No deprecation period required, but changelog entry mandatory |
13
- | **internal** | Not exported; core-contributor only | May change without notice |
14
-
15
- ## Versioning
16
-
17
- Spine follows [semver](https://semver.org/) with these conventions:
18
-
19
- - **0.x.y** — Pre-stable. Breaking changes increment the minor version.
20
- - **1.0.0+** — Stable. Breaking changes increment the major version.
21
-
22
- ## Deprecation Process
23
-
24
- 1. Mark the symbol with `@deprecated` JSDoc tag and a migration hint
25
- 2. Add a console warning on first use (once per process)
26
- 3. Remove after the next minor version bump
27
-
28
- ## What Counts as Breaking
29
-
30
- - Removing or renaming an exported function/type from `_shared/index.ts`
31
- - Changing function signatures (required params added, return type changed)
32
- - Removing a CLI command or changing its required arguments
33
- - Changing database table/column names in migration files
34
- - Removing seed data keys from `seed/*.json` schemas
35
-
36
- ## What Does NOT Count as Breaking
37
-
38
- - Adding new optional parameters to existing functions
39
- - Adding new exports to `_shared/index.ts`
40
- - Adding new CLI commands
41
- - Adding new database tables or columns
42
- - Performance improvements to existing functions
43
- - Bug fixes (even if they change observable behavior that was clearly wrong)
44
-
45
- ## Release Checklist
46
-
47
- 1. `npm run test:boundary` — architectural boundaries intact
48
- 2. `npm run test:unit` — unit tests pass
49
- 3. Update `CHANGELOG.md` with breaking changes highlighted
50
- 4. Bump version in `package.json`
51
- 5. `npm run prepublishOnly` — build + test gate
52
- 6. `npm publish`
@@ -1,77 +0,0 @@
1
- # Example: Custom App
2
-
3
- A minimal React app with manifest and routing, installable via `spine-framework install-app`.
4
-
5
- ## Directory Structure
6
-
7
- ```
8
- custom/apps/my-app/
9
- ├── manifest.json # App metadata, routes, nav items
10
- ├── index.tsx # React entry point (default export)
11
- ├── seed/
12
- │ ├── types.json # Item types this app provides
13
- │ └── triggers.json # Automation triggers
14
- ├── components/
15
- │ └── Dashboard.tsx # App-specific components
16
- └── package.json # npm package metadata (optional)
17
- ```
18
-
19
- ## manifest.json
20
-
21
- ```json
22
- {
23
- "name": "My App",
24
- "slug": "my-app",
25
- "version": "0.1.0",
26
- "description": "A custom app for demonstration",
27
- "routes": [
28
- { "path": "/", "redirect": "/dashboard" },
29
- { "path": "/dashboard", "component": "Dashboard" }
30
- ],
31
- "nav_items": [
32
- { "label": "Dashboard", "path": "/dashboard", "icon": "LayoutDashboard" }
33
- ],
34
- "required_roles": ["member"]
35
- }
36
- ```
37
-
38
- ## index.tsx
39
-
40
- ```tsx
41
- import * as React from 'react'
42
- import { Routes, Route, Navigate } from 'react-router-dom'
43
-
44
- function Dashboard() {
45
- return (
46
- <div className="p-6">
47
- <h1 className="text-2xl font-bold">My App</h1>
48
- </div>
49
- )
50
- }
51
-
52
- export default function MyApp() {
53
- return (
54
- <Routes>
55
- <Route path="/" element={<Navigate to="dashboard" replace />} />
56
- <Route path="dashboard" element={<Dashboard />} />
57
- </Routes>
58
- )
59
- }
60
- ```
61
-
62
- ## Installation
63
-
64
- ```bash
65
- # Create the app scaffold
66
- spine-framework create-app my-app
67
-
68
- # Or install an existing app package
69
- spine-framework install-app my-app
70
- ```
71
-
72
- ## How It Works
73
-
74
- 1. `CustomAppLoader` discovers apps via `import.meta.glob('custom/apps/*/index.tsx')`
75
- 2. The app's `manifest.json` provides routing and navigation metadata
76
- 3. Seed data is applied via `spine-framework install-app` (idempotent upserts)
77
- 4. The app is isolated — it cannot import from other apps or core internals
@@ -1,27 +0,0 @@
1
- # Example: Custom Function
2
-
3
- A minimal Netlify Function using Spine's core context system.
4
-
5
- ## Setup
6
-
7
- 1. Create `custom/functions/custom_my-handler.ts`
8
- 2. Import `createHandler` and `CoreContext` from `spine-framework/_shared`
9
- 3. Your handler receives a fully-resolved `CoreContext` with:
10
- - `ctx.principal` — authenticated user/machine identity
11
- - `ctx.db` — Supabase client (scoped to principal's permissions)
12
- - `ctx.accountId` — resolved account UUID
13
- - `ctx.requestId` — unique request trace ID
14
-
15
- ## Key Points
16
-
17
- - **Naming convention:** Custom functions MUST be prefixed with `custom_`
18
- - **No static imports from core:** Use the `spine-framework` package exports
19
- - **Authentication is automatic:** `createHandler` validates the JWT/API key before your code runs
20
- - **Database access:** Use `ctx.db` for RLS-scoped queries, or import `adminDb` for service-role operations
21
-
22
- ## API
23
-
24
- ```
25
- GET/POST /.netlify/functions/custom_my-handler?action=list
26
- Authorization: Bearer <jwt>
27
- ```
@@ -1,48 +0,0 @@
1
- /**
2
- * Example: Custom Netlify Function using Spine core context
3
- *
4
- * This demonstrates the minimal pattern for creating a custom function
5
- * that leverages Spine's authentication, database, and principal system.
6
- *
7
- * File: custom/functions/custom_my-handler.ts
8
- */
9
-
10
- import { createHandler, adminDb } from 'spine-framework/_shared'
11
- import type { CoreContext } from 'spine-framework/_shared'
12
-
13
- /**
14
- * A simple handler that lists items for the current account.
15
- */
16
- async function listAccountItems(ctx: CoreContext) {
17
- const { data, error } = await ctx.db
18
- .from('items')
19
- .select('id, title, type_id, created_at')
20
- .eq('account_id', ctx.accountId)
21
- .eq('is_active', true)
22
- .order('created_at', { ascending: false })
23
- .limit(50)
24
-
25
- if (error) {
26
- return { statusCode: 500, body: JSON.stringify({ error: error.message }) }
27
- }
28
-
29
- return {
30
- statusCode: 200,
31
- body: JSON.stringify({ items: data }),
32
- }
33
- }
34
-
35
- /**
36
- * Netlify Function entry point.
37
- * `createHandler` resolves the principal, validates auth, and provides CoreContext.
38
- */
39
- export const handler = createHandler(async (ctx) => {
40
- const { action } = ctx.params
41
-
42
- switch (action) {
43
- case 'list':
44
- return listAccountItems(ctx)
45
- default:
46
- return { statusCode: 400, body: JSON.stringify({ error: 'Unknown action' }) }
47
- }
48
- })
@@ -1,68 +0,0 @@
1
- # Example: Custom Webhook Handler
2
-
3
- Demonstrates the self-registration pattern for webhook handlers.
4
-
5
- ## How It Works
6
-
7
- 1. Create a handler function in `custom/functions/custom_my-webhook.ts`
8
- 2. Register it by calling `registerWebhookHandler()` in `custom/functions/custom_webhook-handlers.ts`
9
- 3. Core resolves handlers at runtime via the `webhook_handlers` DB table — no static imports needed
10
-
11
- ## Handler Function
12
-
13
- ```ts
14
- // custom/functions/custom_my-webhook.ts
15
-
16
- import { createHandler } from 'spine-framework/_shared'
17
- import type { CoreContext } from 'spine-framework/_shared'
18
-
19
- export const handler = createHandler(async (ctx: CoreContext) => {
20
- const payload = ctx.body
21
-
22
- // Process the webhook payload
23
- console.log('Received webhook:', payload.event_type)
24
-
25
- // Do something with the data
26
- const { error } = await ctx.db
27
- .from('items')
28
- .insert({
29
- title: `Webhook: ${payload.event_type}`,
30
- type_id: await resolveTypeId('webhook_event'),
31
- account_id: ctx.accountId,
32
- data: payload,
33
- })
34
-
35
- if (error) {
36
- return { statusCode: 500, body: JSON.stringify({ error: error.message }) }
37
- }
38
-
39
- return { statusCode: 200, body: JSON.stringify({ ok: true }) }
40
- })
41
- ```
42
-
43
- ## Registration
44
-
45
- ```ts
46
- // In custom/functions/custom_webhook-handlers.ts
47
-
48
- import { registerWebhookHandler } from 'spine-framework/_shared/webhook-registration'
49
-
50
- registerWebhookHandler({
51
- name: 'my-webhook',
52
- functionName: 'custom_my-webhook',
53
- description: 'Handles incoming webhooks from MyService',
54
- events: ['integration.webhook'],
55
- }).catch(console.error)
56
- ```
57
-
58
- ## Integration Config
59
-
60
- When configuring an integration in the admin panel, set:
61
- - `handler.path` = `my-webhook` (matches the `name` in registration)
62
-
63
- ## Key Points
64
-
65
- - **Self-registration is idempotent** — safe to call on every module load
66
- - **No core changes needed** — just add your handler and register it
67
- - **Runtime resolution** — core looks up handlers from `webhook_handlers` table via `resolveHandler()`
68
- - **Events array** — currently `['integration.webhook']` is the standard event type
@@ -1,22 +0,0 @@
1
- /**
2
- * @module handler-registry
3
- * @audience core-contributor
4
- * @layer shared-service
5
- * @stability stable
6
- *
7
- * Static handler registry for esbuild-compatible dynamic dispatch.
8
- *
9
- * **THIS FILE IS OVERWRITTEN AT ASSEMBLY TIME.**
10
- * The stub here (empty registry) is replaced by `assemble-functions.sh`
11
- * with static imports of every `custom_*.ts` handler found in the assembled
12
- * functions directory. esbuild can then bundle all handlers into the
13
- * `integration-routes` function at build time — no runtime dynamic imports.
14
- *
15
- * Keys are the handler path (e.g. "funnel-signal") AND the full file name
16
- * (e.g. "custom_funnel-signal") so lookups work either way.
17
- *
18
- * @seeAlso scripts/assemble-functions.sh (generates this file)
19
- * @seeAlso integration-routes.ts (consumes HANDLER_REGISTRY)
20
- */
21
-
22
- export const HANDLER_REGISTRY: Record<string, any> = {}
@@ -1,168 +0,0 @@
1
- /**
2
- * @module signal
3
- * @audience both
4
- * @layer api-handler
5
- * @stability stable
6
- *
7
- * Public funnel signal ingestion endpoint. Accepts signals from any source
8
- * (browser JS, CLI, email pixel) without authentication. Rate limiting is
9
- * enforced at the Netlify edge layer via netlify.toml.
10
- *
11
- * **Routed by:** `POST /.netlify/functions/signal`
12
- * `GET /.netlify/functions/signal?format=pixel`
13
- *
14
- * **Request formats:**
15
- * - `POST` with JSON body — standard signal (returns JSON response)
16
- * - `GET ?format=pixel` — tracking pixel (returns 1×1 transparent GIF)
17
- *
18
- * **Required fields (POST body / GET query params):**
19
- * - `stage` — 'anonymous' | 'identified' | 'installed'
20
- * - `source` — 'mar' | 'int' | 'use' | 'manual'
21
- * - `action_type` — string (e.g. 'page_view', 'form_submit')
22
- * - `action_value` — 1 | 2 | 5
23
- * - at least one of: `anonymous_id`, `person_id`, `account_id`
24
- * - `session_id` — required when source = 'mar'
25
- *
26
- * **Optional fields:**
27
- * - `pipeline_slug` — routes signal to a specific funnel pipeline
28
- * - `path`, `referrer`, `url`, `utm_*`, `instance_id`, `environment`
29
- *
30
- * **Authorization:** None — intentionally public. Signals are best-effort and
31
- * validated by `processSignal` in `custom_funnel-signal.ts`.
32
- *
33
- * **CORS:** Allows all origins for cross-origin browser calls.
34
- *
35
- * @seeAlso custom/apps/cortex/functions/custom_funnel-signal.ts (processSignal)
36
- * @seeAlso config/signal-client.ts (browser/Node client for senders)
37
- */
38
-
39
- // 1×1 transparent GIF (base64)
40
- const PIXEL_GIF_BASE64 = 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
41
- const PIXEL_GIF = Buffer.from(PIXEL_GIF_BASE64, 'base64')
42
-
43
- const CORS_HEADERS = {
44
- 'Access-Control-Allow-Origin': '*',
45
- 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
46
- 'Access-Control-Allow-Headers': 'Content-Type, X-Session-Id',
47
- 'Access-Control-Max-Age': '86400',
48
- }
49
-
50
- export const handler = async (event: any) => {
51
- // Handle CORS preflight
52
- if (event.httpMethod === 'OPTIONS') {
53
- return { statusCode: 204, headers: CORS_HEADERS, body: '' }
54
- }
55
-
56
- const requestId = crypto.randomUUID()
57
- const isPixel = event.queryStringParameters?.format === 'pixel'
58
-
59
- try {
60
- // Build signal payload from body (POST) or query params (GET pixel)
61
- let signalData: any
62
-
63
- if (event.httpMethod === 'GET' && isPixel) {
64
- signalData = { ...event.queryStringParameters }
65
- // action_value comes as string in query params — coerce to number
66
- if (signalData.action_value) {
67
- signalData.action_value = parseInt(signalData.action_value, 10)
68
- }
69
- } else if (event.httpMethod === 'POST') {
70
- if (!event.body) {
71
- return {
72
- statusCode: 400,
73
- headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
74
- body: JSON.stringify({ error: 'Missing request body' }),
75
- }
76
- }
77
- try {
78
- signalData = JSON.parse(event.body)
79
- } catch {
80
- return {
81
- statusCode: 400,
82
- headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
83
- body: JSON.stringify({ error: 'Invalid JSON body' }),
84
- }
85
- }
86
- } else {
87
- return {
88
- statusCode: 405,
89
- headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
90
- body: JSON.stringify({ error: 'Method not allowed' }),
91
- }
92
- }
93
-
94
- // Resolve processSignal dynamically — core must not statically import custom handlers.
95
- // After assembly, custom_funnel-signal is a sibling in the same functions directory.
96
- let processSignal: Function
97
- try {
98
- // @ts-ignore — custom_funnel-signal is injected at runtime by the assembly step
99
- const mod = await import('./custom_funnel-signal')
100
- processSignal = mod.processSignal
101
- if (typeof processSignal !== 'function') throw new Error('processSignal not exported')
102
- } catch {
103
- return {
104
- statusCode: 503,
105
- headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
106
- body: JSON.stringify({ error: 'Signal handler not available' }),
107
- }
108
- }
109
-
110
- // Minimal script context for processSignal
111
- const scriptContext = {
112
- requestId,
113
- accountId: null,
114
- slug: 'public-signal',
115
- principal: { id: 'system', type: 'machine', accountId: null },
116
- headers: event.headers || {},
117
- }
118
-
119
- const scriptEvent = {
120
- httpMethod: event.httpMethod,
121
- headers: event.headers || {},
122
- body: signalData,
123
- path: event.path || '/signal',
124
- queryStringParameters: event.queryStringParameters || {},
125
- }
126
-
127
- // Fire-and-forget for pixel requests; await for JSON
128
- if (isPixel) {
129
- processSignal(signalData, scriptContext, scriptEvent).catch(() => {
130
- // Silent fail for pixel — response is always the GIF
131
- })
132
- return {
133
- statusCode: 200,
134
- headers: {
135
- ...CORS_HEADERS,
136
- 'Content-Type': 'image/gif',
137
- 'Cache-Control': 'no-store, no-cache, must-revalidate',
138
- 'Pragma': 'no-cache',
139
- },
140
- body: PIXEL_GIF.toString('base64'),
141
- isBase64Encoded: true,
142
- }
143
- }
144
-
145
- const result = await processSignal(signalData, scriptContext, scriptEvent)
146
-
147
- if (result.status === 'error') {
148
- return {
149
- statusCode: 400,
150
- headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
151
- body: JSON.stringify({ error: result.error }),
152
- }
153
- }
154
-
155
- return {
156
- statusCode: 200,
157
- headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
158
- body: JSON.stringify({ data: result }),
159
- }
160
-
161
- } catch (err: any) {
162
- return {
163
- statusCode: 500,
164
- headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
165
- body: JSON.stringify({ error: 'Internal error' }),
166
- }
167
- }
168
- }