claude-dev-env 1.94.0 → 2.0.0
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.
- package/_shared/advisor/CLAUDE.md +2 -2
- package/_shared/advisor/advisor-protocol.md +41 -33
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +17 -13
- package/_shared/advisor/scripts/model_tier_run_validator.py +27 -18
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +95 -25
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +17 -17
- package/_shared/advisor/scripts/tier_model_ids.py +18 -18
- package/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +2 -1
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +253 -1980
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +32 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/__init__.py +7 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +268 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +172 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +70 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +326 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +85 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +331 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +369 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/conftest.py +14 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_added_line_maps.py +118 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +17 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_arguments.py +29 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +99 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_blob_readers.py +69 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_file_sets.py +137 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +116 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_violation_scoping.py +75 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_wrapper_plumb_check.py +49 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/violation_scoping.py +328 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/wrapper_plumb_check.py +206 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +24 -17
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +19 -2
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +278 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +6 -39
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +43 -0
- package/_shared/pr-loop/worker-spawn.md +186 -0
- package/agents/code-verifier.md +1 -1
- package/bin/CLAUDE.md +10 -1
- package/bin/ever-shipped-skills.mjs +73 -0
- package/bin/expand_home_directory_tokens.mjs +1 -1
- package/bin/install.mjs +140 -7
- package/bin/install.prune.test.mjs +457 -0
- package/docs/CODE_RULES.md +1 -1
- package/hooks/advisory/refactor_guard.py +3 -4
- package/hooks/blocking/CLAUDE.md +7 -1
- package/hooks/blocking/block_main_commit.py +2 -2
- package/hooks/blocking/claude_md_orphan_file_blocker.py +75 -699
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +28 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/orphan_blocker_constants.py +18 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/decision.py +81 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/references.py +307 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/scan_plan.py +124 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/subtree_scan.py +179 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/conftest.py +10 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_decision.py +34 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_references.py +42 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_scan_plan.py +27 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_subtree_scan.py +30 -0
- package/hooks/blocking/code_rules_boolean_mustcheck.py +1 -1
- package/hooks/blocking/code_rules_enforcer.py +4 -0
- package/hooks/blocking/code_rules_mock_completeness.py +1 -1
- package/hooks/blocking/code_rules_optional_params.py +2 -2
- package/hooks/blocking/code_rules_shared.py +83 -1
- package/hooks/blocking/code_rules_test_assertions.py +1 -1
- package/hooks/blocking/code_rules_test_layout.py +9 -3
- package/hooks/blocking/code_rules_typeddict_stub.py +1 -1
- package/hooks/blocking/gh_pr_author_enforcer.py +1 -1
- package/hooks/blocking/inventory_intent_records/CLAUDE.md +26 -0
- package/hooks/blocking/inventory_intent_records/__init__.py +1 -0
- package/hooks/blocking/inventory_intent_records/config/__init__.py +1 -0
- package/hooks/blocking/inventory_intent_records/config/intent_records_constants.py +20 -0
- package/hooks/blocking/inventory_intent_records/records.py +271 -0
- package/hooks/blocking/inventory_intent_records/tests/conftest.py +10 -0
- package/hooks/blocking/inventory_intent_records/tests/test_records.py +80 -0
- package/hooks/blocking/package_inventory_stale_blocker.py +54 -384
- package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +26 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/config/inventory_blocker_constants.py +16 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/decision.py +84 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/inventory_detection.py +307 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/conftest.py +10 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_decision.py +38 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_inventory_detection.py +61 -0
- package/hooks/blocking/pii_payload_scan.py +138 -42
- package/hooks/blocking/pii_prevention_blocker.py +185 -291
- package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +24 -0
- package/hooks/blocking/pii_prevention_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/pii_prevention_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/pii_prevention_blocker_parts/config/repository_resolution_constants.py +28 -0
- package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +214 -0
- package/hooks/blocking/pii_prevention_blocker_parts/repository_resolution.py +208 -0
- package/hooks/blocking/plain_language_blocker.py +138 -4
- package/hooks/blocking/pr_description_command_parser.py +8 -4
- package/hooks/blocking/precommit_code_rules_gate.py +3 -3
- package/hooks/blocking/sensitive_file_protector.py +114 -48
- package/hooks/blocking/tdd_enforcer.py +97 -601
- package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +30 -0
- package/hooks/blocking/tdd_enforcer_parts/__init__.py +1 -0
- package/hooks/blocking/tdd_enforcer_parts/candidate_paths.py +142 -0
- package/hooks/blocking/tdd_enforcer_parts/config/__init__.py +1 -0
- package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +32 -0
- package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +268 -0
- package/hooks/blocking/tdd_enforcer_parts/decisions.py +92 -0
- package/hooks/blocking/tdd_enforcer_parts/freshness.py +80 -0
- package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +63 -0
- package/hooks/blocking/tdd_enforcer_parts/path_classification.py +119 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/conftest.py +10 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_candidate_paths.py +31 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +30 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +34 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_freshness.py +28 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +48 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_path_classification.py +36 -0
- package/hooks/blocking/test_code_rules_enforcer_scratchpad.py +105 -0
- package/hooks/blocking/test_code_rules_shared.py +181 -0
- package/hooks/blocking/test_inventory_deadlock_resolution.py +154 -0
- package/hooks/blocking/test_pii_payload_scan.py +168 -0
- package/hooks/blocking/test_plain_language_blocker_allowlist.py +184 -0
- package/hooks/blocking/test_sensitive_file_protector.py +185 -0
- package/hooks/blocking/test_tdd_enforcer_restore.py +108 -0
- package/hooks/blocking/test_tdd_enforcer_scratchpad.py +105 -0
- package/hooks/blocking/tests/conftest.py +10 -0
- package/hooks/blocking/tests/test_pii_prevention_blocker.py +260 -0
- package/hooks/blocking/tests/test_repository_exemption.py +105 -0
- package/hooks/blocking/tests/test_repository_resolution.py +108 -0
- package/hooks/diagnostic/hook_log_extractor.py +12 -10
- package/hooks/git-hooks/post_commit.py +3 -4
- package/hooks/hooks_constants/CLAUDE.md +4 -2
- package/hooks/hooks_constants/banned_identifiers_constants.py +0 -1
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/harness_scratchpad_constants.py +17 -0
- package/hooks/hooks_constants/local_identity.py +59 -8
- package/hooks/hooks_constants/pii_prevention_constants.py +0 -6
- package/hooks/hooks_constants/plain_language_blocker_constants.py +5 -0
- package/hooks/hooks_constants/sensitive_file_protector_constants.py +42 -0
- package/hooks/hooks_constants/test_local_identity.py +105 -3
- package/hooks/pyproject.toml +52 -4
- package/hooks/session/plugin_data_dir_cleanup.py +0 -1
- package/hooks/validation/mypy_validator.py +2 -2
- package/hooks/validators/CLAUDE.md +1 -1
- package/hooks/validators/README.md +2 -0
- package/hooks/validators/health_check.py +1 -0
- package/hooks/validators/mypy_integration.py +2 -0
- package/hooks/validators/python_style_checks.py +114 -136
- package/hooks/validators/python_style_helpers.py +95 -0
- package/hooks/validators/ruff_integration.py +3 -0
- package/hooks/validators/test_python_style_checks.py +0 -164
- package/hooks/validators/test_python_style_checks_decorator_gap.py +119 -0
- package/hooks/validators/test_python_style_fixes.py +251 -0
- package/hooks/validators/test_python_style_helpers.py +125 -0
- package/hooks/workflow/auto_formatter.py +5 -4
- package/package.json +1 -1
- package/rules/CLAUDE.md +1 -0
- package/rules/anti-corollary-tests.md +69 -0
- package/rules/bdd.md +1 -3
- package/rules/code-reviews.md +1 -1
- package/rules/gh-paginate.md +1 -1
- package/rules/plain-language.md +2 -0
- package/scripts/CLAUDE.md +4 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +6 -4
- package/scripts/dev_env_scripts_constants/code_review_constants.py +71 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +435 -0
- package/scripts/dev_env_scripts_constants/timing.py +7 -1
- package/scripts/grok_headless_runner.py +294 -0
- package/scripts/grok_worker_preflight.py +410 -0
- package/scripts/invoke_code_review.py +463 -0
- package/scripts/resolve_worker_spawn.py +619 -0
- package/scripts/spawn_grok_batch.py +672 -0
- package/scripts/test_grok_headless_runner.py +626 -0
- package/scripts/test_grok_worker_preflight.py +1054 -0
- package/scripts/test_invoke_code_review.py +672 -0
- package/scripts/test_resolve_worker_spawn.py +1014 -0
- package/scripts/test_spawn_grok_batch.py +1017 -0
- package/skills/CLAUDE.md +6 -3
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +72 -13
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +121 -14
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +78 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +121 -0
- package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +196 -6
- package/skills/autoconverge/CLAUDE.md +3 -3
- package/skills/autoconverge/SKILL.md +9 -3
- package/skills/autoconverge/reference/CLAUDE.md +2 -2
- package/skills/autoconverge/reference/convergence.md +33 -11
- package/skills/autoconverge/reference/stop-conditions.md +16 -5
- package/skills/autoconverge/workflow/CLAUDE.md +2 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +7 -2
- package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +300 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +5 -5
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +29 -29
- package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.mjs +202 -18
- package/skills/bugteam/CLAUDE.md +2 -2
- package/skills/bugteam/CONSTRAINTS.md +3 -2
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +18 -13
- package/skills/bugteam/reference/README.md +2 -3
- package/skills/bugteam/reference/audit-and-teammates.md +215 -35
- package/skills/bugteam/reference/design-rationale.md +1 -1
- package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -2
- package/skills/closeout/SKILL.md +153 -0
- package/skills/closeout/reference/handoff-prompt-template.md +72 -0
- package/skills/closeout/reference/issue-body-templates.md +108 -0
- package/skills/closeout/reference/pii-redaction-checklist.md +36 -0
- package/skills/codex-review/CLAUDE.md +46 -0
- package/skills/codex-review/SKILL.md +181 -0
- package/skills/codex-review/reference/CLAUDE.md +15 -0
- package/skills/codex-review/reference/cli-contract.md +253 -0
- package/skills/codex-review/reference/loop-integration.md +118 -0
- package/skills/codex-review/scripts/codex_down_classifier.py +98 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/__init__.py +1 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/classifier_constants.py +35 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +86 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/findings_constants.py +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +45 -0
- package/skills/codex-review/scripts/codex_usage_probe.py +573 -0
- package/skills/codex-review/scripts/fixtures/auth_failure_synthetic.txt +1 -0
- package/skills/codex-review/scripts/fixtures/config_load_failure_v0.125.0.txt +1 -0
- package/skills/codex-review/scripts/fixtures/freeform_findings_v0.144.3.txt +6 -0
- package/skills/codex-review/scripts/fixtures/model_rejection_v0.125.0.jsonl +5 -0
- package/skills/codex-review/scripts/fixtures/structured_findings.txt +13 -0
- package/skills/codex-review/scripts/fixtures/success_stream_v0.144.3.jsonl +6 -0
- package/skills/codex-review/scripts/fixtures/unknown_failure_synthetic.txt +1 -0
- package/skills/codex-review/scripts/fixtures/usage_limit_synthetic.txt +1 -0
- package/skills/codex-review/scripts/parse_codex_findings.py +207 -0
- package/skills/codex-review/scripts/run_codex_review.py +415 -0
- package/skills/codex-review/scripts/test_codex_down_classifier.py +143 -0
- package/skills/codex-review/scripts/test_codex_usage_probe.py +678 -0
- package/skills/codex-review/scripts/test_parse_codex_findings.py +130 -0
- package/skills/codex-review/scripts/test_run_codex_review.py +812 -0
- package/skills/codex-review/test_skill_scaffold.py +192 -0
- package/skills/grok-spawn/CLAUDE.md +28 -0
- package/skills/grok-spawn/SKILL.md +226 -0
- package/skills/grok-spawn/reference/flag-profiles.md +132 -0
- package/skills/grok-spawn/reference/worker-briefs.md +152 -0
- package/skills/grokify/SKILL.md +9 -1
- package/skills/grokify/capability-claims.test.mjs +28 -0
- package/skills/grokify/evals/README.md +72 -0
- package/skills/grokify/evals/parse-payload.test.mjs +171 -0
- package/skills/grokify/evals/run-capability-evals.mjs +545 -0
- package/skills/orchestrator/SKILL.md +32 -22
- package/skills/orchestrator-refresh/SKILL.md +12 -8
- package/skills/pr-converge/CLAUDE.md +1 -1
- package/skills/pr-converge/SKILL.md +34 -13
- package/skills/pr-converge/reference/convergence-gates.md +42 -15
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +130 -42
- package/skills/pr-converge/reference/state-schema.md +10 -0
- package/skills/pr-converge/scripts/CLAUDE.md +2 -0
- package/skills/pr-converge/scripts/_pr_converge_path_setup.py +5 -1
- package/skills/pr-converge/scripts/check_convergence.py +605 -29
- package/skills/pr-converge/scripts/check_convergence_availability.py +232 -0
- package/skills/pr-converge/scripts/check_convergence_gates.py +279 -235
- package/skills/pr-converge/scripts/check_convergence_thread_gates.py +1 -1
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -2
- package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +4 -0
- package/skills/pr-converge/scripts/test_check_convergence.py +71 -3
- package/skills/pr-converge/scripts/test_check_convergence_availability.py +326 -0
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -0
- package/skills/pr-converge/scripts/test_check_convergence_contract.py +89 -17
- package/skills/pr-converge/scripts/test_check_convergence_fixture.py +179 -0
- package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -68
- package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +24 -0
- package/skills/pr-converge/test_step5_host_branch.py +106 -0
- package/skills/pr-fix-protocol/SKILL.md +65 -0
- package/skills/pr-loop-cloud-transport/SKILL.md +2 -0
- package/skills/reviewer-gates/SKILL.md +7 -5
- package/skills/skill-builder/references/skill-modularity.md +1 -1
- package/skills/team-advisor/SKILL.md +16 -12
- package/system-prompts/software-engineer.xml +7 -6
- package/hooks/validators/test_verify_paths.py +0 -32
- package/hooks/validators/verify_paths.py +0 -57
|
@@ -17,6 +17,7 @@ from typing import Callable
|
|
|
17
17
|
import pytest
|
|
18
18
|
|
|
19
19
|
import _pr_converge_path_setup # noqa: F401
|
|
20
|
+
import check_convergence_availability as availability_module
|
|
20
21
|
from pr_converge_skill_constants.constants import EXIT_CODE_GH_ERROR
|
|
21
22
|
|
|
22
23
|
_SCRIPTS_DIRECTORY = Path(__file__).absolute().parent
|
|
@@ -106,8 +107,15 @@ def _raise_if_called(gate_label: str) -> Callable[..., tuple[bool, str]]:
|
|
|
106
107
|
return _stub
|
|
107
108
|
|
|
108
109
|
|
|
110
|
+
def _null_codex_percent_left() -> float | None:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
109
114
|
def _patch_all_gates_passing(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
110
115
|
monkeypatch.setattr(check_convergence, "_get_pr_head_sha", _passing_head_sha)
|
|
116
|
+
monkeypatch.setattr(
|
|
117
|
+
check_convergence, "_probe_codex_percent_left", _null_codex_percent_left
|
|
118
|
+
)
|
|
111
119
|
for each_gate_name in _ALL_LEAF_GATE_NAMES:
|
|
112
120
|
monkeypatch.setattr(check_convergence, each_gate_name, _passing_gate)
|
|
113
121
|
|
|
@@ -282,7 +290,14 @@ def should_bypass_bugbot_gates_when_bugbot_down_is_true(
|
|
|
282
290
|
check_convergence, "_check_bugbot_not_dirty", _raise_if_called("_check_bugbot_not_dirty")
|
|
283
291
|
)
|
|
284
292
|
exit_code = check_convergence.check_all(
|
|
285
|
-
owner="o",
|
|
293
|
+
owner="o",
|
|
294
|
+
repo="r",
|
|
295
|
+
number=1,
|
|
296
|
+
is_bugbot_down=True,
|
|
297
|
+
is_copilot_down=False,
|
|
298
|
+
is_bugteam_post_blocked=False,
|
|
299
|
+
is_codex_down=False,
|
|
300
|
+
live_codex_clean_at=None,
|
|
286
301
|
)
|
|
287
302
|
assert "bypassed (bugbot_down)" in capsys.readouterr().out
|
|
288
303
|
assert exit_code == 0
|
|
@@ -297,7 +312,14 @@ def should_bypass_copilot_gates_when_copilot_down_is_true(
|
|
|
297
312
|
check_convergence, "_check_no_pending_reviews", _raise_if_called("_check_no_pending_reviews")
|
|
298
313
|
)
|
|
299
314
|
exit_code = check_convergence.check_all(
|
|
300
|
-
owner="o",
|
|
315
|
+
owner="o",
|
|
316
|
+
repo="r",
|
|
317
|
+
number=1,
|
|
318
|
+
is_bugbot_down=False,
|
|
319
|
+
is_copilot_down=True,
|
|
320
|
+
is_bugteam_post_blocked=False,
|
|
321
|
+
is_codex_down=False,
|
|
322
|
+
live_codex_clean_at=None,
|
|
301
323
|
)
|
|
302
324
|
assert "bypassed (copilot_down)" in capsys.readouterr().out
|
|
303
325
|
assert exit_code == 0
|
|
@@ -341,7 +363,14 @@ def should_propagate_systemexit_from_get_pr_head_sha(monkeypatch: pytest.MonkeyP
|
|
|
341
363
|
)
|
|
342
364
|
with pytest.raises(SystemExit) as exc_info:
|
|
343
365
|
check_convergence.check_all(
|
|
344
|
-
owner="o",
|
|
366
|
+
owner="o",
|
|
367
|
+
repo="r",
|
|
368
|
+
number=1,
|
|
369
|
+
is_bugbot_down=False,
|
|
370
|
+
is_copilot_down=False,
|
|
371
|
+
is_bugteam_post_blocked=False,
|
|
372
|
+
is_codex_down=False,
|
|
373
|
+
live_codex_clean_at=None,
|
|
345
374
|
)
|
|
346
375
|
assert exc_info.value.code == EXIT_CODE_GH_ERROR
|
|
347
376
|
|
|
@@ -350,6 +379,12 @@ def should_derive_copilot_down_from_env_when_main_omits_the_flag(
|
|
|
350
379
|
monkeypatch: pytest.MonkeyPatch,
|
|
351
380
|
) -> None:
|
|
352
381
|
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "copilot")
|
|
382
|
+
monkeypatch.setattr(
|
|
383
|
+
availability_module,
|
|
384
|
+
"get_claude_user_settings_path",
|
|
385
|
+
lambda: Path("does-not-exist"),
|
|
386
|
+
)
|
|
387
|
+
availability_module._log_disk_settings_fallback_once.cache_clear()
|
|
353
388
|
captured_copilot_down: list[bool] = []
|
|
354
389
|
|
|
355
390
|
def stub_check_all(
|
|
@@ -360,6 +395,9 @@ def should_derive_copilot_down_from_env_when_main_omits_the_flag(
|
|
|
360
395
|
is_bugbot_down: bool,
|
|
361
396
|
is_copilot_down: bool,
|
|
362
397
|
is_bugteam_post_blocked: bool = False,
|
|
398
|
+
is_codex_down: bool = False,
|
|
399
|
+
live_codex_clean_at: str | None = None,
|
|
400
|
+
**_bypass_note_kwargs: str,
|
|
363
401
|
) -> int:
|
|
364
402
|
captured_copilot_down.append(is_copilot_down)
|
|
365
403
|
return 0
|
|
@@ -368,3 +406,33 @@ def should_derive_copilot_down_from_env_when_main_omits_the_flag(
|
|
|
368
406
|
exit_code = check_convergence.main(["--owner", "o", "--repo", "r", "--pr-number", "1"])
|
|
369
407
|
assert exit_code == 0
|
|
370
408
|
assert captured_copilot_down == [True]
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def should_print_probe_reason_on_the_bypassed_copilot_gate_line(
|
|
412
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
413
|
+
) -> None:
|
|
414
|
+
_patch_all_gates_passing(monkeypatch)
|
|
415
|
+
monkeypatch.setattr(
|
|
416
|
+
check_convergence, "_check_bot_review", _raise_if_called("_check_bot_review")
|
|
417
|
+
)
|
|
418
|
+
monkeypatch.setattr(
|
|
419
|
+
check_convergence,
|
|
420
|
+
"_check_no_pending_reviews",
|
|
421
|
+
_raise_if_called("_check_no_pending_reviews"),
|
|
422
|
+
)
|
|
423
|
+
exit_code = check_convergence.check_all(
|
|
424
|
+
owner="o",
|
|
425
|
+
repo="r",
|
|
426
|
+
number=1,
|
|
427
|
+
is_bugbot_down=False,
|
|
428
|
+
is_copilot_down=True,
|
|
429
|
+
is_bugteam_post_blocked=False,
|
|
430
|
+
is_codex_down=False,
|
|
431
|
+
live_codex_clean_at=None,
|
|
432
|
+
copilot_bypass_note="copilot unavailable: out of premium quota",
|
|
433
|
+
)
|
|
434
|
+
captured_stdout = capsys.readouterr().out
|
|
435
|
+
probe_line = "bypassed (copilot unavailable: out of premium quota)"
|
|
436
|
+
assert captured_stdout.count(probe_line) == 2
|
|
437
|
+
assert exit_code == 0
|
|
438
|
+
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""Tests for the reviewer-availability + settings resolution of the convergence gate.
|
|
2
|
+
|
|
3
|
+
::
|
|
4
|
+
|
|
5
|
+
probe out-of-quota ok: waived, "copilot unavailable: <reason>"
|
|
6
|
+
probe available flag: enforced, no note
|
|
7
|
+
probe API down flag: enforced, probe_error_reason set (not waived)
|
|
8
|
+
probe exception flag: enforced, probe_error_reason set
|
|
9
|
+
disk lists copilot ok: waived even when env omits it (disk authoritative)
|
|
10
|
+
disk omits copilot flag: enforced even when env lists it (disk authoritative)
|
|
11
|
+
disk unreadable ok: env fallback, logged once per process
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import importlib.util
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from types import ModuleType
|
|
22
|
+
from typing import Callable
|
|
23
|
+
|
|
24
|
+
import pytest
|
|
25
|
+
|
|
26
|
+
import _pr_converge_path_setup # noqa: F401
|
|
27
|
+
from pr_loop_shared_constants.copilot_quota_constants import (
|
|
28
|
+
EXIT_CODE_NO_ACCOUNT_CONFIGURED,
|
|
29
|
+
EXIT_CODE_OUT_OF_QUOTA,
|
|
30
|
+
EXIT_CODE_QUOTA_API_DOWN,
|
|
31
|
+
EXIT_CODE_QUOTA_AVAILABLE,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
_SCRIPTS_DIRECTORY = Path(__file__).absolute().parent
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _load_availability() -> ModuleType:
|
|
38
|
+
if str(_SCRIPTS_DIRECTORY) not in sys.path:
|
|
39
|
+
sys.path.insert(0, str(_SCRIPTS_DIRECTORY))
|
|
40
|
+
module_path = _SCRIPTS_DIRECTORY / "check_convergence_availability.py"
|
|
41
|
+
spec = importlib.util.spec_from_file_location(
|
|
42
|
+
"check_convergence_availability_under_test", module_path
|
|
43
|
+
)
|
|
44
|
+
assert spec is not None
|
|
45
|
+
assert spec.loader is not None
|
|
46
|
+
module = importlib.util.module_from_spec(spec)
|
|
47
|
+
spec.loader.exec_module(module)
|
|
48
|
+
return module
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
availability = _load_availability()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _StubQuotaDecision:
|
|
55
|
+
"""Stand-in for QuotaDecision the copilot probe reads exit_code and message off."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, exit_code: int, message: str) -> None:
|
|
58
|
+
self.exit_code = exit_code
|
|
59
|
+
self.message = message
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _stub_quota(exit_code: int, message: str) -> Callable[..., _StubQuotaDecision]:
|
|
63
|
+
def _probe(**_call_keywords: object) -> _StubQuotaDecision:
|
|
64
|
+
return _StubQuotaDecision(exit_code, message)
|
|
65
|
+
|
|
66
|
+
return _probe
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _write_settings(
|
|
70
|
+
tmp_path: Path,
|
|
71
|
+
*,
|
|
72
|
+
reviews_disabled_value: str | None = None,
|
|
73
|
+
reviews_enabled_value: str | None = None,
|
|
74
|
+
) -> Path:
|
|
75
|
+
env_block: dict[str, str] = {}
|
|
76
|
+
if reviews_disabled_value is not None:
|
|
77
|
+
env_block["CLAUDE_REVIEWS_DISABLED"] = reviews_disabled_value
|
|
78
|
+
if reviews_enabled_value is not None:
|
|
79
|
+
env_block["CLAUDE_REVIEWS_ENABLED"] = reviews_enabled_value
|
|
80
|
+
settings_path = tmp_path / "settings.json"
|
|
81
|
+
settings_path.write_text(json.dumps({"env": env_block}), encoding="utf-8")
|
|
82
|
+
return settings_path
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.fixture(autouse=True)
|
|
86
|
+
def _reset_probe_cache_and_env(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
87
|
+
availability._probe_copilot_quota.cache_clear()
|
|
88
|
+
availability._log_disk_settings_fallback_once.cache_clear()
|
|
89
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_DISABLED", raising=False)
|
|
90
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
|
|
91
|
+
monkeypatch.setattr(
|
|
92
|
+
availability,
|
|
93
|
+
"get_claude_user_settings_path",
|
|
94
|
+
lambda: Path("does-not-exist"),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def should_copilot_probe_waive_on_out_of_quota(
|
|
99
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
100
|
+
) -> None:
|
|
101
|
+
monkeypatch.setattr(
|
|
102
|
+
availability,
|
|
103
|
+
"evaluate_copilot_quota",
|
|
104
|
+
_stub_quota(EXIT_CODE_OUT_OF_QUOTA, "out of premium quota"),
|
|
105
|
+
)
|
|
106
|
+
waiver = availability._probe_copilot_quota()
|
|
107
|
+
assert waiver.is_waived is True
|
|
108
|
+
assert waiver.bypass_note == "copilot unavailable: out of premium quota"
|
|
109
|
+
assert waiver.probe_error_reason == ""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def should_copilot_probe_enforce_when_quota_available(
|
|
113
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
114
|
+
) -> None:
|
|
115
|
+
monkeypatch.setattr(
|
|
116
|
+
availability,
|
|
117
|
+
"evaluate_copilot_quota",
|
|
118
|
+
_stub_quota(EXIT_CODE_QUOTA_AVAILABLE, "quota available"),
|
|
119
|
+
)
|
|
120
|
+
waiver = availability._probe_copilot_quota()
|
|
121
|
+
assert waiver.is_waived is False
|
|
122
|
+
assert waiver.bypass_note == ""
|
|
123
|
+
assert waiver.probe_error_reason == ""
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def should_copilot_probe_set_probe_error_on_quota_api_down(
|
|
127
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
128
|
+
) -> None:
|
|
129
|
+
monkeypatch.setattr(
|
|
130
|
+
availability,
|
|
131
|
+
"evaluate_copilot_quota",
|
|
132
|
+
_stub_quota(EXIT_CODE_QUOTA_API_DOWN, "quota API unreachable"),
|
|
133
|
+
)
|
|
134
|
+
waiver = availability._probe_copilot_quota()
|
|
135
|
+
assert waiver.is_waived is False
|
|
136
|
+
assert waiver.bypass_note == ""
|
|
137
|
+
assert waiver.probe_error_reason == "quota API unreachable"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def should_copilot_probe_set_probe_error_on_no_account_configured(
|
|
141
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
142
|
+
) -> None:
|
|
143
|
+
monkeypatch.setattr(
|
|
144
|
+
availability,
|
|
145
|
+
"evaluate_copilot_quota",
|
|
146
|
+
_stub_quota(
|
|
147
|
+
EXIT_CODE_NO_ACCOUNT_CONFIGURED,
|
|
148
|
+
"no account configured",
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
waiver = availability._probe_copilot_quota()
|
|
152
|
+
assert waiver.is_waived is False
|
|
153
|
+
assert waiver.bypass_note == ""
|
|
154
|
+
assert waiver.probe_error_reason == "no account configured"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def should_copilot_probe_set_probe_error_on_exception(
|
|
158
|
+
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
|
159
|
+
) -> None:
|
|
160
|
+
def _raising_probe(**_call_keywords: object) -> object:
|
|
161
|
+
raise RuntimeError("quota API unreachable")
|
|
162
|
+
|
|
163
|
+
monkeypatch.setattr(availability, "evaluate_copilot_quota", _raising_probe)
|
|
164
|
+
with caplog.at_level(logging.WARNING):
|
|
165
|
+
waiver = availability._probe_copilot_quota()
|
|
166
|
+
assert waiver.is_waived is False
|
|
167
|
+
assert waiver.probe_error_reason == "quota API unreachable"
|
|
168
|
+
assert "probe error" in caplog.text
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def should_probe_copilot_at_most_once_per_run(
|
|
172
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
173
|
+
) -> None:
|
|
174
|
+
probe_call_count = 0
|
|
175
|
+
|
|
176
|
+
def _counting_probe(**_call_keywords: object) -> _StubQuotaDecision:
|
|
177
|
+
nonlocal probe_call_count
|
|
178
|
+
probe_call_count += 1
|
|
179
|
+
return _StubQuotaDecision(EXIT_CODE_OUT_OF_QUOTA, "down once")
|
|
180
|
+
|
|
181
|
+
monkeypatch.setattr(availability, "evaluate_copilot_quota", _counting_probe)
|
|
182
|
+
availability._probe_copilot_quota()
|
|
183
|
+
availability._probe_copilot_quota()
|
|
184
|
+
assert probe_call_count == 1
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def should_resolve_copilot_waiver_from_flag_with_pinned_note() -> None:
|
|
188
|
+
waiver = availability._resolve_copilot_waiver(True)
|
|
189
|
+
assert waiver.is_waived is True
|
|
190
|
+
assert waiver.bypass_note == "copilot_down"
|
|
191
|
+
assert waiver.probe_error_reason == ""
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def should_resolve_copilot_waiver_from_frozen_env_with_pinned_note(
|
|
195
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
196
|
+
) -> None:
|
|
197
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "copilot")
|
|
198
|
+
waiver = availability._resolve_copilot_waiver(False)
|
|
199
|
+
assert waiver.is_waived is True
|
|
200
|
+
assert waiver.bypass_note == "copilot_down"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def should_waive_copilot_from_disk_when_env_omits_it_without_union_warning(
|
|
204
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path, caplog: pytest.LogCaptureFixture
|
|
205
|
+
) -> None:
|
|
206
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "bugbot")
|
|
207
|
+
settings_path = _write_settings(tmp_path, reviews_disabled_value="bugbot,copilot")
|
|
208
|
+
monkeypatch.setattr(
|
|
209
|
+
availability, "get_claude_user_settings_path", lambda: settings_path
|
|
210
|
+
)
|
|
211
|
+
with caplog.at_level(logging.WARNING):
|
|
212
|
+
waiver = availability._resolve_copilot_waiver(False)
|
|
213
|
+
assert waiver.is_waived is True
|
|
214
|
+
assert waiver.bypass_note == "copilot_down"
|
|
215
|
+
assert "taking the union" not in caplog.text
|
|
216
|
+
assert "differs" not in caplog.text
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def should_enforce_copilot_from_disk_when_env_still_lists_it(
|
|
220
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
221
|
+
) -> None:
|
|
222
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "copilot")
|
|
223
|
+
settings_path = _write_settings(tmp_path, reviews_disabled_value="bugbot")
|
|
224
|
+
monkeypatch.setattr(
|
|
225
|
+
availability, "get_claude_user_settings_path", lambda: settings_path
|
|
226
|
+
)
|
|
227
|
+
monkeypatch.setattr(
|
|
228
|
+
availability,
|
|
229
|
+
"evaluate_copilot_quota",
|
|
230
|
+
_stub_quota(EXIT_CODE_QUOTA_AVAILABLE, "quota available"),
|
|
231
|
+
)
|
|
232
|
+
waiver = availability._resolve_copilot_waiver(False)
|
|
233
|
+
assert waiver.is_waived is False
|
|
234
|
+
assert waiver.probe_error_reason == ""
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def should_not_fall_through_to_env_when_readable_disk_omits_disabled_key(
|
|
238
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
239
|
+
) -> None:
|
|
240
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "copilot")
|
|
241
|
+
settings_path = _write_settings(tmp_path)
|
|
242
|
+
monkeypatch.setattr(
|
|
243
|
+
availability, "get_claude_user_settings_path", lambda: settings_path
|
|
244
|
+
)
|
|
245
|
+
monkeypatch.setattr(
|
|
246
|
+
availability,
|
|
247
|
+
"evaluate_copilot_quota",
|
|
248
|
+
_stub_quota(EXIT_CODE_QUOTA_AVAILABLE, "quota available"),
|
|
249
|
+
)
|
|
250
|
+
waiver = availability._resolve_copilot_waiver(False)
|
|
251
|
+
assert waiver.is_waived is False
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def should_use_env_fallback_and_log_once_when_disk_unreadable(
|
|
255
|
+
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
|
256
|
+
) -> None:
|
|
257
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_DISABLED", "copilot,bugbot")
|
|
258
|
+
monkeypatch.setenv("CLAUDE_REVIEWS_ENABLED", "bugbot")
|
|
259
|
+
with caplog.at_level(logging.WARNING):
|
|
260
|
+
copilot_waiver = availability._resolve_copilot_waiver(False)
|
|
261
|
+
bugbot_waiver = availability._resolve_bugbot_waiver(False)
|
|
262
|
+
assert copilot_waiver.is_waived is True
|
|
263
|
+
assert bugbot_waiver.is_waived is True
|
|
264
|
+
fallback_log_count = caplog.text.count("settings.json unreadable")
|
|
265
|
+
assert fallback_log_count == 1
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def should_resolve_bugbot_disabled_when_enabled_list_omits_it(
|
|
269
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
270
|
+
) -> None:
|
|
271
|
+
settings_path = _write_settings(
|
|
272
|
+
tmp_path, reviews_disabled_value="", reviews_enabled_value=""
|
|
273
|
+
)
|
|
274
|
+
monkeypatch.setattr(
|
|
275
|
+
availability, "get_claude_user_settings_path", lambda: settings_path
|
|
276
|
+
)
|
|
277
|
+
waiver = availability._resolve_bugbot_waiver(False)
|
|
278
|
+
assert waiver.is_waived is True
|
|
279
|
+
assert waiver.bypass_note == "bugbot_down"
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def should_enforce_bugbot_when_disk_enables_it_even_if_env_omits_opt_in(
|
|
283
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
284
|
+
) -> None:
|
|
285
|
+
monkeypatch.delenv("CLAUDE_REVIEWS_ENABLED", raising=False)
|
|
286
|
+
settings_path = _write_settings(
|
|
287
|
+
tmp_path, reviews_disabled_value="", reviews_enabled_value="bugbot"
|
|
288
|
+
)
|
|
289
|
+
monkeypatch.setattr(
|
|
290
|
+
availability, "get_claude_user_settings_path", lambda: settings_path
|
|
291
|
+
)
|
|
292
|
+
waiver = availability._resolve_bugbot_waiver(False)
|
|
293
|
+
assert waiver.is_waived is False
|
|
294
|
+
assert waiver.probe_error_reason == ""
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def should_read_settings_via_get_claude_user_settings_path(
|
|
298
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
299
|
+
) -> None:
|
|
300
|
+
settings_path = _write_settings(tmp_path, reviews_disabled_value="copilot")
|
|
301
|
+
resolved_paths: list[Path] = []
|
|
302
|
+
|
|
303
|
+
def _recording_settings_path() -> Path:
|
|
304
|
+
resolved_paths.append(settings_path)
|
|
305
|
+
return settings_path
|
|
306
|
+
|
|
307
|
+
monkeypatch.setattr(
|
|
308
|
+
availability, "get_claude_user_settings_path", _recording_settings_path
|
|
309
|
+
)
|
|
310
|
+
waiver = availability._resolve_copilot_waiver(False)
|
|
311
|
+
assert resolved_paths == [settings_path]
|
|
312
|
+
assert waiver.is_waived is True
|
|
313
|
+
assert waiver.bypass_note == "copilot_down"
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def should_probe_copilot_when_neither_flag_env_nor_disk_disable_it(
|
|
317
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
318
|
+
) -> None:
|
|
319
|
+
monkeypatch.setattr(
|
|
320
|
+
availability,
|
|
321
|
+
"evaluate_copilot_quota",
|
|
322
|
+
_stub_quota(EXIT_CODE_OUT_OF_QUOTA, "quota exhausted"),
|
|
323
|
+
)
|
|
324
|
+
waiver = availability._resolve_copilot_waiver(False)
|
|
325
|
+
assert waiver.is_waived is True
|
|
326
|
+
assert waiver.bypass_note == "copilot unavailable: quota exhausted"
|