| #!/usr/bin/env python3 |
| """Validate a doris-repo-review PASS comment for the current PR head.""" |
| |
| from __future__ import annotations |
| |
| import argparse |
| import datetime as dt |
| import re |
| import sys |
| from pathlib import Path |
| |
| |
| BEGIN_MARKER = "<!-- doris-repo-review:v1:begin -->" |
| END_MARKER = "<!-- doris-repo-review:v1:end -->" |
| SCHEMA = "doris-repo-review/v1" |
| MAX_BASE_LAG = dt.timedelta(hours=48) |
| |
| ALLOWED_EFFORTS_BY_MODEL = { |
| "claude-opus-5": frozenset({"xhigh", "max"}), |
| "claude-opus-5[1m]": frozenset({"xhigh", "max"}), |
| "claude-fable-5": frozenset({"xhigh", "max"}), |
| "claude-fable-5[1m]": frozenset({"xhigh", "max"}), |
| "claude-fable-5-1": frozenset({"xhigh", "max"}), |
| "claude-fable-5-1[1m]": frozenset({"xhigh", "max"}), |
| "gpt-5.6-sol": frozenset({"xhigh", "max", "ultra"}), |
| "gpt-6-astra": frozenset({"xhigh", "max", "ultra"}), |
| } |
| ALLOWED_AUTHOR_PERMISSIONS = frozenset({"write", "admin"}) |
| EXPECTED_FIELDS = ( |
| "schema", |
| "status", |
| "pr", |
| "commit", |
| "base", |
| "reviewed_at", |
| "reviewer", |
| "model", |
| "effort", |
| "findings", |
| "rounds", |
| "converged", |
| ) |
| FINDINGS_RE = re.compile( |
| r"\{blocker: (\d+), major: (\d+), minor: (\d+), nit: (\d+)\}" |
| ) |
| SHA_RE = re.compile(r"[0-9a-fA-F]{40}") |
| |
| |
| class ValidationError(ValueError): |
| """The comment is not eligible to satisfy the code-review check.""" |
| |
| |
| def parse_comment(comment: str) -> dict[str, object]: |
| if comment.count(BEGIN_MARKER) != 1 or comment.count(END_MARKER) != 1: |
| raise ValidationError("expected exactly one v1 marker pair") |
| if comment.index(BEGIN_MARKER) > comment.index(END_MARKER): |
| raise ValidationError("v1 markers are out of order") |
| |
| marked_body = comment.split(BEGIN_MARKER, 1)[1].split(END_MARKER, 1)[0] |
| yaml_blocks = re.findall(r"```yaml\n(.*?)\n```", marked_body, flags=re.DOTALL) |
| if len(yaml_blocks) != 1: |
| raise ValidationError("expected exactly one fenced yaml block") |
| |
| fields: dict[str, object] = {} |
| field_order: list[str] = [] |
| for line in yaml_blocks[0].splitlines(): |
| if ":" not in line: |
| raise ValidationError(f"malformed yaml field: {line!r}") |
| key, value = line.split(":", 1) |
| key = key.strip() |
| value = value.strip() |
| if key in fields: |
| raise ValidationError(f"duplicate field: {key}") |
| field_order.append(key) |
| fields[key] = value |
| |
| if tuple(field_order) != EXPECTED_FIELDS: |
| raise ValidationError("comment fields do not match the v1 schema") |
| |
| findings_match = FINDINGS_RE.fullmatch(str(fields["findings"])) |
| if findings_match is None: |
| raise ValidationError("findings must use the v1 inline-map format") |
| fields["findings"] = tuple(int(value) for value in findings_match.groups()) |
| |
| try: |
| fields["rounds"] = int(str(fields["rounds"])) |
| except ValueError as exc: |
| raise ValidationError("rounds must be an integer") from exc |
| |
| return fields |
| |
| |
| def validate_comment( |
| comment: str, |
| *, |
| repository: str, |
| pr_number: int, |
| head_sha: str, |
| live_base_sha: str, |
| base_compare_status: str, |
| reviewed_base_committed_at: str | None, |
| live_base_committed_at: str | None, |
| comment_author: str, |
| comment_author_permission: str, |
| ) -> dict[str, object]: |
| fields = parse_comment(comment) |
| |
| if fields["schema"] != SCHEMA: |
| raise ValidationError(f"unsupported schema: {fields['schema']}") |
| if fields["status"] != "PASS": |
| raise ValidationError("status is not PASS") |
| if str(fields["pr"]).casefold() != f"{repository}#{pr_number}".casefold(): |
| raise ValidationError("comment targets a different pull request") |
| |
| reviewed_commit = str(fields["commit"]) |
| require_full_sha("commit", reviewed_commit) |
| if reviewed_commit.casefold() != head_sha.casefold(): |
| raise ValidationError("reviewed commit does not match the current PR head") |
| reviewed_base_sha = str(fields["base"]) |
| require_full_sha("base", reviewed_base_sha) |
| require_full_sha("live base", live_base_sha) |
| |
| same_base = reviewed_base_sha.casefold() == live_base_sha.casefold() |
| if same_base: |
| if base_compare_status != "identical": |
| raise ValidationError("equal base SHAs must have compare status identical") |
| else: |
| if base_compare_status != "ahead": |
| raise ValidationError("reviewed base is not an ancestor of the current PR base") |
| reviewed_base_time = parse_timestamp( |
| "reviewed base commit time", reviewed_base_committed_at |
| ) |
| live_base_time = parse_timestamp("current base commit time", live_base_committed_at) |
| base_lag = live_base_time.astimezone(dt.timezone.utc) - reviewed_base_time.astimezone( |
| dt.timezone.utc |
| ) |
| if base_lag < dt.timedelta(0): |
| raise ValidationError("reviewed base commit time is after the current base commit time") |
| if base_lag > MAX_BASE_LAG: |
| raise ValidationError("reviewed base is more than 48 hours behind the current PR base") |
| |
| if str(fields["reviewer"]).casefold() != comment_author.casefold(): |
| raise ValidationError("reviewer does not match the GitHub comment author") |
| if comment_author_permission not in ALLOWED_AUTHOR_PERMISSIONS: |
| raise ValidationError("comment author does not have write permission") |
| allowed_efforts = ALLOWED_EFFORTS_BY_MODEL.get(str(fields["model"])) |
| if allowed_efforts is None: |
| raise ValidationError(f"model is not allowed: {fields['model']}") |
| if fields["effort"] not in allowed_efforts: |
| raise ValidationError( |
| f"effort {fields['effort']} is not allowed for model {fields['model']}" |
| ) |
| |
| blocker, major, _minor, _nit = fields["findings"] |
| if blocker != 0 or major != 0: |
| raise ValidationError("Blocker and Major findings must both be zero") |
| if fields["converged"] != "true": |
| raise ValidationError("review did not converge") |
| if not 1 <= int(fields["rounds"]) <= 3: |
| raise ValidationError("rounds must be between 1 and 3") |
| |
| parse_timestamp("reviewed_at", str(fields["reviewed_at"])) |
| |
| return fields |
| |
| |
| def parse_timestamp(name: str, value: str | None) -> dt.datetime: |
| if value is None: |
| raise ValidationError(f"{name} is required") |
| try: |
| timestamp = dt.datetime.fromisoformat(value) |
| except ValueError as exc: |
| raise ValidationError(f"{name} is not a valid ISO-8601 timestamp") from exc |
| if timestamp.tzinfo is None or timestamp.utcoffset() is None: |
| raise ValidationError(f"{name} must include a timezone offset") |
| return timestamp |
| |
| |
| def require_full_sha(name: str, value: str) -> None: |
| if SHA_RE.fullmatch(value) is None: |
| raise ValidationError(f"{name} is not a full SHA") |
| |
| |
| def main() -> int: |
| parser = argparse.ArgumentParser() |
| subparsers = parser.add_subparsers(dest="command", required=True) |
| |
| extract_parser = subparsers.add_parser("extract-base") |
| extract_parser.add_argument("--comment-file", type=Path, required=True) |
| |
| validate_parser = subparsers.add_parser("validate") |
| validate_parser.add_argument("--comment-file", type=Path, required=True) |
| validate_parser.add_argument("--repository", required=True) |
| validate_parser.add_argument("--pr-number", type=int, required=True) |
| validate_parser.add_argument("--head-sha", required=True) |
| validate_parser.add_argument("--live-base-sha", required=True) |
| validate_parser.add_argument("--base-compare-status", required=True) |
| validate_parser.add_argument("--reviewed-base-committed-at") |
| validate_parser.add_argument("--live-base-committed-at") |
| validate_parser.add_argument("--comment-author", required=True) |
| validate_parser.add_argument("--comment-author-permission", required=True) |
| args = parser.parse_args() |
| |
| try: |
| comment = args.comment_file.read_text(encoding="utf-8") |
| if args.command == "extract-base": |
| fields = parse_comment(comment) |
| if fields["schema"] != SCHEMA: |
| raise ValidationError(f"unsupported schema: {fields['schema']}") |
| reviewed_base_sha = str(fields["base"]) |
| require_full_sha("base", reviewed_base_sha) |
| print(reviewed_base_sha) |
| return 0 |
| fields = validate_comment( |
| comment, |
| repository=args.repository, |
| pr_number=args.pr_number, |
| head_sha=args.head_sha, |
| live_base_sha=args.live_base_sha, |
| base_compare_status=args.base_compare_status, |
| reviewed_base_committed_at=args.reviewed_base_committed_at, |
| live_base_committed_at=args.live_base_committed_at, |
| comment_author=args.comment_author, |
| comment_author_permission=args.comment_author_permission, |
| ) |
| except (OSError, ValidationError, ValueError) as exc: |
| print(f"INVALID: {exc}", file=sys.stderr) |
| return 1 |
| |
| print( |
| "VALID: local pipeline review passed " |
| f"with {fields['model']} at effort {fields['effort']} for {fields['commit']}" |
| ) |
| return 0 |
| |
| |
| if __name__ == "__main__": |
| raise SystemExit(main()) |