spine-framework 0.4.3 → 0.5.0-rc.4

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 (320) hide show
  1. package/.env.example +13 -3
  2. package/.framework/cli/commands/bootstrap.ts +392 -0
  3. package/.framework/cli/commands/dev.ts +91 -218
  4. package/.framework/cli/commands/doctor.ts +132 -26
  5. package/.framework/cli/commands/generate.ts +38 -298
  6. package/.framework/cli/commands/init.ts +20 -1
  7. package/.framework/cli/commands/migrate.ts +36 -30
  8. package/.framework/cli/commands/migrations.ts +16 -56
  9. package/.framework/cli/commands/upgrade.ts +35 -0
  10. package/.framework/cli/commands/validate-schema.ts +147 -0
  11. package/.framework/cli/context.ts +223 -110
  12. package/.framework/cli/index.ts +6 -0
  13. package/.framework/cli/lib/project-files.ts +241 -1
  14. package/.framework/functions/_shared/agent-runner.ts +1000 -81
  15. package/.framework/functions/_shared/cil.ts +2 -1
  16. package/.framework/functions/_shared/db.ts +3 -6
  17. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  18. package/.framework/functions/_shared/kb.ts +61 -7
  19. package/.framework/functions/_shared/logging.ts +562 -224
  20. package/.framework/functions/_shared/message-sequence.ts +53 -0
  21. package/.framework/functions/_shared/middleware.ts +47 -44
  22. package/.framework/functions/_shared/pipeline-runner.ts +150 -37
  23. package/.framework/functions/_shared/principal.ts +9 -6
  24. package/.framework/functions/_shared/trigger-engine.ts +149 -40
  25. package/.framework/functions/admin-data.ts +48 -31
  26. package/.framework/functions/ai-agents.ts +103 -33
  27. package/.framework/functions/api-keys.ts +301 -122
  28. package/.framework/functions/apps.ts +43 -26
  29. package/.framework/functions/cil-ingest.ts +48 -15
  30. package/.framework/functions/debug-auth.ts +14 -8
  31. package/.framework/functions/deliveries.ts +39 -12
  32. package/.framework/functions/embeddings.ts +145 -76
  33. package/.framework/functions/integration-outbound.ts +67 -27
  34. package/.framework/functions/integrations.ts +254 -127
  35. package/.framework/functions/logs.ts +273 -133
  36. package/.framework/functions/observability.ts +277 -147
  37. package/.framework/functions/pipeline-executions.ts +1 -1
  38. package/.framework/functions/pipeline-runner-background.ts +120 -0
  39. package/.framework/functions/pipelines.ts +14 -9
  40. package/.framework/functions/prompt-configs.ts +7 -4
  41. package/.framework/functions/roles.ts +8 -5
  42. package/.framework/functions/security.ts +30 -17
  43. package/.framework/functions/system-cron.ts +250 -128
  44. package/.framework/functions/tests.ts +7 -6
  45. package/.framework/functions/timers.ts +310 -107
  46. package/.framework/functions/trigger-scheduler.ts +65 -5
  47. package/.framework/functions/triggers.ts +28 -13
  48. package/.framework/functions/types.ts +13 -10
  49. package/.framework/migrations/064_episode_leases.sql +196 -0
  50. package/.framework/migrations/065_execution_tables_updated_at.sql +20 -0
  51. package/.framework/migrations/066_episode_steal_thread_merge.sql +148 -0
  52. package/.framework/migrations/067_episode_lease_stale_steal.sql +93 -0
  53. package/.framework/migrations/068_cron_schedule_timer_rpcs.sql +251 -0
  54. package/.framework/migrations/069_log_stats_window.sql +54 -0
  55. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  56. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  57. package/.framework/migrations/072_slug_autofill.sql +77 -0
  58. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  59. package/.framework/schemas/spine.config.schema.json +62 -0
  60. package/.framework/src/App.tsx +3 -1
  61. package/.framework/src/apps/admin/index.tsx +1141 -775
  62. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  63. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  64. package/.framework/src/components/admin/AdminSidebar.tsx +92 -81
  65. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  66. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  67. package/.framework/src/components/app-shell/GenericAppShell.tsx +3 -1
  68. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  69. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  70. package/.framework/src/components/auth/TurnstileWidget.tsx +9 -6
  71. package/.framework/src/components/kb/KBArticleEditor.tsx +49 -6
  72. package/.framework/src/components/layout/Layout.tsx +4 -2
  73. package/.framework/src/components/layout/Sidebar.tsx +304 -179
  74. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  75. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  76. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  77. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  78. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  79. package/.framework/src/components/ui/sheet.tsx +24 -26
  80. package/.framework/src/components/ui/textarea.tsx +74 -48
  81. package/.framework/src/hooks/useBranding.ts +47 -0
  82. package/.framework/src/hooks/useEntityList.ts +12 -9
  83. package/.framework/src/hooks/useListSchema.ts +108 -58
  84. package/.framework/src/instructions/README.md +57 -0
  85. package/.framework/src/instructions/ai-agents.md +69 -0
  86. package/.framework/src/instructions/alerts.md +80 -0
  87. package/.framework/src/instructions/api-keys.md +87 -0
  88. package/.framework/src/instructions/apps.md +77 -0
  89. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  90. package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
  91. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  92. package/.framework/src/instructions/concepts/automation.md +186 -0
  93. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  94. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  95. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  96. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  97. package/.framework/src/instructions/concepts/observability.md +116 -0
  98. package/.framework/src/instructions/concepts/permissions.md +150 -0
  99. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  100. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  101. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  102. package/.framework/src/instructions/deliveries.md +59 -0
  103. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  104. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  105. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  106. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  107. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  108. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  109. package/.framework/src/instructions/integrations.md +96 -0
  110. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  111. package/.framework/src/instructions/invites.md +60 -0
  112. package/.framework/src/instructions/kb.md +80 -0
  113. package/.framework/src/instructions/logs.md +77 -0
  114. package/.framework/src/instructions/observability.md +61 -0
  115. package/.framework/src/instructions/operations/deployment.md +115 -0
  116. package/.framework/src/instructions/operations/migrations.md +125 -0
  117. package/.framework/src/instructions/operations/security-checklist.md +148 -0
  118. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  119. package/.framework/src/instructions/operations/upgrade.md +116 -0
  120. package/.framework/src/instructions/pipeline-executions.md +62 -0
  121. package/.framework/src/instructions/pipelines.md +78 -0
  122. package/.framework/src/instructions/prompts.md +95 -0
  123. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  124. package/.framework/src/instructions/reference/api/auth.md +113 -0
  125. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  126. package/.framework/src/instructions/reference/app-manifest.md +150 -0
  127. package/.framework/src/instructions/reference/cli.md +124 -0
  128. package/.framework/src/instructions/reference/database.md +132 -0
  129. package/.framework/src/instructions/reference/design-schema.md +280 -0
  130. package/.framework/src/instructions/reference/env-vars.md +133 -0
  131. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  132. package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
  133. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  134. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  135. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  136. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  137. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  138. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  139. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  140. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  141. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  142. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  143. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  144. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  145. package/.framework/src/instructions/reference/functions/index.md +103 -0
  146. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  147. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  148. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  149. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  150. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  151. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  152. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  153. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  154. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  155. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  156. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  157. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  158. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  159. package/.framework/src/instructions/reference/functions/security.md +140 -0
  160. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  161. package/.framework/src/instructions/reference/functions/system.md +106 -0
  162. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  163. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  164. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  165. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  166. package/.framework/src/instructions/reference/functions/types.md +146 -0
  167. package/.framework/src/instructions/reference/seeds.md +126 -0
  168. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  169. package/.framework/src/instructions/reference/spine-config.md +114 -0
  170. package/.framework/src/instructions/roles.md +80 -0
  171. package/.framework/src/instructions/runtime-detail.md +70 -0
  172. package/.framework/src/instructions/runtime-list.md +77 -0
  173. package/.framework/src/instructions/security.md +67 -0
  174. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  175. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  176. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  177. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  178. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  179. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  180. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  181. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  182. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  183. package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
  184. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  185. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  186. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  187. package/.framework/src/instructions/testing.md +59 -0
  188. package/.framework/src/instructions/timers.md +82 -0
  189. package/.framework/src/instructions/triggers.md +69 -0
  190. package/.framework/src/instructions/types.md +93 -0
  191. package/.framework/src/lib/branding.ts +128 -0
  192. package/.framework/src/pages/NoAppPage.tsx +3 -1
  193. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  194. package/.framework/src/pages/SetupPage.tsx +5 -2
  195. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  196. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  197. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  198. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  199. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  200. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  201. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  202. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  203. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  204. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  205. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  206. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  207. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  208. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  209. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  210. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  211. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  212. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  213. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  214. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  215. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  216. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  217. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  218. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  219. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  220. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  221. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  222. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  223. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  224. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  225. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  226. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  227. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  228. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  229. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  230. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  231. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  232. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  233. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  234. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  235. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  236. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  237. package/.framework/src/pages/auth/AuthConfirmPage.tsx +3 -1
  238. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +3 -1
  239. package/.framework/src/pages/auth/LoginPage.tsx +4 -2
  240. package/.framework/src/pages/auth/RegisterPage.tsx +3 -1
  241. package/.framework/templates/agent/AGENTS.md +150 -0
  242. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  243. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  244. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  245. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  246. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
  247. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  248. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
  249. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
  250. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
  251. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  252. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  253. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  254. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  255. package/.framework/templates/project.json +5 -1
  256. package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
  257. package/.framework/tests/unit/agent-files.test.ts +355 -0
  258. package/.framework/tests/unit/agent-runner.test.ts +496 -47
  259. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  260. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  261. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  262. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  263. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  264. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  265. package/.framework/tests/unit/episode-lease.test.ts +695 -0
  266. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  267. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  268. package/.framework/tests/unit/kb-ingest-lease.test.ts +19 -0
  269. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  270. package/.framework/tests/unit/pipeline-runner-background.test.ts +120 -0
  271. package/.framework/tests/unit/pipeline-runner.test.ts +153 -1
  272. package/.framework/tests/unit/principal-cache.test.ts +15 -0
  273. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  274. package/.framework/tests/unit/system-cron.test.ts +240 -0
  275. package/.framework/tests/unit/timers.test.ts +159 -0
  276. package/.framework/tests/unit/trigger-engine.test.ts +278 -91
  277. package/.framework/tests/unit/trigger-scheduler.test.ts +78 -0
  278. package/README.md +125 -166
  279. package/config/tsconfig.cli.json +2 -2
  280. package/netlify.toml +5 -0
  281. package/package.json +15 -4
  282. package/templates/project/package-lock.json +2 -2
  283. package/.framework/docs/admin-routes.md +0 -76
  284. package/.framework/docs/api-docs-progress.md +0 -38
  285. package/.framework/docs/api-governance.md +0 -146
  286. package/.framework/docs/api-testing-results.md +0 -212
  287. package/.framework/docs/assembly-launch-guide.md +0 -138
  288. package/.framework/docs/audit-results.md +0 -590
  289. package/.framework/docs/db-api-inventory.md +0 -95
  290. package/.framework/docs/gap-remediation-backlog.md +0 -103
  291. package/.framework/docs/ui-adoption-verification.md +0 -111
  292. package/.framework/docs/ui-api-coverage.md +0 -84
  293. package/.framework/docs/v2-compatibility-audit.md +0 -228
  294. package/.framework/mcp/index.ts +0 -133
  295. package/.framework/mcp/load-env.ts +0 -60
  296. package/.framework/mcp/shared/app-manifest.ts +0 -275
  297. package/.framework/mcp/shared/db.ts +0 -477
  298. package/.framework/mcp/shared/kb.ts +0 -118
  299. package/.framework/mcp/tools/architect.ts +0 -431
  300. package/.framework/mcp/tools/cil/context.ts +0 -345
  301. package/.framework/mcp/tools/cil/graph.ts +0 -242
  302. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  303. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  304. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  305. package/.framework/mcp/tools/cil/project.ts +0 -791
  306. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  307. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  308. package/.framework/mcp/tools/cil/search.ts +0 -97
  309. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  310. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  311. package/.framework/mcp/tools/kb/delete.ts +0 -139
  312. package/.framework/mcp/tools/kb/index.ts +0 -52
  313. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  314. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  315. package/.framework/mcp/tools/kb/publish.ts +0 -100
  316. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  317. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  318. package/.framework/mcp/tools/kb/search.ts +0 -101
  319. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  320. package/.framework/mcp/tools/observability.ts +0 -510
@@ -0,0 +1,150 @@
1
+ ---
2
+ title: "AI agents, prompt configs, and embeddings"
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/ai-agents.ts
9
+ - .framework/functions/prompt-configs.ts
10
+ - .framework/functions/embeddings.ts
11
+ - .framework/functions/ai-interactions.ts
12
+ - .framework/functions/_shared/agent-runner.ts
13
+ - .framework/functions/_shared/llm.ts
14
+ - .framework/functions/_shared/pipeline-runner.ts
15
+ - .framework/migrations/034_match_embeddings_knn.sql
16
+ - .framework/migrations/044_match_embeddings_filtered_knn.sql
17
+ ---
18
+
19
+ # AI agents, prompt configs, and embeddings
20
+
21
+ Three config entities shape Spine's AI behavior:
22
+
23
+ - An **AI agent** (`public.ai_agents`) is the *who*: a named model + system
24
+ prompt + tool set + guardrails.
25
+ - A **prompt config** (`public.prompt_configs`) is the *how*: the prompt
26
+ template, model parameters, retrieval sources, and escalation policy applied
27
+ to a conversation.
28
+ - An **embedding** (`public.embeddings`) is the *memory*: per-document vector
29
+ rows that semantic retrieval searches over.
30
+
31
+ All three are ordinary admin-data-managed rows: `ai-agents` and
32
+ `prompt-configs` expose CRUD via their own functions, and the conversational
33
+ runtime lives in `.framework/functions/_shared/agent-runner.ts`.
34
+
35
+ ## Agent configuration
36
+
37
+ `ai-agents` (`.framework/functions/ai-agents.ts`) stores, per row:
38
+
39
+ - `name` + `agent_type` — required (`chat`, `analysis`, `automation`,
40
+ `custom` in the admin UI).
41
+ - `model_config` — `{ model, temperature, max_tokens, tools }`. At inference
42
+ time the model resolves as `model_config.model || LLM_DEFAULT_MODEL ||
43
+ 'gpt-4o'`; `model_config.tools` is passed through as OpenAI tool definitions
44
+ with `tool_choice: 'auto'`.
45
+ - `system_prompt` — concatenated with the prompt config's `system_prompt`
46
+ (agent first) to form the system message.
47
+ - `tools`, `capabilities`, `constraints`, `metadata` — `metadata
48
+ .default_prompt_config_id` is the fallback used when the thread doesn't
49
+ stamp its own `prompt_config_id`.
50
+ - `is_active` — inactive agents fail `resolveAgentConfig` lookup.
51
+
52
+ ## Prompt configs and templating
53
+
54
+ `prompt-configs` (`.framework/functions/prompt-configs.ts`) carries the fields
55
+ the runner consumes per turn: `system_prompt`, `context_template`, `model`
56
+ (default `gpt-4o`), `temperature` (0.7), `max_tokens` (4000),
57
+ `is_multi_turn` (true), `max_history_messages` (20), `confidence_threshold`,
58
+ `escalation_action` / `escalation_target`, `output_mode` / `output_field`,
59
+ `requires_review`, `knowledge_sources`, `available_tools`, and
60
+ `tool_constraints`.
61
+
62
+ `context_template` supports exactly two placeholders, substituted in
63
+ `buildContext`: `{{user_message}}` (the inbound text, truncated to 4000 chars)
64
+ and `{{conversation_history}}` (prior messages rendered as
65
+ `Customer:`/`Agent:` lines, truncated to 4000 chars).
66
+
67
+ ## How a thread resolves its agent and prompt
68
+
69
+ `resolveAgentConfig` in `.framework/functions/_shared/agent-runner.ts`:
70
+
71
+ 1. Reads the thread (`threads`, joined with its `types` row).
72
+ 2. Agent id comes from `thread.data.agent_id`, falling back to the thread
73
+ type's `design_schema.default_agent_id`. No agent → the run aborts.
74
+ 3. Prompt config id comes from `thread.data.prompt_config_id`, falling back to
75
+ `agent.metadata.default_prompt_config_id`.
76
+ 4. Both rows must be `is_active`.
77
+
78
+ `buildContext` then assembles the model input: combined system prompt, the
79
+ rendered `context_template`, retrieved knowledge excerpts (numbered `[n]` and
80
+ explicitly marked *untrusted* — the prompt instructs the model to treat them as
81
+ evidence, never instructions), `## Conversation History`, `## Tool Results`,
82
+ the filtered `available_tools` list, and finally `## Current Message` with a
83
+ prompt-injection warning.
84
+
85
+ ## Invoking an agent
86
+
87
+ The interactive path is `POST /api/ai-agents?action=run` with `{ thread_id,
88
+ user_message, mode }` — `mode` is `step`, `complete`, or `episode` (default
89
+ `complete`). Before any tokens are spent the handler runs a prompt-injection
90
+ preflight (`detectInjection`); the app's `security.prompt_injection` setting
91
+ chooses `off` | `flag` (default) | `block`. Each claimed run debits the
92
+ `agent_step` rate limit.
93
+
94
+ Inside a run, `runAgent`/`runAgentStep` build context, call inference, dispatch
95
+ any `tool_calls` the model returns through `executeToolAction`, persist the
96
+ outbound `messages` row stamped with `agent_id` and `prompt_config_id`, and
97
+ manage the episode lease (see [concurrency](./concurrency-and-idempotency.md)).
98
+
99
+ Pipelines reach AI through stage types, not the `run` action:
100
+
101
+ - `agent_inference` — POSTs `{ context, model, temperature, max_tokens }` to
102
+ `config.webhook_url` via `integration-outbound` (`generic-http`); without a
103
+ `webhook_url` it returns a mock response.
104
+ - `search_knowledge` — retrieval over knowledge sources for the stage config's
105
+ `query`.
106
+ - `generate_embeddings` / `revoke_embeddings` / `kb_ingest` / `kb_revoke` /
107
+ `create_interaction` — delegate to the `embeddings`, `kb`, and
108
+ `ai-interactions` functions described below.
109
+
110
+ ## The LLM call itself
111
+
112
+ `_shared/llm.ts` does not hold API keys. `callOpenAI` routes every request
113
+ through `integration-outbound` with `integration_slug: 'openai'` and
114
+ `credential_field: 'api_token'` — credentials come from the `integrations`
115
+ table like any other outbound call. `callOpenAIChat` defaults to
116
+ `gpt-4o-mini`/0.7/4000 tokens, overridable per call and via
117
+ `integration.config.models.chat` / `.embedding`. When no OpenAI integration or
118
+ credential exists, inference returns a clearly-marked mock response rather than
119
+ failing.
120
+
121
+ ## Embeddings and retrieval
122
+
123
+ `embeddings` (`.framework/functions/embeddings.ts`) manages the
124
+ `public.embeddings` vector store:
125
+
126
+ - `action=generate` builds embeddable text per **vector type**. The type's
127
+ `design_schema.vector_types` decides which builders run — default
128
+ `['semantic', 'structure']`; registered builders also cover `intent`,
129
+ `cause`, `procedure`, `outcome`, `context`, and `error_signal`. Each vector
130
+ type occupies a fixed `chunk_index` slot so concurrent types never collide
131
+ on the unique constraint, and unchanged content is skipped by hash.
132
+ - `action=revoke` sets `revoked_at` on a document's vectors; `action=approve`
133
+ sets `approved_at` and clears revocation.
134
+ - `action=search-similar` calls the `match_embeddings` RPC (pgvector KNN,
135
+ defined in migrations `034`/`044`) with `query_embedding`, `match_count`,
136
+ `similarity_threshold`, and optional account/vector-type/item-type/document
137
+ filters.
138
+ - `action=search-semantic` is a Postgres `websearch` full-text query over the
139
+ `content` column — the lexical complement to vector search.
140
+
141
+ ## Interaction records
142
+
143
+ `ai-interactions` (`.framework/functions/ai-interactions.ts`) writes the
144
+ feedback/observability side of the loop as `items` rows (default type
145
+ `ai_feedback_record`, overridable via `type_slug`; the call returns
146
+ `{ skipped: true }` when the type isn't installed). Actions:
147
+ `submit_feedback`, `create_interaction` (what the pipeline stage calls — it
148
+ stamps `thread_id`, `ai_message_id`, `retrieved_sources`, `ai_confidence`,
149
+ `kb_candidate`, and review fields), `update_interaction`,
150
+ `create_cil_proposal`, and `list`.
@@ -0,0 +1,162 @@
1
+ ---
2
+ title: Apps and Renderers
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/migrations/001_core_schema.sql
9
+ - .framework/migrations/013_get_account_apps_role_filter.sql
10
+ - .framework/migrations/014_apps_required_roles.sql
11
+ - .framework/src/App.tsx
12
+ - .framework/src/components/AppWrapper.tsx
13
+ - .framework/src/components/CustomAppLoader.tsx
14
+ - .framework/src/components/app-shell/GenericAppShell.tsx
15
+ - .framework/src/components/app-shell/GenericListPage.tsx
16
+ - .framework/src/components/app-shell/GenericDetailPage.tsx
17
+ - .framework/functions/_shared/app-manifest.ts
18
+ - .framework/functions/apps.ts
19
+ - .framework/cli/commands/create-app.ts
20
+ - .framework/cli/commands/install-app.ts
21
+ ---
22
+
23
+ # Apps and Renderers
24
+
25
+ An app in Spine is a **row in `public.apps`** plus — for apps that render their
26
+ own UI — a directory of code. The registry row decides where the app mounts,
27
+ who can open it, and *how* it renders. The `renderer` column is the fork in
28
+ the road.
29
+
30
+ ## The registry row
31
+
32
+ `.framework/migrations/001_core_schema.sql` defines `public.apps`: unique
33
+ `slug`, `name`, `route_prefix`, `renderer` (CHECK constraint:
34
+ `'generic' | 'custom' | 'none'`), `min_role`, `required_roles` (added in
35
+ migration 014), `nav_items`, `config`, `integration_deps`, `metadata`,
36
+ `is_active`, `is_system`, `owner_account_id`, domains.
37
+
38
+ - `route_prefix` (defaults to `/<slug>`) is where the SPA mounts the app —
39
+ `.framework/src/App.tsx` builds a `<Route>` per active app.
40
+ - `is_active: false` removes the app from navigation and routing.
41
+ - `is_system: true` protects the row from tenant-level deletion.
42
+
43
+ ## Role gating
44
+
45
+ Two layers decide who sees an app:
46
+
47
+ - Server side, `get_account_apps` filters the app list by the caller's role
48
+ slugs (`.framework/functions/apps.ts`;
49
+ `.framework/migrations/013_get_account_apps_role_filter.sql`).
50
+ - Client side, `AppWrapper` computes `required_roles` (falling back to
51
+ `[min_role]`) and renders an access-denied screen unless the user holds
52
+ `system_admin` or one of those roles (`.framework/src/components/AppWrapper.tsx`).
53
+
54
+ This gates *opening the app*; it does not replace record-level
55
+ `record_permissions` on the data underneath.
56
+
57
+ ## `renderer = 'generic'`
58
+
59
+ The platform renders the app's list and detail pages for you, straight from
60
+ each type's `design_schema.views`:
61
+
62
+ - `App.tsx` mounts `GenericAppShell` for `renderer === 'generic'` apps.
63
+ - `GenericAppShell` builds the sidebar from the app's `nav_items` and, for
64
+ every nav item carrying `type_slug` (and optional `view`), generates
65
+ list/detail/new routes backed by `GenericListPage` and
66
+ `GenericDetailPage`.
67
+ - `GenericListPage` turns `views.default_list` (or the nav item's named view)
68
+ into sortable columns; `GenericDetailPage` renders `views.default_detail`
69
+ sections with per-field read/write awareness.
70
+
71
+ **What you get for free:** a working list page, detail page, create/edit form,
72
+ sorting, filters, field-type rendering, and permission-aware editing — with
73
+ zero app code. **What it costs:** every type the app serves must have complete
74
+ `fields`, `views.default_list`, and `views.default_detail`, or the pages fail
75
+ with "No schema available" / schema-load errors. See
76
+ [Design Schema](design-schema.md).
77
+
78
+ ## `renderer = 'custom'`
79
+
80
+ The app ships its own React code under `custom/apps/<slug>/` (a
81
+ project-owned directory, not part of the framework package). `App.tsx` mounts
82
+ `CustomAppLoader`, which resolves the app through an
83
+ `import.meta.glob('custom/apps/*/index.tsx')` — the app's `index.tsx` is its
84
+ entry point and default-exports the routed component (the `create-app`
85
+ scaffold generates exactly this shape).
86
+
87
+ A custom app renders its own pages but still uses the platform for data:
88
+ `/api/admin-data` for runtime CRUD, `/api/types` for schemas. Custom apps
89
+ must never write their own CRUD wrappers — that bypasses permissions,
90
+ validation, audit, and triggers (see [Runtime Records](runtime-records.md)).
91
+
92
+ `renderer = 'none'` registers the app (data, types, integrations) with no UI
93
+ surface.
94
+
95
+ ## The manifest
96
+
97
+ `custom/apps/<slug>/manifest.json` is the app's declarative half, validated by
98
+ `loadManifest` in `.framework/functions/_shared/app-manifest.ts` (the
99
+ `AppManifest` interface is the contract):
100
+
101
+ ```json
102
+ {
103
+ "name": "My App",
104
+ "slug": "my-app",
105
+ "description": "…",
106
+ "version": "1.0.0",
107
+ "required_roles": ["member"],
108
+ "route_prefix": "/my-app",
109
+ "renderer": "custom",
110
+ "routes": ["/my-app", "/my-app/dashboard"],
111
+ "nav_items": [{ "title": "Dashboard", "path": "/my-app/dashboard", "icon": "LayoutDashboard", "order": 1 }],
112
+ "features": [],
113
+ "dependencies": [],
114
+ "entry_point": "./index.tsx",
115
+ "is_public": false,
116
+ "auth_required": true
117
+ }
118
+ ```
119
+
120
+ Validated requirements: non-empty `name`, `slug` matching
121
+ `^[a-z][a-z0-9-]*$` (and equal to the directory name for discovery), and
122
+ `required_roles` as an array of strings. Optional keys the install flow also
123
+ honors: `config` (merged onto the apps row), `seed` (declared seed entries),
124
+ `owned_types`, `registration`, `metadata`, `min_role`,
125
+ `preview_domain`/`prod_domain`.
126
+
127
+ ## Install and seed flow
128
+
129
+ Two paths put an app on disk and in the registry:
130
+
131
+ - **`npx spine-framework create-app <slug>`** — scaffolds
132
+ `custom/apps/<slug>/` (`manifest.json` + `index.tsx` + `components/`) and
133
+ inserts a `public.apps` row with `renderer: 'custom'` and
134
+ `route_prefix: /<slug>` (`.framework/cli/commands/create-app.ts`).
135
+ - **`npx spine-framework install-app <package>`** — copies a packaged app into
136
+ `custom/apps/<slug>/`, runs migrations, then `seedApp`:
137
+ - upserts the `apps` row from the manifest (`appRowPayload`: name,
138
+ description, version, nav_items, route_prefix, renderer, required_roles,
139
+ min_role, config merge, metadata);
140
+ - applies seed files in dependency order — conventional
141
+ `seed/roles.json` → `roles` (conflict `slug`), `seed/types.json` →
142
+ `types` (conflict `kind,slug`, **no** `app_id` injection,
143
+ `ignoreDuplicates`) — plus any `manifest.seed` entries;
144
+ - refuses to overwrite a `types` row whose `design_schema` differs from the
145
+ seed unless the slug is declared in `manifest.owned_types` and
146
+ `--reconcile-owned-types` was passed (types are global — see
147
+ [Scope and Accounts](scope-and-accounts.md));
148
+ - syncs the `registration` block into `spine.config.json` (the project-owned
149
+ config file at the project root).
150
+ - **`update-db-app <slug>`** re-syncs a local `manifest.json` to the DB without
151
+ touching files; **`seed-app <slug>`** re-runs seeds;
152
+ **`uninstall-app`** removes the app (`--keep-data` preserves seeded rows).
153
+ All in `.framework/cli/commands/install-app.ts`.
154
+
155
+ ## The practical split
156
+
157
+ Use `generic` when the app is "records in, records out" — lists, detail forms,
158
+ admin-style editing — and keep views accurate. Use `custom` when the UX is a
159
+ product surface (dashboards, wizards, bespoke layouts) and treat the manifest
160
+ as your app's contract: registration, roles, seeds, nav. Either way, types and
161
+ `design_schema` do the data modeling — the renderer only decides who draws the
162
+ pixels.
@@ -0,0 +1,133 @@
1
+ ---
2
+ title: "Auth: sessions, API keys, and invites"
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/_shared/principal.ts
9
+ - .framework/functions/_shared/middleware.ts
10
+ - .framework/functions/_shared/hash-api-key.ts
11
+ - .framework/functions/_shared/rate-limit.ts
12
+ - .framework/functions/api-keys.ts
13
+ - .framework/functions/invites.ts
14
+ - .framework/functions/integration-routes.ts
15
+ - .framework/migrations/035_rate_limits.sql
16
+ - .framework/migrations/052_registration_security.sql
17
+ ---
18
+
19
+ # Auth: sessions, API keys, and invites
20
+
21
+ Every request to a Spine function resolves to one **Principal**
22
+ (`_shared/principal.ts`): either `type: 'human'` (a `people` row + roles) or
23
+ `type: 'machine'` (an `api_keys` row + scopes), plus `ANONYMOUS_PRINCIPAL` and
24
+ `SYSTEM_PRINCIPAL` sentinels. `resolvePrincipal` is the single dispatch point,
25
+ run by `createHandler` / `createPublicHandler` in `_shared/middleware.ts`:
26
+
27
+ 1. `x-api-key` header → **machine** principal.
28
+ 2. A valid `INTERNAL_INVOKE_SECRET` (function-to-function) → `x-cron-id` /
29
+ `x-trigger-id` impersonation, else `SYSTEM_PRINCIPAL`. These headers are
30
+ honored only alongside the secret — a bare id is ignored.
31
+ 3. `Authorization: Bearer <jwt>` → **human** principal via Supabase auth.
32
+ 4. Nothing → `ANONYMOUS_PRINCIPAL` (machine, no scopes, no account).
33
+
34
+ The principal also selects the database client (`getPrincipalDb`): humans get a
35
+ JWT-scoped client (row-level security applies); machines and anonymous get the
36
+ service-role client, so endpoint code is responsible for checking machine
37
+ scopes — `machineHasScope` honors `resource:action`, `resource:*`, and `*:*`,
38
+ and `admin-data` treats a machine with `*:*` as authorized (with role
39
+ `system_admin`) while lesser keys still pass through record permissions.
40
+
41
+ ## Browser sessions (humans)
42
+
43
+ `Bearer <supabase-jwt>` → `supabase.auth.getUser` → `resolveInternalPersonId`
44
+ binds the auth user to a `people` row by `auth_uid` (email fallback never
45
+ binds unclaimed invite stubs), then loads the person with its role join. The
46
+ principal carries `roles: [role.slug]`, `accountId`, and the raw JWT in
47
+ `authContext` (never serialized — `formatPrincipalForAudit` omits it). Results
48
+ cache for 60 seconds per warm instance, so role/account changes take up to a
49
+ minute to propagate; a person with neither `account_id` nor `role_id`
50
+ (registration not yet placed) is never cached.
51
+
52
+ A human's effective rights come from `roles` → `isSystemAdmin` /
53
+ `humanHasRole` → the `design_schema.record_permissions` on each type. An
54
+ invalid or expired JWT silently becomes anonymous — endpoints then fail with
55
+ "Authentication required" / "Account context required" rather than 401s.
56
+
57
+ ## API keys (machines)
58
+
59
+ Mint a key with `POST /api/api-keys` (`.framework/functions/api-keys.ts`):
60
+ `{ name, key_type?, scopes?, rate_limit?, expires_at?, integration_id?,
61
+ app_id?, machine_type?, is_internal? }`. The response returns `raw_key` —
62
+ `sk_<32 hex>` — **once**, with a "store this now" warning. Only the first 8
63
+ characters (`key_prefix`, the lookup index) and a PBKDF2-SHA256 hash
64
+ (100k iterations, `pbkdf2$iterations$salt$hash`) are stored; `omitSecretFields`
65
+ strips secret material from every read response.
66
+
67
+ Use it as `x-api-key: sk_...` on any function endpoint. Resolution:
68
+
69
+ - Prefix-matched candidate keys must be `is_active` and unexpired, then the
70
+ hash is verified with a timing-safe compare.
71
+ - Per-key rate limit: `rate_limit` (default 100) requests per
72
+ `config.rate_limit_window_seconds` (default 60), counted in
73
+ `api_key_usage_logs`.
74
+ - Per-IP ceiling: `rate_limit_hit` RPC on `ip:<ip>:<path>`, default 300/minute
75
+ (`SPINE_IP_RATE_LIMIT`), fail-open.
76
+ - Every request writes an `api_key_usage_logs` row (method, path, IP,
77
+ user-agent) — `action=usage-logs` on the same endpoint reads them back.
78
+
79
+ The resulting machine principal inherits `key.scopes`, `key.machine_type`
80
+ (default `'integration'`), `key.is_internal`, and `key.account_id`. A `*:*`
81
+ scope acts as `system_admin`. `integration-routes` (inbound webhooks)
82
+ *requires* a machine principal — a JWT cannot deliver webhooks — and keys
83
+ pinned to `integration_id`/`app_id` are restricted to those integrations.
84
+ Related actions: `action=validate` (verify a key + check permissions),
85
+ `action=revoke` (sets `is_active=false`), `action=usage-logs`.
86
+
87
+ Shared throttling also applies: `checkRateLimit(ctx, action)`
88
+ (`_shared/rate-limit.ts`) resolves named policies from
89
+ `accounts.data.rate_limits[action]` (account override) or the calling app's
90
+ `config.rate_limits` (with `rate_limit_profile` indirection), and enforces
91
+ them through the atomic `rate_limit_hit` RPC on `public.rate_limits`
92
+ (migration `035`). Over-limit → 429 (`RateLimitError`); a `fail_closed`
93
+ policy → 503 when the counter RPC is unavailable.
94
+
95
+ ## First admin and invites
96
+
97
+ Bootstrap is a Postgres trigger, not an endpoint: on the first `auth.users`
98
+ signup, `handle_new_auth_user` (migrations `010`, hardened in `052`) takes an
99
+ advisory lock, creates the `people` row, and — only when **no** `people` rows
100
+ exist — grants the `system_admin` role under the `spine-system` account.
101
+ Serialization means two concurrent first signups cannot both become
102
+ system-admin; `system_admin` can never be assigned through registration.
103
+
104
+ Everyone after that arrives by invite (`.framework/functions/invites.ts`):
105
+
106
+ - `action=create-invite` — requires `system_admin` or an `*_admin` role on the
107
+ target account; `role_slug: 'system_admin'` is refused. Creates a stub
108
+ `people` row holding `data.invite_token` (16-char) and
109
+ `invite_expires_at` (7 days), returns
110
+ `{APP_BASE_URL}/register?t=<token>`, and can send the email through a
111
+ configured email integration.
112
+ - `action=validate-token` (public) — checks the token exists, is unblocked,
113
+ and unexpired.
114
+ - `action=complete-registration` — after Supabase signup, calls the
115
+ service-role-only `complete_registration_placement` RPC, which atomically
116
+ stamps `account_id` + `role_id` from the app's server-side registration
117
+ config (`new_account`, `existing_account`, `join_request` paths). Client
118
+ claims about role or target are never trusted — the RPC is the only
119
+ placement writer.
120
+ - Admin management: `list-invites`, `revoke-invite`, `reinvite`,
121
+ `block-invite`, `approve`, `deny`, `list-members`, `update-member`,
122
+ `deactivate-member`. `check-first-user` and `check-domain` are public
123
+ read-only probes used by the registration UI.
124
+
125
+ ## What each surface can reach
126
+
127
+ | | Browser JWT | API key | Internal secret |
128
+ |---|---|---|---|
129
+ | Principal | human (roles) | machine (scopes) | system / cron / trigger |
130
+ | DB client | JWT-scoped (RLS) | service role | service role |
131
+ | `admin-data`, entity APIs | role + record permissions | scopes; `*:*` = system admin | full |
132
+ | `integration-routes` (inbound) | no | required | — |
133
+ | `system-cron` / `trigger-scheduler` | no | `SCHEDULER_API_KEY` or internal machine | yes |
@@ -0,0 +1,186 @@
1
+ ---
2
+ title: "Automation: pipelines, triggers, and timers"
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/pipelines.ts
9
+ - .framework/functions/pipeline-executions.ts
10
+ - .framework/functions/pipeline-runner-background.ts
11
+ - .framework/functions/_shared/pipeline-runner.ts
12
+ - .framework/functions/_shared/trigger-engine.ts
13
+ - .framework/functions/timers.ts
14
+ - .framework/functions/triggers.ts
15
+ - .framework/functions/trigger-scheduler.ts
16
+ - .framework/functions/system-cron.ts
17
+ - .framework/functions/admin-data.ts
18
+ - .framework/migrations/001_core_schema.sql
19
+ - .framework/migrations/018_runtime_tables.sql
20
+ - .framework/migrations/020_schema_reconciliation.sql
21
+ - .framework/migrations/068_cron_schedule_timer_rpcs.sql
22
+ - netlify.toml
23
+ ---
24
+
25
+ # Automation: pipelines, triggers, and timers
26
+
27
+ Spine automation is three primitives plus a ledger:
28
+
29
+ - A **pipeline** (`public.pipelines`) is an ordered JSON `stages` array — the
30
+ definition of *what runs*.
31
+ - A **trigger** (`public.triggers`) maps a record event
32
+ (`<kind>_created` / `<kind>_updated` / `<kind>_deleted`) to a pipeline — *when
33
+ data changes*.
34
+ - A **timer** (`public.timers`) points at a pipeline and carries
35
+ `next_execution` — *when the clock says so*.
36
+ - A **pipeline execution** (`public.pipeline_executions`) is the durable record
37
+ of one run — *what actually happened*.
38
+
39
+ There is no separate "workflow engine" service. Everything runs inside the
40
+ Netlify functions, coordinated through Postgres rows.
41
+
42
+ ## Pipelines and the runner
43
+
44
+ `runPipeline` in `.framework/functions/_shared/pipeline-runner.ts` is the single
45
+ executor. It loads the pipeline row (inactive or missing pipelines throw),
46
+ creates or claims a `pipeline_executions` row, then walks `pipeline.stages` in
47
+ array order.
48
+
49
+ Each stage names a `stage_type`. That value is **not** a function name — it is a
50
+ slug looked up in `public.actions` (`eq('slug', stage.stage_type)`,
51
+ `is_active = true`). The action row supplies `handler_module` and `handler`;
52
+ the stage's `config` is merged over `action.config` along with internal keys
53
+ `_pipelineId`, `_executionId`, `_stageIndex`, and `_triggerData`. The runner
54
+ then routes on `handler_module`:
55
+
56
+ - `functions` — dispatch to a named handler in the runner's `stageHandlers`
57
+ map. Built-ins include `update_item`, `create_record`, `http_request`,
58
+ `send_notification`, `send_email`, `run_pipeline`, `query_items`,
59
+ `search_knowledge`, `agent_inference`, `generate_embeddings`,
60
+ `revoke_embeddings`, `kb_ingest`, `kb_revoke`, and `create_interaction`.
61
+ - `integrations` — dispatch through `integration-outbound` (the same path
62
+ outbound webhooks use).
63
+ - `webhook` — POST/GET to `action.config.url` via `integration-outbound`.
64
+
65
+ Stage config strings support `{{trigger.*}}` and `{{record.*}}` placeholders,
66
+ resolved recursively against the trigger envelope and the firing record's data
67
+ (`record` is `triggerData.entity.data`). Timer- and manual-fired runs have **no
68
+ record context** — `{{record.*}}` placeholders pass through unresolved and
69
+ appear literally in stage output (e.g. notification text). Author timer-driven
70
+ stages against `{{trigger.*}}` or fixed text.
71
+
72
+ A stage that throws is recorded as `failed` in the stage results; the run then
73
+ aborts **unless** the stage sets `continue_on_error: true`. Completed and
74
+ failed runs both write terminal status, stage results, and duration back to the
75
+ execution row — `pipeline-executions` is therefore the ground truth for "did it
76
+ work", and `status: 'cancelled'` is available for operator-initiated stops.
77
+
78
+ ## Triggers: record events
79
+
80
+ Writes that go through `admin-data` call into
81
+ `.framework/functions/_shared/trigger-engine.ts`: creates fire
82
+ `fireCreateTriggers`, updates fire `fireUpdateTriggers` with the previous row
83
+ snapshot. (The delete path currently does not invoke `fireDeleteTriggers` —
84
+ `*_deleted` event types exist in the union, but nothing emits them from
85
+ `admin-data`.) Trigger fires are caught-and-logged, so a failing trigger never
86
+ rolls back the write that caused it.
87
+
88
+ `checkAndFireTriggers` loads active triggers for the event type and evaluates
89
+ each against `config`:
90
+
91
+ - `config.entity_type` must match the runtime table's kind (`items` → `item`,
92
+ `people` → `person`, and so on).
93
+ - `config.type_slug` restricts to one `types.slug`; the engine resolves the
94
+ slug from the record's `type_id` when needed.
95
+ - `config.filters` is a map of path → expected value. Paths support dot
96
+ notation and Postgres-style `data->>'field'` accessors; expected values may
97
+ be literals, arrays (membership), or operator objects using `$eq`, `$ne`,
98
+ `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$exists`.
99
+ - `config.fire_on_transition` makes an update trigger fire only when the record
100
+ *moves into* the matching state — the previous snapshot must not already
101
+ match. This is what prevents "every save to a published record re-fires the
102
+ pipeline".
103
+
104
+ A matching trigger does **not** run the pipeline inline. It:
105
+
106
+ 1. Inserts a `pipeline_executions` row with `status: 'queued'` (a stable
107
+ execution id exists before any work starts).
108
+ 2. POSTs to `/.netlify/functions/pipeline-runner-background` with an
109
+ `X-Internal-Secret` header, awaiting only the dispatch ack (bounded by a 5s
110
+ timeout) — the caller's request never waits for the pipeline to finish.
111
+ 3. Records `record_trigger_fire` bookkeeping and emits a `trigger.fired` audit
112
+ event (or `trigger.failed` on error).
113
+
114
+ The background function verifies `INTERNAL_INVOKE_SECRET`, reconstructs a
115
+ machine context, and calls `runPipeline` with the pre-created `execution_id`,
116
+ which claims the row `queued → running` instead of inserting a duplicate.
117
+
118
+ ## Timers and the scheduler
119
+
120
+ A timer row carries `timer_type`, `pipeline_id`, `config`, and
121
+ `next_execution`. The `get_due_timers` RPC returns active timers whose
122
+ `pipeline_id` is set and `next_execution <= now()`; `update_timer_after_run`
123
+ stamps `last_execution`, increments `execution_count`/`success_count`/
124
+ `failure_count`, and reschedules `next_execution` from
125
+ `config.interval_minutes` — a timer with no positive `interval_minutes` gets
126
+ `next_execution = NULL` and never runs again.
127
+
128
+ The clock is `trigger-scheduler`, a Netlify scheduled function configured for
129
+ `* * * * *` (every minute) in `netlify.toml`. It accepts genuine Netlify
130
+ Clockwork invocations (schedule header or `Netlify Clockwork` user-agent *plus*
131
+ a `{"next_run": ...}` body) and fabricates a system-principal context;
132
+ non-Clockwork calls fall through to the cron handler's own auth, which accepts
133
+ either `?api_key=$SCHEDULER_API_KEY` or an internal machine principal — anything
134
+ else gets 403 and an audit event.
135
+
136
+ Each tick, `system-cron` does all of the following:
137
+
138
+ - **Sweeps stale executions** — `sweepStaleExecutions` marks
139
+ `running`/`pending`/`queued` rows untouched for over 10 minutes as `failed`.
140
+ This is the safety net for a runner that died mid-flight or a dispatch that
141
+ never arrived.
142
+ - **Runs due schedules** — `get_due_schedules` → validate the creator → load
143
+ the `public.actions` row → execute as the schedule's delegated machine
144
+ principal (`schedules`/`actions`/`schedule_executions` are the runtime
145
+ scheduling tables).
146
+ - **Runs due timers** — each due timer runs its pipeline as machine principal
147
+ `timer:<timer_id>` with scope `pipelines:execute`, then
148
+ `update_timer_after_run` reschedules it.
149
+ - **Evaluates threshold alerts** — `threshold_alerts` rows compare
150
+ `error_rate`, `latency_p95`, or `pipeline_failure_rate` metrics against a
151
+ value and fire the alert's `pipeline_id` on breach.
152
+ - **Retries webhook deliveries** — `retryPendingDeliveries` runs every tick.
153
+ - **Cleans logs** — `cleanup_old_logs` runs on the 00:00 UTC tick.
154
+
155
+ ## Observing executions
156
+
157
+ - `pipeline-executions` (`.framework/functions/pipeline-executions.ts`):
158
+ `GET` list/get, `action=running`, `action=stats`, `action=cleanup`
159
+ (`days_to_keep` defaults to 30), plus `start`/`complete`/`cancel`.
160
+ - `pipelines` (`.framework/functions/pipelines.ts`): CRUD plus
161
+ `action=by-trigger`, `action=executions`, `action=toggle`.
162
+ - `triggers` and `timers` (`.framework/functions/triggers.ts`,
163
+ `.framework/functions/timers.ts`): CRUD plus `toggle`; triggers also expose
164
+ `listByEvent` and `getExecutions`.
165
+ - CLI: `spine-framework pipelines run <id>` executes a pipeline;
166
+ `spine-framework pipelines executions <id>` lists recent runs
167
+ (`.framework/cli/commands/pipelines.ts`).
168
+
169
+ An execution stuck in `queued` means dispatch failed before the background
170
+ function claimed it — the sweeper fails it after 10 minutes. An execution stuck
171
+ in `running` means the runner died — same sweeper, same outcome.
172
+
173
+ ## How the pieces connect
174
+
175
+ 1. A client writes a record through `admin-data`; `fireCreateTriggers` /
176
+ `fireUpdateTriggers` run in-process after the write.
177
+ 2. The trigger engine matches the event to active `triggers` rows, inserts a
178
+ `queued` execution, and dispatches to `pipeline-runner-background`.
179
+ 3. Independently, Netlify Clockwork invokes `trigger-scheduler` every minute;
180
+ `system-cron` runs due timers and schedules through the same `runPipeline`,
181
+ sweeps dead executions, retries deliveries, and evaluates alert thresholds.
182
+ 4. `runPipeline` resolves each `stage_type` through `public.actions` and runs
183
+ the stage handlers — which is also where outbound integrations, AI agent
184
+ calls, and embedding jobs enter the system.
185
+ 5. Every step lands in `pipeline_executions`, `logs`, and the audit trail,
186
+ which is what the admin UI and `pipeline-executions` API read.