controlzero 1.9.3__tar.gz → 1.9.4__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.3 → controlzero-1.9.4}/CHANGELOG.md +21 -1
- {controlzero-1.9.3 → controlzero-1.9.4}/PKG-INFO +1 -1
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/__init__.py +1 -1
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/client.py +8 -2
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/error_codes.yaml +10 -5
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/errors.py +105 -2
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/hosted_policy.py +17 -6
- {controlzero-1.9.3 → controlzero-1.9.4}/pyproject.toml +1 -1
- controlzero-1.9.4/tests/test_epic_1247_bryan_acceptance.py +529 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_errors_e_codes.py +93 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/.gitignore +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/Dockerfile.test +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/LICENSE +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/README.md +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/action_aliases.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/action_validator.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/bundle.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/credential_hook.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/credential_scanner.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/credentials_data/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/dlp_scanner.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/enforcer.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/hook_extractors.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/tool_extractors.json +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/_internal/types.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/audit_local.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/audit_remote.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/canonical.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/_secrets.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/console.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/debug_bundle.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/doctor.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/hosts/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/hosts/antigravity.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/hosts/base.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/hosts/claude_code.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/hosts/codex_cli.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/hosts/gemini_cli.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/hosts/kiro.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/hosts/unknown.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/kiro_adapter.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/main.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/migrate.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/spool_cmd.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/telemetry_consent.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/antigravity/hooks.json +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/antigravity.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/autogen.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/claude-code.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/codex-cli.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/cost-cap.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/crewai.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/cursor.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/gemini-cli.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/generic.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/langchain.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/mcp.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/cli/templates/rag.yaml +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/device.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/enrollment.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/error_codes.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/hitl/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/hitl/grant_protocol.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/hitl/mock.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/hitl/pending_approval.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/hitl/secret_leak_guard.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/hitl/status.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/hooks/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/hooks/tool_output_handler.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/anthropic.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/autogen.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/braintrust.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/crewai/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/crewai/agent.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/crewai/crew.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/crewai/task.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/crewai/tool.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/google.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/google_adk/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/google_adk/agent.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/google_adk/tool.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/langchain/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/langchain/agent.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/langchain/callbacks.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/langchain/chain.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/langchain/graph.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/langchain/modern.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/langchain/tool.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/langfuse.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/litellm.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/openai.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/pydantic_ai.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/integrations/vercel_ai.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/layout_migration.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/policy_loader.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/_compress.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/_constants.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/_crc32c.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/_crypto.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/_frame.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/_keyring.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/_metrics.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/_spool.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/_state.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/_uploader.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/spool/cz-audit-v1.dict +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/tamper.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/controlzero/tracecontext.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/examples/hello_world.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/conftest.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/integrations/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/integrations/test_google.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/parity/action_aliases.json +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/__init__.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/conftest.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_cli.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_concurrency.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_conformance.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_core.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_crash.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_diskfull.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_durable_default_tamper.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_keychain_dek.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_sink_wiring.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_transcript_localack.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/spool/test_spool_uploader.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_action_aliases.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_action_canonicalization.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_action_validator_t86.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_agent_name_env.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_antigravity_adapter.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_antigravity_hook_check.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_antigravity_install.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_api_key_mask.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_audit_remote.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_audit_remote_sdk_version.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_audit_sink_isolation.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_bundle_parser.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_bundle_translate.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_canonical_phase1a.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_cli_carve_out.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_cli_debug_bundle.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_cli_extractor_integration.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_cli_hook.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_cli_hosted_refresh.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_cli_init.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_cli_init_templates.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_cli_tail.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_cli_test.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_cli_validate.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_coding_agent_hooks.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_conditions.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_conformance.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_console.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_credential_hook.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_default_action.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_device.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_dlp_scanner.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_doctor.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_engine_version_consistency.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_enrollment.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_env_dump_438.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_error_codes.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_fail_closed_eval.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_glob_matching.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_5d_email_install.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_6a_cli_flag.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_6a_exceptions.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_6a_mock_backend.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_6a_pending_approval.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_6a_request_approval.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_6a_wait.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_conformance.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_phase2b_protocol.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_reason_codes.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hitl_validator_keys.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hook_extractors.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hosted_local_audit_1247.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hosted_policy_e2e.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hosts_adapter.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hybrid_mode_strict.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_hybrid_mode_warn.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_install_hook_command.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_install_hooks.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_kiro_adapter.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_kiro_hook_templates.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_kiro_install.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_layout_migration_t101.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_layout_parity_t102.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_local_mode_dict.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_local_mode_file_json.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_local_mode_file_yaml.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_log_fallback_stderr.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_log_options_ignored_hosted.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_log_rotation.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_migrate.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_min_sdk_version_gate.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_multi_client_per_project_175.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_no_policy_no_key.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_observe_mode_1247.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_package_rename_shim.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_policy_engine_version_phase1b.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_policy_freshness.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_policy_settings.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_policy_source_audit.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_quarantine.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_reason_code.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_refresh.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_secrets.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_sql_semantic_class.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_synthetic_policy_id_t79.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_t103_precedence.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_t104_cache_gc.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_t108_local_override_audit.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_t96_single_audit_log.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_t99_install_prefetch_bundle.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_tamper.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_tamper_behavior.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_tamper_hook.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_telemetry_consent.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_tracecontext.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tests/test_unsafe_int_boundary.py +0 -0
- {controlzero-1.9.3 → controlzero-1.9.4}/tools/cz-kiro-adapter +0 -0
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.4 -- 2026-06-16 (actionable E1101 / API-key-rejected message, #1254, epic gh#1247)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **(#1254) `HostedAuthError` (E1101) is now actionable instead of an
|
|
8
|
+
opaque "API key rejected (401)".** A dead/revoked/expired/placeholder API key
|
|
9
|
+
in hosted mode used to surface a bare message that told the user neither WHY
|
|
10
|
+
nor what to do. The message now always states the key is invalid/revoked/
|
|
11
|
+
expired and how to fix it: "Generate a new API key in the dashboard (Settings
|
|
12
|
+
-> API Keys, https://app.controlzero.ai/settings/api-keys) and set
|
|
13
|
+
CONTROLZERO_API_KEY to it." When the backend sends a structured 401 body, the
|
|
14
|
+
exception preserves the coarse machine-readable `reason` (e.g.
|
|
15
|
+
`invalid_or_revoked`) and the `remediation` on the exception so programmatic
|
|
16
|
+
callers can branch while humans read the fix. The `E1101` code is unchanged.
|
|
17
|
+
All four 401 raise sites (bootstrap, bundle pull, approval request, get
|
|
18
|
+
secret) now parse the body via `HostedAuthError.from_response(...)`.
|
|
19
|
+
Enumeration-safe: the backend keeps the reason coarse (not_found / revoked /
|
|
20
|
+
expired / placeholder all collapse to `invalid_or_revoked`) so a 401 cannot be
|
|
21
|
+
used to probe which keys exist. Tests:
|
|
22
|
+
`tests/test_errors_e_codes.py::TestHostedAuthErrorActionable`.
|
|
23
|
+
|
|
3
24
|
## 1.9.3 -- 2026-06-16 (posture release: empty-bundle OBSERVE + self-explaining no-rule-match deny, epic gh#1247)
|
|
4
25
|
|
|
5
26
|
The consolidated **posture release** for epic gh#1247 (customer Bryan).
|
|
@@ -76,7 +97,6 @@ second-opinion). It is deliberately **narrow and gated**:
|
|
|
76
97
|
bundle translator -> `PolicySettings`. Local YAML policies may set
|
|
77
98
|
`settings.default_on_empty`. The dashboard observe-mode indicator +
|
|
78
99
|
CLI `status` line remain a separate frontend/CLI follow-up.
|
|
79
|
-
|
|
80
100
|
## 1.9.1 -- 2026-06-16 (hosted-mode local audit log P0, epic gh#1247)
|
|
81
101
|
|
|
82
102
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: controlzero
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.4
|
|
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
|
|
@@ -676,9 +676,11 @@ class Client:
|
|
|
676
676
|
# Non-2xx: map status + body code to the right typed exception.
|
|
677
677
|
body_code = ""
|
|
678
678
|
body_msg = ""
|
|
679
|
+
err_body_dict: dict = {}
|
|
679
680
|
try:
|
|
680
681
|
err_body = resp.json()
|
|
681
682
|
if isinstance(err_body, dict):
|
|
683
|
+
err_body_dict = err_body
|
|
682
684
|
body_code = str(err_body.get("code", "") or "")
|
|
683
685
|
body_msg = str(err_body.get("message", "") or err_body.get("error", "") or "")
|
|
684
686
|
except ValueError:
|
|
@@ -717,7 +719,7 @@ class Client:
|
|
|
717
719
|
body_msg or "Requestor identity claim rejected by backend"
|
|
718
720
|
)
|
|
719
721
|
if status == 401:
|
|
720
|
-
raise HostedAuthError(
|
|
722
|
+
raise HostedAuthError.from_response(status, err_body_dict)
|
|
721
723
|
if status == 404:
|
|
722
724
|
raise HITLNotConfiguredError(
|
|
723
725
|
body_msg or "Approvals not configured for this org"
|
|
@@ -885,7 +887,11 @@ class Client:
|
|
|
885
887
|
if status == 404:
|
|
886
888
|
raise SecretNotFound(f"secret {name!r} not found")
|
|
887
889
|
if status == 401:
|
|
888
|
-
|
|
890
|
+
try:
|
|
891
|
+
secret_err_body = resp.json()
|
|
892
|
+
except ValueError:
|
|
893
|
+
secret_err_body = None
|
|
894
|
+
raise HostedAuthError.from_response(status, secret_err_body)
|
|
889
895
|
if not (200 <= status < 300):
|
|
890
896
|
raise HITLBackendUnreachableError(
|
|
891
897
|
f"GET /api/secrets/{name} returned HTTP {status}"
|
|
@@ -95,11 +95,16 @@ codes:
|
|
|
95
95
|
- code: E1101
|
|
96
96
|
title: API key rejected (401)
|
|
97
97
|
what: >-
|
|
98
|
-
The hosted backend rejected the API key. The key is
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
The hosted backend rejected the API key with HTTP 401. The key is
|
|
99
|
+
unknown, revoked, expired, or a never-activated placeholder. For
|
|
100
|
+
safety the backend does not say which (so a 401 cannot be used to
|
|
101
|
+
probe which keys exist), but in every case the same fix applies:
|
|
102
|
+
the key you are using is no longer valid.
|
|
103
|
+
fix: >-
|
|
104
|
+
Generate a fresh key in the dashboard under Settings -> API Keys
|
|
105
|
+
(https://app.controlzero.ai/settings/api-keys) and set
|
|
106
|
+
CONTROLZERO_API_KEY to it. If you just rotated a key, make sure the
|
|
107
|
+
environment your agent runs in picked up the new value.
|
|
103
108
|
doc: E1101-key-rejected
|
|
104
109
|
|
|
105
110
|
- code: E1102
|
|
@@ -28,6 +28,18 @@ from controlzero._internal.enforcer import PolicyDeniedError, PolicyDecision
|
|
|
28
28
|
from controlzero.error_codes import ErrorCode, get as _get_error_code
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def _first_str(*values: object) -> Optional[str]:
|
|
32
|
+
"""Return the first value that is a non-empty string, else None.
|
|
33
|
+
|
|
34
|
+
Used to read a field that the backend may place at the top level or
|
|
35
|
+
nested under ``error`` without crashing on unexpected types.
|
|
36
|
+
"""
|
|
37
|
+
for v in values:
|
|
38
|
+
if isinstance(v, str) and v.strip():
|
|
39
|
+
return v
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
31
43
|
def _augment_with_code(message: str, code_key: Optional[str]) -> str:
|
|
32
44
|
"""Append the catalog's fix + docs URL to a raw error message.
|
|
33
45
|
|
|
@@ -137,6 +149,12 @@ class BundleSignatureError(_CZErrorMixin, Exception):
|
|
|
137
149
|
super().__init__(_augment_with_code(message, self.E_CODE))
|
|
138
150
|
|
|
139
151
|
|
|
152
|
+
_DEFAULT_AUTH_REMEDIATION = (
|
|
153
|
+
"Generate a new API key in the dashboard (Settings -> API Keys, "
|
|
154
|
+
"https://app.controlzero.ai/settings/api-keys) and set CONTROLZERO_API_KEY to it."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
140
158
|
class HostedAuthError(_CZErrorMixin, RuntimeError):
|
|
141
159
|
"""Raised when the project API key is rejected by the backend
|
|
142
160
|
(401/403). Maps to E1101.
|
|
@@ -144,12 +162,97 @@ class HostedAuthError(_CZErrorMixin, RuntimeError):
|
|
|
144
162
|
This is a permanent failure: the caller supplied an invalid or
|
|
145
163
|
revoked API key. Retrying with the same key will not help. The
|
|
146
164
|
SDK surfaces this so the user can correct their configuration.
|
|
165
|
+
|
|
166
|
+
#1254: the bare "API key rejected (401)" gave the user no
|
|
167
|
+
idea WHY or what to do. The message now always tells the user the
|
|
168
|
+
key is invalid/revoked and how to fix it. When the backend supplies
|
|
169
|
+
a structured 401 body (``reason`` + ``remediation``), those are
|
|
170
|
+
preserved on the exception so programmatic callers can branch on
|
|
171
|
+
:attr:`reason` while humans read the actionable message.
|
|
172
|
+
|
|
173
|
+
The backend keeps ``reason`` coarse on purpose (not_found / revoked /
|
|
174
|
+
expired / placeholder all collapse to ``invalid_or_revoked``) so a
|
|
175
|
+
401 is never an enumeration oracle; the SDK does not try to refine it.
|
|
176
|
+
|
|
177
|
+
Attributes:
|
|
178
|
+
reason: coarse machine-readable reason from the backend, e.g.
|
|
179
|
+
``"invalid_or_revoked"``. ``None`` when the backend did not
|
|
180
|
+
send a structured body (older backend).
|
|
181
|
+
remediation: copy-pasteable next step shown to the user.
|
|
147
182
|
"""
|
|
148
183
|
|
|
149
184
|
E_CODE = "E1101"
|
|
150
185
|
|
|
151
|
-
def __init__(
|
|
152
|
-
|
|
186
|
+
def __init__(
|
|
187
|
+
self,
|
|
188
|
+
message: Optional[str] = None,
|
|
189
|
+
*,
|
|
190
|
+
reason: Optional[str] = None,
|
|
191
|
+
remediation: Optional[str] = None,
|
|
192
|
+
):
|
|
193
|
+
self.reason = reason
|
|
194
|
+
self.remediation = remediation or _DEFAULT_AUTH_REMEDIATION
|
|
195
|
+
if message is None:
|
|
196
|
+
message = (
|
|
197
|
+
"Your Control Zero API key was rejected by the backend "
|
|
198
|
+
"(invalid, revoked, or expired)."
|
|
199
|
+
)
|
|
200
|
+
full = f"{message} {self.remediation}"
|
|
201
|
+
super().__init__(_augment_with_code(full, self.E_CODE))
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def from_response(
|
|
205
|
+
cls,
|
|
206
|
+
status_code: int,
|
|
207
|
+
body: object = None,
|
|
208
|
+
*,
|
|
209
|
+
context: str = "",
|
|
210
|
+
) -> "HostedAuthError":
|
|
211
|
+
"""Build a HostedAuthError from a backend 401/403 response.
|
|
212
|
+
|
|
213
|
+
``body`` is the parsed JSON (a dict) when available, else None.
|
|
214
|
+
The backend may put the structured fields either at the top
|
|
215
|
+
level (``body["reason"]``) or nested under ``body["error"]``;
|
|
216
|
+
we read both. ``context`` is an optional short phrase like
|
|
217
|
+
"during bundle pull" appended to the headline so logs say which
|
|
218
|
+
request failed. Never raises on a malformed body -- a bad body
|
|
219
|
+
just yields the actionable default message.
|
|
220
|
+
"""
|
|
221
|
+
reason: Optional[str] = None
|
|
222
|
+
remediation: Optional[str] = None
|
|
223
|
+
backend_msg: Optional[str] = None
|
|
224
|
+
if isinstance(body, dict):
|
|
225
|
+
err = body.get("error")
|
|
226
|
+
err_dict = err if isinstance(err, dict) else {}
|
|
227
|
+
reason = _first_str(body.get("reason"), err_dict.get("reason"))
|
|
228
|
+
remediation = _first_str(
|
|
229
|
+
body.get("remediation"), err_dict.get("remediation")
|
|
230
|
+
)
|
|
231
|
+
# The backend message can be body["message"], the nested
|
|
232
|
+
# error.message, or -- for back-compat with non-structured
|
|
233
|
+
# backends -- a plain string in the top-level "error" key
|
|
234
|
+
# (e.g. {"error": "Unauthorized"}).
|
|
235
|
+
err_str = err if isinstance(err, str) else None
|
|
236
|
+
backend_msg = _first_str(
|
|
237
|
+
body.get("message"), err_dict.get("message"), err_str
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
headline = (
|
|
241
|
+
"Your Control Zero API key was rejected by the backend "
|
|
242
|
+
"(invalid, revoked, or expired)."
|
|
243
|
+
)
|
|
244
|
+
if context:
|
|
245
|
+
headline = (
|
|
246
|
+
f"Your Control Zero API key was rejected by the backend "
|
|
247
|
+
f"{context} (invalid, revoked, or expired)."
|
|
248
|
+
)
|
|
249
|
+
# Prefer the backend's own human message when it is more specific
|
|
250
|
+
# than our generic headline, but always keep the headline so the
|
|
251
|
+
# user sees the "what to do" framing even with an old backend.
|
|
252
|
+
message = headline
|
|
253
|
+
if backend_msg and backend_msg.lower() not in ("invalid api key", ""):
|
|
254
|
+
message = f"{headline} (backend: {backend_msg})"
|
|
255
|
+
return cls(message, reason=reason, remediation=remediation)
|
|
153
256
|
|
|
154
257
|
|
|
155
258
|
class HostedBootstrapError(_CZErrorMixin, RuntimeError):
|
|
@@ -66,6 +66,20 @@ PULL_TIMEOUT_S = 5.0
|
|
|
66
66
|
MAX_BUNDLE_BYTES = 16 * 1024 * 1024
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
def _try_json(resp: object) -> object:
|
|
70
|
+
"""Best-effort parse of an httpx response body as JSON.
|
|
71
|
+
|
|
72
|
+
Returns the decoded object on success, or ``None`` on any failure
|
|
73
|
+
(non-JSON body, empty body, missing ``.json``). Never raises -- a
|
|
74
|
+
structured 401 body is a bonus, not a requirement, so a malformed
|
|
75
|
+
body must not mask the underlying auth error (#1254).
|
|
76
|
+
"""
|
|
77
|
+
try:
|
|
78
|
+
return resp.json() # type: ignore[attr-defined]
|
|
79
|
+
except Exception: # noqa: BLE001
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
69
83
|
def get_api_url() -> str:
|
|
70
84
|
"""Resolve the Control Zero API base URL."""
|
|
71
85
|
return os.environ.get("CONTROLZERO_API_URL", DEFAULT_API_URL).rstrip("/")
|
|
@@ -230,10 +244,7 @@ def fetch_bootstrap(api_key: str, api_url: Optional[str] = None) -> BootstrapKey
|
|
|
230
244
|
) from exc
|
|
231
245
|
|
|
232
246
|
if resp.status_code in (401, 403):
|
|
233
|
-
raise HostedAuthError(
|
|
234
|
-
"Control Zero API key rejected by backend. Check that "
|
|
235
|
-
"CONTROLZERO_API_KEY is a valid cz_live_ or cz_test_ project key."
|
|
236
|
-
)
|
|
247
|
+
raise HostedAuthError.from_response(resp.status_code, _try_json(resp))
|
|
237
248
|
if resp.status_code >= 400:
|
|
238
249
|
raise HostedBootstrapError(
|
|
239
250
|
f"bootstrap returned HTTP {resp.status_code}: {resp.text[:500]}"
|
|
@@ -370,8 +381,8 @@ def pull_bundle(
|
|
|
370
381
|
if resp.status_code == 304:
|
|
371
382
|
return None
|
|
372
383
|
if resp.status_code in (401, 403):
|
|
373
|
-
raise HostedAuthError(
|
|
374
|
-
|
|
384
|
+
raise HostedAuthError.from_response(
|
|
385
|
+
resp.status_code, _try_json(resp), context="during bundle pull"
|
|
375
386
|
)
|
|
376
387
|
if resp.status_code >= 400:
|
|
377
388
|
raise HostedBootstrapError(
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "controlzero"
|
|
7
|
-
version = "1.9.
|
|
7
|
+
version = "1.9.4"
|
|
8
8
|
description = "AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|