149 lines
4.4 KiB
Python
149 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Decide whether AI observability deploy QA should run.
|
|
|
|
Input is a list of changed files via argv or stdin. The script prints matched
|
|
files and can write `needed=true|false` to a GitHub/Gitea Actions output file.
|
|
It exits 0 for both true and false so workflow control stays explicit.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
TRIGGER_PATTERNS = (
|
|
r"^templates/admin/.*",
|
|
r"^templates/ewoooc_base\.html$",
|
|
r"^templates/components/_ewoooc_shell\.html$",
|
|
r"^static/css/observability-system\.css$",
|
|
r"^web/static/css/observability-system\.css$",
|
|
r"^routes/admin_observability_routes\.py$",
|
|
r"^scripts/check_observability_.*",
|
|
r"^scripts/check_observability_suite\.sh$",
|
|
r"^scripts/observability_contract\.py$",
|
|
r"^scripts/quick_review\.sh$",
|
|
r"^scripts/sync_observability_css\.py$",
|
|
r"^docs/guides/observability_ui_governance\.md$",
|
|
r"^docs/guides/deployment_sop\.md$",
|
|
)
|
|
|
|
SELF_TEST_POSITIVE = (
|
|
"templates/admin/business_intel.html",
|
|
"templates/ewoooc_base.html",
|
|
"templates/components/_ewoooc_shell.html",
|
|
"static/css/observability-system.css",
|
|
"web/static/css/observability-system.css",
|
|
"routes/admin_observability_routes.py",
|
|
"scripts/check_observability_ui.py",
|
|
"scripts/check_observability_pages.py",
|
|
"scripts/check_observability_suite.sh",
|
|
"scripts/observability_contract.py",
|
|
"scripts/quick_review.sh",
|
|
"scripts/sync_observability_css.py",
|
|
"docs/guides/observability_ui_governance.md",
|
|
"docs/guides/deployment_sop.md",
|
|
)
|
|
|
|
SELF_TEST_NEGATIVE = (
|
|
"services/pricing_service.py",
|
|
"docs/memory/history_logs.md",
|
|
"README.md",
|
|
"migrations/099_example.sql",
|
|
)
|
|
|
|
|
|
def normalize_paths(raw_paths: list[str]) -> list[str]:
|
|
paths: list[str] = []
|
|
for raw in raw_paths:
|
|
for line in raw.splitlines():
|
|
path = line.strip()
|
|
if path:
|
|
paths.append(path)
|
|
return paths
|
|
|
|
|
|
def match_paths(paths: list[str]) -> list[str]:
|
|
regexes = [re.compile(pattern) for pattern in TRIGGER_PATTERNS]
|
|
return [path for path in paths if any(regex.search(path) for regex in regexes)]
|
|
|
|
|
|
def write_output(output_path: str | None, needed: bool) -> None:
|
|
if not output_path:
|
|
return
|
|
path = Path(output_path)
|
|
with path.open("a", encoding="utf-8") as handle:
|
|
handle.write(f"needed={'true' if needed else 'false'}\n")
|
|
|
|
|
|
def run_self_test() -> int:
|
|
positive_misses = [
|
|
path for path in SELF_TEST_POSITIVE
|
|
if path not in match_paths([path])
|
|
]
|
|
negative_false_positives = [
|
|
path for path in SELF_TEST_NEGATIVE
|
|
if path in match_paths([path])
|
|
]
|
|
|
|
if positive_misses or negative_false_positives:
|
|
print("observability_deploy_gate_self_test=FAIL")
|
|
if positive_misses:
|
|
print("positive_misses:")
|
|
for path in positive_misses:
|
|
print(f"- {path}")
|
|
if negative_false_positives:
|
|
print("negative_false_positives:")
|
|
for path in negative_false_positives:
|
|
print(f"- {path}")
|
|
return 1
|
|
|
|
print("observability_deploy_gate_self_test=PASS")
|
|
print(f"- positive_cases={len(SELF_TEST_POSITIVE)}")
|
|
print(f"- negative_cases={len(SELF_TEST_NEGATIVE)}")
|
|
return 0
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Check observability deploy QA trigger")
|
|
parser.add_argument("paths", nargs="*", help="Changed file paths")
|
|
parser.add_argument("--stdin", action="store_true", help="Read changed paths from stdin")
|
|
parser.add_argument("--self-test", action="store_true", help="Run built-in trigger tests")
|
|
parser.add_argument(
|
|
"--github-output",
|
|
default=os.environ.get("GITHUB_OUTPUT"),
|
|
help="Actions output file to append needed=true|false.",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if args.self_test:
|
|
return run_self_test()
|
|
|
|
raw_paths = list(args.paths)
|
|
if args.stdin:
|
|
import sys
|
|
|
|
raw_paths.append(sys.stdin.read())
|
|
|
|
paths = normalize_paths(raw_paths)
|
|
matches = match_paths(paths)
|
|
needed = bool(matches)
|
|
|
|
write_output(args.github_output, needed)
|
|
|
|
print(f"observability_qa_needed={'true' if needed else 'false'}")
|
|
if matches:
|
|
print("matched_files:")
|
|
for path in matches:
|
|
print(f"- {path}")
|
|
else:
|
|
print("matched_files: none")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|