@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,283 @@
1
+ """End-to-end waterfall tests for ``fetch_dc._run_waterfall``.
2
+
3
+ Mocks ``dc.post`` at the network boundary; dispatches per-query fixture
4
+ rows so the 5-wave orchestration runs to completion. Asserts on:
5
+
6
+ - per-query manifest entries (rows, status, wave)
7
+ - harvested_ids tracking across waves
8
+ - session_shape classification
9
+ - on-disk dc.<name>.json artifacts written by ``storage.save``
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import unittest
15
+ from datetime import datetime, timezone
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 fetch_dc # type: ignore
23
+ from config import paths # type: ignore
24
+ from .fixtures.synthetic_session import IDS, make_rows # type: ignore
25
+
26
+
27
+ def _build_fixture_dispatch() -> dict[str, list[dict]]:
28
+ """Return rows keyed by DMO name for the synthetic session fixture."""
29
+ return make_rows()
30
+
31
+
32
+ def _make_post_dispatcher(rows_by_name: dict[str, list[dict]]):
33
+ """Return a fake ``dc.post`` that dispatches to the right fixture
34
+ rows by inspecting the query_name argument."""
35
+ def fake_post(sql: str, instance_url: str, token: str, query_name: str = ""):
36
+ # `_fetch` passes the template name as query_name; that's exactly
37
+ # what we keyed on in the fixture.
38
+ return list(rows_by_name.get(query_name, []))
39
+ return fake_post
40
+
41
+
42
+ def _make_ctx(tmpdir: Path) -> dict:
43
+ """Build the same ctx shape that fetch_dc.main() builds."""
44
+ return {
45
+ "session_id": IDS.SID,
46
+ "org_alias": "my-org",
47
+ "instance_url": "https://example.salesforce.com",
48
+ "token": "TOKEN",
49
+ "verbose": False,
50
+ "queries": [],
51
+ "started_at": datetime.now(timezone.utc),
52
+ "org_id_15": None,
53
+ "agent_api_name": None,
54
+ "agent_version": None,
55
+ }
56
+
57
+
58
+ class _WaterfallHarness:
59
+ """Patch DATA_ROOT + dc.post; build ctx; run the waterfall."""
60
+
61
+ def __init__(self, *, rows_overrides: dict[str, list[dict]] | None = None):
62
+ self.rows_overrides = rows_overrides or {}
63
+
64
+ def __enter__(self):
65
+ self._tmp = TemporaryDirectory()
66
+ self._tmpdir = Path(self._tmp.name)
67
+ self.ctx = _make_ctx(self._tmpdir)
68
+ rows = _build_fixture_dispatch()
69
+ rows.update(self.rows_overrides)
70
+ self._post = _make_post_dispatcher(rows)
71
+ self._patches = [
72
+ mock.patch.object(paths, "DATA_ROOT", self._tmpdir),
73
+ mock.patch.object(fetch_dc, "post", side_effect=self._post),
74
+ # Keep _log quiet during tests
75
+ mock.patch.object(fetch_dc, "_log"),
76
+ ]
77
+ for p in self._patches:
78
+ p.start()
79
+ return self
80
+
81
+ def __exit__(self, *exc):
82
+ for p in self._patches:
83
+ p.stop()
84
+ self._tmp.cleanup()
85
+
86
+ def session_dir(self) -> Path:
87
+ # macOS resolves /var → /private/var; resolve once so `is_file`
88
+ # checks work after storage.save's path composition.
89
+ return (
90
+ self._tmpdir.resolve()
91
+ / IDS.ORG_ID_15
92
+ / f"{IDS.AGENT_API}__{IDS.AGENT_VERSION}"
93
+ / IDS.SID
94
+ )
95
+
96
+
97
+ # -----------------------------------------------------------------------------
98
+ # Identity stamping (wave 1a)
99
+ # -----------------------------------------------------------------------------
100
+
101
+
102
+ class WaterfallIdentityTests(unittest.TestCase):
103
+
104
+ def test_identity_resolved_into_ctx_after_wave_1a(self):
105
+ with _WaterfallHarness() as h:
106
+ fetch_dc._run_waterfall(h.ctx)
107
+ self.assertEqual(h.ctx["org_id_15"], IDS.ORG_ID_15)
108
+ self.assertEqual(h.ctx["agent_api_name"], IDS.AGENT_API)
109
+ self.assertEqual(h.ctx["agent_version"], IDS.AGENT_VERSION)
110
+
111
+ def test_sessions_and_participants_persisted_after_identity_resolved(self):
112
+ # The deferred-save flush in wave 1a should write both files.
113
+ with _WaterfallHarness() as h:
114
+ fetch_dc._run_waterfall(h.ctx)
115
+ # Search the entire tmp tree — avoids macOS /private/var ↔ /var
116
+ # resolution discrepancies between paths.session_dir() and our
117
+ # locally-built path.
118
+ written = {p.name for p in h._tmpdir.rglob("dc.*.json")}
119
+ self.assertIn("dc.sessions.json", written)
120
+ self.assertIn("dc.participants.json", written)
121
+
122
+
123
+ # -----------------------------------------------------------------------------
124
+ # Wave-by-wave artifact persistence
125
+ # -----------------------------------------------------------------------------
126
+
127
+
128
+ class WaterfallWritesArtifactsTests(unittest.TestCase):
129
+
130
+ def test_populated_dmos_emit_dc_json_artifacts(self):
131
+ with _WaterfallHarness() as h:
132
+ fetch_dc._run_waterfall(h.ctx)
133
+ files = {p.name for p in h._tmpdir.rglob("dc.*.json")}
134
+ # 11 DMOs are populated in the synthetic fixture; each lands as a file.
135
+ for name in (
136
+ "dc.sessions.json", "dc.interactions.json", "dc.steps.json",
137
+ "dc.messages.json", "dc.participants.json",
138
+ "dc.gateway_requests.json", "dc.gateway_responses.json",
139
+ "dc.generations.json", "dc.content_quality.json",
140
+ "dc.gateway_records.json", "dc.gateway_request_tags.json",
141
+ ):
142
+ self.assertIn(name, files, f"{name} should be on disk")
143
+
144
+ def test_zero_row_dmos_skipped_no_artifact(self):
145
+ with _WaterfallHarness() as h:
146
+ fetch_dc._run_waterfall(h.ctx)
147
+ files = {p.name for p in h._tmpdir.rglob("dc.*.json")}
148
+ # `tags` and friends have empty rows → no on-disk artifact.
149
+ self.assertNotIn("dc.tags.json", files)
150
+ self.assertNotIn("dc.tag_definitions.json", files)
151
+
152
+
153
+ # -----------------------------------------------------------------------------
154
+ # Manifest entries (one per query)
155
+ # -----------------------------------------------------------------------------
156
+
157
+
158
+ class WaterfallManifestTests(unittest.TestCase):
159
+
160
+ def test_24_query_entries_appended(self):
161
+ with _WaterfallHarness() as h:
162
+ fetch_dc._run_waterfall(h.ctx)
163
+ names = [q["name"] for q in h.ctx["queries"]]
164
+ # Exactly the 24 templates fetch_dc declares (no duplicates from
165
+ # tag_definitions retry: synthetic fixture's tag_definitions is
166
+ # empty, so retry runs once and overwrites the prior empty entry).
167
+ self.assertGreaterEqual(len(names), 24)
168
+ # Critical names appear at least once
169
+ for required in ("sessions", "interactions", "steps", "participants",
170
+ "gateway_requests", "generations", "messages"):
171
+ self.assertIn(required, names)
172
+
173
+ def test_each_query_has_wave_status_rows_elapsed_ms(self):
174
+ with _WaterfallHarness() as h:
175
+ fetch_dc._run_waterfall(h.ctx)
176
+ for q in h.ctx["queries"]:
177
+ self.assertIn("name", q)
178
+ self.assertIn("wave", q)
179
+ self.assertIn("rows", q)
180
+ self.assertIn("status", q)
181
+ self.assertIn("elapsed_ms", q)
182
+ self.assertIn(q["status"], ("ok", "empty", "skipped", "error"))
183
+
184
+
185
+ # -----------------------------------------------------------------------------
186
+ # harvested_ids
187
+ # -----------------------------------------------------------------------------
188
+
189
+
190
+ class WaterfallHarvestedIdsTests(unittest.TestCase):
191
+
192
+ def test_harvested_ids_match_fixture_counts(self):
193
+ with _WaterfallHarness() as h:
194
+ fetch_dc._run_waterfall(h.ctx)
195
+ harvested = h.ctx["harvested_ids"]
196
+ self.assertEqual(harvested["sessions"], 1)
197
+ self.assertEqual(harvested["interactions"], 3)
198
+ self.assertEqual(harvested["steps_total"], 3)
199
+ self.assertEqual(harvested["gateway_request_ids"], 2)
200
+ # moments DMO is empty; fetch_dc falls through to the participants
201
+ # harvest (AGENT participant row carries IDS.AGENT_API = "DemoAgent").
202
+ from .fixtures.synthetic_session import IDS # type: ignore
203
+ self.assertEqual(harvested["agents_observed"], [IDS.AGENT_API])
204
+
205
+ def test_steps_by_type_categorizes_correctly(self):
206
+ with _WaterfallHarness() as h:
207
+ fetch_dc._run_waterfall(h.ctx)
208
+ sbt = h.ctx["harvested_ids"]["steps_by_type"]
209
+ # Synthetic fixture has 1 each of TOPIC_STEP, ACTION_STEP, TRUST_GUARDRAILS_STEP
210
+ self.assertEqual(sbt["ACTION_STEP"], 1)
211
+ self.assertEqual(sbt["TOPIC_STEP"], 1)
212
+ self.assertEqual(sbt["TRUST_GUARDRAILS_STEP"], 1)
213
+ self.assertEqual(sbt["LLM_STEP"], 0)
214
+
215
+
216
+ # -----------------------------------------------------------------------------
217
+ # Session-shape classification
218
+ # -----------------------------------------------------------------------------
219
+
220
+
221
+ class WaterfallSessionShapeTests(unittest.TestCase):
222
+
223
+ def test_normal_session_classified_as_complete(self):
224
+ with _WaterfallHarness() as h:
225
+ fetch_dc._run_waterfall(h.ctx)
226
+ self.assertEqual(h.ctx["session_shape"], "complete")
227
+
228
+ def test_no_steps_with_gateway_requests_classified_as_lag(self):
229
+ # Override: empty interactions/steps/messages but keep gateway_requests.
230
+ rows = make_rows()
231
+ rows["interactions"] = []
232
+ rows["steps"] = []
233
+ rows["messages"] = []
234
+ with _WaterfallHarness(rows_overrides=rows) as h:
235
+ fetch_dc._run_waterfall(h.ctx)
236
+ self.assertEqual(h.ctx["session_shape"], "interactions_not_materialized_yet")
237
+
238
+ def test_steps_no_llm_no_gateway_classified_as_abandoned(self):
239
+ # ACTION_STEP + TOPIC_STEP exist but no LLM_STEP and no gateway_requests.
240
+ rows = make_rows()
241
+ rows["gateway_requests"] = []
242
+ rows["gateway_responses"] = []
243
+ rows["gateway_records"] = []
244
+ rows["gateway_request_tags"] = []
245
+ with _WaterfallHarness(rows_overrides=rows) as h:
246
+ fetch_dc._run_waterfall(h.ctx)
247
+ self.assertEqual(h.ctx["session_shape"], "abandoned_before_llm")
248
+
249
+
250
+ # -----------------------------------------------------------------------------
251
+ # Empty harvest branches (zero IDs → _fetch_empty path)
252
+ # -----------------------------------------------------------------------------
253
+
254
+
255
+ class WaterfallEmptyHarvestTests(unittest.TestCase):
256
+
257
+ def test_no_interactions_skips_steps_via_fetch_empty(self):
258
+ rows = make_rows()
259
+ rows["interactions"] = []
260
+ with _WaterfallHarness(rows_overrides=rows) as h:
261
+ fetch_dc._run_waterfall(h.ctx)
262
+ # The steps query should be in the manifest as 'skipped'.
263
+ steps_entries = [q for q in h.ctx["queries"] if q["name"] == "steps"]
264
+ self.assertEqual(len(steps_entries), 1)
265
+ self.assertEqual(steps_entries[0]["status"], "skipped")
266
+ self.assertIn("no interactions", steps_entries[0]["_unavailable_reason"])
267
+
268
+ def test_no_gateway_requests_skips_audit_chain(self):
269
+ rows = make_rows()
270
+ rows["gateway_requests"] = []
271
+ with _WaterfallHarness(rows_overrides=rows) as h:
272
+ fetch_dc._run_waterfall(h.ctx)
273
+ # gateway_responses + tags + records + metadata + llm all skipped
274
+ for name in ("gateway_responses", "gateway_request_tags",
275
+ "gateway_records", "gateway_request_metadata",
276
+ "gateway_request_llm"):
277
+ entries = [q for q in h.ctx["queries"] if q["name"] == name]
278
+ self.assertEqual(entries[0]["status"], "skipped",
279
+ f"{name} should be skipped")
280
+
281
+
282
+ if __name__ == "__main__":
283
+ unittest.main()
@@ -0,0 +1,327 @@
1
+ """Top-level vs nested identity coherence under manifest-placeholder shapes.
2
+
3
+ Regression coverage for the bug where ``fetch_dc._resolve_identity`` falls
4
+ through its strict AGENT-participant pick (fetch_dc.py:570-597) on agent
5
+ shapes like MyAgent — leaving the manifest with
6
+ ``agent_version="v0"`` (placeholder) — while wave 5 then materializes
7
+ ``gateway_request_tags`` rows that carry the real ``v24``. The harvester
8
+ ``_build_session_identity`` correctly reads ``v24`` into
9
+ ``session.identity.agent_version``, but the top-level ``identity`` block
10
+ was previously copied straight off the manifest and silently disagreed
11
+ with the same JSON file's nested copy.
12
+
13
+ These tests pin the promotion policy implemented by
14
+ ``_promote_identity`` / ``_reconcile_top_identity``:
15
+
16
+ - manifest "v0" + session "v24" -> top promotes to v24
17
+ - manifest "v3" + session None -> top stays v3 (manifest wins)
18
+ - manifest api None + session real -> top promotes
19
+ - manifest "v3" + session "v5" -> top stays v3 (manifest wins;
20
+ strict AGENT pick is intentional)
21
+ - manifest "v0" + session "v0" -> top stays v0 (no real value
22
+ available; placeholder leak
23
+ remains visible to investigator)
24
+
25
+ Plus a full round-trip through ``assemble_dc.assemble`` to confirm the
26
+ top-level and nested identity records agree on a real MyAgent-shape
27
+ session.
28
+ """
29
+ from __future__ import annotations
30
+
31
+ import json
32
+ import unittest
33
+ from copy import deepcopy
34
+ from pathlib import Path
35
+ from tempfile import TemporaryDirectory
36
+ from unittest import mock
37
+
38
+ from . import _bootstrap # noqa: F401 — sys.path setup
39
+
40
+ import assemble_dc # type: ignore
41
+ from config import paths # type: ignore
42
+ from .fixtures.synthetic_session import ( # type: ignore
43
+ IDS, make_manifest, make_rows, write_to_disk,
44
+ )
45
+
46
+
47
+ # -----------------------------------------------------------------------------
48
+ # Unit tests for _promote_identity (single-field policy)
49
+ # -----------------------------------------------------------------------------
50
+
51
+
52
+ class PromoteVersionTests(unittest.TestCase):
53
+ """`_promote_identity(..., kind="version")` — placeholder-only promotion."""
54
+
55
+ def test_v0_placeholder_promoted_when_session_has_real_version(self):
56
+ out = assemble_dc._promote_identity("v0", "v24", kind="version")
57
+ self.assertEqual(out, "v24")
58
+
59
+ def test_keeps_manifest_value_when_session_identity_is_none(self):
60
+ # Manifest already real; no reason to look elsewhere.
61
+ out = assemble_dc._promote_identity("v3", None, kind="version")
62
+ self.assertEqual(out, "v3")
63
+
64
+ def test_keeps_v0_when_session_also_returns_v0(self):
65
+ # No real value harvestable. Placeholder remains visible so
66
+ # the investigator can still see the resolution failed.
67
+ out = assemble_dc._promote_identity("v0", "v0", kind="version")
68
+ self.assertEqual(out, "v0")
69
+
70
+ def test_keeps_v0_when_session_value_is_garbage(self):
71
+ # Session shouldn't be able to "promote" a non-version-shaped string.
72
+ out = assemble_dc._promote_identity("v0", "garbage", kind="version")
73
+ self.assertEqual(out, "v0")
74
+
75
+ def test_keeps_v0_when_session_value_is_none(self):
76
+ out = assemble_dc._promote_identity("v0", None, kind="version")
77
+ self.assertEqual(out, "v0")
78
+
79
+ def test_no_promotion_when_both_real_but_disagree(self):
80
+ # Strict AGENT-row pick is intentional — manifest wins.
81
+ out = assemble_dc._promote_identity("v3", "v5", kind="version")
82
+ self.assertEqual(out, "v3")
83
+
84
+
85
+ class PromoteApiNameTests(unittest.TestCase):
86
+ """`_promote_identity(..., kind="api_name")` — NOT_SET-ish promotion."""
87
+
88
+ def test_none_manifest_promoted_when_session_has_value(self):
89
+ out = assemble_dc._promote_identity(
90
+ None, "MyAgent", kind="api_name",
91
+ )
92
+ self.assertEqual(out, "MyAgent")
93
+
94
+ def test_not_set_string_promoted_when_session_has_value(self):
95
+ out = assemble_dc._promote_identity(
96
+ "NOT_SET", "MyAgent", kind="api_name",
97
+ )
98
+ self.assertEqual(out, "MyAgent")
99
+
100
+ def test_empty_manifest_promoted_when_session_has_value(self):
101
+ out = assemble_dc._promote_identity("", "MyAgent", kind="api_name")
102
+ self.assertEqual(out, "MyAgent")
103
+
104
+ def test_keeps_manifest_value_when_session_is_none(self):
105
+ # Symmetric of the version case — manifest wins when session
106
+ # has nothing to offer.
107
+ out = assemble_dc._promote_identity(
108
+ "MyAgent", None, kind="api_name",
109
+ )
110
+ self.assertEqual(out, "MyAgent")
111
+
112
+ def test_keeps_manifest_when_both_real(self):
113
+ out = assemble_dc._promote_identity(
114
+ "ManifestAgent", "SessionAgent", kind="api_name",
115
+ )
116
+ self.assertEqual(out, "ManifestAgent")
117
+
118
+
119
+ # -----------------------------------------------------------------------------
120
+ # Reconcile helper — composed policy across both fields + stderr note
121
+ # -----------------------------------------------------------------------------
122
+
123
+
124
+ class ReconcileTopIdentityTests(unittest.TestCase):
125
+
126
+ def test_reconcile_emits_stderr_note_when_version_promoted(self):
127
+ manifest = {"agent_api_name": "MyAgent", "agent_version": "v0"}
128
+ session_identity = {
129
+ "agent_api_name": None,
130
+ "agent_version": "v24",
131
+ }
132
+ with mock.patch("sys.stderr") as fake_stderr:
133
+ top = assemble_dc._reconcile_top_identity(
134
+ manifest, session_identity, "00DXX0000000ABC",
135
+ )
136
+ self.assertEqual(top["agent_version"], "v24")
137
+ self.assertEqual(top["agent_api_name"], "MyAgent")
138
+ self.assertEqual(top["org_id_15"], "00DXX0000000ABC")
139
+ # The stderr note format is part of the contract — at least
140
+ # one print call mentioning "promoted" must fire.
141
+ emitted = [
142
+ call.args[0] for call in fake_stderr.write.call_args_list
143
+ if call.args
144
+ ]
145
+ self.assertTrue(
146
+ any("promoted" in str(s) for s in emitted),
147
+ f"expected promotion note on stderr; got writes: {emitted}",
148
+ )
149
+
150
+ def test_reconcile_silent_when_no_promotion_needed(self):
151
+ manifest = {"agent_api_name": "DemoAgent", "agent_version": "v5"}
152
+ session_identity = {
153
+ "agent_api_name": "DemoAgent",
154
+ "agent_version": "v5",
155
+ }
156
+ with mock.patch("sys.stderr") as fake_stderr:
157
+ top = assemble_dc._reconcile_top_identity(
158
+ manifest, session_identity, "00DXX0000000ABC",
159
+ )
160
+ self.assertEqual(top["agent_version"], "v5")
161
+ emitted = [
162
+ call.args[0] for call in fake_stderr.write.call_args_list
163
+ if call.args
164
+ ]
165
+ self.assertFalse(
166
+ any("promoted" in str(s) for s in emitted),
167
+ f"unexpected promotion note: {emitted}",
168
+ )
169
+
170
+
171
+ # -----------------------------------------------------------------------------
172
+ # Full-fixture round-trip — MyAgent-shape session through assemble()
173
+ # -----------------------------------------------------------------------------
174
+
175
+
176
+ def _serviceagent2_shape_disk_writer(tmp_root: Path) -> Path:
177
+ """Materialize a MyAgent-shape session under tmp_root.
178
+
179
+ Mutates the synthetic fixture so that:
180
+
181
+ - manifest carries the placeholder ``agent_version="v0"`` (mirrors
182
+ what ``fetch_dc._resolve_identity``'s fallback stamps).
183
+ - the AGENT participant row has its ``ssot__AiAgentVersionApiName__c``
184
+ cleared to NOT_SET (so ``_build_session_identity`` doesn't pick up
185
+ a version from there).
186
+ - ``gateway_request_tags`` carries an ``agent_version_api_name=v24``
187
+ tag against the declared GW request, so the harvester finds the
188
+ real value.
189
+
190
+ The session dir lives under ``<org>/<api_name>__v0/<sid>/`` to match the
191
+ placeholder-stamped layout fetch_dc actually writes on this path.
192
+ """
193
+ rows = make_rows()
194
+
195
+ # AGENT row: clear version so _build_session_identity has no hint
196
+ # via that path; the harvest must come from gateway_request_tags.
197
+ for p in rows["participants"]:
198
+ if p.get("ssot__AiAgentSessionParticipantRole__c") == "AGENT":
199
+ p["ssot__AiAgentVersionApiName__c"] = "NOT_SET"
200
+
201
+ # Add the agent_version_api_name tag against the declared GW request.
202
+ rows["gateway_request_tags"].append({
203
+ "id__c": "tag-agent-version",
204
+ "parent__c": IDS.GW_REQ_DECLARED,
205
+ "tag__c": "agent_version_api_name",
206
+ "tagValue__c": '"v24"', # quoted form mirrors live shape
207
+ })
208
+ # Plus agent_developer_name so session.identity.agent_api_name
209
+ # also gets a real value to test the api_name path.
210
+ rows["gateway_request_tags"].append({
211
+ "id__c": "tag-agent-dev",
212
+ "parent__c": IDS.GW_REQ_DECLARED,
213
+ "tag__c": "agent_developer_name",
214
+ "tagValue__c": '"MyAgent"',
215
+ })
216
+
217
+ # Manifest: placeholder version, real api_name (matches the
218
+ # MyAgent fallback's actual output — it stamps api_name from
219
+ # any participant row but version is forced to "v0").
220
+ manifest = make_manifest()
221
+ manifest["agent_api_name"] = "MyAgent"
222
+ manifest["agent_version"] = "v0"
223
+ manifest["org_id_15"] = IDS.ORG_ID_15
224
+
225
+ # Layout: <org>/<api_name>__v0/<sid>/ — the placeholder-shaped dir.
226
+ sdir = tmp_root / IDS.ORG_ID_15 / "MyAgent__v0" / IDS.SID
227
+ sdir.mkdir(parents=True, exist_ok=True)
228
+
229
+ (sdir / "dc._session_manifest.json").write_text(
230
+ json.dumps(manifest, indent=2) + "\n"
231
+ )
232
+ for name, rs in rows.items():
233
+ if rs:
234
+ (sdir / f"dc.{name}.json").write_text(
235
+ json.dumps(rs, indent=2) + "\n"
236
+ )
237
+
238
+ # Breadcrumb for _find_session_dir's primary path.
239
+ link_dir = tmp_root / IDS.ORG_ID_15 / "_sessions"
240
+ link_dir.mkdir(parents=True, exist_ok=True)
241
+ (link_dir / f"{IDS.SID}.link").write_text(
242
+ f"../MyAgent__v0/{IDS.SID}\n"
243
+ )
244
+ return sdir
245
+
246
+
247
+ class AssembleEndToEndIdentityCoherenceTests(unittest.TestCase):
248
+ """The bug repro — top-level vs nested identity must agree after fix."""
249
+
250
+ def test_top_identity_promoted_when_manifest_has_v0_and_tags_have_v24(self):
251
+ with TemporaryDirectory() as tmp:
252
+ tmp_root = Path(tmp)
253
+ _serviceagent2_shape_disk_writer(tmp_root)
254
+ with mock.patch.object(paths, "DATA_ROOT", tmp_root):
255
+ tree, _ = assemble_dc.assemble(IDS.SID)
256
+ # The harvester (_build_session_identity) reads v24 from tags.
257
+ self.assertEqual(tree["session"]["identity"]["agent_version"], "v24")
258
+ # The top-level — previously v0 — must now agree.
259
+ self.assertEqual(tree["identity"]["agent_version"], "v24")
260
+
261
+ def test_top_and_session_identity_agree_on_agent_version(self):
262
+ with TemporaryDirectory() as tmp:
263
+ tmp_root = Path(tmp)
264
+ _serviceagent2_shape_disk_writer(tmp_root)
265
+ with mock.patch.object(paths, "DATA_ROOT", tmp_root):
266
+ tree, _ = assemble_dc.assemble(IDS.SID)
267
+ # The whole point of the fix.
268
+ self.assertEqual(
269
+ tree["identity"]["agent_version"],
270
+ tree["session"]["identity"]["agent_version"],
271
+ )
272
+
273
+ def test_api_name_top_level_keeps_manifest_when_session_returns_null(self):
274
+ # Mirror the live live-verification observation: top-level
275
+ # api_name is "MyAgent" (from manifest), session.identity
276
+ # api_name is None because no agent_developer_name tag fired.
277
+ # Strip the agent_developer_name tag we added in the helper.
278
+ with TemporaryDirectory() as tmp:
279
+ tmp_root = Path(tmp)
280
+ _serviceagent2_shape_disk_writer(tmp_root)
281
+ sdir = tmp_root / IDS.ORG_ID_15 / "MyAgent__v0" / IDS.SID
282
+ tags_path = sdir / "dc.gateway_request_tags.json"
283
+ tags = json.loads(tags_path.read_text())
284
+ tags = [t for t in tags if t.get("tag__c") != "agent_developer_name"]
285
+ tags_path.write_text(json.dumps(tags, indent=2) + "\n")
286
+
287
+ with mock.patch.object(paths, "DATA_ROOT", tmp_root):
288
+ tree, _ = assemble_dc.assemble(IDS.SID)
289
+ # session.identity.agent_api_name None — no harvestable value.
290
+ self.assertIsNone(tree["session"]["identity"]["agent_api_name"])
291
+ # Top-level keeps manifest's "MyAgent" — promotion does NOT
292
+ # erase a real manifest value when session has nothing to offer.
293
+ self.assertEqual(tree["identity"]["agent_api_name"], "MyAgent")
294
+
295
+
296
+ # -----------------------------------------------------------------------------
297
+ # Sanity: the existing happy-path fixture still produces matching identity
298
+ # (regression guard against the fix mutating the simple case).
299
+ # -----------------------------------------------------------------------------
300
+
301
+
302
+ class HappyPathStillCoherentTests(unittest.TestCase):
303
+
304
+ def test_happy_path_unchanged(self):
305
+ # The synthetic fixture's gateway_request_tags don't include
306
+ # `agent_version_api_name`, so session.identity.agent_version is
307
+ # None on this path. The fix's policy preserves the manifest
308
+ # value ("v5") in that case — verifying the no-promote branch
309
+ # doesn't accidentally null out the top-level when the harvest
310
+ # layer has nothing to offer.
311
+ with TemporaryDirectory() as tmp:
312
+ tmp_root = Path(tmp)
313
+ write_to_disk(tmp_root)
314
+ with mock.patch.object(paths, "DATA_ROOT", tmp_root):
315
+ tree, _ = assemble_dc.assemble(IDS.SID)
316
+ self.assertEqual(tree["identity"]["agent_version"], IDS.AGENT_VERSION)
317
+ self.assertEqual(tree["identity"]["agent_api_name"], IDS.AGENT_API)
318
+ # session.identity may be None here (no agent_version_api_name tag);
319
+ # the top-level keeps the manifest's real value, which is the point.
320
+ self.assertIn(
321
+ tree["session"]["identity"]["agent_version"],
322
+ (None, IDS.AGENT_VERSION),
323
+ )
324
+
325
+
326
+ if __name__ == "__main__":
327
+ unittest.main()