From 1d2fc5807d1f93210cee9cc07bd677356d513ff6 Mon Sep 17 00:00:00 2001 From: Mohsen Azimi Date: Sat, 31 Oct 2026 19:46:47 +0100 Subject: [PATCH] Sync CI failure line contracts The control plane's public contract gained CiFailureLine, CiFailureAttribution, CiFailureUndiagnosed and ReviewOpenItems.ci_failures/ci_runs_undiagnosed (prooflinedev/proofline#5560). openapi.public.json is copied byte for byte and the client is regenerated with this repository's own generator. Co-Authored-By: Claude Opus 5.5 --- openapi.public.json | 122 +++++++++ proofline/_generated/models/__init__.py | 4 + .../models/ci_failure_attribution.py | 12 + .../_generated/models/ci_failure_line.py | 251 ++++++++++++++++++ .../_generated/models/review_open_items.py | 54 ++++ 5 files changed, 443 insertions(+) create mode 100644 proofline/_generated/models/ci_failure_attribution.py create mode 100644 proofline/_generated/models/ci_failure_line.py diff --git a/openapi.public.json b/openapi.public.json index de01a8e..2641819 100644 --- a/openapi.public.json +++ b/openapi.public.json @@ -793,6 +793,103 @@ } ] }, + "CiFailureAttribution": { + "description": "What the control plane records as a failure's cause.", + "enum": [ + "CAUSED_BY_CHANGE", + "PRE_EXISTING_ON_BASE", + "FLAKY_OR_INFRASTRUCTURE", + "ENVIRONMENT_STATE", + "UNKNOWN" + ], + "type": "string" + }, + "CiFailureLine": { + "description": "One failed CI job as the Check states it.", + "properties": { + "attribution": { + "allOf": [ + { + "$ref": "#/components/schemas/CiFailureAttribution" + } + ], + "description": "The recorded cause: `CAUSED_BY_CHANGE`, `PRE_EXISTING_ON_BASE`,\n `FLAKY_OR_INFRASTRUCTURE`, `ENVIRONMENT_STATE` or `UNKNOWN`. `UNKNOWN`\n means the failure was not diagnosed, and `undiagnosed` says why." + }, + "failed_step": { + "description": "The first failed step's name, admitted the same way. Null when no\n step concluded as failed.", + "type": [ + "string", + "null" + ] + }, + "job_name": { + "description": "The job's name as the diagnosis admitted it: masked, or ``.\n A protected value of the observing review reads as the withheld\n marker.", + "type": "string" + }, + "run_attempt": { + "description": "The GitHub Actions run attempt the job failed in, from 1. The line\n is the newest diagnosed attempt of its run.", + "format": "int64", + "type": "integer" + }, + "timed_out": { + "description": "Whether the job timed out rather than failed.", + "type": "boolean" + }, + "undiagnosed": { + "anyOf": [ + { + "$ref": "#/components/schemas/CiFailureUndiagnosed" + }, + { + "type": "null" + } + ], + "description": "Why the failure was not diagnosed. Set exactly when `attribution` is\n `UNKNOWN`." + } + }, + "required": [ + "job_name", + "timed_out", + "run_attempt", + "attribution" + ], + "type": "object" + }, + "CiFailureUndiagnosed": { + "description": "Why a failed job's attribution is `UNKNOWN`.", + "oneOf": [ + { + "const": "LOG_UNAVAILABLE", + "description": "GitHub returned no log for the job.", + "type": "string" + }, + { + "const": "LOG_TOO_LARGE", + "description": "The complete log exceeds the read bound.", + "type": "string" + }, + { + "const": "LOG_NOT_ADMITTED", + "description": "The log could not cross the credential mask or protected-value\n admission. The console names which.", + "type": "string" + }, + { + "const": "DECISION_UNAVAILABLE", + "description": "The attribution decision fell back: Jev was unavailable or refused.", + "type": "string" + }, + { + "const": "SHADOW", + "description": "The attribution decision runs in shadow mode, so its answer is not\n applied.", + "type": "string" + }, + { + "const": "UNDETERMINED", + "description": "The decision applied no cause.", + "type": "string" + } + ] + }, "CodeAssessmentStatus": { "description": "A code assessment's recorded execution status. It is never a verdict on\n the change: only `COMPLETED` says the assessment covered what it read.", "oneOf": [ @@ -4885,6 +4982,31 @@ "minimum": 0, "type": "integer" }, + "ci_failures": { + "description": "Failed CI jobs diagnosed on the reviewed head, the newest diagnosed\n attempt of each run, in diagnosis order. The Check states each one;\n none is a Proofline open item. Absent when no job was diagnosed.", + "items": { + "$ref": "#/components/schemas/CiFailureLine" + }, + "type": "array" + }, + "ci_failures_omitted": { + "description": "Diagnosed failed CI jobs on the reviewed head beyond `ci_failures`.", + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "ci_runs_pending": { + "description": "Failed CI run attempts on the reviewed head whose diagnosis is queued\n or running, so none of their jobs has a line yet.", + "format": "uint64", + "minimum": 0, + "type": "integer" + }, + "ci_runs_unfinished": { + "description": "Failed CI run attempts on the reviewed head whose diagnosis failed or\n was cancelled, so none of their jobs has a line.", + "format": "uint64", + "minimum": 0, + "type": "integer" + }, "current_head_provider_updated_at": { "description": "GitHub's update time for the recorded pull request state that\n `current_head_sha` comes from. Null when none is recorded.", "format": "date-time", diff --git a/proofline/_generated/models/__init__.py b/proofline/_generated/models/__init__.py index 59ec348..7e61de4 100644 --- a/proofline/_generated/models/__init__.py +++ b/proofline/_generated/models/__init__.py @@ -19,6 +19,8 @@ from .change_diagram_outcome_type_0 import ChangeDiagramOutcomeType0 from .change_diagram_view import ChangeDiagramView from .change_summary import ChangeSummary +from .ci_failure_attribution import CiFailureAttribution +from .ci_failure_line import CiFailureLine from .code_assessment_status_type_0 import CodeAssessmentStatusType0 from .comment_address_evaluation_view import CommentAddressEvaluationView from .comment_address_outcome import CommentAddressOutcome @@ -200,6 +202,8 @@ "ChangeDiagramOutcomeType0", "ChangeDiagramView", "ChangeSummary", + "CiFailureAttribution", + "CiFailureLine", "CodeAssessmentStatusType0", "CommentAddressEvaluationView", "CommentAddressOutcome", diff --git a/proofline/_generated/models/ci_failure_attribution.py b/proofline/_generated/models/ci_failure_attribution.py new file mode 100644 index 0000000..5905e67 --- /dev/null +++ b/proofline/_generated/models/ci_failure_attribution.py @@ -0,0 +1,12 @@ +from enum import StrEnum + + +class CiFailureAttribution(StrEnum): + CAUSED_BY_CHANGE = "CAUSED_BY_CHANGE" + ENVIRONMENT_STATE = "ENVIRONMENT_STATE" + FLAKY_OR_INFRASTRUCTURE = "FLAKY_OR_INFRASTRUCTURE" + PRE_EXISTING_ON_BASE = "PRE_EXISTING_ON_BASE" + UNKNOWN = "UNKNOWN" + + def __str__(self) -> str: + return str(self.value) diff --git a/proofline/_generated/models/ci_failure_line.py b/proofline/_generated/models/ci_failure_line.py new file mode 100644 index 0000000..0e7431c --- /dev/null +++ b/proofline/_generated/models/ci_failure_line.py @@ -0,0 +1,251 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Literal, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..models.ci_failure_attribution import CiFailureAttribution +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CiFailureLine") + + +@_attrs_define +class CiFailureLine: + """One failed CI job as the Check states it. + + Attributes: + attribution (CiFailureAttribution): What the control plane records as a failure's cause. + job_name (str): The job's name as the diagnosis admitted it: masked, or ``. + A protected value of the observing review reads as the withheld + marker. + run_attempt (int): The GitHub Actions run attempt the job failed in, from 1. The line + is the newest diagnosed attempt of its run. + timed_out (bool): Whether the job timed out rather than failed. + failed_step (None | str | Unset): The first failed step's name, admitted the same way. Null when no + step concluded as failed. + undiagnosed (Literal['DECISION_UNAVAILABLE'] | Literal['LOG_NOT_ADMITTED'] | Literal['LOG_TOO_LARGE'] | + Literal['LOG_UNAVAILABLE'] | Literal['SHADOW'] | Literal['UNDETERMINED'] | None | Unset): Why the failure was + not diagnosed. Set exactly when `attribution` is + `UNKNOWN`. + """ + + attribution: CiFailureAttribution + job_name: str + run_attempt: int + timed_out: bool + failed_step: None | str | Unset = UNSET + undiagnosed: ( + Literal["DECISION_UNAVAILABLE"] + | Literal["LOG_NOT_ADMITTED"] + | Literal["LOG_TOO_LARGE"] + | Literal["LOG_UNAVAILABLE"] + | Literal["SHADOW"] + | Literal["UNDETERMINED"] + | None + | Unset + ) = UNSET + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + attribution = self.attribution.value + + job_name = self.job_name + + run_attempt = self.run_attempt + + timed_out = self.timed_out + + failed_step: None | str | Unset + if isinstance(self.failed_step, Unset): + failed_step = UNSET + else: + failed_step = self.failed_step + + undiagnosed: ( + Literal["DECISION_UNAVAILABLE"] + | Literal["LOG_NOT_ADMITTED"] + | Literal["LOG_TOO_LARGE"] + | Literal["LOG_UNAVAILABLE"] + | Literal["SHADOW"] + | Literal["UNDETERMINED"] + | None + | Unset + ) + if isinstance(self.undiagnosed, Unset): + undiagnosed = UNSET + else: + undiagnosed = self.undiagnosed + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "attribution": attribution, + "job_name": job_name, + "run_attempt": run_attempt, + "timed_out": timed_out, + } + ) + if failed_step is not UNSET: + field_dict["failed_step"] = failed_step + if undiagnosed is not UNSET: + field_dict["undiagnosed"] = undiagnosed + + return field_dict + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + attribution = CiFailureAttribution(d.pop("attribution")) + + job_name = d.pop("job_name") + + run_attempt = d.pop("run_attempt") + + timed_out = d.pop("timed_out") + + def _parse_failed_step(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + failed_step = _parse_failed_step(d.pop("failed_step", UNSET)) + + def _parse_undiagnosed( + data: object, + ) -> ( + Literal["DECISION_UNAVAILABLE"] + | Literal["LOG_NOT_ADMITTED"] + | Literal["LOG_TOO_LARGE"] + | Literal["LOG_UNAVAILABLE"] + | Literal["SHADOW"] + | Literal["UNDETERMINED"] + | None + | Unset + ): + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not (type(data) is str and data == "LOG_UNAVAILABLE"): + raise TypeError() + componentsschemas_ci_failure_undiagnosed_type_0 = cast( + Literal["LOG_UNAVAILABLE"], data + ) + if componentsschemas_ci_failure_undiagnosed_type_0 != "LOG_UNAVAILABLE": + raise ValueError( + f"/components/schemas/CiFailureUndiagnosed_type_0 must match const 'LOG_UNAVAILABLE', got '{componentsschemas_ci_failure_undiagnosed_type_0}'" + ) + return componentsschemas_ci_failure_undiagnosed_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not (type(data) is str and data == "LOG_TOO_LARGE"): + raise TypeError() + componentsschemas_ci_failure_undiagnosed_type_1 = cast( + Literal["LOG_TOO_LARGE"], data + ) + if componentsschemas_ci_failure_undiagnosed_type_1 != "LOG_TOO_LARGE": + raise ValueError( + f"/components/schemas/CiFailureUndiagnosed_type_1 must match const 'LOG_TOO_LARGE', got '{componentsschemas_ci_failure_undiagnosed_type_1}'" + ) + return componentsschemas_ci_failure_undiagnosed_type_1 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not (type(data) is str and data == "LOG_NOT_ADMITTED"): + raise TypeError() + componentsschemas_ci_failure_undiagnosed_type_2 = cast( + Literal["LOG_NOT_ADMITTED"], data + ) + if componentsschemas_ci_failure_undiagnosed_type_2 != "LOG_NOT_ADMITTED": + raise ValueError( + f"/components/schemas/CiFailureUndiagnosed_type_2 must match const 'LOG_NOT_ADMITTED', got '{componentsschemas_ci_failure_undiagnosed_type_2}'" + ) + return componentsschemas_ci_failure_undiagnosed_type_2 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not (type(data) is str and data == "DECISION_UNAVAILABLE"): + raise TypeError() + componentsschemas_ci_failure_undiagnosed_type_3 = cast( + Literal["DECISION_UNAVAILABLE"], data + ) + if componentsschemas_ci_failure_undiagnosed_type_3 != "DECISION_UNAVAILABLE": + raise ValueError( + f"/components/schemas/CiFailureUndiagnosed_type_3 must match const 'DECISION_UNAVAILABLE', got '{componentsschemas_ci_failure_undiagnosed_type_3}'" + ) + return componentsschemas_ci_failure_undiagnosed_type_3 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not (type(data) is str and data == "SHADOW"): + raise TypeError() + componentsschemas_ci_failure_undiagnosed_type_4 = cast(Literal["SHADOW"], data) + if componentsschemas_ci_failure_undiagnosed_type_4 != "SHADOW": + raise ValueError( + f"/components/schemas/CiFailureUndiagnosed_type_4 must match const 'SHADOW', got '{componentsschemas_ci_failure_undiagnosed_type_4}'" + ) + return componentsschemas_ci_failure_undiagnosed_type_4 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not (type(data) is str and data == "UNDETERMINED"): + raise TypeError() + componentsschemas_ci_failure_undiagnosed_type_5 = cast( + Literal["UNDETERMINED"], data + ) + if componentsschemas_ci_failure_undiagnosed_type_5 != "UNDETERMINED": + raise ValueError( + f"/components/schemas/CiFailureUndiagnosed_type_5 must match const 'UNDETERMINED', got '{componentsschemas_ci_failure_undiagnosed_type_5}'" + ) + return componentsschemas_ci_failure_undiagnosed_type_5 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast( + Literal["DECISION_UNAVAILABLE"] + | Literal["LOG_NOT_ADMITTED"] + | Literal["LOG_TOO_LARGE"] + | Literal["LOG_UNAVAILABLE"] + | Literal["SHADOW"] + | Literal["UNDETERMINED"] + | None + | Unset, + data, + ) + + undiagnosed = _parse_undiagnosed(d.pop("undiagnosed", UNSET)) + + ci_failure_line = cls( + attribution=attribution, + job_name=job_name, + run_attempt=run_attempt, + timed_out=timed_out, + failed_step=failed_step, + undiagnosed=undiagnosed, + ) + + ci_failure_line.additional_properties = d + return ci_failure_line + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/proofline/_generated/models/review_open_items.py b/proofline/_generated/models/review_open_items.py index e07ec8b..f01499c 100644 --- a/proofline/_generated/models/review_open_items.py +++ b/proofline/_generated/models/review_open_items.py @@ -12,6 +12,7 @@ from ..types import UNSET, Unset if TYPE_CHECKING: + from ..models.ci_failure_line import CiFailureLine from ..models.earlier_open_finding import EarlierOpenFinding from ..models.open_finding_counts import OpenFindingCounts from ..models.open_item_environment import OpenItemEnvironment @@ -42,6 +43,14 @@ class ReviewOpenItems: open_findings (OpenFindingCounts): review_status (OpenItemsReviewStatus): threads (OpenItemsThreadsType0 | OpenItemsThreadsType1): + ci_failures (list[CiFailureLine] | Unset): Failed CI jobs diagnosed on the reviewed head, the newest diagnosed + attempt of each run, in diagnosis order. The Check states each one; + none is a Proofline open item. Absent when no job was diagnosed. + ci_failures_omitted (int | Unset): Diagnosed failed CI jobs on the reviewed head beyond `ci_failures`. + ci_runs_pending (int | Unset): Failed CI run attempts on the reviewed head whose diagnosis is queued + or running, so none of their jobs has a line yet. + ci_runs_unfinished (int | Unset): Failed CI run attempts on the reviewed head whose diagnosis failed or + was cancelled, so none of their jobs has a line. current_head_provider_updated_at (datetime.datetime | None | Unset): GitHub's update time for the recorded pull request state that `current_head_sha` comes from. Null when none is recorded. @@ -67,6 +76,10 @@ class ReviewOpenItems: open_findings: OpenFindingCounts review_status: OpenItemsReviewStatus threads: OpenItemsThreadsType0 | OpenItemsThreadsType1 + ci_failures: list[CiFailureLine] | Unset = UNSET + ci_failures_omitted: int | Unset = UNSET + ci_runs_pending: int | Unset = UNSET + ci_runs_unfinished: int | Unset = UNSET current_head_provider_updated_at: datetime.datetime | None | Unset = UNSET current_head_sha: None | str | Unset = UNSET earlier_findings: list[EarlierOpenFinding] | Unset = UNSET @@ -104,6 +117,19 @@ def to_dict(self) -> dict[str, Any]: else: threads = self.threads.to_dict() + ci_failures: list[dict[str, Any]] | Unset = UNSET + if not isinstance(self.ci_failures, Unset): + ci_failures = [] + for ci_failures_item_data in self.ci_failures: + ci_failures_item = ci_failures_item_data.to_dict() + ci_failures.append(ci_failures_item) + + ci_failures_omitted = self.ci_failures_omitted + + ci_runs_pending = self.ci_runs_pending + + ci_runs_unfinished = self.ci_runs_unfinished + current_head_provider_updated_at: None | str | Unset if isinstance(self.current_head_provider_updated_at, Unset): current_head_provider_updated_at = UNSET @@ -153,6 +179,14 @@ def to_dict(self) -> dict[str, Any]: "threads": threads, } ) + if ci_failures is not UNSET: + field_dict["ci_failures"] = ci_failures + if ci_failures_omitted is not UNSET: + field_dict["ci_failures_omitted"] = ci_failures_omitted + if ci_runs_pending is not UNSET: + field_dict["ci_runs_pending"] = ci_runs_pending + if ci_runs_unfinished is not UNSET: + field_dict["ci_runs_unfinished"] = ci_runs_unfinished if current_head_provider_updated_at is not UNSET: field_dict["current_head_provider_updated_at"] = current_head_provider_updated_at if current_head_sha is not UNSET: @@ -168,6 +202,7 @@ def to_dict(self) -> dict[str, Any]: @classmethod def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.ci_failure_line import CiFailureLine # noqa: PLC0415 from ..models.earlier_open_finding import EarlierOpenFinding # noqa: PLC0415 from ..models.open_finding_counts import OpenFindingCounts # noqa: PLC0415 from ..models.open_item_environment import OpenItemEnvironment # noqa: PLC0415 @@ -215,6 +250,21 @@ def _parse_threads(data: object) -> OpenItemsThreadsType0 | OpenItemsThreadsType threads = _parse_threads(d.pop("threads")) + _ci_failures = d.pop("ci_failures", UNSET) + ci_failures: list[CiFailureLine] | Unset = UNSET + if _ci_failures is not UNSET: + ci_failures = [] + for ci_failures_item_data in _ci_failures: + ci_failures_item = CiFailureLine.from_dict(ci_failures_item_data) + + ci_failures.append(ci_failures_item) + + ci_failures_omitted = d.pop("ci_failures_omitted", UNSET) + + ci_runs_pending = d.pop("ci_runs_pending", UNSET) + + ci_runs_unfinished = d.pop("ci_runs_unfinished", UNSET) + def _parse_current_head_provider_updated_at( data: object, ) -> datetime.datetime | None | Unset: @@ -285,6 +335,10 @@ def _parse_reviewed_head_sha(data: object) -> None | str | Unset: open_findings=open_findings, review_status=review_status, threads=threads, + ci_failures=ci_failures, + ci_failures_omitted=ci_failures_omitted, + ci_runs_pending=ci_runs_pending, + ci_runs_unfinished=ci_runs_unfinished, current_head_provider_updated_at=current_head_provider_updated_at, current_head_sha=current_head_sha, earlier_findings=earlier_findings,