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,125 @@
1
+ ---
2
+ title: "Concurrency and idempotency"
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/_shared/message-sequence.ts
9
+ - .framework/functions/_shared/agent-runner.ts
10
+ - .framework/functions/_shared/pipeline-runner.ts
11
+ - .framework/functions/_shared/trigger-engine.ts
12
+ - .framework/functions/_shared/retry-deliveries.ts
13
+ - .framework/functions/integration-outbound.ts
14
+ - .framework/functions/_shared/kb.ts
15
+ - .framework/cli/commands/migrate.ts
16
+ - .framework/migrations/001_core_schema.sql
17
+ - .framework/migrations/035_rate_limits.sql
18
+ - .framework/migrations/054_kb_ingest_lease.sql
19
+ - .framework/migrations/064_episode_leases.sql
20
+ - .framework/migrations/066_episode_steal_thread_merge.sql
21
+ - .framework/migrations/067_episode_lease_stale_steal.sql
22
+ ---
23
+
24
+ # Concurrency and idempotency
25
+
26
+ Spine runs on serverless functions: an invocation can die at the response
27
+ flush, a client can retry a request that already succeeded, and two instances
28
+ can race the same resource. Spine's answer is consistent: **the database
29
+ decides**. Ordering, single-flight, and deduplication are enforced by
30
+ constraints and atomic RPCs — never by reading state and deciding in memory.
31
+
32
+ ## Ordered writes: message sequencing
33
+
34
+ `public.messages` has `UNIQUE(thread_id, sequence)`
35
+ (migration `001_core_schema.sql`) — the hard invariant for conversation order.
36
+ Two writers honor it differently:
37
+
38
+ - `_shared/message-sequence.ts` (`insertMessageSequenced`) is the simple path:
39
+ read the max `sequence` for the thread, insert `max + 1`, retry up to 3
40
+ times on a `23505` duplicate-key error. The retry exists because a racing
41
+ writer may take the same number; the constraint makes the conflict visible.
42
+ - The agent runner keeps a per-instance counter (`threadSeqCounter`) seeded by
43
+ one `COUNT(*)` per thread, then allocates locally — a batch of tool-result
44
+ messages gets consecutive numbers up front so `Promise.all` inserts stay in
45
+ order. A `23505` triggers a reseed + retry. The counter is an optimization;
46
+ the unique constraint remains the backstop.
47
+
48
+ ## Pipeline executions: claim, don't create-twice
49
+
50
+ Trigger dispatches reserve a `pipeline_executions` row with
51
+ `status: 'queued'` *before* calling the background runner. `runPipeline` then
52
+ **claims** that row (`queued → running` on `id = executionId AND status =
53
+ 'queued'`) rather than inserting a second execution — a retried or duplicated
54
+ dispatch converges on the same row. If the claim finds nothing, the runner
55
+ falls back to inserting, so an execution record always exists. Rows stuck in
56
+ `queued`/`running`/`pending` for over 10 minutes are swept to `failed` — a
57
+ runner that died mid-flight can never write its own terminal status.
58
+
59
+ Individual stages can also be idempotent: `create_record` accepts
60
+ `config.dedupe { field, value, account_scoped? }` — when a record matching
61
+ `field = value` already exists, the stage returns the existing row
62
+ (`deduped: true`) instead of inserting a duplicate.
63
+
64
+ ## Webhook deliveries: retry in place
65
+
66
+ An outbound delivery is one `webhook_deliveries` row for its whole life:
67
+ `pending → processing → delivered | pending | failed`. Retries — automatic
68
+ (`retryPendingDeliveries` each cron tick) or manual
69
+ (`deliveries?action=retry`) — call `integration-outbound` with the existing
70
+ `delivery_id`, which reloads the row, reuses its payload, and increments
71
+ `attempt_count` in place; they never create a second delivery. Backoff is
72
+ exponential (`30s × 2^attempt`, 1h cap, ±20% jitter) until `max_attempts`
73
+ turns the row `failed`; the sweep is bounded (`attempt_count < 10`, 100 rows
74
+ per pass) so one poisoned integration can't monopolize the tick. Inbound
75
+ deliveries get the same row lifecycle, but inbound retry is not implemented —
76
+ a failed inbound delivery stays visible for manual handling.
77
+
78
+ ## Leases: single-flight with fencing
79
+
80
+ Long-running agent episodes coordinate through `public.episode_leases`
81
+ (migration `064`; service-role only). `acquire_episode_lease` is atomic: an
82
+ `INSERT … ON CONFLICT (thread_id) DO NOTHING` fast path, then a
83
+ `FOR UPDATE` conflict path returning `claimed` + a fresh `lease_token`, or
84
+ `held` + the live holder's `driver_id`. The same driver re-acquiring renews;
85
+ an expired lease is taken over with a new token.
86
+
87
+ The token is a **fence**, not just a lock:
88
+
89
+ - `heartbeat_episode_lease(thread_id, lease_token)` extends the TTL only when
90
+ the token matches — `false` means "you were displaced; stop writing now."
91
+ - `release_episode_lease` is token-gated for the same reason.
92
+ - `merge_thread_data(thread_id, patch, lease_token)` (migration `066`) is the
93
+ only way episode checkpoints are written: an atomic jsonb merge whose
94
+ `WHERE` clause refuses the write when a different token holds the lease —
95
+ so a fenced driver's late write is dropped instead of resurrecting stale
96
+ state.
97
+
98
+ Migration `067` adds `p_steal_detached` / `p_stale_seconds`: request-bound
99
+ drivers may steal a lease from a `detached:`-prefixed holder **only** when its
100
+ heartbeat is older than `p_stale_seconds` (default 30). Detached drivers are a
101
+ liveness fallback, never an owner that blocks live work. The same lease shape
102
+ guards knowledge ingest: `document_leases` with
103
+ `acquire_document_lease`/`release_document_lease` (migration `054`), claimed
104
+ by content hash in `_shared/kb.ts`.
105
+
106
+ ## Atomic counters and bookkeeping
107
+
108
+ - `rate_limit_hit` (migration `035`) upsert-increments the `rate_limits`
109
+ window counter in one statement — concurrent invocations can't double-spend
110
+ a window.
111
+ - `record_trigger_fire` (migration `064`) bumps trigger bookkeeping atomically
112
+ on each fire.
113
+
114
+ ## Migrations: ordered, transactional, ledgered
115
+
116
+ `spine-framework migrate` (`.framework/cli/commands/migrate.ts`) applies
117
+ `.framework/migrations/*.sql` (plus `custom/migrations/` unless
118
+ `--framework-only`) in **filename order** — the numeric prefix is the order.
119
+ Each file runs inside one transaction together with its ledger insert into
120
+ `_spine_migrations` (`filename UNIQUE`, `applied_at`): a file either lands
121
+ complete with its ledger row or rolls back, and the run stops at the first
122
+ failure. Re-runs skip anything already in the ledger (`--dry-run` lists
123
+ pending without touching the DB). The connection is derived from
124
+ `SUPABASE_URL` (a hosted `*.supabase.co` URL only — no credentials, ports, or
125
+ paths) plus `SUPABASE_DB_PASSWORD`.
@@ -0,0 +1,124 @@
1
+ ---
2
+ title: Core vs Custom Boundary
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/templates/project.json
9
+ - .framework/cli/commands/upgrade.ts
10
+ - .framework/cli/lib/project-files.ts
11
+ - .framework/cli/commands/migrate.ts
12
+ - scripts/boundary-check.sh
13
+ - scripts/assemble-frontend.sh
14
+ - scripts/assemble-functions.sh
15
+ - config/vite.config.ts
16
+ - config/tsconfig.json
17
+ - config/tsconfig.functions.json
18
+ - netlify.toml
19
+ ---
20
+
21
+ # Core vs Custom Boundary
22
+
23
+ Spine projects are two trees with different owners: `.framework/` is the
24
+ sealed, upgradeable package; `custom/` is project-owned code and data the
25
+ package never writes. Everything about the boundary — the alias config, the
26
+ assembly, the upgrade command, the lint script — exists to keep one promise:
27
+ **upgrading the framework can never destroy your work.**
28
+
29
+ ## What lives where
30
+
31
+ **`.framework/` (core, framework-owned)** — the React app shell and admin UI
32
+ (`.framework/src/`), Netlify functions and shared middleware
33
+ (`.framework/functions/`), ordered SQL migrations (`.framework/migrations/`),
34
+ the CLI (`.framework/cli/`), this instruction book
35
+ (`.framework/src/instructions/`), schemas, seeds, tests, and templates
36
+ (`.framework/templates/`). The shipped file set is declared in
37
+ `.framework/templates/project.json` under `framework_dirs`, `framework_files`,
38
+ `config_files`, `root_scripts`, and `bin`.
39
+
40
+ **`custom/` (project-owned)** — apps under `custom/apps/<slug>/` (each with a
41
+ `manifest.json` and `index.tsx`), migrations under `custom/migrations/`,
42
+ function handlers under `custom/functions/` and `custom/apps/<slug>/functions`,
43
+ plus tests and vendored packages. This tree is created by `init` / `create-app`
44
+ / `install-app`; it is not part of the npm package and `upgrade` never writes
45
+ into it.
46
+
47
+ **Shared but owned:** `config/` (Vite + tsconfig), `netlify.toml`, and the
48
+ `scripts/` assembly-and-integrity scripts ship with the framework and are
49
+ synced on upgrade, with one carve-out — `netlify.toml`
50
+ `[functions."custom_*"]` sections belong to apps and are re-appended verbatim
51
+ by `renderNetlifyToml` in `.framework/cli/lib/project-files.ts`.
52
+
53
+ ## Why the boundary exists: upgrade safety
54
+
55
+ `npx spine-framework upgrade` is a file sync, not a git operation:
56
+
57
+ 1. It reads `.spine-manifest.json` — frozen per-file hashes written at install
58
+ — and **aborts if any framework file was locally modified or deleted**
59
+ unless `--force` is passed (`.framework/cli/commands/upgrade.ts`,
60
+ `runUpgrade`).
61
+ 2. It copies the new package's framework files over yours and deletes only
62
+ `.framework/` entries that disappeared upstream. Files outside
63
+ `.framework/` that upstream dropped are warned about, never removed.
64
+ 3. It re-merges `package.json` (framework scripts + dependency pin), re-renders
65
+ `netlify.toml` preserving `custom_*` sections, and regenerates the manifest.
66
+ New migrations are reported, never run — `npx spine-framework migrate`
67
+ applies them.
68
+
69
+ Because overwrite-on-upgrade is guaranteed, the only way a local change inside
70
+ `.framework/` survives is `verify`/`upgrade` treating the tree as sealed. The
71
+ frozen manifest is the integrity contract: `scripts/verify-integrity.sh`
72
+ re-hashes the tree and compares.
73
+
74
+ ## How the boundary is enforced
75
+
76
+ - **`scripts/boundary-check.sh`** — fails the build if `.framework/` code
77
+ imports `custom/` or `custom_*` modules, if core source references
78
+ known app slugs, or if `custom/` code reaches `.framework/` via relative
79
+ imports instead of the `@core` alias.
80
+ - **Aliases** — `config/tsconfig.json` maps `@core/*` → `.framework/src/*` and
81
+ `@custom/*` → `custom/*`; `config/vite.config.ts` maps `@core` to the
82
+ assembled `src` tree. `config/tsconfig.functions.json` maps `@shared/*` to
83
+ the assembled `_shared/` directory — custom functions import `./_shared/...`
84
+ at runtime.
85
+ - **Assembly** — `scripts/assemble-frontend.sh` copies `.framework/src` as the
86
+ base layer and each `custom/apps/<slug>` verbatim into `.assembled/src/<slug>/`;
87
+ `scripts/assemble-functions.sh` flattens framework + custom handlers into
88
+ `.assembled/netlify/functions/` and treats duplicate function basenames as a
89
+ hard error, while letting `custom/` overlay `_shared/` helpers.
90
+ - **Manifest** — `.spine-manifest.json` + `scripts/verify-integrity.sh` seal
91
+ the `.framework/` tree between upgrades.
92
+
93
+ ## How to extend core without crossing the boundary
94
+
95
+ Custom code reaches core through three sanctioned channels, in order of
96
+ preference:
97
+
98
+ 1. **Data** — `public.apps` rows, `public.types` `design_schema`, roles,
99
+ link types, triggers, pipelines. Most "customization" is data.
100
+ 2. **Custom apps** — `custom/apps/<slug>/` with a `manifest.json`, mounted by
101
+ `route_prefix`/`renderer` and loaded dynamically by
102
+ `.framework/src/components/CustomAppLoader.tsx`.
103
+ 3. **Custom functions** — new handler files alongside the assembled
104
+ `_shared/` layer, for behavior admin-data can't express. Never wrappers
105
+ for runtime CRUD — that is `/api/admin-data`'s job.
106
+
107
+ ## What breaks if you cross it
108
+
109
+ - **Core → custom import:** `boundary-check.sh` fails; even if merged, the next
110
+ `upgrade` overwrites or refuses, and the core now fails in projects lacking
111
+ that app.
112
+ - **Custom → `.framework/` relative import:** `boundary-check.sh` check 2b
113
+ fails; the path also breaks under the assembled layout Vite actually builds.
114
+ - **Edit inside `.framework/`:** `verify` reports drift and `upgrade` aborts —
115
+ or `--force` silently reverts your change. Either way the change was never
116
+ yours to keep.
117
+ - **Custom runtime-CRUD function:** compiles fine and quietly bypasses
118
+ permissions, validation, audit, and triggers — the worst kind of violation
119
+ because nothing fails.
120
+
121
+ ## See also
122
+
123
+ - [The Spine Way](spine-way.md) — the ten rules this boundary is part of
124
+ - [Apps and Renderers](apps-and-renderers.md) — how `custom/` apps register and mount
@@ -0,0 +1,152 @@
1
+ ---
2
+ title: Design Schema
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/src/types/types.ts
9
+ - .framework/functions/types.ts
10
+ - .framework/functions/admin-data.ts
11
+ - .framework/functions/_shared/schema-utils.ts
12
+ - .framework/functions/_shared/permissions.ts
13
+ - .framework/functions/_shared/resolve-ids.ts
14
+ - .framework/src/components/app-shell/GenericListPage.tsx
15
+ - .framework/src/components/app-shell/GenericDetailPage.tsx
16
+ - .framework/src/hooks/useListSchema.ts
17
+ - .framework/migrations/058_account_data_field_declarations.sql
18
+ - .framework/migrations/060_account_person_record_permissions.sql
19
+ ---
20
+
21
+ # Design Schema
22
+
23
+ `design_schema` is the JSON contract on a `public.types` row that declares
24
+ everything the platform needs to store, validate, permission, and render one
25
+ kind of runtime record — without code. It is the single most important
26
+ structure in Spine: get it right and permissions, validation, and the generic
27
+ UI all work for free; get it wrong or leave it out and records are created
28
+ unpermissionable and unrenderable.
29
+
30
+ The TypeScript shape is `DesignSchema` in `.framework/src/types/types.ts`:
31
+ `fields`, `views`, `record_permissions`, plus optional `scope` and
32
+ `functionality`.
33
+
34
+ ## Where it lives and how it propagates
35
+
36
+ - The **type definition** holds the canonical copy in
37
+ `public.types.design_schema` (created/updated only via
38
+ `.framework/functions/types.ts`, which requires `system_admin`).
39
+ - **Every record created through `admin-data` is stamped** with the type's
40
+ `design_schema` and `validation_schema` at insert time
41
+ (`.framework/functions/admin-data.ts`, `create`). The stamped copy on the
42
+ record — not the type row — is what the permission engine reads at request
43
+ time (`.framework/functions/_shared/permissions.ts`,
44
+ `sanitizeFirstSurfaceRecordData` / `validateFirstSurfaceUpdatePermissions`
45
+ both refuse when the record carries no `record_permissions`).
46
+ - **Editing a type does not restamp existing records.** Migrations that change
47
+ a type schema re-stamp rows explicitly — see
48
+ `.framework/migrations/058_account_data_field_declarations.sql` and
49
+ `.framework/migrations/060_account_person_record_permissions.sql` for the
50
+ pattern.
51
+ - `validation_schema` is a derived artifact: `generateValidationSchema` in
52
+ `.framework/functions/_shared/schema-utils.ts` extracts `data_type`,
53
+ `required`, `validation`, `options`, and `reference_*` from `fields` and runs
54
+ on every type create and on every update that touches `design_schema`
55
+ (`.framework/functions/types.ts`). Never hand-edit it — change
56
+ `design_schema` and let the API regenerate.
57
+
58
+ ## The sections
59
+
60
+ ### `scope`
61
+
62
+ `"account"` (default when omitted) or `"platform"`. At create time `admin-data`
63
+ reads `designSchema.scope ?? 'account'`; `platform` records are stamped with
64
+ the `spine-system` account's id instead of the caller's account. Child records
65
+ created against a parent (via `thread_id`, `target_id`, `parent_id`) also
66
+ inherit the parent's stamped scope. See
67
+ [Scope and Accounts](scope-and-accounts.md).
68
+
69
+ ### `fields`
70
+
71
+ A flat map of `snake_case` field names to `FieldDefinition`
72
+ (`.framework/src/types/types.ts`): `data_type`, `label`, `required`, plus
73
+ optional `system`, `placeholder`, `validation` (pattern, min/max, lengths,
74
+ `reference_kind`/`reference_type`, `currency_code`), `options` (required for
75
+ `select`/`multiselect`/`radio`), and `permissions` (per-role `read`/`write`
76
+ overrides — see [Permissions](permissions.md)).
77
+
78
+ The `system` flag controls storage: `system: true` fields are top-level record
79
+ columns; all others live under `record.data.<field>` — see
80
+ [Runtime Records](runtime-records.md).
81
+
82
+ Known `data_type` values (the authoritative switch is `sanitizeFieldData` in
83
+ `.framework/functions/_shared/schema-utils.ts`): `text`, `textarea`,
84
+ `rich_text`, `email`, `phone`, `url`, `number`, `currency`, `range`, `date`,
85
+ `datetime`, `boolean`, `checkbox`, `select`, `multiselect`, `radio`, `color`,
86
+ `file`, `image`, `json`, `reference`, `address`. Unknown types pass through
87
+ unsanitized — they are not rejected, they are unvalidated.
88
+
89
+ ### `views`
90
+
91
+ A map of view names to view configs. `default_list` and `default_detail` are
92
+ what the generic renderer consumes:
93
+
94
+ - `default_list` (`type: "list"`, `display: "table" | "card" | "board"`,
95
+ `fields` map with `sortable`/`display_type`, `default_sort`, optional
96
+ `filters`/`stats`) becomes columns in `GenericListPage`
97
+ (`.framework/src/components/app-shell/GenericListPage.tsx`) — the schema
98
+ itself resolves via `useListSchema`, which prefers a stamped record's
99
+ `design_schema` and falls back to the types API.
100
+ - `default_detail` (`type: "detail"`, `sections[]` of `{title, fields}`) drives
101
+ `GenericDetailPage`'s `SchemaDetailForm`; each section field resolves its
102
+ `FieldDefinition` and reads `record[field]` or `record.data[field]` by
103
+ `system` (`.framework/src/components/app-shell/GenericDetailPage.tsx`).
104
+
105
+ `display_type` values for view fields come from `ViewFieldConfig` in
106
+ `.framework/src/types/types.ts` (`input`, `text`, `badge`, `timestamp`,
107
+ `select`, `date_picker`, `file_upload`, …). An app with `renderer = "generic"`
108
+ is broken without these views; a `custom` app may omit them but loses the
109
+ generic admin/runtime fallback.
110
+
111
+ ### `record_permissions`
112
+
113
+ `{ "<role_slug>": ["create" | "read" | "update" | "delete", ...] }` — the base
114
+ grant for the record, evaluated per request against the caller's role plus the
115
+ `'all'` wildcard role. This is the contract evaluated in
116
+ `.framework/functions/_shared/permissions.ts`; the full model is in
117
+ [Permissions](permissions.md).
118
+
119
+ If a type has no `record_permissions`, `admin-data` create merges a permissive
120
+ default (`{ all: ["create","read","update","delete"], fields: {} }`) before
121
+ stamping — an explicit empty object would instead deny everything downstream,
122
+ so always author them deliberately.
123
+
124
+ ### `functionality`
125
+
126
+ Optional automation bindings the runtime interprets — e.g.
127
+ `functionality.lineage` (parent/child resolution for items, read in
128
+ `admin-data` list/get) and the integration/constraint bindings declared in
129
+ `FunctionalityBindings` (`field_map` sync, `unique`/`conditional_required`/
130
+ `immutable` constraints) in `.framework/src/types/types.ts`. Set it to `null`
131
+ until the automation binding you need is confirmed live.
132
+
133
+ ## What each consumer reads
134
+
135
+ | Consumer | Reads |
136
+ |---|---|
137
+ | `admin-data` create | `fields`/`record_permissions`/`scope` for stamping, gating, and account resolution |
138
+ | `permissions.ts` | `record_permissions`, `fields.*.permissions` — on the **record's** stamped schema |
139
+ | `schema-utils.ts` | generated `validation_schema` for `sanitizeFieldData` / `formatFieldData` |
140
+ | `GenericListPage` | `views.default_list` (columns, sort, filters, stats) |
141
+ | `GenericDetailPage` | `views.default_detail`, `fields`, `display_field` |
142
+ | `useEntityRecord` | `fields.*.system` to split system vs `data` writes |
143
+
144
+ ## Authoring minimum
145
+
146
+ Every type needs, at minimum: `fields` (each with `data_type`, `label`,
147
+ `required`), `record_permissions` (at least `system_admin`), and — for generic
148
+ apps — `default_list` + `default_detail`. `slug` must be unique per `kind`
149
+ (enforced by the `types_kind_slug_unique` index in
150
+ `.framework/migrations/015_types_kind_slug_unique.sql`), and `kind` must match
151
+ the runtime table the type governs. The entity page
152
+ [../types.md](../types.md) has minimal and full examples.
@@ -0,0 +1,127 @@
1
+ ---
2
+ title: "Integrations and webhooks"
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/integrations.ts
9
+ - .framework/functions/integration-outbound.ts
10
+ - .framework/functions/integration-routes.ts
11
+ - .framework/functions/deliveries.ts
12
+ - .framework/functions/_shared/webhook-registration.ts
13
+ - .framework/functions/_shared/webhook-registry.ts
14
+ - .framework/functions/_shared/resolve-credentials.ts
15
+ - .framework/functions/_shared/retry-deliveries.ts
16
+ - .framework/functions/_shared/notify.ts
17
+ - .framework/functions/adapters/generic-webhook-outbound.ts
18
+ - .framework/migrations/004_core_rpc_and_broker.sql
19
+ ---
20
+
21
+ # Integrations and webhooks
22
+
23
+ An **integration** (`public.integrations`) is a named bundle of `config` +
24
+ `credentials` that outbound calls and inbound webhooks share. Spine routes both
25
+ directions through `public.webhook_deliveries`, so every attempt is a durable
26
+ row you can list, inspect, and retry — there is no fire-and-forget.
27
+
28
+ ## The integration row
29
+
30
+ `integrations` (`.framework/functions/integrations.ts`) requires `name`,
31
+ `integration_type`, and `provider` on create; it also carries `app_id`,
32
+ `version`, `config`, `credentials`, `metadata`, and a computed
33
+ `is_configured` flag.
34
+
35
+ Credentials live in a dedicated `credentials` jsonb column (added by migration
36
+ `004`) shaped `{ <field>: { source: "value" | "env_var", value: string } }`.
37
+ `resolve-credentials` resolves them **server-side only**, inside the functions
38
+ runtime: `source: "env_var"` reads `process.env[value]`; `source: "value"`
39
+ returns the literal string. Every resolution (or failure) emits an
40
+ `integration.credential_*` audit event. Non-system-admin, non-machine API reads
41
+ of an integration row are sanitized to minimal fields — credential values are
42
+ never returned to ordinary users, though they are stored in the database (use
43
+ `env_var` sources for the tightest posture).
44
+
45
+ ## Outbound calls
46
+
47
+ `integration-outbound` (`.framework/functions/integration-outbound.ts`) is the
48
+ single egress path — `callOpenAI` in `_shared/llm.ts`, the `http_request`,
49
+ `webhook`, `send_email`, and `agent_inference` pipeline stages, and manual
50
+ calls all go through its `execute`:
51
+
52
+ 1. **Resolve the integration**: account-scoped row matching
53
+ `integration_slug` first, then a system integration (`account_id IS NULL`).
54
+ Both must be `is_active`.
55
+ 2. **Open a delivery row**: inserts `webhook_deliveries` with
56
+ `direction: 'outbound'`, `status: 'pending'`, `attempt_count: 0`, and
57
+ `max_attempts` from `action_config.max_attempts` →
58
+ `integration.config.retry_policy.max_attempts` → default 3.
59
+ 3. **Resolve the credential** (`action_config.credential_field`, default
60
+ `api_token`) — skipped when the entry is absent or an empty
61
+ `source: 'value'` literal, which means "call without auth"; the adapter
62
+ then decides whether a key is required — and **pick an adapter**:
63
+ `action_config.adapter || integration.config.outbound_adapter`. Built-ins:
64
+ `adapters/openai-outbound`, `adapters/generic-webhook-outbound`,
65
+ `adapters/resend-outbound`; anything else is dynamically imported as a
66
+ sibling module exposing `buildRequest`.
67
+ 4. **Send**: the adapter builds `{ url, method, headers, body }`; the function
68
+ fetches it, then updates the delivery to `delivered` (with
69
+ `response_status`, `response_body`, `duration_ms`) or `pending`/`failed`.
70
+ 5. Optionally, `action_config.update_record` stamps
71
+ `last_outbound_status` / `last_outbound_at` back onto the source item.
72
+
73
+ ## Delivery and retry semantics
74
+
75
+ Both directions share the same retry math: `computeNextAttempt` is exponential
76
+ backoff — `30s * 2^attempt`, capped at 1 hour, with up to 20% jitter. A failed
77
+ attempt stays `pending` with `next_attempt_at` set until `attempt_count`
78
+ reaches `max_attempts`, when it becomes `failed`.
79
+
80
+ Retries are driven by `retryPendingDeliveries`
81
+ (`.framework/functions/_shared/retry-deliveries.ts`), which `system-cron` runs
82
+ every tick (see [automation](./automation.md)). It sweeps `webhook_deliveries`
83
+ rows that are `pending`, `attempt_count < 10`, and `next_attempt_at` due (or
84
+ null), up to 100 per pass, and re-invokes `integration-outbound` with
85
+ `delivery_id` under the system principal — the `delivery_id` path reuses the
86
+ existing row instead of inserting a duplicate. You can also retry one delivery
87
+ manually via `POST /api/deliveries?action=retry { id }`
88
+ (`.framework/functions/deliveries.ts`); manual retry is outbound-only. Inbound
89
+ retry is not implemented — inbound failures are counted and logged.
90
+
91
+ `deliveries` list/get are account-scoped reads over the same table, filterable
92
+ by `direction`, `status`, `integration_id`, `api_key_id`.
93
+
94
+ ## Inbound webhooks
95
+
96
+ `integration-routes` (`.framework/functions/integration-routes.ts`) is a
97
+ `createPublicHandler` endpoint — it resolves the caller as an **API-key
98
+ machine principal** (see [auth](./auth-and-api-keys.md)):
99
+
100
+ 1. `?slug=` names the integration; the caller's API key must belong to the
101
+ same account, and keys pinned to `integration_id` or `app_id` can only hit
102
+ those integrations.
103
+ 2. The event name comes from the `x-event-type` header or
104
+ `integration.config.event_path` (a dot-path into the payload).
105
+ 3. `config.handler_name || provider || 'generic-webhook'` is resolved through
106
+ the **webhook registry** (`_shared/webhook-registry.ts`): the
107
+ `webhook_handlers` table maps a handler name to a `function_name`, scoped
108
+ app → account → global fallback. The named module is dynamically imported
109
+ (`custom/…` paths resolve into `custom/`) and must export `adapt`,
110
+ `handler`, or a default function.
111
+ 4. The adapter returns `{ type_slug, data, title?, status? }` — the route then
112
+ creates an `items` record through `admin-data` (full permission/type/audit
113
+ pipeline) and marks the inbound delivery `delivered`. Adapter errors marked
114
+ `retryable` leave it `pending`.
115
+
116
+ Handlers are registered with `registerWebhookHandler`
117
+ (`_shared/webhook-registration.ts`), an upsert on `(app_id, account_id,
118
+ name)`; `deregisterWebhookHandler` soft-deletes (`is_active=false`,
119
+ `is_deleted=true`).
120
+
121
+ ## Notifications
122
+
123
+ `_shared/notify.ts` resolves pipeline `send_notification` recipients: each
124
+ entry is either a person UUID or `role:<slug>`, resolved to active `people`
125
+ rows in the caller's account via the `role_id → roles.slug` join. Anything
126
+ else is skipped with a warning. The resulting ids feed the `notifications`
127
+ insert in the pipeline runner.
@@ -0,0 +1,116 @@
1
+ ---
2
+ title: "Observability: logs, deliveries, executions"
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/logs.ts
9
+ - .framework/functions/observability.ts
10
+ - .framework/functions/deliveries.ts
11
+ - .framework/functions/pipeline-executions.ts
12
+ - .framework/functions/item-progress.ts
13
+ - .framework/functions/_shared/logging.ts
14
+ - .framework/functions/_shared/tracing.ts
15
+ - .framework/functions/_shared/audit.ts
16
+ - .framework/migrations/069_log_stats_window.sql
17
+ ---
18
+
19
+ # Observability: logs, deliveries, executions
20
+
21
+ Everything an operator needs lands in a small set of queryable tables. There is
22
+ no external telemetry service: `public.logs` is the unified sink, and the
23
+ `logs`, `observability`, `deliveries`, and `pipeline-executions` functions are
24
+ the read APIs over it.
25
+
26
+ ## What gets written, and where
27
+
28
+ `public.logs` holds three overlapping kinds of rows:
29
+
30
+ - **Audit/log events** — `emitLog`, `emitAudit`, and `emitActivity` in
31
+ `_shared/audit.ts` insert business events (`trigger.fired`,
32
+ `pipeline.completed`, `integration.outbound.failed_http`,
33
+ `api_key.created`, `registration.completed`, …) with actor, target, and
34
+ details.
35
+ - **Spans** — `emitSpan` writes trace spans (`span_name`, `trace_id`,
36
+ `span_id`, `parent_span_id`, `duration_ms`, `outcome`). `_shared/tracing.ts`
37
+ implements W3C `traceparent` parsing/formatting, and `traceDb` wraps the
38
+ request's DB client so every query emits a `db.<operation>` span — a single
39
+ `trace_id` stitches the request, its queries, and the pipeline stages it
40
+ caused.
41
+ - **Operation logs** — `logAdminDataOperation` in `_shared/logging.ts` writes
42
+ admin-data CRUD records, gated by resolved log levels.
43
+
44
+ Log **levels** are `'off' | 'timing' | 'warnings' | 'errors' | 'all'`, resolved
45
+ per request from `system_settings` rows (`key='logging'`, global plus scoped
46
+ overrides); the default is `['timing', 'errors']`. Under the default, errors
47
+ and timing rows are written; `warnings`-level and `all`-level verbosity require
48
+ opt-in settings. A request's `x-log-level` header can raise verbosity for that
49
+ call.
50
+
51
+ ## Reading logs
52
+
53
+ `logs` (`.framework/functions/logs.ts`) is system-admin oriented (system
54
+ admins query with the service client across accounts; account members get
55
+ their RLS-scoped view):
56
+
57
+ - `action=account` / `action=target` / `action=person` — filtered lists.
58
+ - `action=search` — `ILIKE` message search.
59
+ - `action=stats` — calls the `get_log_stats` RPC (totals, error count,
60
+ distinct levels/sources). **Note:** since migration `069`, when no
61
+ `p_date_from` is passed the window defaults to the **last 7 days** — a call
62
+ that omits the date range returns 7-day stats, not all-time stats.
63
+ - `action=cleanup` — deletes rows older than `days_to_keep` (default 30).
64
+ `system-cron` also runs `cleanup_old_logs` on the 00:00 UTC tick, so
65
+ retention is enforced continuously.
66
+
67
+ `observability` (`.framework/functions/observability.ts`) aggregates the same
68
+ rows into dashboard metrics — actions `event_volume`, `error_rate`,
69
+ `latency_percentiles`, `pipeline_stats`, `top_actors`, `ai_support_metrics`,
70
+ `cleanup` — scoped to the caller's account unless the principal is
71
+ system-admin.
72
+
73
+ ## Executions and deliveries
74
+
75
+ Two sibling tables answer "did it run / did it arrive":
76
+
77
+ - `public.pipeline_executions` — one row per pipeline run with `status`
78
+ (`queued` → `running` → `completed`/`failed`/`cancelled`), per-stage results,
79
+ `error_message`, `duration_ms`, and the `trigger_data` that caused it
80
+ (`.framework/functions/pipeline-executions.ts`; `action=running`,
81
+ `action=stats`, `action=cleanup`). Rows that never reach a terminal state
82
+ are swept to `failed` after 10 minutes.
83
+ - `public.webhook_deliveries` — one row per inbound/outbound webhook attempt
84
+ with `direction`, `status` (`pending`/`processing`/`delivered`/`failed`),
85
+ `attempt_count`, `next_attempt_at`, `last_error`, `response_status`, and
86
+ `response_body` (`.framework/functions/deliveries.ts`; `action=retry`).
87
+
88
+ `item-progress` (`.framework/functions/item-progress.ts`) is a different lens:
89
+ per-person, per-item progress rows (`status`, `score`, `attempts`) that follow
90
+ the type's `design_schema.pipeline` stage list (default
91
+ `not_started → in_progress → completed`). Transitions must move forward
92
+ unless `force: true` is passed; `started_at`/`completed_at` are stamped on
93
+ first entry. Use it when you need per-user completion state rather than
94
+ execution forensics.
95
+
96
+ ## Debugging a failed automation
97
+
98
+ Work backwards through the tables:
99
+
100
+ 1. **`pipeline_executions`** — find the run. `status: 'failed'` +
101
+ `error_message` and per-stage results name the failing stage.
102
+ `status: 'queued'` past 10 minutes = dispatch never arrived (sweeper will
103
+ mark it failed); `status: 'running'` stuck = the runner died (same
104
+ sweeper).
105
+ 2. **`logs`** — search by `trace_id` (from the request) or `span_name`
106
+ (`pipeline.run`, `pipeline.stage.<handler>`, `db.<operation>`); audit rows
107
+ `trigger.fired` / `trigger.failed` / `pipeline.completed` /
108
+ `pipeline.failed` bracket the run. Check log levels if expected `timing`
109
+ rows are absent.
110
+ 3. **`webhook_deliveries`** — for outbound stages/integrations, the delivery
111
+ row carries `response_status`, `response_body`, `last_error`, and
112
+ `next_attempt_at`; retry with `action=retry`.
113
+ 4. **`api_key_usage_logs`** (`api-keys?action=usage-logs`) — whether the
114
+ machine caller ever arrived, and from where.
115
+ 5. **`observability` actions** — confirm the blast radius (`error_rate`,
116
+ `pipeline_stats`, `top_actors`) rather than chasing a single event.