@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.
- package/package.json +1 -1
- package/skills/activating-datacloud/SKILL.md +0 -1
- package/skills/analyzing-omnistudio-dependencies/SKILL.md +0 -1
- package/skills/applying-slds/SKILL.md +322 -0
- package/skills/applying-slds/checklists.md +83 -0
- package/skills/applying-slds/examples.md +283 -0
- package/skills/applying-slds/guidance/README.md +83 -0
- package/skills/applying-slds/guidance/blueprints-index.md +213 -0
- package/skills/applying-slds/guidance/icons-guidance.md +186 -0
- package/skills/applying-slds/guidance/overviews/borders.md +236 -0
- package/skills/applying-slds/guidance/overviews/color.md +266 -0
- package/skills/applying-slds/guidance/overviews/display-density.md +366 -0
- package/skills/applying-slds/guidance/overviews/icons.md +240 -0
- package/skills/applying-slds/guidance/overviews/illustrations.md +235 -0
- package/skills/applying-slds/guidance/overviews/shadows.md +176 -0
- package/skills/applying-slds/guidance/overviews/spacing.md +216 -0
- package/skills/applying-slds/guidance/overviews/typography.md +323 -0
- package/skills/applying-slds/guidance/overviews/utilities.md +542 -0
- package/skills/applying-slds/guidance/slds-development-guide.md +288 -0
- package/skills/applying-slds/guidance/styling-hooks/borders.md +202 -0
- package/skills/applying-slds/guidance/styling-hooks/color/expressive-palette-hooks.md +153 -0
- package/skills/applying-slds/guidance/styling-hooks/color/index.md +171 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/accent-hooks.md +204 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/feedback-hooks.md +768 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/surface-hooks.md +337 -0
- package/skills/applying-slds/guidance/styling-hooks/color/system-hooks.md +132 -0
- package/skills/applying-slds/guidance/styling-hooks/index.md +327 -0
- package/skills/applying-slds/guidance/styling-hooks/shadows.md +238 -0
- package/skills/applying-slds/guidance/styling-hooks/spacing.md +254 -0
- package/skills/applying-slds/guidance/styling-hooks/typography.md +448 -0
- package/skills/applying-slds/guidance/utilities/alignment.md +119 -0
- package/skills/applying-slds/guidance/utilities/borders.md +131 -0
- package/skills/applying-slds/guidance/utilities/box.md +125 -0
- package/skills/applying-slds/guidance/utilities/color.md +165 -0
- package/skills/applying-slds/guidance/utilities/dark-mode.md +111 -0
- package/skills/applying-slds/guidance/utilities/description-list.md +168 -0
- package/skills/applying-slds/guidance/utilities/floats.md +117 -0
- package/skills/applying-slds/guidance/utilities/grid.md +264 -0
- package/skills/applying-slds/guidance/utilities/horizontal-list.md +110 -0
- package/skills/applying-slds/guidance/utilities/hyphenation.md +84 -0
- package/skills/applying-slds/guidance/utilities/index.md +205 -0
- package/skills/applying-slds/guidance/utilities/interactions.md +89 -0
- package/skills/applying-slds/guidance/utilities/layout.md +109 -0
- package/skills/applying-slds/guidance/utilities/line-clamp.md +131 -0
- package/skills/applying-slds/guidance/utilities/margin.md +155 -0
- package/skills/applying-slds/guidance/utilities/media-object.md +161 -0
- package/skills/applying-slds/guidance/utilities/name-value-list.md +152 -0
- package/skills/applying-slds/guidance/utilities/padding.md +155 -0
- package/skills/applying-slds/guidance/utilities/position.md +177 -0
- package/skills/applying-slds/guidance/utilities/print.md +114 -0
- package/skills/applying-slds/guidance/utilities/scrollable.md +126 -0
- package/skills/applying-slds/guidance/utilities/sizing.md +190 -0
- package/skills/applying-slds/guidance/utilities/themes.md +121 -0
- package/skills/applying-slds/guidance/utilities/truncate.md +127 -0
- package/skills/applying-slds/guidance/utilities/typography.md +166 -0
- package/skills/applying-slds/guidance/utilities/vertical-list.md +166 -0
- package/skills/applying-slds/guidance/utilities/visibility.md +228 -0
- package/skills/applying-slds/metadata/README.md +84 -0
- package/skills/applying-slds/metadata/blueprints/components/accordion.yaml +304 -0
- package/skills/applying-slds/metadata/blueprints/components/activity-timeline.yaml +92 -0
- package/skills/applying-slds/metadata/blueprints/components/alert.yaml +103 -0
- package/skills/applying-slds/metadata/blueprints/components/app-launcher.yaml +94 -0
- package/skills/applying-slds/metadata/blueprints/components/avatar-group.yaml +81 -0
- package/skills/applying-slds/metadata/blueprints/components/avatar.yaml +97 -0
- package/skills/applying-slds/metadata/blueprints/components/badges.yaml +102 -0
- package/skills/applying-slds/metadata/blueprints/components/brand-band.yaml +198 -0
- package/skills/applying-slds/metadata/blueprints/components/breadcrumbs.yaml +95 -0
- package/skills/applying-slds/metadata/blueprints/components/builder-header.yaml +192 -0
- package/skills/applying-slds/metadata/blueprints/components/button-groups.yaml +82 -0
- package/skills/applying-slds/metadata/blueprints/components/button-icons.yaml +295 -0
- package/skills/applying-slds/metadata/blueprints/components/buttons.yaml +230 -0
- package/skills/applying-slds/metadata/blueprints/components/cards.yaml +124 -0
- package/skills/applying-slds/metadata/blueprints/components/carousel.yaml +140 -0
- package/skills/applying-slds/metadata/blueprints/components/chat.yaml +179 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-button-group.yaml +192 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-button.yaml +204 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-toggle.yaml +177 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox.yaml +108 -0
- package/skills/applying-slds/metadata/blueprints/components/color-picker.yaml +172 -0
- package/skills/applying-slds/metadata/blueprints/components/combobox.yaml +136 -0
- package/skills/applying-slds/metadata/blueprints/components/counter.yaml +147 -0
- package/skills/applying-slds/metadata/blueprints/components/data-tables.yaml +157 -0
- package/skills/applying-slds/metadata/blueprints/components/datepickers.yaml +130 -0
- package/skills/applying-slds/metadata/blueprints/components/datetime-picker.yaml +155 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-composer.yaml +201 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-form-footer.yaml +161 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-utility-bar.yaml +175 -0
- package/skills/applying-slds/metadata/blueprints/components/drop-zone.yaml +115 -0
- package/skills/applying-slds/metadata/blueprints/components/dueling-picklist.yaml +196 -0
- package/skills/applying-slds/metadata/blueprints/components/dynamic-icons.yaml +128 -0
- package/skills/applying-slds/metadata/blueprints/components/dynamic-menu.yaml +141 -0
- package/skills/applying-slds/metadata/blueprints/components/expandable-section.yaml +115 -0
- package/skills/applying-slds/metadata/blueprints/components/expression.yaml +143 -0
- package/skills/applying-slds/metadata/blueprints/components/feeds.yaml +125 -0
- package/skills/applying-slds/metadata/blueprints/components/file-selector.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/files.yaml +119 -0
- package/skills/applying-slds/metadata/blueprints/components/form-element.yaml +145 -0
- package/skills/applying-slds/metadata/blueprints/components/global-header.yaml +120 -0
- package/skills/applying-slds/metadata/blueprints/components/global-navigation.yaml +100 -0
- package/skills/applying-slds/metadata/blueprints/components/icons.yaml +138 -0
- package/skills/applying-slds/metadata/blueprints/components/illustration.yaml +205 -0
- package/skills/applying-slds/metadata/blueprints/components/input.yaml +151 -0
- package/skills/applying-slds/metadata/blueprints/components/list-builder.yaml +127 -0
- package/skills/applying-slds/metadata/blueprints/components/lookups.yaml +132 -0
- package/skills/applying-slds/metadata/blueprints/components/map.yaml +118 -0
- package/skills/applying-slds/metadata/blueprints/components/menus.yaml +134 -0
- package/skills/applying-slds/metadata/blueprints/components/modals.yaml +152 -0
- package/skills/applying-slds/metadata/blueprints/components/notifications.yaml +88 -0
- package/skills/applying-slds/metadata/blueprints/components/page-headers.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/panels.yaml +149 -0
- package/skills/applying-slds/metadata/blueprints/components/path.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/picklist.yaml +125 -0
- package/skills/applying-slds/metadata/blueprints/components/pills.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/popovers.yaml +120 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-bar.yaml +110 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-indicator.yaml +133 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-ring.yaml +102 -0
- package/skills/applying-slds/metadata/blueprints/components/prompt.yaml +126 -0
- package/skills/applying-slds/metadata/blueprints/components/publishers.yaml +178 -0
- package/skills/applying-slds/metadata/blueprints/components/radio-button-group.yaml +172 -0
- package/skills/applying-slds/metadata/blueprints/components/radio-group.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/rich-text-editor.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/scoped-notifications.yaml +188 -0
- package/skills/applying-slds/metadata/blueprints/components/scoped-tabs.yaml +97 -0
- package/skills/applying-slds/metadata/blueprints/components/select.yaml +127 -0
- package/skills/applying-slds/metadata/blueprints/components/setup-assistant.yaml +152 -0
- package/skills/applying-slds/metadata/blueprints/components/slider.yaml +111 -0
- package/skills/applying-slds/metadata/blueprints/components/spinners.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/split-view.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/summary-detail.yaml +103 -0
- package/skills/applying-slds/metadata/blueprints/components/tabs.yaml +138 -0
- package/skills/applying-slds/metadata/blueprints/components/textarea.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/tiles.yaml +108 -0
- package/skills/applying-slds/metadata/blueprints/components/timepicker.yaml +111 -0
- package/skills/applying-slds/metadata/blueprints/components/toast.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/tooltips.yaml +107 -0
- package/skills/applying-slds/metadata/blueprints/components/tree-grid.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/trees.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/trial-bar.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/vertical-navigation.yaml +130 -0
- package/skills/applying-slds/metadata/blueprints/components/vertical-tabs.yaml +140 -0
- package/skills/applying-slds/metadata/blueprints/components/visual-picker.yaml +150 -0
- package/skills/applying-slds/metadata/blueprints/components/welcome-mat.yaml +136 -0
- package/skills/applying-slds/metadata/hooks-index.json +6272 -0
- package/skills/applying-slds/metadata/icon-metadata.json +38466 -0
- package/skills/applying-slds/metadata/utilities-index.json +21912 -0
- package/skills/applying-slds/references/component-selection.md +112 -0
- package/skills/applying-slds/references/icons-decision-guide.md +124 -0
- package/skills/applying-slds/references/styling-decision-guide.md +228 -0
- package/skills/applying-slds/references/utilities-quick-ref.md +125 -0
- package/skills/applying-slds/scripts/search-blueprints.cjs +117 -0
- package/skills/applying-slds/scripts/search-hooks.cjs +139 -0
- package/skills/applying-slds/scripts/search-icons.cjs +174 -0
- package/skills/applying-slds/scripts/search-utilities.cjs +161 -0
- package/skills/building-mobile-apps/SKILL.md +0 -1
- package/skills/building-omnistudio-callable-apex/SKILL.md +0 -1
- package/skills/building-omnistudio-datamapper/SKILL.md +0 -1
- package/skills/building-omnistudio-flexcard/SKILL.md +0 -1
- package/skills/building-omnistudio-integration-procedure/SKILL.md +0 -1
- package/skills/building-omnistudio-omniscript/SKILL.md +0 -1
- package/skills/building-sf-integrations/SKILL.md +0 -1
- package/skills/configuring-connected-apps/SKILL.md +0 -1
- package/skills/connecting-datacloud/SKILL.md +0 -1
- package/skills/creating-b2b-commerce-store/SKILL.md +0 -1
- package/skills/debugging-apex-logs/SKILL.md +0 -1
- package/skills/deploying-metadata/SKILL.md +0 -1
- package/skills/deploying-omnistudio-datapacks/SKILL.md +0 -1
- package/skills/developing-agentforce/SKILL.md +0 -1
- package/skills/fetching-salesforce-docs/SKILL.md +0 -1
- package/skills/generating-custom-lightning-type/SKILL.md +17 -39
- package/skills/generating-custom-lightning-type/assets/primitive-types-and-constraints.md +41 -0
- package/skills/generating-custom-lightning-type/references/widget-rendition.md +124 -0
- package/skills/generating-lwc-components/SKILL.md +0 -1
- package/skills/generating-mermaid-diagrams/SKILL.md +0 -1
- package/skills/generating-visual-diagrams/SKILL.md +0 -1
- package/skills/handling-sf-data/SKILL.md +0 -1
- package/skills/harmonizing-datacloud/SKILL.md +0 -1
- package/skills/integrating-b2b-commerce-open-code-components/SKILL.md +0 -1
- package/skills/investigating-agentforce-architecture/README.md +156 -0
- package/skills/investigating-agentforce-architecture/SKILL.md +230 -0
- package/skills/investigating-agentforce-architecture/assets/cli/describe_sobject.yaml +16 -0
- package/skills/investigating-agentforce-architecture/assets/cli/describe_tooling_sobject.yaml +17 -0
- package/skills/investigating-agentforce-architecture/assets/cli/list_metadata_genaiprompttemplate.yaml +17 -0
- package/skills/investigating-agentforce-architecture/assets/cli/org_display.yaml +15 -0
- package/skills/investigating-agentforce-architecture/assets/cli/retrieve_genai_plugin.yaml +18 -0
- package/skills/investigating-agentforce-architecture/assets/cli/show_access_token.yaml +27 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/action_tree.mmd +20 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/data_flow.mmd +19 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/dependency_graph.mmd +19 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/invocation_sequence.mmd +20 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/planner_state.mmd +18 -0
- package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_names.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/bot_definition_details.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/bot_version_lookup.soql +4 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_by_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_ids_by_names.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_view_by_durable_ids.soql +4 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_metadata_by_id.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/functions_by_plugins.soql +5 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_attrs_by_parent_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_bundle_functions.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_definition_by_agent_chain.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugin_functions_by_plugin_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugin_instructions_by_plugin_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugins_by_planner.soql +4 -0
- package/skills/investigating-agentforce-architecture/references/architecture_sections.md +243 -0
- package/skills/investigating-agentforce-architecture/references/contract.json +244 -0
- package/skills/investigating-agentforce-architecture/references/soql_fields.md +512 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/__init__.py +1 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/fs_guard.py +329 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/paths.py +110 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/runtime.py +59 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/sql.py +10 -0
- package/skills/investigating-agentforce-architecture/scripts/cache_check.py +234 -0
- package/skills/investigating-agentforce-architecture/scripts/config.py +131 -0
- package/skills/investigating-agentforce-architecture/scripts/fetch_soql.py +689 -0
- package/skills/investigating-agentforce-architecture/scripts/finalize.py +295 -0
- package/skills/investigating-agentforce-architecture/scripts/main.py +2835 -0
- package/skills/investigating-agentforce-architecture/scripts/metadata_listing.py +265 -0
- package/skills/investigating-agentforce-architecture/scripts/parallel_retrieve.py +69 -0
- package/skills/investigating-agentforce-architecture/scripts/parse_bundle.py +215 -0
- package/skills/investigating-agentforce-architecture/scripts/parse_wave.py +845 -0
- package/skills/investigating-agentforce-architecture/scripts/probe_channels.py +302 -0
- package/skills/investigating-agentforce-architecture/scripts/render_architecture.py +1043 -0
- package/skills/investigating-agentforce-architecture/scripts/resolve_bot.py +255 -0
- package/skills/investigating-agentforce-architecture/scripts/resolve_invocation_target.py +130 -0
- package/skills/investigating-agentforce-architecture/scripts/rest_client.py +763 -0
- package/skills/investigating-agentforce-architecture/scripts/retrieve_planner.py +13 -0
- package/skills/investigating-agentforce-architecture/scripts/sf_cli.py +242 -0
- package/skills/investigating-agentforce-architecture/scripts/soql_loader.py +253 -0
- package/skills/investigating-agentforce-architecture/scripts/summarize_tree.py +143 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/__init__.py +0 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/_bootstrap.py +23 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/__init__.py +0 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/genai_payloads.py +400 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check.py +307 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check_main.py +283 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_config.py +115 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_end_to_end_fixture.py +651 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_finalize.py +278 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_flow_children_inflation.py +582 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_fs_guard.py +113 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_iterative_wave_b.py +478 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_main_pipeline.py +3359 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parallel_retrieve.py +131 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_bundle.py +400 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave.py +644 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_classifiers.py +224 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_helpers.py +380 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_main.py +397 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_per_branch_visited.py +244 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_channels.py +359 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_cli_recipes.py +185 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_render_architecture.py +810 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_bot.py +203 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_creds.py +157 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_invocation_target.py +145 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_rest_client.py +1253 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_runtime_override.py +100 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_sf_cli.py +261 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_signature_stamping.py +466 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_soql_loader.py +501 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_summarize_tree.py +241 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_write_emit_ctx.py +480 -0
- package/skills/investigating-agentforce-architecture/tools/emit_env.py +157 -0
- package/skills/investigating-agentforce-architecture/tools/emit_result.py +262 -0
- package/skills/investigating-agentforce-architecture/tools/sanitize.py +33 -0
- package/skills/investigating-agentforce-architecture/tools/write_emit_ctx.py +332 -0
- package/skills/investigating-agentforce-d360/README.md +123 -0
- package/skills/investigating-agentforce-d360/SKILL.md +163 -0
- package/skills/investigating-agentforce-d360/assets/dc/app_generation.sql +51 -0
- package/skills/investigating-agentforce-d360/assets/dc/content_category.sql +44 -0
- package/skills/investigating-agentforce-d360/assets/dc/content_quality.sql +41 -0
- package/skills/investigating-agentforce-d360/assets/dc/discover_sessions.sql +36 -0
- package/skills/investigating-agentforce-d360/assets/dc/feedback.sql +47 -0
- package/skills/investigating-agentforce-d360/assets/dc/feedback_details.sql +38 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_records.sql +45 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_llm.sql +50 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_metadata.sql +44 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_tags.sql +42 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_requests.sql +89 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_responses.sql +43 -0
- package/skills/investigating-agentforce-d360/assets/dc/generations.sql +52 -0
- package/skills/investigating-agentforce-d360/assets/dc/interactions.sql +53 -0
- package/skills/investigating-agentforce-d360/assets/dc/messages.sql +53 -0
- package/skills/investigating-agentforce-d360/assets/dc/messaging_session.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/moment_interactions.sql +34 -0
- package/skills/investigating-agentforce-d360/assets/dc/moments.sql +39 -0
- package/skills/investigating-agentforce-d360/assets/dc/participants.sql +48 -0
- package/skills/investigating-agentforce-d360/assets/dc/sessions.sql +78 -0
- package/skills/investigating-agentforce-d360/assets/dc/steps.sql +64 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_associations.sql +46 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_definition_associations.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_definitions.sql +50 -0
- package/skills/investigating-agentforce-d360/assets/dc/tags.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/telemetry_spans.sql +55 -0
- package/skills/investigating-agentforce-d360/references/artifacts.md +50 -0
- package/skills/investigating-agentforce-d360/references/dc_dmo_fields.md +823 -0
- package/skills/investigating-agentforce-d360/references/dc_pipeline_contract.md +608 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/__init__.py +2 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/cli_override.py +98 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/fs_guard.py +334 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/paths.py +155 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/runtime.py +59 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/sql.py +14 -0
- package/skills/investigating-agentforce-d360/scripts/assemble_dc.py +1624 -0
- package/skills/investigating-agentforce-d360/scripts/config.py +45 -0
- package/skills/investigating-agentforce-d360/scripts/dc.py +188 -0
- package/skills/investigating-agentforce-d360/scripts/discover_sessions.py +556 -0
- package/skills/investigating-agentforce-d360/scripts/fetch_dc.py +1045 -0
- package/skills/investigating-agentforce-d360/scripts/render_dc.py +1750 -0
- package/skills/investigating-agentforce-d360/scripts/resolve_session.py +264 -0
- package/skills/investigating-agentforce-d360/scripts/storage.py +92 -0
- package/skills/investigating-agentforce-d360/scripts/tests/__init__.py +0 -0
- package/skills/investigating-agentforce-d360/scripts/tests/_bootstrap.py +15 -0
- package/skills/investigating-agentforce-d360/scripts/tests/fixtures/__init__.py +0 -0
- package/skills/investigating-agentforce-d360/scripts/tests/fixtures/synthetic_session.py +424 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_bootstrap_and_mode.py +115 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct.py +220 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct_integration.py +158 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_helpers.py +287 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_integration.py +247 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_dc_and_resolve_session.py +433 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions.py +458 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions_grep_ci.py +193 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_helpers.py +266 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_identity.py +528 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_main.py +251 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall.py +229 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall_full.py +283 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_identity_coherence.py +327 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_branches.py +256 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_gateway_direct.py +130 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_helpers.py +291 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_integration.py +220 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_planner_llm_calls.py +284 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_show_prompts_gating.py +215 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_from_disk.py +100 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_session_main.py +149 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_runtime_override.py +104 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape.py +95 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape_dropped_by_stdm.py +85 -0
- package/skills/managing-managed-event-subscription/SKILL.md +152 -0
- package/skills/managing-managed-event-subscription/assets/managed-event-subscription-template.xml +20 -0
- package/skills/managing-managed-event-subscription/references/delete-guide.md +57 -0
- package/skills/managing-managed-event-subscription/references/topic-name-formats.md +26 -0
- package/skills/managing-managed-event-subscription/references/update-constraints.md +30 -0
- package/skills/modeling-omnistudio-epc-catalog/SKILL.md +0 -1
- package/skills/observing-agentforce/SKILL.md +0 -1
- package/skills/orchestrating-datacloud/SKILL.md +0 -1
- package/skills/preparing-datacloud/SKILL.md +0 -1
- package/skills/querying-soql/SKILL.md +0 -1
- package/skills/retrieving-datacloud/SKILL.md +0 -1
- package/skills/running-apex-tests/SKILL.md +0 -1
- package/skills/running-code-analyzer/SKILL.md +0 -1
- package/skills/segmenting-datacloud/SKILL.md +0 -1
- package/skills/testing-agentforce/SKILL.md +0 -1
- package/skills/uplifting-components-to-slds2/SKILL.md +3 -2
- package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +30 -9
- package/skills/uplifting-components-to-slds2/references/examples.md +24 -6
- package/skills/validating-slds/SKILL.md +262 -0
- package/skills/validating-slds/references/quality-checks.md +308 -0
- package/skills/validating-slds/references/report-format.md +302 -0
- package/skills/validating-slds/scripts/analyze-quality.cjs +521 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""BFS graph builder — walks the cross-type reference graph.
|
|
2
|
+
|
|
3
|
+
(visited keys = (kind, canonical_name) tuples) + (MAX_DEPTH=5 cap
|
|
4
|
+
→ _partial + PARTIAL_OK) land in Batches 2 and 3.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_fetch_graph() -> dict:
|
|
10
|
+
"""TODO + BFS with per-kind visited tuples, cycle-safe,
|
|
11
|
+
MAX_DEPTH cap surfaces as _partial=true in the tree.
|
|
12
|
+
"""
|
|
13
|
+
raise NotImplementedError("build_fetch_graph implements in P0 Batches 2+3")
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""sf CLI recipe loader — reads assets/cli/*.yaml, runs argv via subprocess.
|
|
2
|
+
|
|
3
|
+
Security: YAML recipes are loaded via yaml.safe_load only; yaml.load is banned.
|
|
4
|
+
PyYAML's `yaml.load` deserializes arbitrary Python objects (including
|
|
5
|
+
constructor-injected code in some tags) and must never run on attacker-reachable
|
|
6
|
+
YAML. The recipes live inside this repo and are author-controlled, but we
|
|
7
|
+
still use `safe_load` as defence in depth — a compromised dist/ or a botched
|
|
8
|
+
copy-paste can't escalate to code execution.
|
|
9
|
+
|
|
10
|
+
the `_SAFE_LOADER` module-level binding is THE loader used everywhere
|
|
11
|
+
in this module; tests assert its identity against `yaml.safe_load`.
|
|
12
|
+
|
|
13
|
+
every stringification of a subprocess.CalledProcessError or related
|
|
14
|
+
exception is routed through `rest_client.redact_error` so stderr containing
|
|
15
|
+
an access token (rare but observed on some `sf` failure paths) cannot leak
|
|
16
|
+
into logs or RESULT blocks.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import subprocess
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any, Dict
|
|
26
|
+
|
|
27
|
+
import yaml
|
|
28
|
+
|
|
29
|
+
from config import CLI_DIR
|
|
30
|
+
# hoisted `redact_text` to module-top alongside
|
|
31
|
+
# `redact_error`. Previously `_redact_subprocess_stderr` performed a lazy
|
|
32
|
+
# import of the module-private `_redact_text` on every call, which (a)
|
|
33
|
+
# tied this module to a name that could be renamed silently and (b) meant
|
|
34
|
+
# an ImportError from rest_client would surface only at redaction time —
|
|
35
|
+
# possibly in a frame whose locals held the raw, un-redacted stderr.
|
|
36
|
+
# Hoisting is safe: rest_client does not import sf_cli (no cycle).
|
|
37
|
+
from rest_client import redact_error, redact_text
|
|
38
|
+
|
|
39
|
+
# the ONE allowed loader for this module. Test asserts identity.
|
|
40
|
+
# NEVER change this to yaml.load (which can construct arbitrary Python
|
|
41
|
+
# objects via tag hooks).
|
|
42
|
+
_SAFE_LOADER = yaml.safe_load
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AuthRequired(RuntimeError):
|
|
46
|
+
"""sf CLI returned a known auth-failure pattern.
|
|
47
|
+
|
|
48
|
+
Carries a redacted stderr snippet — any bearer token in stderr is scrubbed
|
|
49
|
+
via redact_error before reaching this message.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SfCliError(RuntimeError):
|
|
54
|
+
"""sf CLI returned non-zero exit or JSON status != 0.
|
|
55
|
+
|
|
56
|
+
Message is redacted before construction.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _load_recipe(name: str) -> Dict[str, Any]:
|
|
61
|
+
"""Read and parse a CLI recipe file with yaml.safe_load.
|
|
62
|
+
|
|
63
|
+
_SAFE_LOADER is bound to yaml.safe_load at module top; using it
|
|
64
|
+
here (instead of `yaml.safe_load` directly) makes the identity testable
|
|
65
|
+
and gives a single point to review if the loader ever changes.
|
|
66
|
+
"""
|
|
67
|
+
path = CLI_DIR / f"{name}.yaml"
|
|
68
|
+
with path.open("r", encoding="utf-8") as fh:
|
|
69
|
+
recipe = _SAFE_LOADER(fh)
|
|
70
|
+
if not isinstance(recipe, dict):
|
|
71
|
+
raise SfCliError(
|
|
72
|
+
f"recipe {name!r} did not parse to a mapping (got {type(recipe).__name__})"
|
|
73
|
+
)
|
|
74
|
+
return recipe
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _sub(arg: str, params: Dict[str, str]) -> str:
|
|
78
|
+
"""Substitute {{KEY}} placeholders in a single argv element.
|
|
79
|
+
|
|
80
|
+
Input validation: `params` values are expected to be already-validated
|
|
81
|
+
strings (SKILL.md phase 1 runs fs_guard on every user-supplied input).
|
|
82
|
+
We do not revalidate here because argv substitution is shell-escape-safe
|
|
83
|
+
by construction — subprocess.run with a list argv never invokes a shell.
|
|
84
|
+
"""
|
|
85
|
+
out = arg
|
|
86
|
+
for key, value in params.items():
|
|
87
|
+
out = out.replace(f"{{{{{key}}}}}", value)
|
|
88
|
+
return out
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _parse_stdout_json(stdout: str) -> Dict[str, Any]:
|
|
92
|
+
"""Parse sf CLI's --json stdout.
|
|
93
|
+
|
|
94
|
+
sf CLI emits `{"status": 0, "result": {...}}` on success; malformed
|
|
95
|
+
stdout (e.g. when the CLI crashes before JSON serialization) raises
|
|
96
|
+
SfCliError with a redacted message.
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
return json.loads(stdout)
|
|
100
|
+
except json.JSONDecodeError as e:
|
|
101
|
+
# stdout could in theory contain a token on some crash paths;
|
|
102
|
+
# redact before surfacing.
|
|
103
|
+
raise SfCliError(
|
|
104
|
+
f"sf CLI stdout is not JSON: {redact_error(e)}"
|
|
105
|
+
) from None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_AUTH_LINE_PREFIX_RE = re.compile(r"^(?:Error|Warning):\s") # @rule-suppress starter-sec-002 — re.compile, not python compile()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _stderr_matches_auth(stderr: str, patterns) -> bool:
|
|
112
|
+
"""Check if any auth-failure pattern appears on an Error:/Warning: line.
|
|
113
|
+
|
|
114
|
+
previously a substring scan across the entire stderr, which
|
|
115
|
+
false-positived on Node ESM warnings ("Warning: @gthoppae/sf-cli-
|
|
116
|
+
plugin-data360 is a linked ESM module") if they happened to echo an
|
|
117
|
+
auth pattern substring. Now we only match on lines starting with
|
|
118
|
+
`Error:` or `Warning:` followed by whitespace. This keeps every
|
|
119
|
+
true-positive we care about (real sf auth errors always surface as
|
|
120
|
+
`Error: NoOrgAuthenticationError ...`) while pruning the noise.
|
|
121
|
+
|
|
122
|
+
Matching rules:
|
|
123
|
+
* Line starts with `Error:<ws>` or `Warning:<ws>` (anchored at
|
|
124
|
+
start-of-line, so embedded-in-prose mentions don't match).
|
|
125
|
+
* Pattern is a substring of the REST of that line (after the
|
|
126
|
+
prefix). We strip only the prefix — we do not re-scope to word
|
|
127
|
+
boundaries, since sf auth tokens like `NoOrgAuthenticationError`
|
|
128
|
+
are always well-formed identifiers without adjacent alphanum.
|
|
129
|
+
* Empty stderr / empty patterns → no match.
|
|
130
|
+
"""
|
|
131
|
+
if not stderr or not patterns:
|
|
132
|
+
return False
|
|
133
|
+
for line in stderr.splitlines():
|
|
134
|
+
m = _AUTH_LINE_PREFIX_RE.match(line)
|
|
135
|
+
if not m:
|
|
136
|
+
continue
|
|
137
|
+
tail = line[m.end():]
|
|
138
|
+
if any(p in tail for p in patterns):
|
|
139
|
+
return True
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def run_sf(name: str, **params: str) -> Dict[str, Any]:
|
|
144
|
+
"""Execute a recipe-defined sf CLI command and return parsed stdout.
|
|
145
|
+
|
|
146
|
+
Steps:
|
|
147
|
+
1. Load recipe (yaml.safe_load).
|
|
148
|
+
2. Enforce required_params.
|
|
149
|
+
3. Substitute {{KEY}} into argv.
|
|
150
|
+
4. subprocess.run with list argv (no shell, no bash -c).
|
|
151
|
+
5. Parse stdout JSON; check `status == 0`.
|
|
152
|
+
6. On failure: classify as AuthRequired (stderr pattern match) or
|
|
153
|
+
SfCliError; every message is redacted via redact_error .
|
|
154
|
+
|
|
155
|
+
Exception semantics:
|
|
156
|
+
* AuthRequired — user needs to `sf org login`
|
|
157
|
+
* SfCliError — anything else (timeout, non-zero exit, JSON parse,
|
|
158
|
+
status != 0)
|
|
159
|
+
|
|
160
|
+
NEVER uses shell=True. NEVER calls log.exception(). NEVER echoes raw
|
|
161
|
+
stderr without redaction .
|
|
162
|
+
"""
|
|
163
|
+
recipe = _load_recipe(name)
|
|
164
|
+
|
|
165
|
+
required = set(recipe.get("required_params") or [])
|
|
166
|
+
missing = required - params.keys()
|
|
167
|
+
if missing:
|
|
168
|
+
raise SfCliError(f"missing required params for {name!r}: {sorted(missing)}")
|
|
169
|
+
|
|
170
|
+
argv_template = recipe.get("argv") or []
|
|
171
|
+
if not isinstance(argv_template, list):
|
|
172
|
+
raise SfCliError(f"recipe {name!r} argv must be a list")
|
|
173
|
+
|
|
174
|
+
argv = [_sub(str(e), params) for e in argv_template]
|
|
175
|
+
timeout = int(recipe.get("timeout_seconds", 60))
|
|
176
|
+
auth_patterns = recipe.get("auth_required_stderr_patterns") or []
|
|
177
|
+
|
|
178
|
+
# SF_TEMP_SHOW_SECRETS=true is required for `sf org display --verbose
|
|
179
|
+
# --json` to emit `accessToken` instead of the literal redaction string
|
|
180
|
+
# `"[REDACTED] Use 'sf org auth show-access-token' to view"` introduced
|
|
181
|
+
# in sf CLI v2. Without it, downstream callers receive the redaction
|
|
182
|
+
# string as the bearer token and every Tooling/REST call returns
|
|
183
|
+
# INVALID_AUTH_HEADER 401. Set on every recipe — recipes that don't
|
|
184
|
+
# touch tokens are unaffected.
|
|
185
|
+
env = {**os.environ, "SF_TEMP_SHOW_SECRETS": "true"}
|
|
186
|
+
try:
|
|
187
|
+
cp = subprocess.run(
|
|
188
|
+
argv,
|
|
189
|
+
capture_output=True,
|
|
190
|
+
text=True,
|
|
191
|
+
timeout=timeout,
|
|
192
|
+
check=False,
|
|
193
|
+
env=env,
|
|
194
|
+
)
|
|
195
|
+
except subprocess.TimeoutExpired as e:
|
|
196
|
+
# timeout exceptions carry stderr via e.stderr — redact.
|
|
197
|
+
raise SfCliError(
|
|
198
|
+
f"sf CLI {name!r} timed out after {timeout}s: {redact_error(e)}"
|
|
199
|
+
) from None
|
|
200
|
+
except (OSError, subprocess.SubprocessError) as e:
|
|
201
|
+
# any subprocess-layer error gets redacted.
|
|
202
|
+
raise SfCliError(
|
|
203
|
+
f"sf CLI {name!r} invocation failed: {redact_error(e)}"
|
|
204
|
+
) from None
|
|
205
|
+
|
|
206
|
+
# Even on nonzero exit sf CLI usually still emits JSON on stdout; try
|
|
207
|
+
# to parse before deciding failure mode.
|
|
208
|
+
try:
|
|
209
|
+
data = _parse_stdout_json(cp.stdout) if cp.stdout else {}
|
|
210
|
+
except SfCliError:
|
|
211
|
+
data = {}
|
|
212
|
+
|
|
213
|
+
status_field = data.get("status", 0) if isinstance(data, dict) else 1
|
|
214
|
+
if cp.returncode != 0 or status_field != 0:
|
|
215
|
+
# stderr could contain a token on some failure paths; redact
|
|
216
|
+
# the ENTIRE string before deciding how to classify or what to show.
|
|
217
|
+
safe_stderr = _redact_subprocess_stderr(cp.stderr)
|
|
218
|
+
if _stderr_matches_auth(cp.stderr, auth_patterns):
|
|
219
|
+
raise AuthRequired(safe_stderr or "auth required")
|
|
220
|
+
# Include exit code + JSON status in the message (both are
|
|
221
|
+
# numeric/well-known, no token risk) plus the redacted stderr tail.
|
|
222
|
+
raise SfCliError(
|
|
223
|
+
f"sf CLI {name!r} failed "
|
|
224
|
+
f"(exit={cp.returncode}, json_status={status_field}): {safe_stderr}"
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return data
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _redact_subprocess_stderr(stderr: str) -> str:
|
|
231
|
+
"""run stderr text through the rest_client redaction regexes.
|
|
232
|
+
|
|
233
|
+
stderr may contain `Authorization: Bearer ...` or `accessToken=...` if
|
|
234
|
+
the sf CLI echoes a failing request; scrub before surfacing.
|
|
235
|
+
|
|
236
|
+
calls the public `redact_text` (imported at module
|
|
237
|
+
top). Previously imported the module-private `_redact_text` lazily on
|
|
238
|
+
every call — a rename could have silently broken redaction, and an
|
|
239
|
+
ImportError would have propagated through `run_sf` with the raw stderr
|
|
240
|
+
still live in the traceback's frame locals.
|
|
241
|
+
"""
|
|
242
|
+
return redact_text(stderr or "")
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""SOQL template loader — reads assets/soql/*.soql and substitutes {{PARAM}} values.
|
|
2
|
+
|
|
3
|
+
every value passed in via **params is regex-revalidated at the
|
|
4
|
+
substitution boundary via `fs_guard.validate_api_name`. This is defence
|
|
5
|
+
in depth — callers (SKILL.md phase 1, parse_bundle.extract_planner_name,
|
|
6
|
+
parse_wave.discovered_names) already validate at their own boundaries, but
|
|
7
|
+
centralizing the gate here guarantees that any future caller can't bypass
|
|
8
|
+
it by forgetting to validate first.
|
|
9
|
+
|
|
10
|
+
Why no escape function: `validate_api_name` restricts every substituted
|
|
11
|
+
string to `^[A-Za-z0-9_]+$` — the legal character set for Salesforce
|
|
12
|
+
DeveloperName / API-name identifiers. No quote, apostrophe, semicolon,
|
|
13
|
+
parenthesis, or whitespace can ever reach the SOQL builder, so there is
|
|
14
|
+
nothing to escape. Adding an escape step would be both dead code and a
|
|
15
|
+
false reassurance that unvalidated input is somehow safe.
|
|
16
|
+
|
|
17
|
+
`load_soql_in` adds support for `WHERE X IN (...)`
|
|
18
|
+
list placeholders. The BFS fan-out passes across Apex/Flow/Plugin/Function
|
|
19
|
+
discovery each need to batch-query by a list of ids or names; forcing
|
|
20
|
+
one SOQL call per element would blow up both the query-count budget and
|
|
21
|
+
the request-latency floor. The new function shares the `load_soql`
|
|
22
|
+
validation path (every element goes through `fs_guard.validate_api_name`)
|
|
23
|
+
so list inputs cannot widen the SOQL-injection surface — `'`, `;`, `--`,
|
|
24
|
+
whitespace, and every other SOQL metachar remain unreachable. The
|
|
25
|
+
existing `load_soql` signature is unchanged.
|
|
26
|
+
|
|
27
|
+
SOQL-string-substitution loader with validation at the substitution
|
|
28
|
+
boundary. Every value substituted into a `{{token}}` placeholder is
|
|
29
|
+
regex-validated before insertion.
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from typing import Dict, List, Optional
|
|
34
|
+
|
|
35
|
+
from config import SOQL_DIR, fs_guard # fs_guard re-exported by config.py from _shared/
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SoqlParamError(ValueError):
|
|
39
|
+
"""Raised when a parameter value fails revalidation at the substitution site.
|
|
40
|
+
|
|
41
|
+
Carries the failing key + a 20-char preview of the rejected value. Callers
|
|
42
|
+
(parse_bundle, parse_wave, main.py) catch this and add the key to
|
|
43
|
+
`_unresolved[]` with `reason=invalid-soql-param:<key>` so the pipeline
|
|
44
|
+
continues with `STATUS=PARTIAL_OK` rather than aborting on a single
|
|
45
|
+
malformed name.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, key: str, value, reason: str) -> None:
|
|
49
|
+
self.key = key
|
|
50
|
+
self.reason = reason
|
|
51
|
+
# Preview is bounded to 20 chars to avoid echoing arbitrarily-long
|
|
52
|
+
# attacker-controlled strings into logs.
|
|
53
|
+
try:
|
|
54
|
+
preview = str(value)[:20]
|
|
55
|
+
except Exception:
|
|
56
|
+
preview = "<unreprable>"
|
|
57
|
+
self.preview = preview
|
|
58
|
+
super().__init__(
|
|
59
|
+
f"SOQL param {key!r} rejected: {reason} (preview={preview!r})"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class SoqlTemplateNotFound(LookupError):
|
|
64
|
+
"""Raised when `load_soql(name)` cannot locate a matching template file.
|
|
65
|
+
|
|
66
|
+
the message deliberately carries ONLY the requested
|
|
67
|
+
template name — never the absolute SOQL_DIR path. This prevents leaking
|
|
68
|
+
the filesystem layout of the skill install (which the attacker does not
|
|
69
|
+
otherwise need to know) via error messages that bubble up to logs or
|
|
70
|
+
RESULT blocks.
|
|
71
|
+
|
|
72
|
+
Distinct from `FileNotFoundError` so callers can tell "template missing"
|
|
73
|
+
apart from "filesystem I/O error" (permission denied, etc.) at the
|
|
74
|
+
except-clause layer.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, name: str) -> None:
|
|
78
|
+
# Preview bounds the echoed name at 60 chars. The validator forbids
|
|
79
|
+
# traversal characters anyway, but defence in depth against an
|
|
80
|
+
# arbitrarily-long attacker-controlled name ever reaching logs.
|
|
81
|
+
try:
|
|
82
|
+
preview = str(name)[:60]
|
|
83
|
+
except Exception:
|
|
84
|
+
preview = "<unreprable>"
|
|
85
|
+
self.name = preview
|
|
86
|
+
super().__init__(f"SOQL template not found: {preview!r}")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def load_soql(name: str, **params) -> str:
|
|
90
|
+
"""Read assets/soql/<name>.soql and substitute {{PARAM}} placeholders.
|
|
91
|
+
|
|
92
|
+
every value in `params` is revalidated via
|
|
93
|
+
`fs_guard.validate_api_name` before being substituted. Invalid values
|
|
94
|
+
(non-string, empty, or containing any character outside [A-Za-z0-9_])
|
|
95
|
+
raise `SoqlParamError`.
|
|
96
|
+
|
|
97
|
+
`name` itself is validated via the same
|
|
98
|
+
`fs_guard.validate_api_name` regex BEFORE any filesystem access.
|
|
99
|
+
Without this, a caller that sources `name` from data (config file,
|
|
100
|
+
user argument, etc.) could read `SOQL_DIR/../../../tmp/pwn.soql` via
|
|
101
|
+
traversal. All SOQL template filenames in this skill match
|
|
102
|
+
`^[A-Za-z0-9_]+$` (e.g. `bot_version_lookup`, `plugins_by_planner`),
|
|
103
|
+
so the regex is the correct gate.
|
|
104
|
+
|
|
105
|
+
Substitution is single-pass: `str.replace` does not re-scan the output,
|
|
106
|
+
so a value that contains `{{OTHER}}` cannot re-trigger substitution of
|
|
107
|
+
a different key. This is verified by test_soql_loader.
|
|
108
|
+
"""
|
|
109
|
+
# validate the template name BEFORE touching the filesystem.
|
|
110
|
+
# A raw `../../../etc/passwd` would otherwise resolve via read_text().
|
|
111
|
+
try:
|
|
112
|
+
fs_guard.validate_api_name(name, label="soql_template_name")
|
|
113
|
+
except fs_guard.ValidationError as e:
|
|
114
|
+
raise SoqlParamError("soql_template_name", name, e.reason) from None
|
|
115
|
+
|
|
116
|
+
# revalidate every SOQL param before substitution. No exceptions,
|
|
117
|
+
# no "this caller already validated" shortcuts. Defence in depth.
|
|
118
|
+
for key, value in params.items():
|
|
119
|
+
try:
|
|
120
|
+
fs_guard.validate_api_name(value, label=key)
|
|
121
|
+
except fs_guard.ValidationError as e:
|
|
122
|
+
raise SoqlParamError(key, value, e.reason) from None
|
|
123
|
+
|
|
124
|
+
# translate FileNotFoundError into SoqlTemplateNotFound so the
|
|
125
|
+
# absolute SOQL_DIR path never bleeds into caller-visible error text.
|
|
126
|
+
# We intentionally do NOT chain the original exception (`from None`) —
|
|
127
|
+
# the original carries the full path in `filename`/`strerror`.
|
|
128
|
+
try:
|
|
129
|
+
soql = (SOQL_DIR / f"{name}.soql").read_text()
|
|
130
|
+
except FileNotFoundError:
|
|
131
|
+
raise SoqlTemplateNotFound(name) from None
|
|
132
|
+
|
|
133
|
+
for key, value in params.items():
|
|
134
|
+
# Plain str.replace: single-pass by construction. No re-substitution
|
|
135
|
+
# of placeholders that appear inside a substituted value (see test).
|
|
136
|
+
soql = soql.replace(f"{{{{{key}}}}}", value)
|
|
137
|
+
return soql.strip()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def load_soql_in(
|
|
141
|
+
name: str,
|
|
142
|
+
*,
|
|
143
|
+
string_params: Optional[Dict[str, str]] = None,
|
|
144
|
+
list_params: Dict[str, List[str]],
|
|
145
|
+
) -> str:
|
|
146
|
+
"""Read assets/soql/<name>.soql and substitute both scalar and list placeholders.
|
|
147
|
+
|
|
148
|
+
the BFS fan-out builds `WHERE X IN (:list)` queries
|
|
149
|
+
(apex_class_bodies_by_names, flow_definition_by_ids, plugin_functions_
|
|
150
|
+
by_plugin_ids, etc.). The existing `load_soql` only handles scalar
|
|
151
|
+
string substitution; this sibling covers the list case without
|
|
152
|
+
touching that signature.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
name:
|
|
157
|
+
Template filename (without `.soql`). Validated via
|
|
158
|
+
`fs_guard.validate_api_name` BEFORE any filesystem access — same
|
|
159
|
+
defence-in-depth gate as `load_soql` .
|
|
160
|
+
string_params:
|
|
161
|
+
Optional scalar {{KEY}} → value map. Values revalidated through
|
|
162
|
+
`fs_guard.validate_api_name` — identical path to `load_soql`.
|
|
163
|
+
Useful for templates that mix a single scalar value with a list
|
|
164
|
+
in the same query. No active callers at present (retired with
|
|
165
|
+
`flow_definition_ids_by_name_with_ns.soql` on 2026-05-05); kept
|
|
166
|
+
on the signature as a stable extension point.
|
|
167
|
+
list_params:
|
|
168
|
+
Required. {{KEY}} → list-of-strings map. Each element is
|
|
169
|
+
revalidated through `fs_guard.validate_api_name`; any failure
|
|
170
|
+
aborts the whole call with `SoqlParamError(key=<list-key>, ...)`.
|
|
171
|
+
Empty lists raise — SOQL `WHERE X IN ()` is a syntax error in
|
|
172
|
+
Salesforce, fail fast rather than let the CLI surface an opaque
|
|
173
|
+
500.
|
|
174
|
+
|
|
175
|
+
Rendering
|
|
176
|
+
---------
|
|
177
|
+
Lists render as `','.join(f"'{v}'" for v in sorted(set(list_value)))`:
|
|
178
|
+
single-quoted, comma-separated, no surrounding parens. The `.soql`
|
|
179
|
+
template supplies the parens around the `{{PLACEHOLDER}}` so the
|
|
180
|
+
result reads `WHERE Id IN ('A','B','C')`.
|
|
181
|
+
|
|
182
|
+
Deduplication is load-bearing: SOQL tolerates dupes but they waste
|
|
183
|
+
the 100k-char query-length budget. Sort is for deterministic order —
|
|
184
|
+
stable cache keys and diffable test output.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
Substituted, `.strip()`-ed SOQL string.
|
|
189
|
+
"""
|
|
190
|
+
# validate template name BEFORE touching the filesystem.
|
|
191
|
+
try:
|
|
192
|
+
fs_guard.validate_api_name(name, label="soql_template_name")
|
|
193
|
+
except fs_guard.ValidationError as e:
|
|
194
|
+
raise SoqlParamError("soql_template_name", name, e.reason) from None
|
|
195
|
+
|
|
196
|
+
string_params = string_params or {}
|
|
197
|
+
|
|
198
|
+
# / revalidate scalar params — same path as `load_soql`.
|
|
199
|
+
for key, value in string_params.items():
|
|
200
|
+
try:
|
|
201
|
+
fs_guard.validate_api_name(value, label=key)
|
|
202
|
+
except fs_guard.ValidationError as e:
|
|
203
|
+
raise SoqlParamError(key, value, e.reason) from None
|
|
204
|
+
|
|
205
|
+
# validate list params first (deterministic failure order — we
|
|
206
|
+
# want the first bad element, not a partially-rendered string).
|
|
207
|
+
rendered_lists: Dict[str, str] = {}
|
|
208
|
+
for key, list_value in list_params.items():
|
|
209
|
+
if not isinstance(list_value, list):
|
|
210
|
+
raise SoqlParamError(
|
|
211
|
+
key,
|
|
212
|
+
list_value,
|
|
213
|
+
f"list_params[{key}] must be a list, got {type(list_value).__name__}",
|
|
214
|
+
)
|
|
215
|
+
if not list_value:
|
|
216
|
+
# Salesforce `WHERE X IN ()` is a hard syntax error — fail fast
|
|
217
|
+
# at the loader, not at the CLI. Callers should branch on the
|
|
218
|
+
# empty-list case upstream (e.g. skip the whole SOQL) rather
|
|
219
|
+
# than rely on an empty-clause fallback.
|
|
220
|
+
raise SoqlParamError(
|
|
221
|
+
key, "[]", "empty-list-would-produce-invalid-soql",
|
|
222
|
+
)
|
|
223
|
+
# Dedupe + sort: cache-key stable, query-length budget sparing.
|
|
224
|
+
# `sorted(set(...))` raises TypeError on non-hashable elements;
|
|
225
|
+
# validate_api_name below will surface the real cause for
|
|
226
|
+
# non-strings, so gate on type FIRST.
|
|
227
|
+
for element in list_value:
|
|
228
|
+
try:
|
|
229
|
+
fs_guard.validate_api_name(element, label=key)
|
|
230
|
+
except fs_guard.ValidationError as e:
|
|
231
|
+
raise SoqlParamError(key, element, e.reason) from None
|
|
232
|
+
unique_sorted = sorted(set(list_value))
|
|
233
|
+
# Single-quote each element, comma-join. The `.soql` template
|
|
234
|
+
# supplies the surrounding parens around `{{KEY}}`.
|
|
235
|
+
rendered_lists[key] = ",".join(f"'{v}'" for v in unique_sorted)
|
|
236
|
+
|
|
237
|
+
# translate FileNotFoundError into SoqlTemplateNotFound. Same
|
|
238
|
+
# hygiene contract as `load_soql` — the absolute SOQL_DIR path must
|
|
239
|
+
# not bleed into caller-visible error text.
|
|
240
|
+
try:
|
|
241
|
+
soql = (SOQL_DIR / f"{name}.soql").read_text()
|
|
242
|
+
except FileNotFoundError:
|
|
243
|
+
raise SoqlTemplateNotFound(name) from None
|
|
244
|
+
|
|
245
|
+
# Single-pass substitution, same semantics as `load_soql`. Scalars
|
|
246
|
+
# first, lists second — the order is only relevant if a scalar value
|
|
247
|
+
# could contain a list-placeholder token, which `validate_api_name`
|
|
248
|
+
# forbids (no `{` or `}` in `[A-Za-z0-9_]+`).
|
|
249
|
+
for key, value in string_params.items():
|
|
250
|
+
soql = soql.replace(f"{{{{{key}}}}}", value)
|
|
251
|
+
for key, rendered in rendered_lists.items():
|
|
252
|
+
soql = soql.replace(f"{{{{{key}}}}}", rendered)
|
|
253
|
+
return soql.strip()
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Render the declared action tree as a box-drawing summary.md.
|
|
3
|
+
|
|
4
|
+
Called by finalize.py (cold path) and cache_check.py (hit path when the
|
|
5
|
+
cached summary is missing). Mirrors the old agent Phase 7 render_tree()
|
|
6
|
+
logic verbatim.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 summarize_tree.py <tree_json_path> <out_md_path> <built_at_utc>
|
|
10
|
+
|
|
11
|
+
Inputs:
|
|
12
|
+
argv[1] absolute path to declared_action_tree.json (or similar)
|
|
13
|
+
argv[2] output .summary.md path (overwritten atomically)
|
|
14
|
+
argv[3] ISO-8601 UTC built-at timestamp (e.g. 2026-04-25T14:00:00Z)
|
|
15
|
+
|
|
16
|
+
Outputs:
|
|
17
|
+
argv[2] rendered markdown
|
|
18
|
+
exit 0 success
|
|
19
|
+
exit 1 missing args, read failure, write failure
|
|
20
|
+
"""
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import pathlib
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
KC_ORDER = [
|
|
27
|
+
"BOT_DEFINITION", "TOPIC", "GEN_AI_FUNCTION",
|
|
28
|
+
"FLOW", "APEX", "PROMPT_TEMPLATE",
|
|
29
|
+
"STANDARD_ACTION", "UNKNOWN",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def render_tree(node, prefix="", is_last=True, lines=None, agent_=None):
|
|
34
|
+
if lines is None:
|
|
35
|
+
lines = []
|
|
36
|
+
connector = "└── " if is_last else "├── "
|
|
37
|
+
kind = node.get("kind", "?")
|
|
38
|
+
api = node.get("api_name", "?")
|
|
39
|
+
elem = node.get("element_name")
|
|
40
|
+
if kind == "BOT_DEFINITION":
|
|
41
|
+
v = (agent_ or {}).get("version") or ""
|
|
42
|
+
label = f"[{kind}] {api}" + (f" ({v})" if v else "")
|
|
43
|
+
elif kind == "GEN_AI_FUNCTION":
|
|
44
|
+
unwraps = node.get("unwraps_to") or {}
|
|
45
|
+
uk = unwraps.get("kind")
|
|
46
|
+
un = unwraps.get("api_name")
|
|
47
|
+
arrow = f" → {uk}:{un}" if uk and un else ""
|
|
48
|
+
label = f"[{kind}] {api}{arrow}"
|
|
49
|
+
elif kind in ("STANDARD_ACTION", "UNKNOWN"):
|
|
50
|
+
# Canonical key is `invocation_type` (schema 3.1+); fall back to
|
|
51
|
+
# the two legacy keys so caches built by an older parse_wave still
|
|
52
|
+
# render the qualifier.
|
|
53
|
+
raw = (
|
|
54
|
+
node.get("invocation_type")
|
|
55
|
+
or node.get("raw_invocation_type")
|
|
56
|
+
or node.get("raw_action_type")
|
|
57
|
+
or ""
|
|
58
|
+
)
|
|
59
|
+
label = f"[{kind}] {api}" + (f" ({raw})" if raw else "")
|
|
60
|
+
else:
|
|
61
|
+
label = f"[{kind}] {api}"
|
|
62
|
+
if elem and kind != "BOT_DEFINITION":
|
|
63
|
+
label += f" — element:{elem}"
|
|
64
|
+
lines.append(prefix + connector + label)
|
|
65
|
+
children = node.get("children") or []
|
|
66
|
+
child_prefix = prefix + (" " if is_last else "│ ")
|
|
67
|
+
for i, c in enumerate(children):
|
|
68
|
+
render_tree(c, child_prefix, i == len(children) - 1, lines, agent_)
|
|
69
|
+
return lines
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def main() -> int:
|
|
73
|
+
if len(sys.argv) != 4:
|
|
74
|
+
sys.stderr.write(
|
|
75
|
+
"summarize_tree.py: usage: python3 summarize_tree.py <tree_json_path> "
|
|
76
|
+
"<out_md_path> <built_at_utc>\n"
|
|
77
|
+
)
|
|
78
|
+
return 1
|
|
79
|
+
|
|
80
|
+
tree_path = pathlib.Path(sys.argv[1])
|
|
81
|
+
out_path = pathlib.Path(sys.argv[2])
|
|
82
|
+
built_at = sys.argv[3]
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
tree = json.loads(tree_path.read_text())
|
|
86
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
87
|
+
sys.stderr.write(f"summarize_tree.py: cannot read {tree_path}: {e}\n")
|
|
88
|
+
return 1
|
|
89
|
+
|
|
90
|
+
a = tree.get("agent", {}) or {}
|
|
91
|
+
kc = tree.get("_kind_counts", {}) or {}
|
|
92
|
+
|
|
93
|
+
lines = [
|
|
94
|
+
f"# {a.get('api_name', '?')} {a.get('version', '?')} — declared action tree",
|
|
95
|
+
"",
|
|
96
|
+
f"- **Bot ID:** `{a.get('bot_id', '?')}`",
|
|
97
|
+
f"- **Master label:** {a.get('master_label', '?')}",
|
|
98
|
+
f"- **Generation:** {a.get('generation', 'unknown')}",
|
|
99
|
+
f"- **Planner:** `{a.get('planner_name', '?')}` ({a.get('planner_type', '?')})",
|
|
100
|
+
f"- **Version auto-picked:** {a.get('_version_auto_picked', False)}",
|
|
101
|
+
f"- **Built at (UTC):** {built_at}",
|
|
102
|
+
f"- **Node count:** {tree.get('node_count', 0)}",
|
|
103
|
+
f"- **Depth:** {tree.get('depth', 0)}",
|
|
104
|
+
f"- **Partial:** {tree.get('_partial', False)}",
|
|
105
|
+
"",
|
|
106
|
+
"## Kind counts",
|
|
107
|
+
"",
|
|
108
|
+
]
|
|
109
|
+
for k in KC_ORDER:
|
|
110
|
+
lines.append(f"- `{k}`: {kc.get(k, 0)}")
|
|
111
|
+
|
|
112
|
+
lines += [
|
|
113
|
+
"",
|
|
114
|
+
"## Declared action tree",
|
|
115
|
+
"",
|
|
116
|
+
"```",
|
|
117
|
+
]
|
|
118
|
+
root = tree.get("root") or {"kind": "BOT_DEFINITION", "api_name": a.get("api_name", "?"), "children": []}
|
|
119
|
+
lines.extend(render_tree(root, "", True, None, a))
|
|
120
|
+
lines.append("```")
|
|
121
|
+
|
|
122
|
+
if tree.get("_unresolved"):
|
|
123
|
+
lines += ["", "## Unresolved", ""]
|
|
124
|
+
for u in tree["_unresolved"]:
|
|
125
|
+
lines.append(
|
|
126
|
+
f"- `{u.get('kind')}`/`{u.get('api_name')}` — {u.get('reason')}"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
content = "\n".join(lines) + "\n"
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
tmp = out_path.with_suffix(out_path.suffix + ".tmp")
|
|
134
|
+
tmp.write_text(content)
|
|
135
|
+
os.replace(tmp, out_path)
|
|
136
|
+
except OSError as e:
|
|
137
|
+
sys.stderr.write(f"summarize_tree.py: write failed: {e}\n")
|
|
138
|
+
return 1
|
|
139
|
+
return 0
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
sys.exit(main())
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Shared sys.path bootstrap for the test suite.
|
|
2
|
+
|
|
3
|
+
The scripts/ scripts expect to be imported as top-level modules (config,
|
|
4
|
+
soql_loader, rest_client, sf_cli) — that's how they behave when main.py
|
|
5
|
+
does `from config import ...`. Tests mirror that by inserting scripts/
|
|
6
|
+
onto sys.path.
|
|
7
|
+
|
|
8
|
+
fs_guard is now sourced via ``from config import fs_guard`` (config.py
|
|
9
|
+
re-exports it from the plugin _shared/ package). No tools/ entry on
|
|
10
|
+
sys.path is required — config.py's dev-fallback walks up to the repo's
|
|
11
|
+
``plugins/investigating-agentforce-architecture/shared/`` when ``scripts/_shared/``
|
|
12
|
+
hasn't yet been mirrored by install.sh / the build script.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
_SCRIPTS_DIR = Path(__file__).resolve().parent.parent
|
|
20
|
+
|
|
21
|
+
s = str(_SCRIPTS_DIR)
|
|
22
|
+
if s not in sys.path:
|
|
23
|
+
sys.path.insert(0, s)
|
|
File without changes
|