spine-framework 0.3.145 → 0.4.1

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 (451) hide show
  1. package/.env.example +76 -0
  2. package/.framework/.spine-manifest.json +19 -0
  3. package/.framework/cli/commands/agents.ts +61 -33
  4. package/.framework/cli/commands/auth.ts +60 -28
  5. package/.framework/cli/commands/create-app.ts +212 -80
  6. package/.framework/cli/commands/dev.ts +184 -68
  7. package/.framework/cli/commands/doctor.ts +324 -152
  8. package/.framework/cli/commands/generate.ts +119 -68
  9. package/.framework/cli/commands/init.ts +141 -242
  10. package/.framework/cli/commands/install-app.ts +1336 -314
  11. package/.framework/cli/commands/items.ts +31 -29
  12. package/.framework/cli/commands/migrate.ts +299 -156
  13. package/.framework/cli/commands/migrations.ts +62 -30
  14. package/.framework/cli/commands/pipelines.ts +31 -29
  15. package/.framework/cli/commands/status.ts +105 -45
  16. package/.framework/cli/commands/system.ts +47 -42
  17. package/.framework/cli/commands/test.ts +161 -102
  18. package/.framework/cli/commands/upgrade.ts +260 -0
  19. package/.framework/cli/commands/validate-app.ts +517 -161
  20. package/.framework/cli/context.ts +117 -85
  21. package/.framework/cli/env-loader.ts +74 -43
  22. package/.framework/cli/index.ts +70 -46
  23. package/.framework/cli/lib/project-files.ts +404 -0
  24. package/.framework/docs/guides/testing-guide.md +1 -1
  25. package/.framework/docs/runtime-types.md +72 -72
  26. package/.framework/functions/_shared/access.ts +84 -0
  27. package/.framework/functions/_shared/agent-runner.ts +5067 -1026
  28. package/.framework/functions/_shared/app-manifest.ts +214 -94
  29. package/.framework/functions/_shared/ast.ts +214 -0
  30. package/.framework/functions/_shared/audit.ts +91 -55
  31. package/.framework/functions/_shared/cil.ts +368 -127
  32. package/.framework/functions/_shared/db.ts +337 -68
  33. package/.framework/functions/_shared/funnel.ts +228 -0
  34. package/.framework/functions/_shared/geo.ts +105 -0
  35. package/.framework/functions/_shared/hash-api-key.ts +94 -17
  36. package/.framework/functions/_shared/index.ts +22 -224
  37. package/.framework/functions/_shared/kb.ts +832 -58
  38. package/.framework/functions/_shared/llm.ts +118 -18
  39. package/.framework/functions/_shared/logging.ts +317 -19
  40. package/.framework/functions/_shared/middleware.ts +701 -131
  41. package/.framework/functions/_shared/notify.ts +70 -0
  42. package/.framework/functions/_shared/outcomes.ts +34 -19
  43. package/.framework/functions/_shared/permissions.ts +501 -315
  44. package/.framework/functions/_shared/pipeline-runner.ts +704 -155
  45. package/.framework/functions/_shared/principal.ts +436 -263
  46. package/.framework/functions/_shared/rate-limit.ts +233 -0
  47. package/.framework/functions/_shared/resolve-credentials.ts +86 -19
  48. package/.framework/functions/_shared/resolve-ids.ts +203 -139
  49. package/.framework/functions/_shared/retry-deliveries.ts +51 -16
  50. package/.framework/functions/_shared/schema-utils.ts +602 -521
  51. package/.framework/functions/_shared/security.ts +345 -0
  52. package/.framework/functions/_shared/testing.ts +197 -172
  53. package/.framework/functions/_shared/tracing.ts +351 -21
  54. package/.framework/functions/_shared/trigger-engine.ts +425 -136
  55. package/.framework/functions/_shared/webhook-registration.ts +71 -70
  56. package/.framework/functions/_shared/webhook-registry.ts +126 -83
  57. package/.framework/functions/account-nodes.ts +30 -30
  58. package/.framework/functions/adapters/generic-webhook-inbound.ts +49 -16
  59. package/.framework/functions/adapters/generic-webhook-outbound.ts +17 -17
  60. package/.framework/functions/adapters/openai-outbound.ts +15 -16
  61. package/.framework/functions/admin-data.ts +596 -173
  62. package/.framework/functions/admin-funnel.ts +64 -60
  63. package/.framework/functions/admin-install-funnel.ts +84 -66
  64. package/.framework/functions/admin-signals.ts +69 -51
  65. package/.framework/functions/ai-agents.ts +431 -106
  66. package/.framework/functions/ai-interactions.ts +186 -112
  67. package/.framework/functions/api-keys.ts +107 -107
  68. package/.framework/functions/apps.ts +154 -136
  69. package/.framework/functions/auth.ts +83 -65
  70. package/.framework/functions/cil-agent-ingest.ts +189 -232
  71. package/.framework/functions/cil-agent.ts +433 -177
  72. package/.framework/functions/cil-callmap.ts +642 -430
  73. package/.framework/functions/cil-config.ts +139 -135
  74. package/.framework/functions/cil-context.ts +214 -127
  75. package/.framework/functions/cil-doc.ts +587 -458
  76. package/.framework/functions/cil-embeddings.ts +453 -364
  77. package/.framework/functions/cil-graph.ts +427 -142
  78. package/.framework/functions/cil-ingest.ts +1418 -1154
  79. package/.framework/functions/cil-pipeline.ts +276 -21
  80. package/.framework/functions/cil-project.ts +659 -132
  81. package/.framework/functions/cil-validate.ts +198 -164
  82. package/.framework/functions/debug-auth.ts +57 -55
  83. package/.framework/functions/deliveries.ts +80 -21
  84. package/.framework/functions/embeddings.ts +1023 -318
  85. package/.framework/functions/eval-harness.ts +146 -108
  86. package/.framework/functions/integration-outbound.ts +205 -75
  87. package/.framework/functions/integration-routes.ts +128 -23
  88. package/.framework/functions/integrations.ts +125 -100
  89. package/.framework/functions/invites.ts +1095 -460
  90. package/.framework/functions/item-progress.ts +158 -102
  91. package/.framework/functions/kb.ts +34 -10
  92. package/.framework/functions/logs.ts +133 -133
  93. package/.framework/functions/observability.ts +152 -122
  94. package/.framework/functions/pipeline-executions.ts +147 -132
  95. package/.framework/functions/pipelines.ts +134 -119
  96. package/.framework/functions/prompt-configs.ts +96 -80
  97. package/.framework/functions/roles.ts +112 -82
  98. package/.framework/functions/security.ts +450 -0
  99. package/.framework/functions/system-cron.ts +117 -80
  100. package/.framework/functions/system.ts +158 -98
  101. package/.framework/functions/tests.ts +29 -29
  102. package/.framework/functions/timers.ts +94 -95
  103. package/.framework/functions/trigger-scheduler.ts +10 -0
  104. package/.framework/functions/triggers.ts +179 -149
  105. package/.framework/functions/types.ts +137 -125
  106. package/.framework/mcp/index.ts +133 -0
  107. package/.framework/mcp/load-env.ts +60 -0
  108. package/.framework/mcp/shared/app-manifest.ts +275 -0
  109. package/.framework/mcp/shared/db.ts +477 -0
  110. package/.framework/mcp/shared/kb.ts +118 -0
  111. package/.framework/mcp/tools/architect.ts +431 -0
  112. package/.framework/mcp/tools/cil/context.ts +345 -0
  113. package/.framework/mcp/tools/cil/graph.ts +242 -0
  114. package/.framework/mcp/tools/cil/ingest.ts +1217 -0
  115. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +693 -0
  116. package/.framework/mcp/tools/cil/pipeline.ts +767 -0
  117. package/.framework/mcp/tools/cil/project.ts +791 -0
  118. package/.framework/mcp/tools/cil/retrieval.ts +336 -0
  119. package/.framework/mcp/tools/cil/search-core.ts +454 -0
  120. package/.framework/mcp/tools/cil/search.ts +97 -0
  121. package/.framework/mcp/tools/cil/suggest.ts +186 -0
  122. package/.framework/mcp/tools/cil/validate.ts +1369 -0
  123. package/.framework/mcp/tools/kb/delete.ts +139 -0
  124. package/.framework/mcp/tools/kb/index.ts +52 -0
  125. package/.framework/mcp/tools/kb/ingest.ts +84 -0
  126. package/.framework/mcp/tools/kb/list-types.ts +102 -0
  127. package/.framework/mcp/tools/kb/publish.ts +100 -0
  128. package/.framework/mcp/tools/kb/reindex.ts +86 -0
  129. package/.framework/mcp/tools/kb/revoke.ts +85 -0
  130. package/.framework/mcp/tools/kb/search.ts +101 -0
  131. package/.framework/mcp/tools/kb/upsert.ts +125 -0
  132. package/.framework/mcp/tools/observability.ts +510 -0
  133. package/.framework/migrations/001_core_schema.sql +15 -0
  134. package/.framework/migrations/002_core_rls_and_functions.sql +15 -0
  135. package/.framework/migrations/003_core_system_data.sql +16 -1
  136. package/.framework/migrations/004_core_rpc_and_broker.sql +17 -1
  137. package/.framework/migrations/005_core_observability.sql +17 -1
  138. package/.framework/migrations/006_security_hardening_rls.sql +15 -0
  139. package/.framework/migrations/007_kb_article_list.sql +17 -1
  140. package/.framework/migrations/008_items_target.sql +15 -0
  141. package/.framework/migrations/009_roles_slug_unique.sql +15 -0
  142. package/.framework/migrations/010_auth_user_trigger.sql +15 -0
  143. package/.framework/migrations/011_spine_system_account.sql +16 -0
  144. package/.framework/migrations/012_service_role_grants.sql +16 -0
  145. package/.framework/migrations/013_get_account_apps_role_filter.sql +15 -0
  146. package/.framework/migrations/014_apps_required_roles.sql +16 -0
  147. package/.framework/migrations/015_types_kind_slug_unique.sql +15 -0
  148. package/.framework/migrations/016_cil_types.sql +281 -0
  149. package/.framework/migrations/017_cil_stable_ids.sql +37 -0
  150. package/.framework/migrations/018_runtime_tables.sql +129 -0
  151. package/.framework/migrations/019_agent_prompt_columns.sql +56 -0
  152. package/.framework/migrations/020_schema_reconciliation.sql +67 -0
  153. package/.framework/migrations/021_kb_embedding_lifecycle.sql +31 -0
  154. package/.framework/migrations/032_cil_status_migration.sql +73 -0
  155. package/.framework/migrations/033_cil_versioning.sql +73 -0
  156. package/.framework/migrations/034_match_embeddings_knn.sql +115 -0
  157. package/.framework/migrations/035_rate_limits.sql +55 -0
  158. package/.framework/migrations/036_demo_record_flags.sql +72 -0
  159. package/.framework/migrations/037_kb_types_canonical.sql +244 -0
  160. package/.framework/migrations/038_drop_demo_record_flags.sql +38 -0
  161. package/.framework/migrations/042_registration_hierarchy.sql +309 -0
  162. package/.framework/migrations/043_policy_actor_cleanup.sql +39 -0
  163. package/.framework/migrations/044_match_embeddings_filtered_knn.sql +151 -0
  164. package/.framework/migrations/045_security_critical_rls.sql +139 -0
  165. package/.framework/migrations/046_security_missing_rls.sql +79 -0
  166. package/.framework/migrations/047_people_updated_by.sql +27 -0
  167. package/.framework/migrations/048_watcher_last_seen_at.sql +24 -0
  168. package/.framework/migrations/049_security_blocklist.sql +148 -0
  169. package/.framework/migrations/050_notifications.sql +49 -0
  170. package/.framework/migrations/051_message_agent_fields.sql +74 -0
  171. package/.framework/migrations/052_registration_security.sql +421 -0
  172. package/.framework/migrations/053_builtin_action_seeds.sql +66 -0
  173. package/.framework/migrations/054_kb_ingest_lease.sql +157 -0
  174. package/.framework/migrations/055_type_id_drift_reconciliation.sql +33 -0
  175. package/.framework/migrations/056_slug_column_reconciliation.sql +38 -0
  176. package/.framework/migrations/057_pipelines_slug_identity.sql +14 -0
  177. package/.framework/migrations/058_account_data_field_declarations.sql +53 -0
  178. package/.framework/migrations/059_registration_placement_fixes.sql +269 -0
  179. package/.framework/migrations/060_account_person_record_permissions.sql +38 -0
  180. package/.framework/migrations/061_fk_dedup.sql +32 -0
  181. package/.framework/public/spine-logo.svg +10 -3
  182. package/.framework/scripts/app-install-cli.ts +190 -93
  183. package/.framework/scripts/assemble-frontend.sh +127 -44
  184. package/.framework/scripts/assemble-functions.sh +128 -30
  185. package/.framework/scripts/boundary-check.sh +6 -2
  186. package/.framework/scripts/build-manifest.sh +24 -68
  187. package/.framework/scripts/check-core-integrity.sh +15 -76
  188. package/.framework/scripts/checksum-manifest.mts +343 -0
  189. package/.framework/scripts/cil-migrate-markers.ts +403 -0
  190. package/.framework/scripts/fix-cil-markers.mjs +176 -0
  191. package/.framework/scripts/verify-integrity.sh +21 -57
  192. package/.framework/scripts/verify-manifest.mts +305 -0
  193. package/.framework/seeds/types/funnel_signal_use.json +74 -21
  194. package/.framework/seeds/types/spine_instance.json +73 -21
  195. package/.framework/src/App.tsx +118 -72
  196. package/.framework/src/apps/admin/index.tsx +855 -58
  197. package/.framework/src/components/AppWrapper.tsx +43 -45
  198. package/.framework/src/components/CustomAppLoader.tsx +160 -116
  199. package/.framework/src/components/admin/AdminListPage.tsx +46 -44
  200. package/.framework/src/components/admin/AdminSidebar.tsx +57 -47
  201. package/.framework/src/components/admin/AdminStatsCard.tsx +46 -44
  202. package/.framework/src/components/admin/PageInstructions.tsx +98 -2
  203. package/.framework/src/components/admin/SortableTableHeader.tsx +44 -45
  204. package/.framework/src/components/app-shell/GenericAppShell.tsx +64 -65
  205. package/.framework/src/components/app-shell/GenericDetailPage.tsx +44 -47
  206. package/.framework/src/components/app-shell/GenericListPage.tsx +45 -49
  207. package/.framework/src/components/app-sidebar.tsx +42 -45
  208. package/.framework/src/components/auth/ProtectedRoute.tsx +48 -41
  209. package/.framework/src/components/auth/TurnstileWidget.tsx +27 -0
  210. package/.framework/src/components/kb/KBArticleEditor.tsx +231 -22
  211. package/.framework/src/components/kb/KBArticleList.tsx +251 -10
  212. package/.framework/src/components/layout/AppShell.tsx +51 -45
  213. package/.framework/src/components/layout/Header.tsx +42 -43
  214. package/.framework/src/components/layout/Layout.tsx +46 -46
  215. package/.framework/src/components/layout/Sidebar.tsx +196 -96
  216. package/.framework/src/components/runtime/DataDetailHeader.tsx +55 -30
  217. package/.framework/src/components/runtime/DataDetailPage.tsx +31 -269
  218. package/.framework/src/components/runtime/DataFilters.tsx +49 -74
  219. package/.framework/src/components/runtime/DataHeader.tsx +53 -32
  220. package/.framework/src/components/runtime/DataListPage.tsx +51 -155
  221. package/.framework/src/components/runtime/DataStats.tsx +66 -44
  222. package/.framework/src/components/runtime/DataTable.tsx +49 -149
  223. package/.framework/src/components/runtime/SchemaDetailForm.tsx +45 -75
  224. package/.framework/src/components/runtime/index.ts +119 -16
  225. package/.framework/src/components/search-form.tsx +46 -29
  226. package/.framework/src/components/shared/AgentView.tsx +65 -48
  227. package/.framework/src/components/shared/FieldRenderer.tsx +144 -50
  228. package/.framework/src/components/shared/InviteManager.tsx +207 -97
  229. package/.framework/src/components/shared/MemberList.tsx +97 -0
  230. package/.framework/src/components/shared/SchemaFields.tsx +107 -86
  231. package/.framework/src/components/ui/DataTable.tsx +80 -68
  232. package/.framework/src/components/ui/Form.tsx +157 -146
  233. package/.framework/src/components/ui/ItemCard.tsx +74 -67
  234. package/.framework/src/components/ui/ItemListView.tsx +73 -68
  235. package/.framework/src/components/ui/LoadingSpinner.tsx +43 -48
  236. package/.framework/src/components/ui/Modal.tsx +78 -68
  237. package/.framework/src/components/ui/RichTextEditor.tsx +89 -69
  238. package/.framework/src/components/ui/accordion.tsx +85 -86
  239. package/.framework/src/components/ui/alert-dialog.tsx +197 -184
  240. package/.framework/src/components/ui/alert.tsx +105 -94
  241. package/.framework/src/components/ui/aspect-ratio.tsx +57 -43
  242. package/.framework/src/components/ui/avatar.tsx +122 -107
  243. package/.framework/src/components/ui/badge.tsx +60 -55
  244. package/.framework/src/components/ui/breadcrumb.tsx +127 -128
  245. package/.framework/src/components/ui/button-group.tsx +86 -72
  246. package/.framework/src/components/ui/button.tsx +71 -50
  247. package/.framework/src/components/ui/calendar.tsx +72 -57
  248. package/.framework/src/components/ui/card.tsx +136 -129
  249. package/.framework/src/components/ui/carousel.tsx +226 -107
  250. package/.framework/src/components/ui/chart.tsx +252 -147
  251. package/.framework/src/components/ui/checkbox.tsx +43 -44
  252. package/.framework/src/components/ui/collapsible.tsx +86 -71
  253. package/.framework/src/components/ui/combobox.tsx +282 -249
  254. package/.framework/src/components/ui/command.tsx +158 -146
  255. package/.framework/src/components/ui/context-menu.tsx +239 -240
  256. package/.framework/src/components/ui/dialog.tsx +174 -157
  257. package/.framework/src/components/ui/direction.tsx +72 -56
  258. package/.framework/src/components/ui/drawer.tsx +168 -169
  259. package/.framework/src/components/ui/dropdown-menu.tsx +254 -223
  260. package/.framework/src/components/ui/empty.tsx +131 -120
  261. package/.framework/src/components/ui/field.tsx +205 -173
  262. package/.framework/src/components/ui/hover-card.tsx +71 -72
  263. package/.framework/src/components/ui/input-group.tsx +148 -133
  264. package/.framework/src/components/ui/input-otp.tsx +99 -86
  265. package/.framework/src/components/ui/input.tsx +43 -41
  266. package/.framework/src/components/ui/item.tsx +207 -185
  267. package/.framework/src/components/ui/kbd.tsx +57 -56
  268. package/.framework/src/components/ui/label.tsx +45 -41
  269. package/.framework/src/components/ui/menubar.tsx +268 -236
  270. package/.framework/src/components/ui/native-select.tsx +85 -86
  271. package/.framework/src/components/ui/navigation-menu.tsx +166 -156
  272. package/.framework/src/components/ui/pagination.tsx +131 -141
  273. package/.framework/src/components/ui/popover.tsx +125 -128
  274. package/.framework/src/components/ui/progress.tsx +47 -44
  275. package/.framework/src/components/ui/radio-group.tsx +60 -55
  276. package/.framework/src/components/ui/resizable.tsx +85 -72
  277. package/.framework/src/components/ui/scroll-area.tsx +57 -58
  278. package/.framework/src/components/ui/select.tsx +182 -169
  279. package/.framework/src/components/ui/separator.tsx +42 -43
  280. package/.framework/src/components/ui/sheet.tsx +170 -159
  281. package/.framework/src/components/ui/sidebar.tsx +395 -398
  282. package/.framework/src/components/ui/skeleton.tsx +42 -43
  283. package/.framework/src/components/ui/slider.tsx +61 -44
  284. package/.framework/src/components/ui/sonner.tsx +44 -44
  285. package/.framework/src/components/ui/spinner.tsx +47 -42
  286. package/.framework/src/components/ui/switch.tsx +56 -42
  287. package/.framework/src/components/ui/table-primitives.tsx +272 -132
  288. package/.framework/src/components/ui/table.tsx +140 -142
  289. package/.framework/src/components/ui/tabs.tsx +110 -93
  290. package/.framework/src/components/ui/textarea.tsx +43 -44
  291. package/.framework/src/components/ui/toggle-group.tsx +87 -71
  292. package/.framework/src/components/ui/toggle.tsx +56 -56
  293. package/.framework/src/components/ui/tooltip.tsx +99 -85
  294. package/.framework/src/contexts/AppContext.tsx +166 -114
  295. package/.framework/src/contexts/AuthContext.tsx +232 -137
  296. package/.framework/src/hooks/use-mobile.ts +43 -44
  297. package/.framework/src/hooks/useApi.ts +185 -172
  298. package/.framework/src/hooks/useAppPath.ts +31 -29
  299. package/.framework/src/hooks/useApps.ts +77 -42
  300. package/.framework/src/hooks/useEntityList.ts +66 -29
  301. package/.framework/src/hooks/useEntityRecord.ts +76 -32
  302. package/.framework/src/hooks/useForm.ts +62 -30
  303. package/.framework/src/hooks/useListSchema.ts +60 -43
  304. package/.framework/src/hooks/useSchemaRecord.ts +115 -95
  305. package/.framework/src/lib/api.ts +258 -134
  306. package/.framework/src/lib/registration.ts +181 -0
  307. package/.framework/src/lib/supabase.ts +118 -81
  308. package/.framework/src/lib/utils.ts +266 -266
  309. package/.framework/src/main.tsx +43 -44
  310. package/.framework/src/pages/DashboardPage.tsx +44 -43
  311. package/.framework/src/pages/NoAppPage.tsx +50 -0
  312. package/.framework/src/pages/NotFoundPage.tsx +32 -29
  313. package/.framework/src/pages/SetupPage.tsx +34 -33
  314. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +69 -58
  315. package/.framework/src/pages/admin/AIAgentsPage.tsx +43 -40
  316. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +63 -55
  317. package/.framework/src/pages/admin/APIKeysPage.tsx +44 -42
  318. package/.framework/src/pages/admin/AlertsConfigPage.tsx +117 -94
  319. package/.framework/src/pages/admin/AppDetailPage.tsx +48 -42
  320. package/.framework/src/pages/admin/AppsPage.tsx +45 -42
  321. package/.framework/src/pages/admin/DeliveriesPage.tsx +49 -17
  322. package/.framework/src/pages/admin/DesignedPage.tsx +31 -30
  323. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +58 -0
  324. package/.framework/src/pages/admin/EmbeddingsPage.tsx +49 -0
  325. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +31 -29
  326. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +46 -29
  327. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -29
  328. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +68 -55
  329. package/.framework/src/pages/admin/IntegrationsPage.tsx +45 -42
  330. package/.framework/src/pages/admin/InvitesPage.tsx +60 -44
  331. package/.framework/src/pages/admin/LogsPage.tsx +87 -137
  332. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +31 -29
  333. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +150 -89
  334. package/.framework/src/pages/admin/PipelineDetailPage.tsx +77 -104
  335. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +44 -43
  336. package/.framework/src/pages/admin/PipelinesPage.tsx +59 -145
  337. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +53 -42
  338. package/.framework/src/pages/admin/PromptConfigsPage.tsx +47 -117
  339. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +31 -44
  340. package/.framework/src/pages/admin/RoleDetailPage.tsx +49 -92
  341. package/.framework/src/pages/admin/RolesPage.tsx +47 -164
  342. package/.framework/src/pages/admin/SecurityPage.tsx +310 -0
  343. package/.framework/src/pages/admin/SelectTestPage.tsx +30 -32
  344. package/.framework/src/pages/admin/ShadcnTestPage.tsx +37 -44
  345. package/.framework/src/pages/admin/SimpleDashboard.tsx +32 -44
  346. package/.framework/src/pages/admin/TestRunDetailPage.tsx +97 -44
  347. package/.framework/src/pages/admin/TestingDashboard.tsx +126 -71
  348. package/.framework/src/pages/admin/TimerDetailPage.tsx +67 -55
  349. package/.framework/src/pages/admin/TimersPage.tsx +44 -42
  350. package/.framework/src/pages/admin/TriggerDetailPage.tsx +47 -42
  351. package/.framework/src/pages/admin/TriggersPage.tsx +58 -57
  352. package/.framework/src/pages/admin/TypeDetailPage.tsx +91 -55
  353. package/.framework/src/pages/admin/TypesPage.tsx +45 -42
  354. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +258 -128
  355. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +127 -123
  356. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +308 -235
  357. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +159 -110
  358. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +432 -0
  359. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +200 -178
  360. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +286 -239
  361. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +211 -90
  362. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +156 -143
  363. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +379 -0
  364. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +337 -0
  365. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +219 -214
  366. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  367. package/.framework/src/pages/auth/AuthConfirmPage.tsx +135 -65
  368. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +39 -31
  369. package/.framework/src/pages/auth/LoginPage.tsx +38 -32
  370. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -28
  371. package/.framework/src/pages/auth/RegisterPage.tsx +429 -155
  372. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -30
  373. package/.framework/src/pages/spine-framework/CLIPage.tsx +32 -30
  374. package/.framework/src/types/auth.ts +101 -93
  375. package/.framework/src/types/types.ts +283 -262
  376. package/.framework/templates/project.json +87 -0
  377. package/.framework/templates/spine.config.neutral.json +7 -0
  378. package/.framework/tests/api/api-surface.test.ts +381 -0
  379. package/.framework/tests/fixtures/seed.ts +427 -0
  380. package/.framework/tests/fixtures/teardown.ts +228 -0
  381. package/.framework/tests/reporter.ts +351 -0
  382. package/.framework/tests/setup.ts +135 -0
  383. package/.framework/tests/ui/ui-sweep.ts +278 -0
  384. package/.framework/tests/unit/admin-data-logger.test.ts +310 -0
  385. package/.framework/tests/unit/agent-runner.test.ts +2389 -0
  386. package/.framework/tests/unit/ai-interactions.test.ts +81 -0
  387. package/.framework/tests/unit/app-manifest.test.ts +359 -0
  388. package/.framework/tests/unit/assembly-scripts.test.ts +172 -0
  389. package/.framework/tests/unit/cil-determinism.test.ts +85 -0
  390. package/.framework/tests/unit/core-isolation.test.ts +153 -0
  391. package/.framework/tests/unit/funnel-signals.test.ts +135 -0
  392. package/.framework/tests/unit/install-app.test.ts +1759 -0
  393. package/.framework/tests/unit/invites-security.test.ts +572 -0
  394. package/.framework/tests/unit/kb-ingest-lease.test.ts +287 -0
  395. package/.framework/tests/unit/kb-search.test.ts +119 -0
  396. package/.framework/tests/unit/migrate.test.ts +509 -0
  397. package/.framework/tests/unit/notify.test.ts +136 -0
  398. package/.framework/tests/unit/permissions.test.ts +269 -0
  399. package/.framework/tests/unit/pipeline-runner.test.ts +755 -0
  400. package/.framework/tests/unit/principal-cache.test.ts +138 -0
  401. package/.framework/tests/unit/retrieval-golden.test.ts +520 -0
  402. package/.framework/tests/unit/runtime-isolation.test.ts +642 -0
  403. package/.framework/tests/unit/schema-utils.test.ts +225 -0
  404. package/.framework/tests/unit/trigger-engine.test.ts +513 -0
  405. package/.framework/tests/unit/validate-app.test.ts +527 -0
  406. package/README.md +34 -3
  407. package/bin/spine-framework.cjs +1 -0
  408. package/config/tailwind.config.cjs +7 -0
  409. package/config/tsconfig.functions.json +42 -0
  410. package/config/tsconfig.json +37 -9
  411. package/config/vite.config.ts +31 -2
  412. package/netlify.toml +31 -3
  413. package/package.json +46 -29
  414. package/scripts/assemble-frontend.sh +127 -44
  415. package/scripts/assemble-functions.sh +127 -29
  416. package/scripts/boundary-check.sh +6 -2
  417. package/scripts/build-manifest.sh +24 -68
  418. package/scripts/checksum-manifest.mts +345 -0
  419. package/scripts/dev-clean.sh +86 -0
  420. package/scripts/dev-netlify.sh +49 -0
  421. package/scripts/netlify-dev-wrapper.sh +2 -0
  422. package/scripts/verify-install.ts +358 -0
  423. package/scripts/verify-integrity.sh +21 -57
  424. package/scripts/verify-manifest.mts +305 -0
  425. package/templates/project/gitignore +45 -0
  426. package/{config → templates/project}/package-lock.json +22335 -9366
  427. package/vitest.config.ts +46 -0
  428. package/.framework/public/spine.config.json +0 -91
  429. package/config/deno.lock +0 -108
  430. package/config/tsconfig.node.tsbuildinfo +0 -1
  431. package/config/tsconfig.tsbuildinfo +0 -1
  432. package/config/vite.config.d.ts +0 -2
  433. package/custom/.devin/AGENT.md +0 -357
  434. package/custom/.devin/skills/install-app/SKILL.md +0 -39
  435. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +0 -39
  436. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +0 -36
  437. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +0 -46
  438. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +0 -28
  439. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +0 -38
  440. package/custom/.devin/workflows/app-install.yaml +0 -83
  441. package/custom/README.md +0 -79
  442. package/docs/README.md +0 -15
  443. package/scripts/app-install-cli.ts +0 -286
  444. package/scripts/check-core-integrity.sh +0 -82
  445. package/scripts/ingest-chunks.cjs +0 -202
  446. package/scripts/kb-chunk-parser.cjs +0 -312
  447. package/scripts/kb-chunk-parser.ts +0 -330
  448. package/scripts/kb-ingest-docs.ts +0 -203
  449. package/scripts/load-test-app-install.ts +0 -484
  450. package/scripts/test-login.js +0 -33
  451. package/spine.config.json +0 -91
@@ -0,0 +1,81 @@
1
+ // CIL[DOC-MACRO purpose="Unit tests for createInteraction's optional type_slug override in ai-interactions.ts"]
2
+ // CIL[DOC-RESPONSIBILITY responsibility="Assert body.type_slug selects the resolved item type (default ai_feedback_record), that a missing type reports the resolved slug in the skip reason, and required-field validation is unchanged"]
3
+ // CIL[DOC-CONTEXT module="ai-interactions.test" layer="test-unit" related_files=".framework/functions/ai-interactions.ts"]
4
+ // CIL[DOC-CONTRACT inputs="mocked middleware/db/resolve-ids/admin-data/audit/funnel" outputs="expect assertions on resolveTypeId args, create payloads, and skip reasons" errors="none" side_effects="registers vi.mock factories"]
5
+ // CIL[DOC-AUDIENCE audience="test"]
6
+ // CIL[DOC-STABILITY stability="stable"]
7
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
8
+
9
+ vi.mock('../../functions/_shared/middleware.ts', () => ({
10
+ createHandler: (fn: any) => fn,
11
+ }))
12
+ vi.mock('../../functions/_shared/db.ts', () => ({
13
+ adminDb: { from: vi.fn(), rpc: vi.fn() },
14
+ }))
15
+ vi.mock('../../functions/_shared/resolve-ids.ts', () => ({
16
+ resolveTypeId: vi.fn().mockImplementation((_k: string, slug: string) => Promise.resolve(`t-${slug}`)),
17
+ }))
18
+ vi.mock('../../functions/admin-data.ts', () => ({
19
+ create: vi.fn().mockResolvedValue({ id: 'created' }),
20
+ update: vi.fn().mockResolvedValue({ id: 'updated' }),
21
+ }))
22
+ vi.mock('../../functions/_shared/audit.ts', () => ({
23
+ emitLog: vi.fn().mockResolvedValue(undefined),
24
+ }))
25
+ vi.mock('../../functions/_shared/funnel.ts', () => ({
26
+ emitSignal: vi.fn().mockResolvedValue(undefined),
27
+ }))
28
+
29
+ import { resolveTypeId } from '../../functions/_shared/resolve-ids.ts'
30
+ import { create } from '../../functions/admin-data.ts'
31
+ import { createInteraction } from '../../functions/ai-interactions.ts'
32
+
33
+ function ctx(): any {
34
+ return {
35
+ accountId: 'acct-1',
36
+ principal: { id: 'person-1', type: 'human', roles: ['member'] },
37
+ requestId: 'req-1',
38
+ }
39
+ }
40
+
41
+ describe('createInteraction — optional type_slug', () => {
42
+ beforeEach(() => vi.clearAllMocks())
43
+
44
+ it('defaults to the ai_feedback_record item type', async () => {
45
+ await createInteraction(ctx(), { thread_id: 'thr-1', kb_candidate: true })
46
+ expect(resolveTypeId).toHaveBeenCalledWith('item', 'ai_feedback_record')
47
+ expect(create).toHaveBeenCalledWith(
48
+ ctx(),
49
+ expect.objectContaining({ entity: 'items', type_id: 't-ai_feedback_record' })
50
+ )
51
+ })
52
+
53
+ it('resolves an app-namespaced type when type_slug is provided', async () => {
54
+ await createInteraction(ctx(), {
55
+ type_slug: 'demo_feedback_record',
56
+ thread_id: 'thr-1',
57
+ ai_message_id: 'm-1',
58
+ kb_candidate: true,
59
+ })
60
+ expect(resolveTypeId).toHaveBeenCalledWith('item', 'demo_feedback_record')
61
+ const [, payload] = vi.mocked(create).mock.calls[0] as any[]
62
+ expect(payload.type_id).toBe('t-demo_feedback_record')
63
+ expect(payload.data.ai_message_id).toBe('m-1')
64
+ expect(payload.data.kb_candidate).toBe(true)
65
+ })
66
+
67
+ it('reports the resolved slug in the skip reason when the type is missing', async () => {
68
+ vi.mocked(resolveTypeId).mockRejectedValueOnce(new Error('Type not found: item/demo_feedback_record'))
69
+ const res = await createInteraction(ctx(), {
70
+ type_slug: 'demo_feedback_record',
71
+ thread_id: 'thr-1',
72
+ })
73
+ expect(res).toEqual({ skipped: true, reason: 'demo_feedback_record type not found' })
74
+ expect(create).not.toHaveBeenCalled()
75
+ })
76
+
77
+ it('still requires thread_id or ai_message_id', async () => {
78
+ await expect(createInteraction(ctx(), { type_slug: 'demo_feedback_record' }))
79
+ .rejects.toThrow('thread_id or ai_message_id is required')
80
+ })
81
+ })
@@ -0,0 +1,359 @@
1
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/app-manifest.test.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/tests/unit/app-manifest.test.ts"]
2
+ // CIL[DOC-MACRO purpose="Import Vitest, filesystem fixture utilities, and both manifest implementations."]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Import Vitest, filesystem fixture utilities, and both manifest implementations."]
4
+ // CIL[DOC-CONTEXT module="tests/unit" layer="test-unit" related_files=".framework/functions/_shared/app-manifest.ts,.framework/mcp/shared/app-manifest.ts"]
5
+ // CIL[DOC-CONTRACT inputs="none" outputs="none" errors="none" side_effects="none"]
6
+ // CIL[DOC-MICRO logic_summary="Import Vitest, filesystem fixture utilities, and both manifest implementations." notable_decisions=""]
7
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
8
+ // CIL[DOC-STABILITY stability="experimental"]
9
+ // CIL[DOC-AUDIENCE audience="framework contributors"]
10
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest'
11
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync, symlinkSync } from 'node:fs'
12
+ import { tmpdir } from 'node:os'
13
+ import { join, relative } from 'node:path'
14
+ import * as runtimeMod from '../../functions/_shared/app-manifest.ts'
15
+ import * as mcpMod from '../../../.framework/mcp/shared/app-manifest.ts'
16
+ // CIL[END stable_id="~/.framework/tests/unit/app-manifest.test.ts:module_header:module_header"]
17
+
18
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/app-manifest.test.ts:modules:constant" type="constant" identity="modules" parent="none"]
19
+ // CIL[DOC-MACRO purpose="Pair runtime and MCP manifest implementations for identical behavior tests."]
20
+ // CIL[DOC-RESPONSIBILITY responsibility="Pair runtime and MCP manifest implementations for identical behavior tests."]
21
+ // CIL[DOC-CONTEXT module="tests/unit" layer="test-unit" related_files=".framework/functions/_shared/app-manifest.ts,.framework/mcp/shared/app-manifest.ts"]
22
+ // CIL[DOC-CONTRACT inputs="Imported runtimeMod and mcpMod modules" outputs="Readonly module-label pairs" errors="Assertion or fixture failures propagate to Vitest" side_effects="Initializes a module-local constant"]
23
+ // CIL[DOC-MICRO logic_summary="Pair runtime and MCP manifest implementations for identical behavior tests." notable_decisions=""]
24
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
25
+ // CIL[DOC-STABILITY stability="experimental"]
26
+ // CIL[DOC-AUDIENCE audience="framework contributors"]
27
+ const modules = [
28
+ ['runtime .framework/functions/_shared/app-manifest.ts', runtimeMod],
29
+ ['mcp .framework/mcp/shared/app-manifest.ts', mcpMod],
30
+ ] as const
31
+ // CIL[END stable_id="~/.framework/tests/unit/app-manifest.test.ts:modules:constant"]
32
+
33
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/app-manifest.test.ts:root:variable" type="variable" identity="root" parent="none"]
34
+ // CIL[DOC-MACRO purpose="Declare the temporary repository root used by fixture hooks."]
35
+ // CIL[DOC-RESPONSIBILITY responsibility="Declare the temporary repository root used by fixture hooks."]
36
+ // CIL[DOC-CONTEXT module="tests/unit" layer="test-unit" related_files=".framework/functions/_shared/app-manifest.ts,.framework/mcp/shared/app-manifest.ts"]
37
+ // CIL[DOC-CONTRACT inputs="none" outputs="Mutable root path" errors="Assertion or fixture failures propagate to Vitest" side_effects="Declares module-local fixture state"]
38
+ // CIL[DOC-MICRO logic_summary="Declare the temporary repository root used by fixture hooks." notable_decisions=""]
39
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
40
+ // CIL[DOC-STABILITY stability="experimental"]
41
+ // CIL[DOC-AUDIENCE audience="framework contributors"]
42
+ let root: string
43
+ // CIL[END stable_id="~/.framework/tests/unit/app-manifest.test.ts:root:variable"]
44
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/app-manifest.test.ts:savedrepopath:variable" type="variable" identity="savedRepoPath" parent="none"]
45
+ // CIL[DOC-MACRO purpose="Declare the saved REPO_PATH value restored after each fixture."]
46
+ // CIL[DOC-RESPONSIBILITY responsibility="Declare the saved REPO_PATH value restored after each fixture."]
47
+ // CIL[DOC-CONTEXT module="tests/unit" layer="test-unit" related_files=".framework/functions/_shared/app-manifest.ts,.framework/mcp/shared/app-manifest.ts"]
48
+ // CIL[DOC-CONTRACT inputs="none" outputs="Optional saved environment value" errors="Assertion or fixture failures propagate to Vitest" side_effects="Declares module-local fixture state"]
49
+ // CIL[DOC-MICRO logic_summary="Declare the saved REPO_PATH value restored after each fixture." notable_decisions=""]
50
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
51
+ // CIL[DOC-STABILITY stability="experimental"]
52
+ // CIL[DOC-AUDIENCE audience="framework contributors"]
53
+ let savedRepoPath: string | undefined
54
+ // CIL[END stable_id="~/.framework/tests/unit/app-manifest.test.ts:savedrepopath:variable"]
55
+
56
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/app-manifest.test.ts:fixture_setup:block" type="block" identity="fixture_setup" parent="none"]
57
+ // CIL[DOC-MACRO purpose="Register a beforeEach hook that creates a temporary repo and saves REPO_PATH."]
58
+ // CIL[DOC-RESPONSIBILITY responsibility="Register a beforeEach hook that creates a temporary repo and saves REPO_PATH."]
59
+ // CIL[DOC-CONTEXT module="tests/unit" layer="test-unit" related_files=".framework/functions/_shared/app-manifest.ts,.framework/mcp/shared/app-manifest.ts"]
60
+ // CIL[DOC-CONTRACT inputs="Vitest beforeEach lifecycle" outputs="Temporary root and saved environment state" errors="Assertion or fixture failures propagate to Vitest" side_effects="Registers a fixture hook; the hook creates a temporary directory"]
61
+ // CIL[DOC-MICRO logic_summary="Register a beforeEach hook that creates a temporary repo and saves REPO_PATH." notable_decisions=""]
62
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
63
+ // CIL[DOC-STABILITY stability="experimental"]
64
+ // CIL[DOC-AUDIENCE audience="framework contributors"]
65
+ beforeEach(() => {
66
+ root = mkdtempSync(join(tmpdir(), 'spine-app-manifest-'))
67
+ savedRepoPath = process.env.REPO_PATH
68
+ })
69
+ // CIL[END stable_id="~/.framework/tests/unit/app-manifest.test.ts:fixture_setup:block"]
70
+
71
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/app-manifest.test.ts:fixture_cleanup:block" type="block" identity="fixture_cleanup" parent="none"]
72
+ // CIL[DOC-MACRO purpose="Register an afterEach hook that removes the fixture repo and restores REPO_PATH."]
73
+ // CIL[DOC-RESPONSIBILITY responsibility="Register an afterEach hook that removes the fixture repo and restores REPO_PATH."]
74
+ // CIL[DOC-CONTEXT module="tests/unit" layer="test-unit" related_files=".framework/functions/_shared/app-manifest.ts,.framework/mcp/shared/app-manifest.ts"]
75
+ // CIL[DOC-CONTRACT inputs="Fixture root and savedRepoPath" outputs="Cleaned fixture and restored environment" errors="Assertion or fixture failures propagate to Vitest" side_effects="Registers a hook that removes only the created directory and restores an environment variable"]
76
+ // CIL[DOC-MICRO logic_summary="Register an afterEach hook that removes the fixture repo and restores REPO_PATH." notable_decisions=""]
77
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
78
+ // CIL[DOC-STABILITY stability="experimental"]
79
+ // CIL[DOC-AUDIENCE audience="framework contributors"]
80
+ afterEach(() => {
81
+ rmSync(root, { recursive: true, force: true })
82
+ if (savedRepoPath === undefined) delete process.env.REPO_PATH
83
+ else process.env.REPO_PATH = savedRepoPath
84
+ })
85
+ // CIL[END stable_id="~/.framework/tests/unit/app-manifest.test.ts:fixture_cleanup:block"]
86
+
87
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/app-manifest.test.ts:writejson:function" type="function" identity="writeJson" parent="none"]
88
+ // CIL[DOC-MACRO purpose="Create a fixture directory and write supplied JSON or raw text to its named file."]
89
+ // CIL[DOC-RESPONSIBILITY responsibility="Create a fixture directory and write supplied JSON or raw text to its named file."]
90
+ // CIL[DOC-CONTEXT module="tests/unit" layer="test-unit" related_files=".framework/functions/_shared/app-manifest.ts,.framework/mcp/shared/app-manifest.ts"]
91
+ // CIL[DOC-CONTRACT inputs="dir: string, data: unknown, file: string default manifest.json" outputs="void" errors="Assertion or fixture failures propagate to Vitest" side_effects="Creates fixture directories and writes a fixture file"]
92
+ // CIL[DOC-MICRO logic_summary="Create a fixture directory and write supplied JSON or raw text to its named file." notable_decisions=""]
93
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
94
+ // CIL[DOC-STABILITY stability="experimental"]
95
+ // CIL[DOC-AUDIENCE audience="framework contributors"]
96
+ function writeJson(dir: string, data: unknown, file = 'manifest.json'): void {
97
+ mkdirSync(dir, { recursive: true })
98
+ writeFileSync(join(dir, file), typeof data === 'string' ? data : JSON.stringify(data))
99
+ }
100
+ // CIL[END stable_id="~/.framework/tests/unit/app-manifest.test.ts:writejson:function"]
101
+
102
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/app-manifest.test.ts:valid:constant" type="constant" identity="VALID" parent="none"]
103
+ // CIL[DOC-MACRO purpose="Define minimal valid app manifest data for isolated tests."]
104
+ // CIL[DOC-RESPONSIBILITY responsibility="Define minimal valid app manifest data for isolated tests."]
105
+ // CIL[DOC-CONTEXT module="tests/unit" layer="test-unit" related_files=".framework/functions/_shared/app-manifest.ts,.framework/mcp/shared/app-manifest.ts"]
106
+ // CIL[DOC-CONTRACT inputs="none" outputs="Fixture manifest with name, slug, routes, and nav_items" errors="Assertion or fixture failures propagate to Vitest" side_effects="Initializes a module-local constant"]
107
+ // CIL[DOC-MICRO logic_summary="Define minimal valid app manifest data for isolated tests." notable_decisions=""]
108
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
109
+ // CIL[DOC-STABILITY stability="experimental"]
110
+ // CIL[DOC-AUDIENCE audience="framework contributors"]
111
+ const VALID = {
112
+ name: 'Test App',
113
+ slug: 'test-app',
114
+ routes: ['/x'],
115
+ nav_items: [],
116
+ }
117
+ // CIL[END stable_id="~/.framework/tests/unit/app-manifest.test.ts:valid:constant"]
118
+
119
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/app-manifest.test.ts:manifest_behavior_suites:block" type="block" identity="manifest_behavior_suites" parent="none"]
120
+ // CIL[DOC-MACRO purpose="Register the same loader and merger assertions for runtime and MCP implementations."]
121
+ // CIL[DOC-RESPONSIBILITY responsibility="Register the same loader and merger assertions for runtime and MCP implementations."]
122
+ // CIL[DOC-CONTEXT module="tests/unit" layer="test-unit" related_files=".framework/functions/_shared/app-manifest.ts,.framework/mcp/shared/app-manifest.ts"]
123
+ // CIL[DOC-CONTRACT inputs="modules, fixture state, VALID, writeJson" outputs="Vitest loader and merger test cases" errors="Assertion or fixture failures propagate to Vitest" side_effects="Registers test cases that read and write temporary files and temporarily select REPO_PATH"]
124
+ // CIL[DOC-MICRO logic_summary="Register the same loader and merger assertions for runtime and MCP implementations." notable_decisions=""]
125
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
126
+ // CIL[DOC-STABILITY stability="experimental"]
127
+ // CIL[DOC-AUDIENCE audience="framework contributors"]
128
+ for (const [label, mod] of modules) {
129
+ describe(`loadManifest (${label})`, () => {
130
+ beforeEach(() => mod.clearManifestCache())
131
+
132
+ it('returns null for a missing file', () => {
133
+ expect(mod.loadManifest('custom/apps/none/manifest.json', root)).toBeNull()
134
+ })
135
+
136
+ it('returns null for malformed JSON', () => {
137
+ writeJson(root, '{not json')
138
+ expect(mod.loadManifest('manifest.json', root)).toBeNull()
139
+ })
140
+
141
+ it('returns null for JSON null', () => {
142
+ writeJson(root, 'null')
143
+ expect(mod.loadManifest('manifest.json', root)).toBeNull()
144
+ })
145
+
146
+ it('returns null for a JSON array root', () => {
147
+ writeJson(root, [{ slug: 'x', name: 'x' }])
148
+ expect(mod.loadManifest('manifest.json', root)).toBeNull()
149
+ })
150
+
151
+ it('returns null for a JSON primitive root', () => {
152
+ for (const body of ['"x"', '7', 'true']) {
153
+ writeJson(root, body)
154
+ expect(mod.loadManifest('manifest.json', root), body).toBeNull()
155
+ }
156
+ })
157
+
158
+ it('returns null when name or slug is missing or not a nonempty string', () => {
159
+ writeJson(root, { name: 'x' })
160
+ expect(mod.loadManifest('manifest.json', root)).toBeNull()
161
+ writeJson(root, { slug: 'x' })
162
+ expect(mod.loadManifest('manifest.json', root)).toBeNull()
163
+ writeJson(root, { slug: '', name: 'x' })
164
+ expect(mod.loadManifest('manifest.json', root)).toBeNull()
165
+ writeJson(root, { slug: 7, name: 'x' })
166
+ expect(mod.loadManifest('manifest.json', root)).toBeNull()
167
+ writeJson(root, { slug: 'x', name: ' ' })
168
+ expect(mod.loadManifest('manifest.json', root)).toBeNull()
169
+ })
170
+
171
+ it('returns null for a slug outside the allowed charset', () => {
172
+ for (const slug of ['Demo-App', 'demo_app', '9demo', '-demo', 'demo app']) {
173
+ writeJson(root, { ...VALID, slug })
174
+ expect(mod.loadManifest('manifest.json', root), slug).toBeNull()
175
+ }
176
+ })
177
+
178
+ it('returns null when required_roles is not an array of nonempty strings', () => {
179
+ for (const rr of ['member', ['member', 5], ['member', ''], [null], ['member', ' ']]) {
180
+ writeJson(root, { ...VALID, required_roles: rr })
181
+ expect(mod.loadManifest('manifest.json', root), JSON.stringify(rr)).toBeNull()
182
+ }
183
+ })
184
+
185
+ it('defaults required_roles to [] when absent and loads a valid manifest', () => {
186
+ writeJson(root, VALID)
187
+ const m = mod.loadManifest('manifest.json', root)
188
+ expect(m).not.toBeNull()
189
+ expect(m!.slug).toBe('test-app')
190
+ expect(m!.required_roles).toEqual([])
191
+ })
192
+
193
+ it('keeps an OR-role array verbatim', () => {
194
+ writeJson(root, { ...VALID, required_roles: ['member', 'member_admin'] })
195
+ const m = mod.loadManifest('manifest.json', root)
196
+ expect(m!.required_roles).toEqual(['member', 'member_admin'])
197
+ })
198
+
199
+ it('rejects a lexical path escaping the project root', () => {
200
+ const outside = mkdtempSync(join(tmpdir(), 'spine-outside-'))
201
+ try {
202
+ writeJson(outside, VALID)
203
+ expect(mod.loadManifest(relative(root, join(outside, 'manifest.json')), root)).toBeNull()
204
+ expect(mod.loadManifest(join(outside, 'manifest.json'), root)).toBeNull()
205
+ } finally {
206
+ rmSync(outside, { recursive: true, force: true })
207
+ }
208
+ })
209
+
210
+ it('rejects a manifest that is an external symlink', () => {
211
+ const outside = mkdtempSync(join(tmpdir(), 'spine-outside-'))
212
+ try {
213
+ writeJson(outside, VALID)
214
+ mkdirSync(join(root, 'custom/apps/ext'), { recursive: true })
215
+ symlinkSync(join(outside, 'manifest.json'), join(root, 'custom/apps/ext/manifest.json'))
216
+ expect(mod.loadManifest('custom/apps/ext/manifest.json', root)).toBeNull()
217
+ } finally {
218
+ rmSync(outside, { recursive: true, force: true })
219
+ }
220
+ })
221
+
222
+ it('caches by canonical path so the same relative path in different roots never collides', () => {
223
+ const rootB = mkdtempSync(join(tmpdir(), 'spine-app-manifest-b-'))
224
+ try {
225
+ writeJson(join(root, 'custom/apps/a'), { ...VALID, slug: 'a-one' })
226
+ writeJson(join(rootB, 'custom/apps/a'), { ...VALID, slug: 'a-two' })
227
+ const m1 = mod.loadManifest('custom/apps/a/manifest.json', root)
228
+ const m2 = mod.loadManifest('custom/apps/a/manifest.json', rootB)
229
+ const m1Again = mod.loadManifest('custom/apps/a/manifest.json', root)
230
+ expect(m1!.slug).toBe('a-one')
231
+ expect(m2!.slug).toBe('a-two')
232
+ expect(m1Again!.slug).toBe('a-one')
233
+ } finally {
234
+ rmSync(rootB, { recursive: true, force: true })
235
+ }
236
+ })
237
+ })
238
+
239
+ describe(`mergeWithManifest (${label})`, () => {
240
+ beforeEach(() => {
241
+ mod.clearManifestCache()
242
+ process.env.REPO_PATH = root
243
+ })
244
+
245
+ const dbRecord = {
246
+ id: 'db-1',
247
+ slug: 'db-slug',
248
+ config_source: 'manifest',
249
+ manifest_path: 'custom/apps/test-app/manifest.json',
250
+ route_prefix: '/test-app',
251
+ renderer: 'custom',
252
+ config: { knowledge: { account: true } },
253
+ required_roles: ['db_role_a', 'db_role_b'],
254
+ is_active: true,
255
+ is_system: false,
256
+ account_id: 'acct-1',
257
+ ownership: 'account',
258
+ }
259
+
260
+ it('manifest role array wins and db route_prefix/renderer/config are retained', () => {
261
+ writeJson(join(root, 'custom/apps/test-app'), { ...VALID, required_roles: ['member', 'member_admin'] })
262
+ const merged = mod.mergeWithManifest(dbRecord)
263
+ expect(merged.required_roles).toEqual(['member', 'member_admin'])
264
+ expect(merged.route_prefix).toBe('/test-app')
265
+ expect(merged.renderer).toBe('custom')
266
+ expect(merged.config).toEqual({ knowledge: { account: true } })
267
+ expect(merged._source).toBe('manifest')
268
+ expect(merged.id).toBe('db-1')
269
+ })
270
+
271
+ it('keeps an empty manifest role array verbatim', () => {
272
+ writeJson(join(root, 'custom/apps/test-app'), { ...VALID, required_roles: [] })
273
+ const merged = mod.mergeWithManifest(dbRecord)
274
+ expect(merged.required_roles).toEqual([])
275
+ expect(merged.min_role).toBeNull()
276
+ })
277
+
278
+ it('falls back to the db record preserving an existing required_roles array including empty', () => {
279
+ const merged = mod.mergeWithManifest({ slug: 'x', config_source: 'database', required_roles: [], min_role: 'member' })
280
+ expect(merged.required_roles).toEqual([])
281
+ expect(merged._source).toBe('database')
282
+ const merged2 = mod.mergeWithManifest({ slug: 'x', config_source: 'database', required_roles: ['a', 'b'] })
283
+ expect(merged2.required_roles).toEqual(['a', 'b'])
284
+ })
285
+
286
+ it('derives required_roles from min_role only when db.required_roles is absent', () => {
287
+ const merged = mod.mergeWithManifest({ slug: 'x', config_source: 'database', min_role: 'member' })
288
+ expect(merged.required_roles).toEqual(['member'])
289
+ const merged2 = mod.mergeWithManifest({ slug: 'x', config_source: 'database' })
290
+ expect(merged2.required_roles).toEqual([])
291
+ })
292
+
293
+ it('returns the db fallback tagged database when the manifest cannot be loaded', () => {
294
+ const merged = mod.mergeWithManifest({ ...dbRecord, manifest_path: 'custom/apps/missing/manifest.json' })
295
+ expect(merged._source).toBe('database (manifest missing)')
296
+ expect(merged.required_roles).toEqual(['db_role_a', 'db_role_b'])
297
+ })
298
+
299
+ it('returns null for a falsy record', () => {
300
+ expect(mod.mergeWithManifest(null)).toBeNull()
301
+ })
302
+ })
303
+ }
304
+ // CIL[END stable_id="~/.framework/tests/unit/app-manifest.test.ts:manifest_behavior_suites:block"]
305
+
306
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/app-manifest.test.ts:discovery_suite:block" type="block" identity="discovery_suite" parent="none"]
307
+ // CIL[DOC-MACRO purpose="Register discovery tests for sorted arbitrary apps, absent workspaces, slug mismatch, and safe symlinks."]
308
+ // CIL[DOC-RESPONSIBILITY responsibility="Register discovery tests for sorted arbitrary apps, absent workspaces, slug mismatch, and safe symlinks."]
309
+ // CIL[DOC-CONTEXT module="tests/unit" layer="test-unit" related_files=".framework/functions/_shared/app-manifest.ts,.framework/mcp/shared/app-manifest.ts"]
310
+ // CIL[DOC-CONTRACT inputs="runtimeMod and temporary fixture state" outputs="Vitest discovery test cases" errors="Assertion or fixture failures propagate to Vitest" side_effects="Registers tests using temporary directories and symlinks"]
311
+ // CIL[DOC-MICRO logic_summary="Register discovery tests for sorted arbitrary apps, absent workspaces, slug mismatch, and safe symlinks." notable_decisions=""]
312
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
313
+ // CIL[DOC-STABILITY stability="experimental"]
314
+ // CIL[DOC-AUDIENCE audience="framework contributors"]
315
+ describe('discoverManifests (runtime)', () => {
316
+ beforeEach(() => runtimeMod.clearManifestCache())
317
+
318
+ it('enumerates custom/apps children sorted lexically', () => {
319
+ writeJson(join(root, 'custom/apps/zeta'), { ...VALID, slug: 'zeta' })
320
+ writeJson(join(root, 'custom/apps/alpha'), { ...VALID, slug: 'alpha' })
321
+ writeJson(join(root, 'custom/apps/mid'), { ...VALID, slug: 'mid' })
322
+ const found = runtimeMod.discoverManifests(root)
323
+ expect(found).toEqual([
324
+ { slug: 'alpha', path: 'custom/apps/alpha/manifest.json' },
325
+ { slug: 'mid', path: 'custom/apps/mid/manifest.json' },
326
+ { slug: 'zeta', path: 'custom/apps/zeta/manifest.json' },
327
+ ])
328
+ })
329
+
330
+ it('returns [] when custom/apps is absent', () => {
331
+ expect(runtimeMod.discoverManifests(root)).toEqual([])
332
+ })
333
+
334
+ it('skips a directory whose name does not match the manifest slug', () => {
335
+ writeJson(join(root, 'custom/apps/renamed'), { ...VALID, slug: 'other-slug' })
336
+ writeJson(join(root, 'custom/apps/good'), { ...VALID, slug: 'good' })
337
+ const found = runtimeMod.discoverManifests(root)
338
+ expect(found).toEqual([{ slug: 'good', path: 'custom/apps/good/manifest.json' }])
339
+ })
340
+
341
+ it('skips an external symlink app dir but inspects an internal symlink dir', () => {
342
+ const outside = mkdtempSync(join(tmpdir(), 'spine-outside-'))
343
+ try {
344
+ writeJson(outside, { ...VALID, slug: 'external' })
345
+ mkdirSync(join(root, 'custom/apps'), { recursive: true })
346
+ symlinkSync(outside, join(root, 'custom/apps/external'))
347
+
348
+ const internalTarget = join(root, 'internal-target/linked')
349
+ writeJson(internalTarget, { ...VALID, slug: 'linked' })
350
+ symlinkSync(internalTarget, join(root, 'custom/apps/linked'))
351
+
352
+ const found = runtimeMod.discoverManifests(root)
353
+ expect(found).toEqual([{ slug: 'linked', path: 'custom/apps/linked/manifest.json' }])
354
+ } finally {
355
+ rmSync(outside, { recursive: true, force: true })
356
+ }
357
+ })
358
+ })
359
+ // CIL[END stable_id="~/.framework/tests/unit/app-manifest.test.ts:discovery_suite:block"]
@@ -0,0 +1,172 @@
1
+ // CIL[BEGIN stable_id="~/.framework/tests/unit/assembly-scripts.test.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/tests/unit/assembly-scripts.test.ts"]
2
+ // CIL[DOC-MACRO purpose="Fixture-based invariants for the assemble scripts: per-app layout, public-asset collision detection, duplicate-handler detection, dev-alias symlink handling."]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Run scripts/assemble-frontend.sh and scripts/assemble-functions.sh against fixture project roots and assert layout + collision semantics."]
4
+ // CIL[DOC-CONTEXT module="assembly-scripts.test" layer="test" related_files="scripts/assemble-frontend.sh,scripts/assemble-functions.sh"]
5
+ // CIL[DOC-CONTRACT inputs="fixture roots under os.tmpdir() via SPINE_PROJECT_ROOT" outputs="exit-code + file-existence assertions" errors="none" side_effects="creates+removes temp dirs"]
6
+ // CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
7
+ // CIL[DOC-STABILITY stability="stable"]
8
+ // CIL[DOC-NOTE type="harness" content="Both scripts honour SPINE_PROJECT_ROOT so tests never touch the real .assembled tree. NB: .framework/scripts/ holds stale packaged copies — tests must point at /scripts."]
9
+ // CIL[END stable_id="~/.framework/tests/unit/assembly-scripts.test.ts:module_header:module_header"]
10
+ import { describe, it, expect, afterEach } from 'vitest'
11
+ import {
12
+ mkdtempSync, mkdirSync, writeFileSync, symlinkSync,
13
+ existsSync, rmSync, readFileSync,
14
+ } from 'fs'
15
+ import { tmpdir } from 'os'
16
+ import { join, resolve } from 'path'
17
+ import { spawnSync } from 'child_process'
18
+
19
+ // NB: .framework/scripts/ contains stale packaged copies of these scripts —
20
+ // the tests must run the live ones at repo root /scripts.
21
+ const REPO = resolve(__dirname, '../../..')
22
+ const FRONTEND_SCRIPT = join(REPO, 'scripts/assemble-frontend.sh')
23
+ const FUNCTIONS_SCRIPT = join(REPO, 'scripts/assemble-functions.sh')
24
+
25
+ const roots: string[] = []
26
+
27
+ function makeRoot(): string {
28
+ const root = mkdtempSync(join(tmpdir(), 'spine-asm-'))
29
+ roots.push(root)
30
+ mkdirSync(join(root, '.framework/src'), { recursive: true })
31
+ writeFileSync(join(root, '.framework/src/main.tsx'), 'export {}')
32
+ mkdirSync(join(root, '.framework/functions'), { recursive: true })
33
+ writeFileSync(join(root, '.framework/functions/handler.ts'), 'export const framework = true\n')
34
+ mkdirSync(join(root, 'custom/apps'), { recursive: true })
35
+ return root
36
+ }
37
+
38
+ function addApp(root: string, slug: string, files: Record<string, string>) {
39
+ for (const [rel, content] of Object.entries(files)) {
40
+ const p = join(root, 'custom/apps', slug, rel)
41
+ mkdirSync(join(p, '..'), { recursive: true })
42
+ writeFileSync(p, content)
43
+ }
44
+ }
45
+
46
+ function run(script: string, root: string) {
47
+ return spawnSync('bash', [script], {
48
+ env: { ...process.env, SPINE_PROJECT_ROOT: root },
49
+ encoding: 'utf8',
50
+ timeout: 60_000,
51
+ })
52
+ }
53
+
54
+ afterEach(() => {
55
+ while (roots.length) rmSync(roots.pop()!, { recursive: true, force: true })
56
+ })
57
+
58
+ describe('assemble-frontend.sh', () => {
59
+ it('lays out each app under src/<slug>/ with no basename flattening', () => {
60
+ const root = makeRoot()
61
+ addApp(root, 'appone', {
62
+ 'index.tsx': 'export {}',
63
+ 'components/widget.tsx': 'export {}',
64
+ 'functions/fn.ts': 'export {}',
65
+ 'seed/types.json': '{}',
66
+ 'public/icon.svg': '<svg/>',
67
+ '.hidden': 'x',
68
+ })
69
+ const r = run(FRONTEND_SCRIPT, root)
70
+ expect(r.status, r.stderr).toBe(0)
71
+ const src = join(root, '.assembled/src')
72
+ expect(existsSync(join(src, 'appone/index.tsx'))).toBe(true)
73
+ expect(existsSync(join(src, 'appone/components/widget.tsx'))).toBe(true)
74
+ // no flattening: app's components must not appear at src root
75
+ expect(existsSync(join(src, 'components/widget.tsx'))).toBe(false)
76
+ // exclusions
77
+ expect(existsSync(join(src, 'appone/functions'))).toBe(false)
78
+ expect(existsSync(join(src, 'appone/seed'))).toBe(false)
79
+ expect(existsSync(join(src, 'appone/.hidden'))).toBe(false)
80
+ // public assets merge into project public/
81
+ expect(existsSync(join(root, 'public/icon.svg'))).toBe(true)
82
+ })
83
+
84
+ it('hard-errors when two apps publish the same public path with different content', () => {
85
+ const root = makeRoot()
86
+ addApp(root, 'appone', { 'public/x.txt': 'one' })
87
+ addApp(root, 'apptwo', { 'public/x.txt': 'two' })
88
+ const r = run(FRONTEND_SCRIPT, root)
89
+ expect(r.status).not.toBe(0)
90
+ expect(r.stderr).toContain('x.txt')
91
+ expect(r.stderr).toContain('appone')
92
+ expect(r.stderr).toContain('apptwo')
93
+ })
94
+
95
+ it('also catches app-vs-framework public collisions', () => {
96
+ const root = makeRoot()
97
+ mkdirSync(join(root, '.framework/public'), { recursive: true })
98
+ writeFileSync(join(root, '.framework/public/logo.svg'), 'framework')
99
+ addApp(root, 'appone', { 'public/logo.svg': 'app' })
100
+ const r = run(FRONTEND_SCRIPT, root)
101
+ expect(r.status).not.toBe(0)
102
+ expect(r.stderr).toContain('logo.svg')
103
+ })
104
+
105
+ it('tolerates identical duplicate public assets', () => {
106
+ const root = makeRoot()
107
+ addApp(root, 'appone', { 'public/x.txt': 'same-bytes' })
108
+ addApp(root, 'apptwo', { 'public/x.txt': 'same-bytes' })
109
+ const r = run(FRONTEND_SCRIPT, root)
110
+ expect(r.status, r.stderr).toBe(0)
111
+ expect(readFileSync(join(root, 'public/x.txt'), 'utf8')).toBe('same-bytes')
112
+ })
113
+ })
114
+
115
+ describe('assemble-functions.sh', () => {
116
+ it('hard-errors when a custom function basename collides with a staged file', () => {
117
+ const root = makeRoot()
118
+ addApp(root, 'appone', { 'functions/handler.ts': 'export const app = true\n' })
119
+ const r = run(FUNCTIONS_SCRIPT, root)
120
+ expect(r.status).not.toBe(0)
121
+ expect(r.stderr).toContain('handler.ts')
122
+ expect(r.stderr).toContain('appone')
123
+ })
124
+
125
+ it('hard-errors on app-vs-app basename collisions', () => {
126
+ const root = makeRoot()
127
+ addApp(root, 'appone', { 'functions/dup.ts': 'export {}' })
128
+ addApp(root, 'apptwo', { 'functions/dup.ts': 'export {}' })
129
+ const r = run(FUNCTIONS_SCRIPT, root)
130
+ expect(r.status).not.toBe(0)
131
+ expect(r.stderr).toContain('dup.ts')
132
+ expect(r.stderr).toContain('appone')
133
+ expect(r.stderr).toContain('apptwo')
134
+ })
135
+
136
+ it('silently skips symlink dev-aliases into .framework/functions', () => {
137
+ const root = makeRoot()
138
+ const appFns = join(root, 'custom/apps/appone/functions')
139
+ mkdirSync(appFns, { recursive: true })
140
+ symlinkSync(
141
+ '../../../../.framework/functions/handler.ts',
142
+ join(appFns, 'handler.ts')
143
+ )
144
+ const r = run(FUNCTIONS_SCRIPT, root)
145
+ expect(r.status, r.stderr).toBe(0)
146
+ // staged file is the framework copy, not the alias
147
+ expect(
148
+ readFileSync(join(root, '.assembled/netlify/functions/handler.ts'), 'utf8')
149
+ ).toContain('framework')
150
+ })
151
+
152
+ it('still hard-errors on symlinks that resolve outside .framework/functions', () => {
153
+ const root = makeRoot()
154
+ const appFns = join(root, 'custom/apps/appone/functions')
155
+ mkdirSync(appFns, { recursive: true })
156
+ writeFileSync(join(root, 'custom/apps/appone/real-handler.ts'), 'export {}')
157
+ symlinkSync('../real-handler.ts', join(appFns, 'handler.ts'))
158
+ const r = run(FUNCTIONS_SCRIPT, root)
159
+ expect(r.status).not.toBe(0)
160
+ expect(r.stderr).toContain('handler.ts')
161
+ })
162
+
163
+ it('stages non-colliding custom functions normally', () => {
164
+ const root = makeRoot()
165
+ addApp(root, 'appone', { 'functions/custom_thing.ts': 'export {}' })
166
+ const r = run(FUNCTIONS_SCRIPT, root)
167
+ expect(r.status, r.stderr).toBe(0)
168
+ expect(
169
+ existsSync(join(root, '.assembled/netlify/functions/custom_thing.ts'))
170
+ ).toBe(true)
171
+ })
172
+ })