@salesforce/afv-skills 1.13.0 → 1.15.0

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 (359) hide show
  1. package/package.json +3 -3
  2. package/skills/applying-slds/SKILL.md +322 -0
  3. package/skills/applying-slds/checklists.md +83 -0
  4. package/skills/applying-slds/examples.md +283 -0
  5. package/skills/applying-slds/guidance/README.md +83 -0
  6. package/skills/applying-slds/guidance/blueprints-index.md +213 -0
  7. package/skills/applying-slds/guidance/icons-guidance.md +186 -0
  8. package/skills/applying-slds/guidance/overviews/borders.md +236 -0
  9. package/skills/applying-slds/guidance/overviews/color.md +266 -0
  10. package/skills/applying-slds/guidance/overviews/display-density.md +366 -0
  11. package/skills/applying-slds/guidance/overviews/icons.md +240 -0
  12. package/skills/applying-slds/guidance/overviews/illustrations.md +235 -0
  13. package/skills/applying-slds/guidance/overviews/shadows.md +176 -0
  14. package/skills/applying-slds/guidance/overviews/spacing.md +216 -0
  15. package/skills/applying-slds/guidance/overviews/typography.md +323 -0
  16. package/skills/applying-slds/guidance/overviews/utilities.md +542 -0
  17. package/skills/applying-slds/guidance/slds-development-guide.md +288 -0
  18. package/skills/applying-slds/guidance/styling-hooks/borders.md +202 -0
  19. package/skills/applying-slds/guidance/styling-hooks/color/expressive-palette-hooks.md +153 -0
  20. package/skills/applying-slds/guidance/styling-hooks/color/index.md +171 -0
  21. package/skills/applying-slds/guidance/styling-hooks/color/semantic/accent-hooks.md +204 -0
  22. package/skills/applying-slds/guidance/styling-hooks/color/semantic/feedback-hooks.md +768 -0
  23. package/skills/applying-slds/guidance/styling-hooks/color/semantic/surface-hooks.md +337 -0
  24. package/skills/applying-slds/guidance/styling-hooks/color/system-hooks.md +132 -0
  25. package/skills/applying-slds/guidance/styling-hooks/index.md +327 -0
  26. package/skills/applying-slds/guidance/styling-hooks/shadows.md +238 -0
  27. package/skills/applying-slds/guidance/styling-hooks/spacing.md +254 -0
  28. package/skills/applying-slds/guidance/styling-hooks/typography.md +448 -0
  29. package/skills/applying-slds/guidance/utilities/alignment.md +119 -0
  30. package/skills/applying-slds/guidance/utilities/borders.md +131 -0
  31. package/skills/applying-slds/guidance/utilities/box.md +125 -0
  32. package/skills/applying-slds/guidance/utilities/color.md +165 -0
  33. package/skills/applying-slds/guidance/utilities/dark-mode.md +111 -0
  34. package/skills/applying-slds/guidance/utilities/description-list.md +168 -0
  35. package/skills/applying-slds/guidance/utilities/floats.md +117 -0
  36. package/skills/applying-slds/guidance/utilities/grid.md +264 -0
  37. package/skills/applying-slds/guidance/utilities/horizontal-list.md +110 -0
  38. package/skills/applying-slds/guidance/utilities/hyphenation.md +84 -0
  39. package/skills/applying-slds/guidance/utilities/index.md +205 -0
  40. package/skills/applying-slds/guidance/utilities/interactions.md +89 -0
  41. package/skills/applying-slds/guidance/utilities/layout.md +109 -0
  42. package/skills/applying-slds/guidance/utilities/line-clamp.md +131 -0
  43. package/skills/applying-slds/guidance/utilities/margin.md +155 -0
  44. package/skills/applying-slds/guidance/utilities/media-object.md +161 -0
  45. package/skills/applying-slds/guidance/utilities/name-value-list.md +152 -0
  46. package/skills/applying-slds/guidance/utilities/padding.md +155 -0
  47. package/skills/applying-slds/guidance/utilities/position.md +177 -0
  48. package/skills/applying-slds/guidance/utilities/print.md +114 -0
  49. package/skills/applying-slds/guidance/utilities/scrollable.md +126 -0
  50. package/skills/applying-slds/guidance/utilities/sizing.md +190 -0
  51. package/skills/applying-slds/guidance/utilities/themes.md +121 -0
  52. package/skills/applying-slds/guidance/utilities/truncate.md +127 -0
  53. package/skills/applying-slds/guidance/utilities/typography.md +166 -0
  54. package/skills/applying-slds/guidance/utilities/vertical-list.md +166 -0
  55. package/skills/applying-slds/guidance/utilities/visibility.md +228 -0
  56. package/skills/applying-slds/metadata/README.md +84 -0
  57. package/skills/applying-slds/metadata/blueprints/components/accordion.yaml +304 -0
  58. package/skills/applying-slds/metadata/blueprints/components/activity-timeline.yaml +92 -0
  59. package/skills/applying-slds/metadata/blueprints/components/alert.yaml +103 -0
  60. package/skills/applying-slds/metadata/blueprints/components/app-launcher.yaml +94 -0
  61. package/skills/applying-slds/metadata/blueprints/components/avatar-group.yaml +81 -0
  62. package/skills/applying-slds/metadata/blueprints/components/avatar.yaml +97 -0
  63. package/skills/applying-slds/metadata/blueprints/components/badges.yaml +102 -0
  64. package/skills/applying-slds/metadata/blueprints/components/brand-band.yaml +198 -0
  65. package/skills/applying-slds/metadata/blueprints/components/breadcrumbs.yaml +95 -0
  66. package/skills/applying-slds/metadata/blueprints/components/builder-header.yaml +192 -0
  67. package/skills/applying-slds/metadata/blueprints/components/button-groups.yaml +82 -0
  68. package/skills/applying-slds/metadata/blueprints/components/button-icons.yaml +295 -0
  69. package/skills/applying-slds/metadata/blueprints/components/buttons.yaml +230 -0
  70. package/skills/applying-slds/metadata/blueprints/components/cards.yaml +124 -0
  71. package/skills/applying-slds/metadata/blueprints/components/carousel.yaml +140 -0
  72. package/skills/applying-slds/metadata/blueprints/components/chat.yaml +179 -0
  73. package/skills/applying-slds/metadata/blueprints/components/checkbox-button-group.yaml +192 -0
  74. package/skills/applying-slds/metadata/blueprints/components/checkbox-button.yaml +204 -0
  75. package/skills/applying-slds/metadata/blueprints/components/checkbox-toggle.yaml +177 -0
  76. package/skills/applying-slds/metadata/blueprints/components/checkbox.yaml +108 -0
  77. package/skills/applying-slds/metadata/blueprints/components/color-picker.yaml +172 -0
  78. package/skills/applying-slds/metadata/blueprints/components/combobox.yaml +136 -0
  79. package/skills/applying-slds/metadata/blueprints/components/counter.yaml +147 -0
  80. package/skills/applying-slds/metadata/blueprints/components/data-tables.yaml +157 -0
  81. package/skills/applying-slds/metadata/blueprints/components/datepickers.yaml +130 -0
  82. package/skills/applying-slds/metadata/blueprints/components/datetime-picker.yaml +155 -0
  83. package/skills/applying-slds/metadata/blueprints/components/docked-composer.yaml +201 -0
  84. package/skills/applying-slds/metadata/blueprints/components/docked-form-footer.yaml +161 -0
  85. package/skills/applying-slds/metadata/blueprints/components/docked-utility-bar.yaml +175 -0
  86. package/skills/applying-slds/metadata/blueprints/components/drop-zone.yaml +115 -0
  87. package/skills/applying-slds/metadata/blueprints/components/dueling-picklist.yaml +196 -0
  88. package/skills/applying-slds/metadata/blueprints/components/dynamic-icons.yaml +128 -0
  89. package/skills/applying-slds/metadata/blueprints/components/dynamic-menu.yaml +141 -0
  90. package/skills/applying-slds/metadata/blueprints/components/expandable-section.yaml +115 -0
  91. package/skills/applying-slds/metadata/blueprints/components/expression.yaml +143 -0
  92. package/skills/applying-slds/metadata/blueprints/components/feeds.yaml +125 -0
  93. package/skills/applying-slds/metadata/blueprints/components/file-selector.yaml +154 -0
  94. package/skills/applying-slds/metadata/blueprints/components/files.yaml +119 -0
  95. package/skills/applying-slds/metadata/blueprints/components/form-element.yaml +145 -0
  96. package/skills/applying-slds/metadata/blueprints/components/global-header.yaml +120 -0
  97. package/skills/applying-slds/metadata/blueprints/components/global-navigation.yaml +100 -0
  98. package/skills/applying-slds/metadata/blueprints/components/icons.yaml +138 -0
  99. package/skills/applying-slds/metadata/blueprints/components/illustration.yaml +205 -0
  100. package/skills/applying-slds/metadata/blueprints/components/input.yaml +151 -0
  101. package/skills/applying-slds/metadata/blueprints/components/list-builder.yaml +127 -0
  102. package/skills/applying-slds/metadata/blueprints/components/lookups.yaml +132 -0
  103. package/skills/applying-slds/metadata/blueprints/components/map.yaml +118 -0
  104. package/skills/applying-slds/metadata/blueprints/components/menus.yaml +134 -0
  105. package/skills/applying-slds/metadata/blueprints/components/modals.yaml +152 -0
  106. package/skills/applying-slds/metadata/blueprints/components/notifications.yaml +88 -0
  107. package/skills/applying-slds/metadata/blueprints/components/page-headers.yaml +135 -0
  108. package/skills/applying-slds/metadata/blueprints/components/panels.yaml +149 -0
  109. package/skills/applying-slds/metadata/blueprints/components/path.yaml +154 -0
  110. package/skills/applying-slds/metadata/blueprints/components/picklist.yaml +125 -0
  111. package/skills/applying-slds/metadata/blueprints/components/pills.yaml +154 -0
  112. package/skills/applying-slds/metadata/blueprints/components/popovers.yaml +120 -0
  113. package/skills/applying-slds/metadata/blueprints/components/progress-bar.yaml +110 -0
  114. package/skills/applying-slds/metadata/blueprints/components/progress-indicator.yaml +133 -0
  115. package/skills/applying-slds/metadata/blueprints/components/progress-ring.yaml +102 -0
  116. package/skills/applying-slds/metadata/blueprints/components/prompt.yaml +126 -0
  117. package/skills/applying-slds/metadata/blueprints/components/publishers.yaml +178 -0
  118. package/skills/applying-slds/metadata/blueprints/components/radio-button-group.yaml +172 -0
  119. package/skills/applying-slds/metadata/blueprints/components/radio-group.yaml +112 -0
  120. package/skills/applying-slds/metadata/blueprints/components/rich-text-editor.yaml +135 -0
  121. package/skills/applying-slds/metadata/blueprints/components/scoped-notifications.yaml +188 -0
  122. package/skills/applying-slds/metadata/blueprints/components/scoped-tabs.yaml +97 -0
  123. package/skills/applying-slds/metadata/blueprints/components/select.yaml +127 -0
  124. package/skills/applying-slds/metadata/blueprints/components/setup-assistant.yaml +152 -0
  125. package/skills/applying-slds/metadata/blueprints/components/slider.yaml +111 -0
  126. package/skills/applying-slds/metadata/blueprints/components/spinners.yaml +135 -0
  127. package/skills/applying-slds/metadata/blueprints/components/split-view.yaml +112 -0
  128. package/skills/applying-slds/metadata/blueprints/components/summary-detail.yaml +103 -0
  129. package/skills/applying-slds/metadata/blueprints/components/tabs.yaml +138 -0
  130. package/skills/applying-slds/metadata/blueprints/components/textarea.yaml +116 -0
  131. package/skills/applying-slds/metadata/blueprints/components/tiles.yaml +108 -0
  132. package/skills/applying-slds/metadata/blueprints/components/timepicker.yaml +111 -0
  133. package/skills/applying-slds/metadata/blueprints/components/toast.yaml +154 -0
  134. package/skills/applying-slds/metadata/blueprints/components/tooltips.yaml +107 -0
  135. package/skills/applying-slds/metadata/blueprints/components/tree-grid.yaml +116 -0
  136. package/skills/applying-slds/metadata/blueprints/components/trees.yaml +116 -0
  137. package/skills/applying-slds/metadata/blueprints/components/trial-bar.yaml +112 -0
  138. package/skills/applying-slds/metadata/blueprints/components/vertical-navigation.yaml +130 -0
  139. package/skills/applying-slds/metadata/blueprints/components/vertical-tabs.yaml +140 -0
  140. package/skills/applying-slds/metadata/blueprints/components/visual-picker.yaml +150 -0
  141. package/skills/applying-slds/metadata/blueprints/components/welcome-mat.yaml +136 -0
  142. package/skills/applying-slds/metadata/hooks-index.json +6272 -0
  143. package/skills/applying-slds/metadata/icon-metadata.json +38466 -0
  144. package/skills/applying-slds/metadata/utilities-index.json +21912 -0
  145. package/skills/applying-slds/references/component-selection.md +112 -0
  146. package/skills/applying-slds/references/icons-decision-guide.md +124 -0
  147. package/skills/applying-slds/references/styling-decision-guide.md +228 -0
  148. package/skills/applying-slds/references/utilities-quick-ref.md +125 -0
  149. package/skills/applying-slds/scripts/search-blueprints.cjs +117 -0
  150. package/skills/applying-slds/scripts/search-hooks.cjs +139 -0
  151. package/skills/applying-slds/scripts/search-icons.cjs +174 -0
  152. package/skills/applying-slds/scripts/search-utilities.cjs +161 -0
  153. package/skills/building-ui-bundle-app/SKILL.md +33 -8
  154. package/skills/generating-custom-application/SKILL.md +1 -1
  155. package/skills/generating-custom-lightning-type/SKILL.md +17 -39
  156. package/skills/generating-custom-lightning-type/assets/primitive-types-and-constraints.md +41 -0
  157. package/skills/generating-custom-lightning-type/references/widget-rendition.md +124 -0
  158. package/skills/generating-ui-bundle-custom-app/SKILL.md +93 -0
  159. package/skills/generating-ui-bundle-custom-app/docs/configure-metadata-custom-application.md +70 -0
  160. package/skills/generating-ui-bundle-metadata/SKILL.md +39 -1
  161. package/skills/investigating-agentforce-architecture/README.md +156 -0
  162. package/skills/investigating-agentforce-architecture/SKILL.md +230 -0
  163. package/skills/investigating-agentforce-architecture/assets/cli/describe_sobject.yaml +16 -0
  164. package/skills/investigating-agentforce-architecture/assets/cli/describe_tooling_sobject.yaml +17 -0
  165. package/skills/investigating-agentforce-architecture/assets/cli/list_metadata_genaiprompttemplate.yaml +17 -0
  166. package/skills/investigating-agentforce-architecture/assets/cli/org_display.yaml +15 -0
  167. package/skills/investigating-agentforce-architecture/assets/cli/retrieve_genai_plugin.yaml +18 -0
  168. package/skills/investigating-agentforce-architecture/assets/cli/show_access_token.yaml +27 -0
  169. package/skills/investigating-agentforce-architecture/assets/mermaid/action_tree.mmd +20 -0
  170. package/skills/investigating-agentforce-architecture/assets/mermaid/data_flow.mmd +19 -0
  171. package/skills/investigating-agentforce-architecture/assets/mermaid/dependency_graph.mmd +19 -0
  172. package/skills/investigating-agentforce-architecture/assets/mermaid/invocation_sequence.mmd +20 -0
  173. package/skills/investigating-agentforce-architecture/assets/mermaid/planner_state.mmd +18 -0
  174. package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_ids.soql +3 -0
  175. package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_names.soql +3 -0
  176. package/skills/investigating-agentforce-architecture/assets/soql/bot_definition_details.soql +3 -0
  177. package/skills/investigating-agentforce-architecture/assets/soql/bot_version_lookup.soql +4 -0
  178. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_by_ids.soql +3 -0
  179. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_ids_by_names.soql +3 -0
  180. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_view_by_durable_ids.soql +4 -0
  181. package/skills/investigating-agentforce-architecture/assets/soql/flow_metadata_by_id.soql +3 -0
  182. package/skills/investigating-agentforce-architecture/assets/soql/functions_by_plugins.soql +5 -0
  183. package/skills/investigating-agentforce-architecture/assets/soql/planner_attrs_by_parent_ids.soql +3 -0
  184. package/skills/investigating-agentforce-architecture/assets/soql/planner_bundle_functions.soql +3 -0
  185. package/skills/investigating-agentforce-architecture/assets/soql/planner_definition_by_agent_chain.soql +3 -0
  186. package/skills/investigating-agentforce-architecture/assets/soql/plugin_functions_by_plugin_ids.soql +3 -0
  187. package/skills/investigating-agentforce-architecture/assets/soql/plugin_instructions_by_plugin_ids.soql +3 -0
  188. package/skills/investigating-agentforce-architecture/assets/soql/plugins_by_planner.soql +4 -0
  189. package/skills/investigating-agentforce-architecture/references/architecture_sections.md +243 -0
  190. package/skills/investigating-agentforce-architecture/references/contract.json +244 -0
  191. package/skills/investigating-agentforce-architecture/references/soql_fields.md +512 -0
  192. package/skills/investigating-agentforce-architecture/scripts/_shared/__init__.py +1 -0
  193. package/skills/investigating-agentforce-architecture/scripts/_shared/fs_guard.py +329 -0
  194. package/skills/investigating-agentforce-architecture/scripts/_shared/paths.py +110 -0
  195. package/skills/investigating-agentforce-architecture/scripts/_shared/runtime.py +59 -0
  196. package/skills/investigating-agentforce-architecture/scripts/_shared/sql.py +10 -0
  197. package/skills/investigating-agentforce-architecture/scripts/cache_check.py +234 -0
  198. package/skills/investigating-agentforce-architecture/scripts/config.py +131 -0
  199. package/skills/investigating-agentforce-architecture/scripts/fetch_soql.py +689 -0
  200. package/skills/investigating-agentforce-architecture/scripts/finalize.py +295 -0
  201. package/skills/investigating-agentforce-architecture/scripts/main.py +2835 -0
  202. package/skills/investigating-agentforce-architecture/scripts/metadata_listing.py +265 -0
  203. package/skills/investigating-agentforce-architecture/scripts/parallel_retrieve.py +69 -0
  204. package/skills/investigating-agentforce-architecture/scripts/parse_bundle.py +215 -0
  205. package/skills/investigating-agentforce-architecture/scripts/parse_wave.py +845 -0
  206. package/skills/investigating-agentforce-architecture/scripts/probe_channels.py +302 -0
  207. package/skills/investigating-agentforce-architecture/scripts/render_architecture.py +1043 -0
  208. package/skills/investigating-agentforce-architecture/scripts/resolve_bot.py +255 -0
  209. package/skills/investigating-agentforce-architecture/scripts/resolve_invocation_target.py +130 -0
  210. package/skills/investigating-agentforce-architecture/scripts/rest_client.py +763 -0
  211. package/skills/investigating-agentforce-architecture/scripts/retrieve_planner.py +13 -0
  212. package/skills/investigating-agentforce-architecture/scripts/sf_cli.py +242 -0
  213. package/skills/investigating-agentforce-architecture/scripts/soql_loader.py +253 -0
  214. package/skills/investigating-agentforce-architecture/scripts/summarize_tree.py +143 -0
  215. package/skills/investigating-agentforce-architecture/scripts/tests/__init__.py +0 -0
  216. package/skills/investigating-agentforce-architecture/scripts/tests/_bootstrap.py +23 -0
  217. package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/__init__.py +0 -0
  218. package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/genai_payloads.py +400 -0
  219. package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check.py +307 -0
  220. package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check_main.py +283 -0
  221. package/skills/investigating-agentforce-architecture/scripts/tests/test_config.py +115 -0
  222. package/skills/investigating-agentforce-architecture/scripts/tests/test_end_to_end_fixture.py +651 -0
  223. package/skills/investigating-agentforce-architecture/scripts/tests/test_finalize.py +278 -0
  224. package/skills/investigating-agentforce-architecture/scripts/tests/test_flow_children_inflation.py +582 -0
  225. package/skills/investigating-agentforce-architecture/scripts/tests/test_fs_guard.py +113 -0
  226. package/skills/investigating-agentforce-architecture/scripts/tests/test_iterative_wave_b.py +478 -0
  227. package/skills/investigating-agentforce-architecture/scripts/tests/test_main_pipeline.py +3359 -0
  228. package/skills/investigating-agentforce-architecture/scripts/tests/test_parallel_retrieve.py +131 -0
  229. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_bundle.py +400 -0
  230. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave.py +644 -0
  231. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_classifiers.py +224 -0
  232. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_helpers.py +380 -0
  233. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_main.py +397 -0
  234. package/skills/investigating-agentforce-architecture/scripts/tests/test_per_branch_visited.py +244 -0
  235. package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_channels.py +359 -0
  236. package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_cli_recipes.py +185 -0
  237. package/skills/investigating-agentforce-architecture/scripts/tests/test_render_architecture.py +810 -0
  238. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_bot.py +203 -0
  239. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_creds.py +157 -0
  240. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_invocation_target.py +145 -0
  241. package/skills/investigating-agentforce-architecture/scripts/tests/test_rest_client.py +1253 -0
  242. package/skills/investigating-agentforce-architecture/scripts/tests/test_runtime_override.py +100 -0
  243. package/skills/investigating-agentforce-architecture/scripts/tests/test_sf_cli.py +261 -0
  244. package/skills/investigating-agentforce-architecture/scripts/tests/test_signature_stamping.py +466 -0
  245. package/skills/investigating-agentforce-architecture/scripts/tests/test_soql_loader.py +501 -0
  246. package/skills/investigating-agentforce-architecture/scripts/tests/test_summarize_tree.py +241 -0
  247. package/skills/investigating-agentforce-architecture/scripts/tests/test_write_emit_ctx.py +480 -0
  248. package/skills/investigating-agentforce-architecture/tools/emit_env.py +157 -0
  249. package/skills/investigating-agentforce-architecture/tools/emit_result.py +262 -0
  250. package/skills/investigating-agentforce-architecture/tools/sanitize.py +33 -0
  251. package/skills/investigating-agentforce-architecture/tools/write_emit_ctx.py +332 -0
  252. package/skills/investigating-agentforce-d360/README.md +123 -0
  253. package/skills/investigating-agentforce-d360/SKILL.md +163 -0
  254. package/skills/investigating-agentforce-d360/assets/dc/app_generation.sql +51 -0
  255. package/skills/investigating-agentforce-d360/assets/dc/content_category.sql +44 -0
  256. package/skills/investigating-agentforce-d360/assets/dc/content_quality.sql +41 -0
  257. package/skills/investigating-agentforce-d360/assets/dc/discover_sessions.sql +36 -0
  258. package/skills/investigating-agentforce-d360/assets/dc/feedback.sql +47 -0
  259. package/skills/investigating-agentforce-d360/assets/dc/feedback_details.sql +38 -0
  260. package/skills/investigating-agentforce-d360/assets/dc/gateway_records.sql +45 -0
  261. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_llm.sql +50 -0
  262. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_metadata.sql +44 -0
  263. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_tags.sql +42 -0
  264. package/skills/investigating-agentforce-d360/assets/dc/gateway_requests.sql +89 -0
  265. package/skills/investigating-agentforce-d360/assets/dc/gateway_responses.sql +43 -0
  266. package/skills/investigating-agentforce-d360/assets/dc/generations.sql +52 -0
  267. package/skills/investigating-agentforce-d360/assets/dc/interactions.sql +53 -0
  268. package/skills/investigating-agentforce-d360/assets/dc/messages.sql +53 -0
  269. package/skills/investigating-agentforce-d360/assets/dc/messaging_session.sql +37 -0
  270. package/skills/investigating-agentforce-d360/assets/dc/moment_interactions.sql +34 -0
  271. package/skills/investigating-agentforce-d360/assets/dc/moments.sql +39 -0
  272. package/skills/investigating-agentforce-d360/assets/dc/participants.sql +48 -0
  273. package/skills/investigating-agentforce-d360/assets/dc/sessions.sql +78 -0
  274. package/skills/investigating-agentforce-d360/assets/dc/steps.sql +64 -0
  275. package/skills/investigating-agentforce-d360/assets/dc/tag_associations.sql +46 -0
  276. package/skills/investigating-agentforce-d360/assets/dc/tag_definition_associations.sql +37 -0
  277. package/skills/investigating-agentforce-d360/assets/dc/tag_definitions.sql +50 -0
  278. package/skills/investigating-agentforce-d360/assets/dc/tags.sql +37 -0
  279. package/skills/investigating-agentforce-d360/assets/dc/telemetry_spans.sql +55 -0
  280. package/skills/investigating-agentforce-d360/references/artifacts.md +50 -0
  281. package/skills/investigating-agentforce-d360/references/dc_dmo_fields.md +823 -0
  282. package/skills/investigating-agentforce-d360/references/dc_pipeline_contract.md +608 -0
  283. package/skills/investigating-agentforce-d360/scripts/_shared/__init__.py +2 -0
  284. package/skills/investigating-agentforce-d360/scripts/_shared/cli_override.py +98 -0
  285. package/skills/investigating-agentforce-d360/scripts/_shared/fs_guard.py +334 -0
  286. package/skills/investigating-agentforce-d360/scripts/_shared/paths.py +155 -0
  287. package/skills/investigating-agentforce-d360/scripts/_shared/runtime.py +59 -0
  288. package/skills/investigating-agentforce-d360/scripts/_shared/sql.py +14 -0
  289. package/skills/investigating-agentforce-d360/scripts/assemble_dc.py +1624 -0
  290. package/skills/investigating-agentforce-d360/scripts/config.py +45 -0
  291. package/skills/investigating-agentforce-d360/scripts/dc.py +188 -0
  292. package/skills/investigating-agentforce-d360/scripts/discover_sessions.py +556 -0
  293. package/skills/investigating-agentforce-d360/scripts/fetch_dc.py +1045 -0
  294. package/skills/investigating-agentforce-d360/scripts/render_dc.py +1750 -0
  295. package/skills/investigating-agentforce-d360/scripts/resolve_session.py +264 -0
  296. package/skills/investigating-agentforce-d360/scripts/storage.py +92 -0
  297. package/skills/investigating-agentforce-d360/scripts/tests/__init__.py +0 -0
  298. package/skills/investigating-agentforce-d360/scripts/tests/_bootstrap.py +15 -0
  299. package/skills/investigating-agentforce-d360/scripts/tests/fixtures/__init__.py +0 -0
  300. package/skills/investigating-agentforce-d360/scripts/tests/fixtures/synthetic_session.py +424 -0
  301. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_bootstrap_and_mode.py +115 -0
  302. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct.py +220 -0
  303. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct_integration.py +158 -0
  304. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_helpers.py +287 -0
  305. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_integration.py +247 -0
  306. package/skills/investigating-agentforce-d360/scripts/tests/test_dc_and_resolve_session.py +433 -0
  307. package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions.py +458 -0
  308. package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions_grep_ci.py +193 -0
  309. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_helpers.py +266 -0
  310. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_identity.py +528 -0
  311. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_main.py +251 -0
  312. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall.py +229 -0
  313. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall_full.py +283 -0
  314. package/skills/investigating-agentforce-d360/scripts/tests/test_identity_coherence.py +327 -0
  315. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_branches.py +256 -0
  316. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_gateway_direct.py +130 -0
  317. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_helpers.py +291 -0
  318. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_integration.py +220 -0
  319. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_planner_llm_calls.py +284 -0
  320. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_show_prompts_gating.py +215 -0
  321. package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_from_disk.py +100 -0
  322. package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_session_main.py +149 -0
  323. package/skills/investigating-agentforce-d360/scripts/tests/test_runtime_override.py +104 -0
  324. package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape.py +95 -0
  325. package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape_dropped_by_stdm.py +85 -0
  326. package/skills/managing-managed-event-subscription/SKILL.md +152 -0
  327. package/skills/managing-managed-event-subscription/assets/managed-event-subscription-template.xml +20 -0
  328. package/skills/managing-managed-event-subscription/references/delete-guide.md +57 -0
  329. package/skills/managing-managed-event-subscription/references/topic-name-formats.md +26 -0
  330. package/skills/managing-managed-event-subscription/references/update-constraints.md +30 -0
  331. package/skills/reviewing-lwc-mobile-offline/SKILL.md +168 -0
  332. package/skills/reviewing-lwc-mobile-offline/references/grounding.md +7 -0
  333. package/skills/reviewing-lwc-mobile-offline/references/inline-graphql.md +43 -0
  334. package/skills/reviewing-lwc-mobile-offline/references/komaci-eslint.md +125 -0
  335. package/skills/reviewing-lwc-mobile-offline/references/lwc-if.md +78 -0
  336. package/skills/reviewing-lwc-mobile-offline/scripts/komaci.config.mjs +18 -0
  337. package/skills/reviewing-lwc-mobile-offline/scripts/package.json +10 -0
  338. package/skills/reviewing-lwc-mobile-offline/scripts/run-komaci.sh +69 -0
  339. package/skills/uplifting-components-to-slds2/SKILL.md +3 -2
  340. package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +30 -9
  341. package/skills/uplifting-components-to-slds2/references/examples.md +24 -6
  342. package/skills/using-mobile-native-capabilities/SKILL.md +182 -0
  343. package/skills/using-mobile-native-capabilities/references/app-review.md +68 -0
  344. package/skills/using-mobile-native-capabilities/references/ar-space-capture.md +125 -0
  345. package/skills/using-mobile-native-capabilities/references/barcode-scanner.md +219 -0
  346. package/skills/using-mobile-native-capabilities/references/base-capability.md +22 -0
  347. package/skills/using-mobile-native-capabilities/references/biometrics.md +90 -0
  348. package/skills/using-mobile-native-capabilities/references/calendar.md +213 -0
  349. package/skills/using-mobile-native-capabilities/references/contacts.md +232 -0
  350. package/skills/using-mobile-native-capabilities/references/document-scanner.md +342 -0
  351. package/skills/using-mobile-native-capabilities/references/geofencing.md +123 -0
  352. package/skills/using-mobile-native-capabilities/references/location.md +158 -0
  353. package/skills/using-mobile-native-capabilities/references/mobile-capabilities.md +30 -0
  354. package/skills/using-mobile-native-capabilities/references/nfc.md +181 -0
  355. package/skills/using-mobile-native-capabilities/references/payments.md +95 -0
  356. package/skills/validating-slds/SKILL.md +262 -0
  357. package/skills/validating-slds/references/quality-checks.md +308 -0
  358. package/skills/validating-slds/references/report-format.md +302 -0
  359. package/skills/validating-slds/scripts/analyze-quality.cjs +521 -0
@@ -0,0 +1,220 @@
1
+ """Tests for ``assemble_dc._assemble_gateway_direct``.
2
+
3
+ Exercises the ``interactions_not_materialized_yet`` path: session row
4
+ resolved, gateway_requests populated, interactions/steps empty. Builds a
5
+ minimal rows-dict fixture and calls the private assembler directly — the
6
+ function is pure over (sid, rows, manifest, parse_warnings), so no disk
7
+ fixtures are needed.
8
+
9
+ Scope:
10
+ - ``_source == "gateway_direct"`` sentinel
11
+ - ``session.interactions == []`` (downstream consumers no-op)
12
+ - ``session.gateway_chain`` length == fixture's gateway_requests
13
+ - each gateway_chain entry has a populated ``response.timestamp``
14
+ - finish_reason is lifted out of the HTML-escaped parameters JSON
15
+ - model / provider / prompt_template_dev_name / token counts populated
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import unittest
20
+
21
+ from . import _bootstrap # noqa: F401 — sys.path setup
22
+
23
+ from assemble_dc import _assemble_gateway_direct # type: ignore
24
+
25
+
26
+ _SID = "01912345-0000-7000-8000-000000000001"
27
+
28
+
29
+ def _gw_request(req_id: str, ts: str, model: str, tokens: tuple) -> dict:
30
+ """Shape mirrors GenAIGatewayRequest__dlm rows as fetched."""
31
+ prompt_tok, completion_tok, total_tok = tokens
32
+ return {
33
+ "gatewayRequestId__c": req_id,
34
+ "sf__Id": f"sf-{req_id}",
35
+ "sessionId__c": f'"{_SID}"',
36
+ "timestamp__c": ts,
37
+ "model__c": model,
38
+ "provider__c": "openai",
39
+ "promptTemplateDevName__c": "AiCopilot__ReactTopicPrompt",
40
+ "feature__c": "plannerservice",
41
+ "promptTokens__c": prompt_tok,
42
+ "completionTokens__c": completion_tok,
43
+ "totalTokens__c": total_tok,
44
+ "prompt__c": f"This is the prompt body for {req_id}.",
45
+ }
46
+
47
+
48
+ def _gw_response(req_id: str, resp_id: str, ts: str, finish: str) -> dict:
49
+ """Shape mirrors GenAIGatewayResponse__dlm rows as fetched.
50
+
51
+ parameters__c is HTML-escaped JSON with the finish_reason value
52
+ wrapped in escaped quotes — the exact on-wire shape the live fetcher
53
+ produces. Mirrors the fixture used elsewhere in the test suite.
54
+ """
55
+ # Build the raw JSON first, then HTML-escape the quotes the same way
56
+ # the gateway serializer does.
57
+ raw = '{"finish_reason":"\\"' + finish + '\\""}'
58
+ escaped = raw.replace('"', """)
59
+ return {
60
+ "generationResponseId__c": resp_id,
61
+ "generationRequestId__c": req_id,
62
+ "parameters__c": escaped,
63
+ "timestamp__c": ts,
64
+ }
65
+
66
+
67
+ def _tag(req_id: str, key: str, value: str) -> dict:
68
+ return {
69
+ "id__c": f"tag-{req_id}-{key}",
70
+ "parent__c": req_id,
71
+ "tag__c": key,
72
+ "tagValue__c": value,
73
+ }
74
+
75
+
76
+ def _md(req_id: str, mtype: str, payload: str) -> dict:
77
+ return {
78
+ "id__c": f"md-{req_id}",
79
+ "parent__c": req_id,
80
+ "metadataType__c": mtype,
81
+ "metadata__c": payload,
82
+ }
83
+
84
+
85
+ def _make_rows() -> dict:
86
+ session_row = {
87
+ "ssot__Id__c": _SID,
88
+ "ssot__StartTimestamp__c": "2026-05-03T10:00:00.000Z",
89
+ "ssot__EndTimestamp__c": None,
90
+ "ssot__AiAgentChannelType__c": "EmbeddedMessaging",
91
+ "ssot__AiAgentSessionEndType__c": None,
92
+ }
93
+ return {
94
+ "sessions": [session_row],
95
+ "participants": [],
96
+ "messages": [],
97
+ "moments": [],
98
+ "interactions": [],
99
+ "steps": [],
100
+ "generations": [],
101
+ "gateway_requests": [
102
+ _gw_request("gw-1", "2026-05-03T10:00:01.000Z", "gpt-4o", (100, 50, 150)),
103
+ _gw_request("gw-2", "2026-05-03T10:00:05.000Z", "gpt-4o", (200, 80, 280)),
104
+ _gw_request("gw-3", "2026-05-03T10:00:10.000Z", "gpt-4o-mini", (50, 30, 80)),
105
+ ],
106
+ "gateway_responses": [
107
+ _gw_response("gw-1", "resp-1", "2026-05-03T10:00:02.500Z", "stop"),
108
+ _gw_response("gw-2", "resp-2", "2026-05-03T10:00:06.000Z", "tool_calls"),
109
+ _gw_response("gw-3", "resp-3", "2026-05-03T10:00:11.000Z", "stop"),
110
+ ],
111
+ "gateway_request_tags": [
112
+ _tag("gw-1", "bot_id", "0Xx000000000001"),
113
+ _tag("gw-2", "feature", "plannerservice"),
114
+ ],
115
+ "gateway_request_metadata": [
116
+ _md("gw-2", "ToolCall", '{"tool":"apex"}'),
117
+ ],
118
+ "gateway_request_llm": [],
119
+ "content_quality": [],
120
+ "content_category": [],
121
+ "feedback": [],
122
+ "feedback_details": [],
123
+ "gateway_records": [],
124
+ "tag_associations": [],
125
+ "tag_definitions": [],
126
+ "tag_definition_associations": [],
127
+ "tags": [],
128
+ "app_generation": [],
129
+ "telemetry_spans": [],
130
+ "moment_interactions": [],
131
+ }
132
+
133
+
134
+ def _make_manifest() -> dict:
135
+ return {
136
+ "session_id": _SID,
137
+ "org_alias": "test-org",
138
+ "instance_url": "https://example.my.salesforce.com",
139
+ "org_id_15": "00DXX0000000001",
140
+ "agent_api_name": "My_Agent",
141
+ "agent_version": "v1",
142
+ "session_shape": "interactions_not_materialized_yet",
143
+ "queries": [],
144
+ }
145
+
146
+
147
+ class AssembleGatewayDirectTests(unittest.TestCase):
148
+
149
+ def setUp(self):
150
+ self.rows = _make_rows()
151
+ self.manifest = _make_manifest()
152
+ self.tree = _assemble_gateway_direct(
153
+ _SID, self.rows, self.manifest, parse_warnings=[])
154
+
155
+ def test_source_sentinel(self):
156
+ """The render layer keys off this exact string."""
157
+ self.assertEqual(self.tree["_source"], "gateway_direct")
158
+
159
+ def test_interactions_is_explicit_empty_list(self):
160
+ """Downstream consumers walk session.interactions — must exist + be empty."""
161
+ self.assertEqual(self.tree["session"]["interactions"], [])
162
+
163
+ def test_gateway_chain_length_matches_input(self):
164
+ self.assertEqual(len(self.tree["session"]["gateway_chain"]), 3)
165
+
166
+ def test_each_chain_entry_has_response_timestamp(self):
167
+ """Join via generationRequestId__c must populate response.timestamp on each."""
168
+ for call in self.tree["session"]["gateway_chain"]:
169
+ with self.subTest(req_id=call.get("gateway_request_id")):
170
+ self.assertIsNotNone(call.get("response"))
171
+ self.assertIsNotNone(call["response"].get("timestamp"))
172
+
173
+ def test_finish_reason_parsed(self):
174
+ """HTML-escaped, quote-wrapped finish_reason must decode to bare string."""
175
+ reasons = [c["response"]["finish_reason"]
176
+ for c in self.tree["session"]["gateway_chain"]]
177
+ self.assertEqual(reasons, ["stop", "tool_calls", "stop"])
178
+
179
+ def test_chain_sorted_by_timestamp(self):
180
+ """Deterministic ordering — required for byte-identical output across runs."""
181
+ timestamps = [c["timestamp"] for c in self.tree["session"]["gateway_chain"]]
182
+ self.assertEqual(timestamps, sorted(timestamps))
183
+
184
+ def test_model_and_tokens_harvested(self):
185
+ first = self.tree["session"]["gateway_chain"][0]
186
+ self.assertEqual(first["model"], "gpt-4o")
187
+ self.assertEqual(first["provider"], "openai")
188
+ self.assertEqual(first["prompt_template_dev_name"],
189
+ "AiCopilot__ReactTopicPrompt")
190
+ self.assertEqual(first["prompt_tokens"], 100)
191
+ self.assertEqual(first["total_tokens"], 150)
192
+
193
+ def test_prompt_text_untruncated_on_disk(self):
194
+ """Assembler stores the full prompt — the 64 KB cap is render-only."""
195
+ for call in self.tree["session"]["gateway_chain"]:
196
+ self.assertIn("This is the prompt body for",
197
+ call.get("prompt_text") or "")
198
+
199
+ def test_identity_block_present(self):
200
+ """Top-level identity must carry (org_id_15, agent_api_name, agent_version)."""
201
+ self.assertEqual(self.tree["identity"]["org_id_15"], "00DXX0000000001")
202
+ self.assertEqual(self.tree["identity"]["agent_api_name"], "My_Agent")
203
+ self.assertEqual(self.tree["identity"]["agent_version"], "v1")
204
+
205
+ def test_stdm_lag_note_on_session(self):
206
+ note = self.tree["session"].get("_stdm_lag_note") or ""
207
+ self.assertIn("materialize on a separate cadence", note)
208
+
209
+ def test_counts_reflect_input_rows(self):
210
+ counts = self.tree["session"]["counts"]
211
+ self.assertEqual(counts["gateway_requests"], 3)
212
+ self.assertEqual(counts["gateway_responses"], 3)
213
+ self.assertEqual(counts["interactions_total"], 0)
214
+ self.assertEqual(counts["steps_total"], 0)
215
+ self.assertEqual(counts["session_shape"],
216
+ "interactions_not_materialized_yet")
217
+
218
+
219
+ if __name__ == "__main__":
220
+ unittest.main()
@@ -0,0 +1,158 @@
1
+ """Integration tests for ``assemble_dc._assemble_gateway_direct``.
2
+
3
+ Drives the materialization-lag path: session row + gateway_requests
4
+ populated, but interactions / steps absent. Triggered by setting
5
+ ``manifest.session_shape = "interactions_not_materialized_yet"``.
6
+
7
+ Complements ``test_assemble_dc_gateway_direct.py`` which calls the
8
+ private function with a hand-built rows dict; this file drives the
9
+ public ``assemble`` entry point through the disk-load path so manifest
10
+ + rows + path resolution are all exercised.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import unittest
16
+ from pathlib import Path
17
+ from tempfile import TemporaryDirectory
18
+ from unittest import mock
19
+
20
+ from . import _bootstrap # noqa: F401 — sys.path setup
21
+
22
+ import assemble_dc # type: ignore
23
+ from config import paths # type: ignore
24
+ from .fixtures.synthetic_session import ( # type: ignore
25
+ IDS, write_to_disk, make_manifest,
26
+ )
27
+
28
+
29
+ def _materialization_lag_mutator(sdir: Path) -> None:
30
+ """Make the fixture look like a session whose STDM hierarchy hasn't
31
+ materialized yet: keep gateway_requests + gateway_responses + tags,
32
+ but wipe interactions / steps / messages and flip the manifest.
33
+
34
+ This is the live-DC failure mode that the gateway-direct path was
35
+ written to handle.
36
+ """
37
+ # Wipe interaction/step/message rows (file with empty list)
38
+ for name in ("interactions", "steps", "messages"):
39
+ (sdir / f"dc.{name}.json").write_text("[]")
40
+ # Update manifest session_shape so assemble's branch fires.
41
+ manifest = make_manifest()
42
+ manifest["session_shape"] = "interactions_not_materialized_yet"
43
+ (sdir / "dc._session_manifest.json").write_text(
44
+ json.dumps(manifest, indent=2)
45
+ )
46
+
47
+
48
+ class _GatewayDirectHarness:
49
+ def __enter__(self):
50
+ self._tmp = TemporaryDirectory()
51
+ self._tmpdir = Path(self._tmp.name)
52
+ sdir = write_to_disk(self._tmpdir)
53
+ _materialization_lag_mutator(sdir)
54
+ self._patch = mock.patch.object(paths, "DATA_ROOT", self._tmpdir)
55
+ self._patch.start()
56
+ self.tree, self.sdir = assemble_dc.assemble(IDS.SID)
57
+ return self
58
+
59
+ def __exit__(self, *exc):
60
+ self._patch.stop()
61
+ self._tmp.cleanup()
62
+
63
+
64
+ # -----------------------------------------------------------------------------
65
+ # Top-level shape on the gateway-direct branch
66
+ # -----------------------------------------------------------------------------
67
+
68
+
69
+ class GatewayDirectShapeTests(unittest.TestCase):
70
+
71
+ def test_source_marker_set_to_gateway_direct(self):
72
+ with _GatewayDirectHarness() as h:
73
+ self.assertEqual(h.tree["_source"], "gateway_direct")
74
+
75
+ def test_interactions_is_empty_list(self):
76
+ with _GatewayDirectHarness() as h:
77
+ self.assertEqual(h.tree["session"]["interactions"], [])
78
+
79
+ def test_gateway_chain_populated_from_gateway_requests(self):
80
+ with _GatewayDirectHarness() as h:
81
+ chain = h.tree["session"]["gateway_chain"]
82
+ # Fixture has 2 gateway_requests; both make it into the chain.
83
+ self.assertEqual(len(chain), 2)
84
+ chain_ids = {gw["gateway_request_id"] for gw in chain}
85
+ self.assertEqual(chain_ids, {IDS.GW_REQ_DECLARED, IDS.GW_REQ_WINDOW})
86
+
87
+ def test_chain_sorted_by_timestamp(self):
88
+ # Earlier gateway request timestamp comes first.
89
+ with _GatewayDirectHarness() as h:
90
+ chain = h.tree["session"]["gateway_chain"]
91
+ timestamps = [gw.get("timestamp") for gw in chain]
92
+ self.assertEqual(timestamps, sorted(t or "" for t in timestamps))
93
+
94
+ def test_each_gateway_chain_entry_has_response(self):
95
+ # Only the declared request has a response in the fixture; the
96
+ # window-bound one has response=None. Both shapes are accepted.
97
+ with _GatewayDirectHarness() as h:
98
+ chain = h.tree["session"]["gateway_chain"]
99
+ declared = next(gw for gw in chain
100
+ if gw["gateway_request_id"] == IDS.GW_REQ_DECLARED)
101
+ self.assertIsNotNone(declared["response"])
102
+
103
+ def test_top_identity_block_resolved_from_manifest(self):
104
+ with _GatewayDirectHarness() as h:
105
+ ident = h.tree["identity"]
106
+ self.assertEqual(ident["org_id_15"], IDS.ORG_ID_15)
107
+ self.assertEqual(ident["agent_api_name"], IDS.AGENT_API)
108
+ self.assertEqual(ident["agent_version"], IDS.AGENT_VERSION)
109
+
110
+ def test_counts_block_records_session_shape_and_zero_interactions(self):
111
+ with _GatewayDirectHarness() as h:
112
+ counts = h.tree["session"]["counts"]
113
+ self.assertEqual(counts["session_shape"],
114
+ "interactions_not_materialized_yet")
115
+ self.assertEqual(counts["interactions_total"], 0)
116
+ self.assertEqual(counts["steps_total"], 0)
117
+ self.assertEqual(counts["gateway_requests"], 2)
118
+
119
+
120
+ # -----------------------------------------------------------------------------
121
+ # render_dc on the gateway-direct tree
122
+ # -----------------------------------------------------------------------------
123
+
124
+
125
+ class GatewayDirectRenderTests(unittest.TestCase):
126
+ """The gateway-direct render branch is short — covers identity,
127
+ lag banner, and gateway-chain table. Drives it end-to-end."""
128
+
129
+ def test_render_emits_lag_banner(self):
130
+ import render_dc # noqa: WPS433
131
+ with _GatewayDirectHarness() as h:
132
+ md = render_dc.render(h.tree, manifest=None, session_dir=h.sdir)
133
+ # _STDM_LAG_NOTE is referenced in the gateway-direct render path
134
+ # — must surface as visible text. We assert on the substring
135
+ # "STDM" since the exact phrasing may evolve.
136
+ self.assertIn("STDM", md)
137
+
138
+ def test_render_lists_both_gateway_requests(self):
139
+ import render_dc # noqa: WPS433
140
+ with _GatewayDirectHarness() as h:
141
+ md = render_dc.render(h.tree, manifest=None, session_dir=h.sdir)
142
+ # Gateway-direct render truncates IDs to 8-char prefixes — assert
143
+ # on the prefix substring + on the prompt-template name (verbatim
144
+ # in the per-call detail block).
145
+ self.assertIn(IDS.GW_REQ_DECLARED[:8], md)
146
+ self.assertIn(IDS.GW_REQ_WINDOW[:8], md)
147
+ self.assertIn("AiCopilot__ReactInitialPrompt", md)
148
+ self.assertIn("AiCopilot__PromptTemplateGenerationsInvocable", md)
149
+
150
+ def test_render_includes_session_id(self):
151
+ import render_dc # noqa: WPS433
152
+ with _GatewayDirectHarness() as h:
153
+ md = render_dc.render(h.tree, manifest=None, session_dir=h.sdir)
154
+ self.assertIn(IDS.SID, md)
155
+
156
+
157
+ if __name__ == "__main__":
158
+ unittest.main()
@@ -0,0 +1,287 @@
1
+ """Tests for ``assemble_dc`` pure helpers — independent of disk fixtures.
2
+
3
+ Complements ``test_assemble_dc_gateway_direct.py`` (binding chain) by
4
+ hitting the small, mechanical helpers that bridge raw DC rows and the
5
+ assembled tree:
6
+
7
+ - ``_clean`` NOT_SET sentinel collapse
8
+ - ``_harvest_str`` unescape + quote-strip + UNSET_VALUE
9
+ - ``_ts`` ISO-8601 → datetime; NOT_SET / non-string → None
10
+ - ``_index_unique`` PK dedup with first-write-wins + collision record
11
+ - ``_groupby`` group rows by FK, dropping NOT_SET keys
12
+ - ``_extract_trace_id`` primary col with HTML-escaped fallback
13
+ - ``_tier`` ACTION < TOPIC < GUARDRAIL < other
14
+ - ``_window_contains`` half-open / closed timestamp interval semantics
15
+ - ``_load`` disk read with malformed-json tolerance
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import unittest
21
+ from datetime import datetime, timezone
22
+ from pathlib import Path
23
+ from tempfile import TemporaryDirectory
24
+
25
+ from . import _bootstrap # noqa: F401 — sys.path setup
26
+
27
+ import assemble_dc # type: ignore
28
+
29
+
30
+ # -----------------------------------------------------------------------------
31
+ # _clean — NOT_SET → None
32
+ # -----------------------------------------------------------------------------
33
+
34
+
35
+ class CleanTests(unittest.TestCase):
36
+
37
+ def test_not_set_token_returns_none(self):
38
+ self.assertIsNone(assemble_dc._clean("NOT_SET"))
39
+
40
+ def test_empty_string_returns_none(self):
41
+ self.assertIsNone(assemble_dc._clean(""))
42
+
43
+ def test_actual_value_passes_through(self):
44
+ self.assertEqual(assemble_dc._clean("real value"), "real value")
45
+ self.assertEqual(assemble_dc._clean(42), 42)
46
+
47
+
48
+ # -----------------------------------------------------------------------------
49
+ # _harvest_str — unescape + quote-strip + UNSET_VALUE collapse
50
+ # -----------------------------------------------------------------------------
51
+
52
+
53
+ class HarvestStrTests(unittest.TestCase):
54
+
55
+ def test_none_returns_none(self):
56
+ self.assertIsNone(assemble_dc._harvest_str(None))
57
+
58
+ def test_unescapes_html_entities(self):
59
+ self.assertEqual(
60
+ assemble_dc._harvest_str("&quot;0Xx000&quot;"),
61
+ "0Xx000",
62
+ )
63
+
64
+ def test_strips_outer_double_quotes(self):
65
+ self.assertEqual(assemble_dc._harvest_str('"value"'), "value")
66
+
67
+ def test_unset_value_collapses_to_none(self):
68
+ self.assertIsNone(assemble_dc._harvest_str("UNSET_VALUE"))
69
+
70
+ def test_not_set_collapses_to_none(self):
71
+ self.assertIsNone(assemble_dc._harvest_str("NOT_SET"))
72
+
73
+ def test_empty_string_collapses_to_none(self):
74
+ self.assertIsNone(assemble_dc._harvest_str(""))
75
+
76
+
77
+ # -----------------------------------------------------------------------------
78
+ # _ts — ISO-8601 timestamp parsing
79
+ # -----------------------------------------------------------------------------
80
+
81
+
82
+ class TsTests(unittest.TestCase):
83
+
84
+ def test_parses_iso_z_timestamp(self):
85
+ out = assemble_dc._ts("2026-04-22T10:00:00Z")
86
+ self.assertEqual(out, datetime(2026, 4, 22, 10, 0, 0, tzinfo=timezone.utc))
87
+
88
+ def test_parses_iso_offset_timestamp(self):
89
+ out = assemble_dc._ts("2026-04-22T10:00:00+00:00")
90
+ self.assertEqual(out, datetime(2026, 4, 22, 10, 0, 0, tzinfo=timezone.utc))
91
+
92
+ def test_returns_none_for_non_string(self):
93
+ self.assertIsNone(assemble_dc._ts(None))
94
+ self.assertIsNone(assemble_dc._ts(12345))
95
+
96
+ def test_returns_none_for_not_set(self):
97
+ self.assertIsNone(assemble_dc._ts("NOT_SET"))
98
+ self.assertIsNone(assemble_dc._ts(""))
99
+
100
+ def test_returns_none_for_unparseable(self):
101
+ self.assertIsNone(assemble_dc._ts("not-a-timestamp"))
102
+
103
+
104
+ # -----------------------------------------------------------------------------
105
+ # _index_unique
106
+ # -----------------------------------------------------------------------------
107
+
108
+
109
+ class IndexUniqueTests(unittest.TestCase):
110
+
111
+ def test_builds_dict_keyed_by_field(self):
112
+ rows = [{"id": "a", "v": 1}, {"id": "b", "v": 2}]
113
+ out = assemble_dc._index_unique(rows, "id", [], dmo_label="t")
114
+ self.assertEqual(set(out.keys()), {"a", "b"})
115
+
116
+ def test_first_write_wins_on_collision(self):
117
+ rows = [{"id": "a", "v": 1}, {"id": "a", "v": 2}]
118
+ collisions: list[dict] = []
119
+ out = assemble_dc._index_unique(rows, "id", collisions, dmo_label="dmo1")
120
+ self.assertEqual(out["a"]["v"], 1)
121
+ self.assertEqual(len(collisions), 1)
122
+ self.assertEqual(collisions[0]["dmo"], "dmo1")
123
+ self.assertEqual(collisions[0]["key"], "a")
124
+
125
+ def test_skips_NOT_SET_keys(self):
126
+ rows = [{"id": "NOT_SET", "v": 1}, {"id": "", "v": 2}, {"id": "a", "v": 3}]
127
+ out = assemble_dc._index_unique(rows, "id", [], dmo_label="t")
128
+ self.assertEqual(set(out.keys()), {"a"})
129
+
130
+
131
+ # -----------------------------------------------------------------------------
132
+ # _groupby
133
+ # -----------------------------------------------------------------------------
134
+
135
+
136
+ class GroupbyTests(unittest.TestCase):
137
+
138
+ def test_groups_rows_by_key(self):
139
+ rows = [{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "a", "v": 3}]
140
+ out = assemble_dc._groupby(rows, "k")
141
+ self.assertEqual([r["v"] for r in out["a"]], [1, 3])
142
+ self.assertEqual([r["v"] for r in out["b"]], [2])
143
+
144
+ def test_skips_NOT_SET_keys(self):
145
+ rows = [{"k": "a", "v": 1}, {"k": "NOT_SET", "v": 2}]
146
+ out = assemble_dc._groupby(rows, "k")
147
+ self.assertNotIn("NOT_SET", out)
148
+ self.assertIn("a", out)
149
+
150
+
151
+ # -----------------------------------------------------------------------------
152
+ # _extract_trace_id
153
+ # -----------------------------------------------------------------------------
154
+
155
+
156
+ class ExtractTraceIdTests(unittest.TestCase):
157
+
158
+ def test_uses_primary_column_when_populated(self):
159
+ out = assemble_dc._extract_trace_id({"ssot__TelemetryTraceId__c": "abc"})
160
+ self.assertEqual(out, "abc")
161
+
162
+ def test_falls_back_to_html_escaped_attribute_text(self):
163
+ out = assemble_dc._extract_trace_id({
164
+ "ssot__TelemetryTraceId__c": "",
165
+ "ssot__AttributeText__c": '&quot;internalTraceId&quot;:&quot;deadbeef&quot;',
166
+ })
167
+ self.assertEqual(out, "deadbeef")
168
+
169
+ def test_returns_none_when_no_trace_id_anywhere(self):
170
+ out = assemble_dc._extract_trace_id({
171
+ "ssot__TelemetryTraceId__c": "",
172
+ "ssot__AttributeText__c": "no trace here",
173
+ })
174
+ self.assertIsNone(out)
175
+
176
+ def test_returns_none_for_NOT_SET(self):
177
+ out = assemble_dc._extract_trace_id({
178
+ "ssot__TelemetryTraceId__c": "NOT_SET",
179
+ "ssot__AttributeText__c": "",
180
+ })
181
+ self.assertIsNone(out)
182
+
183
+
184
+ # -----------------------------------------------------------------------------
185
+ # _tier — ACTION < TOPIC < GUARDRAIL < other
186
+ # -----------------------------------------------------------------------------
187
+
188
+
189
+ class TierTests(unittest.TestCase):
190
+
191
+ def test_action_step_is_lowest_tier(self):
192
+ self.assertLess(
193
+ assemble_dc._tier("ACTION_STEP"),
194
+ assemble_dc._tier("TOPIC_STEP"),
195
+ )
196
+
197
+ def test_topic_step_outranks_guardrails(self):
198
+ self.assertLess(
199
+ assemble_dc._tier("TOPIC_STEP"),
200
+ assemble_dc._tier("TRUST_GUARDRAILS_STEP"),
201
+ )
202
+
203
+ def test_unknown_step_type_falls_to_end(self):
204
+ self.assertEqual(
205
+ assemble_dc._tier("MYSTERY"),
206
+ len(assemble_dc._TIER_ORDER),
207
+ )
208
+
209
+
210
+ # -----------------------------------------------------------------------------
211
+ # _window_contains
212
+ # -----------------------------------------------------------------------------
213
+
214
+
215
+ class WindowContainsTests(unittest.TestCase):
216
+
217
+ def _ts(self, h, m, s):
218
+ return datetime(2026, 4, 22, h, m, s, tzinfo=timezone.utc)
219
+
220
+ def test_in_range_inclusive_of_start_and_end(self):
221
+ gw = self._ts(10, 0, 5)
222
+ self.assertTrue(
223
+ assemble_dc._window_contains(gw, self._ts(10, 0, 0), self._ts(10, 0, 10))
224
+ )
225
+
226
+ def test_open_ended_when_end_is_none(self):
227
+ gw = self._ts(11, 0, 0)
228
+ self.assertTrue(
229
+ assemble_dc._window_contains(gw, self._ts(10, 0, 0), None)
230
+ )
231
+
232
+ def test_returns_false_when_start_is_none(self):
233
+ gw = self._ts(10, 0, 0)
234
+ self.assertFalse(
235
+ assemble_dc._window_contains(gw, None, self._ts(11, 0, 0))
236
+ )
237
+
238
+ def test_returns_false_when_before_start(self):
239
+ gw = self._ts(9, 0, 0)
240
+ self.assertFalse(
241
+ assemble_dc._window_contains(gw, self._ts(10, 0, 0), self._ts(11, 0, 0))
242
+ )
243
+
244
+ def test_returns_false_when_after_end(self):
245
+ gw = self._ts(12, 0, 0)
246
+ self.assertFalse(
247
+ assemble_dc._window_contains(gw, self._ts(10, 0, 0), self._ts(11, 0, 0))
248
+ )
249
+
250
+
251
+ # -----------------------------------------------------------------------------
252
+ # _load — disk read with tolerance
253
+ # -----------------------------------------------------------------------------
254
+
255
+
256
+ class LoadTests(unittest.TestCase):
257
+
258
+ def test_returns_empty_list_when_file_missing(self):
259
+ with TemporaryDirectory() as t:
260
+ warnings: list[str] = []
261
+ self.assertEqual(
262
+ assemble_dc._load(Path(t), "missing", warnings), []
263
+ )
264
+ # Missing file is normal — no warning recorded.
265
+ self.assertEqual(warnings, [])
266
+
267
+ def test_returns_loaded_rows_when_file_valid(self):
268
+ with TemporaryDirectory() as t:
269
+ tmp = Path(t)
270
+ (tmp / "dc.sessions.json").write_text(json.dumps([{"a": 1}, {"a": 2}]))
271
+ warnings: list[str] = []
272
+ out = assemble_dc._load(tmp, "sessions", warnings)
273
+ self.assertEqual(out, [{"a": 1}, {"a": 2}])
274
+ self.assertEqual(warnings, [])
275
+
276
+ def test_returns_empty_list_and_records_warning_on_malformed_json(self):
277
+ with TemporaryDirectory() as t:
278
+ tmp = Path(t)
279
+ (tmp / "dc.broken.json").write_text("<<<not json>>>")
280
+ warnings: list[str] = []
281
+ out = assemble_dc._load(tmp, "broken", warnings)
282
+ self.assertEqual(out, [])
283
+ self.assertEqual(warnings, ["broken"])
284
+
285
+
286
+ if __name__ == "__main__":
287
+ unittest.main()