controlzero 1.5.5__tar.gz → 1.5.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 (149) hide show
  1. {controlzero-1.5.5 → controlzero-1.5.6}/CHANGELOG.md +34 -5
  2. {controlzero-1.5.5 → controlzero-1.5.6}/PKG-INFO +1 -1
  3. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/__init__.py +1 -1
  4. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/_internal/bundle.py +1 -1
  5. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/_internal/enforcer.py +3 -3
  6. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/_secrets.py +5 -5
  7. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/debug_bundle.py +1 -1
  8. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/hosts/claude_code.py +1 -1
  9. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/hosts/gemini_cli.py +1 -1
  10. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/main.py +7 -7
  11. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/client.py +1 -1
  12. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/device.py +2 -2
  13. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/enrollment.py +1 -1
  14. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/hosted_policy.py +3 -3
  15. {controlzero-1.5.5 → controlzero-1.5.6}/pyproject.toml +1 -1
  16. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_api_key_mask.py +1 -1
  17. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_bundle_translate.py +1 -1
  18. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_cli_extractor_integration.py +3 -3
  19. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_cli_hosted_refresh.py +1 -1
  20. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_device.py +1 -1
  21. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_env_dump_438.py +1 -1
  22. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_glob_matching.py +3 -3
  23. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_hosts_adapter.py +2 -2
  24. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_install_hook_command.py +1 -1
  25. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_install_hooks.py +1 -1
  26. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_reason_code.py +3 -3
  27. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_refresh.py +1 -1
  28. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_secrets.py +7 -7
  29. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_sql_semantic_class.py +1 -1
  30. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_synthetic_policy_id_t79.py +1 -1
  31. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_t103_precedence.py +2 -2
  32. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_t104_cache_gc.py +1 -1
  33. {controlzero-1.5.5 → controlzero-1.5.6}/.gitignore +0 -0
  34. {controlzero-1.5.5 → controlzero-1.5.6}/Dockerfile.test +0 -0
  35. {controlzero-1.5.5 → controlzero-1.5.6}/LICENSE +0 -0
  36. {controlzero-1.5.5 → controlzero-1.5.6}/README.md +0 -0
  37. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/_internal/__init__.py +0 -0
  38. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/_internal/action_aliases.py +0 -0
  39. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/_internal/dlp_scanner.py +0 -0
  40. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/_internal/hook_extractors.py +0 -0
  41. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/_internal/tool_extractors.json +0 -0
  42. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/_internal/types.py +0 -0
  43. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/audit_local.py +0 -0
  44. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/audit_remote.py +0 -0
  45. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/__init__.py +0 -0
  46. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/console.py +0 -0
  47. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/doctor.py +0 -0
  48. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/hosts/__init__.py +0 -0
  49. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/hosts/base.py +0 -0
  50. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/hosts/codex_cli.py +0 -0
  51. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/hosts/unknown.py +0 -0
  52. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/migrate.py +0 -0
  53. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/telemetry_consent.py +0 -0
  54. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/autogen.yaml +0 -0
  55. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/claude-code.yaml +0 -0
  56. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/codex-cli.yaml +0 -0
  57. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/cost-cap.yaml +0 -0
  58. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/crewai.yaml +0 -0
  59. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/cursor.yaml +0 -0
  60. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/gemini-cli.yaml +0 -0
  61. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/generic.yaml +0 -0
  62. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/langchain.yaml +0 -0
  63. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/mcp.yaml +0 -0
  64. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/cli/templates/rag.yaml +0 -0
  65. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/error_codes.py +0 -0
  66. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/errors.py +0 -0
  67. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/__init__.py +0 -0
  68. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/anthropic.py +0 -0
  69. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/autogen.py +0 -0
  70. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/braintrust.py +0 -0
  71. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/crewai/__init__.py +0 -0
  72. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/crewai/agent.py +0 -0
  73. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/crewai/crew.py +0 -0
  74. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/crewai/task.py +0 -0
  75. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/crewai/tool.py +0 -0
  76. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/google.py +0 -0
  77. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/google_adk/__init__.py +0 -0
  78. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/google_adk/agent.py +0 -0
  79. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/google_adk/tool.py +0 -0
  80. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/langchain/__init__.py +0 -0
  81. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/langchain/agent.py +0 -0
  82. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/langchain/callbacks.py +0 -0
  83. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/langchain/chain.py +0 -0
  84. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/langchain/graph.py +0 -0
  85. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/langchain/modern.py +0 -0
  86. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/langchain/tool.py +0 -0
  87. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/langfuse.py +0 -0
  88. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/litellm.py +0 -0
  89. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/openai.py +0 -0
  90. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/pydantic_ai.py +0 -0
  91. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/integrations/vercel_ai.py +0 -0
  92. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/layout_migration.py +0 -0
  93. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/policy_loader.py +0 -0
  94. {controlzero-1.5.5 → controlzero-1.5.6}/controlzero/tamper.py +0 -0
  95. {controlzero-1.5.5 → controlzero-1.5.6}/examples/hello_world.py +0 -0
  96. {controlzero-1.5.5 → controlzero-1.5.6}/tests/conftest.py +0 -0
  97. {controlzero-1.5.5 → controlzero-1.5.6}/tests/integrations/__init__.py +0 -0
  98. {controlzero-1.5.5 → controlzero-1.5.6}/tests/integrations/test_google.py +0 -0
  99. {controlzero-1.5.5 → controlzero-1.5.6}/tests/parity/action_aliases.json +0 -0
  100. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_action_aliases.py +0 -0
  101. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_action_canonicalization.py +0 -0
  102. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_agent_name_env.py +0 -0
  103. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_audit_remote.py +0 -0
  104. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_audit_remote_sdk_version.py +0 -0
  105. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_audit_sink_isolation.py +0 -0
  106. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_bundle_parser.py +0 -0
  107. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_cli_carve_out.py +0 -0
  108. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_cli_debug_bundle.py +0 -0
  109. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_cli_hook.py +0 -0
  110. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_cli_init.py +0 -0
  111. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_cli_init_templates.py +0 -0
  112. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_cli_tail.py +0 -0
  113. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_cli_test.py +0 -0
  114. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_cli_validate.py +0 -0
  115. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_coding_agent_hooks.py +0 -0
  116. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_conditions.py +0 -0
  117. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_console.py +0 -0
  118. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_default_action.py +0 -0
  119. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_dlp_scanner.py +0 -0
  120. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_doctor.py +0 -0
  121. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_enrollment.py +0 -0
  122. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_error_codes.py +0 -0
  123. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_errors_e_codes.py +0 -0
  124. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_fail_closed_eval.py +0 -0
  125. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_hook_extractors.py +0 -0
  126. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_hosted_policy_e2e.py +0 -0
  127. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_hybrid_mode_strict.py +0 -0
  128. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_hybrid_mode_warn.py +0 -0
  129. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_layout_migration_t101.py +0 -0
  130. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_layout_parity_t102.py +0 -0
  131. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_local_mode_dict.py +0 -0
  132. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_local_mode_file_json.py +0 -0
  133. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_local_mode_file_yaml.py +0 -0
  134. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_log_fallback_stderr.py +0 -0
  135. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_log_options_ignored_hosted.py +0 -0
  136. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_log_rotation.py +0 -0
  137. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_migrate.py +0 -0
  138. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_no_policy_no_key.py +0 -0
  139. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_package_rename_shim.py +0 -0
  140. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_policy_freshness.py +0 -0
  141. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_policy_settings.py +0 -0
  142. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_quarantine.py +0 -0
  143. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_t108_local_override_audit.py +0 -0
  144. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_t96_single_audit_log.py +0 -0
  145. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_t99_install_prefetch_bundle.py +0 -0
  146. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_tamper.py +0 -0
  147. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_tamper_behavior.py +0 -0
  148. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_tamper_hook.py +0 -0
  149. {controlzero-1.5.5 → controlzero-1.5.6}/tests/test_telemetry_consent.py +0 -0
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.5.6 -- 2026-05-15 (PRIVACY)
4
+
5
+ ### Fixed
6
+
7
+ - **Customer names and identifiable references removed from the
8
+ published wheel.** Earlier 1.5.x releases (including 1.5.5)
9
+ carried customer names and individual contributor names in inline
10
+ comments and docstrings as historical context for past incidents.
11
+ This release replaces every such reference with a generic
12
+ technical description (a customer / an enterprise customer / a
13
+ date-stamped incident label) while preserving the technical
14
+ meaning of the comment. No behavior change. Concretely affects
15
+ inline comments in `client.py`, `device.py`, `hosted_policy.py`,
16
+ `enrollment.py`, `cli/main.py`, `cli/_secrets.py`, `cli/debug_bundle.py`,
17
+ `cli/hosts/claude_code.py`, `cli/hosts/gemini_cli.py`,
18
+ `_internal/bundle.py`, `_internal/enforcer.py`. 1.5.5 is yanked
19
+ on PyPI.
20
+ - **Realistic-looking placeholder key in source replaced.** The
21
+ 64-hex-char `cz_live_*` string used as a docstring example and
22
+ test fixture has been swapped for an obviously-synthetic
23
+ `cz_live_aaaa...` placeholder. The original value was a test
24
+ fixture, not a real customer key, but the format was close
25
+ enough to a real key that an external reader could not tell.
26
+
27
+ ## v1.5.5 -- 2026-05-15 (YANKED)
28
+
29
+ YANKED on 2026-05-15 due to customer-name leakage in inline comments.
30
+ Use 1.5.6 instead.
31
+
3
32
  ## Unreleased -- Tier 0a security hotfix (2026-05-15)
4
33
 
5
34
  ### Security (P0 hotfix for #174)
@@ -58,7 +87,7 @@ T96 + T101 layout changes in real customer environments.
58
87
  not contents), and resolved API URL. With `--from-hook` it parses
59
88
  a stdin payload so the output reflects what the hook subprocess
60
89
  actually sees. Built for fast Windows-hook triage after the
61
- 2026-05-12 CloudShift incident -- a customer can now run a single
90
+ 2026-05-12 incident -- a customer can now run a single
62
91
  command and send us the JSON instead of guessing which env vars
63
92
  their hook sees.
64
93
 
@@ -110,7 +139,7 @@ T96 + T101 layout changes in real customer environments.
110
139
  Anthropic's PreToolUse hook schema only accepts `"approve" | "block"`
111
140
  -- the `"allow"` string crashed Claude Code's validator with
112
141
  `Hook JSON output validation failed - (root): Invalid input` and
113
- Claude Code dropped the hook decision (defaults to proceed). CloudShift
142
+ Claude Code dropped the hook decision (defaults to proceed). A customer
114
143
  saw a `Write` correctly blocked, then a `PowerShell` command bypass
115
144
  to the same intent. After 1.5.3 the Claude Code adapter renders
116
145
  allow as `"approve"` and deny as `"block"`; Gemini CLI / Codex CLI
@@ -159,7 +188,7 @@ T96 + T101 layout changes in real customer environments.
159
188
 
160
189
  ### Customer impact
161
190
 
162
- CloudShift / kh.lee reported on 2026-05-12 that Claude Code on
191
+ an enterprise customer admin reported on 2026-05-12 that Claude Code on
163
192
  Windows was not sending any audit logs to the dashboard, while the
164
193
  direct Python SDK script was sending logs without a populated
165
194
  SOURCE column. Both symptoms collapse to the two fixes above.
@@ -229,7 +258,7 @@ GH #424 (umbrella), PRs #425 (precedence), #428 (cache GC), #427
229
258
  (T87, GH #392). The bundle on disk is encrypted+signed; `cat` returns
230
259
  nothing useful, so until now diagnosing a deny-deny incident meant
231
260
  shipping the bundle back to engineering or attaching a debugger
232
- (Bryan's deny-deny took ~3 hours to root-cause for exactly this
261
+ (the deny-deny took ~3 hours to root-cause for exactly this
233
262
  reason). The new subcommand reads the matching `bootstrap-<prefix>.json`
234
263
  for keys, decrypts and verifies the cached bundle blob via the same
235
264
  parser the SDK uses, and prints a human-readable summary: bundle id,
@@ -284,7 +313,7 @@ GH #424 (umbrella), PRs #425 (precedence), #428 (cache GC), #427
284
313
  work, modern rules continue to work, and only previously-broken
285
314
  legacy rules start matching again.
286
315
 
287
- - **Synthetic policy_id sentinels** (T79, Bryan deny-deny postmortem).
316
+ - **Synthetic policy_id sentinels** (T79, the deny-deny postmortem).
288
317
  `PolicyDecision.policy_id` is now stamped with one of six canonical
289
318
  `synthetic:*` values whenever the deny was emitted by a fail-closed
290
319
  code path (no rule matched, empty bundle, missing bundle, T83-class
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: controlzero
3
- Version: 1.5.5
3
+ Version: 1.5.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
@@ -28,7 +28,7 @@ from controlzero.errors import (
28
28
  )
29
29
  from controlzero.policy_loader import load_policy
30
30
 
31
- __version__ = "1.5.5"
31
+ __version__ = "1.5.6"
32
32
 
33
33
  __all__ = [
34
34
  "Client",
@@ -437,7 +437,7 @@ def translate_to_local_policy(payload: dict) -> dict:
437
437
  # reason_code so dashboards still bucket it as "nothing
438
438
  # attached").
439
439
  #
440
- # Copy-choice note (2026-04-19, Bryan's P0): the previous
440
+ # Copy-choice note (2026-04-19, the 2026-04-19 P0): the previous
441
441
  # message -- "No active policies. Define one in the Control Zero
442
442
  # dashboard." -- presumed the user had not defined any policies.
443
443
  # That presumption was wrong in the common case: the user had
@@ -33,7 +33,7 @@ from controlzero._internal.types import PolicyRule
33
33
  # so integrations + dashboards can branch on decision provenance
34
34
  # without regex-matching the human-readable `reason` string.
35
35
  #
36
- # Added 2026-04-19 after Bryan's P0: the SDK fail-closed with "No
36
+ # Added 2026-04-19 after the 2026-04-19 P0: the SDK fail-closed with "No
37
37
  # active policies. Define one in the Control Zero dashboard." when
38
38
  # the user had in fact defined three; the three surfaces disagreed
39
39
  # because policy_attachments was empty. reason_code lets the hosted
@@ -78,7 +78,7 @@ VALID_REASON_CODES = frozenset({
78
78
  REASON_CODE_LOCAL_OVERRIDE_ACTIVE,
79
79
  })
80
80
 
81
- # Synthetic policy_id sentinels (T79 / Bryan deny-deny postmortem,
81
+ # Synthetic policy_id sentinels (T79 / the deny-deny postmortem,
82
82
  # 2026-05-11). When a deny is emitted by anything OTHER than a
83
83
  # user-authored rule, the SDK stamps the audit row's `policy_id` with
84
84
  # one of these `synthetic:*` values so the audit dashboard can render
@@ -317,7 +317,7 @@ class PolicyEvaluator:
317
317
  # resources:["*"] for unscoped rules, so prior to this
318
318
  # fix every cz.guard() call without an explicit resource
319
319
  # was skipping every rule and falling through to the
320
- # default deny (Bryan deny-deny incident, 2026-05-10).
320
+ # default deny (the deny-deny incident, 2026-05-10).
321
321
  # Logic now: if any pattern in rule.resources is the
322
322
  # universal "*", treat the gate as satisfied; otherwise
323
323
  # require a caller-supplied resource and glob-match it.
@@ -6,7 +6,7 @@ Single source of truth for:
6
6
  `controlzero doctor` to scan agent settings files + the cross-SDK
7
7
  contract test that fails the CI on any leak surface).
8
8
  2. Redacting a full key to a last-4 form for display in CLI output
9
- (`cz_live_***f7d7b22`). Matches the gh CLI / Stripe CLI convention.
9
+ (`cz_live_***aaaaa`). Matches the gh CLI / Stripe CLI convention.
10
10
 
11
11
  These helpers MUST stay pure (no I/O, no logging) so the contract test
12
12
  that depends on `find_key_leaks` can run in a sandbox without touching
@@ -22,7 +22,7 @@ from typing import Iterable, List, NamedTuple
22
22
  # characters in production. We also accept shorter trailing alphanumerics
23
23
  # so legacy short-form test keys (cz_test_localdev_...) are caught.
24
24
  # The pattern is deliberately greedy on the suffix so we catch:
25
- # cz_live_d003253c33902c264d5a20146a3efc15475de1d1fe9e1efb0ccace0d7f7d7b22
25
+ # cz_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
26
26
  # cz_test_localdev_000000000000000000000000
27
27
  # cz_test_xxxxxxxx (rarer, but valid)
28
28
  # but stops at a non-alphanumeric so we don't gobble surrounding markup.
@@ -72,8 +72,8 @@ def find_key_leaks(text: str) -> List[KeyMatch]:
72
72
 
73
73
 
74
74
  def redact_key(key: str) -> str:
75
- """Convert `cz_live_d003253c33902c264d5a20146a3efc15475de1d1fe9e1efb0ccace0d7f7d7b22`
76
- to `cz_live_***d7b22` (last-5 of the secret payload).
75
+ """Convert `cz_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`
76
+ to `cz_live_***aaaaa` (last-5 of the secret payload).
77
77
 
78
78
  The prefix (`cz_live_` / `cz_test_`) is preserved so the mode is
79
79
  still readable: ops can tell at a glance which key family this
@@ -81,7 +81,7 @@ def redact_key(key: str) -> str:
81
81
  distinguishable in support tickets without exposing enough to
82
82
  reconstruct the key (5 hex chars = 20 bits = 1M combinations,
83
83
  not enough to brute-force back to the full key but enough for a
84
- human to disambiguate "is this Bryan's key or John's key").
84
+ human to disambiguate "is this customer A's key or customer B's key").
85
85
 
86
86
  Returns the input verbatim if it doesn't match the expected shape,
87
87
  so caller-side `print(redact_key(maybe_key))` is always safe.
@@ -1,6 +1,6 @@
1
1
  """``controlzero debug bundle`` -- inspect SDK-loaded rules + simulate guard.
2
2
 
3
- Bryan's deny-deny incident (T87, GH #392) burned ~3 hours of root-cause
3
+ the deny-deny incident (T87, GH #392) burned ~3 hours of root-cause
4
4
  work because the bundle on disk is encrypted+signed. ``cat`` returns
5
5
  nothing legible; operators had to attach a debugger or ship the bundle
6
6
  back to engineering. This module gives them a self-serve alternative.
@@ -26,7 +26,7 @@ Critical: ``"decision": "allow"`` is **NOT** valid -- Claude Code
26
26
  rejects the hook output with
27
27
  ``Hook JSON output validation failed - (root): Invalid input`` and
28
28
  falls back to its default (proceed). That is the 2026-05-12
29
- CloudShift PowerShell-bypass root cause.
29
+ the Windows PowerShell-bypass root cause.
30
30
  """
31
31
 
32
32
  from __future__ import annotations
@@ -25,7 +25,7 @@ class GeminiCLIAdapter(HostAdapter):
25
25
 
26
26
  # Only true CLI runtime signals. ``GEMINI_API_KEY`` is set by
27
27
  # anyone using Google's Python SDK directly and must NOT match
28
- # (Bryan deny-deny incident, 2026-05-10 -- T78 / T92).
28
+ # (the deny-deny incident, 2026-05-10 -- T78 / T92).
29
29
  _ENV_HINTS = ("GEMINI_CLI", "GEMINI_SANDBOX", "GEMINI_SYSTEM_MD")
30
30
 
31
31
  def claim(self, payload: dict, env: Mapping[str, str]) -> bool:
@@ -391,7 +391,7 @@ def env_dump_cmd(show_secrets: bool, from_hook: bool):
391
391
  controlzero env-dump --from-hook < /tmp/claude-payload.json
392
392
  controlzero env-dump --show-secrets # un-redacts; DANGEROUS
393
393
 
394
- Filed as #438 follow-up to the 2026-05-12 CloudShift Bug E episode
394
+ Filed as #438 follow-up to the 2026-05-12 hook-decision regression
395
395
  where source-detection on Windows was unknown without instrumenting
396
396
  the customer's hook environment.
397
397
  """
@@ -674,7 +674,7 @@ def hook_check(policy: Optional[str]):
674
674
  # Before #228 phase 3 the hook-check CLI passed method="*" for every
675
675
  # call because it could not reach into tool_input to pull a method
676
676
  # out. That meant rules like `deny database:DROP` never matched via
677
- # the hook path. Now every Bryan-style PreToolUse payload collapses
677
+ # the hook path. Now every the 2026-04-19-style PreToolUse payload collapses
678
678
  # to the same canonical (tool, method) the Gateway and SDK
679
679
  # guard() call sites already produce.
680
680
  _tool_args_dict = tool_args if isinstance(tool_args, dict) else {}
@@ -783,12 +783,12 @@ def hook_check(policy: Optional[str]):
783
783
  # 2. api_key set => hosted mode. Client(api_key=...) loads the cached
784
784
  # bundle via hosted_policy and conditional-refreshes it on every
785
785
  # construction (content-hash via If-None-Match, not mtime). This
786
- # is the path John Na should have hit on every hook-check.
786
+ # is the path the customer should have hit on every hook-check.
787
787
  # 3. CONTROLZERO_LOCAL_OVERRIDE=1 with api_key => fall back to file.
788
788
  # 4. No api_key, no enrollment => local file path.
789
789
  #
790
790
  # Before T103 the resolver fell back to ~/.controlzero/policy.yaml
791
- # whenever it existed, even with a valid api_key. John Na's manually
791
+ # whenever it existed, even with a valid api_key. a customer's manually
792
792
  # edited policy.yaml shadowed the dashboard policy with no warning.
793
793
  _local_override = os.environ.get(
794
794
  "CONTROLZERO_LOCAL_OVERRIDE", ""
@@ -905,7 +905,7 @@ def hook_check(policy: Optional[str]):
905
905
  except Exception as e: # noqa: BLE001
906
906
  # Hosted-pull failure (HostedAuthError, HostedBootstrapError, etc).
907
907
  # Fail-closed with a clear reason; do NOT silently allow because
908
- # that's what John Na is suffering from today.
908
+ # that's what the customer was hitting at the time.
909
909
  click.echo(f"controlzero: hosted policy load failed ({e})", err=True)
910
910
  _emit_decision(
911
911
  effect="deny",
@@ -1367,7 +1367,7 @@ def _maybe_refresh_policy(
1367
1367
  reads the signed bundle cache and conditional-refreshes via
1368
1368
  ``hosted_policy.load_hosted_policy`` (If-None-Match etag). The CLI
1369
1369
  no longer clobbers ``~/.controlzero/policy.yaml`` from a hosted pull,
1370
- because that destroyed user-edited content (John Na 2026-05-12).
1370
+ because that destroyed user-edited content (customer report 2026-05-12).
1371
1371
 
1372
1372
  This function now serves only the enrollment-based path used by
1373
1373
  machines that ran ``controlzero login`` and persisted
@@ -1799,7 +1799,7 @@ def install_claude_code(force: bool, merge: bool, settings: Optional[str], api_k
1799
1799
  # `CONTROLZERO_API_KEY=cz_... controlzero hook-check` as a bash-style
1800
1800
  # env-prefix was a Windows-portability bug (PowerShell / cmd.exe cannot
1801
1801
  # parse the syntax) AND placed the cleartext key into settings.json on
1802
- # disk. Both go away here. See 2026-05-12 CloudShift incident.
1802
+ # disk. Both go away here. See 2026-05-12 incident.
1803
1803
  hook_command = "controlzero hook-check"
1804
1804
  new_block = {
1805
1805
  "matcher": "*",
@@ -21,7 +21,7 @@ Resolution order for finding a policy (T103, 2026-05-12):
21
21
  5. no api_key => ./controlzero.yaml in cwd
22
22
  6. nothing => no-op pass-through with one-time stderr warning
23
23
 
24
- Before T103, local always won when both were present. John Na (CloudShift)
24
+ Before T103, local always won when both were present. an enterprise customer
25
25
  hit this in prod: a stale ~/.controlzero/policy.yaml shadowed the dashboard
26
26
  policy and silently disabled enforcement. The reversal makes the api_key
27
27
  the explicit "I want hosted" signal and forces an env-var opt-out for the
@@ -59,7 +59,7 @@ def detect_client_name() -> str:
59
59
 
60
60
  Detection uses signals each tool ACTUALLY ships, not generic env-var
61
61
  prefixes that users might export for unrelated reasons. T92 rewrite,
62
- after the Bryan source-mislabel incident (2026-05-10) where a Python
62
+ after the the source-mislabel incident (2026-05-10) where a Python
63
63
  SDK user with ``GEMINI_API_KEY`` exported for direct Google API access
64
64
  was mislabeled as "Gemini CLI" because the prior implementation
65
65
  matched any ``GEMINI_`` prefix.
@@ -127,7 +127,7 @@ def detect_client_name() -> str:
127
127
 
128
128
  # 6. Gemini CLI. Match ONLY env vars the CLI runtime sets, NOT the
129
129
  # broad GEMINI_ prefix. GEMINI_API_KEY is exported by anyone using
130
- # Google's Python SDK directly (Bryan's case). GEMINI_CLI mirrors
130
+ # Google's Python SDK directly (the customer's case). GEMINI_CLI mirrors
131
131
  # the Node SDK's convention; GEMINI_SANDBOX / GEMINI_SYSTEM_MD are
132
132
  # set by the gemini-cli runtime itself.
133
133
  if (os.environ.get("GEMINI_CLI") or
@@ -1,6 +1,6 @@
1
1
  """Enrollment client for the ControlZero managed mode.
2
2
 
3
- Phase 6 of the Samsung Enterprise Governance plan: SDK-side
3
+ Phase 6 of the enterprise governance plan: SDK-side
4
4
  counterpart to the Phase 5a backend endpoints.
5
5
 
6
6
  Flow:
@@ -135,7 +135,7 @@ def save_cached_bootstrap(api_key: str, keys: BootstrapKeys) -> None:
135
135
  def gc_stale_cache(active_api_key: str) -> int:
136
136
  """Remove cache files belonging to keys other than the active one.
137
137
 
138
- T104 (2026-05-12, John Na): on key rotation the old key's cache files
138
+ T104 (2026-05-12, customer report): on key rotation the old key's cache files
139
139
  (``bootstrap-<oldscope>.json``, ``bundle-<oldscope>.bin``,
140
140
  ``bundle-<oldscope>.meta``) accumulated forever next to the new key's
141
141
  files. John's machine carried a 25-day-old ``bundle-cz_live_566b.bin``
@@ -498,7 +498,7 @@ def _compute_etag(blob: bytes) -> str:
498
498
  stores and serves the full hex string in its ETag header, so the
499
499
  SDK must hash to the same width for `If-None-Match` to ever match.
500
500
  Truncating to 32 chars (the prior shape) silently broke the 304
501
- short-circuit, surfacing as Bryan's investigation finding 1 in
502
- docs/investigations/bryan-db-read-only-fail-closed-2026-05-08.md.
501
+ short-circuit, surfacing as the investigation finding 1 in
502
+ docs/investigations/the 2026-05-08 deny-deny investigation.
503
503
  """
504
504
  return hashlib.sha256(blob).hexdigest()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "controlzero"
7
- version = "1.5.5"
7
+ version = "1.5.6"
8
8
  description = "AI agent governance: policies, audit, and observability for tool calls. Works locally with no signup."
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -42,7 +42,7 @@ def test_mask_empty_string_returns_triple_star():
42
42
 
43
43
 
44
44
  def test_mask_never_leaks_secret_bytes_brute_force():
45
- # The secret portion of a real CloudShift production key (anonymised:
45
+ # The secret portion of a real production key (anonymised:
46
46
  # do not use this value as-is, the customer rotated it after the
47
47
  # 1.5.0 -> 1.5.1 incident on 2026-05-12).
48
48
  secret_tail = "7ebef6b600015e3eaeda9149bf6d9c29a3a2a7a3075209112afde20888280de0"
@@ -1,6 +1,6 @@
1
1
  """Tests for bundle _translate_rule against the backend wire format.
2
2
 
3
- Covers Bug #2 from the 2026-04-19 Bryan investigation: before this
3
+ Covers Bug #2 from the 2026-04-19 investigation: before this
4
4
  change, :func:`controlzero._internal.bundle._translate_rule` ignored
5
5
  the plural ``actions`` list emitted by the Go backend (see
6
6
  ``bundle_handler.go`` ``PolicyRule.Actions``). Any rule carrying
@@ -5,7 +5,7 @@ Codex PreToolUse payloads through the ``controlzero hook-check`` CLI
5
5
  and assert that the canonical ``(tool, method)`` action the extractor
6
6
  produced is the action the policy evaluator matched against.
7
7
 
8
- The flagship case is Bryan's scenario: a policy that denies
8
+ The flagship case is the customer scenario: a policy that denies
9
9
  ``database:DROP`` should not fire on
10
10
  ``{tool: "database", args: {sql: "SELECT * FROM users"}}`` because the
11
11
  extractor resolves the action to ``database:SELECT``. Before this
@@ -77,11 +77,11 @@ def _invoke(policy_path: Path, payload: dict) -> tuple[int, dict, str]:
77
77
 
78
78
 
79
79
  # ---------------------------------------------------------------------------
80
- # Bryan's scenario and its evil twin (#228 phase 3 flagship).
80
+ # the customer scenario and its evil twin (#228 phase 3 flagship).
81
81
  # ---------------------------------------------------------------------------
82
82
 
83
83
 
84
- def test_bryan_scenario_database_select_resolves_to_database_colon_select(
84
+ def test_customer_scenario_database_select_resolves_to_database_colon_select(
85
85
  tmp_path: Path,
86
86
  ):
87
87
  """SELECT payload emits action=database:SELECT (not database:*)."""
@@ -10,7 +10,7 @@ machines that ran ``controlzero login`` and persisted enrollment.json).
10
10
  The old hosted-pull-and-rewrite-file tests were dropped because
11
11
  ``_do_hosted_pull_and_write`` was deleted: it overwrote user-edited
12
12
  ``policy.yaml`` content with hosted bundle rules, which is what caused
13
- John Na's stale-file shadow (2026-05-12).
13
+ the stale-file shadow (2026-05-12).
14
14
  """
15
15
 
16
16
  from __future__ import annotations
@@ -196,7 +196,7 @@ class TestDetectClientName:
196
196
 
197
197
  def test_gemini_api_key_does_NOT_trigger_gemini_cli(self):
198
198
  # T92 regression: pre-T92 a GEMINI_API_KEY export silently
199
- # mislabeled SDK-direct users (Bryan deny-deny incident,
199
+ # mislabeled SDK-direct users (the deny-deny incident,
200
200
  # 2026-05-10) as "gemini-cli". GEMINI_API_KEY is a Google API
201
201
  # credential, not a Gemini CLI runtime signal. Must default
202
202
  # back to the python-sdk fallback (1.5.2 canonical alias).
@@ -1,6 +1,6 @@
1
1
  """Regression tests for `controlzero env-dump` (#438).
2
2
 
3
- Filed as a follow-up to the 2026-05-12 CloudShift Bug E episode where
3
+ Filed as a follow-up to the 2026-05-12 hook-decision regression where
4
4
  source-detection on Windows took multiple rounds because we had no way
5
5
  to ask the customer "what env vars does YOUR hook subprocess see?"
6
6
 
@@ -123,8 +123,8 @@ def test_universal_wildcard(tmp_log):
123
123
  # ---------------------------------------------------------------------
124
124
  # Pre-T83 the dashboard's universal-resource shape (every rule emitted
125
125
  # resources:["*"]) skipped every rule when callers passed no
126
- # context.resource, falling through to default deny. Bryan deny-deny
127
- # incident, 2026-05-10. The exact bundle Bryan ran against is the
126
+ # context.resource, falling through to default deny. the deny-deny
127
+ # incident, 2026-05-10. The exact bundle a customer ran against is the
128
128
  # fixture below.
129
129
 
130
130
  def test_resources_wildcard_matches_without_context_resource(tmp_log):
@@ -147,7 +147,7 @@ def test_resources_wildcard_matches_without_context_resource(tmp_log):
147
147
 
148
148
 
149
149
  def test_bryan_db_read_only_full_bundle_shape(tmp_log):
150
- """End-to-end against the EXACT rule shape from Bryan's published bundle.
150
+ """End-to-end against the EXACT rule shape from the published bundle.
151
151
 
152
152
  This is the smoking-gun reproduction:
153
153
  - rule-0 allow database:read (should fire on SELECT)
@@ -12,7 +12,7 @@ Two pillars under test:
12
12
  fixed here: Claude Code's allow path. Pre-1.5.3 the SDK emitted
13
13
  ``decision: "allow"`` which crashed Claude Code's validator with
14
14
  ``Hook JSON output validation failed - (root): Invalid input``;
15
- the customer (CloudShift / kh.lee, 2026-05-12) saw a Write block
15
+ the customer (an enterprise customer admin, 2026-05-12) saw a Write block
16
16
  succeed and then a PowerShell command bypass on the same intent.
17
17
  After 1.5.3 the Claude Code adapter renders allow as ``approve``
18
18
  (the spec-correct value) and deny as ``block``.
@@ -107,7 +107,7 @@ class TestGeminiCLIClaim:
107
107
  def test_does_NOT_claim_via_gemini_api_key(self):
108
108
  # T78 / T92 regression: GEMINI_API_KEY is a Google API
109
109
  # credential, NOT a CLI runtime signal. Must NOT trigger
110
- # the Gemini adapter (Bryan 2026-05-10 incident).
110
+ # the Gemini adapter (the 2026-05-10 incident).
111
111
  assert not GeminiCLIAdapter().claim(
112
112
  {}, env={"GEMINI_API_KEY": "fake"}
113
113
  )
@@ -8,7 +8,7 @@ Two invariants the installer MUST satisfy:
8
8
  key. Embedding ``CONTROLZERO_API_KEY=cz_live_... controlzero
9
9
  hook-check`` was a portability bug: bash parses the env-prefix,
10
10
  PowerShell / cmd.exe do not, so the hook subprocess failed to
11
- spawn on Windows and CloudShift's Claude Code instance never
11
+ spawn on Windows and a customer's Claude Code instance never
12
12
  delivered audit logs (2026-05-12 incident).
13
13
  2. The api key is persisted to ``~/.controlzero/config.yaml`` so the
14
14
  hook subprocess can pick it up at runtime via the
@@ -1,7 +1,7 @@
1
1
  """End-to-end test suite for the controlzero hook installers and hook-check evaluator.
2
2
 
3
3
  This file is intentionally exhaustive. The audience is procurement / security
4
- review teams (Samsung et al.) who need a clear story of what is covered,
4
+ review teams (enterprise customers) who need a clear story of what is covered,
5
5
  what is intentionally skipped, and what is known broken.
6
6
 
7
7
  Coverage matrix
@@ -1,4 +1,4 @@
1
- """Tests for PolicyDecision.reason_code (Bryan P0, 2026-04-19).
1
+ """Tests for PolicyDecision.reason_code (2026-04-19 P0).
2
2
 
3
3
  The hosted dashboard + audit ingestion + CLI automation all need a
4
4
  machine-readable label for "why did this call get denied?" that is
@@ -89,7 +89,7 @@ def test_bundle_empty_emits_reason_code_NO_ACTIVE_POLICIES():
89
89
  """An empty policy list translated to the local shape MUST emit a
90
90
  single synthetic deny whose ``reason_code`` is NO_ACTIVE_POLICIES.
91
91
 
92
- This is the code that ran on Bryan's project: the bundle was
92
+ This is the code that ran on the customer's project: the bundle was
93
93
  signed + verified + decrypted but carried zero policies because
94
94
  policy_attachments was empty. Prior to the fix, the SDK surfaced a
95
95
  generic English string; now it surfaces a stable machine label so
@@ -108,7 +108,7 @@ def test_bundle_empty_new_copy_does_not_presume_user_undefined_policies():
108
108
  """The pre-2026-04-19 copy told users to "Define one in the Control
109
109
  Zero dashboard." That message is the WRONG recovery action when the
110
110
  user has already defined policies but the attachment state is empty
111
- (Bryan's case). This test fails if anyone restores that wording.
111
+ (the customer's case). This test fails if anyone restores that wording.
112
112
  """
113
113
  local = translate_to_local_policy({"policies": []})
114
114
  reason = local["rules"][0]["reason"].lower()
@@ -1,6 +1,6 @@
1
1
  """Tests for hosted-policy periodic refresh.
2
2
 
3
- Covers Bug #1 from the 2026-04-19 Bryan investigation: before this
3
+ Covers Bug #1 from the 2026-04-19 investigation: before this
4
4
  change, :class:`Client._evaluator` was loaded exactly once in
5
5
  ``__init__`` and held for the life of the process. Updates made on the
6
6
  dashboard never reached a long-running SDK client until it restarted.
@@ -26,10 +26,10 @@ from controlzero.cli._secrets import (
26
26
  class TestFindKeyLeaks:
27
27
  def test_matches_full_64char_live_key(self) -> None:
28
28
  # The exact shape from the 2026-05-14 screenshot incident.
29
- text = "CONTROLZERO_API_KEY=cz_live_d003253c33902c264d5a20146a3efc15475de1d1fe9e1efb0ccace0d7f7d7b22 controlzero hook-check"
29
+ text = "CONTROLZERO_API_KEY=cz_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa controlzero hook-check"
30
30
  matches = find_key_leaks(text)
31
31
  assert len(matches) == 1
32
- assert matches[0].key == "cz_live_d003253c33902c264d5a20146a3efc15475de1d1fe9e1efb0ccace0d7f7d7b22"
32
+ assert matches[0].key == "cz_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
33
33
  assert matches[0].line_number == 1
34
34
 
35
35
  def test_matches_test_key_localdev_shape(self) -> None:
@@ -71,9 +71,9 @@ class TestFindKeyLeaks:
71
71
 
72
72
  def test_does_not_match_redacted_form(self) -> None:
73
73
  # We must NOT re-flag our own redacted output as a leak.
74
- # `cz_live_***d7b22` should be safe to print without doctor
74
+ # `cz_live_***aaaaa` should be safe to print without doctor
75
75
  # warning about it.
76
- text = "Active key: cz_live_***d7b22"
76
+ text = "Active key: cz_live_***aaaaa"
77
77
  # The redaction contains *** which is not in [A-Za-z0-9_].
78
78
  # The pattern stops at the *. So the match would be too short
79
79
  # (cz_live_ alone is < 4 chars suffix). Asserting empty:
@@ -107,8 +107,8 @@ class TestFindKeyLeaks:
107
107
 
108
108
  class TestRedactKey:
109
109
  def test_live_key_redacted_to_last_4(self) -> None:
110
- key = "cz_live_d003253c33902c264d5a20146a3efc15475de1d1fe9e1efb0ccace0d7f7d7b22"
111
- assert redact_key(key) == "cz_live_***d7b22"
110
+ key = "cz_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
111
+ assert redact_key(key) == "cz_live_***aaaaa"
112
112
 
113
113
  def test_test_key_redacted_to_last_5(self) -> None:
114
114
  key = "cz_test_localdev_000000000000000000000000"
@@ -221,7 +221,7 @@ def test_redacted_form_is_idempotent() -> None:
221
221
  """Running `redact_text(redact_text(x)) == redact_text(x)`. If
222
222
  someone changes the redaction to embed key chars, this catches
223
223
  it."""
224
- text = "key=cz_live_d003253c33902c264d5a20146a3efc15475de1d1fe9e1efb0ccace0d7f7d7b22"
224
+ text = "key=cz_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
225
225
  once = redact_text(text)
226
226
  twice = redact_text(once)
227
227
  assert once == twice
@@ -1,6 +1,6 @@
1
1
  """SQL semantic-class extractor tests (issue #345).
2
2
 
3
- Bryan reported a `db-read-only` policy that denied legitimate
3
+ a customer reported a `db-read-only` policy that denied legitimate
4
4
  read-only SQL because the per-keyword extractor surfaced `WITH`,
5
5
  `EXPLAIN`, and `SHOW` as the canonical method while the blueprint
6
6
  wrote rules against `database:query`/`database:execute`/`database:delete`
@@ -72,7 +72,7 @@ def test_all_synthetic_ids_carry_canonical_prefix():
72
72
  """Frontend renders chips by `policy_id.startswith("synthetic:")`.
73
73
  A regression that emits a sentinel without the prefix would fall
74
74
  through to the plain text rendering and re-create the original
75
- "blank Policy column" bug from Bryan's incident.
75
+ "blank Policy column" bug from the incident.
76
76
  """
77
77
  for value in VALID_SYNTHETIC_POLICY_IDS:
78
78
  assert value.startswith(SYNTHETIC_POLICY_ID_PREFIX), value
@@ -1,6 +1,6 @@
1
1
  """T103 regression tests: hosted-vs-local precedence.
2
2
 
3
- John Na (CloudShift Korea) 2026-05-12: a stale ~/.controlzero/policy.yaml
3
+ an enterprise customer (Korea) 2026-05-12: a stale ~/.controlzero/policy.yaml
4
4
  shadowed the dashboard policy with no warning. Local always won over
5
5
  hosted when both were present. T103 reverses this so api_key implies
6
6
  hosted unless CONTROLZERO_LOCAL_OVERRIDE=1.
@@ -104,7 +104,7 @@ def test_no_api_key_accepts_json_policy(monkeypatch, tmp_path):
104
104
  def test_john_na_regression_cwd_yaml_bypassed_when_api_key_set(
105
105
  monkeypatch, tmp_path
106
106
  ):
107
- """John Na (CloudShift Korea) 2026-05-12 regression.
107
+ """an enterprise customer (Korea) 2026-05-12 regression.
108
108
 
109
109
  Pre-T103: a stale ~/.controlzero/policy.yaml shadowed the hosted
110
110
  bundle for 25 days. Post-T103: api_key + cwd policy.yaml + no
@@ -1,6 +1,6 @@
1
1
  """T104 regression tests: stale cache GC on key rotation.
2
2
 
3
- John Na (CloudShift Korea) 2026-05-12: rotated his api_key from
3
+ an enterprise customer (Korea) 2026-05-12: rotated his api_key from
4
4
  ``cz_live_566b...`` to ``cz_live_1af8...``. The new key bootstrapped
5
5
  fresh but ``cache/bundle-cz_live_566b.bin`` lingered for 25 days next to
6
6
  the new key's files. T104 GC's any cache files whose key-scope does NOT
File without changes
File without changes
File without changes
File without changes