controlzero 1.5.6__tar.gz → 1.5.7__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.6 → controlzero-1.5.7}/CHANGELOG.md +23 -0
  2. {controlzero-1.5.6 → controlzero-1.5.7}/PKG-INFO +1 -1
  3. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/__init__.py +1 -1
  4. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/hosts/__init__.py +3 -3
  5. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/hosts/base.py +4 -5
  6. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/hosts/unknown.py +2 -3
  7. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/enrollment.py +3 -4
  8. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/hosted_policy.py +2 -2
  9. {controlzero-1.5.6 → controlzero-1.5.7}/pyproject.toml +1 -1
  10. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_api_key_mask.py +5 -5
  11. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_env_dump_438.py +2 -2
  12. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_install_hook_command.py +2 -2
  13. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_t103_precedence.py +4 -4
  14. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_t104_cache_gc.py +23 -23
  15. {controlzero-1.5.6 → controlzero-1.5.7}/.gitignore +0 -0
  16. {controlzero-1.5.6 → controlzero-1.5.7}/Dockerfile.test +0 -0
  17. {controlzero-1.5.6 → controlzero-1.5.7}/LICENSE +0 -0
  18. {controlzero-1.5.6 → controlzero-1.5.7}/README.md +0 -0
  19. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/_internal/__init__.py +0 -0
  20. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/_internal/action_aliases.py +0 -0
  21. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/_internal/bundle.py +0 -0
  22. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/_internal/dlp_scanner.py +0 -0
  23. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/_internal/enforcer.py +0 -0
  24. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/_internal/hook_extractors.py +0 -0
  25. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/_internal/tool_extractors.json +0 -0
  26. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/_internal/types.py +0 -0
  27. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/audit_local.py +0 -0
  28. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/audit_remote.py +0 -0
  29. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/__init__.py +0 -0
  30. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/_secrets.py +0 -0
  31. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/console.py +0 -0
  32. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/debug_bundle.py +0 -0
  33. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/doctor.py +0 -0
  34. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/hosts/claude_code.py +0 -0
  35. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/hosts/codex_cli.py +0 -0
  36. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/hosts/gemini_cli.py +0 -0
  37. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/main.py +0 -0
  38. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/migrate.py +0 -0
  39. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/telemetry_consent.py +0 -0
  40. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/autogen.yaml +0 -0
  41. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/claude-code.yaml +0 -0
  42. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/codex-cli.yaml +0 -0
  43. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/cost-cap.yaml +0 -0
  44. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/crewai.yaml +0 -0
  45. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/cursor.yaml +0 -0
  46. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/gemini-cli.yaml +0 -0
  47. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/generic.yaml +0 -0
  48. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/langchain.yaml +0 -0
  49. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/mcp.yaml +0 -0
  50. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/cli/templates/rag.yaml +0 -0
  51. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/client.py +0 -0
  52. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/device.py +0 -0
  53. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/error_codes.py +0 -0
  54. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/errors.py +0 -0
  55. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/__init__.py +0 -0
  56. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/anthropic.py +0 -0
  57. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/autogen.py +0 -0
  58. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/braintrust.py +0 -0
  59. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/crewai/__init__.py +0 -0
  60. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/crewai/agent.py +0 -0
  61. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/crewai/crew.py +0 -0
  62. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/crewai/task.py +0 -0
  63. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/crewai/tool.py +0 -0
  64. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/google.py +0 -0
  65. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/google_adk/__init__.py +0 -0
  66. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/google_adk/agent.py +0 -0
  67. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/google_adk/tool.py +0 -0
  68. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/langchain/__init__.py +0 -0
  69. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/langchain/agent.py +0 -0
  70. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/langchain/callbacks.py +0 -0
  71. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/langchain/chain.py +0 -0
  72. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/langchain/graph.py +0 -0
  73. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/langchain/modern.py +0 -0
  74. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/langchain/tool.py +0 -0
  75. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/langfuse.py +0 -0
  76. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/litellm.py +0 -0
  77. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/openai.py +0 -0
  78. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/pydantic_ai.py +0 -0
  79. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/integrations/vercel_ai.py +0 -0
  80. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/layout_migration.py +0 -0
  81. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/policy_loader.py +0 -0
  82. {controlzero-1.5.6 → controlzero-1.5.7}/controlzero/tamper.py +0 -0
  83. {controlzero-1.5.6 → controlzero-1.5.7}/examples/hello_world.py +0 -0
  84. {controlzero-1.5.6 → controlzero-1.5.7}/tests/conftest.py +0 -0
  85. {controlzero-1.5.6 → controlzero-1.5.7}/tests/integrations/__init__.py +0 -0
  86. {controlzero-1.5.6 → controlzero-1.5.7}/tests/integrations/test_google.py +0 -0
  87. {controlzero-1.5.6 → controlzero-1.5.7}/tests/parity/action_aliases.json +0 -0
  88. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_action_aliases.py +0 -0
  89. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_action_canonicalization.py +0 -0
  90. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_agent_name_env.py +0 -0
  91. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_audit_remote.py +0 -0
  92. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_audit_remote_sdk_version.py +0 -0
  93. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_audit_sink_isolation.py +0 -0
  94. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_bundle_parser.py +0 -0
  95. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_bundle_translate.py +0 -0
  96. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_cli_carve_out.py +0 -0
  97. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_cli_debug_bundle.py +0 -0
  98. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_cli_extractor_integration.py +0 -0
  99. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_cli_hook.py +0 -0
  100. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_cli_hosted_refresh.py +0 -0
  101. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_cli_init.py +0 -0
  102. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_cli_init_templates.py +0 -0
  103. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_cli_tail.py +0 -0
  104. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_cli_test.py +0 -0
  105. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_cli_validate.py +0 -0
  106. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_coding_agent_hooks.py +0 -0
  107. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_conditions.py +0 -0
  108. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_console.py +0 -0
  109. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_default_action.py +0 -0
  110. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_device.py +0 -0
  111. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_dlp_scanner.py +0 -0
  112. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_doctor.py +0 -0
  113. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_enrollment.py +0 -0
  114. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_error_codes.py +0 -0
  115. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_errors_e_codes.py +0 -0
  116. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_fail_closed_eval.py +0 -0
  117. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_glob_matching.py +0 -0
  118. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_hook_extractors.py +0 -0
  119. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_hosted_policy_e2e.py +0 -0
  120. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_hosts_adapter.py +0 -0
  121. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_hybrid_mode_strict.py +0 -0
  122. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_hybrid_mode_warn.py +0 -0
  123. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_install_hooks.py +0 -0
  124. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_layout_migration_t101.py +0 -0
  125. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_layout_parity_t102.py +0 -0
  126. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_local_mode_dict.py +0 -0
  127. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_local_mode_file_json.py +0 -0
  128. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_local_mode_file_yaml.py +0 -0
  129. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_log_fallback_stderr.py +0 -0
  130. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_log_options_ignored_hosted.py +0 -0
  131. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_log_rotation.py +0 -0
  132. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_migrate.py +0 -0
  133. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_no_policy_no_key.py +0 -0
  134. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_package_rename_shim.py +0 -0
  135. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_policy_freshness.py +0 -0
  136. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_policy_settings.py +0 -0
  137. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_quarantine.py +0 -0
  138. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_reason_code.py +0 -0
  139. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_refresh.py +0 -0
  140. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_secrets.py +0 -0
  141. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_sql_semantic_class.py +0 -0
  142. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_synthetic_policy_id_t79.py +0 -0
  143. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_t108_local_override_audit.py +0 -0
  144. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_t96_single_audit_log.py +0 -0
  145. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_t99_install_prefetch_bundle.py +0 -0
  146. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_tamper.py +0 -0
  147. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_tamper_behavior.py +0 -0
  148. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_tamper_hook.py +0 -0
  149. {controlzero-1.5.6 → controlzero-1.5.7}/tests/test_telemetry_consent.py +0 -0
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.5.7 -- 2026-05-16 (PRIVACY)
4
+
5
+ ### Fixed
6
+
7
+ - **Customer-context comments in the published wheel.** Codex +
8
+ Gemini outside-voice review surfaced several customer-context
9
+ strings + private monorepo path references that v1.5.6 missed:
10
+ - `controlzero/hosted_policy.py` referenced a customer-specific
11
+ possessive when describing the T104 cache-GC scenario. Rewritten
12
+ in neutral phrasing that preserves the technical context.
13
+ - `controlzero/enrollment.py` documented the wire-format contract
14
+ via a private monorepo path. Replaced with a pointer to the
15
+ public docs site.
16
+ - `controlzero/cli/hosts/base.py` and
17
+ `controlzero/cli/hosts/unknown.py` referenced an internal
18
+ backend filename in inline comments. Replaced with a generic
19
+ description of the constraint.
20
+ - Tests `test_t104_cache_gc.py` and `test_t103_precedence.py`
21
+ carried a geographic identifier and a customer-derived test
22
+ name (NOT in the published wheel since tests are excluded, but
23
+ scrubbed for consistency).
24
+ - **No behavior change.** Comments + docstrings only.
25
+
3
26
  ## v1.5.6 -- 2026-05-15 (PRIVACY)
4
27
 
5
28
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: controlzero
3
- Version: 1.5.6
3
+ Version: 1.5.7
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.6"
31
+ __version__ = "1.5.7"
32
32
 
33
33
  __all__ = [
34
34
  "Client",
@@ -36,9 +36,9 @@ A ``HostAdapter`` owns three responsibilities for one host runtime:
36
36
  (Windows Claude Code is the canonical case).
37
37
  2. ``render(decision)`` -- translate the canonical ``CZDecision``
38
38
  into the JSON shape this host's hook validator accepts.
39
- 3. ``canonical_source`` -- the backend ``audit.NormalizeSource``
40
- alias (``claude_code`` / ``gemini_cli`` / ``codex_cli`` / etc.)
41
- so the audit row's SOURCE column renders correctly.
39
+ 3. ``canonical_source`` -- the backend-side source alias
40
+ (``claude_code`` / ``gemini_cli`` / ``codex_cli`` / etc.) so the
41
+ audit row's SOURCE column renders correctly.
42
42
 
43
43
  Adding a new host (Cursor, Windsurf, OpenClaw, Antigravity, the
44
44
  next agent that ships) is a single file in this package: subclass
@@ -84,7 +84,7 @@ class HostAdapter:
84
84
  A subclass needs three things:
85
85
 
86
86
  * ``name`` -- short identifier used in logs ("claude_code").
87
- * ``canonical_source`` -- backend ``audit.NormalizeSource`` alias
87
+ * ``canonical_source`` -- backend-side source alias
88
88
  ("claude_code" / "gemini_cli" / "codex_cli" / "unknown").
89
89
  This is what the audit row's SOURCE column resolves to so
90
90
  the dashboard renders the right label.
@@ -109,10 +109,9 @@ class HostAdapter:
109
109
  #: Short identifier used in logs / metrics.
110
110
  name: str = "base"
111
111
 
112
- #: Backend ``audit.NormalizeSource`` alias. Must be one of the
113
- #: canonical values in ``backend/internal/audit/source.go`` so
114
- #: the dashboard renders the right label. Default is
115
- #: ``"unknown"`` -- subclasses MUST override.
112
+ #: Backend audit source alias. Must be one of the canonical
113
+ #: values the dashboard accepts so the right label renders.
114
+ #: Default is ``"unknown"`` -- subclasses MUST override.
116
115
  canonical_source: str = "unknown"
117
116
 
118
117
  # -- detection --------------------------------------------------
@@ -26,9 +26,8 @@ from controlzero.cli.hosts.base import CZDecision, HostAdapter
26
26
 
27
27
  class UnknownHostAdapter(HostAdapter):
28
28
  name = "unknown"
29
- # Canonical source value mapped in
30
- # ``backend/internal/audit/source.go`` -- renders as ``--`` on
31
- # the dashboard. Better than mislabelling.
29
+ # Canonical source value the dashboard accepts -- renders as
30
+ # ``--`` on the dashboard. Better than mislabelling.
32
31
  canonical_source = "unknown"
33
32
 
34
33
  def claim(self, payload: dict, env: Mapping[str, str]) -> bool:
@@ -19,10 +19,9 @@ private key lives in ``~/.controlzero/machine.key`` with mode 0600.
19
19
  A follow-up will plug in Keychain/DPAPI/secret-service per platform;
20
20
  the file-on-disk fallback stays as the bottom of the chain.
21
21
 
22
- Wire-format: see ``apps/control-zero-platform/backend/internal/api/
23
- middleware/machine_auth.go`` for the canonical signed string and
24
- header definitions. This module is the source of truth on the
25
- client side.
22
+ Wire-format contract: see the public docs and OpenAPI spec for the
23
+ machine-auth endpoint at https://docs.controlzero.ai. This module is
24
+ the source of truth on the client side.
26
25
  """
27
26
 
28
27
  from __future__ import annotations
@@ -138,8 +138,8 @@ def gc_stale_cache(active_api_key: str) -> int:
138
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
- files. John's machine carried a 25-day-old ``bundle-cz_live_566b.bin``
142
- next to a fresh ``bootstrap-cz_live_1af8.json`` because the rotated
141
+ files. A customer reported a 25-day-old ``bundle-<oldscope>.bin`` left
142
+ next to a fresh ``bootstrap-<newscope>.json`` because the rotated
143
143
  key never invalidated the previous cache. We clean up on every fresh
144
144
  bootstrap so the cache directory always reflects exactly one active
145
145
  key per machine.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "controlzero"
7
- version = "1.5.6"
7
+ version = "1.5.7"
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"}
@@ -19,12 +19,12 @@ from controlzero.client import _mask_api_key
19
19
 
20
20
 
21
21
  def test_mask_live_key_emits_only_public_prefix():
22
- masked = _mask_api_key("cz_live_7ebef6b600015e3eaeda9149bf6d9c29a")
22
+ masked = _mask_api_key("cz_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
23
23
  assert masked == "cz_live_***"
24
24
 
25
25
 
26
26
  def test_mask_test_key_emits_only_public_prefix():
27
- masked = _mask_api_key("cz_test_abcdef0123456789abcdef0123456789")
27
+ masked = _mask_api_key("cz_test_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
28
28
  assert masked == "cz_test_***"
29
29
 
30
30
 
@@ -42,10 +42,10 @@ 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 production key (anonymised:
46
- # do not use this value as-is, the customer rotated it after the
45
+ # Obviously-synthetic placeholder fixture. The 4-char window
46
+ # scan downstream remains meaningful: the masked output must not
47
47
  # 1.5.0 -> 1.5.1 incident on 2026-05-12).
48
- secret_tail = "7ebef6b600015e3eaeda9149bf6d9c29a3a2a7a3075209112afde20888280de0"
48
+ secret_tail = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
49
49
  for prefix in ("cz_live_", "cz_test_"):
50
50
  key = prefix + secret_tail
51
51
  masked = _mask_api_key(key)
@@ -66,7 +66,7 @@ def test_env_dump_outputs_valid_json(monkeypatch):
66
66
  def test_env_dump_redacts_api_key_by_default(monkeypatch):
67
67
  monkeypatch.setenv(
68
68
  "CONTROLZERO_API_KEY",
69
- "cz_live_7ebef6b600015e3eaeda9149bf6d9c29a3a2a7a3075209112afde20888280de0",
69
+ "cz_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
70
70
  )
71
71
  result = _invoke([])
72
72
  assert result.exit_code == 0
@@ -74,7 +74,7 @@ def test_env_dump_redacts_api_key_by_default(monkeypatch):
74
74
  masked = data["env"]["CONTROLZERO_API_KEY"]
75
75
  assert masked == "cz_live_***"
76
76
  # Defensive: the secret bytes MUST NOT appear anywhere in the dump.
77
- secret_tail = "7ebef6b600015e3eaeda9149bf6d9c29a"
77
+ secret_tail = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
78
78
  assert secret_tail not in result.output
79
79
 
80
80
 
@@ -30,8 +30,8 @@ from click.testing import CliRunner
30
30
  from controlzero.cli.main import cli
31
31
 
32
32
 
33
- LIVE_KEY = "cz_live_7ebef6b600015e3eaeda9149bf6d9c29a3a2a7a3075209112afde20888280de0"
34
- TEST_KEY = "cz_test_abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567"
33
+ LIVE_KEY = "cz_live_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
34
+ TEST_KEY = "cz_test_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
35
35
 
36
36
 
37
37
  def _read_pretooluse_command(settings_path: Path) -> str:
@@ -1,6 +1,6 @@
1
1
  """T103 regression tests: hosted-vs-local precedence.
2
2
 
3
- an enterprise customer (Korea) 2026-05-12: a stale ~/.controlzero/policy.yaml
3
+ Customer report 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.
@@ -101,10 +101,10 @@ def test_no_api_key_accepts_json_policy(monkeypatch, tmp_path):
101
101
  cz.close()
102
102
 
103
103
 
104
- def test_john_na_regression_cwd_yaml_bypassed_when_api_key_set(
104
+ def test_customer_2026_05_12_cwd_yaml_bypassed_when_api_key_set(
105
105
  monkeypatch, tmp_path
106
106
  ):
107
- """an enterprise customer (Korea) 2026-05-12 regression.
107
+ """Customer report 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
@@ -125,7 +125,7 @@ def test_john_na_regression_cwd_yaml_bypassed_when_api_key_set(
125
125
  encoding="utf-8",
126
126
  )
127
127
  monkeypatch.chdir(tmp_path)
128
- monkeypatch.setenv("CONTROLZERO_API_KEY", "cz_live_johnnafake")
128
+ monkeypatch.setenv("CONTROLZERO_API_KEY", "cz_live_synthetictest")
129
129
  monkeypatch.delenv("CONTROLZERO_LOCAL_OVERRIDE", raising=False)
130
130
 
131
131
  hosted_policy = {
@@ -1,11 +1,11 @@
1
1
  """T104 regression tests: stale cache GC on key rotation.
2
2
 
3
- an enterprise customer (Korea) 2026-05-12: rotated his api_key from
4
- ``cz_live_566b...`` to ``cz_live_1af8...``. The new key bootstrapped
5
- fresh but ``cache/bundle-cz_live_566b.bin`` lingered for 25 days next to
6
- the new key's files. T104 GC's any cache files whose key-scope does NOT
7
- match the currently-active api_key, but only on a fresh bootstrap (not
8
- on a cache hit, because that means the active key is unchanged).
3
+ Customer report 2026-05-12: rotated their api_key from one value to
4
+ another. The new key bootstrapped fresh but the prior key's
5
+ ``cache/bundle-<oldscope>.bin`` lingered for 25 days next to the new
6
+ key's files. T104 GC's any cache files whose key-scope does NOT match
7
+ the currently-active api_key, but only on a fresh bootstrap (not on a
8
+ cache hit, because that means the active key is unchanged).
9
9
  """
10
10
 
11
11
  from __future__ import annotations
@@ -47,19 +47,19 @@ def _write_cache_triplet(cache_dir: Path, scope: str) -> tuple[Path, Path, Path]
47
47
 
48
48
 
49
49
  def test_gc_removes_files_for_rotated_keys(fake_cache_dir):
50
- """Stale triplet for cz_live_566b... is removed when 1af8 is active."""
51
- _write_cache_triplet(fake_cache_dir, _key_scope("cz_live_566bdeadbeef"))
50
+ """Stale triplet for the old scope is removed when the new scope is active."""
51
+ _write_cache_triplet(fake_cache_dir, _key_scope("cz_live_oldscope0000"))
52
52
  new_files = _write_cache_triplet(
53
- fake_cache_dir, _key_scope("cz_live_1af8fd9bcafe")
53
+ fake_cache_dir, _key_scope("cz_live_newscope0000")
54
54
  )
55
55
 
56
- removed = gc_stale_cache("cz_live_1af8fd9bcafe")
56
+ removed = gc_stale_cache("cz_live_newscope0000")
57
57
 
58
- assert removed == 3 # the 566b triplet
58
+ assert removed == 3 # the old-scope triplet
59
59
  for stale_name in (
60
- f"bootstrap-{_key_scope('cz_live_566bdeadbeef')}.json",
61
- f"bundle-{_key_scope('cz_live_566bdeadbeef')}.bin",
62
- f"bundle-{_key_scope('cz_live_566bdeadbeef')}.meta",
60
+ f"bootstrap-{_key_scope('cz_live_oldscope0000')}.json",
61
+ f"bundle-{_key_scope('cz_live_oldscope0000')}.bin",
62
+ f"bundle-{_key_scope('cz_live_oldscope0000')}.meta",
63
63
  ):
64
64
  assert not (fake_cache_dir / stale_name).exists()
65
65
  for active in new_files:
@@ -68,17 +68,17 @@ def test_gc_removes_files_for_rotated_keys(fake_cache_dir):
68
68
 
69
69
  def test_gc_is_noop_when_only_active_key_present(fake_cache_dir):
70
70
  """No-op when every file already belongs to the active key."""
71
- _write_cache_triplet(fake_cache_dir, _key_scope("cz_live_1af8fd9bcafe"))
72
- removed = gc_stale_cache("cz_live_1af8fd9bcafe")
71
+ _write_cache_triplet(fake_cache_dir, _key_scope("cz_live_newscope0000"))
72
+ removed = gc_stale_cache("cz_live_newscope0000")
73
73
  assert removed == 0
74
74
 
75
75
 
76
76
  def test_gc_leaves_unrelated_files_alone(fake_cache_dir):
77
77
  """Stray user files in the cache dir are preserved.
78
78
 
79
- John's machine had `s3_copy.py` in `~/.controlzero/`; we are not
80
- going to delete arbitrary user content even if they put it in our
81
- cache dir.
79
+ A customer's machine had `s3_copy.py` in `~/.controlzero/`; we are
80
+ not going to delete arbitrary user content even if they put it in
81
+ our cache dir.
82
82
  """
83
83
  stray = fake_cache_dir / "README.txt"
84
84
  stray.write_text("notes", encoding="utf-8")
@@ -114,7 +114,7 @@ def test_get_or_fetch_bootstrap_triggers_gc_on_fresh_fetch(
114
114
  fake_cache_dir, monkeypatch
115
115
  ):
116
116
  """A fresh bootstrap fetch triggers cache GC for rotated keys."""
117
- stale_scope = _key_scope("cz_live_566bdeadbeef")
117
+ stale_scope = _key_scope("cz_live_oldscope0000")
118
118
  _write_cache_triplet(fake_cache_dir, stale_scope)
119
119
 
120
120
  fresh_keys = BootstrapKeys(
@@ -126,16 +126,16 @@ def test_get_or_fetch_bootstrap_triggers_gc_on_fresh_fetch(
126
126
  )
127
127
 
128
128
  def fake_fetch(api_key, api_url=None):
129
- assert api_key == "cz_live_1af8fd9bcafe"
129
+ assert api_key == "cz_live_newscope0000"
130
130
  return fresh_keys
131
131
 
132
132
  monkeypatch.setattr(hosted_policy, "fetch_bootstrap", fake_fetch)
133
133
 
134
- keys = hosted_policy.get_or_fetch_bootstrap("cz_live_1af8fd9bcafe")
134
+ keys = hosted_policy.get_or_fetch_bootstrap("cz_live_newscope0000")
135
135
  assert keys.project_id == "proj"
136
136
 
137
137
  # Stale triplet gone, new bootstrap landed.
138
- new_scope = _key_scope("cz_live_1af8fd9bcafe")
138
+ new_scope = _key_scope("cz_live_newscope0000")
139
139
  assert not (fake_cache_dir / f"bootstrap-{stale_scope}.json").exists()
140
140
  assert not (fake_cache_dir / f"bundle-{stale_scope}.bin").exists()
141
141
  assert not (fake_cache_dir / f"bundle-{stale_scope}.meta").exists()
File without changes
File without changes
File without changes
File without changes