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,87 @@
1
+ ---
2
+ title: API Keys
3
+ audience: both
4
+ kind: entity
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/api-keys.ts
9
+ - .framework/functions/_shared/hash-api-key.ts
10
+ - .framework/functions/_shared/principal.ts
11
+ - .framework/functions/_shared/logging.ts
12
+ - .framework/src/pages/admin/APIKeysPage.tsx
13
+ - .framework/src/pages/admin/APIKeyDetailPage.tsx
14
+ ---
15
+
16
+ ## What this is for
17
+
18
+ An **API key** authenticates callers to Spine — integrations, MCP servers, CLI tooling, and service-to-service calls. A key controls which callers are accepted, how fast they can call, and which permissions they carry.
19
+
20
+ ## Fields and settings
21
+
22
+ - **Name** *(required)* — human-readable label; missing name blocks creation.
23
+ - **Integration ID** — optional UUID of the integration this key serves.
24
+ - **App ID** — optional UUID/slug of the owning app (server resolves slugs via `resolveAppUuid`).
25
+ - **Key Type** — `inbound` (external callers into Spine), `outbound` (Spine calling out), `internal` (service-to-service), `cli` (scripts/tooling). Determines how the key is validated.
26
+ - **Key Prefix** — derived server-side: the raw key is `sk_<uuid>` and `key_prefix` stores its first 8 characters for lookup. Not a form field.
27
+ - **Rate Limit (requests/min)** — `rate_limit`, UI range 1–10000. Enforced per key inside `rate_limit_window_seconds` (default 60s, overridable via key `config`). An unset/`0` value falls back to the default 100 — it does not block requests.
28
+ - **Expires At** — optional date; after it the key is rejected.
29
+ - **Machine Type** — machine principal type, e.g. `integration`.
30
+ - **Internal** — marks the key for internal service-to-service use.
31
+ - **Active** — inactive keys are rejected on every call.
32
+ - **Permissions / Scopes / Metadata (JSON)** — `permissions` and `scopes` are string arrays (validated against required permissions on `action=validate`); `metadata` is free-form. Invalid JSON fails creation.
33
+
34
+ **Downstream effects**
35
+
36
+ - The gateway validates `key_prefix` + `key_hash` on every call — inactive, expired, or over-limit keys are rejected.
37
+ - The raw key is returned **once** at create (`raw_key` in the response envelope). It is never stored or returned again — `key_value`/`key_hash` are stripped from all API responses (S-001/F-040).
38
+ - The API has no update action (list/get/create plus `validate`, `revoke`, `usage-logs`) — rotate = create new + revoke old.
39
+ - Keys still referenced in local MCP configs or scripts fail immediately when revoked/deactivated.
40
+
41
+ ## Minimal configuration
42
+
43
+ ```json
44
+ {
45
+ "name": "CI deploy key",
46
+ "key_type": "cli",
47
+ "rate_limit": 60,
48
+ "is_active": true
49
+ }
50
+ ```
51
+
52
+ ## Full configuration
53
+
54
+ ```json
55
+ {
56
+ "name": "Workbench MCP",
57
+ "key_type": "inbound",
58
+ "app_id": "workbench",
59
+ "rate_limit": 600,
60
+ "expires_at": "2027-01-01",
61
+ "machine_type": "integration",
62
+ "is_active": true,
63
+ "permissions": ["admin-data:read"],
64
+ "scopes": ["admin-data:read", "logs:read"],
65
+ "metadata": { "env": "dev" }
66
+ }
67
+ ```
68
+
69
+ ## Example
70
+
71
+ A local MCP server authenticates with an `inbound` key scoped to read-only admin-data calls; a separate `cli` key (low rate limit) backs the `spine-framework` CLI. A leaked key gets rotated by creating a replacement and revoking the old one — the audit found a plaintext-stored legacy key and deactivated it (S-001/S-003).
72
+
73
+ ## Core files
74
+
75
+ - `.framework/functions/api-keys.ts` — API (create/list/get + `validate`/`revoke`/`usage-logs` actions; mints `sk_<uuid>` raw key)
76
+ - `.framework/functions/_shared/hash-api-key.ts` — `hashApiKey`, `verifyApiKey` (pbkdf2), `omitSecretFields` (strips `key_value`/`key_hash`)
77
+ - `.framework/functions/_shared/logging.ts` — `resolveAppUuid` (app slug → UUID on create)
78
+ - `.framework/functions/_shared/principal.ts` — per-request key resolution (`x-api-key` → prefix lookup → pbkdf2 verify → per-key + per-IP rate limit)
79
+ - `.framework/cli/context.ts` — CLI auth path (F-042)
80
+ - `.framework/src/pages/admin/APIKeysPage.tsx` / `APIKeyDetailPage.tsx` — admin UI
81
+ - Table: `public.api_keys`
82
+
83
+ ## Agent notes
84
+
85
+ - Copy `raw_key` at creation — there is no "show key" later. Treat any key whose value was ever visible as burned.
86
+ - Prefer short `expires_at` + documented `metadata.env` over long-lived keys.
87
+ - `key_type: cli` + a `permissions` array is the documented path for scripted/agent access (see S-002 — a first-class scriptable login is still pending).
@@ -0,0 +1,77 @@
1
+ ---
2
+ title: Apps
3
+ audience: both
4
+ kind: entity
5
+ verified_against: b3b3ae0
6
+ verified_on: 2026-11-01
7
+ source_files:
8
+ - .framework/functions/apps.ts
9
+ - .framework/src/pages/admin/AppsPage.tsx
10
+ - .framework/src/pages/admin/AppDetailPage.tsx
11
+ - .framework/migrations/003_core_system_data.sql
12
+ - .framework/migrations/013_get_account_apps_role_filter.sql
13
+ ---
14
+
15
+ ## What this is for
16
+
17
+ An **app** is a registered application in Spine. The registry controls navigation visibility, who can open the app (min role), how it renders (generic vs custom), and the JSON configuration the app or its types consume at runtime.
18
+
19
+ ## Fields and settings
20
+
21
+ - **Name** — string. Display name in the app switcher and registry.
22
+ - **Slug** — string. Unique identifier; set at creation, rarely changed. Routes and API scoping depend on it.
23
+ - **Description** — string, optional. Short text shown in the app list.
24
+ - **App Type** — select: `system`, `custom`, `marketplace`. Drives how the platform treats the app.
25
+ - **Source** — select: `system`, `custom`, `marketplace`. Tracks where the package came from.
26
+ - **Min Role** — select: `member`, `admin`, `owner`. Lowest role that can see/open the app.
27
+ - **Version** — string. Current version shown in the registry.
28
+ - **Active** — boolean. Inactive apps are hidden and cannot be opened.
29
+ - **System** — boolean. Protects the app from tenant-level deletion.
30
+ - **Icon / Color** — UI presentation for the app tile.
31
+ - **Route Prefix** — string, optional. URL prefix where the app mounts (defaults to `/slug`). `"/"` mounts the app at the site root (exactly one app may own it; the app must render its own not-found); nested prefixes like `/crm/sales` work longest-first. `/spine-framework/*` is reserved. See [Apps and Renderers → Mounting](concepts/apps-and-renderers.md#mounting-directory-or-root).
32
+ - **Renderer** — select: `generic` (renders list/detail from type `design_schema.views`), `custom` (app renders its own pages), `none`.
33
+ - **Config (JSON)** — `config` column; runtime config for the app or its generic renderer.
34
+ - **Nav Items (JSON)** — array of navigation items for the app shell.
35
+ - **Integration Deps (JSON)** — array of integration slugs the app depends on.
36
+ - **Metadata (JSON)** — free-form metadata.
37
+
38
+ **Downstream effects**
39
+
40
+ - The app shell reads `slug`, `route_prefix`, `min_role`, `is_active`, `icon`, `color`, `nav_items` to build navigation and gate access.
41
+ - `renderer = generic` requires every served type to have complete `views` in its `design_schema`.
42
+ - `min_role` raises the floor for the whole app — setting `admin` hides it from members even if record permissions would allow reads.
43
+
44
+ ## Minimal configuration
45
+
46
+ ```json
47
+ {
48
+ "slug": "my-app",
49
+ "name": "My App",
50
+ "app_type": "custom",
51
+ "source": "custom",
52
+ "renderer": "custom",
53
+ "is_active": true,
54
+ "min_role": "member"
55
+ }
56
+ ```
57
+
58
+ ## Full configuration
59
+
60
+ Adds `version`, `icon`, `color`, `route_prefix`, `config` (feature flags, default views), `nav_items` (grouped sidebar entries), `integration_deps` (e.g. `["openai", "sendgrid"]`), and `metadata` (author, homepage, tags).
61
+
62
+ ## Example
63
+
64
+ A support app registers with `renderer: "custom"`, `route_prefix: "/answer-once"`, `min_role: "member"`, and `integration_deps: ["openai"]` — the admin renders its own pages but still uses `/api/admin-data` for record CRUD and `types` for its ticket/thread types.
65
+
66
+ ## Core files
67
+
68
+ - `.framework/functions/apps.ts` — API (list/get/create/update/delete)
69
+ - `.framework/src/pages/admin/AppsPage.tsx` / `AppDetailPage.tsx` — admin UI
70
+ - `.framework/src/components/app-shell/` — shell, generic renderer, app registry
71
+ - Table: `public.apps` (+ `get_account_apps` RPC for counts)
72
+
73
+ ## Agent notes
74
+
75
+ - `renderer = "custom"` still needs `fields` + `record_permissions` on its types so `admin-data` can stamp and validate records.
76
+ - Slug appears in API envelopes and `x-app-id` headers — renaming an in-use slug breaks logging attribution and callers.
77
+ - `marketplace` as a type/source value is under review (F-031) — prefer `custom` for new apps.
@@ -0,0 +1,150 @@
1
+ ---
2
+ title: "AI agents, prompt configs, and embeddings"
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: f69c37d
6
+ verified_on: 2026-10-29
7
+ source_files:
8
+ - .framework/functions/ai-agents.ts
9
+ - .framework/functions/prompt-configs.ts
10
+ - .framework/functions/embeddings.ts
11
+ - .framework/functions/ai-interactions.ts
12
+ - .framework/functions/_shared/agent-runner.ts
13
+ - .framework/functions/_shared/llm.ts
14
+ - .framework/functions/_shared/pipeline-runner.ts
15
+ - .framework/migrations/034_match_embeddings_knn.sql
16
+ - .framework/migrations/044_match_embeddings_filtered_knn.sql
17
+ ---
18
+
19
+ # AI agents, prompt configs, and embeddings
20
+
21
+ Three config entities shape Spine's AI behavior:
22
+
23
+ - An **AI agent** (`public.ai_agents`) is the *who*: a named model + system
24
+ prompt + tool set + guardrails.
25
+ - A **prompt config** (`public.prompt_configs`) is the *how*: the prompt
26
+ template, model parameters, retrieval sources, and escalation policy applied
27
+ to a conversation.
28
+ - An **embedding** (`public.embeddings`) is the *memory*: per-document vector
29
+ rows that semantic retrieval searches over.
30
+
31
+ All three are ordinary admin-data-managed rows: `ai-agents` and
32
+ `prompt-configs` expose CRUD via their own functions, and the conversational
33
+ runtime lives in `.framework/functions/_shared/agent-runner.ts`.
34
+
35
+ ## Agent configuration
36
+
37
+ `ai-agents` (`.framework/functions/ai-agents.ts`) stores, per row:
38
+
39
+ - `name` + `agent_type` — required (`chat`, `analysis`, `automation`,
40
+ `custom` in the admin UI).
41
+ - `model_config` — `{ model, temperature, max_tokens, tools }`. At inference
42
+ time the model resolves as `model_config.model || LLM_DEFAULT_MODEL ||
43
+ 'gpt-4o'`; `model_config.tools` is passed through as OpenAI tool definitions
44
+ with `tool_choice: 'auto'`.
45
+ - `system_prompt` — concatenated with the prompt config's `system_prompt`
46
+ (agent first) to form the system message.
47
+ - `tools`, `capabilities`, `constraints`, `metadata` — `metadata
48
+ .default_prompt_config_id` is the fallback used when the thread doesn't
49
+ stamp its own `prompt_config_id`.
50
+ - `is_active` — inactive agents fail `resolveAgentConfig` lookup.
51
+
52
+ ## Prompt configs and templating
53
+
54
+ `prompt-configs` (`.framework/functions/prompt-configs.ts`) carries the fields
55
+ the runner consumes per turn: `system_prompt`, `context_template`, `model`
56
+ (default `gpt-4o`), `temperature` (0.7), `max_tokens` (4000),
57
+ `is_multi_turn` (true), `max_history_messages` (20), `confidence_threshold`,
58
+ `escalation_action` / `escalation_target`, `output_mode` / `output_field`,
59
+ `requires_review`, `knowledge_sources`, `available_tools`, and
60
+ `tool_constraints`.
61
+
62
+ `context_template` supports exactly two placeholders, substituted in
63
+ `buildContext`: `{{user_message}}` (the inbound text, truncated to 4000 chars)
64
+ and `{{conversation_history}}` (prior messages rendered as
65
+ `Customer:`/`Agent:` lines, truncated to 4000 chars).
66
+
67
+ ## How a thread resolves its agent and prompt
68
+
69
+ `resolveAgentConfig` in `.framework/functions/_shared/agent-runner.ts`:
70
+
71
+ 1. Reads the thread (`threads`, joined with its `types` row).
72
+ 2. Agent id comes from `thread.data.agent_id`, falling back to the thread
73
+ type's `design_schema.default_agent_id`. No agent → the run aborts.
74
+ 3. Prompt config id comes from `thread.data.prompt_config_id`, falling back to
75
+ `agent.metadata.default_prompt_config_id`.
76
+ 4. Both rows must be `is_active`.
77
+
78
+ `buildContext` then assembles the model input: combined system prompt, the
79
+ rendered `context_template`, retrieved knowledge excerpts (numbered `[n]` and
80
+ explicitly marked *untrusted* — the prompt instructs the model to treat them as
81
+ evidence, never instructions), `## Conversation History`, `## Tool Results`,
82
+ the filtered `available_tools` list, and finally `## Current Message` with a
83
+ prompt-injection warning.
84
+
85
+ ## Invoking an agent
86
+
87
+ The interactive path is `POST /api/ai-agents?action=run` with `{ thread_id,
88
+ user_message, mode }` — `mode` is `step`, `complete`, or `episode` (default
89
+ `complete`). Before any tokens are spent the handler runs a prompt-injection
90
+ preflight (`detectInjection`); the app's `security.prompt_injection` setting
91
+ chooses `off` | `flag` (default) | `block`. Each claimed run debits the
92
+ `agent_step` rate limit.
93
+
94
+ Inside a run, `runAgent`/`runAgentStep` build context, call inference, dispatch
95
+ any `tool_calls` the model returns through `executeToolAction`, persist the
96
+ outbound `messages` row stamped with `agent_id` and `prompt_config_id`, and
97
+ manage the episode lease (see [concurrency](./concurrency-and-idempotency.md)).
98
+
99
+ Pipelines reach AI through stage types, not the `run` action:
100
+
101
+ - `agent_inference` — POSTs `{ context, model, temperature, max_tokens }` to
102
+ `config.webhook_url` via `integration-outbound` (`generic-http`); without a
103
+ `webhook_url` it returns a mock response.
104
+ - `search_knowledge` — retrieval over knowledge sources for the stage config's
105
+ `query`.
106
+ - `generate_embeddings` / `revoke_embeddings` / `kb_ingest` / `kb_revoke` /
107
+ `create_interaction` — delegate to the `embeddings`, `kb`, and
108
+ `ai-interactions` functions described below.
109
+
110
+ ## The LLM call itself
111
+
112
+ `_shared/llm.ts` does not hold API keys. `callOpenAI` routes every request
113
+ through `integration-outbound` with `integration_slug: 'openai'` and
114
+ `credential_field: 'api_token'` — credentials come from the `integrations`
115
+ table like any other outbound call. `callOpenAIChat` defaults to
116
+ `gpt-4o-mini`/0.7/4000 tokens, overridable per call and via
117
+ `integration.config.models.chat` / `.embedding`. When no OpenAI integration or
118
+ credential exists, inference returns a clearly-marked mock response rather than
119
+ failing.
120
+
121
+ ## Embeddings and retrieval
122
+
123
+ `embeddings` (`.framework/functions/embeddings.ts`) manages the
124
+ `public.embeddings` vector store:
125
+
126
+ - `action=generate` builds embeddable text per **vector type**. The type's
127
+ `design_schema.vector_types` decides which builders run — default
128
+ `['semantic', 'structure']`; registered builders also cover `intent`,
129
+ `cause`, `procedure`, `outcome`, `context`, and `error_signal`. Each vector
130
+ type occupies a fixed `chunk_index` slot so concurrent types never collide
131
+ on the unique constraint, and unchanged content is skipped by hash.
132
+ - `action=revoke` sets `revoked_at` on a document's vectors; `action=approve`
133
+ sets `approved_at` and clears revocation.
134
+ - `action=search-similar` calls the `match_embeddings` RPC (pgvector KNN,
135
+ defined in migrations `034`/`044`) with `query_embedding`, `match_count`,
136
+ `similarity_threshold`, and optional account/vector-type/item-type/document
137
+ filters.
138
+ - `action=search-semantic` is a Postgres `websearch` full-text query over the
139
+ `content` column — the lexical complement to vector search.
140
+
141
+ ## Interaction records
142
+
143
+ `ai-interactions` (`.framework/functions/ai-interactions.ts`) writes the
144
+ feedback/observability side of the loop as `items` rows (default type
145
+ `ai_feedback_record`, overridable via `type_slug`; the call returns
146
+ `{ skipped: true }` when the type isn't installed). Actions:
147
+ `submit_feedback`, `create_interaction` (what the pipeline stage calls — it
148
+ stamps `thread_id`, `ai_message_id`, `retrieved_sources`, `ai_confidence`,
149
+ `kb_candidate`, and review fields), `update_interaction`,
150
+ `create_cil_proposal`, and `list`.
@@ -0,0 +1,228 @@
1
+ ---
2
+ title: Apps and Renderers
3
+ audience: developers, agents
4
+ kind: concept
5
+ verified_against: b3b3ae0
6
+ verified_on: 2026-11-01
7
+ source_files:
8
+ - .framework/migrations/001_core_schema.sql
9
+ - .framework/migrations/013_get_account_apps_role_filter.sql
10
+ - .framework/migrations/014_apps_required_roles.sql
11
+ - .framework/src/App.tsx
12
+ - .framework/src/components/AppWrapper.tsx
13
+ - .framework/src/components/CustomAppLoader.tsx
14
+ - .framework/src/components/app-shell/GenericAppShell.tsx
15
+ - .framework/src/components/app-shell/GenericListPage.tsx
16
+ - .framework/src/components/app-shell/GenericDetailPage.tsx
17
+ - .framework/functions/_shared/app-manifest.ts
18
+ - .framework/functions/apps.ts
19
+ - .framework/cli/commands/create-app.ts
20
+ - .framework/cli/commands/install-app.ts
21
+ ---
22
+
23
+ # Apps and Renderers
24
+
25
+ An app in Spine is a **row in `public.apps`** plus — for apps that render their
26
+ own UI — a directory of code. The registry row decides where the app mounts,
27
+ who can open it, and *how* it renders. The `renderer` column is the fork in
28
+ the road.
29
+
30
+ ## The registry row
31
+
32
+ `.framework/migrations/001_core_schema.sql` defines `public.apps`: unique
33
+ `slug`, `name`, `route_prefix`, `renderer` (CHECK constraint:
34
+ `'generic' | 'custom' | 'none'`), `min_role`, `required_roles` (added in
35
+ migration 014), `nav_items`, `config`, `integration_deps`, `metadata`,
36
+ `is_active`, `is_system`, `owner_account_id`, domains.
37
+
38
+ - `route_prefix` (defaults to `/<slug>`) is where the SPA mounts the app —
39
+ `.framework/src/App.tsx` builds a `<Route>` per active app.
40
+ - `is_active: false` removes the app from navigation and routing.
41
+ - `is_system: true` protects the row from tenant-level deletion.
42
+
43
+ ## Mounting: directory or root
44
+
45
+ `route_prefix` is the whole mount contract. Every routable app
46
+ (`route_prefix != null`, `renderer !== 'none'`) becomes a
47
+ `<Route path="<prefix>/*">` in `.framework/src/App.tsx`:
48
+
49
+ - **Directory mount** — `"/portal"` → the app owns `/portal/*`.
50
+ - **Root mount** — `"/"` → the app owns `/*` and renders directly at `/` for
51
+ non-admin users.
52
+ - **Nested mount** — `"/crm/sales"` works mechanically (`/crm/sales/*`);
53
+ prefixes sort longest-first, so `/crm/sales` wins over `/crm`. Supported
54
+ but unusual.
55
+
56
+ ### Host-keyed mount overrides
57
+
58
+ `prod_domain` + `prod_route_prefix` (and the `preview_` pair for
59
+ `VITE_ENVIRONMENT=preview`) let one row mount differently per hostname
60
+ (`.framework/src/lib/app-mount.ts`, resolved in `useApps`/`AppContext` before
61
+ records reach the router):
62
+
63
+ - Host matches `prod_domain` → app mounts at `prod_route_prefix` (or
64
+ `route_prefix` when no override is set).
65
+ - Host doesn't match, override set → still routable at `route_prefix`.
66
+ - Host doesn't match, no override → filtered out (legacy domain-lock).
67
+
68
+ So `route_prefix: "/demo"` + `prod_domain: "demo.example.com"` +
69
+ `prod_route_prefix: "/"` serves the app at `/` on its own site while it
70
+ stays at `/demo` everywhere else. `manifest.json` carries all four fields;
71
+ `validate-app` requires the matching domain whenever an override prefix is
72
+ declared. The unique index `idx_apps_route_prefix_domain` allows `/` per
73
+ prod domain.
74
+
75
+ Rules that are easy to get wrong:
76
+
77
+ - **Exactly one app may own `/`.** Two root-mounted apps both compile to
78
+ `/*` and resolution order silently decides which renders — the registry
79
+ does not prevent this; don't create it.
80
+ - **A root-mounted app shadows the global not-found page.** Its `/*` route
81
+ matches every path the more-specific routes don't, so the app must render
82
+ its own not-found for unknown internal paths (a catch-all
83
+ `<Route path="*">` inside its `index.tsx` routes).
84
+ - **`system_admin` always lands on `/spine-framework/admin`.**
85
+ Root-mounting changes what *members* see at `/`, not admins. Non-admin
86
+ landing at `/`: root-mounted app if one exists → else redirect to the
87
+ first app's prefix → else `NoAppPage`.
88
+ - **App-internal navigation must be mount-agnostic.** Use
89
+ `useAppPath()` from `@core/hooks/useAppPath` (or relative paths inside the
90
+ app's `<Routes>`) for every link and `navigate()` call — it returns the
91
+ prefix (`''` when mounted at `/`). A hardcoded `navigate('/dashboard')`
92
+ escapes a directory-mounted app. See [Frontend SDK](../reference/frontend-sdk.md).
93
+ - **`nav_items[].path` accepts two forms** (`GenericAppShell` navigates
94
+ absolute paths verbatim and prefixes relative ones): absolute
95
+ *including* the prefix (`/taskboard/tasks`) or relative (`tasks` →
96
+ resolved to `/taskboard/tasks`). The broken form is absolute *without*
97
+ the prefix (`/tasks` escapes the app). For a root-mounted app the
98
+ absolute form is simply `/…`.
99
+ - **`/spine-framework/*` is reserved** (admin, API docs, CLI pages mount
100
+ there ahead of app routes). Never pick a colliding prefix.
101
+ - **Apps are always behind login.** `is_public`/`auth_required` exist on
102
+ the manifest, but the unauthenticated router only serves auth pages —
103
+ a root-mounted app is not a public site mechanism.
104
+
105
+ Change the mount point via `manifest.route_prefix` + `update-db-app <slug>`
106
+ (or the admin app detail page). On package reinstall the local
107
+ `manifest.json` is preserved, so the file is the durable source.
108
+
109
+ ## Role gating
110
+
111
+ Two layers decide who sees an app:
112
+
113
+ - Server side, `get_account_apps` filters the app list by the caller's role
114
+ slugs (`.framework/functions/apps.ts`;
115
+ `.framework/migrations/013_get_account_apps_role_filter.sql`).
116
+ - Client side, `AppWrapper` computes `required_roles` (falling back to
117
+ `[min_role]`) and renders an access-denied screen unless the user holds
118
+ `system_admin` or one of those roles (`.framework/src/components/AppWrapper.tsx`).
119
+
120
+ This gates *opening the app*; it does not replace record-level
121
+ `record_permissions` on the data underneath.
122
+
123
+ ## `renderer = 'generic'`
124
+
125
+ The platform renders the app's list and detail pages for you, straight from
126
+ each type's `design_schema.views`:
127
+
128
+ - `App.tsx` mounts `GenericAppShell` for `renderer === 'generic'` apps.
129
+ - `GenericAppShell` builds the sidebar from the app's `nav_items` and, for
130
+ every nav item carrying `type_slug` (and optional `view`), generates
131
+ list/detail/new routes backed by `GenericListPage` and
132
+ `GenericDetailPage`.
133
+ - `GenericListPage` turns `views.default_list` (or the nav item's named view)
134
+ into sortable columns; `GenericDetailPage` renders `views.default_detail`
135
+ sections with per-field read/write awareness.
136
+
137
+ **What you get for free:** a working list page, detail page, create/edit form,
138
+ sorting, filters, field-type rendering, and permission-aware editing — with
139
+ zero app code. **What it costs:** every type the app serves must have complete
140
+ `fields`, `views.default_list`, and `views.default_detail`, or the pages fail
141
+ with "No schema available" / schema-load errors. See
142
+ [Design Schema](design-schema.md).
143
+
144
+ ## `renderer = 'custom'`
145
+
146
+ The app ships its own React code under `custom/apps/<slug>/` (a
147
+ project-owned directory, not part of the framework package). `App.tsx` mounts
148
+ `CustomAppLoader`, which resolves the app through an
149
+ `import.meta.glob('custom/apps/*/index.tsx')` — the app's `index.tsx` is its
150
+ entry point and default-exports the routed component (the `create-app`
151
+ scaffold generates exactly this shape).
152
+
153
+ A custom app renders its own pages but still uses the platform for data:
154
+ `/api/admin-data` for runtime CRUD, `/api/types` for schemas. Custom apps
155
+ must never write their own CRUD wrappers — that bypasses permissions,
156
+ validation, audit, and triggers (see [Runtime Records](runtime-records.md)).
157
+
158
+ `renderer = 'none'` registers the app (data, types, integrations) with no UI
159
+ surface.
160
+
161
+ ## The manifest
162
+
163
+ `custom/apps/<slug>/manifest.json` is the app's declarative half, validated by
164
+ `loadManifest` in `.framework/functions/_shared/app-manifest.ts` (the
165
+ `AppManifest` interface is the contract):
166
+
167
+ ```json
168
+ {
169
+ "name": "My App",
170
+ "slug": "my-app",
171
+ "description": "…",
172
+ "version": "1.0.0",
173
+ "required_roles": ["member"],
174
+ "route_prefix": "/my-app",
175
+ "renderer": "custom",
176
+ "routes": ["/my-app", "/my-app/dashboard"],
177
+ "nav_items": [{ "title": "Dashboard", "path": "/my-app/dashboard", "icon": "LayoutDashboard", "order": 1 }],
178
+ "features": [],
179
+ "dependencies": [],
180
+ "entry_point": "./index.tsx",
181
+ "is_public": false,
182
+ "auth_required": true
183
+ }
184
+ ```
185
+
186
+ Validated requirements: non-empty `name`, `slug` matching
187
+ `^[a-z][a-z0-9-]*$` (and equal to the directory name for discovery), and
188
+ `required_roles` as an array of strings. Optional keys the install flow also
189
+ honors: `config` (merged onto the apps row), `seed` (declared seed entries),
190
+ `owned_types`, `registration`, `metadata`, `min_role`,
191
+ `preview_domain`/`prod_domain`.
192
+
193
+ ## Install and seed flow
194
+
195
+ Two paths put an app on disk and in the registry:
196
+
197
+ - **`npx spine-framework create-app <slug>`** — scaffolds
198
+ `custom/apps/<slug>/` (`manifest.json` + `index.tsx` + `components/`) and
199
+ inserts a `public.apps` row with `renderer: 'custom'` and
200
+ `route_prefix: /<slug>` (`.framework/cli/commands/create-app.ts`).
201
+ - **`npx spine-framework install-app <package>`** — copies a packaged app into
202
+ `custom/apps/<slug>/`, runs migrations, then `seedApp`:
203
+ - upserts the `apps` row from the manifest (`appRowPayload`: name,
204
+ description, version, nav_items, route_prefix, renderer, required_roles,
205
+ min_role, config merge, metadata);
206
+ - applies seed files in dependency order — conventional
207
+ `seed/roles.json` → `roles` (conflict `slug`), `seed/types.json` →
208
+ `types` (conflict `kind,slug`, **no** `app_id` injection,
209
+ `ignoreDuplicates`) — plus any `manifest.seed` entries;
210
+ - refuses to overwrite a `types` row whose `design_schema` differs from the
211
+ seed unless the slug is declared in `manifest.owned_types` and
212
+ `--reconcile-owned-types` was passed (types are global — see
213
+ [Scope and Accounts](scope-and-accounts.md));
214
+ - syncs the `registration` block into `spine.config.json` (the project-owned
215
+ config file at the project root).
216
+ - **`update-db-app <slug>`** re-syncs a local `manifest.json` to the DB without
217
+ touching files; **`seed-app <slug>`** re-runs seeds;
218
+ **`uninstall-app`** removes the app (`--keep-data` preserves seeded rows).
219
+ All in `.framework/cli/commands/install-app.ts`.
220
+
221
+ ## The practical split
222
+
223
+ Use `generic` when the app is "records in, records out" — lists, detail forms,
224
+ admin-style editing — and keep views accurate. Use `custom` when the UX is a
225
+ product surface (dashboards, wizards, bespoke layouts) and treat the manifest
226
+ as your app's contract: registration, roles, seeds, nav. Either way, types and
227
+ `design_schema` do the data modeling — the renderer only decides who draws the
228
+ pixels.