@salesforce/afv-skills 1.14.0 → 1.16.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 (365) hide show
  1. package/package.json +1 -1
  2. package/skills/activating-datacloud/SKILL.md +0 -1
  3. package/skills/analyzing-omnistudio-dependencies/SKILL.md +0 -1
  4. package/skills/applying-slds/SKILL.md +322 -0
  5. package/skills/applying-slds/checklists.md +83 -0
  6. package/skills/applying-slds/examples.md +283 -0
  7. package/skills/applying-slds/guidance/README.md +83 -0
  8. package/skills/applying-slds/guidance/blueprints-index.md +213 -0
  9. package/skills/applying-slds/guidance/icons-guidance.md +186 -0
  10. package/skills/applying-slds/guidance/overviews/borders.md +236 -0
  11. package/skills/applying-slds/guidance/overviews/color.md +266 -0
  12. package/skills/applying-slds/guidance/overviews/display-density.md +366 -0
  13. package/skills/applying-slds/guidance/overviews/icons.md +240 -0
  14. package/skills/applying-slds/guidance/overviews/illustrations.md +235 -0
  15. package/skills/applying-slds/guidance/overviews/shadows.md +176 -0
  16. package/skills/applying-slds/guidance/overviews/spacing.md +216 -0
  17. package/skills/applying-slds/guidance/overviews/typography.md +323 -0
  18. package/skills/applying-slds/guidance/overviews/utilities.md +542 -0
  19. package/skills/applying-slds/guidance/slds-development-guide.md +288 -0
  20. package/skills/applying-slds/guidance/styling-hooks/borders.md +202 -0
  21. package/skills/applying-slds/guidance/styling-hooks/color/expressive-palette-hooks.md +153 -0
  22. package/skills/applying-slds/guidance/styling-hooks/color/index.md +171 -0
  23. package/skills/applying-slds/guidance/styling-hooks/color/semantic/accent-hooks.md +204 -0
  24. package/skills/applying-slds/guidance/styling-hooks/color/semantic/feedback-hooks.md +768 -0
  25. package/skills/applying-slds/guidance/styling-hooks/color/semantic/surface-hooks.md +337 -0
  26. package/skills/applying-slds/guidance/styling-hooks/color/system-hooks.md +132 -0
  27. package/skills/applying-slds/guidance/styling-hooks/index.md +327 -0
  28. package/skills/applying-slds/guidance/styling-hooks/shadows.md +238 -0
  29. package/skills/applying-slds/guidance/styling-hooks/spacing.md +254 -0
  30. package/skills/applying-slds/guidance/styling-hooks/typography.md +448 -0
  31. package/skills/applying-slds/guidance/utilities/alignment.md +119 -0
  32. package/skills/applying-slds/guidance/utilities/borders.md +131 -0
  33. package/skills/applying-slds/guidance/utilities/box.md +125 -0
  34. package/skills/applying-slds/guidance/utilities/color.md +165 -0
  35. package/skills/applying-slds/guidance/utilities/dark-mode.md +111 -0
  36. package/skills/applying-slds/guidance/utilities/description-list.md +168 -0
  37. package/skills/applying-slds/guidance/utilities/floats.md +117 -0
  38. package/skills/applying-slds/guidance/utilities/grid.md +264 -0
  39. package/skills/applying-slds/guidance/utilities/horizontal-list.md +110 -0
  40. package/skills/applying-slds/guidance/utilities/hyphenation.md +84 -0
  41. package/skills/applying-slds/guidance/utilities/index.md +205 -0
  42. package/skills/applying-slds/guidance/utilities/interactions.md +89 -0
  43. package/skills/applying-slds/guidance/utilities/layout.md +109 -0
  44. package/skills/applying-slds/guidance/utilities/line-clamp.md +131 -0
  45. package/skills/applying-slds/guidance/utilities/margin.md +155 -0
  46. package/skills/applying-slds/guidance/utilities/media-object.md +161 -0
  47. package/skills/applying-slds/guidance/utilities/name-value-list.md +152 -0
  48. package/skills/applying-slds/guidance/utilities/padding.md +155 -0
  49. package/skills/applying-slds/guidance/utilities/position.md +177 -0
  50. package/skills/applying-slds/guidance/utilities/print.md +114 -0
  51. package/skills/applying-slds/guidance/utilities/scrollable.md +126 -0
  52. package/skills/applying-slds/guidance/utilities/sizing.md +190 -0
  53. package/skills/applying-slds/guidance/utilities/themes.md +121 -0
  54. package/skills/applying-slds/guidance/utilities/truncate.md +127 -0
  55. package/skills/applying-slds/guidance/utilities/typography.md +166 -0
  56. package/skills/applying-slds/guidance/utilities/vertical-list.md +166 -0
  57. package/skills/applying-slds/guidance/utilities/visibility.md +228 -0
  58. package/skills/applying-slds/metadata/README.md +84 -0
  59. package/skills/applying-slds/metadata/blueprints/components/accordion.yaml +304 -0
  60. package/skills/applying-slds/metadata/blueprints/components/activity-timeline.yaml +92 -0
  61. package/skills/applying-slds/metadata/blueprints/components/alert.yaml +103 -0
  62. package/skills/applying-slds/metadata/blueprints/components/app-launcher.yaml +94 -0
  63. package/skills/applying-slds/metadata/blueprints/components/avatar-group.yaml +81 -0
  64. package/skills/applying-slds/metadata/blueprints/components/avatar.yaml +97 -0
  65. package/skills/applying-slds/metadata/blueprints/components/badges.yaml +102 -0
  66. package/skills/applying-slds/metadata/blueprints/components/brand-band.yaml +198 -0
  67. package/skills/applying-slds/metadata/blueprints/components/breadcrumbs.yaml +95 -0
  68. package/skills/applying-slds/metadata/blueprints/components/builder-header.yaml +192 -0
  69. package/skills/applying-slds/metadata/blueprints/components/button-groups.yaml +82 -0
  70. package/skills/applying-slds/metadata/blueprints/components/button-icons.yaml +295 -0
  71. package/skills/applying-slds/metadata/blueprints/components/buttons.yaml +230 -0
  72. package/skills/applying-slds/metadata/blueprints/components/cards.yaml +124 -0
  73. package/skills/applying-slds/metadata/blueprints/components/carousel.yaml +140 -0
  74. package/skills/applying-slds/metadata/blueprints/components/chat.yaml +179 -0
  75. package/skills/applying-slds/metadata/blueprints/components/checkbox-button-group.yaml +192 -0
  76. package/skills/applying-slds/metadata/blueprints/components/checkbox-button.yaml +204 -0
  77. package/skills/applying-slds/metadata/blueprints/components/checkbox-toggle.yaml +177 -0
  78. package/skills/applying-slds/metadata/blueprints/components/checkbox.yaml +108 -0
  79. package/skills/applying-slds/metadata/blueprints/components/color-picker.yaml +172 -0
  80. package/skills/applying-slds/metadata/blueprints/components/combobox.yaml +136 -0
  81. package/skills/applying-slds/metadata/blueprints/components/counter.yaml +147 -0
  82. package/skills/applying-slds/metadata/blueprints/components/data-tables.yaml +157 -0
  83. package/skills/applying-slds/metadata/blueprints/components/datepickers.yaml +130 -0
  84. package/skills/applying-slds/metadata/blueprints/components/datetime-picker.yaml +155 -0
  85. package/skills/applying-slds/metadata/blueprints/components/docked-composer.yaml +201 -0
  86. package/skills/applying-slds/metadata/blueprints/components/docked-form-footer.yaml +161 -0
  87. package/skills/applying-slds/metadata/blueprints/components/docked-utility-bar.yaml +175 -0
  88. package/skills/applying-slds/metadata/blueprints/components/drop-zone.yaml +115 -0
  89. package/skills/applying-slds/metadata/blueprints/components/dueling-picklist.yaml +196 -0
  90. package/skills/applying-slds/metadata/blueprints/components/dynamic-icons.yaml +128 -0
  91. package/skills/applying-slds/metadata/blueprints/components/dynamic-menu.yaml +141 -0
  92. package/skills/applying-slds/metadata/blueprints/components/expandable-section.yaml +115 -0
  93. package/skills/applying-slds/metadata/blueprints/components/expression.yaml +143 -0
  94. package/skills/applying-slds/metadata/blueprints/components/feeds.yaml +125 -0
  95. package/skills/applying-slds/metadata/blueprints/components/file-selector.yaml +154 -0
  96. package/skills/applying-slds/metadata/blueprints/components/files.yaml +119 -0
  97. package/skills/applying-slds/metadata/blueprints/components/form-element.yaml +145 -0
  98. package/skills/applying-slds/metadata/blueprints/components/global-header.yaml +120 -0
  99. package/skills/applying-slds/metadata/blueprints/components/global-navigation.yaml +100 -0
  100. package/skills/applying-slds/metadata/blueprints/components/icons.yaml +138 -0
  101. package/skills/applying-slds/metadata/blueprints/components/illustration.yaml +205 -0
  102. package/skills/applying-slds/metadata/blueprints/components/input.yaml +151 -0
  103. package/skills/applying-slds/metadata/blueprints/components/list-builder.yaml +127 -0
  104. package/skills/applying-slds/metadata/blueprints/components/lookups.yaml +132 -0
  105. package/skills/applying-slds/metadata/blueprints/components/map.yaml +118 -0
  106. package/skills/applying-slds/metadata/blueprints/components/menus.yaml +134 -0
  107. package/skills/applying-slds/metadata/blueprints/components/modals.yaml +152 -0
  108. package/skills/applying-slds/metadata/blueprints/components/notifications.yaml +88 -0
  109. package/skills/applying-slds/metadata/blueprints/components/page-headers.yaml +135 -0
  110. package/skills/applying-slds/metadata/blueprints/components/panels.yaml +149 -0
  111. package/skills/applying-slds/metadata/blueprints/components/path.yaml +154 -0
  112. package/skills/applying-slds/metadata/blueprints/components/picklist.yaml +125 -0
  113. package/skills/applying-slds/metadata/blueprints/components/pills.yaml +154 -0
  114. package/skills/applying-slds/metadata/blueprints/components/popovers.yaml +120 -0
  115. package/skills/applying-slds/metadata/blueprints/components/progress-bar.yaml +110 -0
  116. package/skills/applying-slds/metadata/blueprints/components/progress-indicator.yaml +133 -0
  117. package/skills/applying-slds/metadata/blueprints/components/progress-ring.yaml +102 -0
  118. package/skills/applying-slds/metadata/blueprints/components/prompt.yaml +126 -0
  119. package/skills/applying-slds/metadata/blueprints/components/publishers.yaml +178 -0
  120. package/skills/applying-slds/metadata/blueprints/components/radio-button-group.yaml +172 -0
  121. package/skills/applying-slds/metadata/blueprints/components/radio-group.yaml +112 -0
  122. package/skills/applying-slds/metadata/blueprints/components/rich-text-editor.yaml +135 -0
  123. package/skills/applying-slds/metadata/blueprints/components/scoped-notifications.yaml +188 -0
  124. package/skills/applying-slds/metadata/blueprints/components/scoped-tabs.yaml +97 -0
  125. package/skills/applying-slds/metadata/blueprints/components/select.yaml +127 -0
  126. package/skills/applying-slds/metadata/blueprints/components/setup-assistant.yaml +152 -0
  127. package/skills/applying-slds/metadata/blueprints/components/slider.yaml +111 -0
  128. package/skills/applying-slds/metadata/blueprints/components/spinners.yaml +135 -0
  129. package/skills/applying-slds/metadata/blueprints/components/split-view.yaml +112 -0
  130. package/skills/applying-slds/metadata/blueprints/components/summary-detail.yaml +103 -0
  131. package/skills/applying-slds/metadata/blueprints/components/tabs.yaml +138 -0
  132. package/skills/applying-slds/metadata/blueprints/components/textarea.yaml +116 -0
  133. package/skills/applying-slds/metadata/blueprints/components/tiles.yaml +108 -0
  134. package/skills/applying-slds/metadata/blueprints/components/timepicker.yaml +111 -0
  135. package/skills/applying-slds/metadata/blueprints/components/toast.yaml +154 -0
  136. package/skills/applying-slds/metadata/blueprints/components/tooltips.yaml +107 -0
  137. package/skills/applying-slds/metadata/blueprints/components/tree-grid.yaml +116 -0
  138. package/skills/applying-slds/metadata/blueprints/components/trees.yaml +116 -0
  139. package/skills/applying-slds/metadata/blueprints/components/trial-bar.yaml +112 -0
  140. package/skills/applying-slds/metadata/blueprints/components/vertical-navigation.yaml +130 -0
  141. package/skills/applying-slds/metadata/blueprints/components/vertical-tabs.yaml +140 -0
  142. package/skills/applying-slds/metadata/blueprints/components/visual-picker.yaml +150 -0
  143. package/skills/applying-slds/metadata/blueprints/components/welcome-mat.yaml +136 -0
  144. package/skills/applying-slds/metadata/hooks-index.json +6272 -0
  145. package/skills/applying-slds/metadata/icon-metadata.json +38466 -0
  146. package/skills/applying-slds/metadata/utilities-index.json +21912 -0
  147. package/skills/applying-slds/references/component-selection.md +112 -0
  148. package/skills/applying-slds/references/icons-decision-guide.md +124 -0
  149. package/skills/applying-slds/references/styling-decision-guide.md +228 -0
  150. package/skills/applying-slds/references/utilities-quick-ref.md +125 -0
  151. package/skills/applying-slds/scripts/search-blueprints.cjs +117 -0
  152. package/skills/applying-slds/scripts/search-hooks.cjs +139 -0
  153. package/skills/applying-slds/scripts/search-icons.cjs +174 -0
  154. package/skills/applying-slds/scripts/search-utilities.cjs +161 -0
  155. package/skills/building-mobile-apps/SKILL.md +0 -1
  156. package/skills/building-omnistudio-callable-apex/SKILL.md +0 -1
  157. package/skills/building-omnistudio-datamapper/SKILL.md +0 -1
  158. package/skills/building-omnistudio-flexcard/SKILL.md +0 -1
  159. package/skills/building-omnistudio-integration-procedure/SKILL.md +0 -1
  160. package/skills/building-omnistudio-omniscript/SKILL.md +0 -1
  161. package/skills/building-sf-integrations/SKILL.md +0 -1
  162. package/skills/configuring-connected-apps/SKILL.md +0 -1
  163. package/skills/connecting-datacloud/SKILL.md +0 -1
  164. package/skills/creating-b2b-commerce-store/SKILL.md +0 -1
  165. package/skills/debugging-apex-logs/SKILL.md +0 -1
  166. package/skills/deploying-metadata/SKILL.md +0 -1
  167. package/skills/deploying-omnistudio-datapacks/SKILL.md +0 -1
  168. package/skills/developing-agentforce/SKILL.md +0 -1
  169. package/skills/fetching-salesforce-docs/SKILL.md +0 -1
  170. package/skills/generating-custom-lightning-type/SKILL.md +17 -39
  171. package/skills/generating-custom-lightning-type/assets/primitive-types-and-constraints.md +41 -0
  172. package/skills/generating-custom-lightning-type/references/widget-rendition.md +124 -0
  173. package/skills/generating-lwc-components/SKILL.md +0 -1
  174. package/skills/generating-mermaid-diagrams/SKILL.md +0 -1
  175. package/skills/generating-visual-diagrams/SKILL.md +0 -1
  176. package/skills/handling-sf-data/SKILL.md +0 -1
  177. package/skills/harmonizing-datacloud/SKILL.md +0 -1
  178. package/skills/integrating-b2b-commerce-open-code-components/SKILL.md +0 -1
  179. package/skills/investigating-agentforce-architecture/README.md +156 -0
  180. package/skills/investigating-agentforce-architecture/SKILL.md +230 -0
  181. package/skills/investigating-agentforce-architecture/assets/cli/describe_sobject.yaml +16 -0
  182. package/skills/investigating-agentforce-architecture/assets/cli/describe_tooling_sobject.yaml +17 -0
  183. package/skills/investigating-agentforce-architecture/assets/cli/list_metadata_genaiprompttemplate.yaml +17 -0
  184. package/skills/investigating-agentforce-architecture/assets/cli/org_display.yaml +15 -0
  185. package/skills/investigating-agentforce-architecture/assets/cli/retrieve_genai_plugin.yaml +18 -0
  186. package/skills/investigating-agentforce-architecture/assets/cli/show_access_token.yaml +27 -0
  187. package/skills/investigating-agentforce-architecture/assets/mermaid/action_tree.mmd +20 -0
  188. package/skills/investigating-agentforce-architecture/assets/mermaid/data_flow.mmd +19 -0
  189. package/skills/investigating-agentforce-architecture/assets/mermaid/dependency_graph.mmd +19 -0
  190. package/skills/investigating-agentforce-architecture/assets/mermaid/invocation_sequence.mmd +20 -0
  191. package/skills/investigating-agentforce-architecture/assets/mermaid/planner_state.mmd +18 -0
  192. package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_ids.soql +3 -0
  193. package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_names.soql +3 -0
  194. package/skills/investigating-agentforce-architecture/assets/soql/bot_definition_details.soql +3 -0
  195. package/skills/investigating-agentforce-architecture/assets/soql/bot_version_lookup.soql +4 -0
  196. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_by_ids.soql +3 -0
  197. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_ids_by_names.soql +3 -0
  198. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_view_by_durable_ids.soql +4 -0
  199. package/skills/investigating-agentforce-architecture/assets/soql/flow_metadata_by_id.soql +3 -0
  200. package/skills/investigating-agentforce-architecture/assets/soql/functions_by_plugins.soql +5 -0
  201. package/skills/investigating-agentforce-architecture/assets/soql/planner_attrs_by_parent_ids.soql +3 -0
  202. package/skills/investigating-agentforce-architecture/assets/soql/planner_bundle_functions.soql +3 -0
  203. package/skills/investigating-agentforce-architecture/assets/soql/planner_definition_by_agent_chain.soql +3 -0
  204. package/skills/investigating-agentforce-architecture/assets/soql/plugin_functions_by_plugin_ids.soql +3 -0
  205. package/skills/investigating-agentforce-architecture/assets/soql/plugin_instructions_by_plugin_ids.soql +3 -0
  206. package/skills/investigating-agentforce-architecture/assets/soql/plugins_by_planner.soql +4 -0
  207. package/skills/investigating-agentforce-architecture/references/architecture_sections.md +243 -0
  208. package/skills/investigating-agentforce-architecture/references/contract.json +244 -0
  209. package/skills/investigating-agentforce-architecture/references/soql_fields.md +512 -0
  210. package/skills/investigating-agentforce-architecture/scripts/_shared/__init__.py +1 -0
  211. package/skills/investigating-agentforce-architecture/scripts/_shared/fs_guard.py +329 -0
  212. package/skills/investigating-agentforce-architecture/scripts/_shared/paths.py +110 -0
  213. package/skills/investigating-agentforce-architecture/scripts/_shared/runtime.py +59 -0
  214. package/skills/investigating-agentforce-architecture/scripts/_shared/sql.py +10 -0
  215. package/skills/investigating-agentforce-architecture/scripts/cache_check.py +234 -0
  216. package/skills/investigating-agentforce-architecture/scripts/config.py +131 -0
  217. package/skills/investigating-agentforce-architecture/scripts/fetch_soql.py +689 -0
  218. package/skills/investigating-agentforce-architecture/scripts/finalize.py +295 -0
  219. package/skills/investigating-agentforce-architecture/scripts/main.py +2835 -0
  220. package/skills/investigating-agentforce-architecture/scripts/metadata_listing.py +265 -0
  221. package/skills/investigating-agentforce-architecture/scripts/parallel_retrieve.py +69 -0
  222. package/skills/investigating-agentforce-architecture/scripts/parse_bundle.py +215 -0
  223. package/skills/investigating-agentforce-architecture/scripts/parse_wave.py +845 -0
  224. package/skills/investigating-agentforce-architecture/scripts/probe_channels.py +302 -0
  225. package/skills/investigating-agentforce-architecture/scripts/render_architecture.py +1043 -0
  226. package/skills/investigating-agentforce-architecture/scripts/resolve_bot.py +255 -0
  227. package/skills/investigating-agentforce-architecture/scripts/resolve_invocation_target.py +130 -0
  228. package/skills/investigating-agentforce-architecture/scripts/rest_client.py +763 -0
  229. package/skills/investigating-agentforce-architecture/scripts/retrieve_planner.py +13 -0
  230. package/skills/investigating-agentforce-architecture/scripts/sf_cli.py +242 -0
  231. package/skills/investigating-agentforce-architecture/scripts/soql_loader.py +253 -0
  232. package/skills/investigating-agentforce-architecture/scripts/summarize_tree.py +143 -0
  233. package/skills/investigating-agentforce-architecture/scripts/tests/__init__.py +0 -0
  234. package/skills/investigating-agentforce-architecture/scripts/tests/_bootstrap.py +23 -0
  235. package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/__init__.py +0 -0
  236. package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/genai_payloads.py +400 -0
  237. package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check.py +307 -0
  238. package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check_main.py +283 -0
  239. package/skills/investigating-agentforce-architecture/scripts/tests/test_config.py +115 -0
  240. package/skills/investigating-agentforce-architecture/scripts/tests/test_end_to_end_fixture.py +651 -0
  241. package/skills/investigating-agentforce-architecture/scripts/tests/test_finalize.py +278 -0
  242. package/skills/investigating-agentforce-architecture/scripts/tests/test_flow_children_inflation.py +582 -0
  243. package/skills/investigating-agentforce-architecture/scripts/tests/test_fs_guard.py +113 -0
  244. package/skills/investigating-agentforce-architecture/scripts/tests/test_iterative_wave_b.py +478 -0
  245. package/skills/investigating-agentforce-architecture/scripts/tests/test_main_pipeline.py +3359 -0
  246. package/skills/investigating-agentforce-architecture/scripts/tests/test_parallel_retrieve.py +131 -0
  247. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_bundle.py +400 -0
  248. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave.py +644 -0
  249. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_classifiers.py +224 -0
  250. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_helpers.py +380 -0
  251. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_main.py +397 -0
  252. package/skills/investigating-agentforce-architecture/scripts/tests/test_per_branch_visited.py +244 -0
  253. package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_channels.py +359 -0
  254. package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_cli_recipes.py +185 -0
  255. package/skills/investigating-agentforce-architecture/scripts/tests/test_render_architecture.py +810 -0
  256. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_bot.py +203 -0
  257. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_creds.py +157 -0
  258. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_invocation_target.py +145 -0
  259. package/skills/investigating-agentforce-architecture/scripts/tests/test_rest_client.py +1253 -0
  260. package/skills/investigating-agentforce-architecture/scripts/tests/test_runtime_override.py +100 -0
  261. package/skills/investigating-agentforce-architecture/scripts/tests/test_sf_cli.py +261 -0
  262. package/skills/investigating-agentforce-architecture/scripts/tests/test_signature_stamping.py +466 -0
  263. package/skills/investigating-agentforce-architecture/scripts/tests/test_soql_loader.py +501 -0
  264. package/skills/investigating-agentforce-architecture/scripts/tests/test_summarize_tree.py +241 -0
  265. package/skills/investigating-agentforce-architecture/scripts/tests/test_write_emit_ctx.py +480 -0
  266. package/skills/investigating-agentforce-architecture/tools/emit_env.py +157 -0
  267. package/skills/investigating-agentforce-architecture/tools/emit_result.py +262 -0
  268. package/skills/investigating-agentforce-architecture/tools/sanitize.py +33 -0
  269. package/skills/investigating-agentforce-architecture/tools/write_emit_ctx.py +332 -0
  270. package/skills/investigating-agentforce-d360/README.md +123 -0
  271. package/skills/investigating-agentforce-d360/SKILL.md +163 -0
  272. package/skills/investigating-agentforce-d360/assets/dc/app_generation.sql +51 -0
  273. package/skills/investigating-agentforce-d360/assets/dc/content_category.sql +44 -0
  274. package/skills/investigating-agentforce-d360/assets/dc/content_quality.sql +41 -0
  275. package/skills/investigating-agentforce-d360/assets/dc/discover_sessions.sql +36 -0
  276. package/skills/investigating-agentforce-d360/assets/dc/feedback.sql +47 -0
  277. package/skills/investigating-agentforce-d360/assets/dc/feedback_details.sql +38 -0
  278. package/skills/investigating-agentforce-d360/assets/dc/gateway_records.sql +45 -0
  279. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_llm.sql +50 -0
  280. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_metadata.sql +44 -0
  281. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_tags.sql +42 -0
  282. package/skills/investigating-agentforce-d360/assets/dc/gateway_requests.sql +89 -0
  283. package/skills/investigating-agentforce-d360/assets/dc/gateway_responses.sql +43 -0
  284. package/skills/investigating-agentforce-d360/assets/dc/generations.sql +52 -0
  285. package/skills/investigating-agentforce-d360/assets/dc/interactions.sql +53 -0
  286. package/skills/investigating-agentforce-d360/assets/dc/messages.sql +53 -0
  287. package/skills/investigating-agentforce-d360/assets/dc/messaging_session.sql +37 -0
  288. package/skills/investigating-agentforce-d360/assets/dc/moment_interactions.sql +34 -0
  289. package/skills/investigating-agentforce-d360/assets/dc/moments.sql +39 -0
  290. package/skills/investigating-agentforce-d360/assets/dc/participants.sql +48 -0
  291. package/skills/investigating-agentforce-d360/assets/dc/sessions.sql +78 -0
  292. package/skills/investigating-agentforce-d360/assets/dc/steps.sql +64 -0
  293. package/skills/investigating-agentforce-d360/assets/dc/tag_associations.sql +46 -0
  294. package/skills/investigating-agentforce-d360/assets/dc/tag_definition_associations.sql +37 -0
  295. package/skills/investigating-agentforce-d360/assets/dc/tag_definitions.sql +50 -0
  296. package/skills/investigating-agentforce-d360/assets/dc/tags.sql +37 -0
  297. package/skills/investigating-agentforce-d360/assets/dc/telemetry_spans.sql +55 -0
  298. package/skills/investigating-agentforce-d360/references/artifacts.md +50 -0
  299. package/skills/investigating-agentforce-d360/references/dc_dmo_fields.md +823 -0
  300. package/skills/investigating-agentforce-d360/references/dc_pipeline_contract.md +608 -0
  301. package/skills/investigating-agentforce-d360/scripts/_shared/__init__.py +2 -0
  302. package/skills/investigating-agentforce-d360/scripts/_shared/cli_override.py +98 -0
  303. package/skills/investigating-agentforce-d360/scripts/_shared/fs_guard.py +334 -0
  304. package/skills/investigating-agentforce-d360/scripts/_shared/paths.py +155 -0
  305. package/skills/investigating-agentforce-d360/scripts/_shared/runtime.py +59 -0
  306. package/skills/investigating-agentforce-d360/scripts/_shared/sql.py +14 -0
  307. package/skills/investigating-agentforce-d360/scripts/assemble_dc.py +1624 -0
  308. package/skills/investigating-agentforce-d360/scripts/config.py +45 -0
  309. package/skills/investigating-agentforce-d360/scripts/dc.py +188 -0
  310. package/skills/investigating-agentforce-d360/scripts/discover_sessions.py +556 -0
  311. package/skills/investigating-agentforce-d360/scripts/fetch_dc.py +1045 -0
  312. package/skills/investigating-agentforce-d360/scripts/render_dc.py +1750 -0
  313. package/skills/investigating-agentforce-d360/scripts/resolve_session.py +264 -0
  314. package/skills/investigating-agentforce-d360/scripts/storage.py +92 -0
  315. package/skills/investigating-agentforce-d360/scripts/tests/__init__.py +0 -0
  316. package/skills/investigating-agentforce-d360/scripts/tests/_bootstrap.py +15 -0
  317. package/skills/investigating-agentforce-d360/scripts/tests/fixtures/__init__.py +0 -0
  318. package/skills/investigating-agentforce-d360/scripts/tests/fixtures/synthetic_session.py +424 -0
  319. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_bootstrap_and_mode.py +115 -0
  320. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct.py +220 -0
  321. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct_integration.py +158 -0
  322. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_helpers.py +287 -0
  323. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_integration.py +247 -0
  324. package/skills/investigating-agentforce-d360/scripts/tests/test_dc_and_resolve_session.py +433 -0
  325. package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions.py +458 -0
  326. package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions_grep_ci.py +193 -0
  327. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_helpers.py +266 -0
  328. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_identity.py +528 -0
  329. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_main.py +251 -0
  330. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall.py +229 -0
  331. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall_full.py +283 -0
  332. package/skills/investigating-agentforce-d360/scripts/tests/test_identity_coherence.py +327 -0
  333. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_branches.py +256 -0
  334. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_gateway_direct.py +130 -0
  335. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_helpers.py +291 -0
  336. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_integration.py +220 -0
  337. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_planner_llm_calls.py +284 -0
  338. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_show_prompts_gating.py +215 -0
  339. package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_from_disk.py +100 -0
  340. package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_session_main.py +149 -0
  341. package/skills/investigating-agentforce-d360/scripts/tests/test_runtime_override.py +104 -0
  342. package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape.py +95 -0
  343. package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape_dropped_by_stdm.py +85 -0
  344. package/skills/managing-managed-event-subscription/SKILL.md +152 -0
  345. package/skills/managing-managed-event-subscription/assets/managed-event-subscription-template.xml +20 -0
  346. package/skills/managing-managed-event-subscription/references/delete-guide.md +57 -0
  347. package/skills/managing-managed-event-subscription/references/topic-name-formats.md +26 -0
  348. package/skills/managing-managed-event-subscription/references/update-constraints.md +30 -0
  349. package/skills/modeling-omnistudio-epc-catalog/SKILL.md +0 -1
  350. package/skills/observing-agentforce/SKILL.md +0 -1
  351. package/skills/orchestrating-datacloud/SKILL.md +0 -1
  352. package/skills/preparing-datacloud/SKILL.md +0 -1
  353. package/skills/querying-soql/SKILL.md +0 -1
  354. package/skills/retrieving-datacloud/SKILL.md +0 -1
  355. package/skills/running-apex-tests/SKILL.md +0 -1
  356. package/skills/running-code-analyzer/SKILL.md +0 -1
  357. package/skills/segmenting-datacloud/SKILL.md +0 -1
  358. package/skills/testing-agentforce/SKILL.md +0 -1
  359. package/skills/uplifting-components-to-slds2/SKILL.md +3 -2
  360. package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +30 -9
  361. package/skills/uplifting-components-to-slds2/references/examples.md +24 -6
  362. package/skills/validating-slds/SKILL.md +262 -0
  363. package/skills/validating-slds/references/quality-checks.md +308 -0
  364. package/skills/validating-slds/references/report-format.md +302 -0
  365. package/skills/validating-slds/scripts/analyze-quality.cjs +521 -0
@@ -0,0 +1,113 @@
1
+ """Tests for fs_guard Python-importable validators + api_version check."""
2
+ from __future__ import annotations
3
+
4
+ import unittest
5
+
6
+ from . import _bootstrap # noqa: F401
7
+
8
+ from config import fs_guard # type: ignore — re-exported from _shared/
9
+
10
+
11
+ class ValidateApiNameTests(unittest.TestCase):
12
+ def test_valid_name_passes(self):
13
+ fs_guard.validate_api_name("MyAgent", label="name")
14
+ fs_guard.validate_api_name("Foo_v2", label="name")
15
+ fs_guard.validate_api_name("x", label="name")
16
+
17
+ def test_dotdot_rejected(self):
18
+ with self.assertRaises(fs_guard.ValidationError):
19
+ fs_guard.validate_api_name("..", label="agent_api_name")
20
+
21
+ def test_slash_rejected(self):
22
+ with self.assertRaises(fs_guard.ValidationError):
23
+ fs_guard.validate_api_name("foo/bar", label="agent_api_name")
24
+
25
+ def test_dotdot_in_path_segment_rejected(self):
26
+ with self.assertRaises(fs_guard.ValidationError):
27
+ fs_guard.validate_api_name("foo/../bar", label="agent_api_name")
28
+
29
+ def test_null_byte_rejected(self):
30
+ with self.assertRaises(fs_guard.ValidationError):
31
+ fs_guard.validate_api_name("foo\x00bar", label="name")
32
+
33
+ def test_empty_string_rejected(self):
34
+ with self.assertRaises(fs_guard.ValidationError):
35
+ fs_guard.validate_api_name("", label="name")
36
+
37
+ def test_none_rejected(self):
38
+ with self.assertRaises(fs_guard.ValidationError):
39
+ fs_guard.validate_api_name(None, label="name")
40
+
41
+ def test_non_string_rejected(self):
42
+ with self.assertRaises(fs_guard.ValidationError):
43
+ fs_guard.validate_api_name(42, label="name")
44
+
45
+ def test_validation_error_carries_label(self):
46
+ with self.assertRaises(fs_guard.ValidationError) as ctx:
47
+ fs_guard.validate_api_name("bad/name", label="agent_api_name")
48
+ self.assertEqual(ctx.exception.label, "agent_api_name")
49
+
50
+
51
+ class ValidateApiVersionTests(unittest.TestCase):
52
+ def test_v60_0_ok(self):
53
+ fs_guard.validate_api_version("v60.0", label="api_version")
54
+
55
+ def test_v66_0_ok(self):
56
+ fs_guard.validate_api_version("v66.0", label="api_version")
57
+
58
+ def test_v60_no_minor_rejected(self):
59
+ with self.assertRaises(fs_guard.ValidationError):
60
+ fs_guard.validate_api_version("v60", label="api_version")
61
+
62
+ def test_bare_number_rejected(self):
63
+ with self.assertRaises(fs_guard.ValidationError):
64
+ fs_guard.validate_api_version("60.0", label="api_version")
65
+
66
+ def test_slash_rejected(self):
67
+ with self.assertRaises(fs_guard.ValidationError):
68
+ fs_guard.validate_api_version("v60.0/../", label="api_version")
69
+
70
+ def test_dotdot_rejected(self):
71
+ with self.assertRaises(fs_guard.ValidationError):
72
+ fs_guard.validate_api_version("..", label="api_version")
73
+
74
+ def test_empty_rejected(self):
75
+ with self.assertRaises(fs_guard.ValidationError):
76
+ fs_guard.validate_api_version("", label="api_version")
77
+
78
+
79
+ class ValidateOrgId15Tests(unittest.TestCase):
80
+ def test_valid_15_char_alnum_ok(self):
81
+ fs_guard.validate_org_id_15("00Dxx0000000000", label="org_id_15")
82
+
83
+ def test_slash_rejected(self):
84
+ with self.assertRaises(fs_guard.ValidationError):
85
+ fs_guard.validate_org_id_15("00D/x0000000000", label="org_id_15")
86
+
87
+ def test_wrong_length_rejected(self):
88
+ with self.assertRaises(fs_guard.ValidationError):
89
+ fs_guard.validate_org_id_15("00Dxx000000000", label="org_id_15")
90
+ with self.assertRaises(fs_guard.ValidationError):
91
+ fs_guard.validate_org_id_15("00Dxx0000000000X", label="org_id_15")
92
+
93
+ def test_underscore_rejected(self):
94
+ # ORG_ID_15_RE forbids underscores — stricter than api_name.
95
+ with self.assertRaises(fs_guard.ValidationError):
96
+ fs_guard.validate_org_id_15("00Dxx00000000_0", label="org_id_15")
97
+
98
+
99
+ class CliApiVersionCheckTests(unittest.TestCase):
100
+ """The api_version check type is registered in VALID_CHECKS + CHECKS."""
101
+
102
+ def test_registered_in_valid_checks(self):
103
+ self.assertIn("api_version", fs_guard.VALID_CHECKS)
104
+
105
+ def test_registered_in_checks_dispatch(self):
106
+ self.assertIn("api_version", fs_guard.CHECKS)
107
+
108
+ def test_check_function_exists(self):
109
+ self.assertTrue(callable(fs_guard.check_api_version))
110
+
111
+
112
+ if __name__ == "__main__":
113
+ unittest.main()
@@ -0,0 +1,478 @@
1
+ """Tests for `main._iterate_wave_b` — iterative subflow + apex discovery.
2
+
3
+ The initial `_run_wave_b` only enumerates top-level flow refs from
4
+ `bundle_parsed`. When those flow bodies reference subflows (shared
5
+ utility flows like `handleFlowFault`) or new Apex classes, their bodies
6
+ were never fetched. `_iterate_wave_b` drives Wave B to a fixed point so
7
+ every reachable flow/apex body lands in the tree.
8
+
9
+ Assertions focus on observable behavior:
10
+ * Convergence shape — no extra fetches when there are no new refs.
11
+ * Correctness — a utility flow referenced from the top-level round is
12
+ fetched in the next round and its metadata lands in the merged dict.
13
+ * Safety — iteration cap surfaces remaining unfetched names via
14
+ `unresolved`; fetch failures are tolerated without crashing.
15
+ * Cross-kind discovery — a subflow's actionCall referencing a new
16
+ Apex class adds that class to `apex_rows`.
17
+ * Deduplication — the same subflow referenced twice is fetched once.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import unittest
22
+ from unittest import mock
23
+
24
+ from . import _bootstrap # noqa: F401
25
+
26
+ import main # type: ignore
27
+
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # Helpers
31
+ # ---------------------------------------------------------------------------
32
+
33
+
34
+ def _creds_provider():
35
+ return ("https://example.my.salesforce.com", "fake_token")
36
+
37
+
38
+ def _refresh_fn():
39
+ return _creds_provider()
40
+
41
+
42
+ def _flow_def_row(dev_name: str, version_id: str) -> dict:
43
+ """Shape matches what `fetch_flow_definition_ids_by_names` returns —
44
+ the fields `_build_flow_children` + the pipeline downstream expect."""
45
+ return {
46
+ "Id": f"300{dev_name}",
47
+ "DeveloperName": dev_name,
48
+ "ActiveVersionId": version_id,
49
+ }
50
+
51
+
52
+ def _flow_metadata_record(
53
+ version_id: str,
54
+ full_name: str,
55
+ *,
56
+ subflows: list[tuple[str, str]] | None = None,
57
+ action_calls: list[dict] | None = None,
58
+ ) -> dict:
59
+ """Shape a synthetic Flow.Metadata record the way `fetch_flow_metadata`
60
+ returns it — a dict with Id/FullName/Metadata. Subflow tuples are
61
+ `(element_name, flow_name)`; action_calls are raw dicts."""
62
+ md: dict = {}
63
+ if subflows is not None:
64
+ md["subflows"] = [
65
+ {"name": elem, "flowName": target} for elem, target in subflows
66
+ ]
67
+ if action_calls is not None:
68
+ md["actionCalls"] = action_calls
69
+ return {"Id": version_id, "FullName": full_name, "Metadata": md}
70
+
71
+
72
+ def _apex_row(name: str) -> dict:
73
+ """Shape matches `fetch_apex_bodies_by_names` output — `_iterate_wave_b`
74
+ tracks already-fetched by `Name`."""
75
+ return {"Id": f"01p_{name}", "Name": name, "Body": "public class ..."}
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # Tests
80
+ # ---------------------------------------------------------------------------
81
+
82
+
83
+ class IterativeWaveBTests(unittest.TestCase):
84
+ def test_single_round_when_no_subflows(self):
85
+ """Round 1 flow body references no subflows and no new apex →
86
+ `_extract_refs_from_flow_metadata` returns empty sets →
87
+ `_fetch_wave_b_by_names` is never invoked → initial wave_b passes
88
+ through unchanged."""
89
+ initial = {
90
+ "apex_rows": [],
91
+ "flow_def_rows": [_flow_def_row("TopFlow", "301TOP")],
92
+ "flow_metadata": {
93
+ "301TOP": _flow_metadata_record(
94
+ "301TOP", "TopFlow-1", subflows=[], action_calls=[],
95
+ ),
96
+ },
97
+ "unresolved": [],
98
+ }
99
+
100
+ with mock.patch.object(main, "_fetch_wave_b_by_names") as mocked:
101
+ out = main._iterate_wave_b(
102
+ initial, _creds_provider, _refresh_fn,
103
+ api_version="v60.0", org_alias="test-org",
104
+ parallelism=2, max_iterations=5,
105
+ )
106
+
107
+ mocked.assert_not_called()
108
+ self.assertEqual(
109
+ {r["DeveloperName"] for r in out["flow_def_rows"]},
110
+ {"TopFlow"},
111
+ )
112
+ self.assertEqual(out["unresolved"], [])
113
+
114
+ def test_two_rounds_for_shared_utility_flow(self):
115
+ """Round 1: a top-level flow body references subflow `handleFlowFault`.
116
+ Round 2: fetches `handleFlowFault`'s metadata. Round 3: no new refs.
117
+ Converges. Final flow_metadata contains BOTH flows.
118
+
119
+ This is the real-org `MyAgent__v5` shape — `handleFlowFault`
120
+ is shared across every flow and should be fetched in one extra
121
+ round.
122
+ """
123
+ initial = {
124
+ "apex_rows": [],
125
+ "flow_def_rows": [_flow_def_row("AGNT_Top_Flow", "301TOP")],
126
+ "flow_metadata": {
127
+ "301TOP": _flow_metadata_record(
128
+ "301TOP", "AGNT_Top_Flow-1",
129
+ subflows=[("Handle_Flow_Fault", "handleFlowFault")],
130
+ ),
131
+ },
132
+ "unresolved": [],
133
+ }
134
+
135
+ # Mock `_fetch_wave_b_by_names` for two rounds:
136
+ # Round 1: flow_names=[handleFlowFault] → returns metadata whose
137
+ # actionCalls reference a NEW apex (XCSF_FlowFaultMessage).
138
+ # Round 2: apex_names=[XCSF_FlowFaultMessage] → apex row returned.
139
+ # Round 3: no new refs → converge.
140
+ calls: list[dict] = []
141
+
142
+ def fake_fetch(**kwargs):
143
+ calls.append({
144
+ "flow_names": list(kwargs["flow_names"]),
145
+ "apex_names": list(kwargs["apex_names"]),
146
+ })
147
+ if kwargs["flow_names"] == ["handleFlowFault"]:
148
+ return {
149
+ "apex_rows": [],
150
+ "flow_def_rows": [
151
+ _flow_def_row("handleFlowFault", "301UTIL"),
152
+ ],
153
+ "flow_metadata": {
154
+ "301UTIL": _flow_metadata_record(
155
+ "301UTIL", "handleFlowFault-1",
156
+ subflows=[],
157
+ action_calls=[{
158
+ "name": "Parse_and_log_fault",
159
+ "actionType": "apex",
160
+ "actionName": "XCSF_FlowFaultMessage",
161
+ }],
162
+ ),
163
+ },
164
+ "unresolved": [],
165
+ }
166
+ if kwargs["apex_names"] == ["XCSF_FlowFaultMessage"]:
167
+ return {
168
+ "apex_rows": [_apex_row("XCSF_FlowFaultMessage")],
169
+ "flow_def_rows": [],
170
+ "flow_metadata": {},
171
+ "unresolved": [],
172
+ }
173
+ raise AssertionError(f"unexpected fetch call: {kwargs}")
174
+
175
+ with mock.patch.object(
176
+ main, "_fetch_wave_b_by_names", side_effect=fake_fetch,
177
+ ) as mocked:
178
+ out = main._iterate_wave_b(
179
+ initial, _creds_provider, _refresh_fn,
180
+ api_version="v60.0", org_alias="test-org",
181
+ parallelism=2, max_iterations=5,
182
+ )
183
+
184
+ # Exactly two rounds: round 1 picks up handleFlowFault, round 2
185
+ # picks up the apex discovered in round 1's subflow body, round 3
186
+ # converges without invoking the fetcher.
187
+ self.assertEqual(mocked.call_count, 2)
188
+ dev_names = {r["DeveloperName"] for r in out["flow_def_rows"]}
189
+ self.assertEqual(dev_names, {"AGNT_Top_Flow", "handleFlowFault"})
190
+ self.assertIn("301UTIL", out["flow_metadata"])
191
+ apex_names_out = {r["Name"] for r in out["apex_rows"]}
192
+ self.assertIn("XCSF_FlowFaultMessage", apex_names_out)
193
+ # Call-order sanity: flow round precedes apex round.
194
+ self.assertEqual(calls[0]["flow_names"], ["handleFlowFault"])
195
+ self.assertEqual(calls[1]["apex_names"], ["XCSF_FlowFaultMessage"])
196
+
197
+ def test_actioncall_discovers_new_apex(self):
198
+ """A subflow body carries an actionCall referencing a new Apex
199
+ class. Round 2 fetches that Apex. `wave_b["apex_rows"]` grows."""
200
+ initial = {
201
+ "apex_rows": [],
202
+ "flow_def_rows": [_flow_def_row("TopFlow", "301TOP")],
203
+ "flow_metadata": {
204
+ "301TOP": _flow_metadata_record(
205
+ "301TOP", "TopFlow-1",
206
+ subflows=[],
207
+ action_calls=[{
208
+ "name": "Invoke_Helper",
209
+ "actionType": "apex",
210
+ "actionName": "NewApexHelper",
211
+ }],
212
+ ),
213
+ },
214
+ "unresolved": [],
215
+ }
216
+
217
+ def fake_fetch(**kwargs):
218
+ # Must be the apex-only round — no new flow names.
219
+ self.assertEqual(kwargs["flow_names"], [])
220
+ self.assertEqual(kwargs["apex_names"], ["NewApexHelper"])
221
+ return {
222
+ "apex_rows": [_apex_row("NewApexHelper")],
223
+ "flow_def_rows": [],
224
+ "flow_metadata": {},
225
+ "unresolved": [],
226
+ }
227
+
228
+ with mock.patch.object(
229
+ main, "_fetch_wave_b_by_names", side_effect=fake_fetch,
230
+ ) as mocked:
231
+ out = main._iterate_wave_b(
232
+ initial, _creds_provider, _refresh_fn,
233
+ api_version="v60.0", org_alias="test-org",
234
+ parallelism=2, max_iterations=5,
235
+ )
236
+
237
+ self.assertEqual(mocked.call_count, 1)
238
+ apex_names_seen = {r["Name"] for r in out["apex_rows"]}
239
+ self.assertEqual(apex_names_seen, {"NewApexHelper"})
240
+
241
+ def test_dedup_across_rounds(self):
242
+ """The same subflow referenced from two parent flows is fetched
243
+ exactly once. `_iterate_wave_b` diffs against already-fetched
244
+ names — duplicates are filtered before dispatch."""
245
+ initial = {
246
+ "apex_rows": [],
247
+ "flow_def_rows": [
248
+ _flow_def_row("ParentA", "301A"),
249
+ _flow_def_row("ParentB", "301B"),
250
+ ],
251
+ "flow_metadata": {
252
+ "301A": _flow_metadata_record(
253
+ "301A", "ParentA-1",
254
+ subflows=[("Handle_A", "SharedUtility")],
255
+ ),
256
+ "301B": _flow_metadata_record(
257
+ "301B", "ParentB-1",
258
+ subflows=[("Handle_B", "SharedUtility")],
259
+ ),
260
+ },
261
+ "unresolved": [],
262
+ }
263
+
264
+ call_log: list[list[str]] = []
265
+
266
+ def fake_fetch(**kwargs):
267
+ call_log.append(list(kwargs["flow_names"]))
268
+ return {
269
+ "apex_rows": [],
270
+ "flow_def_rows": [_flow_def_row("SharedUtility", "301UTIL")],
271
+ "flow_metadata": {
272
+ "301UTIL": _flow_metadata_record(
273
+ "301UTIL", "SharedUtility-1", subflows=[],
274
+ ),
275
+ },
276
+ "unresolved": [],
277
+ }
278
+
279
+ with mock.patch.object(
280
+ main, "_fetch_wave_b_by_names", side_effect=fake_fetch,
281
+ ) as mocked:
282
+ main._iterate_wave_b(
283
+ initial, _creds_provider, _refresh_fn,
284
+ api_version="v60.0", org_alias="test-org",
285
+ parallelism=2, max_iterations=5,
286
+ )
287
+
288
+ # Exactly ONE round of fetching — round 1 queries [SharedUtility]
289
+ # (deduped across the two parents); round 2 finds no new refs.
290
+ self.assertEqual(mocked.call_count, 1)
291
+ self.assertEqual(call_log[0], ["SharedUtility"])
292
+
293
+ def test_iteration_cap_surfaces_pending(self):
294
+ """Pathological graph where every round introduces a new subflow
295
+ reference (chain: F1 → F2 → F3 → ...). After `max_iterations`
296
+ rounds, remaining unfetched names are surfaced in `unresolved`
297
+ so the pipeline can degrade to PARTIAL_OK with a clear cause.
298
+
299
+ Use `max_iterations=3` to keep the test fast; the shape is
300
+ identical to the production cap of 5.
301
+ """
302
+ initial = {
303
+ "apex_rows": [],
304
+ "flow_def_rows": [_flow_def_row("F0", "301_V0")],
305
+ "flow_metadata": {
306
+ "301_V0": _flow_metadata_record(
307
+ "301_V0", "F0-1", subflows=[("Chain", "F1")],
308
+ ),
309
+ },
310
+ "unresolved": [],
311
+ }
312
+
313
+ round_counter = {"i": 1}
314
+
315
+ def fake_fetch(**kwargs):
316
+ """Each round returns a flow whose body references the next
317
+ flow in the chain — unbounded growth."""
318
+ requested = kwargs["flow_names"][0]
319
+ next_flow = f"F{round_counter['i'] + 1}"
320
+ round_counter["i"] += 1
321
+ return {
322
+ "apex_rows": [],
323
+ "flow_def_rows": [_flow_def_row(requested, f"301_V{requested}")],
324
+ "flow_metadata": {
325
+ f"301_V{requested}": _flow_metadata_record(
326
+ f"301_V{requested}", f"{requested}-1",
327
+ subflows=[("Chain", next_flow)],
328
+ ),
329
+ },
330
+ "unresolved": [],
331
+ }
332
+
333
+ with mock.patch.object(
334
+ main, "_fetch_wave_b_by_names", side_effect=fake_fetch,
335
+ ) as mocked:
336
+ out = main._iterate_wave_b(
337
+ initial, _creds_provider, _refresh_fn,
338
+ api_version="v60.0", org_alias="test-org",
339
+ parallelism=2, max_iterations=3,
340
+ )
341
+
342
+ # We exhausted exactly max_iterations rounds.
343
+ self.assertEqual(mocked.call_count, 3)
344
+ # The unfetched chain-tail lands in `unresolved` with the cap
345
+ # reason so the pipeline can reflect it in the RESULT block.
346
+ iter_cap_entries = [
347
+ u for u in out["unresolved"]
348
+ if "wave-b-iteration-cap" in (u.get("reason") or "")
349
+ ]
350
+ self.assertTrue(iter_cap_entries)
351
+ # The next-round flow reference (F4, unfetched) must be among them.
352
+ pending_names = {u.get("api_name") for u in iter_cap_entries}
353
+ self.assertIn("F4", pending_names)
354
+
355
+ def test_fetch_failure_tolerated(self):
356
+ """If the round-N fetch raises (managed-package flow not readable,
357
+ permission denied, etc.), the iteration doesn't crash. The
358
+ affected name stays absent from `flow_def_rows` and surfaces via
359
+ the pending-fetches path downstream; other rounds continue.
360
+
361
+ Concretely: we simulate the failure by having the mocked
362
+ `_fetch_wave_b_by_names` append an `unresolved` entry instead of
363
+ returning metadata for the troubled name, which is exactly the
364
+ contract `_fetch_wave_b_by_names` follows internally (it
365
+ catches RestClientError / SoqlParamError and logs to
366
+ `unresolved` without raising).
367
+ """
368
+ initial = {
369
+ "apex_rows": [],
370
+ "flow_def_rows": [_flow_def_row("TopFlow", "301TOP")],
371
+ "flow_metadata": {
372
+ "301TOP": _flow_metadata_record(
373
+ "301TOP", "TopFlow-1",
374
+ subflows=[
375
+ ("Good_Sub", "GoodSubflow"),
376
+ ("Bad_Sub", "UnreachableFlow"),
377
+ ],
378
+ ),
379
+ },
380
+ "unresolved": [],
381
+ }
382
+
383
+ def fake_fetch(**kwargs):
384
+ # Only the reachable one resolves. The unreachable one is
385
+ # reported via the `unresolved` channel by the inner fetcher.
386
+ return {
387
+ "apex_rows": [],
388
+ "flow_def_rows": [_flow_def_row("GoodSubflow", "301GOOD")],
389
+ "flow_metadata": {
390
+ "301GOOD": _flow_metadata_record(
391
+ "301GOOD", "GoodSubflow-1", subflows=[],
392
+ ),
393
+ },
394
+ "unresolved": [{
395
+ "kind": "FLOW",
396
+ "reason": "flow-def-by-name-failed:managed-package-403",
397
+ }],
398
+ }
399
+
400
+ with mock.patch.object(
401
+ main, "_fetch_wave_b_by_names", side_effect=fake_fetch,
402
+ ):
403
+ out = main._iterate_wave_b(
404
+ initial, _creds_provider, _refresh_fn,
405
+ api_version="v60.0", org_alias="test-org",
406
+ parallelism=2, max_iterations=5,
407
+ )
408
+
409
+ dev_names = {r["DeveloperName"] for r in out["flow_def_rows"]}
410
+ # GoodSubflow made it in; UnreachableFlow did not.
411
+ self.assertIn("GoodSubflow", dev_names)
412
+ self.assertNotIn("UnreachableFlow", dev_names)
413
+ # The inner-fetch's unresolved entry propagates.
414
+ reasons = [u.get("reason") or "" for u in out["unresolved"]]
415
+ self.assertTrue(
416
+ any("managed-package-403" in r for r in reasons),
417
+ f"expected propagated failure reason; got {reasons}",
418
+ )
419
+ # UnreachableFlow is still referenced but not fetched — after
420
+ # iteration converges on subsequent rounds (no new discoveries
421
+ # because GoodSubflow is leaf), it lands as iteration-cap
422
+ # pending. In this test max_iterations=5 and the chain stops at
423
+ # round 1; UnreachableFlow is still missing from fetched names,
424
+ # so the convergence scan at the end flags it? Actually no —
425
+ # convergence only surfaces via the cap. Let's verify that the
426
+ # pipeline's parse_wave path would still mark it as pending via
427
+ # visited-vs-referenced — but that's out of scope for this unit
428
+ # test. The contract here is: iteration doesn't crash, good
429
+ # flows are merged, bad-flow signal propagates.
430
+
431
+
432
+ class ExtractRefsFromFlowMetadataTests(unittest.TestCase):
433
+ """Direct coverage for `_extract_refs_from_flow_metadata` — the
434
+ load-bearing extractor that drives iteration. Its permissiveness
435
+ (tolerating missing/None/malformed records) is a correctness
436
+ property: if it raised on edge cases, iteration would crash on any
437
+ flow with a weird metadata shape."""
438
+
439
+ def test_extracts_subflow_and_apex_names(self):
440
+ flow_metadata = {
441
+ "301A": _flow_metadata_record(
442
+ "301A", "A-1",
443
+ subflows=[("E1", "SubA"), ("E2", "SubB")],
444
+ action_calls=[
445
+ {"name": "ApexCall", "actionType": "apex",
446
+ "actionName": "MyApex"},
447
+ # non-apex actionCall shouldn't appear in apex set
448
+ {"name": "StdCall", "actionType": "emailSimple",
449
+ "actionName": "SendEmail"},
450
+ ],
451
+ ),
452
+ }
453
+ subs, apex = main._extract_refs_from_flow_metadata(flow_metadata)
454
+ self.assertEqual(subs, {"SubA", "SubB"})
455
+ self.assertEqual(apex, {"MyApex"})
456
+
457
+ def test_tolerates_missing_fields(self):
458
+ """None records, non-dict metadata, missing keys, blank names —
459
+ none of these should raise; they all drop silently."""
460
+ flow_metadata = {
461
+ "301A": None,
462
+ "301B": {"Metadata": None},
463
+ "301C": {"Metadata": "not-a-dict"},
464
+ "301D": {"Metadata": {}},
465
+ "301E": {"Metadata": {
466
+ "subflows": [{"name": "noName"}, "not-a-dict",
467
+ {"name": "E", "flowName": ""}],
468
+ "actionCalls": [{"actionType": "apex", "actionName": ""},
469
+ "not-a-dict"],
470
+ }},
471
+ }
472
+ subs, apex = main._extract_refs_from_flow_metadata(flow_metadata)
473
+ self.assertEqual(subs, set())
474
+ self.assertEqual(apex, set())
475
+
476
+
477
+ if __name__ == "__main__":
478
+ unittest.main()