apm-cli 0.9.0__tar.gz → 0.9.1__tar.gz
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.
- {apm_cli-0.9.0/src/apm_cli.egg-info → apm_cli-0.9.1}/PKG-INFO +1 -1
- {apm_cli-0.9.0 → apm_cli-0.9.1}/pyproject.toml +1 -1
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/cli.py +2 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/audit.py +74 -29
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/install.py +181 -2
- apm_cli-0.9.1/src/apm_cli/commands/policy.py +378 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/command_logger.py +260 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/context.py +8 -0
- apm_cli-0.9.1/src/apm_cli/install/errors.py +50 -0
- apm_cli-0.9.1/src/apm_cli/install/phases/policy_gate.py +196 -0
- apm_cli-0.9.1/src/apm_cli/install/phases/policy_target_check.py +114 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/pipeline.py +59 -1
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/request.py +1 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/service.py +1 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/__init__.py +4 -2
- apm_cli-0.9.1/src/apm_cli/policy/discovery.py +1377 -0
- apm_cli-0.9.1/src/apm_cli/policy/install_preflight.py +219 -0
- apm_cli-0.9.1/src/apm_cli/policy/outcome_routing.py +190 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/parser.py +15 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/policy_checks.py +160 -32
- apm_cli-0.9.1/src/apm_cli/policy/project_config.py +230 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/schema.py +1 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/diagnostics.py +59 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1/src/apm_cli.egg-info}/PKG-INFO +1 -1
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli.egg-info/SOURCES.txt +7 -0
- apm_cli-0.9.0/src/apm_cli/policy/discovery.py +0 -427
- {apm_cli-0.9.0 → apm_cli-0.9.1}/AUTHORS +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/LICENSE +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/README.md +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/setup.cfg +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/base.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/codex.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/copilot.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/cursor.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/opencode.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/vscode.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/package_manager/base.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/bundle/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/bundle/lockfile_enrichment.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/bundle/packer.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/bundle/plugin_exporter.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/bundle/unpacker.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/_helpers.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/compile/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/compile/cli.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/compile/watcher.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/config.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/deps/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/deps/_utils.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/deps/cli.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/init.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/list_cmd.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/marketplace.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/mcp.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/outdated.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/pack.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/prune.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/run.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/runtime.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/uninstall/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/uninstall/cli.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/uninstall/engine.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/update.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/view.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/agents_compiler.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/claude_formatter.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/constants.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/constitution.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/constitution_block.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/context_optimizer.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/distributed_compiler.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/injector.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/link_resolver.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/template_builder.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/config.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/constants.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/auth.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/conflict_detector.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/docker_args.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/operations.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/safe_installer.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/scope.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/script_runner.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/target_detection.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/token_manager.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/aggregator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/apm_resolver.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/artifactory_entry.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/collection_parser.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/dependency_graph.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/github_downloader.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/installed_package.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/lockfile.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/package_validator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/plugin_parser.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/registry_proxy.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/transport_selection.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/verifier.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/drift.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/factory.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/helpers/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/helpers/security_scan.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/insecure_policy.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/mcp_registry.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/mcp_warnings.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/cleanup.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/download.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/finalize.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/integrate.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/local_content.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/lockfile.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/post_deps_local.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/resolve.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/targets.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/presentation/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/presentation/dry_run.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/services.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/sources.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/template.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/validation.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/agent_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/base_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/cleanup.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/command_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/coverage.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/dispatch.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/hook_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/instruction_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/mcp_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/prompt_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/skill_integrator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/skill_transformer.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/targets.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/utils.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/client.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/errors.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/models.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/registry.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/resolver.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/shadow_detector.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/validator.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/version_pins.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/apm_package.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/dependency/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/dependency/mcp.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/dependency/reference.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/dependency/types.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/plugin.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/results.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/validation.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/output/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/output/formatters.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/output/models.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/output/script_formatters.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/ci_checks.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/inheritance.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/matcher.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/models.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/primitives/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/primitives/discovery.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/primitives/models.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/primitives/parser.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/registry/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/registry/client.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/registry/integration.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/registry/operations.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/base.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/codex_runtime.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/copilot_runtime.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/factory.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/llm_runtime.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/manager.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/security/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/security/audit_report.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/security/content_scanner.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/security/file_scanner.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/security/gate.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/update_policy.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/console.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/content_hash.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/exclude.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/file_ops.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/github_host.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/helpers.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/path_security.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/paths.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/version_checker.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/yaml_io.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/version.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/workflow/__init__.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/workflow/discovery.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/workflow/parser.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/workflow/runner.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli.egg-info/dependency_links.txt +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli.egg-info/entry_points.txt +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli.egg-info/requires.txt +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli.egg-info/top_level.txt +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_apm_package_models.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_apm_resolver.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_codex_docker_args_fix.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_codex_empty_string_and_defaults.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_collision_integration.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_console.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_distributed_compilation.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_empty_string_and_defaults.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_enhanced_discovery.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_github_downloader.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_github_downloader_token_precedence.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_lockfile.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_runnable_prompts.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_runtime_manager_token_precedence.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_token_manager.py +0 -0
- {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_virtual_package_multi_install.py +0 -0
|
@@ -28,6 +28,7 @@ from apm_cli.commands.marketplace import marketplace, search as marketplace_sear
|
|
|
28
28
|
from apm_cli.commands.mcp import mcp
|
|
29
29
|
from apm_cli.commands.outdated import outdated as outdated_cmd
|
|
30
30
|
from apm_cli.commands.pack import pack_cmd, unpack_cmd
|
|
31
|
+
from apm_cli.commands.policy import policy
|
|
31
32
|
from apm_cli.commands.prune import prune
|
|
32
33
|
from apm_cli.commands.run import preview, run
|
|
33
34
|
from apm_cli.commands.runtime import runtime
|
|
@@ -82,6 +83,7 @@ cli.add_command(list_cmd, name="list")
|
|
|
82
83
|
cli.add_command(config)
|
|
83
84
|
cli.add_command(runtime)
|
|
84
85
|
cli.add_command(mcp)
|
|
86
|
+
cli.add_command(policy)
|
|
85
87
|
cli.add_command(outdated_cmd, name="outdated")
|
|
86
88
|
cli.add_command(marketplace)
|
|
87
89
|
cli.add_command(marketplace_search, name="search")
|
|
@@ -447,6 +447,15 @@ def _render_ci_results(ci_result: "CIAuditResult") -> None:
|
|
|
447
447
|
is_flag=True,
|
|
448
448
|
help="Force fresh policy fetch (skip cache).",
|
|
449
449
|
)
|
|
450
|
+
@click.option(
|
|
451
|
+
"--no-policy",
|
|
452
|
+
"no_policy",
|
|
453
|
+
is_flag=True,
|
|
454
|
+
help=(
|
|
455
|
+
"Skip org policy discovery and enforcement. "
|
|
456
|
+
"Overridden when --policy is passed explicitly."
|
|
457
|
+
),
|
|
458
|
+
)
|
|
450
459
|
@click.option(
|
|
451
460
|
"--no-fail-fast",
|
|
452
461
|
"no_fail_fast",
|
|
@@ -454,7 +463,7 @@ def _render_ci_results(ci_result: "CIAuditResult") -> None:
|
|
|
454
463
|
help="Run all checks even after a failure (default: stop at first failure).",
|
|
455
464
|
)
|
|
456
465
|
@click.pass_context
|
|
457
|
-
def audit(ctx, package, file_path, strip, verbose, dry_run, output_format, output_path, ci, policy_source, no_cache, no_fail_fast):
|
|
466
|
+
def audit(ctx, package, file_path, strip, verbose, dry_run, output_format, output_path, ci, policy_source, no_cache, no_policy, no_fail_fast):
|
|
458
467
|
"""Scan deployed prompt files for hidden Unicode characters.
|
|
459
468
|
|
|
460
469
|
Detects invisible characters that could embed hidden instructions in
|
|
@@ -512,45 +521,81 @@ def audit(ctx, package, file_path, strip, verbose, dry_run, output_format, outpu
|
|
|
512
521
|
# Always run baseline checks
|
|
513
522
|
ci_result = run_baseline_checks(project_root, fail_fast=fail_fast)
|
|
514
523
|
|
|
515
|
-
#
|
|
516
|
-
|
|
517
|
-
|
|
524
|
+
# Resolve policy source: explicit --policy wins; otherwise mirror
|
|
525
|
+
# install's auto-discovery (closes #827) so CI catches sideloaded
|
|
526
|
+
# files via unmanaged-files checks. --no-policy skips discovery.
|
|
527
|
+
from ..policy.discovery import discover_policy, discover_policy_with_chain
|
|
528
|
+
from ..policy.project_config import (
|
|
529
|
+
read_project_fetch_failure_default,
|
|
530
|
+
)
|
|
518
531
|
|
|
532
|
+
fetch_result = None
|
|
533
|
+
if policy_source and (not fail_fast or ci_result.passed):
|
|
519
534
|
fetch_result = discover_policy(
|
|
520
535
|
project_root,
|
|
521
536
|
policy_override=policy_source,
|
|
522
537
|
no_cache=no_cache,
|
|
523
538
|
)
|
|
539
|
+
elif (
|
|
540
|
+
not policy_source
|
|
541
|
+
and not no_policy
|
|
542
|
+
and (not fail_fast or ci_result.passed)
|
|
543
|
+
):
|
|
544
|
+
# Auto-discovery (mirror install path)
|
|
545
|
+
fetch_result = discover_policy_with_chain(project_root)
|
|
546
|
+
# Treat outcomes that mean "no policy to enforce" as a no-op.
|
|
547
|
+
if fetch_result.outcome in ("absent", "no_git_remote", "empty", "disabled"):
|
|
548
|
+
fetch_result = None
|
|
549
|
+
|
|
550
|
+
if fetch_result is not None:
|
|
551
|
+
# Honor project-side fetch_failure_default when the org policy
|
|
552
|
+
# could not be fetched / parsed (closes #829). Default "warn"
|
|
553
|
+
# downgrades the previous unconditional sys.exit(1) into a log.
|
|
554
|
+
if fetch_result.error or (
|
|
555
|
+
fetch_result.outcome
|
|
556
|
+
in ("malformed", "cache_miss_fetch_fail", "garbage_response")
|
|
557
|
+
):
|
|
558
|
+
project_default = read_project_fetch_failure_default(project_root)
|
|
559
|
+
err_text = fetch_result.error or fetch_result.fetch_error or fetch_result.outcome
|
|
560
|
+
if project_default == "block":
|
|
561
|
+
logger.error(
|
|
562
|
+
f"Policy fetch failed: {err_text} "
|
|
563
|
+
"(policy.fetch_failure_default=block)"
|
|
564
|
+
)
|
|
565
|
+
sys.exit(1)
|
|
566
|
+
else:
|
|
567
|
+
logger.warning(
|
|
568
|
+
f"Policy fetch failed: {err_text}; "
|
|
569
|
+
"proceeding without policy checks "
|
|
570
|
+
"(set policy.fetch_failure_default=block in apm.yml to fail closed)"
|
|
571
|
+
)
|
|
572
|
+
fetch_result = None
|
|
524
573
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
sys.exit(1)
|
|
574
|
+
if fetch_result is not None and fetch_result.found:
|
|
575
|
+
policy_obj = fetch_result.policy
|
|
528
576
|
|
|
529
|
-
|
|
530
|
-
|
|
577
|
+
# Respect enforcement level
|
|
578
|
+
if policy_obj.enforcement == "off":
|
|
579
|
+
pass # Policy checks disabled
|
|
580
|
+
else:
|
|
581
|
+
from ..policy.models import CheckResult
|
|
531
582
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
583
|
+
policy_result = run_policy_checks(
|
|
584
|
+
project_root, policy_obj, fail_fast=fail_fast
|
|
585
|
+
)
|
|
586
|
+
if policy_obj.enforcement == "block":
|
|
587
|
+
ci_result.checks.extend(policy_result.checks)
|
|
535
588
|
else:
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
# enforcement == "warn": include results but don't fail
|
|
545
|
-
for check in policy_result.checks:
|
|
546
|
-
ci_result.checks.append(
|
|
547
|
-
CheckResult(
|
|
548
|
-
name=check.name,
|
|
549
|
-
passed=True, # downgrade to pass
|
|
550
|
-
message=check.message + (" (enforcement: warn)" if not check.passed else ""),
|
|
551
|
-
details=check.details,
|
|
552
|
-
)
|
|
589
|
+
# enforcement == "warn": include results but don't fail
|
|
590
|
+
for check in policy_result.checks:
|
|
591
|
+
ci_result.checks.append(
|
|
592
|
+
CheckResult(
|
|
593
|
+
name=check.name,
|
|
594
|
+
passed=True, # downgrade to pass
|
|
595
|
+
message=check.message + (" (enforcement: warn)" if not check.passed else ""),
|
|
596
|
+
details=check.details,
|
|
553
597
|
)
|
|
598
|
+
)
|
|
554
599
|
|
|
555
600
|
# Resolve effective format
|
|
556
601
|
effective_format = output_format
|
|
@@ -59,6 +59,7 @@ from apm_cli.install.phases.local_content import (
|
|
|
59
59
|
_has_local_apm_content,
|
|
60
60
|
_project_has_root_primitives,
|
|
61
61
|
)
|
|
62
|
+
from apm_cli.install.errors import PolicyViolationError
|
|
62
63
|
from apm_cli.install.insecure_policy import (
|
|
63
64
|
_InsecureDependencyInfo,
|
|
64
65
|
_allow_insecure_host_callback,
|
|
@@ -86,6 +87,64 @@ from ._helpers import (
|
|
|
86
87
|
_update_gitignore_for_apm_modules,
|
|
87
88
|
)
|
|
88
89
|
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# Manifest snapshot + rollback (W2-pkg-rollback, #827)
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
# When the user runs ``apm install <pkg>``, ``_validate_and_add_packages_to_apm_yml``
|
|
95
|
+
# mutates ``apm.yml`` BEFORE the install pipeline runs. If the pipeline fails
|
|
96
|
+
# (policy block, download error, etc.) the failed package would stay in
|
|
97
|
+
# ``apm.yml`` forever. These helpers snapshot the raw bytes before mutation
|
|
98
|
+
# and atomically restore on failure.
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def _restore_manifest_from_snapshot(
|
|
102
|
+
manifest_path: "Path",
|
|
103
|
+
snapshot: bytes,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Atomically restore ``apm.yml`` from a raw-bytes snapshot.
|
|
106
|
+
|
|
107
|
+
Uses temp-file + ``os.replace`` to avoid torn writes, mirroring the
|
|
108
|
+
W1 cache atomic-write pattern (``discovery.py``).
|
|
109
|
+
"""
|
|
110
|
+
import os
|
|
111
|
+
import tempfile
|
|
112
|
+
|
|
113
|
+
fd, tmp_name = tempfile.mkstemp(
|
|
114
|
+
prefix="apm-restore-", dir=str(manifest_path.parent),
|
|
115
|
+
)
|
|
116
|
+
try:
|
|
117
|
+
with os.fdopen(fd, "wb") as fh:
|
|
118
|
+
fh.write(snapshot)
|
|
119
|
+
os.replace(tmp_name, str(manifest_path))
|
|
120
|
+
except Exception:
|
|
121
|
+
try:
|
|
122
|
+
os.unlink(tmp_name)
|
|
123
|
+
except OSError:
|
|
124
|
+
pass
|
|
125
|
+
raise
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _maybe_rollback_manifest(
|
|
129
|
+
manifest_path: "Path",
|
|
130
|
+
snapshot: "bytes | None",
|
|
131
|
+
logger: "InstallLogger",
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Restore ``apm.yml`` from *snapshot* if one was captured, then log.
|
|
134
|
+
|
|
135
|
+
No-op when *snapshot* is ``None`` (i.e. the command was not
|
|
136
|
+
``apm install <pkg>`` or the manifest did not exist before mutation).
|
|
137
|
+
"""
|
|
138
|
+
if snapshot is None:
|
|
139
|
+
return
|
|
140
|
+
try:
|
|
141
|
+
_restore_manifest_from_snapshot(manifest_path, snapshot)
|
|
142
|
+
logger.progress("apm.yml restored to its previous state.")
|
|
143
|
+
except Exception:
|
|
144
|
+
# Best-effort: if the restore itself fails, warn but don't mask
|
|
145
|
+
# the original exception that triggered the rollback.
|
|
146
|
+
logger.warning("Failed to restore apm.yml to its previous state.")
|
|
147
|
+
|
|
89
148
|
# CRITICAL: Shadow Python builtins that share names with Click commands
|
|
90
149
|
set = builtins.set
|
|
91
150
|
list = builtins.list
|
|
@@ -990,8 +1049,15 @@ def _run_mcp_install(
|
|
|
990
1049
|
"or a stdio command (self-defined entries)."
|
|
991
1050
|
),
|
|
992
1051
|
)
|
|
1052
|
+
@click.option(
|
|
1053
|
+
"--no-policy",
|
|
1054
|
+
"no_policy",
|
|
1055
|
+
is_flag=True,
|
|
1056
|
+
default=False,
|
|
1057
|
+
help="Skip org policy enforcement for this invocation. Loudly logged. Does NOT bypass apm audit --ci.",
|
|
1058
|
+
)
|
|
993
1059
|
@click.pass_context
|
|
994
|
-
def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbose, trust_transitive_mcp, parallel_downloads, dev, target, allow_insecure, allow_insecure_hosts, global_, use_ssh, use_https, allow_protocol_fallback, mcp_name, transport, url, env_pairs, header_pairs, mcp_version, registry_url):
|
|
1060
|
+
def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbose, trust_transitive_mcp, parallel_downloads, dev, target, allow_insecure, allow_insecure_hosts, global_, use_ssh, use_https, allow_protocol_fallback, mcp_name, transport, url, env_pairs, header_pairs, mcp_version, registry_url, no_policy):
|
|
995
1061
|
"""Install APM and MCP dependencies from apm.yml (like npm install).
|
|
996
1062
|
|
|
997
1063
|
Detects AI runtimes from your apm.yml scripts and installs MCP servers for
|
|
@@ -1028,6 +1094,14 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1028
1094
|
is_partial = bool(packages)
|
|
1029
1095
|
logger = InstallLogger(verbose=verbose, dry_run=dry_run, partial=is_partial)
|
|
1030
1096
|
|
|
1097
|
+
# W2-pkg-rollback (#827): snapshot bytes captured BEFORE
|
|
1098
|
+
# _validate_and_add_packages_to_apm_yml mutates apm.yml.
|
|
1099
|
+
# Initialised to None here so exception handlers always have it.
|
|
1100
|
+
_manifest_snapshot: "bytes | None" = None
|
|
1101
|
+
# manifest_path is set later (scope-dependent); keep a stable ref
|
|
1102
|
+
# so exception handlers can use it without NameError.
|
|
1103
|
+
_snapshot_manifest_path: "Path | None" = None
|
|
1104
|
+
|
|
1031
1105
|
# ----------------------------------------------------------------
|
|
1032
1106
|
# --mcp branch (W3): when --mcp is set, route to the dedicated
|
|
1033
1107
|
# MCP-add path. We compute the post-`--` argv here BEFORE Click's
|
|
@@ -1088,6 +1162,43 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1088
1162
|
mcp_scope = InstallScope.PROJECT
|
|
1089
1163
|
mcp_manifest_path = get_manifest_path(mcp_scope)
|
|
1090
1164
|
mcp_apm_dir = get_apm_dir(mcp_scope)
|
|
1165
|
+
# -- W2-mcp-preflight: policy enforcement before MCP install --
|
|
1166
|
+
# Build a lightweight MCPDependency for policy evaluation.
|
|
1167
|
+
# This mirrors _build_mcp_entry routing but we only need the
|
|
1168
|
+
# fields that policy checks inspect (name, transport, registry).
|
|
1169
|
+
from ..models.dependency.mcp import MCPDependency as _MCPDep
|
|
1170
|
+
from ..policy.install_preflight import (
|
|
1171
|
+
PolicyBlockError,
|
|
1172
|
+
run_policy_preflight,
|
|
1173
|
+
)
|
|
1174
|
+
|
|
1175
|
+
_is_self_defined = bool(url or command_argv)
|
|
1176
|
+
_preflight_transport = transport
|
|
1177
|
+
if _preflight_transport is None:
|
|
1178
|
+
if command_argv:
|
|
1179
|
+
_preflight_transport = "stdio"
|
|
1180
|
+
elif url:
|
|
1181
|
+
_preflight_transport = "http"
|
|
1182
|
+
_preflight_dep = _MCPDep(
|
|
1183
|
+
name=mcp_name,
|
|
1184
|
+
transport=_preflight_transport,
|
|
1185
|
+
registry=False if _is_self_defined else None,
|
|
1186
|
+
url=url,
|
|
1187
|
+
)
|
|
1188
|
+
|
|
1189
|
+
try:
|
|
1190
|
+
_pf_result, _pf_active = run_policy_preflight(
|
|
1191
|
+
project_root=Path.cwd(),
|
|
1192
|
+
mcp_deps=[_preflight_dep],
|
|
1193
|
+
no_policy=no_policy,
|
|
1194
|
+
logger=logger,
|
|
1195
|
+
dry_run=dry_run,
|
|
1196
|
+
)
|
|
1197
|
+
except PolicyBlockError:
|
|
1198
|
+
# Diagnostics already emitted by the helper + logger.
|
|
1199
|
+
logger.render_summary()
|
|
1200
|
+
sys.exit(1)
|
|
1201
|
+
|
|
1091
1202
|
if dry_run:
|
|
1092
1203
|
# C1: validate eagerly so dry-run rejects what real install would.
|
|
1093
1204
|
_validate_mcp_dry_run_entry(
|
|
@@ -1186,6 +1297,15 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1186
1297
|
|
|
1187
1298
|
# If packages are specified, validate and add them to apm.yml first
|
|
1188
1299
|
if packages:
|
|
1300
|
+
# ── W2-pkg-rollback (#827): snapshot raw bytes BEFORE mutation ──
|
|
1301
|
+
# _validate_and_add_packages_to_apm_yml does a YAML round-trip
|
|
1302
|
+
# (load + dump) which may alter whitespace, key ordering, or
|
|
1303
|
+
# trailing newlines. We snapshot the raw bytes so rollback is
|
|
1304
|
+
# byte-exact -- no YAML drift.
|
|
1305
|
+
if manifest_path.exists():
|
|
1306
|
+
_manifest_snapshot = manifest_path.read_bytes()
|
|
1307
|
+
_snapshot_manifest_path = manifest_path
|
|
1308
|
+
|
|
1189
1309
|
validated_packages, outcome = _validate_and_add_packages_to_apm_yml(
|
|
1190
1310
|
packages, dry_run, dev=dev, logger=logger,
|
|
1191
1311
|
manifest_path=manifest_path, auth_resolver=auth_resolver,
|
|
@@ -1245,6 +1365,24 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1245
1365
|
|
|
1246
1366
|
# Show what will be installed if dry run
|
|
1247
1367
|
if dry_run:
|
|
1368
|
+
# -- W2-dry-run (#827): policy preflight in preview mode --
|
|
1369
|
+
# Runs discovery + checks against direct manifest deps (not
|
|
1370
|
+
# resolved/transitive -- dry-run does not run the resolver).
|
|
1371
|
+
# Block-severity violations render as "Would be blocked by
|
|
1372
|
+
# policy" without raising. Documented limitation: transitive
|
|
1373
|
+
# deps are NOT evaluated since the resolver does not run.
|
|
1374
|
+
from apm_cli.policy.install_preflight import run_policy_preflight as _dr_preflight
|
|
1375
|
+
|
|
1376
|
+
_dr_apm_deps = builtins.list(apm_deps) + builtins.list(dev_apm_deps)
|
|
1377
|
+
_dr_preflight(
|
|
1378
|
+
project_root=project_root,
|
|
1379
|
+
apm_deps=_dr_apm_deps,
|
|
1380
|
+
mcp_deps=mcp_deps if should_install_mcp else None,
|
|
1381
|
+
no_policy=no_policy,
|
|
1382
|
+
logger=logger,
|
|
1383
|
+
dry_run=True,
|
|
1384
|
+
)
|
|
1385
|
+
|
|
1248
1386
|
from apm_cli.install.presentation.dry_run import render_and_exit
|
|
1249
1387
|
|
|
1250
1388
|
render_and_exit(
|
|
@@ -1311,15 +1449,20 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1311
1449
|
),
|
|
1312
1450
|
protocol_pref=protocol_pref,
|
|
1313
1451
|
allow_protocol_fallback=allow_protocol_fallback,
|
|
1452
|
+
no_policy=no_policy,
|
|
1314
1453
|
)
|
|
1315
1454
|
apm_count = install_result.installed_count
|
|
1316
1455
|
prompt_count = install_result.prompts_integrated
|
|
1317
1456
|
agent_count = install_result.agents_integrated
|
|
1318
1457
|
apm_diagnostics = install_result.diagnostics
|
|
1319
1458
|
except InsecureDependencyPolicyError:
|
|
1459
|
+
_maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
|
|
1320
1460
|
sys.exit(1)
|
|
1321
1461
|
except Exception as e:
|
|
1322
|
-
|
|
1462
|
+
_maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
|
|
1463
|
+
# #832: surface PolicyViolationError verbatim (no double-nesting).
|
|
1464
|
+
msg = str(e) if isinstance(e, PolicyViolationError) else f"Failed to install APM dependencies: {e}"
|
|
1465
|
+
logger.error(msg)
|
|
1323
1466
|
if not verbose:
|
|
1324
1467
|
logger.progress("Run with --verbose for detailed diagnostics")
|
|
1325
1468
|
sys.exit(1)
|
|
@@ -1333,6 +1476,7 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1333
1476
|
clear_apm_yml_cache()
|
|
1334
1477
|
|
|
1335
1478
|
# Collect transitive MCP dependencies from resolved APM packages
|
|
1479
|
+
transitive_mcp = []
|
|
1336
1480
|
apm_modules_path = get_modules_dir(scope)
|
|
1337
1481
|
if should_install_mcp and apm_modules_path.exists():
|
|
1338
1482
|
lock_path = get_lockfile_path(apm_dir)
|
|
@@ -1344,6 +1488,37 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1344
1488
|
logger.verbose_detail(f"Collected {len(transitive_mcp)} transitive MCP dependency(ies)")
|
|
1345
1489
|
mcp_deps = MCPIntegrator.deduplicate(mcp_deps + transitive_mcp)
|
|
1346
1490
|
|
|
1491
|
+
# -- S1/S2 fix (#827-C2/C3): enforce policy on ALL MCP deps ----
|
|
1492
|
+
# The pipeline gate phase (policy_gate.py) checks direct APM deps
|
|
1493
|
+
# and direct MCP deps from apm.yml. However, transitive MCP
|
|
1494
|
+
# servers (discovered via collect_transitive above) are only known
|
|
1495
|
+
# after APM packages are installed. Run a second preflight
|
|
1496
|
+
# against the *merged* MCP set (direct + transitive) BEFORE
|
|
1497
|
+
# MCPIntegrator writes runtime configs. On PolicyBlockError we
|
|
1498
|
+
# abort the MCP write but leave already-installed APM packages
|
|
1499
|
+
# in place (they were approved by the gate phase).
|
|
1500
|
+
if should_install_mcp and mcp_deps:
|
|
1501
|
+
from apm_cli.policy.install_preflight import (
|
|
1502
|
+
PolicyBlockError as _TransitivePBE,
|
|
1503
|
+
run_policy_preflight as _transitive_preflight,
|
|
1504
|
+
)
|
|
1505
|
+
|
|
1506
|
+
try:
|
|
1507
|
+
_transitive_preflight(
|
|
1508
|
+
project_root=project_root,
|
|
1509
|
+
mcp_deps=mcp_deps,
|
|
1510
|
+
no_policy=no_policy,
|
|
1511
|
+
logger=logger,
|
|
1512
|
+
dry_run=False,
|
|
1513
|
+
)
|
|
1514
|
+
except _TransitivePBE:
|
|
1515
|
+
logger.error(
|
|
1516
|
+
"MCP server(s) blocked by org policy. "
|
|
1517
|
+
"APM packages remain installed; MCP configs were NOT written."
|
|
1518
|
+
)
|
|
1519
|
+
logger.render_summary()
|
|
1520
|
+
sys.exit(1)
|
|
1521
|
+
|
|
1347
1522
|
# Continue with MCP installation (existing logic)
|
|
1348
1523
|
mcp_count = 0
|
|
1349
1524
|
new_mcp_servers: builtins.set = builtins.set()
|
|
@@ -1407,12 +1582,14 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
|
|
|
1407
1582
|
sys.exit(1)
|
|
1408
1583
|
|
|
1409
1584
|
except InsecureDependencyPolicyError:
|
|
1585
|
+
_maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
|
|
1410
1586
|
sys.exit(1)
|
|
1411
1587
|
except click.UsageError:
|
|
1412
1588
|
# Conflict matrix / argv parser raises UsageError -- let Click
|
|
1413
1589
|
# render with exit code 2 and the standard "Usage: ..." prefix.
|
|
1414
1590
|
raise
|
|
1415
1591
|
except Exception as e:
|
|
1592
|
+
_maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
|
|
1416
1593
|
logger.error(f"Error installing dependencies: {e}")
|
|
1417
1594
|
if not verbose:
|
|
1418
1595
|
logger.progress("Run with --verbose for detailed diagnostics")
|
|
@@ -1463,6 +1640,7 @@ def _install_apm_dependencies(
|
|
|
1463
1640
|
marketplace_provenance: dict = None,
|
|
1464
1641
|
protocol_pref=None,
|
|
1465
1642
|
allow_protocol_fallback: "Optional[bool]" = None,
|
|
1643
|
+
no_policy: bool = False,
|
|
1466
1644
|
):
|
|
1467
1645
|
"""Thin wrapper -- builds an :class:`InstallRequest` and delegates to
|
|
1468
1646
|
:class:`apm_cli.install.service.InstallService`.
|
|
@@ -1494,5 +1672,6 @@ def _install_apm_dependencies(
|
|
|
1494
1672
|
marketplace_provenance=marketplace_provenance,
|
|
1495
1673
|
protocol_pref=protocol_pref,
|
|
1496
1674
|
allow_protocol_fallback=allow_protocol_fallback,
|
|
1675
|
+
no_policy=no_policy,
|
|
1497
1676
|
)
|
|
1498
1677
|
return InstallService().run(request)
|