@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,307 @@
|
|
|
1
|
+
"""cache_check.py schema-version mismatch → delete-on-read + MISS.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
* manifest.schema_version == config.SCHEMA_VERSION → HIT
|
|
5
|
+
* manifest.schema_version != config.SCHEMA_VERSION → MISS +
|
|
6
|
+
CACHE_INVALIDATED_REASON=schema-version-mismatch + cache dir deleted
|
|
7
|
+
* missing manifest → MISS (existing behavior preserved)
|
|
8
|
+
* expired TTL + correct schema → MISS + dir NOT deleted
|
|
9
|
+
* _safe_rmtree_under_cache_root refuses paths outside CACHE_ROOT
|
|
10
|
+
* module import raises if CACHE_ROOT is a symlink
|
|
11
|
+
|
|
12
|
+
The script reads env vars + writes eval-able K=V lines to stdout +
|
|
13
|
+
exits 0 on both HIT and MISS. We drive it via subprocess so we exercise
|
|
14
|
+
the real stdout stream and exit code.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import datetime as dt
|
|
19
|
+
import importlib
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import pathlib
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
import tempfile
|
|
27
|
+
import unittest
|
|
28
|
+
|
|
29
|
+
from . import _bootstrap # noqa: F401
|
|
30
|
+
|
|
31
|
+
import config # type: ignore
|
|
32
|
+
import cache_check # type: ignore
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
SCRIPTS_DIR = pathlib.Path(__file__).resolve().parent.parent
|
|
36
|
+
CACHE_CHECK = SCRIPTS_DIR / "cache_check.py"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _parse_kv(stdout: str) -> dict[str, str]:
|
|
40
|
+
"""Parse the shlex-quoted K=V lines cache_check emits.
|
|
41
|
+
|
|
42
|
+
cache_check writes one `K=V` per line, V shlex-quoted. For our tests
|
|
43
|
+
we don't need full shell parsing — we just strip one surrounding
|
|
44
|
+
single-quote layer if present.
|
|
45
|
+
"""
|
|
46
|
+
out: dict[str, str] = {}
|
|
47
|
+
for line in stdout.splitlines():
|
|
48
|
+
if "=" not in line:
|
|
49
|
+
continue
|
|
50
|
+
k, _, v = line.partition("=")
|
|
51
|
+
if v.startswith("'") and v.endswith("'") and len(v) >= 2:
|
|
52
|
+
v = v[1:-1]
|
|
53
|
+
out[k] = v
|
|
54
|
+
return out
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CacheCheckSchemaVersionTests(unittest.TestCase):
|
|
58
|
+
"""strict schema-version match, delete-on-mismatch."""
|
|
59
|
+
|
|
60
|
+
def setUp(self) -> None:
|
|
61
|
+
# Isolated tempdir mirroring CACHE_ROOT so we can safely rmtree
|
|
62
|
+
# anything under it. We override config.CACHE_ROOT for the
|
|
63
|
+
# duration of each test — the script reads config.CACHE_ROOT at
|
|
64
|
+
# rmtree-validation time, and we drive the script via subprocess
|
|
65
|
+
# with CACHE_ROOT propagated through env only via a wrapper
|
|
66
|
+
# below (subprocess-style overrides).
|
|
67
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
68
|
+
self.root = pathlib.Path(self.tmp.name).resolve()
|
|
69
|
+
self.cache_root = self.root / "cache"
|
|
70
|
+
self.data_root = self.root / "data"
|
|
71
|
+
self.work_dir = self.root / "work"
|
|
72
|
+
self.cache_root.mkdir()
|
|
73
|
+
self.data_root.mkdir()
|
|
74
|
+
self.work_dir.mkdir()
|
|
75
|
+
# Agent-keyed sub-path under cache_root.
|
|
76
|
+
self.agent = "TestAgent_v1"
|
|
77
|
+
self.version = "v1"
|
|
78
|
+
self.cache_dir = self.cache_root / "00D000000000ABC" / f"{self.agent}__{self.version}"
|
|
79
|
+
self.data_dir = self.data_root / "00D000000000ABC" / f"{self.agent}__{self.version}"
|
|
80
|
+
self.cache_dir.mkdir(parents=True)
|
|
81
|
+
# Data file manifest points at — must exist for the HIT path.
|
|
82
|
+
self.data_file = self.cache_dir / "metadata_tree.json"
|
|
83
|
+
self.data_file.write_text(json.dumps({"root": {"kind": "Bot"}}))
|
|
84
|
+
# summary.md dropped from the output contract — no
|
|
85
|
+
# longer written by cache seeding.
|
|
86
|
+
|
|
87
|
+
def tearDown(self) -> None:
|
|
88
|
+
self.tmp.cleanup()
|
|
89
|
+
|
|
90
|
+
def _write_manifest(self, schema_version: str, age_days: int = 0) -> None:
|
|
91
|
+
built = dt.datetime.now(dt.timezone.utc) - dt.timedelta(days=age_days)
|
|
92
|
+
manifest = {
|
|
93
|
+
"schema_version": schema_version,
|
|
94
|
+
"data_path": str(self.data_file),
|
|
95
|
+
"built_at_utc": built.isoformat().replace("+00:00", "Z"),
|
|
96
|
+
"ttl_days": 7,
|
|
97
|
+
"node_count": 1,
|
|
98
|
+
"depth": 1,
|
|
99
|
+
"agent": {
|
|
100
|
+
"generation": "Einstein",
|
|
101
|
+
"bot_id": "0Xx000000000000",
|
|
102
|
+
"master_label": "TestAgent",
|
|
103
|
+
"planner_name": "p1",
|
|
104
|
+
},
|
|
105
|
+
"kind_counts": {"Bot": 1},
|
|
106
|
+
"partial": False,
|
|
107
|
+
"unresolved_count": 0,
|
|
108
|
+
}
|
|
109
|
+
(self.cache_dir / "manifest.json").write_text(json.dumps(manifest))
|
|
110
|
+
|
|
111
|
+
def _run_with_patched_cache_root(self, extra_env: dict | None = None) -> subprocess.CompletedProcess:
|
|
112
|
+
"""Patch config.CACHE_ROOT via a small wrapper — simpler than the
|
|
113
|
+
HOME-redirect dance and keeps the test local to the module under
|
|
114
|
+
test. We write a tiny entry-point that monkeypatches config
|
|
115
|
+
before invoking cache_check.main, then runs it.
|
|
116
|
+
"""
|
|
117
|
+
wrapper = self.root / "wrapper.py"
|
|
118
|
+
wrapper.write_text(
|
|
119
|
+
"import sys; sys.path.insert(0, %r); sys.path.insert(0, %r)\n"
|
|
120
|
+
"import config\n"
|
|
121
|
+
"config.CACHE_ROOT = __import__('pathlib').Path(%r)\n"
|
|
122
|
+
"import cache_check\n"
|
|
123
|
+
"sys.exit(cache_check.main())\n"
|
|
124
|
+
% (str(SCRIPTS_DIR), str(SCRIPTS_DIR.parent / 'tools'), str(self.cache_root))
|
|
125
|
+
)
|
|
126
|
+
env = os.environ.copy()
|
|
127
|
+
env.update({
|
|
128
|
+
"CACHE_DIR": str(self.cache_dir),
|
|
129
|
+
"DATA_DIR": str(self.data_dir),
|
|
130
|
+
"WORK_DIR": str(self.work_dir),
|
|
131
|
+
"AGENT_API_NAME": self.agent,
|
|
132
|
+
"AGENT_VERSION": self.version,
|
|
133
|
+
})
|
|
134
|
+
if extra_env:
|
|
135
|
+
env.update(extra_env)
|
|
136
|
+
return subprocess.run(
|
|
137
|
+
[sys.executable, str(wrapper)],
|
|
138
|
+
env=env,
|
|
139
|
+
capture_output=True,
|
|
140
|
+
text=True,
|
|
141
|
+
timeout=10,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def test_matching_schema_version_hits(self):
|
|
145
|
+
self._write_manifest(config.SCHEMA_VERSION)
|
|
146
|
+
cp = self._run_with_patched_cache_root()
|
|
147
|
+
self.assertEqual(cp.returncode, 0, cp.stderr)
|
|
148
|
+
kv = _parse_kv(cp.stdout)
|
|
149
|
+
self.assertEqual(kv.get("CACHE_HIT"), "true", cp.stdout)
|
|
150
|
+
self.assertNotIn("CACHE_INVALIDATED_REASON", kv)
|
|
151
|
+
# Cache dir survives.
|
|
152
|
+
self.assertTrue(self.cache_dir.exists())
|
|
153
|
+
|
|
154
|
+
def test_legacy_schema_version_misses_and_deletes(self):
|
|
155
|
+
"""schema_version=2.4 (legacy) != 3.0 (current) → MISS + rmtree."""
|
|
156
|
+
self._write_manifest("2.4")
|
|
157
|
+
self.assertTrue(self.cache_dir.exists())
|
|
158
|
+
cp = self._run_with_patched_cache_root()
|
|
159
|
+
self.assertEqual(cp.returncode, 0, cp.stderr)
|
|
160
|
+
kv = _parse_kv(cp.stdout)
|
|
161
|
+
self.assertEqual(kv.get("CACHE_HIT"), "false")
|
|
162
|
+
self.assertEqual(kv.get("CACHE_INVALIDATED_REASON"), "schema-version-mismatch")
|
|
163
|
+
# Cache dir deleted by the safety-gated rmtree.
|
|
164
|
+
self.assertFalse(self.cache_dir.exists(),
|
|
165
|
+
"cache_dir must be removed on schema-version mismatch")
|
|
166
|
+
|
|
167
|
+
def test_unexpected_future_schema_version_misses_and_deletes(self):
|
|
168
|
+
"""Any value != current SCHEMA_VERSION invalidates (not just legacy)."""
|
|
169
|
+
self._write_manifest("99.99")
|
|
170
|
+
cp = self._run_with_patched_cache_root()
|
|
171
|
+
kv = _parse_kv(cp.stdout)
|
|
172
|
+
self.assertEqual(kv.get("CACHE_HIT"), "false")
|
|
173
|
+
self.assertEqual(kv.get("CACHE_INVALIDATED_REASON"), "schema-version-mismatch")
|
|
174
|
+
self.assertFalse(self.cache_dir.exists())
|
|
175
|
+
|
|
176
|
+
def test_missing_manifest_misses_no_deletion(self):
|
|
177
|
+
"""Existing behavior: no manifest → MISS, NO rmtree, no reason emit."""
|
|
178
|
+
# Don't write any manifest.
|
|
179
|
+
cp = self._run_with_patched_cache_root()
|
|
180
|
+
self.assertEqual(cp.returncode, 0, cp.stderr)
|
|
181
|
+
kv = _parse_kv(cp.stdout)
|
|
182
|
+
self.assertEqual(kv.get("CACHE_HIT"), "false")
|
|
183
|
+
# Missing manifest is a different reason class — no invalidation
|
|
184
|
+
# reason emitted here (cache is simply cold, not corrupt).
|
|
185
|
+
self.assertNotIn("CACHE_INVALIDATED_REASON", kv)
|
|
186
|
+
self.assertTrue(self.cache_dir.exists(),
|
|
187
|
+
"missing manifest must NOT trigger rmtree")
|
|
188
|
+
|
|
189
|
+
def test_expired_ttl_with_matching_schema_misses_no_deletion(self):
|
|
190
|
+
"""TTL-expired cache with CURRENT schema → MISS but dir stays intact."""
|
|
191
|
+
self._write_manifest(config.SCHEMA_VERSION, age_days=30) # 30 > 7
|
|
192
|
+
cp = self._run_with_patched_cache_root()
|
|
193
|
+
kv = _parse_kv(cp.stdout)
|
|
194
|
+
self.assertEqual(kv.get("CACHE_HIT"), "false")
|
|
195
|
+
# No schema-mismatch reason — it's a stale cache, not a corrupt one.
|
|
196
|
+
self.assertNotIn("CACHE_INVALIDATED_REASON", kv)
|
|
197
|
+
self.assertTrue(self.cache_dir.exists(),
|
|
198
|
+
"expired TTL must NOT rmtree — just mark stale")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class SafeRmtreeUnderCacheRootTests(unittest.TestCase):
|
|
202
|
+
"""defensive: `_safe_rmtree_under_cache_root` refuses out-of-root paths."""
|
|
203
|
+
|
|
204
|
+
def setUp(self) -> None:
|
|
205
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
206
|
+
self.root = pathlib.Path(self.tmp.name).resolve()
|
|
207
|
+
self.cache_root = self.root / "cache"
|
|
208
|
+
self.cache_root.mkdir()
|
|
209
|
+
# In-scope victim dir.
|
|
210
|
+
self.in_scope = self.cache_root / "agent_v1"
|
|
211
|
+
self.in_scope.mkdir()
|
|
212
|
+
(self.in_scope / "some.txt").write_text("x")
|
|
213
|
+
# Out-of-scope victim dir (sibling to cache_root).
|
|
214
|
+
self.out_of_scope = self.root / "other"
|
|
215
|
+
self.out_of_scope.mkdir()
|
|
216
|
+
(self.out_of_scope / "important.txt").write_text("do not delete")
|
|
217
|
+
|
|
218
|
+
# Monkeypatch config.CACHE_ROOT for the duration of each test.
|
|
219
|
+
self._orig_root = config.CACHE_ROOT
|
|
220
|
+
config.CACHE_ROOT = self.cache_root
|
|
221
|
+
|
|
222
|
+
def tearDown(self) -> None:
|
|
223
|
+
config.CACHE_ROOT = self._orig_root
|
|
224
|
+
self.tmp.cleanup()
|
|
225
|
+
|
|
226
|
+
def test_rmtree_under_cache_root_succeeds(self):
|
|
227
|
+
ok = cache_check._safe_rmtree_under_cache_root(self.in_scope)
|
|
228
|
+
self.assertTrue(ok)
|
|
229
|
+
self.assertFalse(self.in_scope.exists())
|
|
230
|
+
|
|
231
|
+
def test_rmtree_outside_cache_root_refused(self):
|
|
232
|
+
ok = cache_check._safe_rmtree_under_cache_root(self.out_of_scope)
|
|
233
|
+
self.assertFalse(ok, "out-of-root path must be refused")
|
|
234
|
+
# File still present.
|
|
235
|
+
self.assertTrue((self.out_of_scope / "important.txt").exists())
|
|
236
|
+
|
|
237
|
+
def test_nonexistent_under_root_is_noop_success(self):
|
|
238
|
+
"""A caller asking to delete a path that doesn't exist under the
|
|
239
|
+
root gets a silent success — matches rmtree's semantics for
|
|
240
|
+
already-gone targets and avoids spurious error branches on the
|
|
241
|
+
miss() path."""
|
|
242
|
+
ghost = self.cache_root / "never_existed"
|
|
243
|
+
ok = cache_check._safe_rmtree_under_cache_root(ghost)
|
|
244
|
+
self.assertTrue(ok)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class SymlinkedCacheRootRejectedAtImportTests(unittest.TestCase):
|
|
248
|
+
"""module import raises if CACHE_ROOT is a symlink.
|
|
249
|
+
|
|
250
|
+
A symlinked CACHE_ROOT would defeat the rmtree safety gate — both
|
|
251
|
+
sides of `target.resolve().is_relative_to(root.resolve())` would
|
|
252
|
+
collapse to the same symlink target, so the gate would approve
|
|
253
|
+
deletion of arbitrary paths the attacker aimed the symlink at.
|
|
254
|
+
The defence is a fail-fast guard at cache_check import time.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
def setUp(self) -> None:
|
|
258
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
259
|
+
self.root = pathlib.Path(self.tmp.name).resolve()
|
|
260
|
+
self.real_target = self.root / "evil_target"
|
|
261
|
+
self.real_target.mkdir()
|
|
262
|
+
self.symlinked_root = self.root / "symlinked_cache_root"
|
|
263
|
+
self.symlinked_root.symlink_to(self.real_target)
|
|
264
|
+
self._orig_root = config.CACHE_ROOT
|
|
265
|
+
|
|
266
|
+
def tearDown(self) -> None:
|
|
267
|
+
# Restore and reload so the rest of the suite sees a clean module.
|
|
268
|
+
config.CACHE_ROOT = self._orig_root
|
|
269
|
+
importlib.reload(cache_check)
|
|
270
|
+
self.tmp.cleanup()
|
|
271
|
+
|
|
272
|
+
def test_symlinked_cache_root_raises_on_import(self):
|
|
273
|
+
"""Reloading cache_check with a symlinked CACHE_ROOT must raise
|
|
274
|
+
RuntimeError — proof that the guard actually fires and the fix
|
|
275
|
+
is load-bearing."""
|
|
276
|
+
config.CACHE_ROOT = self.symlinked_root
|
|
277
|
+
with self.assertRaises(RuntimeError) as ctx:
|
|
278
|
+
importlib.reload(cache_check)
|
|
279
|
+
msg = str(ctx.exception)
|
|
280
|
+
# Message must name the offending path so operators can fix it
|
|
281
|
+
# without spelunking the source. Don't over-specify wording —
|
|
282
|
+
# future maintainers may clarify the phrasing.
|
|
283
|
+
self.assertIn(str(self.symlinked_root), msg)
|
|
284
|
+
self.assertIn("symlink", msg.lower())
|
|
285
|
+
|
|
286
|
+
def test_real_directory_cache_root_passes(self):
|
|
287
|
+
"""Sanity: the guard is not over-broad — a real directory (the
|
|
288
|
+
common case) reloads cleanly."""
|
|
289
|
+
real_dir = self.root / "real_cache_root"
|
|
290
|
+
real_dir.mkdir()
|
|
291
|
+
config.CACHE_ROOT = real_dir
|
|
292
|
+
# No raise.
|
|
293
|
+
importlib.reload(cache_check)
|
|
294
|
+
|
|
295
|
+
def test_nonexistent_cache_root_passes(self):
|
|
296
|
+
"""Pristine install: CACHE_ROOT hasn't been created yet.
|
|
297
|
+
Path.is_symlink() returns False on non-existent paths, so import
|
|
298
|
+
must succeed — the directory will be materialized on first write."""
|
|
299
|
+
ghost = self.root / "does_not_exist_yet"
|
|
300
|
+
self.assertFalse(ghost.exists())
|
|
301
|
+
config.CACHE_ROOT = ghost
|
|
302
|
+
# No raise.
|
|
303
|
+
importlib.reload(cache_check)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
if __name__ == "__main__":
|
|
307
|
+
unittest.main()
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""In-process tests for ``cache_check.main``.
|
|
2
|
+
|
|
3
|
+
The existing ``test_cache_check.py`` drives main via subprocess (so its
|
|
4
|
+
Python branches don't show up in coverage). This file calls main()
|
|
5
|
+
directly, patching ``os.environ`` + ``config.CACHE_ROOT`` so all the
|
|
6
|
+
hit/miss decision branches register.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import datetime as dt
|
|
11
|
+
import io
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import unittest
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from tempfile import TemporaryDirectory
|
|
17
|
+
from unittest import mock
|
|
18
|
+
|
|
19
|
+
from . import _bootstrap # noqa: F401 — sys.path setup
|
|
20
|
+
|
|
21
|
+
import cache_check # type: ignore
|
|
22
|
+
import config # type: ignore
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _build_valid_manifest(*, data_path: str,
|
|
26
|
+
built_at_utc: str | None = None,
|
|
27
|
+
schema_version: str = "3.1",
|
|
28
|
+
node_count: int = 5,
|
|
29
|
+
depth: int = 2,
|
|
30
|
+
ttl_days: int = 7,
|
|
31
|
+
partial: bool = False,
|
|
32
|
+
unresolved_count: int = 0,
|
|
33
|
+
generation: str = "nga",
|
|
34
|
+
bot_id: str = "0Xx000000000Demo",
|
|
35
|
+
master_label: str = "Demo Agent",
|
|
36
|
+
version_auto_picked: bool = False,
|
|
37
|
+
planner_name: str = "DemoPlanner",
|
|
38
|
+
kind_counts: dict | None = None) -> dict:
|
|
39
|
+
if built_at_utc is None:
|
|
40
|
+
built_at_utc = (
|
|
41
|
+
dt.datetime.now(dt.timezone.utc)
|
|
42
|
+
.isoformat().replace("+00:00", "Z")
|
|
43
|
+
)
|
|
44
|
+
return {
|
|
45
|
+
"schema_version": schema_version,
|
|
46
|
+
"built_at_utc": built_at_utc,
|
|
47
|
+
"node_count": node_count,
|
|
48
|
+
"depth": depth,
|
|
49
|
+
"ttl_days": ttl_days,
|
|
50
|
+
"data_path": data_path,
|
|
51
|
+
"partial": partial,
|
|
52
|
+
"unresolved_count": unresolved_count,
|
|
53
|
+
"agent": {
|
|
54
|
+
"generation": generation,
|
|
55
|
+
"bot_id": bot_id,
|
|
56
|
+
"master_label": master_label,
|
|
57
|
+
"_version_auto_picked": version_auto_picked,
|
|
58
|
+
"planner_name": planner_name,
|
|
59
|
+
},
|
|
60
|
+
"kind_counts": kind_counts or {"TOPIC": 2, "STANDARD_ACTION": 3},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _CacheHarness:
|
|
65
|
+
"""Patch CACHE_ROOT + os.environ + capture stdout."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, *, env_overrides: dict | None = None,
|
|
68
|
+
manifest: dict | None = None,
|
|
69
|
+
plant_data_file: bool = True):
|
|
70
|
+
self.env_overrides = env_overrides or {}
|
|
71
|
+
self.manifest = manifest
|
|
72
|
+
self.plant_data_file = plant_data_file
|
|
73
|
+
|
|
74
|
+
def __enter__(self):
|
|
75
|
+
self._tmp = TemporaryDirectory()
|
|
76
|
+
self.tmpdir = Path(self._tmp.name)
|
|
77
|
+
# Build a 4-deep CACHE_ROOT so cache_dir lives strictly inside it
|
|
78
|
+
# (the rmtree guard refuses paths NOT under CACHE_ROOT).
|
|
79
|
+
self.cache_root = self.tmpdir / "cache" / "investigating-agentforce-architecture"
|
|
80
|
+
self.cache_root.mkdir(parents=True)
|
|
81
|
+
self.cache_dir = self.cache_root / "ALPHA0000000000" / "MyAgent__v1"
|
|
82
|
+
self.cache_dir.mkdir(parents=True)
|
|
83
|
+
self.data_dir = self.tmpdir / "data" / "ALPHA0000000000" / "MyAgent__v1"
|
|
84
|
+
self.work_dir = self.tmpdir / "work"
|
|
85
|
+
self.work_dir.mkdir(parents=True)
|
|
86
|
+
|
|
87
|
+
# Plant the authoritative tree file UNDER cache_dir (where
|
|
88
|
+
# finalize.py originally wrote it). cache_check copies it INTO
|
|
89
|
+
# data_dir on hit, so source and dest must be different files.
|
|
90
|
+
self.tree_path = self.cache_dir / "MyAgent_v1_metadata_tree.json"
|
|
91
|
+
if self.plant_data_file:
|
|
92
|
+
self.tree_path.write_text(json.dumps({
|
|
93
|
+
"agent": {"api_name": "MyAgent", "version": "v1"},
|
|
94
|
+
"session": {"_schema_version": 1},
|
|
95
|
+
}))
|
|
96
|
+
|
|
97
|
+
# Plant the manifest under cache_dir
|
|
98
|
+
if self.manifest is not None:
|
|
99
|
+
(self.cache_dir / "manifest.json").write_text(
|
|
100
|
+
json.dumps(self.manifest)
|
|
101
|
+
)
|
|
102
|
+
elif self.manifest is None and self.plant_data_file:
|
|
103
|
+
# Default valid manifest pointing at the planted tree
|
|
104
|
+
(self.cache_dir / "manifest.json").write_text(json.dumps(
|
|
105
|
+
_build_valid_manifest(data_path=str(self.tree_path))
|
|
106
|
+
))
|
|
107
|
+
|
|
108
|
+
self._env = {
|
|
109
|
+
"CACHE_DIR": str(self.cache_dir),
|
|
110
|
+
"DATA_DIR": str(self.data_dir),
|
|
111
|
+
"WORK_DIR": str(self.work_dir),
|
|
112
|
+
"AGENT_API_NAME": "MyAgent",
|
|
113
|
+
"AGENT_VERSION": "v1",
|
|
114
|
+
}
|
|
115
|
+
self._env.update(self.env_overrides)
|
|
116
|
+
|
|
117
|
+
self._saved_env = dict(os.environ)
|
|
118
|
+
os.environ.update(self._env)
|
|
119
|
+
|
|
120
|
+
self._patches = [
|
|
121
|
+
mock.patch.object(config, "CACHE_ROOT", self.cache_root),
|
|
122
|
+
]
|
|
123
|
+
for p in self._patches:
|
|
124
|
+
p.start()
|
|
125
|
+
return self
|
|
126
|
+
|
|
127
|
+
def __exit__(self, *exc):
|
|
128
|
+
for p in self._patches:
|
|
129
|
+
p.stop()
|
|
130
|
+
# Restore env
|
|
131
|
+
for k in self._env:
|
|
132
|
+
os.environ.pop(k, None)
|
|
133
|
+
os.environ.update(self._saved_env)
|
|
134
|
+
self._tmp.cleanup()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _run_main_capture() -> tuple[int, str]:
|
|
138
|
+
"""Call main() with stdout captured. Translates SystemExit into rc."""
|
|
139
|
+
buf = io.StringIO()
|
|
140
|
+
rc: int
|
|
141
|
+
try:
|
|
142
|
+
with mock.patch("sys.stdout", buf):
|
|
143
|
+
rc = cache_check.main()
|
|
144
|
+
except SystemExit as e:
|
|
145
|
+
rc = e.code if isinstance(e.code, int) else 0
|
|
146
|
+
return rc, buf.getvalue()
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# -----------------------------------------------------------------------------
|
|
150
|
+
# Hit path
|
|
151
|
+
# -----------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class CacheHitTests(unittest.TestCase):
|
|
155
|
+
|
|
156
|
+
def test_hit_emits_cache_hit_true_with_full_export_block(self):
|
|
157
|
+
with _CacheHarness():
|
|
158
|
+
rc, out = _run_main_capture()
|
|
159
|
+
self.assertEqual(rc, 0)
|
|
160
|
+
self.assertIn("CACHE_HIT=true", out)
|
|
161
|
+
# Full hit block has these signature fields
|
|
162
|
+
self.assertIn("CACHED_AT_UTC=", out)
|
|
163
|
+
self.assertIn("NODE_COUNT=", out)
|
|
164
|
+
self.assertIn("DEPTH=", out)
|
|
165
|
+
self.assertIn("AGENT_GENERATION=", out)
|
|
166
|
+
self.assertIn("BOT_ID=", out)
|
|
167
|
+
self.assertIn("PLANNER_NAME=", out)
|
|
168
|
+
|
|
169
|
+
def test_hit_copies_tree_into_data_dir(self):
|
|
170
|
+
with _CacheHarness() as h:
|
|
171
|
+
rc, _ = _run_main_capture()
|
|
172
|
+
self.assertEqual(rc, 0)
|
|
173
|
+
dst = h.data_dir / "MyAgent_v1_metadata_tree.json"
|
|
174
|
+
# File search across resolved tree to dodge /private/var ↔ /var.
|
|
175
|
+
written = list(h.tmpdir.rglob("MyAgent_v1_metadata_tree.json"))
|
|
176
|
+
self.assertGreaterEqual(len(written), 1)
|
|
177
|
+
|
|
178
|
+
def test_hit_stages_declared_action_tree_in_work_dir(self):
|
|
179
|
+
with _CacheHarness() as h:
|
|
180
|
+
_run_main_capture()
|
|
181
|
+
staged = list(h.tmpdir.rglob("declared_action_tree.json"))
|
|
182
|
+
self.assertGreaterEqual(len(staged), 1)
|
|
183
|
+
|
|
184
|
+
def test_hit_emits_kind_counts_with_kc_prefix(self):
|
|
185
|
+
with _CacheHarness():
|
|
186
|
+
_, out = _run_main_capture()
|
|
187
|
+
self.assertIn("KC_TOPIC=", out)
|
|
188
|
+
self.assertIn("KC_STANDARD_ACTION=", out)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# -----------------------------------------------------------------------------
|
|
192
|
+
# Miss paths (each one exits 0 with CACHE_HIT=false)
|
|
193
|
+
# -----------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class CacheMissTests(unittest.TestCase):
|
|
197
|
+
|
|
198
|
+
def test_miss_when_force_refresh_true(self):
|
|
199
|
+
with _CacheHarness(env_overrides={"FORCE_REFRESH": "true"}):
|
|
200
|
+
rc, out = _run_main_capture()
|
|
201
|
+
self.assertEqual(rc, 0)
|
|
202
|
+
self.assertIn("CACHE_HIT=false", out)
|
|
203
|
+
|
|
204
|
+
def test_miss_when_manifest_missing(self):
|
|
205
|
+
# Build harness without auto-planted manifest
|
|
206
|
+
with _CacheHarness(manifest={}, plant_data_file=False) as h:
|
|
207
|
+
(h.cache_dir / "manifest.json").unlink()
|
|
208
|
+
rc, out = _run_main_capture()
|
|
209
|
+
self.assertEqual(rc, 0)
|
|
210
|
+
self.assertIn("CACHE_HIT=false", out)
|
|
211
|
+
|
|
212
|
+
def test_miss_when_manifest_malformed(self):
|
|
213
|
+
with _CacheHarness(manifest={}) as h:
|
|
214
|
+
(h.cache_dir / "manifest.json").write_text("<<<not json>>>")
|
|
215
|
+
rc, out = _run_main_capture()
|
|
216
|
+
self.assertEqual(rc, 0)
|
|
217
|
+
self.assertIn("CACHE_HIT=false", out)
|
|
218
|
+
|
|
219
|
+
def test_miss_with_invalidated_reason_on_schema_mismatch(self):
|
|
220
|
+
# Schema version != config.SCHEMA_VERSION → rmtree + miss
|
|
221
|
+
with _CacheHarness(manifest=_build_valid_manifest(
|
|
222
|
+
data_path="/tmp/x", schema_version="2.0",
|
|
223
|
+
)) as h:
|
|
224
|
+
rc, out = _run_main_capture()
|
|
225
|
+
self.assertEqual(rc, 0)
|
|
226
|
+
self.assertIn("CACHE_HIT=false", out)
|
|
227
|
+
self.assertIn("CACHE_INVALIDATED_REASON=schema-version-mismatch", out)
|
|
228
|
+
# Cache dir got rmtree'd
|
|
229
|
+
self.assertFalse(h.cache_dir.exists())
|
|
230
|
+
|
|
231
|
+
def test_miss_when_data_path_missing(self):
|
|
232
|
+
with _CacheHarness(manifest=_build_valid_manifest(
|
|
233
|
+
data_path="/nope/not-a-real-path.json",
|
|
234
|
+
)):
|
|
235
|
+
rc, out = _run_main_capture()
|
|
236
|
+
self.assertEqual(rc, 0)
|
|
237
|
+
self.assertIn("CACHE_HIT=false", out)
|
|
238
|
+
|
|
239
|
+
def test_miss_when_built_at_unparseable(self):
|
|
240
|
+
with _CacheHarness() as h:
|
|
241
|
+
(h.cache_dir / "manifest.json").write_text(json.dumps(
|
|
242
|
+
_build_valid_manifest(
|
|
243
|
+
data_path=str(h.tree_path),
|
|
244
|
+
built_at_utc="not-a-timestamp",
|
|
245
|
+
),
|
|
246
|
+
))
|
|
247
|
+
rc, out = _run_main_capture()
|
|
248
|
+
self.assertEqual(rc, 0)
|
|
249
|
+
self.assertIn("CACHE_HIT=false", out)
|
|
250
|
+
|
|
251
|
+
def test_miss_when_ttl_expired(self):
|
|
252
|
+
# built_at_utc is far in the past + ttl_days=1 → age > ttl → miss
|
|
253
|
+
ancient = (dt.datetime.now(dt.timezone.utc)
|
|
254
|
+
- dt.timedelta(days=30)).isoformat().replace("+00:00", "Z")
|
|
255
|
+
with _CacheHarness(manifest=None) as h:
|
|
256
|
+
(h.cache_dir / "manifest.json").write_text(json.dumps(
|
|
257
|
+
_build_valid_manifest(
|
|
258
|
+
data_path=str(h.tree_path),
|
|
259
|
+
built_at_utc=ancient,
|
|
260
|
+
ttl_days=1,
|
|
261
|
+
),
|
|
262
|
+
))
|
|
263
|
+
rc, out = _run_main_capture()
|
|
264
|
+
self.assertEqual(rc, 0)
|
|
265
|
+
self.assertIn("CACHE_HIT=false", out)
|
|
266
|
+
|
|
267
|
+
def test_miss_when_required_env_missing(self):
|
|
268
|
+
with _CacheHarness() as h:
|
|
269
|
+
os.environ.pop("CACHE_DIR", None)
|
|
270
|
+
buf = io.StringIO()
|
|
271
|
+
rc: int
|
|
272
|
+
try:
|
|
273
|
+
with mock.patch("sys.stdout", buf):
|
|
274
|
+
with mock.patch("sys.stderr", io.StringIO()):
|
|
275
|
+
rc = cache_check.main()
|
|
276
|
+
except SystemExit as e:
|
|
277
|
+
rc = e.code if isinstance(e.code, int) else 0
|
|
278
|
+
self.assertEqual(rc, 0)
|
|
279
|
+
self.assertIn("CACHE_HIT=false", buf.getvalue())
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if __name__ == "__main__":
|
|
283
|
+
unittest.main()
|