@memberjunction/ng-dashboards 6.2.0-edge.3 → 6.2.0-edge.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts +4 -3
  2. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts.map +1 -1
  3. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js +8 -5
  4. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js.map +1 -1
  5. package/dist/AI/components/duplicates/compare-value.component.d.ts +67 -0
  6. package/dist/AI/components/duplicates/compare-value.component.d.ts.map +1 -0
  7. package/dist/AI/components/duplicates/compare-value.component.js +168 -0
  8. package/dist/AI/components/duplicates/compare-value.component.js.map +1 -0
  9. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +49 -3
  10. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
  11. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +287 -169
  12. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
  13. package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts +77 -0
  14. package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts.map +1 -0
  15. package/dist/AI/components/duplicates/duplicate-detection-run-scope.js +116 -0
  16. package/dist/AI/components/duplicates/duplicate-detection-run-scope.js.map +1 -0
  17. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts +1 -0
  18. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts.map +1 -1
  19. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js +16 -4
  20. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js.map +1 -1
  21. package/dist/AI/components/tags/tags-resource.component.d.ts +2 -0
  22. package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
  23. package/dist/AI/components/tags/tags-resource.component.js +12 -6
  24. package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
  25. package/dist/AI/components/vectors/sensitive-fields.d.ts +53 -0
  26. package/dist/AI/components/vectors/sensitive-fields.d.ts.map +1 -0
  27. package/dist/AI/components/vectors/sensitive-fields.js +226 -0
  28. package/dist/AI/components/vectors/sensitive-fields.js.map +1 -0
  29. package/dist/AI/components/vectors/vector-document-rules.d.ts +126 -0
  30. package/dist/AI/components/vectors/vector-document-rules.d.ts.map +1 -0
  31. package/dist/AI/components/vectors/vector-document-rules.js +168 -0
  32. package/dist/AI/components/vectors/vector-document-rules.js.map +1 -0
  33. package/dist/AI/components/vectors/vector-index-auto.d.ts +29 -0
  34. package/dist/AI/components/vectors/vector-index-auto.d.ts.map +1 -0
  35. package/dist/AI/components/vectors/vector-index-auto.js +59 -0
  36. package/dist/AI/components/vectors/vector-index-auto.js.map +1 -0
  37. package/dist/AI/components/vectors/vector-management-resource.component.d.ts +58 -0
  38. package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
  39. package/dist/AI/components/vectors/vector-management-resource.component.js +493 -250
  40. package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
  41. package/dist/Actions/components/actions-overview.component.d.ts.map +1 -1
  42. package/dist/Actions/components/actions-overview.component.js +7 -3
  43. package/dist/Actions/components/actions-overview.component.js.map +1 -1
  44. package/dist/Actions/components/categories-list-view.component.d.ts.map +1 -1
  45. package/dist/Actions/components/categories-list-view.component.js +3 -2
  46. package/dist/Actions/components/categories-list-view.component.js.map +1 -1
  47. package/dist/Communication/communication-agent-context.d.ts +11 -1
  48. package/dist/Communication/communication-agent-context.d.ts.map +1 -1
  49. package/dist/Communication/communication-agent-context.js +15 -0
  50. package/dist/Communication/communication-agent-context.js.map +1 -1
  51. package/dist/Communication/communication-dashboard.component.d.ts.map +1 -1
  52. package/dist/Communication/communication-dashboard.component.js +5 -0
  53. package/dist/Communication/communication-dashboard.component.js.map +1 -1
  54. package/dist/Communication/communication-providers-resource.component.d.ts +27 -1
  55. package/dist/Communication/communication-providers-resource.component.d.ts.map +1 -1
  56. package/dist/Communication/communication-providers-resource.component.js +722 -78
  57. package/dist/Communication/communication-providers-resource.component.js.map +1 -1
  58. package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
  59. package/dist/ComponentStudio/component-studio-dashboard.component.js +1 -0
  60. package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
  61. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts +0 -3
  62. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts.map +1 -1
  63. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js +5 -14
  64. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js.map +1 -1
  65. package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
  66. package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -0
  67. package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
  68. package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts.map +1 -1
  69. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js +1 -0
  70. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js.map +1 -1
  71. package/dist/Credentials/components/credentials-list-resource.component.d.ts +2 -0
  72. package/dist/Credentials/components/credentials-list-resource.component.d.ts.map +1 -1
  73. package/dist/Credentials/components/credentials-list-resource.component.js +12 -10
  74. package/dist/Credentials/components/credentials-list-resource.component.js.map +1 -1
  75. package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts +14 -43
  76. package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts.map +1 -1
  77. package/dist/DashboardBrowser/dashboard-browser-agent-context.js +6 -29
  78. package/dist/DashboardBrowser/dashboard-browser-agent-context.js.map +1 -1
  79. package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts +174 -321
  80. package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts.map +1 -1
  81. package/dist/DashboardBrowser/dashboard-browser-resource.component.js +522 -1195
  82. package/dist/DashboardBrowser/dashboard-browser-resource.component.js.map +1 -1
  83. package/dist/DashboardBrowser/dashboard-library-filter.d.ts +116 -0
  84. package/dist/DashboardBrowser/dashboard-library-filter.d.ts.map +1 -0
  85. package/dist/DashboardBrowser/dashboard-library-filter.js +277 -0
  86. package/dist/DashboardBrowser/dashboard-library-filter.js.map +1 -0
  87. package/dist/DashboardBrowser/dashboard-share-dialog.component.d.ts +1 -1
  88. package/dist/DashboardBrowser/dashboard-share-dialog.component.js.map +1 -1
  89. package/dist/DashboardBrowser/dashboards-agent-tools.d.ts +26 -0
  90. package/dist/DashboardBrowser/dashboards-agent-tools.d.ts.map +1 -0
  91. package/dist/DashboardBrowser/dashboards-agent-tools.js +32 -0
  92. package/dist/DashboardBrowser/dashboards-agent-tools.js.map +1 -0
  93. package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts +58 -0
  94. package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts.map +1 -0
  95. package/dist/DashboardBrowser/dashboards-categories-resource.component.js +391 -0
  96. package/dist/DashboardBrowser/dashboards-categories-resource.component.js.map +1 -0
  97. package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts +37 -0
  98. package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts.map +1 -0
  99. package/dist/DashboardBrowser/dashboards-categories.helpers.js +68 -0
  100. package/dist/DashboardBrowser/dashboards-categories.helpers.js.map +1 -0
  101. package/dist/DashboardEditor/dashboard-editor.component.d.ts +295 -0
  102. package/dist/DashboardEditor/dashboard-editor.component.d.ts.map +1 -0
  103. package/dist/DashboardEditor/dashboard-editor.component.js +1022 -0
  104. package/dist/DashboardEditor/dashboard-editor.component.js.map +1 -0
  105. package/dist/DashboardEditor/dashboard-editor.types.d.ts +23 -0
  106. package/dist/DashboardEditor/dashboard-editor.types.d.ts.map +1 -0
  107. package/dist/DashboardEditor/dashboard-editor.types.js +3 -0
  108. package/dist/DashboardEditor/dashboard-editor.types.js.map +1 -0
  109. package/dist/DashboardEditor/dashboard-viewer-factory.d.ts +11 -0
  110. package/dist/DashboardEditor/dashboard-viewer-factory.d.ts.map +1 -0
  111. package/dist/DashboardEditor/dashboard-viewer-factory.js +17 -0
  112. package/dist/DashboardEditor/dashboard-viewer-factory.js.map +1 -0
  113. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +51 -0
  114. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
  115. package/dist/DataExplorer/data-explorer-dashboard.component.js +375 -223
  116. package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
  117. package/dist/DataExplorer/services/explorer-state.service.d.ts +1 -1
  118. package/dist/DataExplorer/services/explorer-state.service.js +2 -5
  119. package/dist/DataExplorer/services/explorer-state.service.js.map +1 -1
  120. package/dist/DevTools/app-state-inspector.component.d.ts.map +1 -1
  121. package/dist/DevTools/app-state-inspector.component.js +1 -0
  122. package/dist/DevTools/app-state-inspector.component.js.map +1 -1
  123. package/dist/DevTools/class-registry.component.d.ts.map +1 -1
  124. package/dist/DevTools/class-registry.component.js +1 -0
  125. package/dist/DevTools/class-registry.component.js.map +1 -1
  126. package/dist/DevTools/event-monitor.component.d.ts.map +1 -1
  127. package/dist/DevTools/event-monitor.component.js +2 -0
  128. package/dist/DevTools/event-monitor.component.js.map +1 -1
  129. package/dist/DevTools/graphql-console.component.d.ts.map +1 -1
  130. package/dist/DevTools/graphql-console.component.js.map +1 -1
  131. package/dist/DevTools/grid-width-lab.component.d.ts.map +1 -1
  132. package/dist/DevTools/grid-width-lab.component.js +1 -0
  133. package/dist/DevTools/grid-width-lab.component.js.map +1 -1
  134. package/dist/DevTools/layout-inspector.component.d.ts.map +1 -1
  135. package/dist/DevTools/layout-inspector.component.js +1 -0
  136. package/dist/DevTools/layout-inspector.component.js.map +1 -1
  137. package/dist/DevTools/lazy-module-status.component.d.ts.map +1 -1
  138. package/dist/DevTools/lazy-module-status.component.js +1 -0
  139. package/dist/DevTools/lazy-module-status.component.js.map +1 -1
  140. package/dist/DevTools/settings-explorer.component.d.ts.map +1 -1
  141. package/dist/DevTools/settings-explorer.component.js +1 -0
  142. package/dist/DevTools/settings-explorer.component.js.map +1 -1
  143. package/dist/DevTools/tab-strip-lab.component.d.ts.map +1 -1
  144. package/dist/DevTools/tab-strip-lab.component.js +1 -0
  145. package/dist/DevTools/tab-strip-lab.component.js.map +1 -1
  146. package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts +5 -0
  147. package/dist/EntityAdmin/entity-admin-dashboard.component.d.ts.map +1 -1
  148. package/dist/EntityAdmin/entity-admin-dashboard.component.js +8 -5
  149. package/dist/EntityAdmin/entity-admin-dashboard.component.js.map +1 -1
  150. package/dist/FormBuilder/form-builder-resource.component.d.ts +0 -14
  151. package/dist/FormBuilder/form-builder-resource.component.d.ts.map +1 -1
  152. package/dist/FormBuilder/form-builder-resource.component.js +7 -29
  153. package/dist/FormBuilder/form-builder-resource.component.js.map +1 -1
  154. package/dist/Home/home-agent-context.d.ts +29 -10
  155. package/dist/Home/home-agent-context.d.ts.map +1 -1
  156. package/dist/Home/home-agent-context.js +50 -24
  157. package/dist/Home/home-agent-context.js.map +1 -1
  158. package/dist/Home/home-dashboard-switcher.component.d.ts +95 -0
  159. package/dist/Home/home-dashboard-switcher.component.d.ts.map +1 -0
  160. package/dist/Home/home-dashboard-switcher.component.js +442 -0
  161. package/dist/Home/home-dashboard-switcher.component.js.map +1 -0
  162. package/dist/Home/home-dashboard.component.d.ts +330 -17
  163. package/dist/Home/home-dashboard.component.d.ts.map +1 -1
  164. package/dist/Home/home-dashboard.component.js +1702 -1014
  165. package/dist/Home/home-dashboard.component.js.map +1 -1
  166. package/dist/Home/home-menus.d.ts +38 -0
  167. package/dist/Home/home-menus.d.ts.map +1 -0
  168. package/dist/Home/home-menus.js +53 -0
  169. package/dist/Home/home-menus.js.map +1 -0
  170. package/dist/Home/home-pinned-dashboards.d.ts +33 -0
  171. package/dist/Home/home-pinned-dashboards.d.ts.map +1 -0
  172. package/dist/Home/home-pinned-dashboards.js +76 -0
  173. package/dist/Home/home-pinned-dashboards.js.map +1 -0
  174. package/dist/Integration/components/overview/overview.component.js +2 -2
  175. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js +2 -2
  176. package/dist/KnowledgeHub/components/config/knowledge-config-resource.component.js.map +1 -1
  177. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.d.ts +11 -1
  178. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.d.ts.map +1 -1
  179. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.js +24 -2
  180. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipeline.engine.js.map +1 -1
  181. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.d.ts +16 -2
  182. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.d.ts.map +1 -1
  183. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.js +87 -33
  184. package/dist/KnowledgeHub/components/feature-pipelines/feature-pipelines-resource.component.js.map +1 -1
  185. package/dist/LoginStudio/login-studio-inspector.component.d.ts +130 -0
  186. package/dist/LoginStudio/login-studio-inspector.component.d.ts.map +1 -0
  187. package/dist/LoginStudio/login-studio-inspector.component.js +781 -0
  188. package/dist/LoginStudio/login-studio-inspector.component.js.map +1 -0
  189. package/dist/LoginStudio/login-studio-resource.component.d.ts +153 -0
  190. package/dist/LoginStudio/login-studio-resource.component.d.ts.map +1 -0
  191. package/dist/LoginStudio/login-studio-resource.component.js +857 -0
  192. package/dist/LoginStudio/login-studio-resource.component.js.map +1 -0
  193. package/dist/PredictiveStudio/components/ps-production.component.d.ts +5 -1
  194. package/dist/PredictiveStudio/components/ps-production.component.d.ts.map +1 -1
  195. package/dist/PredictiveStudio/components/ps-production.component.js +64 -41
  196. package/dist/PredictiveStudio/components/ps-production.component.js.map +1 -1
  197. package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
  198. package/dist/QueryBrowser/query-browser-resource.component.js +3 -2
  199. package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
  200. package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js +2 -2
  201. package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js.map +1 -1
  202. package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts +1 -0
  203. package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts.map +1 -1
  204. package/dist/TelephonyOperations/telephony-operations-dashboard.component.js +7 -0
  205. package/dist/TelephonyOperations/telephony-operations-dashboard.component.js.map +1 -1
  206. package/dist/Testing/components/testing-explorer.component.d.ts.map +1 -1
  207. package/dist/Testing/components/testing-explorer.component.js +7 -7
  208. package/dist/Testing/components/testing-explorer.component.js.map +1 -1
  209. package/dist/Workflows/components/workflow-runs-resource.component.js +4 -4
  210. package/dist/Workflows/components/workflow-runs-resource.component.js.map +1 -1
  211. package/dist/ai-dashboards.module.d.ts +21 -20
  212. package/dist/ai-dashboards.module.d.ts.map +1 -1
  213. package/dist/ai-dashboards.module.js +4 -1
  214. package/dist/ai-dashboards.module.js.map +1 -1
  215. package/dist/core-dashboards.module.d.ts +70 -62
  216. package/dist/core-dashboards.module.d.ts.map +1 -1
  217. package/dist/core-dashboards.module.js +33 -5
  218. package/dist/core-dashboards.module.js.map +1 -1
  219. package/dist/data-explorer-dashboards.module.d.ts +1 -1
  220. package/dist/data-explorer-dashboards.module.d.ts.map +1 -1
  221. package/dist/data-explorer-dashboards.module.js +3 -1
  222. package/dist/data-explorer-dashboards.module.js.map +1 -1
  223. package/dist/public-api.d.ts +3 -0
  224. package/dist/public-api.d.ts.map +1 -1
  225. package/dist/public-api.js +4 -0
  226. package/dist/public-api.js.map +1 -1
  227. package/dist/shared/dashboard-library-changes.d.ts +23 -0
  228. package/dist/shared/dashboard-library-changes.d.ts.map +1 -0
  229. package/dist/shared/dashboard-library-changes.js +26 -0
  230. package/dist/shared/dashboard-library-changes.js.map +1 -0
  231. package/dist/shared/dashboard-owner-names.d.ts +17 -0
  232. package/dist/shared/dashboard-owner-names.d.ts.map +1 -0
  233. package/dist/shared/dashboard-owner-names.js +65 -0
  234. package/dist/shared/dashboard-owner-names.js.map +1 -0
  235. package/dist/shared/dashboard-recents.d.ts +34 -0
  236. package/dist/shared/dashboard-recents.d.ts.map +1 -0
  237. package/dist/shared/dashboard-recents.js +87 -0
  238. package/dist/shared/dashboard-recents.js.map +1 -0
  239. package/dist/shared/dashboards-app-install.d.ts +59 -0
  240. package/dist/shared/dashboards-app-install.d.ts.map +1 -0
  241. package/dist/shared/dashboards-app-install.js +97 -0
  242. package/dist/shared/dashboards-app-install.js.map +1 -0
  243. package/dist/shared/dashboards-app.helpers.d.ts +42 -0
  244. package/dist/shared/dashboards-app.helpers.d.ts.map +1 -0
  245. package/dist/shared/dashboards-app.helpers.js +143 -0
  246. package/dist/shared/dashboards-app.helpers.js.map +1 -0
  247. package/package.json +73 -69
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Fields that must never be embedded into a vector index.
3
+ *
4
+ * Vectorizing a column copies its values into a third-party vector database, where they
5
+ * live outside the entity permissions that normally guard them and are retrievable by
6
+ * nearest-neighbour search rather than by an authorized query. For a password hash, a
7
+ * national insurance or social security number, a bank account or a salary, that is not a
8
+ * feature with a caveat — it is a disclosure.
9
+ *
10
+ * The field picker's suggestions come from a language model, which has no notion of which
11
+ * columns are sensitive and will happily propose `PasswordHash` if the use case seems to
12
+ * call for identity matching. So the refusal cannot live in the prompt: it has to be a
13
+ * property of the code on both sides of the model.
14
+ *
15
+ * Two layers, deliberately:
16
+ *
17
+ * 1. The model never sees these fields, on the entity or on its related entities, so it
18
+ * cannot suggest them.
19
+ * 2. The saved template is re-checked, because the template is hand-editable after the
20
+ * model returns and a user can type a field name the picker never offered.
21
+ *
22
+ * Layer 1 alone would be a prompt-shaped guarantee, which is no guarantee at all.
23
+ */
24
+ /**
25
+ * True when this field name must not be embedded.
26
+ *
27
+ * Note `hash` and `auth` are deliberately broad. A column called `AuthorName` normalises to
28
+ * `authorname`, which contains `auth` — so it is refused. That is the trade accepted above:
29
+ * a visible false refusal the user can work around beats a silent disclosure they cannot.
30
+ */
31
+ export declare function IsSensitiveFieldName(name: string | null | undefined): boolean;
32
+ /** Drops every sensitive field from a list, preserving order. */
33
+ export declare function WithoutSensitiveFields<T extends {
34
+ Name: string;
35
+ }>(fields: ReadonlyArray<T>): T[];
36
+ /**
37
+ * Sensitive field names referenced by a template's `{{Placeholder}}` tokens.
38
+ *
39
+ * This is the second layer. The template is hand-editable after the model returns, so a
40
+ * user can type a field the picker never offered. Returns the offending names so the
41
+ * refusal can say which ones, rather than only that something was wrong.
42
+ */
43
+ export declare function SensitiveFieldsInTemplate(templateText: string | null | undefined): string[];
44
+ /**
45
+ * Field names referenced by a template's `{{Placeholder}}` tokens, in order of appearance
46
+ * and including repeats.
47
+ *
48
+ * Exported so the field-count cap in `vector-document-rules.ts` parses placeholders the same
49
+ * way this refusal does. Two parsers that drift apart would mean a template that satisfies
50
+ * one guard and evades the other.
51
+ */
52
+ export declare function TemplatePlaceholderNames(templateText: string | null | undefined): string[];
53
+ //# sourceMappingURL=sensitive-fields.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sensitive-fields.d.ts","sourceRoot":"","sources":["../../../../src/AI/components/vectors/sensitive-fields.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAuJH;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAW7E;AAED,iEAAiE;AACjE,wBAAgB,sBAAsB,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAEhG;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAQ3F;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAa1F"}
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Fields that must never be embedded into a vector index.
3
+ *
4
+ * Vectorizing a column copies its values into a third-party vector database, where they
5
+ * live outside the entity permissions that normally guard them and are retrievable by
6
+ * nearest-neighbour search rather than by an authorized query. For a password hash, a
7
+ * national insurance or social security number, a bank account or a salary, that is not a
8
+ * feature with a caveat — it is a disclosure.
9
+ *
10
+ * The field picker's suggestions come from a language model, which has no notion of which
11
+ * columns are sensitive and will happily propose `PasswordHash` if the use case seems to
12
+ * call for identity matching. So the refusal cannot live in the prompt: it has to be a
13
+ * property of the code on both sides of the model.
14
+ *
15
+ * Two layers, deliberately:
16
+ *
17
+ * 1. The model never sees these fields, on the entity or on its related entities, so it
18
+ * cannot suggest them.
19
+ * 2. The saved template is re-checked, because the template is hand-editable after the
20
+ * model returns and a user can type a field name the picker never offered.
21
+ *
22
+ * Layer 1 alone would be a prompt-shaped guarantee, which is no guarantee at all.
23
+ */
24
+ /**
25
+ * Substrings that mark a column as unembeddable, matched case-insensitively against the
26
+ * field name with separators ignored — so `password_hash`, `PasswordHash` and `PASSWORD`
27
+ * all match the same rule.
28
+ *
29
+ * These are substrings rather than whole names on purpose. Real schemas spell the same
30
+ * concept a dozen ways (`SSNumber`, `employee_ssn`, `ClientSecretEncrypted`), and the cost
31
+ * of the two kinds of mistake is not symmetric: refusing a harmless column produces a
32
+ * visible, correctable annoyance, while embedding a sensitive one is silent and, once
33
+ * written to the index, not fully reversible.
34
+ *
35
+ * That asymmetry justifies over-refusing a column that RESEMBLES the concept — `AuthorName`
36
+ * contains `auth`, and `auth` really is the prefix of authentication, so the resemblance
37
+ * carries some signal and refusing it is a trade we accept. It does not justify a match
38
+ * with no semantic relationship at all. `Rating` is not refused because it looks like a tax
39
+ * identifier; it is refused because the letters `t-i-n` happen to sit inside an unrelated
40
+ * English word, which trades away a usable column for no signal whatsoever. Patterns of
41
+ * that shape belong in {@link SENSITIVE_FIELD_WORD_PATTERNS} or nowhere — see
42
+ * `vector-sensitive-fields.test.ts`, which pins the refusal count over MJ's own field names
43
+ * so that re-adding one is a red test rather than a quiet doubling of the blast radius.
44
+ */
45
+ const SENSITIVE_FIELD_PATTERNS = [
46
+ // Credentials and secrets. `token` is NOT here on its own: MJ alone has 36 columns that
47
+ // are token *counts* (`ContextWindowMaxTokens`, `InputTokenLimit`, `FirstTokenTime`),
48
+ // and a count is not a secret. The compounds below keep the credentials; `AuthToken`
49
+ // and `OAuthToken` are still caught by `auth`.
50
+ 'password', 'passwd', 'pwd', 'secret', 'apikey', 'accesskey', 'privatekey',
51
+ 'accesstoken', 'refreshtoken', 'apitoken', 'bearertoken', 'sessiontoken',
52
+ 'salt', 'hash', 'credential', 'auth', 'connectionstring',
53
+ // Government and tax identifiers. `tin` is deliberately absent: `taxid` already covers
54
+ // the concept, while `t-i-n` is a substring of ordinary English (Ra-tin-g, Marke-tin-g,
55
+ // Set-tin-gs, Mee-tin-g) and matched 43 MJ columns, none of them a tax identifier.
56
+ // A whole-word `tin` is in SENSITIVE_FIELD_WHOLE_WORD_PATTERNS below.
57
+ 'socialsec', 'nationalinsurance', 'taxid', 'passport', 'driverlicense', 'driverslicense',
58
+ // Financial
59
+ 'bank', 'routing', 'iban', 'swift', 'accountnumber', 'cardnumber', 'cardnum', 'cvv',
60
+ 'cvc', 'creditcard', 'salary', 'compensation', 'income',
61
+ // Protected characteristics and other special-category data
62
+ 'ethnic', 'religion', 'political', 'sexualorientation', 'disability',
63
+ 'healthcondition', 'diagnosis', 'medicalrecord', 'dateofbirth', 'birthdate',
64
+ ];
65
+ /**
66
+ * Patterns that must BEGIN A WORD in the field name rather than appear anywhere in it.
67
+ *
68
+ * These three are short enough that as bare substrings they collide with unrelated words
69
+ * and catch nothing real: `ssn` is inside `cla-ssn-ame` (every `*ClassName` column in MJ),
70
+ * `dob` is inside `recor-dob-ject`, `race` is inside `StackT-race` and `T-race-ID`. Each of
71
+ * those matches carries zero information about sensitivity, and because the template check
72
+ * throws, a user who needs `{{Rating}}` or `{{StackTrace}}` has no workaround at all — it
73
+ * is a feature they cannot use, not an annoyance they route around.
74
+ *
75
+ * Anchoring loses no true positives, because all three are whole words wherever they are
76
+ * meant: `ssn`, `employee_ssn`, `EmployeeSSN`, `SSNumber`, `dob`, `DOB`, `race`,
77
+ * `EmployeeRace`. (`date_of_birth` and `DateOfBirth` are covered by `dateofbirth` above.)
78
+ *
79
+ * `idtoken` is here for the same reason: as a substring it sits inside `Pa-idToken-s` and
80
+ * `Val-idToken-Count`, which are counts, while `IdToken`, `IDToken` and `id_token` all begin
81
+ * a word.
82
+ */
83
+ const SENSITIVE_FIELD_WORD_PATTERNS = ['ssn', 'dob', 'race', 'idtoken'];
84
+ /**
85
+ * Patterns that must be a WHOLE WORD: begin a word and end at the end of the name or where
86
+ * the next word begins.
87
+ *
88
+ * `tin` (tax identification number) is too short even for a word start: `Tint`, `TinyInt`
89
+ * and `Tinker` all begin with it. As a word of its own it means one thing: `TIN`,
90
+ * `VendorTIN`, `vendor_tin`, `TINNumber`.
91
+ */
92
+ const SENSITIVE_FIELD_WHOLE_WORD_PATTERNS = ['tin'];
93
+ /** Characters dropped by normalisation. The two spellings must stay in step. */
94
+ const SEPARATORS_GLOBAL = /[\s_\-.]/g;
95
+ const SEPARATOR = /[\s_\-.]/;
96
+ const LOWER_OR_DIGIT = /[a-z0-9]/;
97
+ const UPPER = /[A-Z]/;
98
+ const LOWER = /[a-z]/;
99
+ /** Normalises a field name for matching: lower-cased, separators and spaces removed. */
100
+ function normalizeFieldName(name) {
101
+ return name.toLowerCase().replace(SEPARATORS_GLOBAL, '');
102
+ }
103
+ /**
104
+ * The offsets in `normalizeFieldName(name)` at which a word begins.
105
+ *
106
+ * Normalisation *removes* separators rather than replacing them, so by the time a pattern is
107
+ * matched there is no separator left to anchor a `\b` against — `employee_ssn` and
108
+ * `className` are both one unbroken run of letters (`employeessn`, `classname`). A regex word
109
+ * boundary would therefore match at offset 0 and nowhere else, which is not the boundary we
110
+ * need. So the word starts are computed from the ORIGINAL spelling, where the evidence still
111
+ * exists, and carried as offsets into the normalised string.
112
+ *
113
+ * A word starts at the beginning, after a separator, and at the two camelCase transitions:
114
+ * lower-or-digit followed by upper (`employee|SSN`), and the tail of an acronym run that
115
+ * begins a capitalised word (`SS|Number`, so `SSNumber` still matches `ssn`).
116
+ */
117
+ function wordStartOffsets(name) {
118
+ const starts = new Set();
119
+ let offset = 0;
120
+ let startsWord = true;
121
+ for (let i = 0; i < name.length; i++) {
122
+ const ch = name[i];
123
+ if (SEPARATOR.test(ch)) {
124
+ // Dropped by normalizeFieldName, so it consumes no offset — but whatever
125
+ // survives next begins a word.
126
+ startsWord = true;
127
+ continue;
128
+ }
129
+ const prev = i > 0 ? name[i - 1] : '';
130
+ const next = i + 1 < name.length ? name[i + 1] : '';
131
+ if (UPPER.test(ch) && (LOWER_OR_DIGIT.test(prev) || (UPPER.test(prev) && LOWER.test(next)))) {
132
+ startsWord = true;
133
+ }
134
+ if (startsWord) {
135
+ starts.add(offset);
136
+ }
137
+ startsWord = false;
138
+ offset++;
139
+ }
140
+ return starts;
141
+ }
142
+ /** True when `pattern` occurs in `normalized` starting at one of `wordStarts`. */
143
+ function matchesAtWordStart(normalized, wordStarts, pattern) {
144
+ let at = normalized.indexOf(pattern);
145
+ while (at !== -1) {
146
+ if (wordStarts.has(at)) {
147
+ return true;
148
+ }
149
+ at = normalized.indexOf(pattern, at + 1);
150
+ }
151
+ return false;
152
+ }
153
+ /** True when `pattern` occurs in `normalized` as a whole word, by the offsets in `wordStarts`. */
154
+ function matchesWholeWord(normalized, wordStarts, pattern) {
155
+ let at = normalized.indexOf(pattern);
156
+ while (at !== -1) {
157
+ const end = at + pattern.length;
158
+ if (wordStarts.has(at) && (end === normalized.length || wordStarts.has(end))) {
159
+ return true;
160
+ }
161
+ at = normalized.indexOf(pattern, at + 1);
162
+ }
163
+ return false;
164
+ }
165
+ /**
166
+ * True when this field name must not be embedded.
167
+ *
168
+ * Note `hash` and `auth` are deliberately broad. A column called `AuthorName` normalises to
169
+ * `authorname`, which contains `auth` — so it is refused. That is the trade accepted above:
170
+ * a visible false refusal the user can work around beats a silent disclosure they cannot.
171
+ */
172
+ export function IsSensitiveFieldName(name) {
173
+ if (!name) {
174
+ return false;
175
+ }
176
+ const normalized = normalizeFieldName(name);
177
+ if (SENSITIVE_FIELD_PATTERNS.some((pattern) => normalized.includes(pattern))) {
178
+ return true;
179
+ }
180
+ const wordStarts = wordStartOffsets(name);
181
+ return SENSITIVE_FIELD_WORD_PATTERNS.some((pattern) => matchesAtWordStart(normalized, wordStarts, pattern))
182
+ || SENSITIVE_FIELD_WHOLE_WORD_PATTERNS.some((pattern) => matchesWholeWord(normalized, wordStarts, pattern));
183
+ }
184
+ /** Drops every sensitive field from a list, preserving order. */
185
+ export function WithoutSensitiveFields(fields) {
186
+ return fields.filter((f) => !IsSensitiveFieldName(f.Name));
187
+ }
188
+ /**
189
+ * Sensitive field names referenced by a template's `{{Placeholder}}` tokens.
190
+ *
191
+ * This is the second layer. The template is hand-editable after the model returns, so a
192
+ * user can type a field the picker never offered. Returns the offending names so the
193
+ * refusal can say which ones, rather than only that something was wrong.
194
+ */
195
+ export function SensitiveFieldsInTemplate(templateText) {
196
+ const found = new Set();
197
+ for (const token of TemplatePlaceholderNames(templateText)) {
198
+ if (IsSensitiveFieldName(token)) {
199
+ found.add(token);
200
+ }
201
+ }
202
+ return [...found].sort();
203
+ }
204
+ /**
205
+ * Field names referenced by a template's `{{Placeholder}}` tokens, in order of appearance
206
+ * and including repeats.
207
+ *
208
+ * Exported so the field-count cap in `vector-document-rules.ts` parses placeholders the same
209
+ * way this refusal does. Two parsers that drift apart would mean a template that satisfies
210
+ * one guard and evades the other.
211
+ */
212
+ export function TemplatePlaceholderNames(templateText) {
213
+ if (!templateText) {
214
+ return [];
215
+ }
216
+ // Nunjucks/Handlebars-style placeholders. The field name is the first identifier
217
+ // inside the braces; filters and whitespace after it are ignored.
218
+ const placeholder = /\{\{\s*([A-Za-z0-9_.\-]+)/g;
219
+ const names = [];
220
+ let match;
221
+ while ((match = placeholder.exec(templateText)) !== null) {
222
+ names.push(match[1]);
223
+ }
224
+ return names;
225
+ }
226
+ //# sourceMappingURL=sensitive-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sensitive-fields.js","sourceRoot":"","sources":["../../../../src/AI/components/vectors/sensitive-fields.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fields that must never be embedded into a vector index.\n *\n * Vectorizing a column copies its values into a third-party vector database, where they\n * live outside the entity permissions that normally guard them and are retrievable by\n * nearest-neighbour search rather than by an authorized query. For a password hash, a\n * national insurance or social security number, a bank account or a salary, that is not a\n * feature with a caveat — it is a disclosure.\n *\n * The field picker's suggestions come from a language model, which has no notion of which\n * columns are sensitive and will happily propose `PasswordHash` if the use case seems to\n * call for identity matching. So the refusal cannot live in the prompt: it has to be a\n * property of the code on both sides of the model.\n *\n * Two layers, deliberately:\n *\n * 1. The model never sees these fields, on the entity or on its related entities, so it\n * cannot suggest them.\n * 2. The saved template is re-checked, because the template is hand-editable after the\n * model returns and a user can type a field name the picker never offered.\n *\n * Layer 1 alone would be a prompt-shaped guarantee, which is no guarantee at all.\n */\n\n/**\n * Substrings that mark a column as unembeddable, matched case-insensitively against the\n * field name with separators ignored — so `password_hash`, `PasswordHash` and `PASSWORD`\n * all match the same rule.\n *\n * These are substrings rather than whole names on purpose. Real schemas spell the same\n * concept a dozen ways (`SSNumber`, `employee_ssn`, `ClientSecretEncrypted`), and the cost\n * of the two kinds of mistake is not symmetric: refusing a harmless column produces a\n * visible, correctable annoyance, while embedding a sensitive one is silent and, once\n * written to the index, not fully reversible.\n *\n * That asymmetry justifies over-refusing a column that RESEMBLES the concept — `AuthorName`\n * contains `auth`, and `auth` really is the prefix of authentication, so the resemblance\n * carries some signal and refusing it is a trade we accept. It does not justify a match\n * with no semantic relationship at all. `Rating` is not refused because it looks like a tax\n * identifier; it is refused because the letters `t-i-n` happen to sit inside an unrelated\n * English word, which trades away a usable column for no signal whatsoever. Patterns of\n * that shape belong in {@link SENSITIVE_FIELD_WORD_PATTERNS} or nowhere — see\n * `vector-sensitive-fields.test.ts`, which pins the refusal count over MJ's own field names\n * so that re-adding one is a red test rather than a quiet doubling of the blast radius.\n */\nconst SENSITIVE_FIELD_PATTERNS: ReadonlyArray<string> = [\n // Credentials and secrets. `token` is NOT here on its own: MJ alone has 36 columns that\n // are token *counts* (`ContextWindowMaxTokens`, `InputTokenLimit`, `FirstTokenTime`),\n // and a count is not a secret. The compounds below keep the credentials; `AuthToken`\n // and `OAuthToken` are still caught by `auth`.\n 'password', 'passwd', 'pwd', 'secret', 'apikey', 'accesskey', 'privatekey',\n 'accesstoken', 'refreshtoken', 'apitoken', 'bearertoken', 'sessiontoken',\n 'salt', 'hash', 'credential', 'auth', 'connectionstring',\n // Government and tax identifiers. `tin` is deliberately absent: `taxid` already covers\n // the concept, while `t-i-n` is a substring of ordinary English (Ra-tin-g, Marke-tin-g,\n // Set-tin-gs, Mee-tin-g) and matched 43 MJ columns, none of them a tax identifier.\n // A whole-word `tin` is in SENSITIVE_FIELD_WHOLE_WORD_PATTERNS below.\n 'socialsec', 'nationalinsurance', 'taxid', 'passport', 'driverlicense', 'driverslicense',\n // Financial\n 'bank', 'routing', 'iban', 'swift', 'accountnumber', 'cardnumber', 'cardnum', 'cvv',\n 'cvc', 'creditcard', 'salary', 'compensation', 'income',\n // Protected characteristics and other special-category data\n 'ethnic', 'religion', 'political', 'sexualorientation', 'disability',\n 'healthcondition', 'diagnosis', 'medicalrecord', 'dateofbirth', 'birthdate',\n];\n\n/**\n * Patterns that must BEGIN A WORD in the field name rather than appear anywhere in it.\n *\n * These three are short enough that as bare substrings they collide with unrelated words\n * and catch nothing real: `ssn` is inside `cla-ssn-ame` (every `*ClassName` column in MJ),\n * `dob` is inside `recor-dob-ject`, `race` is inside `StackT-race` and `T-race-ID`. Each of\n * those matches carries zero information about sensitivity, and because the template check\n * throws, a user who needs `{{Rating}}` or `{{StackTrace}}` has no workaround at all — it\n * is a feature they cannot use, not an annoyance they route around.\n *\n * Anchoring loses no true positives, because all three are whole words wherever they are\n * meant: `ssn`, `employee_ssn`, `EmployeeSSN`, `SSNumber`, `dob`, `DOB`, `race`,\n * `EmployeeRace`. (`date_of_birth` and `DateOfBirth` are covered by `dateofbirth` above.)\n *\n * `idtoken` is here for the same reason: as a substring it sits inside `Pa-idToken-s` and\n * `Val-idToken-Count`, which are counts, while `IdToken`, `IDToken` and `id_token` all begin\n * a word.\n */\nconst SENSITIVE_FIELD_WORD_PATTERNS: ReadonlyArray<string> = ['ssn', 'dob', 'race', 'idtoken'];\n\n/**\n * Patterns that must be a WHOLE WORD: begin a word and end at the end of the name or where\n * the next word begins.\n *\n * `tin` (tax identification number) is too short even for a word start: `Tint`, `TinyInt`\n * and `Tinker` all begin with it. As a word of its own it means one thing: `TIN`,\n * `VendorTIN`, `vendor_tin`, `TINNumber`.\n */\nconst SENSITIVE_FIELD_WHOLE_WORD_PATTERNS: ReadonlyArray<string> = ['tin'];\n\n/** Characters dropped by normalisation. The two spellings must stay in step. */\nconst SEPARATORS_GLOBAL = /[\\s_\\-.]/g;\nconst SEPARATOR = /[\\s_\\-.]/;\nconst LOWER_OR_DIGIT = /[a-z0-9]/;\nconst UPPER = /[A-Z]/;\nconst LOWER = /[a-z]/;\n\n/** Normalises a field name for matching: lower-cased, separators and spaces removed. */\nfunction normalizeFieldName(name: string): string {\n return name.toLowerCase().replace(SEPARATORS_GLOBAL, '');\n}\n\n/**\n * The offsets in `normalizeFieldName(name)` at which a word begins.\n *\n * Normalisation *removes* separators rather than replacing them, so by the time a pattern is\n * matched there is no separator left to anchor a `\\b` against — `employee_ssn` and\n * `className` are both one unbroken run of letters (`employeessn`, `classname`). A regex word\n * boundary would therefore match at offset 0 and nowhere else, which is not the boundary we\n * need. So the word starts are computed from the ORIGINAL spelling, where the evidence still\n * exists, and carried as offsets into the normalised string.\n *\n * A word starts at the beginning, after a separator, and at the two camelCase transitions:\n * lower-or-digit followed by upper (`employee|SSN`), and the tail of an acronym run that\n * begins a capitalised word (`SS|Number`, so `SSNumber` still matches `ssn`).\n */\nfunction wordStartOffsets(name: string): ReadonlySet<number> {\n const starts = new Set<number>();\n let offset = 0;\n let startsWord = true;\n for (let i = 0; i < name.length; i++) {\n const ch = name[i];\n if (SEPARATOR.test(ch)) {\n // Dropped by normalizeFieldName, so it consumes no offset — but whatever\n // survives next begins a word.\n startsWord = true;\n continue;\n }\n const prev = i > 0 ? name[i - 1] : '';\n const next = i + 1 < name.length ? name[i + 1] : '';\n if (UPPER.test(ch) && (LOWER_OR_DIGIT.test(prev) || (UPPER.test(prev) && LOWER.test(next)))) {\n startsWord = true;\n }\n if (startsWord) {\n starts.add(offset);\n }\n startsWord = false;\n offset++;\n }\n return starts;\n}\n\n/** True when `pattern` occurs in `normalized` starting at one of `wordStarts`. */\nfunction matchesAtWordStart(normalized: string, wordStarts: ReadonlySet<number>, pattern: string): boolean {\n let at = normalized.indexOf(pattern);\n while (at !== -1) {\n if (wordStarts.has(at)) {\n return true;\n }\n at = normalized.indexOf(pattern, at + 1);\n }\n return false;\n}\n\n/** True when `pattern` occurs in `normalized` as a whole word, by the offsets in `wordStarts`. */\nfunction matchesWholeWord(normalized: string, wordStarts: ReadonlySet<number>, pattern: string): boolean {\n let at = normalized.indexOf(pattern);\n while (at !== -1) {\n const end = at + pattern.length;\n if (wordStarts.has(at) && (end === normalized.length || wordStarts.has(end))) {\n return true;\n }\n at = normalized.indexOf(pattern, at + 1);\n }\n return false;\n}\n\n/**\n * True when this field name must not be embedded.\n *\n * Note `hash` and `auth` are deliberately broad. A column called `AuthorName` normalises to\n * `authorname`, which contains `auth` — so it is refused. That is the trade accepted above:\n * a visible false refusal the user can work around beats a silent disclosure they cannot.\n */\nexport function IsSensitiveFieldName(name: string | null | undefined): boolean {\n if (!name) {\n return false;\n }\n const normalized = normalizeFieldName(name);\n if (SENSITIVE_FIELD_PATTERNS.some((pattern) => normalized.includes(pattern))) {\n return true;\n }\n const wordStarts = wordStartOffsets(name);\n return SENSITIVE_FIELD_WORD_PATTERNS.some((pattern) => matchesAtWordStart(normalized, wordStarts, pattern))\n || SENSITIVE_FIELD_WHOLE_WORD_PATTERNS.some((pattern) => matchesWholeWord(normalized, wordStarts, pattern));\n}\n\n/** Drops every sensitive field from a list, preserving order. */\nexport function WithoutSensitiveFields<T extends { Name: string }>(fields: ReadonlyArray<T>): T[] {\n return fields.filter((f) => !IsSensitiveFieldName(f.Name));\n}\n\n/**\n * Sensitive field names referenced by a template's `{{Placeholder}}` tokens.\n *\n * This is the second layer. The template is hand-editable after the model returns, so a\n * user can type a field the picker never offered. Returns the offending names so the\n * refusal can say which ones, rather than only that something was wrong.\n */\nexport function SensitiveFieldsInTemplate(templateText: string | null | undefined): string[] {\n const found = new Set<string>();\n for (const token of TemplatePlaceholderNames(templateText)) {\n if (IsSensitiveFieldName(token)) {\n found.add(token);\n }\n }\n return [...found].sort();\n}\n\n/**\n * Field names referenced by a template's `{{Placeholder}}` tokens, in order of appearance\n * and including repeats.\n *\n * Exported so the field-count cap in `vector-document-rules.ts` parses placeholders the same\n * way this refusal does. Two parsers that drift apart would mean a template that satisfies\n * one guard and evades the other.\n */\nexport function TemplatePlaceholderNames(templateText: string | null | undefined): string[] {\n if (!templateText) {\n return [];\n }\n // Nunjucks/Handlebars-style placeholders. The field name is the first identifier\n // inside the braces; filters and whitespace after it are ignored.\n const placeholder = /\\{\\{\\s*([A-Za-z0-9_.\\-]+)/g;\n const names: string[] = [];\n let match: RegExpExecArray | null;\n while ((match = placeholder.exec(templateText)) !== null) {\n names.push(match[1]);\n }\n return names;\n}\n"]}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Rules the Vectors tab applies when it turns a model suggestion into real metadata.
3
+ *
4
+ * Three separate refusals live here, and they share a theme with `sensitive-fields.ts`: the
5
+ * suggestion comes from a language model and the template stays hand-editable afterwards, so
6
+ * anything that matters has to be a property of the code rather than of the prompt.
7
+ *
8
+ * 1. The Entity Document Type is resolved from the use case the user actually picked,
9
+ * instead of always writing a duplicate-detection document.
10
+ * 2. The number of interpolated fields is bounded, because an unbounded list is how a
11
+ * template balloons and how columns nobody chose end up embedded.
12
+ * 3. MemberJunction's own internal entities are not offered as vectorization targets.
13
+ *
14
+ * Kept out of the component so each rule can be tested as a function rather than through a
15
+ * TestBed, matching how `sensitive-fields.ts` is structured.
16
+ */
17
+ /**
18
+ * The Entity Document Type each use case writes.
19
+ *
20
+ * The dialog offers these use cases, and until now every one of them produced a
21
+ * `Record Duplicate` document — the type name was hardcoded in the save path, so choosing
22
+ * "search" silently discarded the choice and wrote the wrong type. A vector pool is typed:
23
+ * `Provider.SearchEntity` and the Search Entity action read `Search`-typed documents, so a
24
+ * search request stored as `Record Duplicate` is not merely mislabelled, it is not found by
25
+ * the thing that wanted it.
26
+ *
27
+ * The two names here are MJ's own, not invented: both are seeded Entity Document Types, and
28
+ * the Vectorize Entity action's `EntityDocumentType` parameter is documented as defaulting
29
+ * to `Record Duplicate` and accepting `Search`.
30
+ *
31
+ * The dialog's Use Case buttons are exactly this map's keys. There is no `classification`
32
+ * entry: MJ seeds no `Classification` type, so that button failed on every stock install and
33
+ * was removed until the type exists (#5630).
34
+ */
35
+ export declare const ENTITY_DOCUMENT_TYPE_BY_USE_CASE: Readonly<Record<string, string>>;
36
+ /**
37
+ * The Entity Document Type name for a use case, or null when the use case is unrecognised.
38
+ *
39
+ * Returns null rather than falling back to a default: a use case this code does not know
40
+ * about must not quietly become a duplicate-detection document.
41
+ */
42
+ export declare function EntityDocumentTypeForUseCase(useCase: string | null | undefined): string | null;
43
+ /**
44
+ * The use case the suggestion prompt is given for an existing document of this type, or null
45
+ * when the type has none (e.g. `Context`).
46
+ *
47
+ * The edit panel's "Regenerate template" uses it, so a regenerated template still matches the
48
+ * document's type. It reads the same map as {@link EntityDocumentTypeForUseCase}, so the two
49
+ * directions cannot drift. Callers refuse on null rather than substitute a use case.
50
+ */
51
+ export declare function SuggestionUseCaseForDocumentType(typeName: string | null | undefined): string | null;
52
+ /**
53
+ * The most distinct fields one generated template may interpolate.
54
+ *
55
+ * Derived from the suggestion prompt's own contract rather than copied from elsewhere.
56
+ * `metadata/prompts/templates/system/entity-document-suggestion.template.md` requires
57
+ * "**Output must be 1-4 natural language sentences**" and explains at length why a
58
+ * key-value dump embeds badly compared with English prose.
59
+ *
60
+ * So: 4 sentences — the prompt's own ceiling — times 6 interpolations per sentence, past
61
+ * which a sentence stops being the natural English the prompt demands and becomes the dump
62
+ * it explicitly forbids. That gives 24.
63
+ *
64
+ * Cross-check against the prompt's three worked examples, which interpolate 8, 6 and 4
65
+ * distinct placeholders: 24 is three times the richest thing the prompt itself demonstrates,
66
+ * so the cap cannot fire on anything the prompt teaches a model to produce. It fires when a
67
+ * model has ignored the sentence contract and reached for the whole table.
68
+ */
69
+ export declare const MAX_TEMPLATE_FIELDS = 24;
70
+ /** Distinct `{{Placeholder}}` field names a template interpolates. */
71
+ export declare function TemplateFieldNames(templateText: string | null | undefined): string[];
72
+ /**
73
+ * Truncates a model-supplied field list to the cap.
74
+ *
75
+ * Truncation rather than refusal, because this list is the model's own description of what
76
+ * it selected and is shown as chips — dropping the tail and saying so is more useful than
77
+ * discarding the whole suggestion. The authoritative refusal is on the template, below,
78
+ * since the template is what actually gets embedded.
79
+ */
80
+ export declare function CapSelectedFields(fields: ReadonlyArray<string> | null | undefined): {
81
+ fields: string[];
82
+ dropped: number;
83
+ };
84
+ /**
85
+ * The refusal message when a template interpolates more fields than the cap allows, or null
86
+ * when it is within the cap.
87
+ *
88
+ * A template cannot be truncated without mangling its prose, so this one refuses rather than
89
+ * trimming — and the message carries both numbers, because "too many fields" without the
90
+ * count is not actionable. The template is editable in the dialog, so the user can act on it
91
+ * without losing the suggestion.
92
+ */
93
+ export declare function TemplateFieldCapRefusal(templateText: string | null | undefined): string | null;
94
+ /**
95
+ * Schemas whose entities are not offered in the picker.
96
+ *
97
+ * Follows the existing frontend precedent — `FRONTEND_BLOCKED_SCHEMAS` in
98
+ * `DatabaseDesigner/services/database-designer.engine.ts` and in
99
+ * `DatabaseDesigner/components/create-wizard/steps/step-relationships.component.ts`, both
100
+ * `new Set(['__mj'])` — rather than inventing a rule.
101
+ */
102
+ export declare const VECTOR_BLOCKED_SCHEMAS: ReadonlySet<string>;
103
+ /**
104
+ * True when an entity may be offered as a vectorization target.
105
+ *
106
+ * The picker previously listed every entity in metadata, so MemberJunction's own ~378
107
+ * internal entities — prompt runs, audit logs, record changes, credentials — were offered
108
+ * alongside the user's business tables, both as noise and as embedding targets.
109
+ *
110
+ * Two rules, and they currently pick out the same set: every `MJ:`-prefixed entity is in the
111
+ * `__mj` schema and vice versa (378 and 378, exactly coextensive, in the 5.46 baseline). The
112
+ * name-prefix rule is therefore defence in depth against an MJ entity registered into some
113
+ * other schema, not extra coverage today. Said plainly so nobody later reads it as load
114
+ * bearing and is surprised.
115
+ *
116
+ * Note MJ does seed two `Search`-typed documents over `__mj` entities (`AI Agents Search`,
117
+ * `AI Prompts Search`), so framework entities are not unvectorizable in principle. Those
118
+ * arrive through metadata sync, which is a different authoring channel with a different
119
+ * audience; this dialog is where a business user configures vectorization of their own data,
120
+ * and burying their tables under the framework's is the defect being fixed.
121
+ */
122
+ export declare function IsVectorizableEntity(entity: {
123
+ Name: string;
124
+ SchemaName?: string | null;
125
+ }): boolean;
126
+ //# sourceMappingURL=vector-document-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vector-document-rules.d.ts","sourceRoot":"","sources":["../../../../src/AI/components/vectors/vector-document-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,gCAAgC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAG7E,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAK9F;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQnG;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,sEAAsE;AACtE,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAEpF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC7B,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,GAAG,SAAS,GACjD;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAYvC;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAW9F;AAID;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAAqB,CAAC;AAE7E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAChC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACrD,OAAO,CAKT"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Rules the Vectors tab applies when it turns a model suggestion into real metadata.
3
+ *
4
+ * Three separate refusals live here, and they share a theme with `sensitive-fields.ts`: the
5
+ * suggestion comes from a language model and the template stays hand-editable afterwards, so
6
+ * anything that matters has to be a property of the code rather than of the prompt.
7
+ *
8
+ * 1. The Entity Document Type is resolved from the use case the user actually picked,
9
+ * instead of always writing a duplicate-detection document.
10
+ * 2. The number of interpolated fields is bounded, because an unbounded list is how a
11
+ * template balloons and how columns nobody chose end up embedded.
12
+ * 3. MemberJunction's own internal entities are not offered as vectorization targets.
13
+ *
14
+ * Kept out of the component so each rule can be tested as a function rather than through a
15
+ * TestBed, matching how `sensitive-fields.ts` is structured.
16
+ */
17
+ import { TemplatePlaceholderNames } from './sensitive-fields';
18
+ // ─── 1. Entity Document Type per use case ────────────────────────────────────
19
+ /**
20
+ * The Entity Document Type each use case writes.
21
+ *
22
+ * The dialog offers these use cases, and until now every one of them produced a
23
+ * `Record Duplicate` document — the type name was hardcoded in the save path, so choosing
24
+ * "search" silently discarded the choice and wrote the wrong type. A vector pool is typed:
25
+ * `Provider.SearchEntity` and the Search Entity action read `Search`-typed documents, so a
26
+ * search request stored as `Record Duplicate` is not merely mislabelled, it is not found by
27
+ * the thing that wanted it.
28
+ *
29
+ * The two names here are MJ's own, not invented: both are seeded Entity Document Types, and
30
+ * the Vectorize Entity action's `EntityDocumentType` parameter is documented as defaulting
31
+ * to `Record Duplicate` and accepting `Search`.
32
+ *
33
+ * The dialog's Use Case buttons are exactly this map's keys. There is no `classification`
34
+ * entry: MJ seeds no `Classification` type, so that button failed on every stock install and
35
+ * was removed until the type exists (#5630).
36
+ */
37
+ export const ENTITY_DOCUMENT_TYPE_BY_USE_CASE = {
38
+ 'duplicate detection': 'Record Duplicate',
39
+ 'search': 'Search',
40
+ };
41
+ /**
42
+ * The Entity Document Type name for a use case, or null when the use case is unrecognised.
43
+ *
44
+ * Returns null rather than falling back to a default: a use case this code does not know
45
+ * about must not quietly become a duplicate-detection document.
46
+ */
47
+ export function EntityDocumentTypeForUseCase(useCase) {
48
+ if (!useCase) {
49
+ return null;
50
+ }
51
+ return ENTITY_DOCUMENT_TYPE_BY_USE_CASE[useCase.trim().toLowerCase()] ?? null;
52
+ }
53
+ /**
54
+ * The use case the suggestion prompt is given for an existing document of this type, or null
55
+ * when the type has none (e.g. `Context`).
56
+ *
57
+ * The edit panel's "Regenerate template" uses it, so a regenerated template still matches the
58
+ * document's type. It reads the same map as {@link EntityDocumentTypeForUseCase}, so the two
59
+ * directions cannot drift. Callers refuse on null rather than substitute a use case.
60
+ */
61
+ export function SuggestionUseCaseForDocumentType(typeName) {
62
+ if (!typeName) {
63
+ return null;
64
+ }
65
+ const wanted = typeName.trim().toLowerCase();
66
+ const match = Object.entries(ENTITY_DOCUMENT_TYPE_BY_USE_CASE)
67
+ .find(([, documentType]) => documentType.toLowerCase() === wanted);
68
+ return match ? match[0] : null;
69
+ }
70
+ // ─── 2. How many fields a template may interpolate ───────────────────────────
71
+ /**
72
+ * The most distinct fields one generated template may interpolate.
73
+ *
74
+ * Derived from the suggestion prompt's own contract rather than copied from elsewhere.
75
+ * `metadata/prompts/templates/system/entity-document-suggestion.template.md` requires
76
+ * "**Output must be 1-4 natural language sentences**" and explains at length why a
77
+ * key-value dump embeds badly compared with English prose.
78
+ *
79
+ * So: 4 sentences — the prompt's own ceiling — times 6 interpolations per sentence, past
80
+ * which a sentence stops being the natural English the prompt demands and becomes the dump
81
+ * it explicitly forbids. That gives 24.
82
+ *
83
+ * Cross-check against the prompt's three worked examples, which interpolate 8, 6 and 4
84
+ * distinct placeholders: 24 is three times the richest thing the prompt itself demonstrates,
85
+ * so the cap cannot fire on anything the prompt teaches a model to produce. It fires when a
86
+ * model has ignored the sentence contract and reached for the whole table.
87
+ */
88
+ export const MAX_TEMPLATE_FIELDS = 24;
89
+ /** Distinct `{{Placeholder}}` field names a template interpolates. */
90
+ export function TemplateFieldNames(templateText) {
91
+ return [...new Set(TemplatePlaceholderNames(templateText))];
92
+ }
93
+ /**
94
+ * Truncates a model-supplied field list to the cap.
95
+ *
96
+ * Truncation rather than refusal, because this list is the model's own description of what
97
+ * it selected and is shown as chips — dropping the tail and saying so is more useful than
98
+ * discarding the whole suggestion. The authoritative refusal is on the template, below,
99
+ * since the template is what actually gets embedded.
100
+ */
101
+ export function CapSelectedFields(fields) {
102
+ if (!fields || fields.length === 0) {
103
+ return { fields: [], dropped: 0 };
104
+ }
105
+ const unique = [...new Set(fields)];
106
+ if (unique.length <= MAX_TEMPLATE_FIELDS) {
107
+ return { fields: unique, dropped: 0 };
108
+ }
109
+ return {
110
+ fields: unique.slice(0, MAX_TEMPLATE_FIELDS),
111
+ dropped: unique.length - MAX_TEMPLATE_FIELDS,
112
+ };
113
+ }
114
+ /**
115
+ * The refusal message when a template interpolates more fields than the cap allows, or null
116
+ * when it is within the cap.
117
+ *
118
+ * A template cannot be truncated without mangling its prose, so this one refuses rather than
119
+ * trimming — and the message carries both numbers, because "too many fields" without the
120
+ * count is not actionable. The template is editable in the dialog, so the user can act on it
121
+ * without losing the suggestion.
122
+ */
123
+ export function TemplateFieldCapRefusal(templateText) {
124
+ const names = TemplateFieldNames(templateText);
125
+ if (names.length <= MAX_TEMPLATE_FIELDS) {
126
+ return null;
127
+ }
128
+ return (`This template interpolates ${names.length} fields, and at most ${MAX_TEMPLATE_FIELDS} ` +
129
+ `may be embedded. A template this wide stops reading like the natural language that ` +
130
+ `embeds well, and pulls in columns nobody chose. Remove ` +
131
+ `${names.length - MAX_TEMPLATE_FIELDS} field(s) from the template and save again.`);
132
+ }
133
+ // ─── 3. Which entities may be offered as vectorization targets ───────────────
134
+ /**
135
+ * Schemas whose entities are not offered in the picker.
136
+ *
137
+ * Follows the existing frontend precedent — `FRONTEND_BLOCKED_SCHEMAS` in
138
+ * `DatabaseDesigner/services/database-designer.engine.ts` and in
139
+ * `DatabaseDesigner/components/create-wizard/steps/step-relationships.component.ts`, both
140
+ * `new Set(['__mj'])` — rather than inventing a rule.
141
+ */
142
+ export const VECTOR_BLOCKED_SCHEMAS = new Set(['__mj']);
143
+ /**
144
+ * True when an entity may be offered as a vectorization target.
145
+ *
146
+ * The picker previously listed every entity in metadata, so MemberJunction's own ~378
147
+ * internal entities — prompt runs, audit logs, record changes, credentials — were offered
148
+ * alongside the user's business tables, both as noise and as embedding targets.
149
+ *
150
+ * Two rules, and they currently pick out the same set: every `MJ:`-prefixed entity is in the
151
+ * `__mj` schema and vice versa (378 and 378, exactly coextensive, in the 5.46 baseline). The
152
+ * name-prefix rule is therefore defence in depth against an MJ entity registered into some
153
+ * other schema, not extra coverage today. Said plainly so nobody later reads it as load
154
+ * bearing and is surprised.
155
+ *
156
+ * Note MJ does seed two `Search`-typed documents over `__mj` entities (`AI Agents Search`,
157
+ * `AI Prompts Search`), so framework entities are not unvectorizable in principle. Those
158
+ * arrive through metadata sync, which is a different authoring channel with a different
159
+ * audience; this dialog is where a business user configures vectorization of their own data,
160
+ * and burying their tables under the framework's is the defect being fixed.
161
+ */
162
+ export function IsVectorizableEntity(entity) {
163
+ if (entity.SchemaName && VECTOR_BLOCKED_SCHEMAS.has(entity.SchemaName)) {
164
+ return false;
165
+ }
166
+ return !entity.Name.startsWith('MJ:');
167
+ }
168
+ //# sourceMappingURL=vector-document-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vector-document-rules.js","sourceRoot":"","sources":["../../../../src/AI/components/vectors/vector-document-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Rules the Vectors tab applies when it turns a model suggestion into real metadata.\n *\n * Three separate refusals live here, and they share a theme with `sensitive-fields.ts`: the\n * suggestion comes from a language model and the template stays hand-editable afterwards, so\n * anything that matters has to be a property of the code rather than of the prompt.\n *\n * 1. The Entity Document Type is resolved from the use case the user actually picked,\n * instead of always writing a duplicate-detection document.\n * 2. The number of interpolated fields is bounded, because an unbounded list is how a\n * template balloons and how columns nobody chose end up embedded.\n * 3. MemberJunction's own internal entities are not offered as vectorization targets.\n *\n * Kept out of the component so each rule can be tested as a function rather than through a\n * TestBed, matching how `sensitive-fields.ts` is structured.\n */\n\nimport { TemplatePlaceholderNames } from './sensitive-fields';\n\n// ─── 1. Entity Document Type per use case ────────────────────────────────────\n\n/**\n * The Entity Document Type each use case writes.\n *\n * The dialog offers these use cases, and until now every one of them produced a\n * `Record Duplicate` document — the type name was hardcoded in the save path, so choosing\n * \"search\" silently discarded the choice and wrote the wrong type. A vector pool is typed:\n * `Provider.SearchEntity` and the Search Entity action read `Search`-typed documents, so a\n * search request stored as `Record Duplicate` is not merely mislabelled, it is not found by\n * the thing that wanted it.\n *\n * The two names here are MJ's own, not invented: both are seeded Entity Document Types, and\n * the Vectorize Entity action's `EntityDocumentType` parameter is documented as defaulting\n * to `Record Duplicate` and accepting `Search`.\n *\n * The dialog's Use Case buttons are exactly this map's keys. There is no `classification`\n * entry: MJ seeds no `Classification` type, so that button failed on every stock install and\n * was removed until the type exists (#5630).\n */\nexport const ENTITY_DOCUMENT_TYPE_BY_USE_CASE: Readonly<Record<string, string>> = {\n 'duplicate detection': 'Record Duplicate',\n 'search': 'Search',\n};\n\n/**\n * The Entity Document Type name for a use case, or null when the use case is unrecognised.\n *\n * Returns null rather than falling back to a default: a use case this code does not know\n * about must not quietly become a duplicate-detection document.\n */\nexport function EntityDocumentTypeForUseCase(useCase: string | null | undefined): string | null {\n if (!useCase) {\n return null;\n }\n return ENTITY_DOCUMENT_TYPE_BY_USE_CASE[useCase.trim().toLowerCase()] ?? null;\n}\n\n/**\n * The use case the suggestion prompt is given for an existing document of this type, or null\n * when the type has none (e.g. `Context`).\n *\n * The edit panel's \"Regenerate template\" uses it, so a regenerated template still matches the\n * document's type. It reads the same map as {@link EntityDocumentTypeForUseCase}, so the two\n * directions cannot drift. Callers refuse on null rather than substitute a use case.\n */\nexport function SuggestionUseCaseForDocumentType(typeName: string | null | undefined): string | null {\n if (!typeName) {\n return null;\n }\n const wanted = typeName.trim().toLowerCase();\n const match = Object.entries(ENTITY_DOCUMENT_TYPE_BY_USE_CASE)\n .find(([, documentType]) => documentType.toLowerCase() === wanted);\n return match ? match[0] : null;\n}\n\n// ─── 2. How many fields a template may interpolate ───────────────────────────\n\n/**\n * The most distinct fields one generated template may interpolate.\n *\n * Derived from the suggestion prompt's own contract rather than copied from elsewhere.\n * `metadata/prompts/templates/system/entity-document-suggestion.template.md` requires\n * \"**Output must be 1-4 natural language sentences**\" and explains at length why a\n * key-value dump embeds badly compared with English prose.\n *\n * So: 4 sentences — the prompt's own ceiling — times 6 interpolations per sentence, past\n * which a sentence stops being the natural English the prompt demands and becomes the dump\n * it explicitly forbids. That gives 24.\n *\n * Cross-check against the prompt's three worked examples, which interpolate 8, 6 and 4\n * distinct placeholders: 24 is three times the richest thing the prompt itself demonstrates,\n * so the cap cannot fire on anything the prompt teaches a model to produce. It fires when a\n * model has ignored the sentence contract and reached for the whole table.\n */\nexport const MAX_TEMPLATE_FIELDS = 24;\n\n/** Distinct `{{Placeholder}}` field names a template interpolates. */\nexport function TemplateFieldNames(templateText: string | null | undefined): string[] {\n return [...new Set(TemplatePlaceholderNames(templateText))];\n}\n\n/**\n * Truncates a model-supplied field list to the cap.\n *\n * Truncation rather than refusal, because this list is the model's own description of what\n * it selected and is shown as chips — dropping the tail and saying so is more useful than\n * discarding the whole suggestion. The authoritative refusal is on the template, below,\n * since the template is what actually gets embedded.\n */\nexport function CapSelectedFields(\n fields: ReadonlyArray<string> | null | undefined,\n): { fields: string[]; dropped: number } {\n if (!fields || fields.length === 0) {\n return { fields: [], dropped: 0 };\n }\n const unique = [...new Set(fields)];\n if (unique.length <= MAX_TEMPLATE_FIELDS) {\n return { fields: unique, dropped: 0 };\n }\n return {\n fields: unique.slice(0, MAX_TEMPLATE_FIELDS),\n dropped: unique.length - MAX_TEMPLATE_FIELDS,\n };\n}\n\n/**\n * The refusal message when a template interpolates more fields than the cap allows, or null\n * when it is within the cap.\n *\n * A template cannot be truncated without mangling its prose, so this one refuses rather than\n * trimming — and the message carries both numbers, because \"too many fields\" without the\n * count is not actionable. The template is editable in the dialog, so the user can act on it\n * without losing the suggestion.\n */\nexport function TemplateFieldCapRefusal(templateText: string | null | undefined): string | null {\n const names = TemplateFieldNames(templateText);\n if (names.length <= MAX_TEMPLATE_FIELDS) {\n return null;\n }\n return (\n `This template interpolates ${names.length} fields, and at most ${MAX_TEMPLATE_FIELDS} ` +\n `may be embedded. A template this wide stops reading like the natural language that ` +\n `embeds well, and pulls in columns nobody chose. Remove ` +\n `${names.length - MAX_TEMPLATE_FIELDS} field(s) from the template and save again.`\n );\n}\n\n// ─── 3. Which entities may be offered as vectorization targets ───────────────\n\n/**\n * Schemas whose entities are not offered in the picker.\n *\n * Follows the existing frontend precedent — `FRONTEND_BLOCKED_SCHEMAS` in\n * `DatabaseDesigner/services/database-designer.engine.ts` and in\n * `DatabaseDesigner/components/create-wizard/steps/step-relationships.component.ts`, both\n * `new Set(['__mj'])` — rather than inventing a rule.\n */\nexport const VECTOR_BLOCKED_SCHEMAS: ReadonlySet<string> = new Set(['__mj']);\n\n/**\n * True when an entity may be offered as a vectorization target.\n *\n * The picker previously listed every entity in metadata, so MemberJunction's own ~378\n * internal entities — prompt runs, audit logs, record changes, credentials — were offered\n * alongside the user's business tables, both as noise and as embedding targets.\n *\n * Two rules, and they currently pick out the same set: every `MJ:`-prefixed entity is in the\n * `__mj` schema and vice versa (378 and 378, exactly coextensive, in the 5.46 baseline). The\n * name-prefix rule is therefore defence in depth against an MJ entity registered into some\n * other schema, not extra coverage today. Said plainly so nobody later reads it as load\n * bearing and is surprised.\n *\n * Note MJ does seed two `Search`-typed documents over `__mj` entities (`AI Agents Search`,\n * `AI Prompts Search`), so framework entities are not unvectorizable in principle. Those\n * arrive through metadata sync, which is a different authoring channel with a different\n * audience; this dialog is where a business user configures vectorization of their own data,\n * and burying their tables under the framework's is the defect being fixed.\n */\nexport function IsVectorizableEntity(\n entity: { Name: string; SchemaName?: string | null },\n): boolean {\n if (entity.SchemaName && VECTOR_BLOCKED_SCHEMAS.has(entity.SchemaName)) {\n return false;\n }\n return !entity.Name.startsWith('MJ:');\n}\n"]}