controlzero 1.11.4__tar.gz → 1.11.6__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.
- {controlzero-1.11.4 → controlzero-1.11.6}/CHANGELOG.md +60 -1
- {controlzero-1.11.4 → controlzero-1.11.6}/PKG-INFO +1 -1
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/__init__.py +1 -1
- controlzero-1.11.6/controlzero/__main__.py +23 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/__main__.py +6 -1
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/doctor.py +3 -1
- controlzero-1.11.6/controlzero/cli/hook_entry.py +118 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/hosts/__init__.py +18 -1
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/hosts/antigravity.py +151 -15
- controlzero-1.11.6/controlzero/cli/hosts/base.py +532 -0
- controlzero-1.11.6/controlzero/cli/hosts/claude_code.py +191 -0
- controlzero-1.11.6/controlzero/cli/hosts/codex_cli.py +101 -0
- controlzero-1.11.6/controlzero/cli/hosts/cursor.py +322 -0
- controlzero-1.11.6/controlzero/cli/hosts/cursor_cli.py +223 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/hosts/gemini_cli.py +27 -7
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/hosts/kiro.py +53 -3
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/hosts/unknown.py +18 -2
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/kiro_adapter.py +52 -6
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/main.py +935 -55
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/antigravity.yaml +14 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/claude-code.yaml +14 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/codex-cli.yaml +14 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/cursor.yaml +14 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/gemini-cli.yaml +14 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/generic.yaml +14 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/kiro/kiro.yaml +14 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/error_codes.yaml +22 -3
- {controlzero-1.11.4 → controlzero-1.11.6}/pyproject.toml +8 -2
- controlzero-1.11.6/tests/_fixtures/antigravity-envelope-expected.json +146 -0
- controlzero-1.11.6/tests/test_adapter_interface.py +390 -0
- controlzero-1.11.6/tests/test_agy_docs_no_exit2_1373.py +131 -0
- controlzero-1.11.6/tests/test_antigravity_adapter.py +519 -0
- controlzero-1.11.6/tests/test_antigravity_envelope_parity.py +96 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_antigravity_ga_blockers_1248.py +3 -3
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_antigravity_hook_check.py +4 -3
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_antigravity_install.py +48 -0
- controlzero-1.11.6/tests/test_capability_matrix_doc.py +175 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_cli_hook.py +15 -3
- controlzero-1.11.6/tests/test_cli_hook_entry.py +189 -0
- controlzero-1.11.6/tests/test_cursor_adapter.py +362 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_device.py +25 -38
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hosts_adapter.py +207 -33
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_install_hooks.py +24 -11
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_kiro_adapter.py +98 -7
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_kiro_cli_hook_pin_1265.py +13 -1
- controlzero-1.11.6/tests/test_kiro_install.py +463 -0
- controlzero-1.11.6/tests/test_policy_load_failclosed_f3.py +279 -0
- controlzero-1.11.6/tests/test_probe_audit_isolation.py +177 -0
- controlzero-1.11.4/controlzero/__main__.py +0 -12
- controlzero-1.11.4/controlzero/cli/hosts/base.py +0 -171
- controlzero-1.11.4/controlzero/cli/hosts/claude_code.py +0 -135
- controlzero-1.11.4/controlzero/cli/hosts/codex_cli.py +0 -54
- controlzero-1.11.4/tests/test_antigravity_adapter.py +0 -281
- controlzero-1.11.4/tests/test_kiro_install.py +0 -75
- {controlzero-1.11.4 → controlzero-1.11.6}/.gitignore +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/Dockerfile.test +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/LICENSE +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/README.md +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/enforcer.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/_internal/types.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/audit_local.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/audit_remote.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/canonical.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/console.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/exec_cmd.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/cli/update_cmd.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/client.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/device.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/enrollment.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/error_codes.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/errors.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/hitl/status.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/hosted_policy.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/google.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/layout_migration.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/policy_loader.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/secrets/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/secrets/reference.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/secrets/resolver.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/secrets/secretstr.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/_keyring.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/_state.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/tamper.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/controlzero/tracecontext.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/examples/hello_world.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/conftest.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/integrations/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/integrations/test_google.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/__init__.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/conftest.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_durable_default_tamper.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_keychain_dek.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_action_aliases.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_antigravity_posttooluse_observe_58.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_antigravity_tool_vocab_1303.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_audit_remote.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_bundle_cache_freshness_1303.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_cli_init.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_cli_tail.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_cli_test.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_cli_validate.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_coding_agent_hooks.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_conditions.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_config_format_parity_1303.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_conformance.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_console.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_credential_hook.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_default_action.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_doctor.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_enrollment.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_epic_1247_bryan_acceptance.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_error_codes.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_failopen_1303.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_glob_matching.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_6a_request_approval.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_phase2b_protocol.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hosted_local_audit_1247.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hosted_local_shadow_warn_1265.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_install_hook_command.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_kiro_cli_e2e.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_kiro_hook_templates.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_log_rotation.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_migrate.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_observe_mode_1247.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_part3_active_policy_count_1303.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_policy_settings.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_quarantine.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_reason_code.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_refresh.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_secrets.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_secrets_reference_resolver.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_synthetic_policy_id_t79.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_tamper.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_tracecontext.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_update_cmd.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tests/test_upgrade_nudge.py +0 -0
- {controlzero-1.11.4 → controlzero-1.11.6}/tools/cz-kiro-adapter +0 -0
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.11.6 -- 2026-06-21 (cross-surface enforcement hardening: Kiro v3 + Antigravity honesty + warn-mode)
|
|
4
|
+
|
|
5
|
+
Ships the #1373 / #65 cross-surface enforcement work and follow-ups that landed
|
|
6
|
+
after 1.11.5 was published.
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **Kiro CLI v3 enforcement (#1375 / #1378).** The shipping kiro-cli (>= 2.8.x)
|
|
11
|
+
enforces ONLY via an agent-config `hooks.preToolUse` block on the default
|
|
12
|
+
agent. `controlzero install kiro` now writes `~/.kiro/agents/controlzero.json`
|
|
13
|
+
(matchers `execute_bash` / `fs_write` / `fs_read` / `use_aws`, interpreter-
|
|
14
|
+
pinned `hook-check --strict`) and sets it as the default agent. The legacy v2
|
|
15
|
+
embedded `settings.json` hooks and standalone `.kiro/hooks/*.json` are ignored
|
|
16
|
+
by the shipping CLI. Host-verified on kiro-cli 2.8.1: a denied bash command is
|
|
17
|
+
blocked; an allowed file read proceeds.
|
|
18
|
+
- **Crash-proof hook entry (#1189 / #1377).** `python -m controlzero` and the
|
|
19
|
+
console scripts route through the crash-proof `hook_entry`, so a broken
|
|
20
|
+
install fails CLOSED, not open.
|
|
21
|
+
- **Kiro deciding-gate fail-close (#1377).** A non-{0,2} hook exit is treated as
|
|
22
|
+
a block, not a silent allow.
|
|
23
|
+
- **Antigravity #202 honesty (#1373 / #1377).** The agy adapter is `observe`
|
|
24
|
+
(covered families `file_read` / `file_search`), not a false `enforced` claim;
|
|
25
|
+
it emits the documented stdout `{"decision":...}` envelope. agy default-mode
|
|
26
|
+
enforcement is host-side (COMING SOON), not SDK-fixable.
|
|
27
|
+
- **Audit-probe isolation (#1375 / #1377).** kiro/cursor init + verify self-test
|
|
28
|
+
probes run with an isolated HOME, so synthetic denies never pollute
|
|
29
|
+
`~/.controlzero/audit.log`.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **`default_action` posture toggle is first-class (#1374).** The no-rule-match
|
|
34
|
+
posture (`deny` default | `warn` | `allow`) is documented + surfaced in the
|
|
35
|
+
init templates, with a new deny-list recipe. Comment-only in templates; the
|
|
36
|
+
default stays `deny`.
|
|
37
|
+
- **Kiro sub-agent bypass warning (#7671).** `controlzero install kiro` warns
|
|
38
|
+
that Kiro does not fire preToolUse hooks for sub-agent tool calls (upstream
|
|
39
|
+
kiro #7671) -- direct tool calls are governed, subagent calls are not.
|
|
40
|
+
|
|
41
|
+
## 1.11.5 -- 2026-06-19 (cross-surface CONTROLZERO_CLIENT foreign-pin fail-open)
|
|
42
|
+
|
|
43
|
+
Closes a routing fail-open where an explicit `CONTROLZERO_CLIENT` pin -- set by
|
|
44
|
+
`controlzero install <host>` so a kiro/codex/antigravity/cursor hook routes to
|
|
45
|
+
the right adapter -- was NOT authoritative. The `ClaudeCodeAdapter` is first in
|
|
46
|
+
the registry and falls through to ambient Anthropic env hints (`CLAUDECODE` /
|
|
47
|
+
`CLAUDE_CODE` / ...), so a correctly installed foreign-host hook that ALSO
|
|
48
|
+
carried an ambient `CLAUDECODE` (e.g. launched from a Claude Code-spawned shell)
|
|
49
|
+
was hijacked to the Claude adapter, which renders the wrong wire shape and reads
|
|
50
|
+
as allow on a destructive tool call.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- **Foreign `CONTROLZERO_CLIENT` pin now defers (cross-surface fail-open).**
|
|
55
|
+
Every host adapter's `claim()` defers when `CONTROLZERO_CLIENT` names a
|
|
56
|
+
DIFFERENT client, via the shared `HostAdapter._pinned_elsewhere` helper, so an
|
|
57
|
+
installer-pinned hook can never be hijacked by an ambient `CLAUDECODE`;
|
|
58
|
+
`UnknownHostAdapter` stays the always-claim backstop. A 7-surface guard matrix
|
|
59
|
+
(`tests/test_hosts_adapter.py`) pins it fail-if-reverted. Mirrors the Node SDK
|
|
60
|
+
fix. gh#81.
|
|
61
|
+
|
|
3
62
|
## 1.11.4 -- 2026-06-17 (close the count==0 degraded fail-open + stale-empty-cache replay + cached-bundle integrity, gh#1303)
|
|
4
63
|
|
|
5
64
|
Closes a degraded-bundle fail-open in the count-provenance discriminator plus
|
|
@@ -715,7 +774,7 @@ secret-value-leak guard that gates every outbound HITL payload.
|
|
|
715
774
|
POSTs a HITL approval request, builds the body from the
|
|
716
775
|
`PolicyDecision`, threads in `X-CZ-Requestor-Email` from
|
|
717
776
|
`~/.controlzero/config.yaml`, sends a per-call `Idempotency-Key`, and
|
|
718
|
-
returns a `PendingApproval`. Maps backend error codes
|
|
777
|
+
returns a `PendingApproval`. Maps backend error codes E1705-E1708 onto
|
|
719
778
|
the matching SDK exceptions. (#577)
|
|
720
779
|
|
|
721
780
|
### Conformance
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.11.
|
|
3
|
+
Version: 1.11.6
|
|
4
4
|
Summary: AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup.
|
|
5
5
|
Project-URL: Homepage, https://controlzero.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.controlzero.ai
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Enable ``python -m controlzero`` to run the CLI.
|
|
2
|
+
|
|
3
|
+
The Kiro CLI / Cursor hook commands pin the interpreter as
|
|
4
|
+
``"<sys.executable>" -m controlzero hook-check --strict`` (#1265) so the host
|
|
5
|
+
invokes the exact, importable controlzero the user installed, never a bare
|
|
6
|
+
``controlzero`` that PATH might resolve to a broken or shadowing install.
|
|
7
|
+
|
|
8
|
+
CRITICAL (#1373 / re-opens #1189): this entry MUST route through the crash-proof
|
|
9
|
+
``hook_entry.cli`` shim, NOT ``controlzero.cli.main:cli`` directly. The
|
|
10
|
+
interpreter-pin made every Kiro/Cursor hook run ``python -m controlzero
|
|
11
|
+
hook-check`` -> this module. A direct unguarded ``from controlzero.cli.main
|
|
12
|
+
import cli`` raises a bare nonzero traceback on a broken/partial install (missing
|
|
13
|
+
transitive dep, packaging defect); Kiro reads any exit code that is neither 0
|
|
14
|
+
nor 2 as "warn / proceed" -> the tool RUNS while audit may log a deny. Routing
|
|
15
|
+
through ``hook_entry.cli`` fails SAFE (exit 2 = block) on an import crash for a
|
|
16
|
+
hook invocation -- closing the exact #1189 fail-open the console script already
|
|
17
|
+
fixed but ``-m controlzero`` had silently bypassed.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from controlzero.cli.hook_entry import cli
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
cli()
|
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
Mirrors the ``controlzero`` console-script entry point. Used by
|
|
4
4
|
``controlzero kiro verify`` to invoke hook-check as a subprocess in a way
|
|
5
5
|
that works even when the console script is not on PATH (e.g. CI, venvs).
|
|
6
|
+
|
|
7
|
+
Routes through the crash-proof ``hook_entry.cli`` shim (#1189 / #1373): this is
|
|
8
|
+
a hook-invocation path, so an import crash on a broken install must fail SAFE
|
|
9
|
+
(exit 2 = block), never escape as a bare nonzero traceback that a host reads as
|
|
10
|
+
"warn / proceed".
|
|
6
11
|
"""
|
|
7
12
|
|
|
8
13
|
from __future__ import annotations
|
|
9
14
|
|
|
10
|
-
from controlzero.cli.
|
|
15
|
+
from controlzero.cli.hook_entry import cli
|
|
11
16
|
|
|
12
17
|
if __name__ == "__main__":
|
|
13
18
|
cli()
|
|
@@ -353,7 +353,9 @@ def _antigravity_dry_run_roundtrips() -> bool:
|
|
|
353
353
|
adapter = select_adapter({}, {"CONTROLZERO_CLIENT": "antigravity"})
|
|
354
354
|
if getattr(adapter, "name", "") != "antigravity":
|
|
355
355
|
return False
|
|
356
|
-
|
|
356
|
+
# render_envelope is the per-host wire dict; render() folds it into a
|
|
357
|
+
# RenderResult (exit code + stdout). The dry-run inspects the envelope.
|
|
358
|
+
out = adapter.render_envelope(
|
|
357
359
|
CZDecision(effect="allow", reason="doctor dry-run", reason_code="RULE_MATCH")
|
|
358
360
|
)
|
|
359
361
|
return isinstance(out, dict) and out.get("decision") == "allow"
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Crash-proof console-script entry point for the ``controlzero`` CLI (#1189).
|
|
2
|
+
|
|
3
|
+
The ``controlzero`` console script declared in ``pyproject.toml`` MUST NOT be
|
|
4
|
+
pointed directly at ``controlzero.cli.main:cli``. The pip-generated wrapper
|
|
5
|
+
imports the target at module load with no guard, so a top-level import crash in
|
|
6
|
+
``controlzero/cli/main.py`` (a missing transitive dependency, a packaging
|
|
7
|
+
defect, a syntax error in any eagerly-imported submodule) raises before
|
|
8
|
+
``cli()`` runs. The wrapper then exits nonzero with a traceback, and a host that
|
|
9
|
+
treats nonzero-with-no-decision as "proceed" (Claude Code's PreToolUse falls
|
|
10
|
+
back to allow on a hook it cannot evaluate) silently lets the tool call
|
|
11
|
+
through. That is a fail-OPEN on the governance hot path -- the exact defect
|
|
12
|
+
QA #1189 flagged.
|
|
13
|
+
|
|
14
|
+
This shim makes the entry point crash-proof:
|
|
15
|
+
|
|
16
|
+
* The heavy import of ``controlzero.cli.main`` happens inside a try/except
|
|
17
|
+
with a ``sys.path`` source-checkout fallback, mirroring
|
|
18
|
+
``tools/cz-kiro-adapter`` so a dev checkout that has not been pip-installed
|
|
19
|
+
still resolves the package.
|
|
20
|
+
* If the import or evaluation of the CLI cannot complete AT ALL and this is a
|
|
21
|
+
hook invocation (``hook-check`` on argv -- the governance hot path), the
|
|
22
|
+
shim fails SAFE: it writes a hard, human-readable error to stderr and exits
|
|
23
|
+
with the host's DENY/block code (Claude Code PreToolUse = exit 2), never a
|
|
24
|
+
silent allow.
|
|
25
|
+
* For a non-hook invocation (interactive ``controlzero ...`` usage) a broken
|
|
26
|
+
install should surface its traceback so the user can fix it -- there is no
|
|
27
|
+
governance decision at stake -- so the shim re-raises in that case.
|
|
28
|
+
|
|
29
|
+
What this shim does NOT change: the runtime fail-OPEN contract that lives
|
|
30
|
+
inside ``hook_check`` (E1501 bad-JSON, E1502 timeout, empty stdin, missing
|
|
31
|
+
tool_name) is intentional and is preserved. Those paths are reached only after
|
|
32
|
+
the CLI imports successfully and represent a *decision* to pass through, not an
|
|
33
|
+
import crash. This shim only converts the previously-silent import-crash
|
|
34
|
+
fall-through into an explicit fail-safe deny.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import os
|
|
40
|
+
import sys
|
|
41
|
+
|
|
42
|
+
# Host block/deny exit codes for the hook hot path. Claude Code's PreToolUse
|
|
43
|
+
# protocol treats exit 2 as "block"; Kiro reuses the same convention. We do not
|
|
44
|
+
# have the resolved host adapter available before the import succeeds, so we use
|
|
45
|
+
# the lowest-common-denominator block code (2), which every supported host
|
|
46
|
+
# interprets as deny rather than allow.
|
|
47
|
+
_HOOK_DENY_EXIT_CODE = 2
|
|
48
|
+
|
|
49
|
+
_HOOK_SUBCOMMANDS = frozenset({"hook-check"})
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _is_hook_invocation(argv: list[str]) -> bool:
|
|
53
|
+
"""True when the process was launched on the governance hot path.
|
|
54
|
+
|
|
55
|
+
The ``hook-check`` subcommand is what hosts (Claude Code, Kiro, Codex,
|
|
56
|
+
Gemini, Antigravity) wire into their PreToolUse hooks. Detecting it on argv
|
|
57
|
+
lets the shim choose fail-SAFE (deny) on an import crash for hook calls
|
|
58
|
+
while leaving interactive CLI usage to surface its traceback normally.
|
|
59
|
+
"""
|
|
60
|
+
return any(arg in _HOOK_SUBCOMMANDS for arg in argv[1:])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cli() -> None:
|
|
64
|
+
"""Crash-proof wrapper around ``controlzero.cli.main:cli``.
|
|
65
|
+
|
|
66
|
+
Defers the heavy import so an import-time crash cannot escape as a bare
|
|
67
|
+
nonzero traceback that a host would mistake for "allow".
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
from controlzero.cli.main import cli as _real_cli
|
|
71
|
+
except ModuleNotFoundError:
|
|
72
|
+
# Possibly a source checkout where the package is not pip-installed.
|
|
73
|
+
# Add the SDK root to sys.path and retry once, mirroring
|
|
74
|
+
# tools/cz-kiro-adapter. controlzero/cli/hook_entry.py -> SDK root is
|
|
75
|
+
# three levels up (controlzero/cli/ -> controlzero/ -> SDK root).
|
|
76
|
+
_sdk_root = os.path.dirname(
|
|
77
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
78
|
+
)
|
|
79
|
+
if _sdk_root not in sys.path:
|
|
80
|
+
sys.path.insert(0, _sdk_root)
|
|
81
|
+
try:
|
|
82
|
+
from controlzero.cli.main import cli as _real_cli
|
|
83
|
+
except BaseException: # noqa: BLE001 -- crash-proof boundary
|
|
84
|
+
_fail_safe_or_reraise()
|
|
85
|
+
return
|
|
86
|
+
except BaseException: # noqa: BLE001 -- crash-proof boundary
|
|
87
|
+
# Any other top-level import failure (ImportError on a transitive dep,
|
|
88
|
+
# a syntax error in an eagerly-imported submodule, etc).
|
|
89
|
+
_fail_safe_or_reraise()
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
_real_cli()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _fail_safe_or_reraise() -> None:
|
|
96
|
+
"""On import failure: fail-safe DENY for hook calls, re-raise otherwise.
|
|
97
|
+
|
|
98
|
+
Reads the live exception from ``sys.exc_info`` so it can re-raise the
|
|
99
|
+
original traceback for interactive usage.
|
|
100
|
+
"""
|
|
101
|
+
if _is_hook_invocation(sys.argv):
|
|
102
|
+
exc = sys.exc_info()[1]
|
|
103
|
+
# Hard, loud error to stderr -- never a silent allow. The host sees a
|
|
104
|
+
# nonzero block exit code and denies the tool call.
|
|
105
|
+
sys.stderr.write(
|
|
106
|
+
"[Control Zero] hook entry point could not load the policy engine "
|
|
107
|
+
f"({type(exc).__name__}: {exc}); DENYING this tool call (fail-safe). "
|
|
108
|
+
"Reinstall the controlzero package to restore policy enforcement.\n"
|
|
109
|
+
)
|
|
110
|
+
sys.stderr.flush()
|
|
111
|
+
sys.exit(_HOOK_DENY_EXIT_CODE)
|
|
112
|
+
# Not a hook invocation: surface the real traceback so the user can fix
|
|
113
|
+
# their install. No governance decision is at stake here.
|
|
114
|
+
raise
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
cli()
|
|
@@ -55,9 +55,18 @@ known host treats no-decision as "proceed") and the universal
|
|
|
55
55
|
from __future__ import annotations
|
|
56
56
|
|
|
57
57
|
from controlzero.cli.hosts.antigravity import AntigravityAdapter
|
|
58
|
-
from controlzero.cli.hosts.base import
|
|
58
|
+
from controlzero.cli.hosts.base import (
|
|
59
|
+
AdapterError,
|
|
60
|
+
CoverageDecl,
|
|
61
|
+
CZDecision,
|
|
62
|
+
HostAdapter,
|
|
63
|
+
RenderResult,
|
|
64
|
+
ToolCall,
|
|
65
|
+
)
|
|
59
66
|
from controlzero.cli.hosts.claude_code import ClaudeCodeAdapter
|
|
60
67
|
from controlzero.cli.hosts.codex_cli import CodexCLIAdapter
|
|
68
|
+
from controlzero.cli.hosts.cursor import CursorIDEAdapter
|
|
69
|
+
from controlzero.cli.hosts.cursor_cli import CursorCLIAdapter
|
|
61
70
|
from controlzero.cli.hosts.gemini_cli import GeminiCLIAdapter
|
|
62
71
|
from controlzero.cli.hosts.kiro import KiroCLIAdapter, KiroIDEAdapter
|
|
63
72
|
from controlzero.cli.hosts.unknown import UnknownHostAdapter
|
|
@@ -77,8 +86,10 @@ REGISTRY: list[HostAdapter] = [
|
|
|
77
86
|
CodexCLIAdapter(),
|
|
78
87
|
GeminiCLIAdapter(),
|
|
79
88
|
AntigravityAdapter(), # distinct toolCall / PreToolCall signature
|
|
89
|
+
CursorCLIAdapter(), # beforeShellExecution + top-level `command`, no tool_name (before KiroCLI)
|
|
80
90
|
KiroCLIAdapter(), # camelCase hook_event_name (disjoint from Claude PascalCase)
|
|
81
91
|
KiroIDEAdapter(), # CZ_KIRO_SURFACE=ide / TERM_PROGRAM=kiro (shim-set)
|
|
92
|
+
CursorIDEAdapter(), # cursor_version / generation_id payload marker or CURSOR_* env (after KiroIDE)
|
|
82
93
|
UnknownHostAdapter(), # always claims; must stay at the tail
|
|
83
94
|
]
|
|
84
95
|
|
|
@@ -105,9 +116,15 @@ def select_adapter(payload: dict, env) -> HostAdapter:
|
|
|
105
116
|
|
|
106
117
|
__all__ = [
|
|
107
118
|
"CZDecision",
|
|
119
|
+
"ToolCall",
|
|
120
|
+
"RenderResult",
|
|
121
|
+
"CoverageDecl",
|
|
122
|
+
"AdapterError",
|
|
108
123
|
"HostAdapter",
|
|
109
124
|
"ClaudeCodeAdapter",
|
|
110
125
|
"CodexCLIAdapter",
|
|
126
|
+
"CursorCLIAdapter",
|
|
127
|
+
"CursorIDEAdapter",
|
|
111
128
|
"GeminiCLIAdapter",
|
|
112
129
|
"AntigravityAdapter",
|
|
113
130
|
"KiroCLIAdapter",
|
|
@@ -55,7 +55,7 @@ from __future__ import annotations
|
|
|
55
55
|
import os
|
|
56
56
|
from typing import Mapping
|
|
57
57
|
|
|
58
|
-
from controlzero.cli.hosts.base import CZDecision, HostAdapter
|
|
58
|
+
from controlzero.cli.hosts.base import CoverageDecl, CZDecision, HostAdapter
|
|
59
59
|
|
|
60
60
|
# File-based EVENT names (NOT the in-process SDK class names). PreToolUse is
|
|
61
61
|
# the deciding gate; PostToolUse is observe-only.
|
|
@@ -70,9 +70,62 @@ _ANTIGRAVITY_HOOK_EVENTS = frozenset(
|
|
|
70
70
|
# disambiguators. Antigravity's unambiguous signal is the ``toolCall`` envelope,
|
|
71
71
|
# which the claim below keys on first.
|
|
72
72
|
_ANTIGRAVITY_CLAIM_EVENTS = frozenset({"PreInvocation", "PostInvocation"})
|
|
73
|
-
|
|
73
|
+
# CONTROLZERO_CLIENT values the adapter CLAIMS as Antigravity. Includes the
|
|
74
|
+
# surface-split forms (#58) so an install pinning CONTROLZERO_CLIENT=
|
|
75
|
+
# antigravity_ide / antigravity_cli is still claimed.
|
|
76
|
+
_CLIENT_ALIASES = frozenset(
|
|
77
|
+
{"antigravity", "agy", "antigravity-cli", "antigravity_ide", "antigravity_cli"}
|
|
78
|
+
)
|
|
74
79
|
_ENV_PREFIXES = ("ANTIGRAVITY_", "AGY_")
|
|
75
80
|
|
|
81
|
+
# Surface split (#58): one adapter governs BOTH the Antigravity IDE and the
|
|
82
|
+
# ``agy`` CLI -- they share the SAME external PreToolUse stdout-decision hook
|
|
83
|
+
# contract, the same engine-executed ``~/.gemini/config/hooks.json``, and the
|
|
84
|
+
# same sanitized hook environment (only a GEMINI_PROJECT_DIR-style whitelist is
|
|
85
|
+
# forwarded; arbitrary user env vars do NOT reach the hook subshell -- verified
|
|
86
|
+
# cmux#4768). Enforcement is therefore IDENTICAL on both; the founder wants the
|
|
87
|
+
# AUDIT/coverage SOURCE label to distinguish them.
|
|
88
|
+
#
|
|
89
|
+
# There is NO runtime signal that tells the IDE apart from the CLI at hook time
|
|
90
|
+
# (Google's own docs + blog confirm a shared harness + synced settings; the
|
|
91
|
+
# stdin payload carries no surface marker, and the env is sanitized). So the
|
|
92
|
+
# split is INSTALL-TIME: each installer writes the surface into the hook command
|
|
93
|
+
# as ``CONTROLZERO_CLIENT=antigravity_ide`` / ``=antigravity_cli`` (an env var
|
|
94
|
+
# set INSIDE the executed command survives agy's env sanitization because the
|
|
95
|
+
# shell running the command sets it -- it is not inherited from the parent).
|
|
96
|
+
#
|
|
97
|
+
# Back-compat (#58): a bare ``antigravity`` (old SDKs / env-only detection)
|
|
98
|
+
# stays ``antigravity`` -- its own canonical value, NOT remapped -- so existing
|
|
99
|
+
# audit rows and pre-split emitters are never downgraded or rewritten.
|
|
100
|
+
_SOURCE_ANTIGRAVITY = "antigravity"
|
|
101
|
+
_SOURCE_ANTIGRAVITY_IDE = "antigravity_ide"
|
|
102
|
+
_SOURCE_ANTIGRAVITY_CLI = "antigravity_cli"
|
|
103
|
+
# CONTROLZERO_CLIENT values that resolve to the CLI surface. The CLI install
|
|
104
|
+
# pins ``antigravity_cli``; the legacy agy aliases also resolve to CLI since
|
|
105
|
+
# ``agy`` IS the CLI.
|
|
106
|
+
_CLI_SURFACE_CLIENTS = frozenset({"antigravity_cli", "agy", "antigravity-cli"})
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _resolve_antigravity_source(env: Mapping[str, str] | None = None) -> str:
|
|
110
|
+
"""Resolve the canonical audit SOURCE for the Antigravity surface (#58).
|
|
111
|
+
|
|
112
|
+
Install-time split: the installer pins ``CONTROLZERO_CLIENT`` in the hook
|
|
113
|
+
command so the runtime knows which surface fired:
|
|
114
|
+
|
|
115
|
+
* ``antigravity_ide`` -> ``antigravity_ide`` (the IDE installer).
|
|
116
|
+
* ``antigravity_cli`` / ``agy`` / ``antigravity-cli`` -> ``antigravity_cli``
|
|
117
|
+
(the agy CLI installer; the legacy agy aliases ARE the CLI).
|
|
118
|
+
* anything else (bare ``antigravity``, unset, env-only detection) ->
|
|
119
|
+
``antigravity`` -- the back-compat value, never remapped.
|
|
120
|
+
"""
|
|
121
|
+
src = env if env is not None else os.environ
|
|
122
|
+
raw = (src.get("CONTROLZERO_CLIENT") or "").strip().lower()
|
|
123
|
+
if raw == _SOURCE_ANTIGRAVITY_IDE:
|
|
124
|
+
return _SOURCE_ANTIGRAVITY_IDE
|
|
125
|
+
if raw in _CLI_SURFACE_CLIENTS:
|
|
126
|
+
return _SOURCE_ANTIGRAVITY_CLI
|
|
127
|
+
return _SOURCE_ANTIGRAVITY
|
|
128
|
+
|
|
76
129
|
# Source-confirmed decision tokens for PreToolUse output.
|
|
77
130
|
_VALID_DECISIONS = frozenset({"allow", "deny", "ask", "force_ask"})
|
|
78
131
|
|
|
@@ -130,16 +183,76 @@ def _resolve_hitl_decision(env: Mapping[str, str] | None = None) -> str:
|
|
|
130
183
|
|
|
131
184
|
class AntigravityAdapter(HostAdapter):
|
|
132
185
|
name = "antigravity"
|
|
133
|
-
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
# hook-check command must exit 0 for this host and let the JSON decide.
|
|
186
|
+
# Antigravity's documented block contract is the stdout JSON
|
|
187
|
+
# ``{"decision":"deny"}``, NOT the exit code -- so ``decision_via_exit_code``
|
|
188
|
+
# stays False (the base exit-code folding does not apply; this adapter owns
|
|
189
|
+
# its own exit-code rule via ``_exit_code_for`` below).
|
|
138
190
|
decision_via_exit_code = False
|
|
139
191
|
|
|
192
|
+
# Coverage-as-data (#202, #1373). HONESTY: shipped Antigravity (1.0.1 /
|
|
193
|
+
# 1.0.3) does NOT route the run_command / Bash / file-WRITE family through
|
|
194
|
+
# the user PreToolUse hook -- it uses a NATIVE confirmation dialog. Our
|
|
195
|
+
# adapter is therefore never invoked for those destructive calls, so even a
|
|
196
|
+
# perfect deny verdict is never consulted: we CANNOT enforce them. Claiming
|
|
197
|
+
# tier="enforced" over ``covered_tool_families=("*",)`` was a #202 false
|
|
198
|
+
# claim (issue #1373).
|
|
199
|
+
#
|
|
200
|
+
# So the tier is ``observe`` (the not-functional-end-to-end posture this
|
|
201
|
+
# adapter-tier enum carries; mirrors how kiro_ide declares the bounded IDE
|
|
202
|
+
# surface). The maturity story for the shell/file family on the public
|
|
203
|
+
# surfaces is COMING SOON -- not GA/BETA -- until Antigravity ships a real
|
|
204
|
+
# shell-blocking hook path (partner-integration track). ``covered_tool_
|
|
205
|
+
# families`` is narrowed to the families agy actually ROUTES to the hook
|
|
206
|
+
# (reads / observe: file_read, file_search; PostToolUse observe) -- NOT the
|
|
207
|
+
# shell/Bash/file_write family. ``block_mechanism`` is ``stdout_decision``:
|
|
208
|
+
# agy's documented contract is the stdout ``{"decision":"deny"}`` token, and
|
|
209
|
+
# ``decision_via_exit_code=False`` so the exit code stays 0 (a deliberate
|
|
210
|
+
# #1248 decision -- a non-zero hook exit on agy is risky / unverified, so we
|
|
211
|
+
# rely on the documented stdout deny, which blocks).
|
|
212
|
+
#
|
|
213
|
+
# CRITICAL still holds: agy reads an empty / decision-less stdout as
|
|
214
|
+
# invalid_args -> DENY, so empty_stdout_is="deny" and the render envelope
|
|
215
|
+
# ALWAYS carries an explicit decision.
|
|
216
|
+
coverage = CoverageDecl(
|
|
217
|
+
tier="observe",
|
|
218
|
+
block_mechanism="stdout_decision",
|
|
219
|
+
deciding_events=("PreToolUse",),
|
|
220
|
+
observe_events=("PostToolUse", "PostInvocation", "Stop"),
|
|
221
|
+
delivers_tool_args=True,
|
|
222
|
+
empty_stdout_is="deny",
|
|
223
|
+
# ONLY the families agy actually routes to the hook today: read /
|
|
224
|
+
# discovery tools (observed). The shell (run_command -> Bash) and the
|
|
225
|
+
# file-WRITE family (write_to_file / replace_file_content) are
|
|
226
|
+
# intentionally ABSENT -- agy never calls our hook for them, so we
|
|
227
|
+
# cannot enforce them (#1373). Adding them back here would re-open the
|
|
228
|
+
# #202 false claim guarded by TestAntigravityCoverageHonesty1373 in
|
|
229
|
+
# tests/test_antigravity_adapter.py.
|
|
230
|
+
covered_tool_families=("file_read", "file_search"),
|
|
231
|
+
min_core_version="1.9.0",
|
|
232
|
+
adapter_contract_version="1",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def canonical_source(self) -> str: # type: ignore[override]
|
|
237
|
+
"""Audit SOURCE label, split IDE vs CLI at install time (#58).
|
|
238
|
+
|
|
239
|
+
Resolved from ``CONTROLZERO_CLIENT`` so the single adapter governs both
|
|
240
|
+
surfaces (shared hook contract -> identical enforcement) while the audit
|
|
241
|
+
row distinguishes ``antigravity_ide`` from ``antigravity_cli``. A bare
|
|
242
|
+
``antigravity`` (old SDKs / env-only detection) stays ``antigravity``
|
|
243
|
+
for back-compat. ``name`` stays ``"antigravity"`` so the fail-closed
|
|
244
|
+
guards that key on the adapter NAME are unaffected by the source split.
|
|
245
|
+
"""
|
|
246
|
+
return _resolve_antigravity_source()
|
|
247
|
+
|
|
140
248
|
def claim(self, payload: dict, env: Mapping[str, str]) -> bool:
|
|
141
249
|
if (env.get("CONTROLZERO_CLIENT") or "").strip().lower() in _CLIENT_ALIASES:
|
|
142
250
|
return True
|
|
251
|
+
# Cross-surface routing fail-open backstop: a foreign pin defers so the
|
|
252
|
+
# pinned host's adapter (not this one) claims the call -- even when an
|
|
253
|
+
# ambient ANTIGRAVITY_/AGY_ env var is also present.
|
|
254
|
+
if self._pinned_elsewhere(env, _CLIENT_ALIASES):
|
|
255
|
+
return False
|
|
143
256
|
for key in env:
|
|
144
257
|
if key.upper().startswith(_ENV_PREFIXES):
|
|
145
258
|
return True
|
|
@@ -169,7 +282,34 @@ class AntigravityAdapter(HostAdapter):
|
|
|
169
282
|
normalized["tool_input"] = args if isinstance(args, dict) else {}
|
|
170
283
|
return normalized
|
|
171
284
|
|
|
172
|
-
def
|
|
285
|
+
def _verdict(self, decision: CZDecision) -> str:
|
|
286
|
+
"""Single source of truth for the agy decision TOKEN.
|
|
287
|
+
|
|
288
|
+
The stdout envelope (``render_envelope``) derives from THIS method.
|
|
289
|
+
agy decides from this stdout token (the exit code stays 0 per #1248):
|
|
290
|
+
|
|
291
|
+
* HITL (``requires_approval`` deny) -> ``_resolve_hitl_decision()``
|
|
292
|
+
(``force_ask`` by default; ``ask``/``deny``/``allow`` only via an
|
|
293
|
+
explicit ``CZ_ANTIGRAVITY_HITL_DECISION`` override).
|
|
294
|
+
* hard deny -> ``"deny"``.
|
|
295
|
+
* allow / warn -> ``"allow"``.
|
|
296
|
+
"""
|
|
297
|
+
if decision.is_hitl:
|
|
298
|
+
return _resolve_hitl_decision()
|
|
299
|
+
if decision.is_deny:
|
|
300
|
+
return "deny"
|
|
301
|
+
return "allow"
|
|
302
|
+
|
|
303
|
+
# NOTE (#1373): agy intentionally does NOT override the exit code. agy's
|
|
304
|
+
# documented block contract is the stdout ``{"decision":"deny"}`` token, and
|
|
305
|
+
# ``decision_via_exit_code=False`` keeps the exit code 0 for every decision
|
|
306
|
+
# (the deliberate #1248 design -- a non-zero hook exit on agy is unverified
|
|
307
|
+
# / risky, so we rely on the stdout deny, which blocks). An exit-2 backstop
|
|
308
|
+
# was considered (finding F1) and dropped: it could not be validated against
|
|
309
|
+
# a live agy and would contradict #1248. The honest posture is the
|
|
310
|
+
# CoverageDecl above (tier=observe; shell/file COMING SOON).
|
|
311
|
+
|
|
312
|
+
def render_envelope(self, decision: CZDecision) -> dict:
|
|
173
313
|
"""Render to Antigravity's PreToolUse stdout-JSON contract.
|
|
174
314
|
|
|
175
315
|
ALWAYS emits an explicit ``decision`` key. An empty / decision-less
|
|
@@ -177,14 +317,10 @@ class AntigravityAdapter(HostAdapter):
|
|
|
177
317
|
(cmux#5358), so silence-that-still-prints-{} is never produced here.
|
|
178
318
|
camelCase per the external contract; Control Zero diagnostic
|
|
179
319
|
metadata is namespaced under ``controlzero`` so it cannot collide
|
|
180
|
-
with agy's reserved output keys.
|
|
320
|
+
with agy's reserved output keys. The decision token comes from the
|
|
321
|
+
single-source ``_verdict``.
|
|
181
322
|
"""
|
|
182
|
-
|
|
183
|
-
verdict = _resolve_hitl_decision()
|
|
184
|
-
elif decision.is_deny:
|
|
185
|
-
verdict = "deny"
|
|
186
|
-
else:
|
|
187
|
-
verdict = "allow"
|
|
323
|
+
verdict = self._verdict(decision)
|
|
188
324
|
|
|
189
325
|
envelope: dict = {
|
|
190
326
|
"decision": verdict,
|