spine-framework 0.3.125 → 0.3.126

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 (319) hide show
  1. package/.framework/cli/commands/agents.ts +35 -37
  2. package/.framework/cli/commands/auth.ts +35 -34
  3. package/.framework/cli/commands/create-app.ts +104 -40
  4. package/.framework/cli/commands/dev.ts +104 -69
  5. package/.framework/cli/commands/doctor.ts +169 -97
  6. package/.framework/cli/commands/generate.ts +82 -160
  7. package/.framework/cli/commands/init.ts +72 -34
  8. package/.framework/cli/commands/install-app.ts +260 -125
  9. package/.framework/cli/commands/items.ts +33 -44
  10. package/.framework/cli/commands/migrate.ts +122 -40
  11. package/.framework/cli/commands/migrations.ts +33 -40
  12. package/.framework/cli/commands/pipelines.ts +31 -39
  13. package/.framework/cli/commands/status.ts +47 -23
  14. package/.framework/cli/commands/system.ts +47 -37
  15. package/.framework/cli/commands/test.ts +112 -68
  16. package/.framework/cli/commands/validate-app.ts +215 -121
  17. package/.framework/cli/context.ts +112 -130
  18. package/.framework/cli/env-loader.ts +47 -18
  19. package/.framework/cli/index.ts +81 -42
  20. package/.framework/docs/runtime-types.md +1024 -0
  21. package/.framework/functions/_shared/agent-runner.ts +1296 -961
  22. package/.framework/functions/_shared/app-manifest.ts +82 -68
  23. package/.framework/functions/_shared/audit.ts +135 -83
  24. package/.framework/functions/_shared/{cil-state.ts → cil.ts} +147 -54
  25. package/.framework/functions/_shared/db.ts +80 -126
  26. package/.framework/functions/_shared/hash-api-key.ts +63 -0
  27. package/.framework/functions/_shared/index.ts +222 -347
  28. package/.framework/functions/_shared/kb.ts +319 -0
  29. package/.framework/functions/_shared/llm.ts +92 -0
  30. package/.framework/functions/_shared/logging.ts +288 -0
  31. package/.framework/functions/_shared/middleware.ts +329 -365
  32. package/.framework/functions/_shared/outcomes.ts +36 -0
  33. package/.framework/functions/_shared/permissions.ts +359 -643
  34. package/.framework/functions/_shared/pipeline-runner.ts +243 -316
  35. package/.framework/functions/_shared/principal.ts +376 -468
  36. package/.framework/functions/_shared/resolve-credentials.ts +102 -0
  37. package/.framework/functions/_shared/resolve-ids.ts +162 -14
  38. package/.framework/functions/_shared/retry-deliveries.ts +104 -0
  39. package/.framework/functions/_shared/schema-utils.ts +611 -373
  40. package/.framework/functions/_shared/testing.ts +207 -137
  41. package/.framework/functions/_shared/tracing.ts +276 -0
  42. package/.framework/functions/_shared/trigger-engine.ts +159 -211
  43. package/.framework/functions/_shared/webhook-registration.ts +95 -81
  44. package/.framework/functions/_shared/webhook-registry.ts +153 -75
  45. package/.framework/functions/account-nodes.ts +31 -53
  46. package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
  47. package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
  48. package/.framework/functions/adapters/openai-outbound.ts +40 -0
  49. package/.framework/functions/admin-data.ts +687 -533
  50. package/.framework/functions/admin-funnel.ts +31 -20
  51. package/.framework/functions/admin-install-funnel.ts +47 -28
  52. package/.framework/functions/admin-signals.ts +47 -29
  53. package/.framework/functions/ai-agents.ts +122 -147
  54. package/.framework/functions/ai-interactions.ts +180 -90
  55. package/.framework/functions/api-keys.ts +250 -192
  56. package/.framework/functions/apps.ts +203 -197
  57. package/.framework/functions/auth.ts +98 -91
  58. package/.framework/functions/cil-agent-ingest.ts +773 -0
  59. package/.framework/functions/cil-agent.ts +592 -0
  60. package/.framework/functions/cil-callmap.ts +1227 -0
  61. package/.framework/functions/cil-config.ts +583 -0
  62. package/.framework/functions/cil-context.ts +445 -0
  63. package/.framework/functions/cil-doc.ts +1752 -0
  64. package/.framework/functions/cil-embeddings.ts +1202 -0
  65. package/.framework/functions/cil-graph.ts +585 -0
  66. package/.framework/functions/cil-ingest.ts +2416 -0
  67. package/.framework/functions/cil-pipeline.ts +318 -0
  68. package/.framework/functions/cil-project.ts +499 -0
  69. package/.framework/functions/cil-validate.ts +469 -0
  70. package/.framework/functions/debug-auth.ts +73 -53
  71. package/.framework/functions/deliveries.ts +117 -0
  72. package/.framework/functions/embeddings.ts +528 -516
  73. package/.framework/functions/eval-harness.ts +158 -106
  74. package/.framework/functions/integration-outbound.ts +311 -0
  75. package/.framework/functions/integration-routes.ts +240 -466
  76. package/.framework/functions/integrations.ts +132 -140
  77. package/.framework/functions/invites.ts +401 -201
  78. package/.framework/functions/item-progress.ts +113 -53
  79. package/.framework/functions/kb.ts +28 -0
  80. package/.framework/functions/logs.ts +239 -222
  81. package/.framework/functions/observability.ts +308 -259
  82. package/.framework/functions/pipeline-executions.ts +179 -235
  83. package/.framework/functions/pipelines.ts +163 -205
  84. package/.framework/functions/prompt-configs.ts +109 -156
  85. package/.framework/functions/roles.ts +97 -147
  86. package/.framework/functions/system-cron.ts +132 -218
  87. package/.framework/functions/system.ts +112 -52
  88. package/.framework/functions/tests.ts +32 -28
  89. package/.framework/functions/timers.ts +162 -210
  90. package/.framework/functions/triggers.ts +282 -346
  91. package/.framework/functions/types.ts +283 -400
  92. package/.framework/migrations/001_core_schema.sql +630 -0
  93. package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
  94. package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
  95. package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
  96. package/.framework/migrations/005_core_observability.sql +598 -0
  97. package/.framework/migrations/006_security_hardening_rls.sql +97 -0
  98. package/.framework/migrations/007_kb_article_list.sql +237 -0
  99. package/.framework/migrations/008_items_target.sql +8 -0
  100. package/.framework/migrations/009_roles_slug_unique.sql +11 -0
  101. package/.framework/public/spine.config.json +91 -0
  102. package/.framework/scripts/app-install-cli.ts +112 -98
  103. package/.framework/src/App.tsx +67 -20
  104. package/.framework/src/apps/admin/index.tsx +97 -27
  105. package/.framework/src/components/AppWrapper.tsx +50 -11
  106. package/.framework/src/components/CustomAppLoader.tsx +131 -22
  107. package/.framework/src/components/admin/AdminListPage.tsx +75 -8
  108. package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
  109. package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
  110. package/.framework/src/components/admin/PageInstructions.tsx +49 -0
  111. package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
  112. package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
  113. package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
  114. package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
  115. package/.framework/src/components/app-sidebar.tsx +50 -3
  116. package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
  117. package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
  118. package/.framework/src/components/kb/KBArticleList.tsx +557 -0
  119. package/.framework/src/components/layout/AppShell.tsx +54 -16
  120. package/.framework/src/components/layout/Header.tsx +50 -38
  121. package/.framework/src/components/layout/Layout.tsx +48 -32
  122. package/.framework/src/components/layout/Sidebar.tsx +167 -16
  123. package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
  124. package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
  125. package/.framework/src/components/runtime/DataFilters.tsx +78 -1
  126. package/.framework/src/components/runtime/DataHeader.tsx +32 -29
  127. package/.framework/src/components/runtime/DataListPage.tsx +161 -2
  128. package/.framework/src/components/runtime/DataStats.tsx +48 -1
  129. package/.framework/src/components/runtime/DataTable.tsx +153 -1
  130. package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
  131. package/.framework/src/components/runtime/index.ts +16 -9
  132. package/.framework/src/components/search-form.tsx +33 -1
  133. package/.framework/src/components/shared/AgentView.tsx +48 -51
  134. package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
  135. package/.framework/src/components/shared/InviteManager.tsx +113 -52
  136. package/.framework/src/components/shared/MemberList.tsx +182 -0
  137. package/.framework/src/components/shared/SchemaFields.tsx +96 -78
  138. package/.framework/src/components/ui/DataTable.tsx +86 -56
  139. package/.framework/src/components/ui/Form.tsx +188 -109
  140. package/.framework/src/components/ui/ItemCard.tsx +86 -58
  141. package/.framework/src/components/ui/ItemListView.tsx +88 -61
  142. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
  143. package/.framework/src/components/ui/Modal.tsx +81 -4
  144. package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
  145. package/.framework/src/components/ui/accordion.tsx +97 -5
  146. package/.framework/src/components/ui/alert-dialog.tsx +225 -13
  147. package/.framework/src/components/ui/alert.tsx +113 -6
  148. package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
  149. package/.framework/src/components/ui/avatar.tsx +129 -7
  150. package/.framework/src/components/ui/badge.tsx +65 -3
  151. package/.framework/src/components/ui/breadcrumb.tsx +145 -8
  152. package/.framework/src/components/ui/button-group.tsx +81 -4
  153. package/.framework/src/components/ui/button.tsx +49 -2
  154. package/.framework/src/components/ui/calendar.tsx +65 -3
  155. package/.framework/src/components/ui/card.tsx +145 -8
  156. package/.framework/src/components/ui/carousel.tsx +129 -7
  157. package/.framework/src/components/ui/chart.tsx +161 -10
  158. package/.framework/src/components/ui/checkbox.tsx +49 -2
  159. package/.framework/src/components/ui/collapsible.tsx +81 -5
  160. package/.framework/src/components/ui/combobox.tsx +305 -20
  161. package/.framework/src/components/ui/command.tsx +177 -11
  162. package/.framework/src/components/ui/context-menu.tsx +273 -17
  163. package/.framework/src/components/ui/dialog.tsx +209 -27
  164. package/.framework/src/components/ui/direction.tsx +64 -4
  165. package/.framework/src/components/ui/drawer.tsx +193 -12
  166. package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
  167. package/.framework/src/components/ui/empty.tsx +145 -9
  168. package/.framework/src/components/ui/field.tsx +209 -14
  169. package/.framework/src/components/ui/hover-card.tsx +81 -4
  170. package/.framework/src/components/ui/input-group.tsx +162 -10
  171. package/.framework/src/components/ui/input-otp.tsx +98 -5
  172. package/.framework/src/components/ui/input.tsx +49 -2
  173. package/.framework/src/components/ui/item.tsx +225 -13
  174. package/.framework/src/components/ui/kbd.tsx +65 -3
  175. package/.framework/src/components/ui/label.tsx +49 -2
  176. package/.framework/src/components/ui/menubar.tsx +289 -17
  177. package/.framework/src/components/ui/native-select.tsx +97 -5
  178. package/.framework/src/components/ui/navigation-menu.tsx +177 -10
  179. package/.framework/src/components/ui/pagination.tsx +161 -9
  180. package/.framework/src/components/ui/popover.tsx +145 -8
  181. package/.framework/src/components/ui/progress.tsx +49 -2
  182. package/.framework/src/components/ui/radio-group.tsx +65 -3
  183. package/.framework/src/components/ui/resizable.tsx +82 -4
  184. package/.framework/src/components/ui/scroll-area.tsx +65 -3
  185. package/.framework/src/components/ui/select.tsx +193 -11
  186. package/.framework/src/components/ui/separator.tsx +49 -2
  187. package/.framework/src/components/ui/sheet.tsx +193 -11
  188. package/.framework/src/components/ui/sidebar.tsx +481 -38
  189. package/.framework/src/components/ui/skeleton.tsx +49 -2
  190. package/.framework/src/components/ui/slider.tsx +49 -2
  191. package/.framework/src/components/ui/sonner.tsx +49 -2
  192. package/.framework/src/components/ui/spinner.tsx +49 -2
  193. package/.framework/src/components/ui/switch.tsx +49 -2
  194. package/.framework/src/components/ui/table-primitives.tsx +160 -33
  195. package/.framework/src/components/ui/table.tsx +161 -9
  196. package/.framework/src/components/ui/tabs.tsx +113 -6
  197. package/.framework/src/components/ui/textarea.tsx +49 -2
  198. package/.framework/src/components/ui/toggle-group.tsx +81 -4
  199. package/.framework/src/components/ui/toggle.tsx +65 -3
  200. package/.framework/src/components/ui/tooltip.tsx +97 -5
  201. package/.framework/src/contexts/AppContext.tsx +129 -45
  202. package/.framework/src/contexts/AuthContext.tsx +173 -145
  203. package/.framework/src/hooks/use-mobile.ts +48 -2
  204. package/.framework/src/hooks/useApi.ts +208 -159
  205. package/.framework/src/hooks/useAppPath.ts +32 -23
  206. package/.framework/src/hooks/useApps.ts +51 -20
  207. package/.framework/src/hooks/useEntityList.ts +32 -78
  208. package/.framework/src/hooks/useEntityRecord.ts +35 -96
  209. package/.framework/src/hooks/useForm.ts +32 -92
  210. package/.framework/src/hooks/useListSchema.ts +49 -78
  211. package/.framework/src/hooks/useSchemaRecord.ts +114 -105
  212. package/.framework/src/lib/api.ts +192 -115
  213. package/.framework/src/lib/supabase.ts +98 -64
  214. package/.framework/src/lib/utils.ts +287 -151
  215. package/.framework/src/main.tsx +49 -3
  216. package/.framework/src/pages/DashboardPage.tsx +52 -29
  217. package/.framework/src/pages/NotFoundPage.tsx +32 -10
  218. package/.framework/src/pages/SetupPage.tsx +32 -12
  219. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
  220. package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
  221. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
  222. package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
  223. package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
  224. package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
  225. package/.framework/src/pages/admin/AppsPage.tsx +49 -2
  226. package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
  227. package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
  228. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
  229. package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
  230. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
  231. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
  232. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
  233. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
  234. package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
  235. package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
  236. package/.framework/src/pages/admin/LogsPage.tsx +422 -75
  237. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
  238. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
  239. package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
  240. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
  241. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
  242. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
  243. package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
  244. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
  245. package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
  246. package/.framework/src/pages/admin/RolesPage.tsx +171 -18
  247. package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
  248. package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
  249. package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
  250. package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
  251. package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
  252. package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
  253. package/.framework/src/pages/admin/TimersPage.tsx +48 -20
  254. package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
  255. package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
  256. package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
  257. package/.framework/src/pages/admin/TypesPage.tsx +48 -20
  258. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
  259. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
  260. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
  261. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
  262. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
  263. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
  264. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
  265. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
  266. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
  267. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  268. package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
  269. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
  270. package/.framework/src/pages/auth/LoginPage.tsx +50 -23
  271. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
  272. package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
  273. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
  274. package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
  275. package/.framework/src/types/auth.ts +128 -71
  276. package/.framework/src/types/types.ts +373 -157
  277. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
  278. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
  279. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
  280. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
  281. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
  282. package/docs/README.md +9 -130
  283. package/netlify.toml +3 -3
  284. package/package.json +13 -25
  285. package/scripts/kb-ingest-docs.ts +203 -0
  286. package/spine.config.json +65 -4
  287. package/.framework/migrations/001_schema.sql +0 -425
  288. package/.framework/migrations/002_rls_and_functions.sql +0 -763
  289. package/.framework/migrations/004_enhancements.sql +0 -43
  290. package/.framework/migrations/005_auth_user_trigger.sql +0 -105
  291. package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
  292. package/.framework/migrations/016_invites.sql +0 -27
  293. package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
  294. package/.framework/migrations/018_apps_required_roles.sql +0 -17
  295. package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
  296. package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
  297. package/.framework/migrations/021_app_domains.sql +0 -112
  298. package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
  299. package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
  300. package/.framework/migrations/024_messages_account_id.sql +0 -9
  301. package/.framework/migrations/025_retype_threads_messages.sql +0 -240
  302. package/.framework/migrations/026_fix_rls_policies.sql +0 -47
  303. package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
  304. package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
  305. package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
  306. package/.framework/migrations/030_messages_updated_by.sql +0 -6
  307. package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
  308. package/docs/APPS.md +0 -232
  309. package/docs/ARCHITECTURE.md +0 -246
  310. package/docs/INSTALL.md +0 -155
  311. package/docs/design-schema-spec.md +0 -1837
  312. package/docs/dev-guides/01-create-an-app.md +0 -183
  313. package/docs/dev-guides/02-create-a-function.md +0 -178
  314. package/docs/dev-guides/03-create-a-component.md +0 -178
  315. package/docs/dev-guides/04-webhook-handlers.md +0 -129
  316. package/docs/dev-guides/05-testing.md +0 -168
  317. package/docs/dev-guides/README.md +0 -40
  318. package/docs/npm-packages-plan.md +0 -288
  319. package/docs/spine-core--api-reference.md +0 -822
@@ -1,22 +1,85 @@
1
- /// <reference types="node" />
2
- /**
3
- * @module cli/commands/install-app
4
- * @audience installer
5
- * @layer cli
6
- * @stability stable
7
- *
8
- * `spine-framework install-app` — installs a Spine Framework app from npm.
9
- * Copies app files into custom/apps/, seeds types/roles/link-types into the DB.
10
- *
11
- * **Usage:**
12
- * ```bash
13
- * npx spine-framework install-app spine-framework-portal@latest
14
- * npx spine-framework install-app spine-framework-cortex
15
- * npx spine-framework uninstall-app spine-framework-portal
16
- * npx spine-framework update-app spine-framework-portal
17
- * ```
18
- */
19
1
 
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+ // CIL[BEGIN chunk_id="install-app_registrationconfig_1784069813" type="interface" identity="RegistrationConfig" parent="none"]
12
+ // CIL[DOC-MACRO purpose="Define registration configuration for an installed app"]
13
+ // CIL[DOC-RESPONSIBILITY responsibility="Own the RegistrationConfig interface definition"]
14
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[]"]
15
+ // CIL[DOC-CONTRACT inputs="none" outputs="interface definition" errors="none" side_effects="none"]
16
+ // CIL[DOC-MICRO logic_summary="Defines registration settings including default role, redirect path, and account strategy" notable_decisions="[]"]
17
+ // CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
18
+ // CIL[DOC-STABILITY stability="stable"]
19
+ // CIL[DOC-AUDIENCE audience="developer"]
20
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 0. Direct calls: 0. Related files: none."]
21
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
22
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
23
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
24
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
25
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
26
+ interface RegistrationConfig {
27
+ enabled: boolean
28
+ default_role: string
29
+ redirect_path: string
30
+ account_strategy?: 'existing' | 'new' | 'choice'
31
+ target_account?: string
32
+ }
33
+ // CIL[END chunk_id="install-app_registrationconfig_1784069813"]
34
+
35
+ // CIL[BEGIN chunk_id="install-app_spineconfig_1784069813" type="interface" identity="SpineConfig" parent="none"]
36
+ // CIL[DOC-MACRO purpose="Define spine.config.json structure for registration"]
37
+ // CIL[DOC-RESPONSIBILITY responsibility="Own the SpineConfig interface definition"]
38
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[]"]
39
+ // CIL[DOC-CONTRACT inputs="none" outputs="interface definition" errors="none" side_effects="none"]
40
+ // CIL[DOC-MICRO logic_summary="Defines registration config with default app/role, URL override flag, and apps map" notable_decisions="[]"]
41
+ // CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
42
+ // CIL[DOC-STABILITY stability="stable"]
43
+ // CIL[DOC-AUDIENCE audience="developer"]
44
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 0. Direct calls: 0. Related files: none."]
45
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
46
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
47
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
48
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
49
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
50
+ interface SpineConfig {
51
+ registration?: {
52
+ default_app?: string
53
+ default_role?: string
54
+ allow_url_override?: boolean
55
+ default_app_by_role?: Record<string, string>
56
+ apps?: Record<string, {
57
+ roles: string[]
58
+ default_role: string
59
+ path: string
60
+ enabled: boolean
61
+ account_strategy: 'existing' | 'new' | 'choice'
62
+ target_account?: string
63
+ }>
64
+ }
65
+ }
66
+ // CIL[END chunk_id="install-app_spineconfig_1784069813"]
67
+
68
+ // CIL[BEGIN chunk_id="install-app_moduleheader_1784069813" type="module_header" identity="module_header" parent="none"]
69
+ // CIL[DOC-MACRO purpose="Module imports for app installation CLI"]
70
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide filesystem, database, and CLI utilities"]
71
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[\".framework/migrations/015_simplify_apps_table.sql\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"custom/apps/portal/pages/ContentPage.tsx\",\".framework/src/pages/admin/TypeDetailPage.tsx\",\"\\\"]"]
72
+ // CIL[DOC-CONTRACT inputs="none" outputs="imports" errors="none" side_effects="none"]
73
+ // CIL[DOC-MICRO logic_summary="Imports Node.js modules, Supabase client, and migration utilities" notable_decisions="[]"]
74
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
75
+ // CIL[DOC-STABILITY stability="stable"]
76
+ // CIL[DOC-AUDIENCE audience="developer"]
77
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 0. Direct calls: 0. Related files: .framework/migrations/015_simplify_apps_table.sql, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, custom/apps/portal/pages/ContentPage.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, \."]
78
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
79
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
80
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
81
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
82
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
20
83
  import type { Command } from 'commander'
21
84
  import { existsSync, readFileSync, mkdirSync, cpSync, writeFileSync } from 'fs'
22
85
  import { resolve, dirname, join, basename } from 'path'
@@ -29,9 +92,26 @@ import { runMigrations } from './migrate.js'
29
92
  const __filename = fileURLToPath(import.meta.url)
30
93
  const __dirname = dirname(__filename)
31
94
  const PROJECT_ROOT = process.cwd()
32
-
95
+ const SPINE_CONFIG_PATH = join(PROJECT_ROOT, 'spine.config.json')
96
+ // CIL[END chunk_id="install-app_moduleheader_1784069813"]
97
+ // CIL[BEGIN chunk_id="install-app_registerinstallappcommands_1784069813" type="function" identity="registerInstallAppCommands" parent="none"]
98
+ // CIL[DOC-LEARNING topic="install-app:two_phase_install" content="install-app is two phases: npm copy then DB seed. If seed fails or is skipped, custom/apps/<slug> files may exist while the app is absent from public.apps, so it will not appear in the app registry."]
99
+ // CIL[DOC-MACRO purpose="Register app management CLI commands"]
100
+ // CIL[DOC-RESPONSIBILITY responsibility="Provide install, uninstall, update, and sync operations"]
101
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[\".framework/migrations/015_simplify_apps_table.sql\",\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\"\\\"]"]
102
+ // CIL[DOC-CONTRACT inputs="none" outputs="CLI commands" errors="npm, filesystem, database errors" side_effects="filesystem writes, database writes"]
103
+ // CIL[DOC-NOTE type="manifest_config_sync" content="install-app and update-db-app now persist manifest.config and manifest.metadata into the apps table's config/metadata JSONB columns."]
104
+ // CIL[DOC-MICRO logic_summary="Registers install-app, uninstall-app, update-app, and update-db-app commands; syncs manifest config/metadata into apps table" notable_decisions="[]"]
105
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
106
+ // CIL[DOC-STABILITY stability="stable"]
107
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 0. Direct calls: 0. Related files: .framework/migrations/015_simplify_apps_table.sql, custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, custom/apps/cortex/functions/custom_cil-embeddings.ts, \."]
108
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
109
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
110
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
111
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
112
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
33
113
  export function registerInstallAppCommands(program: Command) {
34
- // install-app
114
+
35
115
  program
36
116
  .command('install-app <package>')
37
117
  .description('Install a Spine Framework app from npm')
@@ -42,7 +122,7 @@ export function registerInstallAppCommands(program: Command) {
42
122
  loadEnv()
43
123
 
44
124
  try {
45
- // 1. npm install the package
125
+
46
126
  console.log(` Installing ${packageName} via npm...`)
47
127
  execSync(`npm install ${packageName} --save`, {
48
128
  cwd: PROJECT_ROOT,
@@ -50,7 +130,7 @@ export function registerInstallAppCommands(program: Command) {
50
130
  })
51
131
  console.log(' ✓ npm install complete')
52
132
 
53
- // 2. Resolve package location in node_modules
133
+
54
134
  const pkgSlug = packageName.split('@')[0] // strip version
55
135
  const pkgPath = join(PROJECT_ROOT, 'node_modules', pkgSlug)
56
136
 
@@ -60,20 +140,18 @@ export function registerInstallAppCommands(program: Command) {
60
140
 
61
141
  console.log(` Package path: ${pkgPath}`)
62
142
 
63
- // 3. Determine app slug from package manifest or name
143
+
64
144
  const appSlug = resolveAppSlug(pkgPath, pkgSlug)
65
145
  console.log(` App slug: ${appSlug}`)
66
146
 
67
- // 4. Copy app files to custom/apps/<slug>/
147
+
68
148
  const appDest = join(PROJECT_ROOT, 'custom/apps', appSlug)
69
149
  const isReinstall = existsSync(appDest)
70
150
  if (!isReinstall) {
71
151
  mkdirSync(appDest, { recursive: true })
72
152
  }
73
153
 
74
- // On reinstall, preserve the developer's local manifest.json — it is
75
- // the source of truth for route_prefix and other project-level config.
76
- // All other files are updated from npm as normal.
154
+
77
155
  const allowedEntries = [
78
156
  'index.tsx', 'manifest.json', 'package.json',
79
157
  'pages', 'components', 'hooks', 'utils', 'functions', 'seed', 'public'
@@ -85,7 +163,7 @@ export function registerInstallAppCommands(program: Command) {
85
163
  const src = join(pkgPath, entry)
86
164
  if (!existsSync(src)) continue
87
165
 
88
- // Preserve local manifest on reinstall — developer owns it after first install
166
+
89
167
  if (entry === 'manifest.json' && isReinstall) {
90
168
  console.log(` Preserving local manifest.json (use update-db-app to sync changes)`)
91
169
  continue
@@ -96,7 +174,7 @@ export function registerInstallAppCommands(program: Command) {
96
174
  }
97
175
  console.log(` ✓ App files copied to custom/apps/${appSlug}/`)
98
176
 
99
- // 5. Run pending migrations before seeding so schema is up to date
177
+
100
178
  console.log(' Running migrations...')
101
179
  try {
102
180
  const migrateResult = await runMigrations({ frameworkOnly: false })
@@ -114,17 +192,17 @@ export function registerInstallAppCommands(program: Command) {
114
192
  console.warn(` ⚠ Migration error: ${migrateErr.message} — continuing with seed`)
115
193
  }
116
194
 
117
- // 6. Seed types, roles, link-types into database
195
+
118
196
  if (!opts.seed === false) {
119
197
  await seedApp(appDest, appSlug)
120
198
  }
121
199
 
122
- // 7. Aggregate registration config from manifest into spine.config.json
200
+
123
201
  const manifestPath = join(appDest, 'manifest.json')
124
202
  let manifest: Record<string, any> = {}
125
203
  try { manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) } catch {}
126
204
  if (manifest.registration?.enabled) {
127
- updateRegistrationConfig(appSlug, manifest.registration)
205
+ updateRegistrationConfig(appSlug, manifest.registration, manifest.required_roles ?? [])
128
206
  }
129
207
 
130
208
  let installedVersion = 'unknown'
@@ -140,7 +218,7 @@ export function registerInstallAppCommands(program: Command) {
140
218
  }
141
219
  })
142
220
 
143
- // uninstall-app
221
+
144
222
  program
145
223
  .command('uninstall-app <package>')
146
224
  .description('Remove an installed Spine Framework app')
@@ -173,7 +251,7 @@ export function registerInstallAppCommands(program: Command) {
173
251
  }
174
252
  })
175
253
 
176
- // update-app
254
+
177
255
  program
178
256
  .command('update-app <package>')
179
257
  .description('Update an installed Spine Framework app to a newer version')
@@ -203,12 +281,12 @@ export function registerInstallAppCommands(program: Command) {
203
281
  }
204
282
  }
205
283
 
206
- // Update registration config from manifest
284
+
207
285
  const manifestPath = join(appDest, 'manifest.json')
208
286
  let manifest: Record<string, any> = {}
209
287
  try { manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) } catch {}
210
288
  if (manifest.registration?.enabled) {
211
- updateRegistrationConfig(appSlug, manifest.registration)
289
+ updateRegistrationConfig(appSlug, manifest.registration, manifest.required_roles ?? [])
212
290
  }
213
291
 
214
292
  let updatedVersion = 'unknown'
@@ -221,7 +299,7 @@ export function registerInstallAppCommands(program: Command) {
221
299
  }
222
300
  })
223
301
 
224
- // update-db-app
302
+
225
303
  program
226
304
  .command('update-db-app <slug>')
227
305
  .description('Sync local manifest.json changes to the database (no npm, no file changes)')
@@ -274,6 +352,8 @@ export function registerInstallAppCommands(program: Command) {
274
352
  min_role: manifest.required_roles?.[0] ?? manifest.min_role ?? null,
275
353
  preview_domain: manifest.preview_domain ?? null,
276
354
  prod_domain: manifest.prod_domain ?? null,
355
+ config: manifest.config ?? undefined,
356
+ metadata: manifest.metadata ?? undefined,
277
357
  },
278
358
  { onConflict: 'slug' }
279
359
  )
@@ -285,30 +365,56 @@ export function registerInstallAppCommands(program: Command) {
285
365
  process.exit(1)
286
366
  }
287
367
 
288
- console.log(` ✓ apps row updated (route_prefix: ${manifest.route_prefix ?? '/' + slug})`)
368
+ console.log(` ✓ apps row updated (route_prefix: ${manifest.route_prefix ?? '/' + slug}, config: ${manifest.config ? 'synced' : 'none'})`)
289
369
 
290
- // Update registration config in spine.config.json if declared
370
+
291
371
  if (manifest.registration?.enabled) {
292
- updateRegistrationConfig(slug, manifest.registration)
372
+ updateRegistrationConfig(slug, manifest.registration, manifest.required_roles ?? [])
293
373
  }
294
374
 
295
375
  console.log(`\n✅ ${slug} DB synced from local manifest.\n`)
296
376
  console.log(`Next: npm run assemble && netlify dev\n`)
297
377
  })
298
378
  }
299
-
300
- // ─── HELPERS ────────────────────────────────────────────────────────────────
301
-
379
+ // CIL[END chunk_id="install-app_registerinstallappcommands_1784069813"]
380
+ // CIL[BEGIN chunk_id="install-app_resolveappslug_1784069813" type="function" identity="resolveAppSlug" parent="none"]
381
+ // CIL[DOC-MACRO purpose="Resolve app slug from package name or manifest"]
382
+ // CIL[DOC-RESPONSIBILITY responsibility="Extract app identifier from package metadata"]
383
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[\".framework/migrations/015_simplify_apps_table.sql\",\"custom/apps/portal/pages/ContentPage.tsx\",\"custom/apps/cortex/pages/operations/OperationsDashboard.tsx\",\"custom/apps/cortex/functions/custom_cil-embeddings.ts\",\"\\\"]"]
384
+ // CIL[DOC-CONTRACT inputs="none" outputs="app slug" errors="file read errors" side_effects="none"]
385
+ // CIL[DOC-MICRO logic_summary="Reads manifest.json slug field or strips spine-framework- prefix" notable_decisions="[]"]
386
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
387
+ // CIL[DOC-STABILITY stability="stable"]
388
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 1. Direct calls: 0. Related files: .framework/migrations/015_simplify_apps_table.sql, custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, custom/apps/cortex/functions/custom_cil-embeddings.ts, \."]
389
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 1. Total affected: 1."]
390
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
391
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
392
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
393
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
302
394
  function resolveAppSlug(pkgPath: string, pkgName: string): string {
303
395
  try {
304
396
  const manifest = JSON.parse(readFileSync(join(pkgPath, 'manifest.json'), 'utf8'))
305
397
  if (manifest.slug) return manifest.slug
306
398
  } catch {}
307
- // Derive from package name: spine-framework-portal → portal
399
+
308
400
  const slug = pkgName.replace(/^spine-framework-/, '')
309
401
  return slug || pkgName // fallback to full name if replacement results in empty
310
402
  }
311
-
403
+ // CIL[END chunk_id="install-app_resolveappslug_1784069813"]
404
+ // CIL[BEGIN chunk_id="install-app_loadenv_1784069813" type="function" identity="loadEnv" parent="none"]
405
+ // CIL[DOC-MACRO purpose="Load environment variables from .env file"]
406
+ // CIL[DOC-RESPONSIBILITY responsibility="Parse and set environment variables"]
407
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[\"\\\"]"]
408
+ // CIL[DOC-CONTRACT inputs="none" outputs="process.env updates" errors="file read errors" side_effects="process.env mutation"]
409
+ // CIL[DOC-MICRO logic_summary="Reads .env file, parses key=value pairs, sets process.env" notable_decisions="[]"]
410
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
411
+ // CIL[DOC-STABILITY stability="stable"]
412
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 1. Direct calls: 0. Related files: \."]
413
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 1. Total affected: 1."]
414
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
415
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
416
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
417
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
312
418
  function loadEnv() {
313
419
  const envPath = join(PROJECT_ROOT, '.env')
314
420
  if (!existsSync(envPath)) return
@@ -323,7 +429,25 @@ function loadEnv() {
323
429
  if (key && !(key in process.env)) process.env[key] = val
324
430
  }
325
431
  }
326
-
432
+ // CIL[END chunk_id="install-app_loadenv_1784069813"]
433
+ // CIL[BEGIN chunk_id="install-app_seedapp_1784069813" type="function" identity="seedApp" parent="none"]
434
+ // CIL[DOC-LEARNING topic="install-app:seed_requires_env" content="DB seed requires SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in .env; missing env vars cause install-app to copy files without registering the app in public.apps."]
435
+ // CIL[DOC-LEARNING topic="install-app:types_unique_slug" content="public.types must enforce a unique slug. Seeding types per app (inject_app_id with conflict 'app_id,kind,slug') created duplicate funnel_signal, support_ticket, etc. rows, breaking resolveTypeId and .single() lookups. Types are now seeded with conflict 'kind,slug', inject_app_id: false, and ignoreDuplicates to keep them global."]
436
+ // CIL[DOC-NOTE type="caution" content="The apps upsert uses onConflict: 'slug' and can still fail on the active route_prefix unique index if another active app already owns the same route_prefix."]
437
+ // CIL[DOC-NOTE type="caution" content="types seeds must not inject app_id. The types table uses slug as the global type identifier; app-scoped duplicates cause lookup ambiguity."]
438
+ // CIL[DOC-MACRO purpose="Seed database with app types, roles, and permissions"]
439
+ // CIL[DOC-RESPONSIBILITY responsibility="Populate database with app-specific seed data"]
440
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[]"]
441
+ // CIL[DOC-CONTRACT inputs="none" outputs="database records" errors="database errors" side_effects="database writes, app config sync from manifest"]
442
+ // CIL[DOC-MICRO logic_summary="Registers app (including manifest.config/metadata), seeds roles/types, handles permission patches with user prompts" notable_decisions="[]"]
443
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="complex_database_operations"]
444
+ // CIL[DOC-STABILITY stability="stable"]
445
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 0. Direct calls: 0. Related files: none."]
446
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
447
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
448
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
449
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
450
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
327
451
  async function seedApp(appDir: string, appSlug: string) {
328
452
  const supabaseUrl = process.env.SUPABASE_URL
329
453
  const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY
@@ -337,9 +461,7 @@ async function seedApp(appDir: string, appSlug: string) {
337
461
  auth: { persistSession: false },
338
462
  })
339
463
 
340
- // Upsert the app row first so we always have a real app_id UUID.
341
- // apps.slug is UNIQUE NOT NULL, making it the only reliable conflict target
342
- // at install time (we have no UUID yet).
464
+
343
465
  const manifestPath = join(appDir, 'manifest.json')
344
466
  let manifest: Record<string, any> = {}
345
467
  try { manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) } catch {}
@@ -358,12 +480,14 @@ async function seedApp(appDir: string, appSlug: string) {
358
480
  is_active: true,
359
481
  route_prefix: manifest.route_prefix ?? ('/' + appSlug),
360
482
  renderer: manifest.renderer ?? 'custom',
361
- // required_roles: full array from manifest (e.g. ["support", "support_admin"])
483
+
362
484
  required_roles: manifest.required_roles ?? (manifest.min_role ? [manifest.min_role] : []),
363
- // min_role: kept as legacy fallback — first entry of required_roles
485
+
364
486
  min_role: manifest.required_roles?.[0] ?? manifest.min_role ?? null,
365
487
  preview_domain: manifest.preview_domain ?? null,
366
488
  prod_domain: manifest.prod_domain ?? null,
489
+ config: manifest.config ?? undefined,
490
+ metadata: manifest.metadata ?? undefined,
367
491
  },
368
492
  { onConflict: 'slug' }
369
493
  )
@@ -377,37 +501,29 @@ async function seedApp(appDir: string, appSlug: string) {
377
501
  }
378
502
 
379
503
  const appId = appRow.id
380
- console.log(` ✓ App registered (id: ${appId})`)
504
+ console.log(` ✓ App registered (id: ${appId}, config: ${manifest.config ? 'synced' : 'none'})`)
381
505
 
382
- // ── Manifest-driven seed loop ─────────────────────────────────────────────
383
- // If manifest declares a `seed` array, use it. Each entry:
384
- // { file, table, conflict, inject_app_id? }
385
- // inject_app_id defaults to true — set false for tables with no app_id column.
386
- //
387
- // Fallback: if no manifest.seed, use legacy hardcoded behavior so existing
388
- // apps without a seed block continue to work.
389
- // ─────────────────────────────────────────────────────────────────────────
506
+
390
507
 
391
508
  interface SeedEntry {
392
509
  file: string
393
510
  table: string
394
511
  conflict: string
395
512
  inject_app_id?: boolean
396
- // When true: only patch design_schema.record_permissions on existing types.
397
- // The record must have { kind, slug, record_permissions } — no app_id injection.
513
+ ignore_duplicates?: boolean
398
514
  permissions_patch?: boolean
399
515
  }
400
516
 
401
517
  const seedEntries: SeedEntry[] = Array.isArray(manifest.seed)
402
518
  ? manifest.seed
403
519
  : [
404
- // Legacy fallback — matches pre-019 behavior
405
- { file: 'seed/roles.json', table: 'roles', conflict: 'app_id,slug' },
406
- { file: 'seed/types.json', table: 'types', conflict: 'app_id,kind,slug' },
520
+
521
+ { file: 'seed/roles.json', table: 'roles', conflict: 'slug' },
522
+ { file: 'seed/types.json', table: 'types', conflict: 'kind,slug', inject_app_id: false },
407
523
  { file: 'seed/link-types.json', table: 'link_types', conflict: 'app_id,slug' },
408
524
  ]
409
525
 
410
- // Always append the permissions patch file if it exists, unless already declared
526
+
411
527
  const hasPermissionsPatch = seedEntries.some(e => e.permissions_patch)
412
528
  if (!hasPermissionsPatch) {
413
529
  seedEntries.push({
@@ -439,7 +555,7 @@ async function seedApp(appDir: string, appSlug: string) {
439
555
  continue
440
556
  }
441
557
 
442
- // ── type_permissions.json: targeted record_permissions patch with check-and-choose ──
558
+
443
559
  if (entry.permissions_patch) {
444
560
  console.log(` Patching record_permissions for ${records.length} type(s) from ${entry.file}...`)
445
561
  for (const record of records) {
@@ -449,7 +565,7 @@ async function seedApp(appDir: string, appSlug: string) {
449
565
  continue
450
566
  }
451
567
 
452
- // Fetch the existing type (match on kind+slug regardless of app_id)
568
+
453
569
  const { data: existing } = await db
454
570
  .from('types')
455
571
  .select('id, design_schema')
@@ -465,13 +581,13 @@ async function seedApp(appDir: string, appSlug: string) {
465
581
  const existingSchema = (existing.design_schema ?? {}) as Record<string, any>
466
582
  const existingPerms = existingSchema.record_permissions ?? null
467
583
 
468
- // Deep-equal check
584
+
469
585
  if (JSON.stringify(existingPerms) === JSON.stringify(newPerms)) {
470
586
  console.log(` ✓ ${kind}/${slug} record_permissions unchanged — skipped`)
471
587
  continue
472
588
  }
473
589
 
474
- // Show diff and prompt
590
+
475
591
  console.log(`\n Type: ${kind}/${slug}`)
476
592
  console.log(` Existing record_permissions:`)
477
593
  console.log(' ' + JSON.stringify(existingPerms, null, 2).replace(/\n/g, '\n '))
@@ -484,7 +600,7 @@ async function seedApp(appDir: string, appSlug: string) {
484
600
  continue
485
601
  }
486
602
 
487
- // Targeted JSON merge: only update record_permissions key in design_schema
603
+
488
604
  const { error: patchErr } = await db
489
605
  .from('types')
490
606
  .update({
@@ -502,7 +618,7 @@ async function seedApp(appDir: string, appSlug: string) {
502
618
  continue
503
619
  }
504
620
 
505
- // ── Standard seed: upsert whole record ────────────────────────────────────
621
+
506
622
  const injectAppId = entry.inject_app_id !== false // default true
507
623
  console.log(` Seeding ${records.length} record(s) from ${entry.file} → ${entry.table}...`)
508
624
 
@@ -510,7 +626,10 @@ async function seedApp(appDir: string, appSlug: string) {
510
626
  const row = injectAppId ? { ...record, app_id: appId } : { ...record }
511
627
  const { error } = await db
512
628
  .from(entry.table)
513
- .upsert(row, { onConflict: entry.conflict })
629
+ .upsert(row, {
630
+ onConflict: entry.conflict,
631
+ ignoreDuplicates: entry.table === 'types' || entry.ignore_duplicates === true,
632
+ })
514
633
  if (error) {
515
634
  const label = record.slug ?? record.name ?? JSON.stringify(record).slice(0, 40)
516
635
  console.warn(` ⚠ ${entry.table} "${label}": ${error.message}`)
@@ -520,9 +639,21 @@ async function seedApp(appDir: string, appSlug: string) {
520
639
  console.log(` ✓ ${entry.table} seeded`)
521
640
  }
522
641
  }
523
-
524
- // ─── PROMPT HELPER ──────────────────────────────────────────────────────────
525
-
642
+ // CIL[END chunk_id="install-app_seedapp_1784069813"]
643
+ // CIL[BEGIN chunk_id="install-app_prompt_1784069813" type="function" identity="prompt" parent="none"]
644
+ // CIL[DOC-MACRO purpose="Prompt user for input via command line"]
645
+ // CIL[DOC-RESPONSIBILITY responsibility="Handle interactive user input"]
646
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[]"]
647
+ // CIL[DOC-CONTRACT inputs="none" outputs="user answer" errors="readline errors" side_effects="stdin interaction"]
648
+ // CIL[DOC-MICRO logic_summary="Creates readline interface and returns user input as Promise" notable_decisions="[]"]
649
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
650
+ // CIL[DOC-STABILITY stability="stable"]
651
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 0. Direct calls: 0. Related files: none."]
652
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
653
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
654
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
655
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
656
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
526
657
  function prompt(question: string): Promise<string> {
527
658
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
528
659
  return new Promise(resolve => {
@@ -532,35 +663,21 @@ function prompt(question: string): Promise<string> {
532
663
  })
533
664
  })
534
665
  }
535
-
536
- // ─── SPINE CONFIG HELPERS ───────────────────────────────────────────────────
537
-
538
- interface RegistrationConfig {
539
- enabled: boolean
540
- default_role: string
541
- redirect_path: string
542
- account_strategy?: 'existing' | 'new' | 'choice'
543
- target_account?: string
544
- }
545
-
546
- interface SpineConfig {
547
- registration?: {
548
- default_app?: string
549
- default_role?: string
550
- allow_url_override?: boolean
551
- apps?: Record<string, {
552
- roles: string[]
553
- default_role: string
554
- path: string
555
- enabled: boolean
556
- account_strategy: 'existing' | 'new' | 'choice'
557
- target_account?: string
558
- }>
559
- }
560
- }
561
-
562
- const SPINE_CONFIG_PATH = join(PROJECT_ROOT, 'spine.config.json')
563
-
666
+ // CIL[END chunk_id="install-app_prompt_1784069813"]
667
+ // CIL[BEGIN chunk_id="install-app_loadspineconfig_1784069813" type="function" identity="loadSpineConfig" parent="none"]
668
+ // CIL[DOC-MACRO purpose="Load spine.config.json configuration file"]
669
+ // CIL[DOC-RESPONSIBILITY responsibility="Read and parse Spine configuration"]
670
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[]"]
671
+ // CIL[DOC-CONTRACT inputs="none" outputs="SpineConfig object" errors="file read errors" side_effects="none"]
672
+ // CIL[DOC-MICRO logic_summary="Reads spine.config.json with fallback to empty config" notable_decisions="[]"]
673
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
674
+ // CIL[DOC-STABILITY stability="stable"]
675
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 0. Direct calls: 0. Related files: none."]
676
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
677
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
678
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
679
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
680
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
564
681
  function loadSpineConfig(): SpineConfig {
565
682
  if (!existsSync(SPINE_CONFIG_PATH)) {
566
683
  return { registration: { apps: {} } }
@@ -571,15 +688,44 @@ function loadSpineConfig(): SpineConfig {
571
688
  return { registration: { apps: {} } }
572
689
  }
573
690
  }
574
-
691
+ // CIL[END chunk_id="install-app_loadspineconfig_1784069813"]
692
+ // CIL[BEGIN chunk_id="install-app_savespineconfig_1784069813" type="function" identity="saveSpineConfig" parent="none"]
693
+ // CIL[DOC-MACRO purpose="Save configuration to spine.config.json"]
694
+ // CIL[DOC-RESPONSIBILITY responsibility="Write Spine configuration to file"]
695
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[]"]
696
+ // CIL[DOC-CONTRACT inputs="none" outputs="file write" errors="file write errors" side_effects="filesystem write"]
697
+ // CIL[DOC-MICRO logic_summary="Writes configuration as formatted JSON to spine.config.json" notable_decisions="[]"]
698
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
699
+ // CIL[DOC-STABILITY stability="stable"]
700
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 0. Direct calls: 0. Related files: none."]
701
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
702
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
703
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
704
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
705
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
575
706
  function saveSpineConfig(config: SpineConfig): void {
576
707
  writeFileSync(SPINE_CONFIG_PATH, JSON.stringify(config, null, 2) + '\n')
577
708
  }
578
-
579
- function updateRegistrationConfig(appSlug: string, regConfig: RegistrationConfig): void {
709
+ // CIL[END chunk_id="install-app_savespineconfig_1784069813"]
710
+ // CIL[BEGIN chunk_id="install-app_updateregistrationconfig_1784069813" type="function" identity="updateRegistrationConfig" parent="none"]
711
+ // CIL[DOC-LEARNING topic="spine.config:registration_slug_match" content="spine.config.json registration.apps is keyed by app slug and default_app points to that key; the slug must match the public.apps row or post-login redirects will point to a non-routable app."]
712
+ // CIL[DOC-MACRO purpose="Update registration config for app in spine.config.json"]
713
+ // CIL[DOC-RESPONSIBILITY responsibility="Configure app registration and role settings"]
714
+ // CIL[DOC-CONTEXT module="cli.commands.install-app" layer="cli" related_files="[]"]
715
+ // CIL[DOC-CONTRACT inputs="none" outputs="config file update" errors="file errors" side_effects="config file write"]
716
+ // CIL[DOC-MICRO logic_summary="Updates spine.config.json with app registration settings and available roles" notable_decisions="[]"]
717
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
718
+ // CIL[DOC-STABILITY stability="stable"]
719
+ // CIL[DOC-VECTOR type="context" content="Layer: cli. Direct callers: 0. Direct calls: 0. Related files: none."]
720
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
721
+ // CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
722
+ // CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
723
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
724
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
725
+ function updateRegistrationConfig(appSlug: string, regConfig: RegistrationConfig, requiredRoles: string[] = []): void {
580
726
  const config = loadSpineConfig()
581
727
 
582
- // Ensure registration section exists
728
+
583
729
  if (!config.registration) {
584
730
  config.registration = { apps: {} }
585
731
  }
@@ -587,22 +733,10 @@ function updateRegistrationConfig(appSlug: string, regConfig: RegistrationConfig
587
733
  config.registration.apps = {}
588
734
  }
589
735
 
590
- // Try to load all roles from the app's seed data
591
- let availableRoles: string[] = [regConfig.default_role] // Start with default role from manifest
592
736
 
593
- try {
594
- const rolesPath = join(PROJECT_ROOT, 'custom/apps', appSlug, 'seed/roles.json')
595
- if (existsSync(rolesPath)) {
596
- const rolesData = JSON.parse(readFileSync(rolesPath, 'utf8'))
597
- if (Array.isArray(rolesData)) {
598
- availableRoles = rolesData.map((role: any) => role.slug).filter(Boolean)
599
- }
600
- }
601
- } catch (error) {
602
- console.log(` ⚠️ Could not load roles from seed data, using default role only`)
603
- }
737
+ let availableRoles: string[] = requiredRoles.length ? requiredRoles : [regConfig.default_role]
604
738
 
605
- // Add/update this app's registration entry with multi-role and account strategy support
739
+
606
740
  config.registration.apps[appSlug] = {
607
741
  roles: availableRoles,
608
742
  default_role: regConfig.default_role,
@@ -612,14 +746,14 @@ function updateRegistrationConfig(appSlug: string, regConfig: RegistrationConfig
612
746
  target_account: regConfig.target_account,
613
747
  }
614
748
 
615
- // If this is the first registration-enabled app, make it the default
749
+
616
750
  const enabledApps = Object.entries(config.registration.apps).filter(([, cfg]) => cfg.enabled)
617
751
  if (enabledApps.length === 1 || !config.registration.default_app) {
618
752
  config.registration.default_app = appSlug
619
753
  config.registration.default_role = regConfig.default_role
620
754
  }
621
755
 
622
- // Default allow_url_override to true if not set
756
+
623
757
  if (config.registration.allow_url_override === undefined) {
624
758
  config.registration.allow_url_override = true
625
759
  }
@@ -628,3 +762,4 @@ function updateRegistrationConfig(appSlug: string, regConfig: RegistrationConfig
628
762
  console.log(` ✓ Updated spine.config.json registration for ${appSlug}`)
629
763
  console.log(` ✓ Available roles: ${availableRoles.join(', ')}`)
630
764
  }
765
+ // CIL[END chunk_id="install-app_updateregistrationconfig_1784069813"]