spine-framework 0.4.4 → 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 (283) 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/hash-api-key.ts +183 -79
  15. package/.framework/functions/_shared/logging.ts +562 -224
  16. package/.framework/functions/_shared/middleware.ts +47 -44
  17. package/.framework/functions/admin-data.ts +39 -28
  18. package/.framework/functions/ai-agents.ts +10 -6
  19. package/.framework/functions/api-keys.ts +301 -122
  20. package/.framework/functions/apps.ts +43 -26
  21. package/.framework/functions/cil-ingest.ts +48 -15
  22. package/.framework/functions/debug-auth.ts +14 -8
  23. package/.framework/functions/deliveries.ts +39 -12
  24. package/.framework/functions/embeddings.ts +145 -76
  25. package/.framework/functions/integration-outbound.ts +67 -27
  26. package/.framework/functions/integrations.ts +254 -127
  27. package/.framework/functions/logs.ts +273 -133
  28. package/.framework/functions/observability.ts +277 -147
  29. package/.framework/functions/pipeline-executions.ts +1 -1
  30. package/.framework/functions/pipelines.ts +14 -9
  31. package/.framework/functions/prompt-configs.ts +7 -4
  32. package/.framework/functions/roles.ts +8 -5
  33. package/.framework/functions/security.ts +30 -17
  34. package/.framework/functions/system-cron.ts +240 -127
  35. package/.framework/functions/tests.ts +7 -6
  36. package/.framework/functions/timers.ts +310 -107
  37. package/.framework/functions/triggers.ts +28 -13
  38. package/.framework/functions/types.ts +13 -10
  39. package/.framework/migrations/069_log_stats_window.sql +54 -0
  40. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  41. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  42. package/.framework/migrations/072_slug_autofill.sql +77 -0
  43. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  44. package/.framework/src/apps/admin/index.tsx +1141 -775
  45. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  46. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  47. package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
  48. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  49. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  50. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  51. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  52. package/.framework/src/components/layout/Sidebar.tsx +300 -177
  53. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  54. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  55. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  56. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  57. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  58. package/.framework/src/components/ui/sheet.tsx +24 -26
  59. package/.framework/src/components/ui/textarea.tsx +74 -48
  60. package/.framework/src/hooks/useEntityList.ts +12 -9
  61. package/.framework/src/hooks/useListSchema.ts +108 -58
  62. package/.framework/src/instructions/README.md +57 -0
  63. package/.framework/src/instructions/ai-agents.md +69 -0
  64. package/.framework/src/instructions/alerts.md +80 -0
  65. package/.framework/src/instructions/api-keys.md +87 -0
  66. package/.framework/src/instructions/apps.md +77 -0
  67. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  68. package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
  69. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  70. package/.framework/src/instructions/concepts/automation.md +186 -0
  71. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  72. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  73. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  74. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  75. package/.framework/src/instructions/concepts/observability.md +116 -0
  76. package/.framework/src/instructions/concepts/permissions.md +150 -0
  77. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  78. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  79. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  80. package/.framework/src/instructions/deliveries.md +59 -0
  81. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  82. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  83. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  84. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  85. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  86. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  87. package/.framework/src/instructions/integrations.md +96 -0
  88. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  89. package/.framework/src/instructions/invites.md +60 -0
  90. package/.framework/src/instructions/kb.md +80 -0
  91. package/.framework/src/instructions/logs.md +77 -0
  92. package/.framework/src/instructions/observability.md +61 -0
  93. package/.framework/src/instructions/operations/deployment.md +115 -0
  94. package/.framework/src/instructions/operations/migrations.md +125 -0
  95. package/.framework/src/instructions/operations/security-checklist.md +148 -0
  96. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  97. package/.framework/src/instructions/operations/upgrade.md +116 -0
  98. package/.framework/src/instructions/pipeline-executions.md +62 -0
  99. package/.framework/src/instructions/pipelines.md +78 -0
  100. package/.framework/src/instructions/prompts.md +95 -0
  101. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  102. package/.framework/src/instructions/reference/api/auth.md +113 -0
  103. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  104. package/.framework/src/instructions/reference/app-manifest.md +150 -0
  105. package/.framework/src/instructions/reference/cli.md +124 -0
  106. package/.framework/src/instructions/reference/database.md +132 -0
  107. package/.framework/src/instructions/reference/design-schema.md +280 -0
  108. package/.framework/src/instructions/reference/env-vars.md +133 -0
  109. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  110. package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
  111. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  112. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  113. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  114. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  115. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  116. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  117. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  118. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  119. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  120. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  121. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  122. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  123. package/.framework/src/instructions/reference/functions/index.md +103 -0
  124. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  125. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  126. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  127. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  128. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  129. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  130. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  131. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  132. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  133. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  134. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  135. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  136. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  137. package/.framework/src/instructions/reference/functions/security.md +140 -0
  138. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  139. package/.framework/src/instructions/reference/functions/system.md +106 -0
  140. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  141. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  142. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  143. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  144. package/.framework/src/instructions/reference/functions/types.md +146 -0
  145. package/.framework/src/instructions/reference/seeds.md +126 -0
  146. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  147. package/.framework/src/instructions/reference/spine-config.md +114 -0
  148. package/.framework/src/instructions/roles.md +80 -0
  149. package/.framework/src/instructions/runtime-detail.md +70 -0
  150. package/.framework/src/instructions/runtime-list.md +77 -0
  151. package/.framework/src/instructions/security.md +67 -0
  152. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  153. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  154. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  155. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  156. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  157. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  158. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  159. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  160. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  161. package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
  162. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  163. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  164. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  165. package/.framework/src/instructions/testing.md +59 -0
  166. package/.framework/src/instructions/timers.md +82 -0
  167. package/.framework/src/instructions/triggers.md +69 -0
  168. package/.framework/src/instructions/types.md +93 -0
  169. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  170. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  171. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  172. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  173. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  174. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  175. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  176. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  177. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  178. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  179. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  180. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  181. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  182. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  183. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  184. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  185. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  186. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  187. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  188. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  189. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  190. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  191. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  192. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  193. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  194. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  195. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  196. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  197. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  198. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  199. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  200. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  201. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  202. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  203. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  204. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  205. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  206. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  207. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  208. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  209. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  210. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  211. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  212. package/.framework/templates/agent/AGENTS.md +150 -0
  213. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  214. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  215. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  216. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  217. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
  218. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  219. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
  220. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
  221. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
  222. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  223. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  224. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  225. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  226. package/.framework/templates/project.json +4 -1
  227. package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
  228. package/.framework/tests/unit/agent-files.test.ts +355 -0
  229. package/.framework/tests/unit/agent-runner.test.ts +479 -44
  230. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  231. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  232. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  233. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  234. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  235. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  236. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  237. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  238. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  239. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  240. package/.framework/tests/unit/system-cron.test.ts +240 -0
  241. package/.framework/tests/unit/timers.test.ts +159 -0
  242. package/README.md +125 -166
  243. package/config/tsconfig.cli.json +2 -2
  244. package/package.json +15 -4
  245. package/templates/project/package-lock.json +2 -2
  246. package/.framework/docs/admin-routes.md +0 -76
  247. package/.framework/docs/api-docs-progress.md +0 -38
  248. package/.framework/docs/api-governance.md +0 -146
  249. package/.framework/docs/api-testing-results.md +0 -212
  250. package/.framework/docs/assembly-launch-guide.md +0 -138
  251. package/.framework/docs/audit-results.md +0 -590
  252. package/.framework/docs/db-api-inventory.md +0 -95
  253. package/.framework/docs/gap-remediation-backlog.md +0 -103
  254. package/.framework/docs/ui-adoption-verification.md +0 -111
  255. package/.framework/docs/ui-api-coverage.md +0 -84
  256. package/.framework/docs/v2-compatibility-audit.md +0 -228
  257. package/.framework/mcp/index.ts +0 -133
  258. package/.framework/mcp/load-env.ts +0 -60
  259. package/.framework/mcp/shared/app-manifest.ts +0 -275
  260. package/.framework/mcp/shared/db.ts +0 -477
  261. package/.framework/mcp/shared/kb.ts +0 -118
  262. package/.framework/mcp/tools/architect.ts +0 -431
  263. package/.framework/mcp/tools/cil/context.ts +0 -345
  264. package/.framework/mcp/tools/cil/graph.ts +0 -242
  265. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  266. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  267. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  268. package/.framework/mcp/tools/cil/project.ts +0 -791
  269. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  270. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  271. package/.framework/mcp/tools/cil/search.ts +0 -97
  272. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  273. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  274. package/.framework/mcp/tools/kb/delete.ts +0 -139
  275. package/.framework/mcp/tools/kb/index.ts +0 -52
  276. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  277. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  278. package/.framework/mcp/tools/kb/publish.ts +0 -100
  279. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  280. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  281. package/.framework/mcp/tools/kb/search.ts +0 -101
  282. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  283. package/.framework/mcp/tools/observability.ts +0 -510
@@ -0,0 +1,106 @@
1
+ ---
2
+ title: Add a trigger
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/triggers.ts
9
+ - .framework/functions/_shared/trigger-engine.ts
10
+ - .framework/functions/admin-data.ts
11
+ - .framework/functions/pipeline-runner-background.ts
12
+ - .framework/src/pages/admin/TriggerDetailPage.tsx
13
+ ---
14
+
15
+ # Add a trigger
16
+
17
+ ## Goal
18
+
19
+ Create a `public.triggers` row so a record event (`item_created`,
20
+ `item_updated`, …) dispatches a pipeline execution.
21
+
22
+ ## Prerequisites
23
+
24
+ - The pipeline the trigger will point at ([Add a pipeline](./add-a-pipeline.md)).
25
+ - `INTERNAL_INVOKE_SECRET` set on the deployed functions — dispatch POSTs to
26
+ `pipeline-runner-background` with an `X-Internal-Secret` header and logs
27
+ `cannot dispatch trigger pipeline` (then silently no-ops) when it is
28
+ unset (`.framework/functions/_shared/trigger-engine.ts`). See
29
+ [Environment variables](../reference/env-vars.md).
30
+
31
+ ## Steps
32
+
33
+ 1. **Pick the `event_type`.** Snake_case `<entity>_<verb>` — the engine's
34
+ `EventType` union covers `item`, `account`, `person`, `thread`,
35
+ `message`, `attachment`, and `link` × `created`/`updated`/`deleted`
36
+ (`.framework/functions/_shared/trigger-engine.ts`). Use
37
+ `item_created`, not `item.created` — the admin UI's own placeholder text
38
+ (`e.g., item.created`) is misleading
39
+ (`.framework/src/pages/admin/TriggerDetailPage.tsx`).
40
+
41
+ Scheduled and webhook entry points are **not** trigger event types —
42
+ use a [timer](./add-a-timer.md) or an
43
+ [inbound webhook](./add-a-webhook.md) for those.
44
+
45
+ 2. **Add filter config (optional).** `config.entity_type` scopes the row to
46
+ one table; `config.type_slug` scopes it to one type (resolved from
47
+ `type_id` when the record carries only that);
48
+ `config.filters` supports `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`,
49
+ `$in`, `$nin`, `$exists` on `data->>'field'` paths; and
50
+ `config.fire_on_transition` fires only when the record moves *into* a
51
+ matching state (`.framework/functions/_shared/trigger-engine.ts`).
52
+
53
+ ```json
54
+ {
55
+ "name": "New ticket",
56
+ "event_type": "item_created",
57
+ "pipeline_id": "<pipeline-uuid>",
58
+ "config": { "entity_type": "items", "type_slug": "ticket" }
59
+ }
60
+ ```
61
+
62
+ 3. **Create the trigger.** Admin UI → **Configs → Triggers → New**, or
63
+ `POST /api/triggers` — `name`, `event_type`, and `pipeline_id` required
64
+ (`.framework/functions/triggers.ts`).
65
+
66
+ 4. **Keep it enabled.** `is_active: true` (default). Only active triggers
67
+ are matched on an event; `?action=toggle` flips it
68
+ (`.framework/functions/triggers.ts`). Deleting a trigger is a soft
69
+ delete (`is_active = false`) — execution history survives.
70
+
71
+ ## Verify
72
+
73
+ - Create or update a matching record via `/api/admin-data` — creates and
74
+ updates call `fireCreateTriggers` / `fireUpdateTriggers` after the write
75
+ (`.framework/functions/admin-data.ts`).
76
+ - A `pipeline_executions` row appears in `queued`, then `running` /
77
+ `completed` as `pipeline-runner-background` picks it up — watch
78
+ **Observability → Executions**.
79
+ - The `record_trigger_fire` RPC bumps `triggers.trigger_count` /
80
+ `last_triggered`, and `trigger.fired` / `trigger.failed` audit rows land
81
+ in `public.logs`.
82
+
83
+ ## Common mistakes
84
+
85
+ - **Dotted event names.** `item.created` matches nothing — event names are
86
+ `item_created` etc.
87
+ - **Expecting `item_deleted`.** The enum includes it, but the `admin-data`
88
+ delete path never invokes `fireDeleteTriggers` — only create and update
89
+ fire (`.framework/functions/admin-data.ts`;
90
+ `.framework/functions/_shared/trigger-engine.ts`). Use an update
91
+ (`is_active = false`) if you need delete-like notification.
92
+ - **Missing `INTERNAL_INVOKE_SECRET`.** The trigger inserts the queued
93
+ execution row, but the dispatch to `pipeline-runner-background` cannot be
94
+ authenticated, so nothing runs — check function logs for the
95
+ `INTERNAL_INVOKE_SECRET not set` error.
96
+ - **Filtering on `type_slug` without realizing the resolution.** Records
97
+ carry `type_id`, not the slug; the engine resolves the slug once per
98
+ event when any trigger filters on `config.type_slug`
99
+ (`.framework/functions/_shared/trigger-engine.ts`).
100
+
101
+ ## Reference
102
+
103
+ - [triggers entity page](../triggers.md) · [Automation](../concepts/automation.md)
104
+ - [trigger-scheduler reference](../reference/functions/trigger-scheduler.md) ·
105
+ [pipeline-runner-background reference](../reference/functions/pipeline-runner-background.md)
106
+ - [Add a pipeline](./add-a-pipeline.md) · [Add a timer](./add-a-timer.md)
@@ -0,0 +1,151 @@
1
+ ---
2
+ title: Add a type
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/types.ts
9
+ - .framework/functions/_shared/schema-utils.ts
10
+ - .framework/functions/_shared/permissions.ts
11
+ - .framework/src/pages/admin/TypeDetailPage.tsx
12
+ - .framework/migrations/015_types_kind_slug_unique.sql
13
+ ---
14
+
15
+ # Add a type
16
+
17
+ ## Goal
18
+
19
+ Create a `public.types` row whose `design_schema` drives validation,
20
+ permissions, and the generic list/detail UI for a new record shape. Records
21
+ created through `/api/admin-data` stamp this schema onto themselves at write
22
+ time, so a broken schema propagates into every record.
23
+
24
+ ## Prerequisites
25
+
26
+ - A `system_admin` login or an API key with `*:*` scope — `POST /api/types`
27
+ throws `System admin required` for anyone else
28
+ (`.framework/functions/types.ts`).
29
+ - The target `kind` decided up front (`item`, `thread`, `message`, `link`,
30
+ `attachment`, `watcher`, `person`, `account`). The kind chooses the runtime
31
+ table and cannot be implied later — see
32
+ [Runtime records](../concepts/runtime-records.md).
33
+
34
+ ## Steps
35
+
36
+ 1. **Draft the `design_schema`.** Work through the checklist in
37
+ [Design schema](../concepts/design-schema.md); the field/display enums are
38
+ in [Design schema reference](../reference/design-schema.md). The schema
39
+ below is the minimum for the generic renderer:
40
+
41
+ ```json
42
+ {
43
+ "scope": "account",
44
+ "fields": {
45
+ "title": { "data_type": "text", "label": "Title", "required": true, "system": true },
46
+ "status": { "data_type": "select", "label": "Status", "required": true, "system": true,
47
+ "options": ["open", "closed"] }
48
+ },
49
+ "views": {
50
+ "default_list": {
51
+ "type": "list", "display": "table", "label": "Tickets",
52
+ "fields": {
53
+ "title": { "sortable": true, "display_type": "text" },
54
+ "status": { "sortable": true, "display_type": "badge" },
55
+ "created_at": { "sortable": true, "display_type": "timestamp" }
56
+ },
57
+ "default_sort": { "field": "created_at", "direction": "desc" }
58
+ },
59
+ "default_detail": {
60
+ "type": "detail", "label": "Ticket",
61
+ "sections": [
62
+ { "title": "Core",
63
+ "fields": { "title": { "display_type": "input" },
64
+ "status": { "display_type": "select" } } }
65
+ ]
66
+ }
67
+ },
68
+ "record_permissions": {
69
+ "system_admin": ["create", "read", "update", "delete"],
70
+ "support": ["create", "read", "update"]
71
+ },
72
+ "functionality": null
73
+ }
74
+ ```
75
+
76
+ - `fields`: every entry needs `data_type`, `label`, `required`. `select`,
77
+ `multiselect`, and `radio` need `options` — `generateValidationSchema`
78
+ derives allowed values from them (`.framework/functions/_shared/schema-utils.ts`).
79
+ - `record_permissions` keys are `roles.slug` values matched **verbatim** —
80
+ `system_admin`, not `system-admin`. A misspelled or nonexistent slug
81
+ silently grants nothing (`.framework/functions/_shared/permissions.ts`).
82
+ - **Reserved column names.** `items` has real columns `title`,
83
+ `description`, and `status` — a field with one of those names and
84
+ `system: true` maps onto the column; the same name with `system: false`
85
+ lands in `record.data` and list/detail views show the column's value
86
+ (e.g. lifecycle `active`) instead of yours. Pick `system: true` for
87
+ these names or rename the field.
88
+ - `record_permissions` is the **base, most restrictive** grant; add
89
+ `fields.*.permissions` only to loosen one field or to restrict a
90
+ `system: true` field — see [Permission model](../concepts/permissions.md).
91
+ - Generic apps (`renderer: "generic"`) need `views.default_list` and
92
+ `views.default_detail` referencing real fields with known
93
+ `display_type` values; custom apps can omit views but still need
94
+ `fields` + `record_permissions` so `admin-data` can stamp and validate.
95
+
96
+ 2. **Create the type.** Either:
97
+
98
+ - Admin UI → **Configs → Types → New** (`configs/types/new`), fill in
99
+ name, slug, kind, and paste the schema into the Design Schema JSON
100
+ field — the page POSTs to `/api/types?action=create`
101
+ (`.framework/src/pages/admin/TypeDetailPage.tsx`).
102
+ - API: `POST /api/types` with a body containing `name`, `slug`, `kind`,
103
+ and `design_schema` — all three fields are required
104
+ (`.framework/functions/types.ts`).
105
+
106
+ 3. **Let the API generate `validation_schema`.** Do not send it yourself —
107
+ the create handler replaces a missing `validation_schema` with
108
+ `generateValidationSchema(design_schema)` before insert, and update
109
+ regenerates it whenever `design_schema` changes
110
+ (`.framework/functions/types.ts`).
111
+
112
+ 4. **Set the row-level fields.** `ownership` (`system`/`app`/`tenant`),
113
+ `is_active`, `icon`, `color`, and optional `app_id` are columns on the
114
+ `types` row, not inside `design_schema`.
115
+
116
+ ## Verify
117
+
118
+ - `GET /api/types?action=get&slug=<slug>&include_schema=true` returns the row
119
+ with a non-null `validation_schema`.
120
+ - Create a record: `POST /api/admin-data?action=create` with
121
+ `{ "entity": "items", "type_slug": "<slug>", "title": "…", "data": {…} }`
122
+ — the stored record carries the stamped `design_schema`.
123
+ - If the app uses the generic renderer, open its list page — an unknown
124
+ `display_type` or missing `default_list` view shows up immediately as a
125
+ broken page.
126
+
127
+ ## Common mistakes
128
+
129
+ - **Slug collisions.** Slugs must be unique per `kind`
130
+ (`.framework/migrations/015_types_kind_slug_unique.sql`); create throws
131
+ `Type with slug '<slug>' already exists for kind '<kind>'`. App-install
132
+ seeds go further — `conflict: "kind,slug"` makes type slugs effectively
133
+ global, so prefix app types (`support_ticket`, not `ticket`) — see
134
+ [Seeds](../reference/seeds.md).
135
+ - **`system-admin` vs `system_admin`.** Seeded role slugs use underscores;
136
+ `system-admin` grants nothing.
137
+ - **Editing `data_type` on a live field.** Existing records keep their old
138
+ stored values; the schema change does not migrate data.
139
+ - **Using `generate` to scaffold types.** `npx spine-framework generate`
140
+ emits a stale legacy schema shape — author via the admin UI or `/api/types`
141
+ (see [Build your first app — Gaps](../guide/04-first-app.md)).
142
+
143
+ ## Reference
144
+
145
+ - [Design schema (concept)](../concepts/design-schema.md) ·
146
+ [Design schema reference](../reference/design-schema.md)
147
+ - [Permission model](../concepts/permissions.md) ·
148
+ [Runtime records](../concepts/runtime-records.md)
149
+ - [types entity page](../types.md) ·
150
+ [types function reference](../reference/functions/types.md) ·
151
+ [admin-data function reference](../reference/functions/admin-data.md)
@@ -0,0 +1,108 @@
1
+ ---
2
+ title: Add a webhook
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/integration-routes.ts
9
+ - .framework/functions/_shared/webhook-registry.ts
10
+ - .framework/functions/_shared/webhook-registration.ts
11
+ - .framework/functions/integration-outbound.ts
12
+ - .framework/functions/adapters/generic-webhook-inbound.ts
13
+ - .framework/migrations/004_core_rpc_and_broker.sql
14
+ ---
15
+
16
+ # Add a webhook
17
+
18
+ Two directions, two different mechanisms: **inbound** webhooks arrive at the
19
+ `integration-routes` function and are normalized by a registry adapter into
20
+ `items` records; **outbound** webhooks leave through
21
+ `integration-outbound` (usually from a pipeline stage). See
22
+ [Integrations and webhooks](../concepts/integrations-and-webhooks.md).
23
+
24
+ ## Goal — inbound
25
+
26
+ Receive an external POST and turn it into a record.
27
+
28
+ ### Steps
29
+
30
+ 1. **Create the integration** ([Add an integration](./add-an-integration.md))
31
+ the webhook posts against. Set `config.event_path` if the event name
32
+ lives inside the payload (`"type"` or `"data.event"`), and
33
+ `config.handler_name` to force an adapter — otherwise the adapter name
34
+ falls back to `integration.provider`, then `generic-webhook`
35
+ (`.framework/functions/integration-routes.ts`).
36
+
37
+ 2. **Mint an API key for the sender.** `integration-routes` requires a
38
+ machine principal (`x-api-key`), and the key is authorized per
39
+ `account_id` — optionally pinned to `integration_id` and `app_id`
40
+ (`.framework/functions/integration-routes.ts`). See
41
+ [Auth and API keys](../concepts/auth-and-api-keys.md).
42
+
43
+ 3. **Register or reuse an adapter.** `webhook_handlers` rows map a handler
44
+ name → `function_name`; lookup prefers the app/account-scoped row and
45
+ falls back to a global one, then `loadHandler` dynamically imports the
46
+ module and takes `adapt` → `handler` → `default`
47
+ (`.framework/functions/_shared/webhook-registry.ts`). Migration
48
+ 004 seeds a global `generic-webhook` handler bound to
49
+ `adapters/generic-webhook-inbound`, which stores the raw payload as a
50
+ `webhook_event` item (`.framework/migrations/004_core_rpc_and_broker.sql`).
51
+
52
+ 4. **Adapter contract.** The adapter receives `(payload, ctx)` where `ctx`
53
+ carries `integration`, `apiKey`, `accountId`, `appId`, `event`,
54
+ `headers`, and a `logger`; it returns
55
+ `{ type_slug, data, title?, status?, thread?, links?, external_id?, error?, retryable? }`.
56
+ A truthy `error` marks the delivery `failed` (or `pending` when
57
+ `retryable`); a result with `type_slug` is created through `admin-data`
58
+ as an `items` record (`.framework/functions/integration-routes.ts`).
59
+
60
+ 5. **Post to it.** `POST /api/integration-routes?slug=<integration-slug>`
61
+ with `x-api-key: <key>` and the payload body. The event name comes from
62
+ the `x-event-type` header first, then `config.event_path`
63
+ (`.framework/functions/integration-routes.ts`).
64
+
65
+ ## Goal — outbound
66
+
67
+ Call an external URL from a pipeline.
68
+
69
+ ### Steps
70
+
71
+ 1. Add an `http_request` stage (arbitrary HTTP) or a webhook action whose
72
+ `handler_module: 'webhook'` routes to `executeWebhookHandler` — the
73
+ webhook URL comes from the action's config
74
+ (`.framework/functions/_shared/pipeline-runner.ts`).
75
+ 2. For credentialed or retried sends, go through `integration-outbound`
76
+ instead — see [Add an integration](./add-an-integration.md). Deliveries
77
+ are tracked in `webhook_deliveries` with exponential backoff.
78
+
79
+ ## Verify
80
+
81
+ - Inbound: `webhook_deliveries` shows `direction: 'inbound'` rows moving
82
+ `pending → delivered`; the created item appears via `/api/admin-data`.
83
+ - Outbound: `webhook_deliveries` shows `direction: 'outbound'` rows with
84
+ `response_status`.
85
+
86
+ ## Common mistakes
87
+
88
+ - **Human JWT instead of `x-api-key`** — the route requires a machine
89
+ principal and returns `Machine principal required` (401).
90
+ - **API key pinned to a different integration/app** — `403 API key is not
91
+ authorized`.
92
+ - **`x-event-type` vs `event_path` confusion** — the header wins; if the
93
+ sender can't set headers, put the event inside the payload and set
94
+ `config.event_path`.
95
+ - **Unregistered handler name** — `config.handler_name` (or `provider`)
96
+ that has no `webhook_handlers` row and no global fallback returns
97
+ `Handler not found` (404).
98
+ - **Registering handlers under the wrong scope** — an app- or
99
+ account-scoped `webhook_handlers` row shadows the global fallback for
100
+ that scope only.
101
+
102
+ ## Reference
103
+
104
+ - [integration-routes reference](../reference/functions/integration-routes.md) ·
105
+ [integration-outbound reference](../reference/functions/integration-outbound.md) ·
106
+ [deliveries entity page](../deliveries.md)
107
+ - [Integrations and webhooks](../concepts/integrations-and-webhooks.md) ·
108
+ [Add a pipeline](./add-a-pipeline.md)
@@ -0,0 +1,108 @@
1
+ ---
2
+ title: Add an AI agent
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/ai-agents.ts
9
+ - .framework/functions/_shared/agent-runner.ts
10
+ - .framework/functions/_shared/llm.ts
11
+ - .framework/functions/_shared/pipeline-runner.ts
12
+ - .framework/functions/prompt-configs.ts
13
+ - .framework/functions/integration-outbound.ts
14
+ ---
15
+
16
+ # Add an AI agent
17
+
18
+ ## Goal
19
+
20
+ Create an `ai_agents` row plus a `prompt_configs` row, then run it either
21
+ conversationally (`ai-agents?action=run` on a thread) or inside a pipeline
22
+ (`agent_inference` stage). See
23
+ [AI agents, prompt configs, and embeddings](../concepts/ai-and-prompts.md).
24
+
25
+ ## Steps
26
+
27
+ 1. **Create the prompt config.** Admin UI → **Configs → Prompts → New**, or
28
+ `POST /api/prompt-configs`. Key fields: `system_prompt`,
29
+ `context_template` (substitutes `{{user_message}}` and
30
+ `{{conversation_history}}` only), `model` (default `gpt-4o`),
31
+ `temperature` (default `0.7`), `max_tokens` (default `4000`),
32
+ `is_multi_turn`, `max_history_messages` (default `20`)
33
+ (`.framework/functions/prompt-configs.ts`).
34
+
35
+ 2. **Create the agent.** Admin UI → **Configs → AI Agents → New**, or
36
+ `POST /api/ai-agents` — `name` and `agent_type` required; optional
37
+ `model_config`, `system_prompt`, `tools`, `capabilities`,
38
+ `constraints`, `metadata` (`.framework/functions/ai-agents.ts`).
39
+ Link the prompt config via `metadata.default_prompt_config_id`.
40
+
41
+ 3. **Configure the LLM integration.** Inference routes through
42
+ `integration-outbound` against the integration slug `openai` with
43
+ `credential_field: 'api_token'` (`.framework/functions/_shared/llm.ts`).
44
+ Migration 004 seeds `openai` with
45
+ `api_token: { source: 'env_var', value: 'OPENAI_API_KEY' }` — set that env
46
+ var or update the credential. Without it the runner returns a
47
+ `[Mock Response]` string instead of calling the model
48
+ (`.framework/functions/_shared/agent-runner.ts`).
49
+
50
+ 4. **Conversational path — `ai-agents?action=run`.**
51
+ `POST /api/ai-agents?action=run` with `{ thread_id, user_message, mode }`;
52
+ `mode` is `step` | `complete` (default) | `episode`. The runner resolves
53
+ the agent from `thread.data.agent_id` → `design_schema.default_agent_id`,
54
+ and the prompt config from `thread.data.prompt_config_id` →
55
+ `agent.metadata.default_prompt_config_id` — both must be `is_active`
56
+ (`.framework/functions/_shared/agent-runner.ts`). A
57
+ prompt-injection preflight reads `config.security.prompt_injection`
58
+ (`off` | `flag` | `block`) on the agent
59
+ (`.framework/functions/ai-agents.ts`).
60
+
61
+ 5. **Pipeline path — `agent_inference` stage.** Add a stage with
62
+ `stage_type: 'agent_inference'` and `config.context` (required;
63
+ `{{trigger.*}}`/`{{record.*}}` templates resolve). With a
64
+ `config.webhook_url` the stage POSTs `{ context, model, temperature,
65
+ max_tokens }` to that URL via the `generic-http` integration; without
66
+ one it returns a mock response —
67
+ `[Mock Response] Received N chars of context`
68
+ (`.framework/functions/_shared/pipeline-runner.ts`).
69
+
70
+ ## Verify
71
+
72
+ - `POST /api/ai-agents?action=run` on a thread whose `data.agent_id` points
73
+ at the agent — returns a completion (or the mock string when the OpenAI
74
+ credential is missing).
75
+ - `agent_step` rate-limit debits and injection audits appear in
76
+ `public.logs`.
77
+ - The `agent_inference` stage output appears in the execution's stage
78
+ results (**Observability → Executions**).
79
+
80
+ ## Common mistakes
81
+
82
+ - **`threads.target_type` is `NOT NULL`.** A chat thread must be bound to a
83
+ target record — there is no unbound "conversation" row. Point it at
84
+ something stable such as `target_type: 'account'` +
85
+ `target_id: <account id>` and keep `data.agent_id` /
86
+ `data.prompt_config_id` on the thread.
87
+ - **No `default_prompt_config_id` on the agent and no
88
+ `prompt_config_id` on the thread** — the run has no prompt config to
89
+ assemble from; always set the agent fallback.
90
+ - **Missing `OPENAI_API_KEY` / unconfigured `openai` integration** — you
91
+ get a mock response, not an error; check the reply text for
92
+ `[Mock Response]`.
93
+ - **`agent_inference` with no `webhook_url`** — returns a mock. This stage
94
+ is an HTTP bridge, not the in-process `runAgent` path.
95
+ - **Agent or prompt config `is_active: false`** — resolution fails before
96
+ inference.
97
+ - **`context_template` with unsupported placeholders** — only
98
+ `{{user_message}}` and `{{conversation_history}}` substitute; anything
99
+ else stays literal (`.framework/functions/_shared/agent-runner.ts`).
100
+
101
+ ## Reference
102
+
103
+ - [ai-agents entity page](../ai-agents.md) ·
104
+ [ai-agents function reference](../reference/functions/ai-agents.md) ·
105
+ [prompt-configs function reference](../reference/functions/prompt-configs.md) ·
106
+ [prompts entity page](../prompts.md)
107
+ - [AI agents, prompt configs, and embeddings](../concepts/ai-and-prompts.md) ·
108
+ [Add a pipeline](./add-a-pipeline.md) · [Add an integration](./add-an-integration.md)
@@ -0,0 +1,124 @@
1
+ ---
2
+ title: Add an integration
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/integrations.ts
9
+ - .framework/functions/integration-outbound.ts
10
+ - .framework/functions/_shared/resolve-credentials.ts
11
+ - .framework/functions/_shared/permissions.ts
12
+ - .framework/migrations/004_core_rpc_and_broker.sql
13
+ ---
14
+
15
+ # Add an integration
16
+
17
+ ## Goal
18
+
19
+ Create a `public.integrations` row — the config + credentials handle for
20
+ outbound HTTP calls (`integration-outbound`) and inbound webhook routing
21
+ (`integration-routes`). See
22
+ [Integrations and webhooks](../concepts/integrations-and-webhooks.md) for
23
+ the full model.
24
+
25
+ ## Prerequisites
26
+
27
+ - A `system_admin` login — the integrations function enforces it
28
+ (`.framework/functions/integrations.ts`, `isSystemAdmin` signal).
29
+
30
+ ## Steps
31
+
32
+ 1. **Choose the integration shape.**
33
+
34
+ - `integration_type`: semantic category — `webhook`, `oauth`, `api`,
35
+ `scheduled_sync` (column comment,
36
+ `.framework/migrations/004_core_rpc_and_broker.sql`).
37
+ - `provider`: the vendor name — `stripe`, `openai`, `generic`, …
38
+ (used as the fallback inbound adapter name).
39
+ - `slug`: unique per `(app_id, account_id)` —
40
+ `integrations_app_account_slug_key`
41
+ (`.framework/migrations/004_core_rpc_and_broker.sql`).
42
+
43
+ 2. **Configure `config` and `credentials`.**
44
+
45
+ - `config.outbound_adapter` selects the adapter module for outbound
46
+ calls — built-ins: `adapters/openai-outbound`,
47
+ `adapters/generic-webhook-outbound`, `adapters/resend-outbound`
48
+ (`.framework/functions/integration-outbound.ts`). `action_config.adapter`
49
+ can override it per call.
50
+ - `credentials` is a map `{ field: { source, value } }`:
51
+ `source: "value"` stores a literal secret; `source: "env_var"` resolves
52
+ `value` as a `process.env` name server-side at call time
53
+ (`.framework/functions/_shared/resolve-credentials.ts`).
54
+
55
+ ```json
56
+ {
57
+ "slug": "acme-api", "name": "Acme API",
58
+ "integration_type": "api", "provider": "acme",
59
+ "config": {
60
+ "base_url": "https://api.acme.test",
61
+ "outbound_adapter": "adapters/generic-webhook-outbound"
62
+ },
63
+ "credentials": {
64
+ "api_token": { "source": "env_var", "value": "ACME_API_TOKEN" }
65
+ }
66
+ }
67
+ ```
68
+
69
+ `is_configured` is recomputed on every save — `true` only when every
70
+ credential entry is present and non-empty
71
+ (`.framework/functions/integrations.ts`).
72
+
73
+ 3. **Create the integration.** Admin UI → **Configs → Integrations → New**,
74
+ or `POST /api/integrations` — `name`, `integration_type`, and `provider`
75
+ required (`.framework/functions/integrations.ts`).
76
+
77
+ 4. **Call it outbound.** `POST /api/integration-outbound` (or a pipeline
78
+ `http_request`/webhook stage) with `{ integration_slug, action_config }`.
79
+ `action_config` carries `endpoint`/`url`, `body`, `credential_field`
80
+ (default `api_token`), `action_slug`, `max_attempts`, and optional
81
+ `adapter` (`.framework/functions/integration-outbound.ts`).
82
+ Deliveries land in `webhook_deliveries` with retry bookkeeping — see
83
+ [integration-outbound reference](../reference/functions/integration-outbound.md).
84
+
85
+ 5. **For inbound**, point senders at `integration-routes` and pick an
86
+ adapter — see [Add a webhook](./add-a-webhook.md).
87
+
88
+ ## Verify
89
+
90
+ - `GET /api/integrations?action=list` — `is_configured: true` once every
91
+ credential resolves.
92
+ - A `webhook_deliveries` row is written per outbound call with
93
+ `response_status` and retry fields.
94
+
95
+ ## Common mistakes
96
+
97
+ - **An empty or absent credential means "no auth", not an error.**
98
+ `execute` skips resolution for an absent entry or an empty
99
+ `source: "value"` literal and the call goes out without a credential —
100
+ the stock `generic-http` seed (`{"api_token":{"source":"value","value":""}}`,
101
+ `.framework/migrations/004_core_rpc_and_broker.sql`) therefore works
102
+ unauthenticated out of the box. Whether a credential is required is the
103
+ *adapter's* call: `generic-webhook-outbound` only attaches
104
+ `Authorization: Bearer` when a credential resolved, while
105
+ `openai-outbound`/`resend-outbound` throw in `buildRequest`. What still
106
+ fails hard: an `env_var` entry naming an unset variable
107
+ (`credential_env_missing`).
108
+ - **Expecting credentials in reads.** Integrations are a protected
109
+ second-surface table — non-system-admin/machine reads are sanitized to
110
+ minimal fields; secrets are only resolved server-side
111
+ (`.framework/functions/_shared/permissions.ts`).
112
+ - **`env_var` pointing at a missing env** — `resolveCredential` throws
113
+ before the request is sent and emits `integration.credential_*` audit
114
+ rows.
115
+ - **Slug not unique within `(app_id, account_id)`** — the DB unique
116
+ constraint rejects it; account-scoped and global rows share the key.
117
+
118
+ ## Reference
119
+
120
+ - [integrations entity page](../integrations.md) ·
121
+ [integrations function reference](../reference/functions/integrations.md) ·
122
+ [integration-outbound reference](../reference/functions/integration-outbound.md) ·
123
+ [integration-routes reference](../reference/functions/integration-routes.md)
124
+ - [Integrations and webhooks](../concepts/integrations-and-webhooks.md)