controlzero 1.9.4__tar.gz → 1.9.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.9.4 → controlzero-1.9.6}/CHANGELOG.md +75 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/PKG-INFO +18 -8
- {controlzero-1.9.4 → controlzero-1.9.6}/README.md +17 -7
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/__init__.py +1 -1
- controlzero-1.9.6/controlzero/cli/__main__.py +13 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/doctor.py +225 -2
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/antigravity.py +58 -15
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/kiro.py +7 -4
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/main.py +389 -7
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/claude-code.yaml +15 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/pyproject.toml +1 -1
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_antigravity_adapter.py +18 -12
- controlzero-1.9.6/tests/test_antigravity_ga_blockers_1248.py +554 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hosts_adapter.py +6 -5
- controlzero-1.9.6/tests/test_kiro_cli_e2e.py +297 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/.gitignore +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/Dockerfile.test +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/LICENSE +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/enforcer.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/types.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/audit_local.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/audit_remote.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/canonical.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/console.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/base.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/claude_code.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/codex_cli.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/kiro_adapter.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/antigravity.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/client.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/device.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/enrollment.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/error_codes.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/error_codes.yaml +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/errors.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/status.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hosted_policy.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/google.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/layout_migration.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/policy_loader.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_keyring.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_state.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/tamper.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/tracecontext.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/examples/hello_world.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/conftest.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/integrations/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/integrations/test_google.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/__init__.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/conftest.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_durable_default_tamper.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_keychain_dek.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_action_aliases.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_antigravity_hook_check.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_antigravity_install.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_audit_remote.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_hook.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_init.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_tail.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_test.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_validate.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_coding_agent_hooks.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_conditions.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_conformance.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_console.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_credential_hook.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_default_action.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_device.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_doctor.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_enrollment.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_epic_1247_bryan_acceptance.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_error_codes.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_errors_e_codes.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_glob_matching.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_request_approval.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_phase2b_protocol.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hosted_local_audit_1247.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_install_hook_command.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_install_hooks.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_kiro_adapter.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_kiro_hook_templates.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_kiro_install.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_log_rotation.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_migrate.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_observe_mode_1247.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_policy_settings.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_quarantine.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_reason_code.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_refresh.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_secrets.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_synthetic_policy_id_t79.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_tamper.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_tracecontext.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.9.4 → controlzero-1.9.6}/tools/cz-kiro-adapter +0 -0
|
@@ -1,5 +1,79 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.6 -- 2026-06-16 (Antigravity GA-blocker hardening, gh#1248 / epic gh#925)
|
|
4
|
+
|
|
5
|
+
Closes 3 of the prod-readiness GA blockers for the Antigravity (`agy`)
|
|
6
|
+
integration. Antigravity stays **BETA** -- GA still requires a manual check
|
|
7
|
+
against a real pinned agy build (agy cannot run headless in CI), which these
|
|
8
|
+
changes do not perform.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Empty-stdout fail-closed on the Antigravity surface (blocker #1).** Every
|
|
13
|
+
`controlzero hook-check` error exit -- stdin read error, empty stdin,
|
|
14
|
+
malformed JSON, missing `tool_name`, and an invalid/unreadable policy --
|
|
15
|
+
previously emitted EMPTY stdout and exited 0. Agy decides from stdout JSON,
|
|
16
|
+
and empty stdout is undocumented (agy *currently* reads it as
|
|
17
|
+
`invalid_args` -> deny, but that is unverified across builds). On the
|
|
18
|
+
Antigravity surface those paths now emit an explicit
|
|
19
|
+
`{"decision":"deny", "reason":...}` so a parse/policy error DENIES
|
|
20
|
+
deterministically. Pass-through hosts (Claude Code / Gemini / Codex) keep
|
|
21
|
+
the documented empty-stdout = "no opinion, proceed" behavior unchanged.
|
|
22
|
+
|
|
23
|
+
- **HITL approval gate no longer silently auto-approvable (blocker #2).** The
|
|
24
|
+
HITL decision token now defaults to `force_ask` (a MANDATORY prompt that
|
|
25
|
+
ignores agy's "Always Allow" cache) instead of `ask` (which a cached
|
|
26
|
+
approval can silently satisfy -- fail-OPEN on an approval-gated destructive
|
|
27
|
+
action). `CZ_ANTIGRAVITY_HITL_DECISION` selects the token; an empty or
|
|
28
|
+
unrecognized value falls back to `force_ask`, NEVER to `ask`. If a pinned
|
|
29
|
+
agy build rejects `force_ask`, set `CZ_ANTIGRAVITY_HITL_DECISION=deny` for a
|
|
30
|
+
hard block-with-reason. `ask`/`allow` remain available only as an explicit,
|
|
31
|
+
knowing operator downgrade.
|
|
32
|
+
|
|
33
|
+
- **`doctor` checks the REAL Antigravity paths and verifies the hook is live
|
|
34
|
+
(blocker #3).** `controlzero doctor` previously checked
|
|
35
|
+
`~/.antigravity/config.json` -- a path the installer never writes. It now
|
|
36
|
+
scans the executed `~/.gemini/config/hooks.json` and the agy TUI mirror
|
|
37
|
+
`~/.gemini/antigravity-cli/hooks.json` (plus the cwd-relative project-scope
|
|
38
|
+
`.agents/hooks.json`), confirms a live `controlzero hook-check` `PreToolUse`
|
|
39
|
+
entry is present (`E1006` when missing), and runs a dry-run hook-check
|
|
40
|
+
round-trip to confirm the adapter renders an explicit decision rather than
|
|
41
|
+
empty stdout (`E1007` when it does not).
|
|
42
|
+
|
|
43
|
+
## 1.9.5 -- 2026-06-16 (Kiro CLI -> GA, epic gh#877)
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **Kiro CLI (`kiro-cli chat`) promoted BETA -> GA.** The CLI surface routes
|
|
48
|
+
the full event JSON (snake_case `tool_name` / `tool_input`) through the same
|
|
49
|
+
`controlzero hook-check` -> policy engine -> audit path Claude Code / Codex /
|
|
50
|
+
Gemini ship on, with argument-level deny rules and `agent="kiro"`
|
|
51
|
+
(`canonical_source="kiro_cli"`) attribution. The **Kiro IDE** surface stays
|
|
52
|
+
**limited preview**: upstream Kiro #6188 (open through IDE v0.12) delivers an
|
|
53
|
+
empty payload to IDE `preToolUse` hooks, so argument-level pre-tool blocking
|
|
54
|
+
is impossible IDE-side; the IDE Pre Tool Use hook still ships disabled.
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **`controlzero kiro verify`** -- install-validation self-test. Confirms the
|
|
59
|
+
Kiro CLI hook is wired into `settings.json` AND fires a synthetic known-deny
|
|
60
|
+
payload through the real hook to prove it blocks (exit 2). Exit 0 = installed
|
|
61
|
+
and enforcing; exit 1 = a problem (not wired, or a deny was not blocked).
|
|
62
|
+
|
|
63
|
+
- **Strict mode for the Kiro CLI hook-check path (`CZ_KIRO_CLI_STRICT`).** When
|
|
64
|
+
set (the Kiro CLI installer wires it on by default), `hook-check` fails
|
|
65
|
+
**closed** (exit 2, with a cause-specific diagnostic) on empty stdin,
|
|
66
|
+
malformed JSON, a missing `tool_name`, or an installed-but-unreadable local
|
|
67
|
+
policy -- distinguishing "policy unreadable" from "no policy installed". The
|
|
68
|
+
Kiro CLI always delivers a well-formed payload, so such a payload is an
|
|
69
|
+
anomaly, not a benign passthrough. The default for every other host stays
|
|
70
|
+
fail-open so an agent is never bricked by a partial payload.
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
|
|
74
|
+
- Tightened the Kiro adapter docstring: IDE post-tool-use carries the tool
|
|
75
|
+
**name + result only** (`toolArgs` arrives empty, #6188), not the arguments.
|
|
76
|
+
|
|
3
77
|
## 1.9.4 -- 2026-06-16 (actionable E1101 / API-key-rejected message, #1254, epic gh#1247)
|
|
4
78
|
|
|
5
79
|
### Fixed
|
|
@@ -97,6 +171,7 @@ second-opinion). It is deliberately **narrow and gated**:
|
|
|
97
171
|
bundle translator -> `PolicySettings`. Local YAML policies may set
|
|
98
172
|
`settings.default_on_empty`. The dashboard observe-mode indicator +
|
|
99
173
|
CLI `status` line remain a separate frontend/CLI follow-up.
|
|
174
|
+
|
|
100
175
|
## 1.9.1 -- 2026-06-16 (hosted-mode local audit log P0, epic gh#1247)
|
|
101
176
|
|
|
102
177
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.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
|
|
@@ -129,7 +129,7 @@ Templates available (`controlzero init -t <name>`):
|
|
|
129
129
|
- `autogen` — AutoGen starter policy
|
|
130
130
|
- `codex-cli` — Codex CLI hook starter
|
|
131
131
|
- `gemini-cli` — Gemini CLI hook starter
|
|
132
|
-
- `kiro` — Kiro (AWS
|
|
132
|
+
- `kiro` — Kiro (AWS) hook starter (CLI: GA; IDE: limited preview)
|
|
133
133
|
- `antigravity` — Google Antigravity (IDE + `agy` CLI) hook starter (Beta)
|
|
134
134
|
|
|
135
135
|
## Loading a policy
|
|
@@ -225,14 +225,27 @@ to the Control Zero backend so your team can see it on the dashboard.
|
|
|
225
225
|
|
|
226
226
|
## Local audit log
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
Every decision (allow **and** deny) is written to a local audit log in **every
|
|
229
|
+
mode** — local, hybrid, and hosted. The local log is never skipped, so
|
|
230
|
+
`controlzero tail`, `cz debug-bundle`, and the tamper hash-chain always have a
|
|
231
|
+
record to read.
|
|
230
232
|
|
|
231
233
|
```bash
|
|
232
234
|
controlzero tail
|
|
233
235
|
```
|
|
234
236
|
|
|
235
|
-
|
|
237
|
+
Default paths:
|
|
238
|
+
|
|
239
|
+
- **Local / unenrolled mode** (no API key): `./controlzero.log`, with daily
|
|
240
|
+
rotation and 30-day retention.
|
|
241
|
+
- **Hosted mode** (`CONTROLZERO_API_KEY` set): `~/.controlzero/audit.log` when you
|
|
242
|
+
do not pass an explicit `log_path`. Local audit is written **in addition to**
|
|
243
|
+
the remote dashboard sink, not instead of it — the remote sink is layered on
|
|
244
|
+
top. In hosted mode, PII and financial DLP `matched_text` is redacted from the
|
|
245
|
+
local plaintext row (the secret category is already hashed); the remote sink
|
|
246
|
+
keeps full fidelity.
|
|
247
|
+
|
|
248
|
+
Configure rotation via the client (honoured in any mode):
|
|
236
249
|
|
|
237
250
|
```python
|
|
238
251
|
cz = Client(
|
|
@@ -245,9 +258,6 @@ cz = Client(
|
|
|
245
258
|
)
|
|
246
259
|
```
|
|
247
260
|
|
|
248
|
-
When `CONTROLZERO_API_KEY` is set, audit ships to the remote dashboard and
|
|
249
|
-
these `log_*` options are ignored with a warning.
|
|
250
|
-
|
|
251
261
|
## Hybrid mode
|
|
252
262
|
|
|
253
263
|
Default (T103, 2026-05-12): when `CONTROLZERO_API_KEY` is set, the
|
|
@@ -75,7 +75,7 @@ Templates available (`controlzero init -t <name>`):
|
|
|
75
75
|
- `autogen` — AutoGen starter policy
|
|
76
76
|
- `codex-cli` — Codex CLI hook starter
|
|
77
77
|
- `gemini-cli` — Gemini CLI hook starter
|
|
78
|
-
- `kiro` — Kiro (AWS
|
|
78
|
+
- `kiro` — Kiro (AWS) hook starter (CLI: GA; IDE: limited preview)
|
|
79
79
|
- `antigravity` — Google Antigravity (IDE + `agy` CLI) hook starter (Beta)
|
|
80
80
|
|
|
81
81
|
## Loading a policy
|
|
@@ -171,14 +171,27 @@ to the Control Zero backend so your team can see it on the dashboard.
|
|
|
171
171
|
|
|
172
172
|
## Local audit log
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
Every decision (allow **and** deny) is written to a local audit log in **every
|
|
175
|
+
mode** — local, hybrid, and hosted. The local log is never skipped, so
|
|
176
|
+
`controlzero tail`, `cz debug-bundle`, and the tamper hash-chain always have a
|
|
177
|
+
record to read.
|
|
176
178
|
|
|
177
179
|
```bash
|
|
178
180
|
controlzero tail
|
|
179
181
|
```
|
|
180
182
|
|
|
181
|
-
|
|
183
|
+
Default paths:
|
|
184
|
+
|
|
185
|
+
- **Local / unenrolled mode** (no API key): `./controlzero.log`, with daily
|
|
186
|
+
rotation and 30-day retention.
|
|
187
|
+
- **Hosted mode** (`CONTROLZERO_API_KEY` set): `~/.controlzero/audit.log` when you
|
|
188
|
+
do not pass an explicit `log_path`. Local audit is written **in addition to**
|
|
189
|
+
the remote dashboard sink, not instead of it — the remote sink is layered on
|
|
190
|
+
top. In hosted mode, PII and financial DLP `matched_text` is redacted from the
|
|
191
|
+
local plaintext row (the secret category is already hashed); the remote sink
|
|
192
|
+
keeps full fidelity.
|
|
193
|
+
|
|
194
|
+
Configure rotation via the client (honoured in any mode):
|
|
182
195
|
|
|
183
196
|
```python
|
|
184
197
|
cz = Client(
|
|
@@ -191,9 +204,6 @@ cz = Client(
|
|
|
191
204
|
)
|
|
192
205
|
```
|
|
193
206
|
|
|
194
|
-
When `CONTROLZERO_API_KEY` is set, audit ships to the remote dashboard and
|
|
195
|
-
these `log_*` options are ignored with a warning.
|
|
196
|
-
|
|
197
207
|
## Hybrid mode
|
|
198
208
|
|
|
199
209
|
Default (T103, 2026-05-12): when `CONTROLZERO_API_KEY` is set, the
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Enable ``python -m controlzero.cli`` to run the Control Zero CLI.
|
|
2
|
+
|
|
3
|
+
Mirrors the ``controlzero`` console-script entry point. Used by
|
|
4
|
+
``controlzero kiro verify`` to invoke hook-check as a subprocess in a way
|
|
5
|
+
that works even when the console script is not on PATH (e.g. CI, venvs).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from controlzero.cli.main import cli
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
cli()
|
|
@@ -82,8 +82,18 @@ def _agent_paths() -> List[_AgentPath]:
|
|
|
82
82
|
"VS Code user settings"),
|
|
83
83
|
_AgentPath("cline", home / "Library" / "Application Support" / "Code" / "User" / "globalStorage" / "saoudrizwan.claude-dev" / "settings" / "cline_mcp_settings.json",
|
|
84
84
|
"Cline MCP settings (macOS)"),
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
# Antigravity (agy): the install writes the EXECUTED hook config to
|
|
86
|
+
# ~/.gemini/config/hooks.json and mirrors it to
|
|
87
|
+
# ~/.gemini/antigravity-cli/hooks.json for the agy TUI /hooks display
|
|
88
|
+
# (antigravity-cli#49). The pre-#1248 doctor checked
|
|
89
|
+
# ~/.antigravity/config.json -- a path the installer NEVER writes -- so
|
|
90
|
+
# it could neither find a leaked key there nor confirm the hook was
|
|
91
|
+
# live. Scan the real executed + mirror paths now. Project scope
|
|
92
|
+
# (<cwd>/.agents/hooks.json) is covered by the cwd-aware live check.
|
|
93
|
+
_AgentPath("antigravity", home / ".gemini" / "config" / "hooks.json",
|
|
94
|
+
"Antigravity PreToolUse hook command"),
|
|
95
|
+
_AgentPath("antigravity", home / ".gemini" / "antigravity-cli" / "hooks.json",
|
|
96
|
+
"Antigravity agy TUI /hooks mirror"),
|
|
87
97
|
_AgentPath("adal", home / ".adal" / "config.json",
|
|
88
98
|
"Adal config"),
|
|
89
99
|
_AgentPath("jetbrains", home / ".config" / "JetBrains" / "mcp.json",
|
|
@@ -243,6 +253,208 @@ def _check_global_environment() -> List[Finding]:
|
|
|
243
253
|
return findings
|
|
244
254
|
|
|
245
255
|
|
|
256
|
+
# -----------------------------------------------------------------------------
|
|
257
|
+
# Antigravity hook liveness (GA blocker #3, issue #1248).
|
|
258
|
+
#
|
|
259
|
+
# The pre-#1248 doctor only scanned files for leaked keys. For Antigravity it
|
|
260
|
+
# also checked the WRONG path (~/.antigravity/config.json), so it gave a user
|
|
261
|
+
# zero signal about whether their governance hook was actually installed and
|
|
262
|
+
# firing. These checks confirm the hook entry is present at the REAL executed
|
|
263
|
+
# path AND that a dry-run hook-check round-trips a valid decision -- the two
|
|
264
|
+
# things that have to be true for the gate to govern a single tool call.
|
|
265
|
+
# -----------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
# The substring that marks a Control Zero hook entry, matching the installer's
|
|
268
|
+
# own self-detection (`_antigravity_hook_is_cz`). Kept in one place so a future
|
|
269
|
+
# command-string change only edits here.
|
|
270
|
+
_CZ_HOOK_MARKER = "controlzero hook-check"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _antigravity_executed_hook_paths() -> List[Path]:
|
|
274
|
+
"""The hooks.json paths agy actually EXECUTES, in precedence order.
|
|
275
|
+
|
|
276
|
+
Project scope wins for a workspace, so it is checked first; the global
|
|
277
|
+
user file is the fallback. The TUI mirror (~/.gemini/antigravity-cli/...)
|
|
278
|
+
is a display surface, not an executed path, so it is intentionally NOT in
|
|
279
|
+
this list -- a hook present only in the mirror does not fire.
|
|
280
|
+
"""
|
|
281
|
+
home = Path.home()
|
|
282
|
+
return [
|
|
283
|
+
Path.cwd() / ".agents" / "hooks.json", # project scope (<cwd>)
|
|
284
|
+
home / ".gemini" / "config" / "hooks.json", # global user (executed)
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _antigravity_mirror_path() -> Path:
|
|
289
|
+
"""The agy TUI /hooks DISPLAY mirror (antigravity-cli#49).
|
|
290
|
+
|
|
291
|
+
A hook present ONLY here is shown in the agy `/hooks` UI but is NOT
|
|
292
|
+
executed, so it does not fire. Used purely as a presence signal so doctor
|
|
293
|
+
can warn about that display-but-not-live state.
|
|
294
|
+
"""
|
|
295
|
+
return Path.home() / ".gemini" / "antigravity-cli" / "hooks.json"
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _antigravity_hook_command_present(hooks_path: Path) -> bool:
|
|
299
|
+
"""True iff hooks_path has a live PreToolUse Control Zero hook entry.
|
|
300
|
+
|
|
301
|
+
Mirrors the installer's written shape: top-level ``hooks`` ->
|
|
302
|
+
``PreToolUse`` -> list of blocks each with a ``hooks`` list of
|
|
303
|
+
``{type, command, ...}`` entries. A CZ entry is any whose ``command``
|
|
304
|
+
contains ``controlzero hook-check``. Tolerant of hand-edits / partial
|
|
305
|
+
shapes -- anything it cannot parse counts as "not present".
|
|
306
|
+
"""
|
|
307
|
+
import json
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
data = json.loads(hooks_path.read_text(encoding="utf-8"))
|
|
311
|
+
except (OSError, ValueError):
|
|
312
|
+
return False
|
|
313
|
+
if not isinstance(data, dict):
|
|
314
|
+
return False
|
|
315
|
+
hooks = data.get("hooks")
|
|
316
|
+
if not isinstance(hooks, dict):
|
|
317
|
+
return False
|
|
318
|
+
pre = hooks.get("PreToolUse")
|
|
319
|
+
if not isinstance(pre, list):
|
|
320
|
+
return False
|
|
321
|
+
for block in pre:
|
|
322
|
+
if not isinstance(block, dict):
|
|
323
|
+
continue
|
|
324
|
+
block_hooks = block.get("hooks")
|
|
325
|
+
if not isinstance(block_hooks, list):
|
|
326
|
+
# Tolerate a malformed-but-valid-JSON shape (e.g. "hooks": 3);
|
|
327
|
+
# iterating a non-list would raise TypeError and crash doctor.
|
|
328
|
+
continue
|
|
329
|
+
for entry in block_hooks:
|
|
330
|
+
if not isinstance(entry, dict):
|
|
331
|
+
continue
|
|
332
|
+
cmd = entry.get("command")
|
|
333
|
+
if isinstance(cmd, str) and _CZ_HOOK_MARKER in cmd:
|
|
334
|
+
return True
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _antigravity_dry_run_roundtrips() -> bool:
|
|
339
|
+
"""True iff a dry-run hook-check produces a valid Antigravity decision.
|
|
340
|
+
|
|
341
|
+
This is the "does the hook actually fire" half of the check. We do NOT
|
|
342
|
+
shell out to ``controlzero hook-check`` (slow, and a misconfigured PATH
|
|
343
|
+
would make a working install look broken). Instead we exercise the same
|
|
344
|
+
code path the hook runs: select the Antigravity adapter and render a
|
|
345
|
+
decision, asserting it emits an explicit ``decision`` token (never the
|
|
346
|
+
empty-stdout ``invalid_args`` trap). If the adapter import or render
|
|
347
|
+
raises, the round-trip has failed.
|
|
348
|
+
"""
|
|
349
|
+
try:
|
|
350
|
+
from controlzero.cli.hosts import CZDecision, select_adapter
|
|
351
|
+
|
|
352
|
+
# Force Antigravity selection via the env signal its claim() honors.
|
|
353
|
+
adapter = select_adapter({}, {"CONTROLZERO_CLIENT": "antigravity"})
|
|
354
|
+
if getattr(adapter, "name", "") != "antigravity":
|
|
355
|
+
return False
|
|
356
|
+
out = adapter.render(
|
|
357
|
+
CZDecision(effect="allow", reason="doctor dry-run", reason_code="RULE_MATCH")
|
|
358
|
+
)
|
|
359
|
+
return isinstance(out, dict) and out.get("decision") == "allow"
|
|
360
|
+
except Exception: # noqa: BLE001
|
|
361
|
+
return False
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _check_antigravity_hook_live() -> List[Finding]:
|
|
365
|
+
"""Confirm the Antigravity governance hook is installed AND can fire.
|
|
366
|
+
|
|
367
|
+
Respects agy's file precedence: the highest-precedence EXISTING executed
|
|
368
|
+
file wins (project scope <cwd>/.agents/hooks.json shadows the global
|
|
369
|
+
~/.gemini/config/hooks.json), so that file alone determines liveness.
|
|
370
|
+
|
|
371
|
+
Emits at most one finding:
|
|
372
|
+
|
|
373
|
+
* If the authoritative executed file carries a CZ entry and the dry-run
|
|
374
|
+
round-trips -> no finding (the gate is live).
|
|
375
|
+
* If the authoritative executed file (or only the display mirror) lacks a
|
|
376
|
+
CZ entry -> WARN installed-but-not-live (or never installed). WARN, not
|
|
377
|
+
ERROR: a box that never installed the agy hook is a valid state, and
|
|
378
|
+
doctor exit 1 is reserved for active security problems (leaked keys,
|
|
379
|
+
bad perms).
|
|
380
|
+
* If the CZ entry is present but the dry-run does NOT round-trip -> ERROR:
|
|
381
|
+
the entry exists but the adapter cannot render a decision, so the gate
|
|
382
|
+
would emit empty stdout -> agy invalid_args / undefined behavior.
|
|
383
|
+
"""
|
|
384
|
+
findings: List[Finding] = []
|
|
385
|
+
executed = _antigravity_executed_hook_paths()
|
|
386
|
+
|
|
387
|
+
# agy executes the HIGHEST-PRECEDENCE existing file only (project scope
|
|
388
|
+
# <cwd>/.agents/hooks.json shadows the global ~/.gemini/config/hooks.json).
|
|
389
|
+
# So the FIRST existing executed file is authoritative: if it carries a CZ
|
|
390
|
+
# entry the gate is live; if it does NOT, the workspace is ungoverned even
|
|
391
|
+
# when a lower-precedence file happens to have one. We must NOT fall through
|
|
392
|
+
# to that lower file, or doctor would falsely report "live" (codex P1).
|
|
393
|
+
authoritative = next((p for p in executed if p.exists()), None)
|
|
394
|
+
live_path = (
|
|
395
|
+
authoritative
|
|
396
|
+
if authoritative is not None and _antigravity_hook_command_present(authoritative)
|
|
397
|
+
else None
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
if live_path is None:
|
|
401
|
+
# Surface a not-live WARN when ANY agy hooks.json exists -- the
|
|
402
|
+
# authoritative executed file without a CZ entry, or only the
|
|
403
|
+
# display-only TUI mirror. A hook present only in the mirror (or absent
|
|
404
|
+
# from the executed file that wins) is shown in the agy `/hooks` UI yet
|
|
405
|
+
# does NOT govern tool calls -- exactly the misleading state to flag.
|
|
406
|
+
# Stay silent when no agy file exists at all so a non-Antigravity user
|
|
407
|
+
# gets no spurious warning.
|
|
408
|
+
mirror = _antigravity_mirror_path()
|
|
409
|
+
present = [p for p in executed if p.exists()]
|
|
410
|
+
if mirror.exists():
|
|
411
|
+
present.append(mirror)
|
|
412
|
+
if present:
|
|
413
|
+
mirror_only = all(p == mirror for p in present)
|
|
414
|
+
findings.append(Finding(
|
|
415
|
+
path=str(present[0]),
|
|
416
|
+
line=1,
|
|
417
|
+
col=1,
|
|
418
|
+
severity="WARN",
|
|
419
|
+
code="E1006",
|
|
420
|
+
message=(
|
|
421
|
+
(
|
|
422
|
+
"Antigravity hook appears only in the agy /hooks DISPLAY "
|
|
423
|
+
"mirror (~/.gemini/antigravity-cli/hooks.json) -- that path "
|
|
424
|
+
"is NOT executed, so the governance gate is NOT firing."
|
|
425
|
+
)
|
|
426
|
+
if mirror_only else
|
|
427
|
+
(
|
|
428
|
+
"Antigravity hooks.json exists but has no live "
|
|
429
|
+
f"'{_CZ_HOOK_MARKER}' PreToolUse entry -- the governance "
|
|
430
|
+
"gate is NOT firing for tool calls."
|
|
431
|
+
)
|
|
432
|
+
),
|
|
433
|
+
fix_hint="run `controlzero install antigravity` to (re)install the hook",
|
|
434
|
+
))
|
|
435
|
+
return findings
|
|
436
|
+
|
|
437
|
+
# Hook entry is present -- verify it can actually render a decision.
|
|
438
|
+
if not _antigravity_dry_run_roundtrips():
|
|
439
|
+
findings.append(Finding(
|
|
440
|
+
path=str(live_path),
|
|
441
|
+
line=1,
|
|
442
|
+
col=1,
|
|
443
|
+
severity="ERROR",
|
|
444
|
+
code="E1007",
|
|
445
|
+
message=(
|
|
446
|
+
"Antigravity hook entry is installed but a dry-run hook-check "
|
|
447
|
+
"does NOT round-trip a decision -- the gate would emit empty "
|
|
448
|
+
"stdout (agy reads that as invalid_args / undefined)."
|
|
449
|
+
),
|
|
450
|
+
fix_hint=(
|
|
451
|
+
"reinstall the SDK (`pip install -U control-zero`) and rerun "
|
|
452
|
+
"`controlzero install antigravity`; if it persists, file a bug"
|
|
453
|
+
),
|
|
454
|
+
))
|
|
455
|
+
return findings
|
|
456
|
+
|
|
457
|
+
|
|
246
458
|
# -----------------------------------------------------------------------------
|
|
247
459
|
# Public entrypoint. Wired into the CLI as `controlzero doctor`.
|
|
248
460
|
# -----------------------------------------------------------------------------
|
|
@@ -257,6 +469,7 @@ def run_doctor(verbose: bool = False) -> int:
|
|
|
257
469
|
findings.extend(_check_agent_key_leaks(_agent_paths()))
|
|
258
470
|
findings.extend(_check_config_permissions())
|
|
259
471
|
findings.extend(_check_global_environment())
|
|
472
|
+
findings.extend(_check_antigravity_hook_live())
|
|
260
473
|
|
|
261
474
|
errors = [f for f in findings if f.severity == "ERROR"]
|
|
262
475
|
warns = [f for f in findings if f.severity == "WARN"]
|
|
@@ -272,6 +485,16 @@ def run_doctor(verbose: bool = False) -> int:
|
|
|
272
485
|
for ap in _agent_paths():
|
|
273
486
|
status = "exists" if ap.path.exists() else "absent"
|
|
274
487
|
cz_console.info(f" {ap.agent:14s} {status:7s} {ap.path}")
|
|
488
|
+
_live = next(
|
|
489
|
+
(p for p in _antigravity_executed_hook_paths()
|
|
490
|
+
if p.exists() and _antigravity_hook_command_present(p)),
|
|
491
|
+
None,
|
|
492
|
+
)
|
|
493
|
+
if _live is not None:
|
|
494
|
+
rt = "round-trips" if _antigravity_dry_run_roundtrips() else "BROKEN"
|
|
495
|
+
cz_console.info(f" {'antigravity':14s} {'live':7s} {_live} (dry-run: {rt})")
|
|
496
|
+
else:
|
|
497
|
+
cz_console.info(f" {'antigravity':14s} {'not-live':7s} (no executed PreToolUse hook entry)")
|
|
275
498
|
return 0
|
|
276
499
|
|
|
277
500
|
for f in findings:
|
|
@@ -28,14 +28,21 @@ danicat.dev):
|
|
|
28
28
|
We therefore ALWAYS emit an explicit ``{"decision": ...}`` -- the allow
|
|
29
29
|
path is ``{"decision":"allow"}``, never silence-that-still-prints-{}.
|
|
30
30
|
|
|
31
|
-
HITL mapping: a Control Zero approval gate maps to
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
HITL mapping (GA blocker #2, issue #1248): a Control Zero approval gate maps to
|
|
32
|
+
``force_ask`` by DEFAULT -- a MANDATORY prompt that ignores agy's "Always Allow"
|
|
33
|
+
cache. A plain ``ask`` CAN be silently auto-satisfied by a cached "Always Allow"
|
|
34
|
+
(fail-OPEN on an approval-gated destructive action), so it is NOT the default
|
|
35
|
+
and is NEVER reached by the unrecognized-value fallback. The token is
|
|
36
|
+
configurable via ``CZ_ANTIGRAVITY_HITL_DECISION``:
|
|
37
|
+
|
|
38
|
+
* unset / unrecognized -> ``force_ask`` (fail-closed default).
|
|
39
|
+
* ``force_ask`` -> guaranteed prompt.
|
|
40
|
+
* ``deny`` -> hard block-with-reason. Use this as the fallback when a pinned agy
|
|
41
|
+
build is known to reject ``force_ask``; the gate then blocks rather than
|
|
42
|
+
silently auto-approving.
|
|
43
|
+
* ``ask`` -> the legacy cache-bypassable behavior. Permitted only as an
|
|
44
|
+
explicit, knowing downgrade (fail-open).
|
|
45
|
+
* ``allow`` -> defeats the gate; explicit override only.
|
|
39
46
|
|
|
40
47
|
Subagent tool calls fire the parent ``PreToolUse`` hook, so one adapter
|
|
41
48
|
governs parent + subagents.
|
|
@@ -68,21 +75,57 @@ _ENV_PREFIXES = ("ANTIGRAVITY_", "AGY_")
|
|
|
68
75
|
|
|
69
76
|
# Source-confirmed decision tokens for PreToolUse output.
|
|
70
77
|
_VALID_DECISIONS = frozenset({"allow", "deny", "ask", "force_ask"})
|
|
71
|
-
|
|
78
|
+
|
|
79
|
+
# HITL approval-gate token. GA blocker #2 (issue #1248): a plain ``ask`` CAN be
|
|
80
|
+
# silently auto-satisfied by a cached "Always Allow" -- which is fail-OPEN on an
|
|
81
|
+
# approval-gated destructive action. For a governance gate that is unacceptable,
|
|
82
|
+
# so the safe default for a HITL gate is ``force_ask`` (a MANDATORY prompt that
|
|
83
|
+
# ignores the cache). If a particular agy build rejects ``force_ask`` (older
|
|
84
|
+
# builds may not implement it), the operator must fall back to ``deny`` -- a hard
|
|
85
|
+
# block-with-reason -- NOT to ``ask``. ``ask`` is therefore no longer the default
|
|
86
|
+
# and is never reached by the unrecognized-value fallback; an operator who wants
|
|
87
|
+
# the cache-bypassable behavior must opt into it EXPLICITLY and knowingly.
|
|
88
|
+
_DEFAULT_HITL_DECISION = "force_ask"
|
|
89
|
+
# The fail-closed fallback for a HITL gate when the configured token is empty or
|
|
90
|
+
# unrecognized. Must be a guaranteed-prompt / hard-block token, never ``ask``.
|
|
91
|
+
_HITL_FALLBACK_DECISION = "force_ask"
|
|
92
|
+
# Tokens that are SAFE for a HITL approval gate (cannot be silently
|
|
93
|
+
# auto-approved from a cache). ``ask`` is deliberately excluded -- it is
|
|
94
|
+
# cache-bypassable -- and ``allow`` is excluded because allowing outright defeats
|
|
95
|
+
# the gate. Only an explicit operator override may select a non-safe token.
|
|
96
|
+
_HITL_SAFE_DECISIONS = frozenset({"force_ask", "deny"})
|
|
72
97
|
|
|
73
98
|
|
|
74
99
|
def _resolve_hitl_decision(env: Mapping[str, str] | None = None) -> str:
|
|
75
100
|
"""Resolve the decision token used for a HITL approval gate.
|
|
76
101
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
102
|
+
GA blocker #2 (issue #1248): a HITL gate must never silently auto-approve.
|
|
103
|
+
A plain ``ask`` can be satisfied by agy's cached "Always Allow" (fail-OPEN),
|
|
104
|
+
so it is NOT a safe HITL token. The resolution rules are:
|
|
105
|
+
|
|
106
|
+
* Default (env unset): ``force_ask`` -- a MANDATORY prompt that ignores the
|
|
107
|
+
cache. This is the safe, fail-closed choice.
|
|
108
|
+
* Empty / unrecognized value: falls back to ``force_ask`` (NEVER ``ask``),
|
|
109
|
+
so a typo or an unset shell can never silently downgrade the gate to a
|
|
110
|
+
cache-bypassable token.
|
|
111
|
+
* Explicit operator override via ``CZ_ANTIGRAVITY_HITL_DECISION``:
|
|
112
|
+
- ``force_ask`` -- guaranteed prompt (the default).
|
|
113
|
+
- ``deny`` -- hard block-with-reason. This is the correct fallback when
|
|
114
|
+
a pinned agy build is known to reject ``force_ask`` (which would
|
|
115
|
+
otherwise trip the ``invalid_args`` -> deny trap anyway, but ``deny``
|
|
116
|
+
makes the block explicit and carries the reason).
|
|
117
|
+
- ``ask`` -- the LEGACY cache-bypassable behavior. Permitted only as an
|
|
118
|
+
explicit, knowing downgrade; it is fail-open and must not be used for
|
|
119
|
+
an approval-gated destructive action.
|
|
120
|
+
- ``allow`` -- defeats the gate entirely; permitted only as an explicit
|
|
121
|
+
override (e.g. a soak/observe deployment).
|
|
82
122
|
"""
|
|
83
123
|
src = env if env is not None else os.environ
|
|
84
124
|
raw = (src.get("CZ_ANTIGRAVITY_HITL_DECISION") or "").strip().lower()
|
|
85
|
-
|
|
125
|
+
if raw in _VALID_DECISIONS:
|
|
126
|
+
return raw
|
|
127
|
+
# Empty or unrecognized -> fail closed to a guaranteed-prompt token.
|
|
128
|
+
return _HITL_FALLBACK_DECISION
|
|
86
129
|
|
|
87
130
|
|
|
88
131
|
class AntigravityAdapter(HostAdapter):
|
|
@@ -22,10 +22,13 @@ correct.
|
|
|
22
22
|
stdin shape before piping to hook-check, and sets ``CZ_KIRO_SURFACE=ide``
|
|
23
23
|
so KiroIDEAdapter claims the call.
|
|
24
24
|
|
|
25
|
-
Known upstream bug (kiro #7375 / #7408 / #7500): IDE ``preToolUse``
|
|
26
|
-
delivers ``USER_PROMPT={}`` with no tool context, so IDE
|
|
27
|
-
pre-tool policy is bounded
|
|
28
|
-
|
|
25
|
+
Known upstream bug (kiro #6188 / #7375 / #7408 / #7500): IDE ``preToolUse``
|
|
26
|
+
currently delivers ``USER_PROMPT={}`` with no tool context, so IDE
|
|
27
|
+
argument-level pre-tool policy is bounded. Prompt-submit carries the raw
|
|
28
|
+
prompt text; post-tool-use carries the tool *name + result only* (``toolArgs``
|
|
29
|
+
arrives empty, #6188), so IDE post-tool data does not include the tool's
|
|
30
|
+
arguments. See docs/integrations/kiro-hook-payloads.md. Pin the validated
|
|
31
|
+
Kiro version.
|
|
29
32
|
|
|
30
33
|
Note on adapter ordering: Claude Code matches PascalCase ``hook_event_name``
|
|
31
34
|
(``PreToolUse``), Kiro CLI matches camelCase (``preToolUse``) -- byte-disjoint,
|