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.
Files changed (238) hide show
  1. {controlzero-1.9.4 → controlzero-1.9.6}/CHANGELOG.md +75 -0
  2. {controlzero-1.9.4 → controlzero-1.9.6}/PKG-INFO +18 -8
  3. {controlzero-1.9.4 → controlzero-1.9.6}/README.md +17 -7
  4. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/__init__.py +1 -1
  5. controlzero-1.9.6/controlzero/cli/__main__.py +13 -0
  6. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/doctor.py +225 -2
  7. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/antigravity.py +58 -15
  8. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/kiro.py +7 -4
  9. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/main.py +389 -7
  10. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/claude-code.yaml +15 -0
  11. {controlzero-1.9.4 → controlzero-1.9.6}/pyproject.toml +1 -1
  12. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_antigravity_adapter.py +18 -12
  13. controlzero-1.9.6/tests/test_antigravity_ga_blockers_1248.py +554 -0
  14. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hosts_adapter.py +6 -5
  15. controlzero-1.9.6/tests/test_kiro_cli_e2e.py +297 -0
  16. {controlzero-1.9.4 → controlzero-1.9.6}/.gitignore +0 -0
  17. {controlzero-1.9.4 → controlzero-1.9.6}/Dockerfile.test +0 -0
  18. {controlzero-1.9.4 → controlzero-1.9.6}/LICENSE +0 -0
  19. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/__init__.py +0 -0
  20. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/action_aliases.py +0 -0
  21. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/action_validator.py +0 -0
  22. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/bundle.py +0 -0
  23. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/credential_hook.py +0 -0
  24. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/credential_scanner.py +0 -0
  25. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/credentials_data/__init__.py +0 -0
  26. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/credentials_data/built_in.yaml +0 -0
  27. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/dlp_scanner.py +0 -0
  28. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/enforcer.py +0 -0
  29. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/hook_extractors.py +0 -0
  30. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/tool_extractors.json +0 -0
  31. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/_internal/types.py +0 -0
  32. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/audit_local.py +0 -0
  33. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/audit_remote.py +0 -0
  34. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/canonical.py +0 -0
  35. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/__init__.py +0 -0
  36. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/_secrets.py +0 -0
  37. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/console.py +0 -0
  38. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/debug_bundle.py +0 -0
  39. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/__init__.py +0 -0
  40. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/base.py +0 -0
  41. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/claude_code.py +0 -0
  42. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/codex_cli.py +0 -0
  43. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/gemini_cli.py +0 -0
  44. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/hosts/unknown.py +0 -0
  45. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/kiro_adapter.py +0 -0
  46. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/migrate.py +0 -0
  47. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/spool_cmd.py +0 -0
  48. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/telemetry_consent.py +0 -0
  49. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/antigravity/hooks.json +0 -0
  50. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/antigravity.yaml +0 -0
  51. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/autogen.yaml +0 -0
  52. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/codex-cli.yaml +0 -0
  53. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/cost-cap.yaml +0 -0
  54. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/crewai.yaml +0 -0
  55. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/cursor.yaml +0 -0
  56. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/gemini-cli.yaml +0 -0
  57. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/generic.yaml +0 -0
  58. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/kiro/ide-file-save.kiro.hook +0 -0
  59. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/kiro/ide-pre-tool-use.kiro.hook +0 -0
  60. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/kiro/ide-prompt-submit.kiro.hook +0 -0
  61. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/kiro/kiro.yaml +0 -0
  62. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/langchain.yaml +0 -0
  63. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/mcp.yaml +0 -0
  64. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/cli/templates/rag.yaml +0 -0
  65. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/client.py +0 -0
  66. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/device.py +0 -0
  67. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/enrollment.py +0 -0
  68. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/error_codes.py +0 -0
  69. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/error_codes.yaml +0 -0
  70. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/errors.py +0 -0
  71. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/__init__.py +0 -0
  72. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/grant_protocol.py +0 -0
  73. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/mock.py +0 -0
  74. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/pending_approval.py +0 -0
  75. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/secret_leak_guard.py +0 -0
  76. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hitl/status.py +0 -0
  77. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hooks/__init__.py +0 -0
  78. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hooks/tool_output_handler.py +0 -0
  79. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/hosted_policy.py +0 -0
  80. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/__init__.py +0 -0
  81. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/anthropic.py +0 -0
  82. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/autogen.py +0 -0
  83. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/braintrust.py +0 -0
  84. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/crewai/__init__.py +0 -0
  85. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/crewai/agent.py +0 -0
  86. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/crewai/crew.py +0 -0
  87. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/crewai/task.py +0 -0
  88. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/crewai/tool.py +0 -0
  89. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/google.py +0 -0
  90. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/google_adk/__init__.py +0 -0
  91. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/google_adk/agent.py +0 -0
  92. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/google_adk/tool.py +0 -0
  93. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/__init__.py +0 -0
  94. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/agent.py +0 -0
  95. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/callbacks.py +0 -0
  96. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/chain.py +0 -0
  97. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/graph.py +0 -0
  98. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/modern.py +0 -0
  99. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langchain/tool.py +0 -0
  100. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/langfuse.py +0 -0
  101. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/litellm.py +0 -0
  102. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/openai.py +0 -0
  103. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/pydantic_ai.py +0 -0
  104. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/integrations/vercel_ai.py +0 -0
  105. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/layout_migration.py +0 -0
  106. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/policy_loader.py +0 -0
  107. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/__init__.py +0 -0
  108. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_compress.py +0 -0
  109. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_constants.py +0 -0
  110. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_crc32c.py +0 -0
  111. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_crypto.py +0 -0
  112. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_frame.py +0 -0
  113. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_keyring.py +0 -0
  114. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_metrics.py +0 -0
  115. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_spool.py +0 -0
  116. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_state.py +0 -0
  117. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/_uploader.py +0 -0
  118. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/spool/cz-audit-v1.dict +0 -0
  119. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/tamper.py +0 -0
  120. {controlzero-1.9.4 → controlzero-1.9.6}/controlzero/tracecontext.py +0 -0
  121. {controlzero-1.9.4 → controlzero-1.9.6}/examples/hello_world.py +0 -0
  122. {controlzero-1.9.4 → controlzero-1.9.6}/tests/_fixtures/jcs_args_hash_vectors.json +0 -0
  123. {controlzero-1.9.4 → controlzero-1.9.6}/tests/conftest.py +0 -0
  124. {controlzero-1.9.4 → controlzero-1.9.6}/tests/integrations/__init__.py +0 -0
  125. {controlzero-1.9.4 → controlzero-1.9.6}/tests/integrations/test_google.py +0 -0
  126. {controlzero-1.9.4 → controlzero-1.9.6}/tests/parity/action_aliases.json +0 -0
  127. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/__init__.py +0 -0
  128. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/conftest.py +0 -0
  129. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_cli.py +0 -0
  130. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_concurrency.py +0 -0
  131. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_conformance.py +0 -0
  132. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_core.py +0 -0
  133. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_crash.py +0 -0
  134. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_diskfull.py +0 -0
  135. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_durable_default_tamper.py +0 -0
  136. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_keychain_dek.py +0 -0
  137. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_sink_wiring.py +0 -0
  138. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_transcript_localack.py +0 -0
  139. {controlzero-1.9.4 → controlzero-1.9.6}/tests/spool/test_spool_uploader.py +0 -0
  140. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_action_aliases.py +0 -0
  141. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_action_canonicalization.py +0 -0
  142. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_action_validator_t86.py +0 -0
  143. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_agent_name_env.py +0 -0
  144. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_antigravity_hook_check.py +0 -0
  145. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_antigravity_install.py +0 -0
  146. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_api_key_mask.py +0 -0
  147. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_audit_remote.py +0 -0
  148. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_audit_remote_sdk_version.py +0 -0
  149. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_audit_sink_isolation.py +0 -0
  150. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_bundle_parser.py +0 -0
  151. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_bundle_translate.py +0 -0
  152. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_canonical_phase1a.py +0 -0
  153. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_carve_out.py +0 -0
  154. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_debug_bundle.py +0 -0
  155. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_extractor_integration.py +0 -0
  156. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_hook.py +0 -0
  157. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_hosted_refresh.py +0 -0
  158. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_init.py +0 -0
  159. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_init_templates.py +0 -0
  160. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_tail.py +0 -0
  161. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_test.py +0 -0
  162. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_cli_validate.py +0 -0
  163. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_coding_agent_hooks.py +0 -0
  164. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_conditions.py +0 -0
  165. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_conformance.py +0 -0
  166. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_console.py +0 -0
  167. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_credential_hook.py +0 -0
  168. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_default_action.py +0 -0
  169. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_device.py +0 -0
  170. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_dlp_scanner.py +0 -0
  171. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_doctor.py +0 -0
  172. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_engine_version_consistency.py +0 -0
  173. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_enrollment.py +0 -0
  174. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_env_dump_438.py +0 -0
  175. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_epic_1247_bryan_acceptance.py +0 -0
  176. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_error_codes.py +0 -0
  177. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_errors_e_codes.py +0 -0
  178. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_fail_closed_eval.py +0 -0
  179. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_glob_matching.py +0 -0
  180. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_5d_email_install.py +0 -0
  181. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_cli_flag.py +0 -0
  182. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_exceptions.py +0 -0
  183. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_get_secret_hitl.py +0 -0
  184. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_mock_backend.py +0 -0
  185. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_pending_approval.py +0 -0
  186. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_request_approval.py +0 -0
  187. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_secret_leak_guard.py +0 -0
  188. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_6a_wait.py +0 -0
  189. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_conformance.py +0 -0
  190. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_phase2b_protocol.py +0 -0
  191. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_reason_codes.py +0 -0
  192. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hitl_validator_keys.py +0 -0
  193. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hook_extractors.py +0 -0
  194. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hosted_local_audit_1247.py +0 -0
  195. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hosted_policy_e2e.py +0 -0
  196. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hybrid_mode_strict.py +0 -0
  197. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_hybrid_mode_warn.py +0 -0
  198. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_install_hook_command.py +0 -0
  199. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_install_hooks.py +0 -0
  200. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_kiro_adapter.py +0 -0
  201. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_kiro_hook_templates.py +0 -0
  202. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_kiro_install.py +0 -0
  203. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_layout_migration_t101.py +0 -0
  204. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_layout_parity_t102.py +0 -0
  205. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_local_mode_dict.py +0 -0
  206. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_local_mode_file_json.py +0 -0
  207. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_local_mode_file_yaml.py +0 -0
  208. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_log_fallback_stderr.py +0 -0
  209. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_log_options_ignored_hosted.py +0 -0
  210. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_log_rotation.py +0 -0
  211. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_migrate.py +0 -0
  212. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_min_sdk_version_gate.py +0 -0
  213. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_multi_client_per_project_175.py +0 -0
  214. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_no_policy_no_key.py +0 -0
  215. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_observe_mode_1247.py +0 -0
  216. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_package_rename_shim.py +0 -0
  217. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_policy_engine_version_phase1b.py +0 -0
  218. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_policy_freshness.py +0 -0
  219. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_policy_settings.py +0 -0
  220. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_policy_source_audit.py +0 -0
  221. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_quarantine.py +0 -0
  222. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_reason_code.py +0 -0
  223. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_refresh.py +0 -0
  224. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_secrets.py +0 -0
  225. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_sql_semantic_class.py +0 -0
  226. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_synthetic_policy_id_t79.py +0 -0
  227. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_t103_precedence.py +0 -0
  228. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_t104_cache_gc.py +0 -0
  229. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_t108_local_override_audit.py +0 -0
  230. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_t96_single_audit_log.py +0 -0
  231. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_t99_install_prefetch_bundle.py +0 -0
  232. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_tamper.py +0 -0
  233. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_tamper_behavior.py +0 -0
  234. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_tamper_hook.py +0 -0
  235. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_telemetry_consent.py +0 -0
  236. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_tracecontext.py +0 -0
  237. {controlzero-1.9.4 → controlzero-1.9.6}/tests/test_unsafe_int_boundary.py +0 -0
  238. {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.4
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 IDE) hook starter (Beta)
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
- When running without an API key, every decision is written to `./controlzero.log`
229
- with daily rotation and 30-day retention. Tail it:
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
- Configure rotation via the client:
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 IDE) hook starter (Beta)
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
- When running without an API key, every decision is written to `./controlzero.log`
175
- with daily rotation and 30-day retention. Tail it:
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
- Configure rotation via the client:
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
@@ -40,7 +40,7 @@ from controlzero.hitl.grant_protocol import (
40
40
  )
41
41
  from controlzero.policy_loader import load_policy
42
42
 
43
- __version__ = "1.9.4"
43
+ __version__ = "1.9.6"
44
44
 
45
45
  __all__ = [
46
46
  "Client",
@@ -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
- _AgentPath("antigravity", home / ".antigravity" / "config.json",
86
- "Antigravity config"),
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 ``ask`` by default. A
32
- plain ``ask`` CAN be auto-satisfied by a cached "Always Allow" (so it is not
33
- a guaranteed prompt); some agy builds also accept ``force_ask`` (always
34
- prompts). Because ``force_ask`` is not confirmed across all builds and an
35
- unrecognized decision risks the same ``invalid_args`` -> deny trap, the HITL
36
- decision token is configurable via ``CZ_ANTIGRAVITY_HITL_DECISION`` (default
37
- ``ask``; set to ``force_ask`` once verified against your pinned agy, or
38
- ``deny`` for a hard block-with-reason).
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
- _DEFAULT_HITL_DECISION = "ask"
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
- Default ``ask`` (source-confirmed valid on every agy build). Operators
78
- can opt into ``force_ask`` (guaranteed prompt) or ``deny`` once verified
79
- against their pinned agy via ``CZ_ANTIGRAVITY_HITL_DECISION``. An
80
- unrecognized value falls back to ``ask`` so we never emit a token that
81
- could trip the ``invalid_args`` -> deny trap.
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
- return raw if raw in _VALID_DECISIONS else _DEFAULT_HITL_DECISION
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`` currently
26
- delivers ``USER_PROMPT={}`` with no tool context, so IDE argument-level
27
- pre-tool policy is bounded; prompt-submit and post-tool-use carry data. See
28
- docs/integrations/kiro-hook-payloads.md. Pin the validated Kiro version.
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,