controlzero 1.11.4__tar.gz → 1.11.6__tar.gz

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