diff --git a/scripts/diagnose_cached_stage_failure.py b/scripts/diagnose_cached_stage_failure.py index d4c430d..bce66f8 100644 --- a/scripts/diagnose_cached_stage_failure.py +++ b/scripts/diagnose_cached_stage_failure.py @@ -27,6 +27,10 @@ "environment", "network", "quota", + "traffic_tag_length", + "traffic_tag_format", + "traffic_tag_conflict", + "traffic_tag_url_disabled", ) _REASON_PATTERNS = { "act_as": re.compile(r"iam\.serviceaccounts\.actas|actas|service account user", re.I), @@ -47,6 +51,10 @@ "environment": re.compile(r"environment variable|environment configuration|env var", re.I), "network": re.compile(r"\bvpc\b|network|subnet|connector", re.I), "quota": re.compile(r"quota|resource[_ ]exhausted|limit exceeded", re.I), + "traffic_tag_length": re.compile(r"traffic[^\n]*tags?[^\n]*(length|longer|shorter|characters|too long|at most|at least)", re.I), + "traffic_tag_format": re.compile(r"traffic[^\n]*tags?[^\n]*(format|lowercase|dns|regex|valid)", re.I), + "traffic_tag_conflict": re.compile(r"traffic[^\n]*tags?[^\n]*(reserved|already|duplicate|unique)", re.I), + "traffic_tag_url_disabled": re.compile(r"traffic[^\n]*tags?[^\n]*(url[^\n]*disabled|disabled[^\n]*url|unsupported|not supported)", re.I), } _KNOWN_RPC_STATUS_CODES = frozenset(range(17)) _AUDIT_MESSAGE_TERMS = ( @@ -381,6 +389,7 @@ def summarize( "service_readable": service_ok, "target_matches": target_matches, "diagnostic_tag_budget_ok": tag_budget_ok, + "service_name_length": len(expected_service) if target_matches else None, "failure_subcategory": "combined_traffic_tag_service_name_length" if combined_tag_name_error else "none", "combined_traffic_tag_service_name_length_error_observed": combined_tag_name_error, "service_ready": _ready(service), diff --git a/tests/test_cached_stage_failure_diagnostic.py b/tests/test_cached_stage_failure_diagnostic.py index 4a6de0a..3d7dcde 100644 --- a/tests/test_cached_stage_failure_diagnostic.py +++ b/tests/test_cached_stage_failure_diagnostic.py @@ -89,6 +89,7 @@ def test_summary_exposes_only_closed_statuses_counts_and_categories(): "service_readable": True, "target_matches": True, "diagnostic_tag_budget_ok": True, + "service_name_length": len(PRIVATE_SERVICE), "failure_subcategory": "none", "combined_traffic_tag_service_name_length_error_observed": False, "service_ready": True, @@ -150,7 +151,7 @@ def test_summary_exposes_only_closed_statuses_counts_and_categories(): ( "traffic[].tag: traffic tag [TAG] and service name [SERVICE] together are too long. " "Combined traffic tag and service name cannot exceed 46 characters.", - ["invalid_name"], + ["invalid_name", "traffic_tag_length"], ), ("opaque upstream diagnostic", ["unknown"]), ], @@ -159,6 +160,18 @@ def test_error_text_is_reduced_to_fixed_reason(message, expected): assert diagnostic._classify([message]) == expected +@pytest.mark.parametrize(("message", "reason"), [ + ("Traffic tag [PRIVATE_TAG] should be at most one character long", "traffic_tag_length"), + ("Traffic tag [PRIVATE_TAG] must have a valid DNS format", "traffic_tag_format"), + ("Traffic tag [PRIVATE_TAG] is reserved", "traffic_tag_conflict"), + ("Traffic tags are not supported when the URL is disabled", "traffic_tag_url_disabled"), +]) +def test_traffic_tag_rejections_remain_closed(message, reason): + summary = diagnostic._classify([message]) + assert summary == [reason] + assert "PRIVATE_TAG" not in json.dumps(summary) + + def test_missing_logging_permission_is_reported_without_guessing_failure_category(): service, revisions, policy, jobs, audit = _evidence("permission denied") summary = diagnostic.summarize( @@ -199,7 +212,7 @@ def test_combined_traffic_tag_length_is_closed_subcategory_and_budget_stays_priv audit_entries=audit, audit_status="ok", ) - assert summary["failure_categories"] == ["invalid_name"] + assert summary["failure_categories"] == ["invalid_name", "traffic_tag_length"] assert summary["failure_subcategory"] == "combined_traffic_tag_service_name_length" assert summary["combined_traffic_tag_service_name_length_error_observed"] is True assert summary["diagnostic_tag_budget_ok"] is True