spine-framework 0.4.4 → 0.5.0-rc.5

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 (305) 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/install-app.ts +183 -13
  8. package/.framework/cli/commands/migrate.ts +36 -30
  9. package/.framework/cli/commands/migrations.ts +16 -56
  10. package/.framework/cli/commands/upgrade.ts +35 -0
  11. package/.framework/cli/commands/validate-app.ts +127 -1
  12. package/.framework/cli/commands/validate-schema.ts +147 -0
  13. package/.framework/cli/context.ts +223 -110
  14. package/.framework/cli/index.ts +6 -0
  15. package/.framework/cli/lib/project-files.ts +241 -1
  16. package/.framework/functions/_shared/app-manifest.ts +2 -0
  17. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  18. package/.framework/functions/_shared/logging.ts +562 -224
  19. package/.framework/functions/_shared/middleware.ts +47 -44
  20. package/.framework/functions/_shared/permissions.ts +4 -0
  21. package/.framework/functions/admin-data.ts +51 -28
  22. package/.framework/functions/ai-agents.ts +10 -6
  23. package/.framework/functions/api-keys.ts +301 -122
  24. package/.framework/functions/apps.ts +63 -29
  25. package/.framework/functions/cil-ingest.ts +48 -15
  26. package/.framework/functions/debug-auth.ts +14 -8
  27. package/.framework/functions/deliveries.ts +39 -12
  28. package/.framework/functions/embeddings.ts +149 -78
  29. package/.framework/functions/integration-outbound.ts +67 -27
  30. package/.framework/functions/integrations.ts +254 -127
  31. package/.framework/functions/logs.ts +273 -133
  32. package/.framework/functions/observability.ts +278 -148
  33. package/.framework/functions/pipeline-executions.ts +1 -1
  34. package/.framework/functions/pipelines.ts +14 -9
  35. package/.framework/functions/prompt-configs.ts +7 -4
  36. package/.framework/functions/roles.ts +8 -5
  37. package/.framework/functions/security.ts +30 -17
  38. package/.framework/functions/system-cron.ts +240 -127
  39. package/.framework/functions/tests.ts +7 -6
  40. package/.framework/functions/timers.ts +310 -107
  41. package/.framework/functions/triggers.ts +28 -13
  42. package/.framework/functions/types.ts +13 -10
  43. package/.framework/migrations/069_log_stats_window.sql +54 -0
  44. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  45. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  46. package/.framework/migrations/072_slug_autofill.sql +77 -0
  47. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  48. package/.framework/migrations/074_app_route_prefix_domains.sql +168 -0
  49. package/.framework/migrations/075_security_definer_grants.sql +157 -0
  50. package/.framework/src/apps/admin/index.tsx +1141 -775
  51. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  52. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  53. package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
  54. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  55. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  56. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  57. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  58. package/.framework/src/components/layout/Sidebar.tsx +300 -177
  59. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  60. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  61. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  62. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  63. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  64. package/.framework/src/components/ui/sheet.tsx +24 -26
  65. package/.framework/src/components/ui/textarea.tsx +74 -48
  66. package/.framework/src/contexts/AppContext.tsx +5 -12
  67. package/.framework/src/contexts/AuthContext.tsx +17 -9
  68. package/.framework/src/hooks/useApps.ts +18 -22
  69. package/.framework/src/hooks/useEntityList.ts +12 -9
  70. package/.framework/src/hooks/useListSchema.ts +108 -58
  71. package/.framework/src/instructions/README.md +74 -0
  72. package/.framework/src/instructions/accounts.md +97 -0
  73. package/.framework/src/instructions/ai-agents.md +69 -0
  74. package/.framework/src/instructions/alerts.md +80 -0
  75. package/.framework/src/instructions/api-keys.md +87 -0
  76. package/.framework/src/instructions/apps.md +77 -0
  77. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  78. package/.framework/src/instructions/concepts/apps-and-renderers.md +228 -0
  79. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  80. package/.framework/src/instructions/concepts/automation.md +186 -0
  81. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  82. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  83. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  84. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  85. package/.framework/src/instructions/concepts/observability.md +116 -0
  86. package/.framework/src/instructions/concepts/permissions.md +150 -0
  87. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  88. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  89. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  90. package/.framework/src/instructions/deliveries.md +59 -0
  91. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  92. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  93. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  94. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  95. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  96. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  97. package/.framework/src/instructions/integrations.md +96 -0
  98. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  99. package/.framework/src/instructions/internal/docs-contract.md +158 -0
  100. package/.framework/src/instructions/invites.md +60 -0
  101. package/.framework/src/instructions/kb.md +80 -0
  102. package/.framework/src/instructions/logs.md +77 -0
  103. package/.framework/src/instructions/notifications.md +77 -0
  104. package/.framework/src/instructions/observability.md +61 -0
  105. package/.framework/src/instructions/operations/deployment.md +115 -0
  106. package/.framework/src/instructions/operations/migrations.md +125 -0
  107. package/.framework/src/instructions/operations/security-checklist.md +170 -0
  108. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  109. package/.framework/src/instructions/operations/upgrade.md +116 -0
  110. package/.framework/src/instructions/people.md +91 -0
  111. package/.framework/src/instructions/pipeline-executions.md +62 -0
  112. package/.framework/src/instructions/pipelines.md +78 -0
  113. package/.framework/src/instructions/prompts.md +95 -0
  114. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  115. package/.framework/src/instructions/reference/api/auth.md +113 -0
  116. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  117. package/.framework/src/instructions/reference/app-manifest.md +184 -0
  118. package/.framework/src/instructions/reference/cli.md +124 -0
  119. package/.framework/src/instructions/reference/database.md +132 -0
  120. package/.framework/src/instructions/reference/design-schema.md +280 -0
  121. package/.framework/src/instructions/reference/env-vars.md +133 -0
  122. package/.framework/src/instructions/reference/frontend-sdk.md +207 -0
  123. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  124. package/.framework/src/instructions/reference/functions/admin-data.md +197 -0
  125. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  126. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  127. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  128. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  129. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  130. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  131. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  132. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  133. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  134. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  135. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  136. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  137. package/.framework/src/instructions/reference/functions/index.md +103 -0
  138. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  139. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  140. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  141. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  142. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  143. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  144. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  145. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  146. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  147. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  148. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  149. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  150. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  151. package/.framework/src/instructions/reference/functions/security.md +140 -0
  152. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  153. package/.framework/src/instructions/reference/functions/system.md +106 -0
  154. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  155. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  156. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  157. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  158. package/.framework/src/instructions/reference/functions/types.md +146 -0
  159. package/.framework/src/instructions/reference/seeds.md +126 -0
  160. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  161. package/.framework/src/instructions/reference/spine-config.md +114 -0
  162. package/.framework/src/instructions/roles.md +80 -0
  163. package/.framework/src/instructions/runtime-collaboration.md +106 -0
  164. package/.framework/src/instructions/runtime-detail.md +70 -0
  165. package/.framework/src/instructions/runtime-list.md +77 -0
  166. package/.framework/src/instructions/security.md +67 -0
  167. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  168. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  169. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  170. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  171. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  172. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  173. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  174. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  175. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  176. package/.framework/src/instructions/tasks/document-your-app.md +117 -0
  177. package/.framework/src/instructions/tasks/package-and-install-an-app.md +136 -0
  178. package/.framework/src/instructions/tasks/register-an-app.md +137 -0
  179. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  180. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  181. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  182. package/.framework/src/instructions/testing.md +59 -0
  183. package/.framework/src/instructions/timers.md +84 -0
  184. package/.framework/src/instructions/triggers.md +69 -0
  185. package/.framework/src/instructions/types.md +93 -0
  186. package/.framework/src/lib/app-mount.ts +88 -0
  187. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  188. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  189. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  190. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  191. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  192. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  193. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  194. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  195. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  196. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  197. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  198. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  199. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  200. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  201. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  202. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  203. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  204. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  205. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  206. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  207. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  208. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  209. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  210. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  211. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  212. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  213. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  214. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  215. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  216. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  217. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  218. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  219. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  220. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  221. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  222. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  223. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  224. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  225. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  226. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  227. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  228. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  229. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  230. package/.framework/src/pages/auth/LoginPage.tsx +3 -2
  231. package/.framework/src/pages/auth/RegisterPage.tsx +16 -8
  232. package/.framework/templates/agent/AGENTS.md +167 -0
  233. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  234. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  235. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  236. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  237. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +79 -0
  238. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  239. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +79 -0
  240. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +71 -0
  241. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +72 -0
  242. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  243. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  244. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  245. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  246. package/.framework/templates/project.json +4 -1
  247. package/.framework/tests/unit/account-scoping-gates.test.ts +394 -0
  248. package/.framework/tests/unit/agent-files.test.ts +355 -0
  249. package/.framework/tests/unit/agent-runner.test.ts +479 -44
  250. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  251. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  252. package/.framework/tests/unit/app-mount.test.ts +80 -0
  253. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  254. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  255. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  256. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  257. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  258. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  259. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  260. package/.framework/tests/unit/security-definer-grants.test.ts +154 -0
  261. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  262. package/.framework/tests/unit/system-cron.test.ts +240 -0
  263. package/.framework/tests/unit/timers.test.ts +159 -0
  264. package/README.md +125 -166
  265. package/config/tsconfig.cli.json +2 -2
  266. package/package.json +15 -4
  267. package/templates/project/package-lock.json +2 -2
  268. package/.framework/docs/admin-routes.md +0 -76
  269. package/.framework/docs/api-docs-progress.md +0 -38
  270. package/.framework/docs/api-governance.md +0 -146
  271. package/.framework/docs/api-testing-results.md +0 -212
  272. package/.framework/docs/assembly-launch-guide.md +0 -138
  273. package/.framework/docs/audit-results.md +0 -590
  274. package/.framework/docs/db-api-inventory.md +0 -95
  275. package/.framework/docs/gap-remediation-backlog.md +0 -103
  276. package/.framework/docs/ui-adoption-verification.md +0 -111
  277. package/.framework/docs/ui-api-coverage.md +0 -84
  278. package/.framework/docs/v2-compatibility-audit.md +0 -228
  279. package/.framework/mcp/index.ts +0 -133
  280. package/.framework/mcp/load-env.ts +0 -60
  281. package/.framework/mcp/shared/app-manifest.ts +0 -275
  282. package/.framework/mcp/shared/db.ts +0 -477
  283. package/.framework/mcp/shared/kb.ts +0 -118
  284. package/.framework/mcp/tools/architect.ts +0 -431
  285. package/.framework/mcp/tools/cil/context.ts +0 -345
  286. package/.framework/mcp/tools/cil/graph.ts +0 -242
  287. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  288. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  289. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  290. package/.framework/mcp/tools/cil/project.ts +0 -791
  291. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  292. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  293. package/.framework/mcp/tools/cil/search.ts +0 -97
  294. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  295. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  296. package/.framework/mcp/tools/kb/delete.ts +0 -139
  297. package/.framework/mcp/tools/kb/index.ts +0 -52
  298. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  299. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  300. package/.framework/mcp/tools/kb/publish.ts +0 -100
  301. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  302. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  303. package/.framework/mcp/tools/kb/search.ts +0 -101
  304. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  305. package/.framework/mcp/tools/observability.ts +0 -510
@@ -0,0 +1,108 @@
1
+ ---
2
+ title: Test your code
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - vitest.config.ts
9
+ - .framework/tests/setup.ts
10
+ - .framework/tests/reporter.ts
11
+ - package.json
12
+ - config/tsconfig.json
13
+ - config/tsconfig.functions.json
14
+ ---
15
+
16
+ # Test your code
17
+
18
+ ## Goal
19
+
20
+ Run the Vitest suite and the TypeScript checks, and place new tests where
21
+ the runner picks them up.
22
+
23
+ ## Steps
24
+
25
+ 1. **Know the runners** (`package.json` scripts):
26
+
27
+ - `npm run test` → `vitest run` — everything matching the `include`
28
+ globs in `vitest.config.ts`: `.framework/tests/unit/**/*.test.ts`,
29
+ `.framework/tests/api/**/*.test.ts`, `.framework/tests/integration/**/*.test.ts`,
30
+ and `custom/tests/**/*.test.ts`.
31
+ - `npm run test:unit` — the unit suite only.
32
+ - `npm run test:integration` — the integration suite (needs real env,
33
+ see below).
34
+ - `npm run test:core` — the core-isolation suite.
35
+ - `npm run typecheck` → `tsc -b config/tsconfig.json` — frontend +
36
+ CLI. Functions are **not** covered here: check them separately with
37
+ `npx tsc --noEmit -p config/tsconfig.functions.json` after
38
+ `npm run assemble` (it typechecks the assembled layout).
39
+ - `npx spine-framework test [suite]` — the CLI wrapper
40
+ (`--watch`, `--reporter`, `--json`; see [CLI reference](../reference/cli.md)).
41
+
42
+ 2. **Put tests where the globs look.** New project tests belong in
43
+ `custom/tests/**/*.test.ts` — picked up automatically, and they keep
44
+ `.framework/` untouched. Framework-level additions go under
45
+ `.framework/tests/unit/`. Test files inside `custom/functions/` are
46
+ excluded from function assembly (`*-test.ts`, `*.test.ts` are skipped
47
+ by `scripts/assemble-functions.sh`), but naming convention is still
48
+ `*.test.ts`.
49
+
50
+ 3. **Understand the env layering.** `vitest.config.ts` loads a root
51
+ `.env.test` file (if present) into `process.env` **before** any module
52
+ graph runs — that is how integration/API tests get real Supabase
53
+ credentials before `db.ts`/`principal.ts` call `createClient` at import
54
+ time. `.framework/tests/setup.ts` then stubs `SUPABASE_URL`,
55
+ `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY` so unit tests never
56
+ crash on missing env. Integration helpers additionally require
57
+ `SPINE_TEST_ACCOUNT_ID`; API-surface tests use `SPINE_DEV_URL`,
58
+ `SPINE_DEV_JWT`, `SPINE_TEST_API_KEY` (see
59
+ [Environment variables](../reference/env-vars.md)).
60
+
61
+ 4. **Mock at module boundaries.** The config sets `isolate: true` — every
62
+ test file gets a fresh module registry, so `vi.mock` on
63
+ `./_shared/db`-style imports works per file. Existing unit suites mock
64
+ the Supabase client, principal resolution, and the LLM/embedding calls
65
+ rather than hitting the network (browse `.framework/tests/unit/` for
66
+ patterns — `pipeline-runner.test.ts`, `permissions.test.ts`,
67
+ `principal-cache.test.ts`).
68
+
69
+ 5. **Integration tests need a real project.** They exercise admin-data,
70
+ triggers, pipelines, and schema validation against a live Supabase
71
+ project using a service-role client and a seeded test account —
72
+ `custom/tests` and the unit suite run with no credentials, the
73
+ integration suite does not. `passWithNoTests: true` means a missing
74
+ env never fails the suite outright, so check that tests actually ran.
75
+
76
+ ## Verify
77
+
78
+ - `npm run test` exits 0; `vitest` prints per-file pass counts. A custom
79
+ reporter that persists runs to `public.test_runs` exists at
80
+ `.framework/tests/reporter.ts` but is **not** registered — the config
81
+ uses `reporters: ['default']`, so opt in with `--reporter` if you want
82
+ persisted runs.
83
+ - `npm run typecheck` and
84
+ `npx tsc --noEmit -p config/tsconfig.functions.json` are both clean.
85
+ - `npx spine-framework doctor` sanity-checks the environment when tests
86
+ behave oddly ([CLI reference](../reference/cli.md)).
87
+
88
+ ## Common mistakes
89
+
90
+ - **`typecheck` doesn't cover functions.** `config/tsconfig.json` excludes
91
+ the functions project by design — always run the
92
+ `tsconfig.functions.json` check too, and only after `npm run assemble`.
93
+ - **Assuming integration tests ran.** With no `.env.test`, integration
94
+ files that can't build a client may skip silently — grep the output for
95
+ your file's pass count.
96
+ - **Importing `createClient` at module top-level in a unit test.** Setup
97
+ stubs the env, but a module that builds a client at import time before
98
+ `setupFiles` runs still fails — mock the module or import lazily.
99
+ - **Writing repo-only paths into `custom/tests`** — tests there must run
100
+ in an installed project too; import from `custom/` and assembled
101
+ aliases, not from repo internals.
102
+
103
+ ## Reference
104
+
105
+ - [CLI reference](../reference/cli.md) — `test`, `doctor`
106
+ - [Environment variables](../reference/env-vars.md) — `SPINE_TEST_*`,
107
+ `SPINE_DEV_*`, `SUPABASE_*`
108
+ - [Write a function](./write-a-function.md) — assembled-layout imports
@@ -0,0 +1,132 @@
1
+ ---
2
+ title: Write a function
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/_shared/middleware.ts
9
+ - .framework/functions/_shared/db.ts
10
+ - .framework/functions/_shared/permissions.ts
11
+ - .framework/functions/_shared/logging.ts
12
+ - .framework/functions/timers.ts
13
+ - .framework/public/_redirects
14
+ - scripts/assemble-functions.sh
15
+ ---
16
+
17
+ # Write a function
18
+
19
+ ## Goal
20
+
21
+ Add a custom Netlify function when — and only when — the generic
22
+ `/api/admin-data` endpoint cannot cover the case: specialized auth, inbound
23
+ webhooks, heavy compute, or bespoke orchestration. Record CRUD must go
24
+ through `admin-data` so permission, validation, audit, and trigger logic
25
+ stay shared ([Runtime records](../concepts/runtime-records.md)).
26
+
27
+ ## Steps
28
+
29
+ 1. **Decide it is warranted.** `admin-data` already covers list/get/create/
30
+ update/remove/stats across every runtime table with permissions and
31
+ triggers enforced ([admin-data reference](../reference/functions/admin-data.md)).
32
+ Legitimate custom functions: public webhook receivers, scheduled
33
+ sweeps, compute-heavy transforms, auth-bridging endpoints.
34
+
35
+ 2. **Create the file.** Put it in `custom/functions/` (or
36
+ `custom/apps/<slug>/functions/` for app-scoped code) with a `custom_`
37
+ prefix — assembly copies `.framework/functions`, `custom/functions`,
38
+ and `custom/apps/*/functions` into one flat Netlify dir and **fails the
39
+ build on a basename collision**
40
+ (`scripts/assemble-functions.sh`). All functions are reachable at
41
+ `/api/<basename>` via the `_redirects` rule
42
+ (`.framework/public/_redirects`).
43
+
44
+ Imports resolve in the **assembled** layout, not the source tree:
45
+ `./_shared/*` and `./<other-function>` are sibling paths inside
46
+ `.assembled/netlify/functions/`, and `@shared/*` is an alias to the
47
+ same dir (`config/tsconfig.functions.json`). A function's own
48
+ `functions/_shared -> .framework/functions/_shared` symlink (as the
49
+ existing app functions dirs carry) makes `./_shared` resolve in the
50
+ source tree too.
51
+
52
+ ```ts
53
+ // custom/functions/custom_my-endpoint.ts
54
+ import { createHandler, error, json } from './_shared/middleware'
55
+ import { adminDb } from './_shared/db'
56
+ import { PermissionEngine } from './_shared/permissions'
57
+ import { emitLog } from './_shared/logging'
58
+
59
+ export const handler = createHandler(async (ctx, body) => {
60
+ // ctx.requestId, ctx.principal, ctx.db, ctx.accountId, ctx.appId,
61
+ // ctx.query, ctx.headers, ctx.trace — all populated by middleware
62
+ if (!PermissionEngine.isSystemAdmin(ctx)) {
63
+ throw new Error('System admin required')
64
+ }
65
+ const { data, error: err } = await ctx.db
66
+ .from('items').select('id,title').eq('is_active', true).limit(10)
67
+ if (err) throw err
68
+ return data
69
+ })
70
+ ```
71
+
72
+ 3. **Follow the `createHandler` contract** (`.framework/functions/_shared/middleware.ts`):
73
+
74
+ - The middleware resolves the principal (`x-api-key` → internal secret →
75
+ Bearer JWT), builds `RequestContext`, and wraps your return value in
76
+ the `{ data, error, meta }` envelope. Thrown `Error`s become
77
+ `{ error }` responses — don't build response objects yourself.
78
+ - `ctx.db` is the principal-scoped client (humans get a user-scoped,
79
+ RLS-bound client; machines/anonymous get `adminDb`).
80
+ - `ctx.query` merges `queryStringParameters`; `ctx.query.method` carries
81
+ the HTTP method. Functions dispatching on `?action=` use a
82
+ `switch (ctx.query.action)` (see `.framework/functions/timers.ts`).
83
+ - For endpoints that must accept anonymous or raw requests, use
84
+ `createPublicHandler` instead (same file, `createPublicHandler`).
85
+
86
+ 4. **Use the `_shared` modules, don't reinvent them** — `db` (`adminDb`,
87
+ `getUserDb`), `permissions` (`PermissionEngine`, `isSystemAdmin`,
88
+ `sanitizeRecordData`), `principal` (`resolvePrincipal`,
89
+ `SYSTEM_PRINCIPAL`), `audit`/`logging` (`emitLog`, `emitAudit`),
90
+ `resolve-ids`, `rate-limit` — see
91
+ [Shared modules reference](../reference/shared-modules.md) and
92
+ [Functions index](../reference/functions/index.md).
93
+
94
+ 5. **Assemble and run.** `npm run assemble && npm run dev:netlify`, then
95
+ `curl http://localhost:8888/api/custom_my-endpoint` — see
96
+ [Install and first run](../guide/01-install-and-first-run.md).
97
+ Function typecheck is separate from `npm run typecheck`:
98
+ `npx tsc --noEmit -p config/tsconfig.functions.json` checks the
99
+ assembled output (run it after `assemble`).
100
+
101
+ ## Verify
102
+
103
+ - `GET /api/custom_my-endpoint` returns the `{ data, error, meta }`
104
+ envelope; unauthenticated calls return `401 Authentication required`
105
+ from the middleware, not from your code.
106
+ - `npm run assemble` passes (no basename collision error) and
107
+ `npm run typecheck` compiles.
108
+
109
+ ## Common mistakes
110
+
111
+ - **Duplicating a framework basename.** Assembly hard-fails naming both
112
+ sources — prefix custom files `custom_`.
113
+ - **Returning raw Response objects.** `createHandler` expects a return
114
+ value (serialized into `data`) or a thrown error — don't emit Netlify
115
+ `{statusCode, body}` shapes yourself inside a `createHandler` function.
116
+ - **Using `adminDb` for user-facing reads.** It bypasses RLS — prefer
117
+ `ctx.db` unless you intentionally need service-role access.
118
+ - **Putting secrets in the response.** `sanitizeRecordData` /
119
+ `omitSecretFields` exist for a reason — strip credentials before
120
+ returning.
121
+ - **Writing record CRUD** — if the function creates/updates `items`,
122
+ `threads`, etc., you are bypassing `admin-data`; call it via
123
+ `adminDataCreate`/`/api/admin-data` instead.
124
+
125
+ ## Reference
126
+
127
+ - [Shared modules reference](../reference/shared-modules.md) ·
128
+ [Functions index](../reference/functions/index.md) ·
129
+ [Auth and API keys](../concepts/auth-and-api-keys.md)
130
+ - [admin-data reference](../reference/functions/admin-data.md) ·
131
+ [Build a custom page](./build-a-custom-page.md) ·
132
+ [Anatomy of an installed project](../guide/02-project-map.md)
@@ -0,0 +1,110 @@
1
+ ---
2
+ title: Write a migration
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/cli/commands/migrate.ts
9
+ - .framework/migrations/068_cron_schedule_timer_rpcs.sql
10
+ - .framework/migrations/053_builtin_action_seeds.sql
11
+ ---
12
+
13
+ # Write a migration
14
+
15
+ ## Goal
16
+
17
+ Add a `.sql` file that the `migrate` command applies exactly once, in order,
18
+ tracked in the `_spine_migrations` ledger table. See
19
+ [Database reference](../reference/database.md) for the table map.
20
+
21
+ ## Steps
22
+
23
+ 1. **Pick the directory.**
24
+
25
+ - `custom/migrations/` — project/app migrations (**default choice**).
26
+ `collectMigrations` reads `.framework/migrations/*.sql` first, then
27
+ `custom/migrations/*.sql`, each sorted lexically — custom migrations
28
+ always run after every framework migration
29
+ (`.framework/cli/commands/migrate.ts`).
30
+ - `.framework/migrations/` — only for framework-owned changes (you are
31
+ editing the framework itself).
32
+
33
+ 2. **Pick the number.** Framework files use `NNN_name.sql` with zero-padded
34
+ sequence numbers. Occupied ranges today: `001`–`021`, `032`–`038`,
35
+ `042`–`062`, `064`–`069`; gaps `22`–`31`, `39`–`41`, `63` are reserved/
36
+ consolidated — **do not reuse gap numbers** (see
37
+ [Database reference](../reference/database.md)). Custom migrations get
38
+ their own files in `custom/migrations/`; filenames are tracked verbatim
39
+ in `_spine_migrations`, so keep them globally unique.
40
+
41
+ 3. **Write idempotent SQL.** Migrations run once per filename — but
42
+ installs re-run them against databases in unknown drifted states, so
43
+ guard everything:
44
+
45
+ ```sql
46
+ ALTER TABLE public.my_table
47
+ ADD COLUMN IF NOT EXISTS my_col text;
48
+
49
+ CREATE INDEX IF NOT EXISTS idx_my_table_col
50
+ ON public.my_table (my_col);
51
+
52
+ INSERT INTO public.roles (slug, name, is_active)
53
+ SELECT 'my_role', 'My Role', true
54
+ WHERE NOT EXISTS (SELECT 1 FROM public.roles WHERE slug = 'my_role');
55
+ ```
56
+
57
+ `CREATE TABLE IF NOT EXISTS`, `ADD COLUMN IF NOT EXISTS`,
58
+ `DROP … IF EXISTS`, `ON CONFLICT … DO NOTHING`, and
59
+ `INSERT … SELECT … WHERE NOT EXISTS` are the house patterns (see
60
+ `.framework/migrations/053_builtin_action_seeds.sql`).
61
+
62
+ 4. **Apply it.**
63
+
64
+ ```bash
65
+ npx spine-framework migrate # framework + custom
66
+ npx spine-framework migrate --dry-run # list pending only
67
+ npx spine-framework migrate --framework-only
68
+ ```
69
+
70
+ Requires `SUPABASE_URL` (a hosted `https://<ref>.supabase.co` URL — the
71
+ CLI rejects anything else before connecting) and
72
+ `SUPABASE_DB_PASSWORD` or `--db-password`
73
+ (`.framework/cli/commands/migrate.ts`).
74
+
75
+ 5. **Check state.** `npx spine-framework migrations list` reads the
76
+ `_spine_migrations` ledger; `migrations status` diffs local files vs
77
+ applied — see [CLI reference](../reference/cli.md).
78
+
79
+ ## Verify
80
+
81
+ - `_spine_migrations` contains the filename: each file runs in a
82
+ transaction, records `INSERT INTO _spine_migrations (filename)`, and the
83
+ runner **stops at the first failure** — fix and re-run
84
+ (`.framework/cli/commands/migrate.ts`).
85
+ - Re-running `migrate` reports the file as skipped.
86
+
87
+ ## Common mistakes
88
+
89
+ - **Renaming a file after it ran.** Tracking is by filename — a rename
90
+ makes it a new migration and re-applies the SQL.
91
+ - **Non-idempotent DDL.** `ALTER TABLE … ADD COLUMN` without
92
+ `IF NOT EXISTS` fails on the second run (e.g. a fresh install that
93
+ already got the column from a later reconciliation migration).
94
+ - **Gap numbers.** `022`–`031`, `039`–`041`, `063` are reserved — using
95
+ them collides with framework history.
96
+ - **Depending on framework ordering.** Custom files all sort *after* every
97
+ framework file; you cannot interleave a custom migration between
98
+ framework ones — express dependencies inside the SQL (e.g.
99
+ `CREATE OR REPLACE`, existence checks).
100
+ - **`SUPABASE_URL` with a port, path, or credentials** — rejected by the
101
+ CLI before it ever connects.
102
+
103
+ ## Reference
104
+
105
+ - [Database reference](../reference/database.md) — table map, numbering
106
+ gaps
107
+ - [CLI reference](../reference/cli.md) — `migrate`, `migrations list`,
108
+ `migrations status`
109
+ - [Package and install an app](./package-and-install-an-app.md) — app
110
+ installers run `migrate` automatically before seeding
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: Testing
3
+ audience: both
4
+ kind: entity
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/src/pages/admin/TestingDashboard.tsx
9
+ - .framework/src/pages/admin/TestRunDetailPage.tsx
10
+ - .framework/functions/tests.ts
11
+ - .framework/migrations/001_core_schema.sql
12
+ ---
13
+
14
+ ## What this is for
15
+
16
+ The **Testing** page (`/spine-framework/admin/testing`) is a dashboard over `test_runs`/`test_results` — rows written by external test harnesses (e.g. suite runs triggered from the IDE). It summarizes per-suite health and lists recent runs so you can spot a red suite without opening the CI output.
17
+
18
+ ## Fields and settings
19
+
20
+ - **Suite cards** — one per suite (`unit`, `integration`, `api`, `ui`): last status, last run time, last pass/fail/total counts, and total runs (last 30 days).
21
+ - **Recent runs table** — last 20 runs: suite, status (`passed`/`failed`/`running`/etc.), started time, passed, failed, total, duration.
22
+ - **Row click** — navigates to `testing/:run_id` for the individual test-case results (`name`, `status`, `message`, `duration_ms` per case).
23
+ - No actions on this page — runs are produced by harnesses, not created here.
24
+
25
+ **Downstream effects**
26
+
27
+ - Backed by `/api/tests` (function `tests`): `action=stats` aggregates the last 30 days by suite; `action=list` returns recent runs; `action=get&id=` returns one run plus its ordered `test_results`. All require system context.
28
+ - `system` health checks (`/api/system`) and `spine-framework doctor` also read `test_runs` — a stale suite shows up in deploy health.
29
+ - Rows are append-only; there is no cleanup action on this API today.
30
+
31
+ ## Minimal configuration
32
+
33
+ Nothing on the page — have your harness insert `test_runs` (+ `test_results` per case) and the dashboard populates itself.
34
+
35
+ ## Full configuration
36
+
37
+ ```
38
+ GET /.netlify/functions/tests?action=stats # per-suite 30-day rollup
39
+ GET /.netlify/functions/tests?action=list&limit=20
40
+ GET /.netlify/functions/tests?action=get&id=<run uuid> # run + ordered test_results
41
+ ```
42
+
43
+ ## Example
44
+
45
+ A nightly job runs the `integration` suite and writes a `test_runs` row with `failed: 2`. The dashboard's Integration card flips red with last-run counts; click the row to see which two `test_results` failed and their messages.
46
+
47
+ ## Core files
48
+
49
+ - `.framework/src/pages/admin/TestingDashboard.tsx` — this page
50
+ - `.framework/src/pages/admin/TestRunDetailPage.tsx` — run detail route (`testing/:run_id`)
51
+ - `.framework/functions/tests.ts` — API (`stats`, `list`, `get`)
52
+ - Tables: `public.test_runs`, `public.test_results`
53
+
54
+ ## Agent notes
55
+
56
+ - Known mismatch: `TestRunDetailPage` fetches `/api/test-runs?action=...`, but the shipped function is `tests` — the detail route currently fails to load. Use `tests?action=get&id=` until the page is repointed.
57
+ - The suites list is hardcoded (`unit`, `integration`, `api`, `ui`) — a run with a different `suite` value won't get a card, though it still appears in Recent runs.
58
+ - `stats` is a fixed 30-day window capped at 200 rows — high-volume suites need direct table queries for longer trends.
59
+ - Nothing here triggers runs; the "no runs yet" empty state points at running suites from your IDE/CI.
@@ -0,0 +1,84 @@
1
+ ---
2
+ title: Timers
3
+ audience: both
4
+ kind: entity
5
+ verified_against: b3b3ae0
6
+ verified_on: 2026-11-01
7
+ source_files:
8
+ - .framework/functions/timers.ts
9
+ - .framework/functions/system-cron.ts
10
+ - .framework/functions/trigger-scheduler.ts
11
+ - .framework/src/pages/admin/TimersPage.tsx
12
+ - .framework/src/pages/admin/TimerDetailPage.tsx
13
+ - .framework/migrations/068_cron_schedule_timer_rpcs.sql
14
+ - .framework/migrations/020_schema_reconciliation.sql
15
+ ---
16
+
17
+ ## What this is for
18
+
19
+ A **timer** is a scheduled job that invokes a pipeline when it comes due — Spine's recurring/one-off background task primitive. `system-cron` (invoked every minute by the `trigger-scheduler` Netlify scheduled function) asks the `get_due_timers` RPC for due timers and runs each one's pipeline.
20
+
21
+ ## Fields and settings
22
+
23
+ - **Name** — string. Display name; required (`name` and `timer_type` are the API's required fields).
24
+ - **Type** — select: `interval`, `cron`, `once`. **Stored but not read by the scheduler** — it labels intent only; see Downstream effects.
25
+ - **App ID** — select, optional. Owning app; blank = account-scoped.
26
+ - **Pipeline ID** — select. The pipeline invoked when the timer fires. `get_due_timers` skips rows where `pipeline_id IS NULL`, so a blank pipeline means the timer never runs.
27
+ - **Active** — checkbox. Inactive timers are skipped by `get_due_timers`.
28
+ - **Description** — string, optional.
29
+ - **Config (JSON)** — the schedule. The only key the rescheduler reads today is `interval_minutes` (minutes between runs). The form seeds `{ "schedule": "", "interval_seconds": 3600 }` — neither key is honored by the current RPCs.
30
+ - **Metadata (JSON)** — optional metadata.
31
+
32
+ **Downstream effects**
33
+
34
+ - `get_due_timers` returns active timers with `pipeline_id IS NOT NULL` and `next_execution <= now()`. The timers API arms `next_execution` automatically on create/activate when `config.interval_minutes` (or `interval_seconds`) resolves to a positive interval — or pass `next_execution` explicitly.
35
+ - `update_timer_after_run` reschedules `next_execution = now() + interval` only when `config.interval_minutes` is a positive number; otherwise it sets `next_execution = NULL` and the timer becomes one-shot/dead. `timer_type`, `interval_seconds`, `schedule`, and `run_at` are ignored by the RPC.
36
+ - Each firing also stamps `last_execution` and increments `execution_count` / `success_count` / `failure_count` — the source of the list page's Executions and Success Rate columns.
37
+ - True cron expressions live on the separate `public.schedules` table (read by `get_due_schedules`), not on `timers` — a `cron`-type timer is a label, not a working cron.
38
+
39
+ ## Minimal configuration
40
+
41
+ ```json
42
+ {
43
+ "name": "Hourly sync",
44
+ "timer_type": "interval",
45
+ "pipeline_id": "<pipeline uuid>",
46
+ "is_active": true,
47
+ "config": { "interval_minutes": 60 }
48
+ }
49
+ ```
50
+
51
+ ## Full configuration
52
+
53
+ ```json
54
+ {
55
+ "name": "Nightly-ish rollup",
56
+ "timer_type": "interval",
57
+ "pipeline_id": "<pipeline uuid>",
58
+ "is_active": true,
59
+ "config": { "interval_minutes": 1440 },
60
+ "metadata": { "owner": "platform", "notes": "fires ~daily once next_execution is seeded" }
61
+ }
62
+ ```
63
+
64
+ ## Example
65
+
66
+ A support app keeps an `interval` timer with `interval_minutes: 360` to re-check stale tickets every six hours, and a second timer whose `interval_minutes` was left blank to run a one-off backfill — after its first fire, `update_timer_after_run` writes `next_execution = NULL` so it never re-fires.
67
+
68
+ ## Core files
69
+
70
+ - `.framework/functions/timers.ts` — API (list/get/create/update/delete; does not set `next_execution`)
71
+ - `.framework/functions/system-cron.ts` — per-tick driver: calls `get_due_timers`, runs `runPipeline` as a `timer:<id>` principal, then `update_timer_after_run`
72
+ - `.framework/functions/trigger-scheduler.ts` — Netlify scheduled entry point that delegates to `system-cron`
73
+ - `.framework/migrations/068_cron_schedule_timer_rpcs.sql` — the `get_due_timers` / `update_timer_after_run` RPCs
74
+ - `.framework/src/pages/admin/TimersPage.tsx` / `TimerDetailPage.tsx` — admin UI
75
+ - Table: `public.timers` (`timer_type`, `pipeline_id`, `next_execution`, `execution_count` added by migration 020)
76
+
77
+ ## Agent notes
78
+
79
+ - The list's "Next Execution" column shows `Not scheduled` when `next_execution` is NULL — an active timer in that state cannot fire until it is re-armed (edit its interval or toggle it off/on).
80
+ - A timer with a blank `pipeline_id` saves fine but is filtered out by `get_due_timers` — always pick the pipeline.
81
+ - **Under `netlify dev`, timers arm but never fire.** Netlify scheduled functions only run on deployed sites — locally, `trigger-scheduler` is loaded but never invoked on schedule. Emulate a tick with `netlify functions:invoke trigger-scheduler` or `curl -X POST localhost:8888/.netlify/functions/trigger-scheduler -H 'x-scheduler-key: <SCHEDULER_API_KEY>'`. A timer that schedules but never executes locally is an environment limitation, not a defect — unless manual invoke also fails.
82
+ - Trigger dispatch requires `INTERNAL_INVOKE_SECRET` set **and a dev-server restart** — a queued execution that never runs usually means the secret is missing (see [Add a trigger](tasks/add-a-trigger.md)).
83
+ - To arm a new timer, set `next_execution` in the DB (e.g. `now() + interval`); after the first successful run, `interval_minutes` keeps it rescheduling.
84
+ - "Success Rate" is `success_count / execution_count`; failures still reschedule — a broken pipeline does not stop the cadence.
@@ -0,0 +1,69 @@
1
+ ---
2
+ title: Triggers
3
+ audience: both
4
+ kind: entity
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/trigger-scheduler.ts
11
+ - .framework/src/pages/admin/TriggersPage.tsx
12
+ - .framework/src/pages/admin/TriggerDetailPage.tsx
13
+ - .framework/migrations/064_episode_leases.sql
14
+ ---
15
+
16
+ ## What this is for
17
+
18
+ A **trigger** listens for a Spine event and, when active, hands the event payload to a pipeline for processing. It is the link between emitted platform events (e.g. `item_created`) and the workflows that react to them.
19
+
20
+ ## Fields and settings
21
+
22
+ - **Name** — string. Display name; missing name prevents saving.
23
+ - **Event Type** — string. The event to listen to, e.g. `item_created`. Must match the emitted event name **exactly** — a typo means the trigger never fires.
24
+ - **App ID** — select, optional. Owning app; blank = account-scoped.
25
+ - **Pipeline ID** — select, required at create (`name`, `event_type`, `pipeline_id` are all required by the API). The pipeline invoked when the trigger fires; a stale ID means the queued execution fails instead of running.
26
+ - **Active** — checkbox. Inactive triggers are skipped at fire time — matching events produce no run.
27
+ - **Description** — string, optional.
28
+ - **Config (JSON)** — trigger-engine configuration, e.g. filters or payload mapping.
29
+ - **Metadata (JSON)** — optional metadata.
30
+
31
+ **Downstream effects**
32
+
33
+ - The trigger engine (`admin-data` writes → `_shared/trigger-engine`) matches emitted events to active triggers by `event_type`; real event names use underscores — `item_created`, `item_updated`, `item_deleted`, `thread_created`, `message_created`, etc. (see the `EventType` union in `_shared/trigger-engine.ts`).
34
+ - `config` filters narrow which matching events actually invoke the pipeline.
35
+ - A wrong or stale `pipeline_id` means the trigger fires, queues an execution row, and the run fails — check Pipeline Executions, not just the trigger, when "nothing happened".
36
+
37
+ ## Minimal configuration
38
+
39
+ ```json
40
+ {
41
+ "name": "On item created",
42
+ "event_type": "item_created",
43
+ "pipeline_id": "<pipeline uuid>",
44
+ "is_active": true
45
+ }
46
+ ```
47
+
48
+ ## Full configuration
49
+
50
+ Adds `config` with payload filters (e.g. match only `item_created` where `type_slug = "ticket"`), field mappings into the pipeline input, and `metadata` documenting the contract the event producer is expected to emit.
51
+
52
+ ## Example
53
+
54
+ A support app fires `item_created` filtered to `type_slug = "ticket"`; the trigger invokes its triage pipeline, which assigns the ticket and posts a thread message. A second trigger on `item_updated` handles SLA warnings — both share the same pipeline pattern but different filters.
55
+
56
+ ## Core files
57
+
58
+ - `.framework/functions/triggers.ts` — API
59
+ - `.framework/functions/_shared/trigger-engine.ts` — event matching and dispatch to `pipeline-runner-background`
60
+ - `.framework/functions/trigger-scheduler.ts` — Netlify scheduled entry point (delegates to `system-cron`)
61
+ - `.framework/src/pages/admin/TriggersPage.tsx` / `TriggerDetailPage.tsx` — admin UI
62
+ - Table: `public.triggers`
63
+
64
+ ## Agent notes
65
+
66
+ - Event names are `<entity>_<past tense>` (`item_created`, `message_deleted`, ...) — the Event Type input's own placeholder still shows the stale `item.created` form; use underscores.
67
+ - `config` filters (`entity_type`, `type_slug`, `filters` with `$eq`/`$ne`/`$gt`/etc.) narrow which matching events actually invoke the pipeline.
68
+ - The list's "Total Fires" stat and "Fires"/"Last Fired" columns count trigger firings (the `record_trigger_fire` RPC), not successful pipeline runs — a trigger can fire while its pipeline fails.
69
+ - `config` must be a JSON **object**, not a string — the second most common save failure after event-type typos.
@@ -0,0 +1,93 @@
1
+ ---
2
+ title: Types
3
+ audience: both
4
+ kind: entity
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/types.ts
9
+ - .framework/src/pages/admin/TypesPage.tsx
10
+ - .framework/src/pages/admin/TypeDetailPage.tsx
11
+ - .framework/functions/_shared/permissions.ts
12
+ - .framework/functions/_shared/resolve-ids.ts
13
+ - .framework/functions/_shared/schema-utils.ts
14
+ - .framework/cli/commands/install-app.ts
15
+ - .framework/migrations/015_types_kind_slug_unique.sql
16
+ ---
17
+
18
+ ## What this is for
19
+
20
+ A **type** is the design schema for a runtime record kind. It defines the fields, default list/detail views, record permissions, icon, and color that the generic admin and runtime UIs use to create, validate, and display records. Every `items`/`threads`/`messages`/etc. record is stamped with its type's `design_schema` at creation.
21
+
22
+ ## Fields and settings
23
+
24
+ - **Name** — string. Display label for the type. Empty name blocks save.
25
+ - **Slug** — string. URL-friendly identifier, unique per `kind`. Callers resolve types by slug; duplicates break `resolveTypeId`/`.single()` lookups. Rarely change after records exist.
26
+ - **Description** — string, optional. Human-readable context.
27
+ - **Kind** — select: `item`, `account`, `person`, `thread`, `message`, `link`, `attachment`, `watcher`. Determines which runtime table the type governs.
28
+ - **Ownership** — select: `system` (platform types, protected from deletion), `app` (owned by an app), `tenant` (account-specific).
29
+ - **Is Active** — checkbox. Inactive types are hidden from lists and cannot have records created.
30
+ - **App ID** — select. Required whenever ownership is not `system` — the form rejects save otherwise.
31
+ - **Icon / Color** — UI presentation stored on the `types` row.
32
+ - **Design Schema (JSON)** — the full schema: `fields`, `views` (`default_list`, `default_detail`), `record_permissions`, `scope`, `functionality`.
33
+ - **Validation Schema** — read-only, regenerated from `design_schema` on every save by the API.
34
+
35
+ **Downstream effects**
36
+
37
+ - Generic list/detail pages render directly from `views`. A missing `default_list`/`default_detail` or unknown `display_type` breaks those pages.
38
+ - `record_permissions` is the **base, most restrictive grant**: if a role can read/update a record, all fields default to read/write. `fields.*.permissions` only *loosen* access per field or restrict `system: true` fields.
39
+ - `system: true` fields are read from `record.<field>`; non-system fields from `record.data.<field>`.
40
+ - Changing `data_type` on a field with existing records does not migrate data — old values remain until rewritten.
41
+ - Type seeds use `conflict: "kind,slug"` with `inject_app_id: false` — types are global, not duplicated per app install.
42
+
43
+ ## Minimal configuration
44
+
45
+ ```json
46
+ {
47
+ "scope": "account",
48
+ "fields": {
49
+ "title": { "data_type": "text", "label": "Title", "required": true, "system": true }
50
+ },
51
+ "views": {
52
+ "default_list": {
53
+ "type": "list", "display": "table", "label": "List",
54
+ "fields": {
55
+ "title": { "sortable": true, "display_type": "text" },
56
+ "created_at": { "sortable": true, "display_type": "timestamp" }
57
+ },
58
+ "default_sort": { "field": "created_at", "direction": "desc" }
59
+ },
60
+ "default_detail": {
61
+ "type": "detail", "label": "Detail",
62
+ "sections": [{ "title": "Details", "fields": { "title": { "display_type": "input" } } }]
63
+ }
64
+ },
65
+ "record_permissions": { "system_admin": ["create", "read", "update", "delete"] },
66
+ "functionality": null
67
+ }
68
+ ```
69
+
70
+ ## Full configuration
71
+
72
+ Adds typed fields, select `options`, per-field `permissions` overrides, multiple views with sorting/badges, and per-role record grants (`admin`, `member`). See the [design schema reference](reference/design-schema.md) for the full `data_type` list (`text`, `number`, `select`, `multiselect`, `reference`, `json`, `date`, `boolean`, ...) and `display_type` list (`badge`, `timestamp`, `select`, `currency`, ...).
73
+
74
+ ## Example
75
+
76
+ A support app defines a `ticket` type (kind `item`, ownership `app`): `title` (system text), `status` (select: open/pending/closed), `priority` (select), `assignee_id` (reference). `record_permissions` grants `system_admin` all actions, `admin` create/read/update, `member` read. `fields.assignee_id.permissions` loosens `write` to `member` so agents can self-assign.
77
+
78
+ ## Core files
79
+
80
+ - `.framework/functions/types.ts` — API (list/get/create/update/delete; regenerates `validation_schema`)
81
+ - `.framework/src/pages/admin/TypesPage.tsx` / `TypeDetailPage.tsx` — admin UI
82
+ - `.framework/src/components/app-shell/GenericListPage.tsx` / `GenericDetailPage.tsx` — generic renderer
83
+ - `.framework/functions/_shared/permissions.ts` — record/field permission evaluation
84
+ - [Design schema reference](reference/design-schema.md) — full field/view schema reference in this book
85
+ - Table: `public.types`
86
+
87
+ ## Agent notes
88
+
89
+ - Never hand-edit `validation_schema`; change `design_schema` and the API regenerates it.
90
+ - Slug uniqueness is per `(kind, slug)` — reuse across kinds is fine, reuse within a kind breaks lookups.
91
+ - Every field needs `data_type`, `label`, `required`; `select`/`multiselect`/`radio` also need `options`.
92
+ - Don't add `fields.*.permissions` unless overriding the base grant — the common mistake is re-stating record-level permissions per field.
93
+ - Custom apps CRUD runtime records through `/api/admin-data` — never custom wrappers.