Merge checks: statuses and check runs on every commit
Conflict: Op::ALL is 257 (the gateway's integration update and the twelve checks operations). The docs' OpenAPI copy is regenerated.
| 1 | + | //! Checks over REST and MCP: statuses on commits, and check runs and | |
| 2 | + | //! check suites, in GitHub's shapes so that existing integrations and | |
| 3 | + | //! actions report to g1t unchanged. | |
| 4 | + | //! | |
| 5 | + | //! The work service keeps them and decides who may read and report them | |
| 6 | + | //! (`g1t_contracts::checks`). A g1t Actions job is a check run here too, | |
| 7 | + | //! and its workflow run the suite. | |
| 8 | + | ||
| 9 | + | use g1t_contracts::checks::*; | |
| 10 | + | use g1t_contracts::{FailureCode, Outcome, Viewer}; | |
| 11 | + | use serde::Serialize; | |
| 12 | + | use serde::de::DeserializeOwned; | |
| 13 | + | use serde_json::{Map, Value, json}; | |
| 14 | + | use worker::Result; | |
| 15 | + | ||
| 16 | + | use crate::operations::Services; | |
| 17 | + | ||
| 18 | + | /// One operation on checks. | |
| 19 | + | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | |
| 20 | + | pub enum ChecksOp { | |
| 21 | + | CreateCommitStatus, | |
| 22 | + | ListCommitStatuses, | |
| 23 | + | GetCombinedStatus, | |
| 24 | + | CreateCheckRun, | |
| 25 | + | UpdateCheckRun, | |
| 26 | + | GetCheckRun, | |
| 27 | + | ListCheckRunAnnotations, | |
| 28 | + | RerequestCheckRun, | |
| 29 | + | ListCheckRunsForRef, | |
| 30 | + | ListCheckSuitesForRef, | |
| 31 | + | GetCheckSuite, | |
| 32 | + | RerequestCheckSuite, | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | impl ChecksOp { | |
| 36 | + | /// Every one: `Op::ALL` lists each as `Op::Checks(…)`, which a test | |
| 37 | + | /// checks against this. | |
| 38 | + | #[cfg(test)] | |
| 39 | + | pub const ALL: [ChecksOp; 12] = [ | |
| 40 | + | ChecksOp::CreateCommitStatus, | |
| 41 | + | ChecksOp::ListCommitStatuses, | |
| 42 | + | ChecksOp::GetCombinedStatus, | |
| 43 | + | ChecksOp::CreateCheckRun, | |
| 44 | + | ChecksOp::UpdateCheckRun, | |
| 45 | + | ChecksOp::GetCheckRun, | |
| 46 | + | ChecksOp::ListCheckRunAnnotations, | |
| 47 | + | ChecksOp::RerequestCheckRun, | |
| 48 | + | ChecksOp::ListCheckRunsForRef, | |
| 49 | + | ChecksOp::ListCheckSuitesForRef, | |
| 50 | + | ChecksOp::GetCheckSuite, | |
| 51 | + | ChecksOp::RerequestCheckSuite, | |
| 52 | + | ]; | |
| 53 | + | ||
| 54 | + | pub fn name(self) -> &'static str { | |
| 55 | + | match self { | |
| 56 | + | ChecksOp::CreateCommitStatus => "create_commit_status", | |
| 57 | + | ChecksOp::ListCommitStatuses => "list_commit_statuses", | |
| 58 | + | ChecksOp::GetCombinedStatus => "get_combined_status", | |
| 59 | + | ChecksOp::CreateCheckRun => "create_check_run", | |
| 60 | + | ChecksOp::UpdateCheckRun => "update_check_run", | |
| 61 | + | ChecksOp::GetCheckRun => "get_check_run", | |
| 62 | + | ChecksOp::ListCheckRunAnnotations => "list_check_run_annotations", | |
| 63 | + | ChecksOp::RerequestCheckRun => "rerequest_check_run", | |
| 64 | + | ChecksOp::ListCheckRunsForRef => "list_check_runs_for_ref", | |
| 65 | + | ChecksOp::ListCheckSuitesForRef => "list_check_suites_for_ref", | |
| 66 | + | ChecksOp::GetCheckSuite => "get_check_suite", | |
| 67 | + | ChecksOp::RerequestCheckSuite => "rerequest_check_suite", | |
| 68 | + | } | |
| 69 | + | } | |
| 70 | + | ||
| 71 | + | /// For the API reference. | |
| 72 | + | pub fn title(self) -> &'static str { | |
| 73 | + | match self { | |
| 74 | + | ChecksOp::CreateCommitStatus => "Create a commit status", | |
| 75 | + | ChecksOp::ListCommitStatuses => "List a commit's statuses", | |
| 76 | + | ChecksOp::GetCombinedStatus => "Get a commit's combined status", | |
| 77 | + | ChecksOp::CreateCheckRun => "Create a check run", | |
| 78 | + | ChecksOp::UpdateCheckRun => "Update a check run", | |
| 79 | + | ChecksOp::GetCheckRun => "Get a check run", | |
| 80 | + | ChecksOp::ListCheckRunAnnotations => "List a check run's annotations", | |
| 81 | + | ChecksOp::RerequestCheckRun => "Rerequest a check run", | |
| 82 | + | ChecksOp::ListCheckRunsForRef => "List a commit's check runs", | |
| 83 | + | ChecksOp::ListCheckSuitesForRef => "List a commit's check suites", | |
| 84 | + | ChecksOp::GetCheckSuite => "Get a check suite", | |
| 85 | + | ChecksOp::RerequestCheckSuite => "Rerequest a check suite", | |
| 86 | + | } | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | pub fn description(self) -> &'static str { | |
| 90 | + | match self { | |
| 91 | + | ChecksOp::CreateCommitStatus => "Set a status on a commit: state (pending, success, failure or error), context (what reports it, such as ci/build; default by default), description (at most 140 characters) and target_url (where to see more). A context has one status per commit: setting it again replaces it. A status is a check: a required check or a ruleset's required status check of its context is met by it. Needs the Write role; publishes status.created.", | |
| 92 | + | ChecksOp::ListCommitStatuses => "List the statuses on a commit, named by its SHA, a branch or a tag: one per context, newest first. Check runs are listed by list_check_runs_for_ref instead.", | |
| 93 | + | ChecksOp::GetCombinedStatus => "A commit's statuses, one per context, and the state they add up to: failure if any failed or errored, pending if any is pending or there are none, success otherwise. Named by its SHA, a branch or a tag.", | |
| 94 | + | ChecksOp::CreateCheckRun => "Report a check run on a commit: name and head_sha are required; status (queued, in_progress or completed; queued by default), conclusion (success, failure, neutral, cancelled, skipped, timed_out or action_required; it makes the run completed), started_at and completed_at (RFC 3339; filled in when left out), details_url (your page for it), external_id (your id for it), output (title, summary and text in Markdown, and up to 50 annotations: path, start_line, end_line, start_column, end_column, annotation_level notice, warning or failure, message, title, raw_details) and actions (up to 3 buttons: label, description, identifier). app names who reports it, by default your token's name. Runs are grouped per reporter and commit into a check suite. A check run is a check: a required check of its name is met by it, a cancelled one failing. Needs the Write role; publishes check_run.created, and check_run.completed when it is created completed.", | |
| 95 | + | ChecksOp::UpdateCheckRun => "Change a check run reported through the API, by id (cr_…). Fields left out stay as they are; output annotations are added to the ones it has (at most 1000 in all); actions, when given, replace its buttons. Giving a conclusion completes it. Publishes check_run.completed when it completes. A g1t Actions job's check run is its workflow's and cannot be changed.", | |
| 96 | + | ChecksOp::GetCheckRun => "Get a check run by id: cr_… for one reported through the API, or a g1t Actions job's id (job_…), whose workflow run is its suite and whose workflow says its name, run and event in workflow.", | |
| 97 | + | ChecksOp::ListCheckRunAnnotations => "List a check run's annotations in the order they were reported: path, start_line, end_line, start_column, end_column, annotation_level (notice, warning or failure), message, title and raw_details.", | |
| 98 | + | ChecksOp::RerequestCheckRun => "Ask for a check run to run again. For one reported through the API, its reporter is sent check_run.rerequested; for a g1t Actions job, its workflow run runs again (which also needs workflows:write). Needs the Write role.", | |
| 99 | + | ChecksOp::ListCheckRunsForRef => "List a commit's check runs, named by its SHA, a branch or a tag: those reported through the API and each job of its g1t Actions workflow runs. filter latest (the default) gives each name's latest run and each workflow's latest run per event; all gives every one. Narrow with check_name, status and app (a reporter's slug; actions for g1t Actions).", | |
| 100 | + | ChecksOp::ListCheckSuitesForRef => "List a commit's check suites, named by its SHA, a branch or a tag: one per reporter that reported check runs on it through the API, and one per g1t Actions workflow run, with its status and conclusion worked out from its latest check runs. Narrow with app and check_name.", | |
| 101 | + | ChecksOp::GetCheckSuite => "Get a check suite by id: cs_… for a reporter's, or a g1t Actions workflow run's id (run_…).", | |
| 102 | + | ChecksOp::RerequestCheckSuite => "Ask for a check suite to run again: its reporter is sent check_suite.rerequested, or a g1t Actions workflow run runs again (which also needs workflows:write). Needs the Write role.", | |
| 103 | + | } | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | /// Whether it only reads, which anyone who can see the repository may. | |
| 107 | + | pub fn reads(self) -> bool { | |
| 108 | + | !matches!( | |
| 109 | + | self, | |
| 110 | + | ChecksOp::CreateCommitStatus | |
| 111 | + | | ChecksOp::CreateCheckRun | |
| 112 | + | | ChecksOp::UpdateCheckRun | |
| 113 | + | | ChecksOp::RerequestCheckRun | |
| 114 | + | | ChecksOp::RerequestCheckSuite | |
| 115 | + | ) | |
| 116 | + | } | |
| 117 | + | ||
| 118 | + | pub fn input(self) -> Value { | |
| 119 | + | let repo = || json!({ "type": "string", "description": "Repository as \"owner/name\", e.g. \"flagon-io/hello\"." }); | |
| 120 | + | let git_ref = || json!({ "type": "string", "description": "The commit: its SHA, a branch or a tag." }); | |
| 121 | + | let run_id = || json!({ "type": "string", "description": "The check run's id: cr_…, or a g1t Actions job's job_…" }); | |
| 122 | + | let suite_id = || json!({ "type": "string", "description": "The check suite's id: cs_…, or a g1t Actions run's run_…" }); | |
| 123 | + | let run_fields = |mut properties: Value, creating: bool| { | |
| 124 | + | properties["name"] = json!({ "type": "string", "description": "The check's name, at most 100 characters, such as lint or coverage." }); | |
| 125 | + | if creating { | |
| 126 | + | properties["head_sha"] = json!({ "type": "string", "description": "The commit's full SHA (or a branch or tag, read as the commit it points to now)." }); | |
| 127 | + | properties["app"] = json!({ "type": "string", "description": "Who reports it, shown with it and grouping its check suite: by default your token's name." }); | |
| 128 | + | } | |
| 129 | + | properties["status"] = json!({ "type": "string", "enum": STATUSES, "description": "Where it is: queued, in_progress or completed." }); | |
| 130 | + | properties["conclusion"] = json!({ "type": "string", "enum": CONCLUSIONS, "description": "How it came out; giving one completes it." }); | |
| 131 | + | properties["started_at"] = json!({ "type": "string", "description": "When it started, RFC 3339." }); | |
| 132 | + | properties["completed_at"] = json!({ "type": "string", "description": "When it completed, RFC 3339." }); | |
| 133 | + | properties["details_url"] = json!({ "type": "string", "description": "Your page for it, http or https." }); | |
| 134 | + | properties["external_id"] = json!({ "type": "string", "description": "Your id for it." }); | |
| 135 | + | properties["output"] = json!({ | |
| 136 | + | "type": "object", | |
| 137 | + | "description": "Its report: a title, a Markdown summary and text, and annotations on lines of files (at most 50 a request).", | |
| 138 | + | "properties": { | |
| 139 | + | "title": { "type": "string" }, | |
| 140 | + | "summary": { "type": "string" }, | |
| 141 | + | "text": { "type": "string" }, | |
| 142 | + | "annotations": { | |
| 143 | + | "type": "array", | |
| 144 | + | "items": { | |
| 145 | + | "type": "object", | |
| 146 | + | "properties": { | |
| 147 | + | "path": { "type": "string" }, | |
| 148 | + | "start_line": { "type": "integer" }, | |
| 149 | + | "end_line": { "type": "integer" }, | |
| 150 | + | "start_column": { "type": "integer" }, | |
| 151 | + | "end_column": { "type": "integer" }, | |
| 152 | + | "annotation_level": { "type": "string", "enum": ANNOTATION_LEVELS }, | |
| 153 | + | "message": { "type": "string" }, | |
| 154 | + | "title": { "type": "string" }, | |
| 155 | + | "raw_details": { "type": "string" }, | |
| 156 | + | }, | |
| 157 | + | "required": ["path", "start_line", "end_line", "annotation_level", "message"], | |
| 158 | + | }, | |
| 159 | + | }, | |
| 160 | + | }, | |
| 161 | + | }); | |
| 162 | + | properties["actions"] = json!({ | |
| 163 | + | "type": "array", | |
| 164 | + | "description": "Up to 3 buttons on its page. Pressing one sends you check_run.requested_action with its identifier.", | |
| 165 | + | "items": { | |
| 166 | + | "type": "object", | |
| 167 | + | "properties": { | |
| 168 | + | "label": { "type": "string", "description": "At most 20 characters." }, | |
| 169 | + | "description": { "type": "string", "description": "At most 40 characters." }, | |
| 170 | + | "identifier": { "type": "string", "description": "At most 20 characters." }, | |
| 171 | + | }, | |
| 172 | + | "required": ["label", "description", "identifier"], | |
| 173 | + | }, | |
| 174 | + | }); | |
| 175 | + | properties | |
| 176 | + | }; | |
| 177 | + | let (properties, required): (Value, &[&str]) = match self { | |
| 178 | + | ChecksOp::CreateCommitStatus => ( | |
| 179 | + | json!({ | |
| 180 | + | "repo": repo(), | |
| 181 | + | "sha": { "type": "string", "description": "The commit's full SHA." }, | |
| 182 | + | "state": { "type": "string", "enum": STATUS_STATES, "description": "pending, success, failure or error." }, | |
| 183 | + | "context": { "type": "string", "description": "What reports it, such as ci/build; default when left out." }, | |
| 184 | + | "description": { "type": "string", "description": "A short word on it, at most 140 characters." }, | |
| 185 | + | "target_url": { "type": "string", "description": "Where to see more, http or https." }, | |
| 186 | + | }), | |
| 187 | + | &["repo", "sha", "state"], | |
| 188 | + | ), | |
| 189 | + | ChecksOp::ListCommitStatuses | ChecksOp::GetCombinedStatus => (json!({ "repo": repo(), "ref": git_ref() }), &["repo", "ref"]), | |
| 190 | + | ChecksOp::CreateCheckRun => (run_fields(json!({ "repo": repo() }), true), &["repo", "name", "head_sha"]), | |
| 191 | + | ChecksOp::UpdateCheckRun => (run_fields(json!({ "repo": repo(), "id": run_id() }), false), &["repo", "id"]), | |
| 192 | + | ChecksOp::GetCheckRun | ChecksOp::ListCheckRunAnnotations | ChecksOp::RerequestCheckRun => { | |
| 193 | + | (json!({ "repo": repo(), "id": run_id() }), &["repo", "id"]) | |
| 194 | + | } | |
| 195 | + | ChecksOp::ListCheckRunsForRef => ( | |
| 196 | + | json!({ | |
| 197 | + | "repo": repo(), | |
| 198 | + | "ref": git_ref(), | |
| 199 | + | "check_name": { "type": "string", "description": "Only runs of this name." }, | |
| 200 | + | "status": { "type": "string", "enum": STATUSES, "description": "Only runs in this status." }, | |
| 201 | + | "app": { "type": "string", "description": "Only this reporter's runs, by slug: actions for g1t Actions." }, | |
| 202 | + | "filter": { "type": "string", "enum": ["latest", "all"], "description": "latest (the default) or all." }, | |
| 203 | + | }), | |
| 204 | + | &["repo", "ref"], | |
| 205 | + | ), | |
| 206 | + | ChecksOp::ListCheckSuitesForRef => ( | |
| 207 | + | json!({ | |
| 208 | + | "repo": repo(), | |
| 209 | + | "ref": git_ref(), | |
| 210 | + | "app": { "type": "string", "description": "Only this reporter's suites, by slug." }, | |
| 211 | + | "check_name": { "type": "string", "description": "Only suites with a run of this name." }, | |
| 212 | + | }), | |
| 213 | + | &["repo", "ref"], | |
| 214 | + | ), | |
| 215 | + | ChecksOp::GetCheckSuite | ChecksOp::RerequestCheckSuite => (json!({ "repo": repo(), "id": suite_id() }), &["repo", "id"]), | |
| 216 | + | }; | |
| 217 | + | json!({ "type": "object", "properties": properties, "required": required }) | |
| 218 | + | } | |
| 219 | + | } | |
| 220 | + | ||
| 221 | + | fn text(input: &Value, key: &str) -> Option<String> { | |
| 222 | + | match &input[key] { | |
| 223 | + | Value::String(text) => Some(text.trim().to_owned()).filter(|text| !text.is_empty()), | |
| 224 | + | _ => None, | |
| 225 | + | } | |
| 226 | + | } | |
| 227 | + | ||
| 228 | + | /// A key in `camelCase`, as the contracts read it: `start_line` is `startLine`. | |
| 229 | + | fn camel_key(key: &str) -> String { | |
| 230 | + | let mut out = String::with_capacity(key.len()); | |
| 231 | + | let mut upper = false; | |
| 232 | + | for c in key.chars() { | |
| 233 | + | if c == '_' { | |
| 234 | + | upper = true; | |
| 235 | + | } else if upper { | |
| 236 | + | out.extend(c.to_uppercase()); | |
| 237 | + | upper = false; | |
| 238 | + | } else { | |
| 239 | + | out.push(c); | |
| 240 | + | } | |
| 241 | + | } | |
| 242 | + | out | |
| 243 | + | } | |
| 244 | + | ||
| 245 | + | fn camel(value: &Value) -> Value { | |
| 246 | + | match value { | |
| 247 | + | Value::Object(fields) => Value::Object(fields.iter().map(|(key, value)| (camel_key(key), camel(value))).collect::<Map<_, _>>()), | |
| 248 | + | Value::Array(items) => Value::Array(items.iter().map(camel).collect()), | |
| 249 | + | other => other.clone(), | |
| 250 | + | } | |
| 251 | + | } | |
| 252 | + | ||
| 253 | + | /// A check run's fields from a request body. | |
| 254 | + | pub(crate) fn run_input(input: &Value) -> std::result::Result<CheckRunInput, String> { | |
| 255 | + | const FIELDS: [&str; 10] = | |
| 256 | + | ["name", "head_sha", "status", "conclusion", "started_at", "completed_at", "details_url", "external_id", "output", "actions"]; | |
| 257 | + | let mut fields = Map::new(); | |
| 258 | + | for key in FIELDS { | |
| 259 | + | if let Some(value) = input.get(key).filter(|value| !value.is_null()) { | |
| 260 | + | fields.insert(camel_key(key), camel(value)); | |
| 261 | + | } | |
| 262 | + | } | |
| 263 | + | serde_json::from_value(Value::Object(fields)).map_err(|error| format!("The check run could not be read: {error}")) | |
| 264 | + | } | |
| 265 | + | ||
| 266 | + | /// Pages on the site, which the work service names by path, as full | |
| 267 | + | /// addresses. | |
| 268 | + | fn absolute(value: Value, site: &str) -> Value { | |
| 269 | + | match value { | |
| 270 | + | Value::Object(fields) => Value::Object( | |
| 271 | + | fields | |
| 272 | + | .into_iter() | |
| 273 | + | .map(|(key, value)| { | |
| 274 | + | let value = match value { | |
| 275 | + | Value::String(path) if matches!(key.as_str(), "htmlUrl" | "detailsUrl" | "targetUrl") && path.starts_with('/') => { | |
| 276 | + | Value::String(format!("{site}{path}")) | |
| 277 | + | } | |
| 278 | + | other => absolute(other, site), | |
| 279 | + | }; | |
| 280 | + | (key, value) | |
| 281 | + | }) | |
| 282 | + | .collect(), | |
| 283 | + | ), | |
| 284 | + | Value::Array(items) => Value::Array(items.into_iter().map(|item| absolute(item, site)).collect()), | |
| 285 | + | other => other, | |
| 286 | + | } | |
| 287 | + | } | |
| 288 | + | ||
| 289 | + | async fn call<A: Serialize, T: DeserializeOwned + Serialize>(services: &Services, method: &str, args: &A) -> Result<Outcome<Value>> { | |
| 290 | + | let found: Outcome<T> = g1t_kit::call(&services.work, method, args).await?; | |
| 291 | + | Ok(match found { | |
| 292 | + | Outcome::Ok(value) => Outcome::Ok(absolute(serde_json::to_value(value)?, &services.addresses.site)), | |
| 293 | + | Outcome::Fail(failure) => Outcome::Fail(failure), | |
| 294 | + | }) | |
| 295 | + | } | |
| 296 | + | ||
| 297 | + | pub async fn run(op: ChecksOp, services: &Services, viewer: &Viewer, input: &Value) -> Result<Outcome<Value>> { | |
| 298 | + | let Some(repo) = crate::operations::repo_path(input) else { | |
| 299 | + | return Ok(Outcome::fail(FailureCode::Invalid, "Give the repository as \"owner/name\".")); | |
| 300 | + | }; | |
| 301 | + | let actor = || viewer.clone().unwrap_or_default(); | |
| 302 | + | let id = || text(input, "id").unwrap_or_default(); | |
| 303 | + | let git_ref = || text(input, "ref").unwrap_or_default(); | |
| 304 | + | match op { | |
| 305 | + | ChecksOp::CreateCommitStatus => { | |
| 306 | + | let args = CreateStatusArgs { | |
| 307 | + | actor: actor(), | |
| 308 | + | repo, | |
| 309 | + | sha: text(input, "sha").unwrap_or_default(), | |
| 310 | + | state: text(input, "state").unwrap_or_default(), | |
| 311 | + | context: text(input, "context"), | |
| 312 | + | description: text(input, "description"), | |
| 313 | + | target_url: text(input, "target_url"), | |
| 314 | + | }; | |
| 315 | + | call::<_, g1t_contracts::work::CommitStatus>(services, "create_commit_status", &args).await | |
| 316 | + | } | |
| 317 | + | ChecksOp::ListCommitStatuses => { | |
| 318 | + | call::<_, Vec<g1t_contracts::work::CommitStatus>>(services, "commit_statuses", &RefArgs { viewer: viewer.clone(), repo, git_ref: git_ref() }).await | |
| 319 | + | } | |
| 320 | + | ChecksOp::GetCombinedStatus => { | |
| 321 | + | call::<_, CombinedStatus>(services, "combined_status", &RefArgs { viewer: viewer.clone(), repo, git_ref: git_ref() }).await | |
| 322 | + | } | |
| 323 | + | ChecksOp::CreateCheckRun | ChecksOp::UpdateCheckRun => { | |
| 324 | + | let run = match run_input(input) { | |
| 325 | + | Ok(run) => run, | |
| 326 | + | Err(message) => return Ok(Outcome::fail(FailureCode::Invalid, message)), | |
| 327 | + | }; | |
| 328 | + | if op == ChecksOp::CreateCheckRun { | |
| 329 | + | let args = CreateCheckRunArgs { actor: actor(), repo, app: text(input, "app"), run }; | |
| 330 | + | call::<_, CommitCheckRun>(services, "create_check_run", &args).await | |
| 331 | + | } else { | |
| 332 | + | call::<_, CommitCheckRun>(services, "update_check_run", &UpdateCheckRunArgs { actor: actor(), repo, id: id(), run }).await | |
| 333 | + | } | |
| 334 | + | } | |
| 335 | + | ChecksOp::GetCheckRun => call::<_, CommitCheckRun>(services, "get_check_run", &CheckIdArgs { viewer: viewer.clone(), repo, id: id() }).await, | |
| 336 | + | ChecksOp::ListCheckRunAnnotations => { | |
| 337 | + | call::<_, Vec<CheckAnnotation>>(services, "check_run_annotations", &CheckIdArgs { viewer: viewer.clone(), repo, id: id() }).await | |
| 338 | + | } | |
| 339 | + | ChecksOp::GetCheckSuite => call::<_, CommitCheckSuite>(services, "get_check_suite", &CheckIdArgs { viewer: viewer.clone(), repo, id: id() }).await, | |
| 340 | + | ChecksOp::RerequestCheckRun | ChecksOp::RerequestCheckSuite => { | |
| 341 | + | let method = if op == ChecksOp::RerequestCheckRun { "rerequest_check_run" } else { "rerequest_check_suite" }; | |
| 342 | + | let done: Outcome<bool> = g1t_kit::call(&services.work, method, &RerequestArgs { actor: actor(), repo, id: id() }).await?; | |
| 343 | + | Ok(match done { | |
| 344 | + | Outcome::Ok(_) => Outcome::Ok(json!({ "rerequested": true })), | |
| 345 | + | Outcome::Fail(failure) => Outcome::Fail(failure), | |
| 346 | + | }) | |
| 347 | + | } | |
| 348 | + | ChecksOp::ListCheckRunsForRef => { | |
| 349 | + | let args = RefCheckRunsArgs { | |
| 350 | + | viewer: viewer.clone(), | |
| 351 | + | repo, | |
| 352 | + | git_ref: git_ref(), | |
| 353 | + | check_name: text(input, "check_name"), | |
| 354 | + | status: text(input, "status"), | |
| 355 | + | app: text(input, "app"), | |
| 356 | + | filter: text(input, "filter"), | |
| 357 | + | }; | |
| 358 | + | call::<_, CheckRunList>(services, "ref_check_runs", &args).await | |
| 359 | + | } | |
| 360 | + | ChecksOp::ListCheckSuitesForRef => { | |
| 361 | + | let args = RefCheckSuitesArgs { viewer: viewer.clone(), repo, git_ref: git_ref(), app: text(input, "app"), check_name: text(input, "check_name") }; | |
| 362 | + | call::<_, CheckSuiteList>(services, "ref_check_suites", &args).await | |
| 363 | + | } | |
| 364 | + | } | |
| 365 | + | } | |
| 366 | + | ||
| 367 | + | #[cfg(test)] | |
| 368 | + | mod tests { | |
| 369 | + | use super::*; | |
| 370 | + | ||
| 371 | + | #[test] | |
| 372 | + | fn a_body_in_snake_case_is_a_check_run() { | |
| 373 | + | let body = json!({ | |
| 374 | + | "repo": "acme/web", | |
| 375 | + | "name": "lint", | |
| 376 | + | "head_sha": "a".repeat(40), | |
| 377 | + | "status": "completed", | |
| 378 | + | "conclusion": "failure", | |
| 379 | + | "output": { | |
| 380 | + | "title": "2 problems", | |
| 381 | + | "summary": "**2** problems", | |
| 382 | + | "annotations": [{ "path": "src/a.rs", "start_line": 3, "end_line": 3, "annotation_level": "warning", "message": "unused" }] | |
| 383 | + | }, | |
| 384 | + | "actions": [{ "label": "Fix", "description": "Fix it", "identifier": "fix" }] | |
| 385 | + | }); | |
| 386 | + | let run = run_input(&body).unwrap(); | |
| 387 | + | assert_eq!(run.head_sha.as_deref(), Some("a".repeat(40).as_str())); | |
| 388 | + | let output = run.output.unwrap(); | |
| 389 | + | assert_eq!(output.annotations[0].start_line, 3); | |
| 390 | + | assert_eq!(output.annotations[0].annotation_level, "warning"); | |
| 391 | + | assert_eq!(run.actions.unwrap()[0].identifier, "fix"); | |
| 392 | + | assert!(run_input(&json!({ "output": { "annotations": "no" } })).is_err()); | |
| 393 | + | } | |
| 394 | + | ||
| 395 | + | #[test] | |
| 396 | + | fn pages_on_the_site_become_full_addresses() { | |
| 397 | + | let value = json!({ "checkRuns": [{ "htmlUrl": "/acme/web/checks/cr_1", "detailsUrl": "https://ci.example.com/1", "name": "/x" }] }); | |
| 398 | + | let out = absolute(value, "https://g1t.sh"); | |
| 399 | + | assert_eq!(out["checkRuns"][0]["htmlUrl"], "https://g1t.sh/acme/web/checks/cr_1"); | |
| 400 | + | assert_eq!(out["checkRuns"][0]["detailsUrl"], "https://ci.example.com/1"); | |
| 401 | + | assert_eq!(out["checkRuns"][0]["name"], "/x"); | |
| 402 | + | } | |
| 403 | + | ||
| 404 | + | #[test] | |
| 405 | + | fn each_operation_is_described_with_a_schema() { | |
| 406 | + | for op in ChecksOp::ALL { | |
| 407 | + | assert!(crate::operations::Op::ALL.contains(&crate::operations::Op::Checks(op)), "{}", op.name()); | |
| 408 | + | assert!(!op.title().is_empty() && op.description().len() > 40, "{}", op.name()); | |
| 409 | + | assert!(op.input()["required"].as_array().unwrap().contains(&json!("repo")), "{}", op.name()); | |
| 410 | + | assert_eq!(op.reads(), g1t_contracts::scopes::scope_for(op.name()).unwrap().level() == g1t_contracts::scopes::Level::Read, "{}", op.name()); | |
| 411 | + | } | |
| 412 | + | } | |
| 413 | + | } |
| 10 | 10 | mod audit; | |
| 11 | 11 | mod billing; | |
| 12 | 12 | mod blobs; | |
| 13 | + | mod checks; | |
| 13 | 14 | mod deployments; | |
| 14 | 15 | mod mcp; | |
| 15 | 16 | mod notifications; |
| 10 | 10 | use crate::about::AboutOp; | |
| 11 | 11 | use crate::deployments::DeploymentsOp; | |
| 12 | 12 | use crate::operations::Op; | |
| 13 | + | use crate::checks::ChecksOp; | |
| 13 | 14 | use crate::rules::RulesOp; | |
| 14 | 15 | use crate::security::SecurityOp; | |
| 15 | 16 | use crate::rest::{ROUTES, Route}; | |
| 255 | 256 | ], | |
| 256 | 257 | ), | |
| 257 | 258 | ( | |
| 259 | + | "Checks", | |
| 260 | + | "What CI, integrations and g1t Actions say about a commit, in the shapes CI tools already send: statuses (a state per context) and check runs (a lifecycle, a conclusion, a Markdown report, annotations on lines and buttons), grouped per reporter into check suites. g1t Actions jobs are check runs too. Required checks are met by either.", | |
| 261 | + | &[ | |
| 262 | + | Op::Checks(ChecksOp::CreateCommitStatus), | |
| 263 | + | Op::Checks(ChecksOp::ListCommitStatuses), | |
| 264 | + | Op::Checks(ChecksOp::GetCombinedStatus), | |
| 265 | + | Op::Checks(ChecksOp::CreateCheckRun), | |
| 266 | + | Op::Checks(ChecksOp::UpdateCheckRun), | |
| 267 | + | Op::Checks(ChecksOp::GetCheckRun), | |
| 268 | + | Op::Checks(ChecksOp::ListCheckRunAnnotations), | |
| 269 | + | Op::Checks(ChecksOp::RerequestCheckRun), | |
| 270 | + | Op::Checks(ChecksOp::ListCheckRunsForRef), | |
| 271 | + | Op::Checks(ChecksOp::ListCheckSuitesForRef), | |
| 272 | + | Op::Checks(ChecksOp::GetCheckSuite), | |
| 273 | + | Op::Checks(ChecksOp::RerequestCheckSuite), | |
| 274 | + | ], | |
| 275 | + | ), | |
| 276 | + | ( | |
| 258 | 277 | "Issues", | |
| 259 | 278 | "What should change in a repository, with labels and comments. Issues and pull requests share one sequence of numbers.", | |
| 260 | 279 | &[ | |
| 611 | 630 | Op::GetCodeownersErrors => "List CODEOWNERS errors", | |
| 612 | 631 | Op::Security(op) => op.title(), | |
| 613 | 632 | Op::Rules(op) => op.title(), | |
| 633 | + | Op::Checks(op) => op.title(), | |
| 614 | 634 | Op::About(op) => op.title(), | |
| 615 | 635 | Op::Deployments(op) => op.title(), | |
| 616 | 636 | } | |
| 748 | 768 | fn operation(route: &Route) -> Value { | |
| 749 | 769 | let op = route.op; | |
| 750 | 770 | let path_params: Vec<&str> = route.params().collect(); | |
| 751 | − | // `owner` and `name` in the path stand for the operation's `repo` input. | |
| 752 | − | let covered = |name: &str| name == "repo" || path_params.contains(&name); | |
| 771 | + | // `owner` and `name` in the path stand for the operation's `repo` input, | |
| 772 | + | // so a `name` in the body, such as a check run's, is the body's own. | |
| 773 | + | let stands_for_repo = | |
| 774 | + | |name: &str| matches!(name, "owner" | "name") && path_params.contains(&"owner") && path_params.contains(&"name"); | |
| 775 | + | let covered = |name: &str| name == "repo" || (path_params.contains(&name) && !stands_for_repo(name)); | |
| 753 | 776 | let all_properties = op.properties(); | |
| 754 | 777 | let mut properties = all_properties.clone(); | |
| 755 | 778 | properties.retain(|name, _| !covered(name)); | |
| 761 | 784 | ||
| 762 | 785 | let mut parameters: Vec<Value> = path_params | |
| 763 | 786 | .iter() | |
| 764 | − | .map(|name| parameter(name, "path", true, all_properties.get(*name))) | |
| 787 | + | .map(|name| parameter(name, "path", true, if stands_for_repo(name) { None } else { all_properties.get(*name) })) | |
| 765 | 788 | .collect(); | |
| 766 | 789 | let mut body = Value::Null; | |
| 767 | 790 | if route.method == "GET" { |
| 26 | 26 | }; | |
| 27 | 27 | ||
| 28 | 28 | use crate::alerts::{AlertKind, SecurityAlert}; | |
| 29 | + | use crate::checks::ChecksOp; | |
| 29 | 30 | use crate::about::AboutOp; | |
| 30 | 31 | use crate::deployments::DeploymentsOp; | |
| 31 | 32 | use crate::rules::RulesOp; | |
| 276 | 277 | Security(SecurityOp), | |
| 277 | 278 | /// Rulesets: rules.rs. | |
| 278 | 279 | Rules(RulesOp), | |
| 280 | + | /// Statuses, check runs and check suites on commits: checks.rs. | |
| 281 | + | Checks(ChecksOp), | |
| 279 | 282 | /// A repository's languages, contributors, license, stars and releases: about.rs. | |
| 280 | 283 | About(AboutOp), | |
| 281 | 284 | /// Deployments wherever they run, and environments: deployments.rs. | |
| 642 | 645 | } | |
| 643 | 646 | ||
| 644 | 647 | impl Op { | |
| 645 | − | pub const ALL: [Op; 245] = [ | |
| 648 | + | pub const ALL: [Op; 257] = [ | |
| 646 | 649 | Op::Whoami, | |
| 647 | 650 | Op::GetWorkspace, | |
| 648 | 651 | Op::CreateWorkspace, | |
| 866 | 869 | Op::Rules(RulesOp::UpdateWorkspaceRuleset), | |
| 867 | 870 | Op::Rules(RulesOp::DeleteWorkspaceRuleset), | |
| 868 | 871 | Op::Rules(RulesOp::ListWorkspaceRuleEvaluations), | |
| 872 | + | Op::Checks(ChecksOp::CreateCommitStatus), | |
| 873 | + | Op::Checks(ChecksOp::ListCommitStatuses), | |
| 874 | + | Op::Checks(ChecksOp::GetCombinedStatus), | |
| 875 | + | Op::Checks(ChecksOp::CreateCheckRun), | |
| 876 | + | Op::Checks(ChecksOp::UpdateCheckRun), | |
| 877 | + | Op::Checks(ChecksOp::GetCheckRun), | |
| 878 | + | Op::Checks(ChecksOp::ListCheckRunAnnotations), | |
| 879 | + | Op::Checks(ChecksOp::RerequestCheckRun), | |
| 880 | + | Op::Checks(ChecksOp::ListCheckRunsForRef), | |
| 881 | + | Op::Checks(ChecksOp::ListCheckSuitesForRef), | |
| 882 | + | Op::Checks(ChecksOp::GetCheckSuite), | |
| 883 | + | Op::Checks(ChecksOp::RerequestCheckSuite), | |
| 869 | 884 | Op::About(AboutOp::GetLanguages), | |
| 870 | 885 | Op::About(AboutOp::ListContributors), | |
| 871 | 886 | Op::About(AboutOp::GetLicense), | |
| 1078 | 1093 | Op::GetCodeownersErrors => "get_codeowners_errors", | |
| 1079 | 1094 | Op::Security(op) => op.name(), | |
| 1080 | 1095 | Op::Rules(op) => op.name(), | |
| 1096 | + | Op::Checks(op) => op.name(), | |
| 1081 | 1097 | Op::About(op) => op.name(), | |
| 1082 | 1098 | Op::Deployments(op) => op.name(), | |
| 1083 | 1099 | } | |
| 1590 | 1606 | } | |
| 1591 | 1607 | Op::Security(op) => op.description(), | |
| 1592 | 1608 | Op::Rules(op) => op.description(), | |
| 1609 | + | Op::Checks(op) => op.description(), | |
| 1593 | 1610 | Op::About(op) => op.description(), | |
| 1594 | 1611 | Op::Deployments(op) => op.description(), | |
| 1595 | 1612 | } | |
| 2960 | 2977 | ), | |
| 2961 | 2978 | Op::Security(op) => op.input(), | |
| 2962 | 2979 | Op::Rules(op) => op.input(), | |
| 2980 | + | Op::Checks(op) => op.input(), | |
| 2963 | 2981 | Op::About(op) => op.input(), | |
| 2964 | 2982 | Op::Deployments(op) => op.input(), | |
| 2965 | 2983 | } | |
| 2967 | 2985 | ||
| 2968 | 2986 | /// Whether the operation refuses an anonymous caller outright. | |
| 2969 | 2987 | pub(crate) fn needs_user(self) -> bool { | |
| 2988 | + | // A public repository's checks are anyone's to read. | |
| 2989 | + | if let Op::Checks(op) = self { | |
| 2990 | + | return !op.reads(); | |
| 2991 | + | } | |
| 2970 | 2992 | if let Op::About(op) = self { | |
| 2971 | 2993 | return !op.anonymous(); | |
| 2972 | 2994 | } | |
| 5014 | 5036 | // each answer its public shape. | |
| 5015 | 5037 | Op::Security(op) => crate::security::run(op, services, viewer, input).await, | |
| 5016 | 5038 | Op::Rules(op) => crate::rules::run(op, services, viewer, input).await, | |
| 5039 | + | Op::Checks(op) => crate::checks::run(op, services, viewer, input).await, | |
| 5017 | 5040 | Op::About(op) => crate::about::run(op, services, viewer, input).await, | |
| 5018 | 5041 | Op::Deployments(op) => crate::deployments::run(op, services, viewer, input).await, | |
| 5019 | 5042 | Op::ReopenSecurityAlert => { |
| 10227 | 10227 | "response": { | |
| 10228 | 10228 | "deleted": true | |
| 10229 | 10229 | } | |
| 10230 | + | }, | |
| 10231 | + | "create_commit_status": { | |
| 10232 | + | "params": { | |
| 10233 | + | "owner": "flagon-io", | |
| 10234 | + | "name": "hello", | |
| 10235 | + | "sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e" | |
| 10236 | + | }, | |
| 10237 | + | "request": { | |
| 10238 | + | "state": "success", | |
| 10239 | + | "context": "ci/build", | |
| 10240 | + | "description": "Build #4821 passed", | |
| 10241 | + | "target_url": "https://ci.example.com/builds/4821" | |
| 10242 | + | }, | |
| 10243 | + | "response": { | |
| 10244 | + | "context": "ci/build", | |
| 10245 | + | "state": "success", | |
| 10246 | + | "description": "Build #4821 passed", | |
| 10247 | + | "target_url": "https://ci.example.com/builds/4821", | |
| 10248 | + | "updated_at": "2026-10-07T14:03:02.118Z", | |
| 10249 | + | "source": "api" | |
| 10250 | + | }, | |
| 10251 | + | "notes": "Setting a context again replaces its status on that commit. A status counts as a check: a required check named `ci/build`, in branch protection or a ruleset's `required_status_checks`, is met by it, and a ruleset check pinned to the `api` integration only by statuses and check runs reported through the API. See the [Checks guide](/guides/checks/)." | |
| 10252 | + | }, | |
| 10253 | + | "list_commit_statuses": { | |
| 10254 | + | "params": { | |
| 10255 | + | "owner": "flagon-io", | |
| 10256 | + | "name": "hello", | |
| 10257 | + | "ref": "main" | |
| 10258 | + | }, | |
| 10259 | + | "response": [ | |
| 10260 | + | { | |
| 10261 | + | "context": "ci/build", | |
| 10262 | + | "state": "success", | |
| 10263 | + | "description": "Build #4821 passed", | |
| 10264 | + | "target_url": "https://ci.example.com/builds/4821", | |
| 10265 | + | "updated_at": "2026-10-07T14:03:02.118Z", | |
| 10266 | + | "source": "api" | |
| 10267 | + | } | |
| 10268 | + | ] | |
| 10269 | + | }, | |
| 10270 | + | "get_combined_status": { | |
| 10271 | + | "params": { | |
| 10272 | + | "owner": "flagon-io", | |
| 10273 | + | "name": "hello", | |
| 10274 | + | "ref": "main" | |
| 10275 | + | }, | |
| 10276 | + | "response": { | |
| 10277 | + | "state": "success", | |
| 10278 | + | "sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e", | |
| 10279 | + | "total_count": 1, | |
| 10280 | + | "statuses": [ | |
| 10281 | + | { | |
| 10282 | + | "context": "ci/build", | |
| 10283 | + | "state": "success", | |
| 10284 | + | "description": "Build #4821 passed", | |
| 10285 | + | "target_url": "https://ci.example.com/builds/4821", | |
| 10286 | + | "updated_at": "2026-10-07T14:03:02.118Z", | |
| 10287 | + | "source": "api" | |
| 10288 | + | } | |
| 10289 | + | ] | |
| 10290 | + | } | |
| 10291 | + | }, | |
| 10292 | + | "create_check_run": { | |
| 10293 | + | "params": { | |
| 10294 | + | "owner": "flagon-io", | |
| 10295 | + | "name": "hello" | |
| 10296 | + | }, | |
| 10297 | + | "request": { | |
| 10298 | + | "name": "lint", | |
| 10299 | + | "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e", | |
| 10300 | + | "status": "completed", | |
| 10301 | + | "conclusion": "failure", | |
| 10302 | + | "details_url": "https://ci.example.com/builds/4821", | |
| 10303 | + | "external_id": "4821", | |
| 10304 | + | "output": { | |
| 10305 | + | "title": "2 problems", | |
| 10306 | + | "summary": "**2** problems in 1 file.", | |
| 10307 | + | "annotations": [ | |
| 10308 | + | { | |
| 10309 | + | "path": "src/parse.rs", | |
| 10310 | + | "start_line": 42, | |
| 10311 | + | "end_line": 42, | |
| 10312 | + | "annotation_level": "warning", | |
| 10313 | + | "message": "unused variable: `depth`" | |
| 10314 | + | } | |
| 10315 | + | ] | |
| 10316 | + | }, | |
| 10317 | + | "actions": [ | |
| 10318 | + | { | |
| 10319 | + | "label": "Fix this", | |
| 10320 | + | "description": "Apply the suggested fixes", | |
| 10321 | + | "identifier": "fix" | |
| 10322 | + | } | |
| 10323 | + | ] | |
| 10324 | + | }, | |
| 10325 | + | "response": { | |
| 10326 | + | "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n", | |
| 10327 | + | "name": "lint", | |
| 10328 | + | "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e", | |
| 10329 | + | "status": "completed", | |
| 10330 | + | "conclusion": "failure", | |
| 10331 | + | "started_at": "2026-10-07T14:02:11.000Z", | |
| 10332 | + | "completed_at": "2026-10-07T14:02:36.000Z", | |
| 10333 | + | "details_url": "https://ci.example.com/builds/4821", | |
| 10334 | + | "external_id": "4821", | |
| 10335 | + | "html_url": "https://g1t.sh/flagon-io/hello/checks/cr_01kq4b7c8d9e0f1g2h3j4k5m6n", | |
| 10336 | + | "output": { | |
| 10337 | + | "title": "2 problems", | |
| 10338 | + | "summary": "**2** problems in 1 file.", | |
| 10339 | + | "text": null, | |
| 10340 | + | "annotations_count": 2 | |
| 10341 | + | }, | |
| 10342 | + | "actions": [ | |
| 10343 | + | { | |
| 10344 | + | "label": "Fix this", | |
| 10345 | + | "description": "Apply the suggested fixes", | |
| 10346 | + | "identifier": "fix" | |
| 10347 | + | } | |
| 10348 | + | ], | |
| 10349 | + | "check_suite": { | |
| 10350 | + | "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p" | |
| 10351 | + | }, | |
| 10352 | + | "app": { | |
| 10353 | + | "slug": "buildkite", | |
| 10354 | + | "name": "Buildkite" | |
| 10355 | + | }, | |
| 10356 | + | "created_at": "2026-10-07T14:02:11.318Z" | |
| 10357 | + | }, | |
| 10358 | + | "notes": "Start a run as `queued` or `in_progress` and complete it later with [`update_check_run`](/reference/api/checks/update-check-run/), or create it completed. `app` names who reports it; by default it is your token's name, and a g1t Actions job's `G1T_TOKEN` reports as g1t Actions. Each reporter's runs on a commit form one check suite. The run also stands as a status of its name, so a required check `lint` is met by it: `success`, `neutral` and `skipped` pass, any other conclusion fails. See the [Checks guide](/guides/checks/)." | |
| 10359 | + | }, | |
| 10360 | + | "update_check_run": { | |
| 10361 | + | "params": { | |
| 10362 | + | "owner": "flagon-io", | |
| 10363 | + | "name": "hello", | |
| 10364 | + | "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n" | |
| 10365 | + | }, | |
| 10366 | + | "request": { | |
| 10367 | + | "conclusion": "success", | |
| 10368 | + | "output": { | |
| 10369 | + | "title": "No problems", | |
| 10370 | + | "summary": "All clear." | |
| 10371 | + | } | |
| 10372 | + | }, | |
| 10373 | + | "response": { | |
| 10374 | + | "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n", | |
| 10375 | + | "name": "lint", | |
| 10376 | + | "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e", | |
| 10377 | + | "status": "completed", | |
| 10378 | + | "conclusion": "success", | |
| 10379 | + | "started_at": "2026-10-07T14:02:11.000Z", | |
| 10380 | + | "completed_at": "2026-10-07T14:02:36.000Z", | |
| 10381 | + | "details_url": "https://ci.example.com/builds/4821", | |
| 10382 | + | "external_id": "4821", | |
| 10383 | + | "html_url": "https://g1t.sh/flagon-io/hello/checks/cr_01kq4b7c8d9e0f1g2h3j4k5m6n", | |
| 10384 | + | "output": { | |
| 10385 | + | "title": "No problems", | |
| 10386 | + | "summary": "All clear.", | |
| 10387 | + | "text": null, | |
| 10388 | + | "annotations_count": 2 | |
| 10389 | + | }, | |
| 10390 | + | "actions": [ | |
| 10391 | + | { | |
| 10392 | + | "label": "Fix this", | |
| 10393 | + | "description": "Apply the suggested fixes", | |
| 10394 | + | "identifier": "fix" | |
| 10395 | + | } | |
| 10396 | + | ], | |
| 10397 | + | "check_suite": { | |
| 10398 | + | "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p" | |
| 10399 | + | }, | |
| 10400 | + | "app": { | |
| 10401 | + | "slug": "buildkite", | |
| 10402 | + | "name": "Buildkite" | |
| 10403 | + | }, | |
| 10404 | + | "created_at": "2026-10-07T14:02:11.318Z" | |
| 10405 | + | }, | |
| 10406 | + | "notes": "Annotations given here are added to the ones the run has, at most 50 a request and 1000 in all." | |
| 10407 | + | }, | |
| 10408 | + | "get_check_run": { | |
| 10409 | + | "params": { | |
| 10410 | + | "owner": "flagon-io", | |
| 10411 | + | "name": "hello", | |
| 10412 | + | "id": "job_01kq4b6a7b8c9d0e1f2g3h4j5k" | |
| 10413 | + | }, | |
| 10414 | + | "response": { | |
| 10415 | + | "id": "job_01kq4b6a7b8c9d0e1f2g3h4j5k", | |
| 10416 | + | "name": "Test", | |
| 10417 | + | "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e", | |
| 10418 | + | "status": "completed", | |
| 10419 | + | "conclusion": "success", | |
| 10420 | + | "started_at": "2026-10-07T14:01:02.000Z", | |
| 10421 | + | "completed_at": "2026-10-07T14:01:27.000Z", | |
| 10422 | + | "details_url": "https://g1t.sh/flagon-io/hello/actions/runs/run_01kq4b6a7b8c9d0e1f2g3h4j5m?job=job_01kq4b6a7b8c9d0e1f2g3h4j5k", | |
| 10423 | + | "external_id": null, | |
| 10424 | + | "html_url": "https://g1t.sh/flagon-io/hello/actions/runs/run_01kq4b6a7b8c9d0e1f2g3h4j5m?job=job_01kq4b6a7b8c9d0e1f2g3h4j5k", | |
| 10425 | + | "output": { | |
| 10426 | + | "title": null, | |
| 10427 | + | "summary": null, | |
| 10428 | + | "text": null, | |
| 10429 | + | "annotations_count": 0 | |
| 10430 | + | }, | |
| 10431 | + | "actions": [], | |
| 10432 | + | "check_suite": { | |
| 10433 | + | "id": "run_01kq4b6a7b8c9d0e1f2g3h4j5m" | |
| 10434 | + | }, | |
| 10435 | + | "app": { | |
| 10436 | + | "slug": "actions", | |
| 10437 | + | "name": "g1t Actions" | |
| 10438 | + | }, | |
| 10439 | + | "workflow": { | |
| 10440 | + | "run_id": "run_01kq4b6a7b8c9d0e1f2g3h4j5m", | |
| 10441 | + | "name": "CI", | |
| 10442 | + | "event": "push" | |
| 10443 | + | }, | |
| 10444 | + | "created_at": "2026-10-07T14:01:00.512Z" | |
| 10445 | + | } | |
| 10446 | + | }, | |
| 10447 | + | "list_check_run_annotations": { | |
| 10448 | + | "params": { | |
| 10449 | + | "owner": "flagon-io", | |
| 10450 | + | "name": "hello", | |
| 10451 | + | "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n" | |
| 10452 | + | }, | |
| 10453 | + | "response": [ | |
| 10454 | + | { | |
| 10455 | + | "path": "src/parse.rs", | |
| 10456 | + | "start_line": 42, | |
| 10457 | + | "end_line": 42, | |
| 10458 | + | "start_column": 9, | |
| 10459 | + | "end_column": 14, | |
| 10460 | + | "annotation_level": "warning", | |
| 10461 | + | "message": "unused variable: `depth`", | |
| 10462 | + | "title": "unused_variables", | |
| 10463 | + | "raw_details": "#[warn(unused_variables)] on by default" | |
| 10464 | + | } | |
| 10465 | + | ] | |
| 10466 | + | }, | |
| 10467 | + | "rerequest_check_run": { | |
| 10468 | + | "params": { | |
| 10469 | + | "owner": "flagon-io", | |
| 10470 | + | "name": "hello", | |
| 10471 | + | "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n" | |
| 10472 | + | }, | |
| 10473 | + | "response": { | |
| 10474 | + | "rerequested": true | |
| 10475 | + | } | |
| 10476 | + | }, | |
| 10477 | + | "list_check_runs_for_ref": { | |
| 10478 | + | "params": { | |
| 10479 | + | "owner": "flagon-io", | |
| 10480 | + | "name": "hello", | |
| 10481 | + | "ref": "main" | |
| 10482 | + | }, | |
| 10483 | + | "query": { | |
| 10484 | + | "filter": "latest" | |
| 10485 | + | }, | |
| 10486 | + | "response": { | |
| 10487 | + | "total_count": 2, | |
| 10488 | + | "check_runs": [ | |
| 10489 | + | { | |
| 10490 | + | "id": "cr_01kq4b7c8d9e0f1g2h3j4k5m6n", | |
| 10491 | + | "name": "lint", | |
| 10492 | + | "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e", | |
| 10493 | + | "status": "completed", | |
| 10494 | + | "conclusion": "failure", | |
| 10495 | + | "started_at": "2026-10-07T14:02:11.000Z", | |
| 10496 | + | "completed_at": "2026-10-07T14:02:36.000Z", | |
| 10497 | + | "details_url": "https://ci.example.com/builds/4821", | |
| 10498 | + | "external_id": "4821", | |
| 10499 | + | "html_url": "https://g1t.sh/flagon-io/hello/checks/cr_01kq4b7c8d9e0f1g2h3j4k5m6n", | |
| 10500 | + | "output": { | |
| 10501 | + | "title": "2 problems", | |
| 10502 | + | "summary": "**2** problems in 1 file.", | |
| 10503 | + | "text": null, | |
| 10504 | + | "annotations_count": 2 | |
| 10505 | + | }, | |
| 10506 | + | "actions": [ | |
| 10507 | + | { | |
| 10508 | + | "label": "Fix this", | |
| 10509 | + | "description": "Apply the suggested fixes", | |
| 10510 | + | "identifier": "fix" | |
| 10511 | + | } | |
| 10512 | + | ], | |
| 10513 | + | "check_suite": { | |
| 10514 | + | "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p" | |
| 10515 | + | }, | |
| 10516 | + | "app": { | |
| 10517 | + | "slug": "buildkite", | |
| 10518 | + | "name": "Buildkite" | |
| 10519 | + | }, | |
| 10520 | + | "created_at": "2026-10-07T14:02:11.318Z" | |
| 10521 | + | }, | |
| 10522 | + | { | |
| 10523 | + | "id": "job_01kq4b6a7b8c9d0e1f2g3h4j5k", | |
| 10524 | + | "name": "Test", | |
| 10525 | + | "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e", | |
| 10526 | + | "status": "completed", | |
| 10527 | + | "conclusion": "success", | |
| 10528 | + | "started_at": "2026-10-07T14:01:02.000Z", | |
| 10529 | + | "completed_at": "2026-10-07T14:01:27.000Z", | |
| 10530 | + | "details_url": "https://g1t.sh/flagon-io/hello/actions/runs/run_01kq4b6a7b8c9d0e1f2g3h4j5m?job=job_01kq4b6a7b8c9d0e1f2g3h4j5k", | |
| 10531 | + | "external_id": null, | |
| 10532 | + | "html_url": "https://g1t.sh/flagon-io/hello/actions/runs/run_01kq4b6a7b8c9d0e1f2g3h4j5m?job=job_01kq4b6a7b8c9d0e1f2g3h4j5k", | |
| 10533 | + | "output": { | |
| 10534 | + | "title": null, | |
| 10535 | + | "summary": null, | |
| 10536 | + | "text": null, | |
| 10537 | + | "annotations_count": 0 | |
| 10538 | + | }, | |
| 10539 | + | "actions": [], | |
| 10540 | + | "check_suite": { | |
| 10541 | + | "id": "run_01kq4b6a7b8c9d0e1f2g3h4j5m" | |
| 10542 | + | }, | |
| 10543 | + | "app": { | |
| 10544 | + | "slug": "actions", | |
| 10545 | + | "name": "g1t Actions" | |
| 10546 | + | }, | |
| 10547 | + | "workflow": { | |
| 10548 | + | "run_id": "run_01kq4b6a7b8c9d0e1f2g3h4j5m", | |
| 10549 | + | "name": "CI", | |
| 10550 | + | "event": "push" | |
| 10551 | + | }, | |
| 10552 | + | "created_at": "2026-10-07T14:01:00.512Z" | |
| 10553 | + | } | |
| 10554 | + | ] | |
| 10555 | + | } | |
| 10556 | + | }, | |
| 10557 | + | "list_check_suites_for_ref": { | |
| 10558 | + | "params": { | |
| 10559 | + | "owner": "flagon-io", | |
| 10560 | + | "name": "hello", | |
| 10561 | + | "ref": "main" | |
| 10562 | + | }, | |
| 10563 | + | "response": { | |
| 10564 | + | "total_count": 2, | |
| 10565 | + | "check_suites": [ | |
| 10566 | + | { | |
| 10567 | + | "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p", | |
| 10568 | + | "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e", | |
| 10569 | + | "head_branch": "main", | |
| 10570 | + | "status": "completed", | |
| 10571 | + | "conclusion": "failure", | |
| 10572 | + | "app": { | |
| 10573 | + | "slug": "buildkite", | |
| 10574 | + | "name": "Buildkite" | |
| 10575 | + | }, | |
| 10576 | + | "latest_check_runs_count": 1, | |
| 10577 | + | "created_at": "2026-10-07T14:02:11.318Z", | |
| 10578 | + | "updated_at": "2026-10-07T14:02:36.502Z" | |
| 10579 | + | }, | |
| 10580 | + | { | |
| 10581 | + | "id": "run_01kq4b6a7b8c9d0e1f2g3h4j5m", | |
| 10582 | + | "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e", | |
| 10583 | + | "head_branch": "main", | |
| 10584 | + | "status": "completed", | |
| 10585 | + | "conclusion": "success", | |
| 10586 | + | "app": { | |
| 10587 | + | "slug": "actions", | |
| 10588 | + | "name": "g1t Actions" | |
| 10589 | + | }, | |
| 10590 | + | "name": "CI", | |
| 10591 | + | "latest_check_runs_count": 1, | |
| 10592 | + | "created_at": "2026-10-07T14:01:00.512Z", | |
| 10593 | + | "updated_at": "2026-10-07T14:01:28.040Z" | |
| 10594 | + | } | |
| 10595 | + | ] | |
| 10596 | + | } | |
| 10597 | + | }, | |
| 10598 | + | "get_check_suite": { | |
| 10599 | + | "params": { | |
| 10600 | + | "owner": "flagon-io", | |
| 10601 | + | "name": "hello", | |
| 10602 | + | "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p" | |
| 10603 | + | }, | |
| 10604 | + | "response": { | |
| 10605 | + | "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p", | |
| 10606 | + | "head_sha": "9f3c2a1b7e6d5c4b3a2918f7e6d5c4b3a2918f7e", | |
| 10607 | + | "head_branch": "main", | |
| 10608 | + | "status": "completed", | |
| 10609 | + | "conclusion": "failure", | |
| 10610 | + | "app": { | |
| 10611 | + | "slug": "buildkite", | |
| 10612 | + | "name": "Buildkite" | |
| 10613 | + | }, | |
| 10614 | + | "latest_check_runs_count": 1, | |
| 10615 | + | "created_at": "2026-10-07T14:02:11.318Z", | |
| 10616 | + | "updated_at": "2026-10-07T14:02:36.502Z" | |
| 10617 | + | } | |
| 10618 | + | }, | |
| 10619 | + | "rerequest_check_suite": { | |
| 10620 | + | "params": { | |
| 10621 | + | "owner": "flagon-io", | |
| 10622 | + | "name": "hello", | |
| 10623 | + | "id": "cs_01kq4b7c8d9e0f1g2h3j4k5m6p" | |
| 10624 | + | }, | |
| 10625 | + | "response": { | |
| 10626 | + | "rerequested": true | |
| 10627 | + | } | |
| 10230 | 10628 | } | |
| 10231 | 10629 | } |
| 7 | 7 | //! encoded again, so that every field the type has is sent, not only the | |
| 8 | 8 | //! ones an example shows. | |
| 9 | 9 | ||
| 10 | − | use g1t_contracts::{access, actions, codeowners, integrations, repos, rules, search, teams, webhooks, work}; | |
| 10 | + | use g1t_contracts::{access, actions, checks, codeowners, integrations, repos, rules, search, teams, webhooks, work}; | |
| 11 | 11 | use g1t_kit::wire::{self, USER_KEYED}; | |
| 12 | 12 | use serde::Serialize; | |
| 13 | 13 | use serde::de::DeserializeOwned; | |
| 15 | 15 | ||
| 16 | 16 | use crate::openapi::document; | |
| 17 | 17 | use crate::operations::Op; | |
| 18 | + | use crate::checks::ChecksOp; | |
| 18 | 19 | use crate::rules::RulesOp; | |
| 19 | 20 | ||
| 20 | 21 | /// A key as `#[serde(rename_all = "camelCase")]` writes it. | |
| 198 | 199 | Op::GetThreadSubscription | Op::SetThreadSubscription | Op::DeleteThreadSubscription => { | |
| 199 | 200 | through::<g1t_contracts::inbox::ThreadSubscription>(op, sent) | |
| 200 | 201 | } | |
| 202 | + | Op::Checks(ChecksOp::CreateCommitStatus) => through::<work::CommitStatus>(op, sent), | |
| 203 | + | Op::Checks(ChecksOp::ListCommitStatuses) => through::<Vec<work::CommitStatus>>(op, sent), | |
| 204 | + | Op::Checks(ChecksOp::GetCombinedStatus) => through::<checks::CombinedStatus>(op, sent), | |
| 205 | + | Op::Checks(ChecksOp::CreateCheckRun | ChecksOp::UpdateCheckRun | ChecksOp::GetCheckRun) => { | |
| 206 | + | through::<checks::CommitCheckRun>(op, sent) | |
| 207 | + | } | |
| 208 | + | Op::Checks(ChecksOp::ListCheckRunAnnotations) => through::<Vec<checks::CheckAnnotation>>(op, sent), | |
| 209 | + | Op::Checks(ChecksOp::ListCheckRunsForRef) => through::<checks::CheckRunList>(op, sent), | |
| 210 | + | Op::Checks(ChecksOp::ListCheckSuitesForRef) => through::<checks::CheckSuiteList>(op, sent), | |
| 211 | + | Op::Checks(ChecksOp::GetCheckSuite) => through::<checks::CommitCheckSuite>(op, sent), | |
| 201 | 212 | _ => sent, | |
| 202 | 213 | } | |
| 203 | 214 | } |
| 5 | 5 | use crate::about::AboutOp; | |
| 6 | 6 | use crate::deployments::DeploymentsOp; | |
| 7 | 7 | use crate::operations::Op; | |
| 8 | + | use crate::checks::ChecksOp; | |
| 8 | 9 | use crate::rules::RulesOp; | |
| 9 | 10 | use crate::security::SecurityOp; | |
| 10 | 11 | ||
| 261 | 262 | &[], | |
| 262 | 263 | ), | |
| 263 | 264 | route("GET", "/repos/:owner/:name/check-names", Op::ListCheckNames, &[]), | |
| 265 | + | // Checks: GitHub's addresses for statuses, check runs and check suites. | |
| 266 | + | route("POST", "/repos/:owner/:name/statuses/:sha", Op::Checks(ChecksOp::CreateCommitStatus), &[]), | |
| 267 | + | route("GET", "/repos/:owner/:name/commits/:ref/statuses", Op::Checks(ChecksOp::ListCommitStatuses), &[]), | |
| 268 | + | route("GET", "/repos/:owner/:name/commits/:ref/status", Op::Checks(ChecksOp::GetCombinedStatus), &[]), | |
| 269 | + | route( | |
| 270 | + | "GET", | |
| 271 | + | "/repos/:owner/:name/commits/:ref/check-runs", | |
| 272 | + | Op::Checks(ChecksOp::ListCheckRunsForRef), | |
| 273 | + | &[("check_name", "check_name"), ("status", "status"), ("app", "app"), ("filter", "filter")], | |
| 274 | + | ), | |
| 275 | + | route( | |
| 276 | + | "GET", | |
| 277 | + | "/repos/:owner/:name/commits/:ref/check-suites", | |
| 278 | + | Op::Checks(ChecksOp::ListCheckSuitesForRef), | |
| 279 | + | &[("app", "app"), ("check_name", "check_name")], | |
| 280 | + | ), | |
| 281 | + | route("POST", "/repos/:owner/:name/check-runs", Op::Checks(ChecksOp::CreateCheckRun), &[]), | |
| 282 | + | route("GET", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::GetCheckRun), &[]), | |
| 283 | + | route("PATCH", "/repos/:owner/:name/check-runs/:id", Op::Checks(ChecksOp::UpdateCheckRun), &[]), | |
| 284 | + | route("GET", "/repos/:owner/:name/check-runs/:id/annotations", Op::Checks(ChecksOp::ListCheckRunAnnotations), &[]), | |
| 285 | + | route("POST", "/repos/:owner/:name/check-runs/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckRun), &[]), | |
| 286 | + | route("GET", "/repos/:owner/:name/check-suites/:id", Op::Checks(ChecksOp::GetCheckSuite), &[]), | |
| 287 | + | route("POST", "/repos/:owner/:name/check-suites/:id/rerequest", Op::Checks(ChecksOp::RerequestCheckSuite), &[]), | |
| 264 | 288 | // Rulesets: a repository's, a workspace's, the rules of one branch, | |
| 265 | 289 | // and how they judged pushes and merges. | |
| 266 | 290 | route("GET", "/repos/:owner/:name/rulesets", Op::Rules(RulesOp::ListRepoRulesets), &[("include_parents", "include_parents")]), | |
| 1157 | 1181 | } | |
| 1158 | 1182 | ||
| 1159 | 1183 | #[test] | |
| 1184 | + | fn checks_are_at_githubs_addresses() { | |
| 1185 | + | let sha = "a".repeat(40); | |
| 1186 | + | let body = json!({ "state": "success", "context": "ci/build" }); | |
| 1187 | + | let (route, input) = resolve("POST", &format!("/repos/acme/web/statuses/{sha}"), &[], body).unwrap(); | |
| 1188 | + | assert_eq!(route.op, Op::Checks(ChecksOp::CreateCommitStatus)); | |
| 1189 | + | assert_eq!(input, json!({ "state": "success", "context": "ci/build", "sha": sha, "repo": "acme/web" })); | |
| 1190 | + | let (route, input) = resolve("GET", "/repos/acme/web/commits/release%2F1.x/status", &[], Value::Null).unwrap(); | |
| 1191 | + | assert_eq!(route.op, Op::Checks(ChecksOp::GetCombinedStatus)); | |
| 1192 | + | assert_eq!(input, json!({ "ref": "release/1.x", "repo": "acme/web" })); | |
| 1193 | + | let query = [("check_name".to_owned(), "lint".to_owned())]; | |
| 1194 | + | let (route, input) = resolve("GET", "/repos/acme/web/commits/main/check-runs", &query, Value::Null).unwrap(); | |
| 1195 | + | assert_eq!(route.op, Op::Checks(ChecksOp::ListCheckRunsForRef)); | |
| 1196 | + | assert_eq!(input, json!({ "check_name": "lint", "ref": "main", "repo": "acme/web" })); | |
| 1197 | + | let (route, input) = resolve("PATCH", "/repos/acme/web/check-runs/cr_1", &[], json!({ "conclusion": "success" })).unwrap(); | |
| 1198 | + | assert_eq!(route.op, Op::Checks(ChecksOp::UpdateCheckRun)); | |
| 1199 | + | assert_eq!(input, json!({ "conclusion": "success", "id": "cr_1", "repo": "acme/web" })); | |
| 1200 | + | let op = |method: &str, path: &str| resolve(method, path, &[], Value::Null).unwrap().0.op; | |
| 1201 | + | assert_eq!(op("POST", "/repos/acme/web/check-runs"), Op::Checks(ChecksOp::CreateCheckRun)); | |
| 1202 | + | assert_eq!(op("GET", "/repos/acme/web/check-runs/cr_1/annotations"), Op::Checks(ChecksOp::ListCheckRunAnnotations)); | |
| 1203 | + | assert_eq!(op("POST", "/repos/acme/web/check-suites/cs_1/rerequest"), Op::Checks(ChecksOp::RerequestCheckSuite)); | |
| 1204 | + | assert_eq!(op("GET", "/repos/acme/web/commits/main/check-suites"), Op::Checks(ChecksOp::ListCheckSuitesForRef)); | |
| 1205 | + | } | |
| 1206 | + | ||
| 1207 | + | #[test] | |
| 1160 | 1208 | fn query_parameters_are_renamed() { | |
| 1161 | 1209 | let query = [ | |
| 1162 | 1210 | ("q".to_owned(), "parser".to_owned()), |
| 21 | 21 | use crate::about::AboutOp; | |
| 22 | 22 | use crate::deployments::DeploymentsOp; | |
| 23 | 23 | use crate::operations::Op; | |
| 24 | + | use crate::checks::ChecksOp; | |
| 24 | 25 | use crate::rules::RulesOp; | |
| 25 | 26 | use crate::security::SecurityOp; | |
| 26 | 27 | ||
| 199 | 200 | Tool { | |
| 200 | 201 | name: "workflow", | |
| 201 | 202 | title: "Workflows", | |
| 202 | − | description: "GitHub Actions workflows from .g1t/workflows: their runs, jobs and logs, and running, cancelling or rerunning them. Deployments wherever they run (reported from any CI, made by jobs with an `environment:`, or built on g1t.page), their statuses and environments, and reporting your own. Also the self-hosted runners they run on: a workspace's (`workspace`) or a repository's own (`repo`), their groups, and where agent work runs.", | |
| 203 | + | description: "GitHub Actions workflows from .g1t/workflows: their runs, jobs and logs, and running, cancelling or rerunning them. Deployments wherever they run (reported from any CI, made by jobs with an `environment:`, or built on g1t.page), their statuses and environments, and reporting your own. Checks on commits: statuses, check runs (a g1t Actions job is one) and check suites, to read where a commit stands or report on it from CI or an integration. Also the self-hosted runners they run on: a workspace's (`workspace`) or a repository's own (`repo`), their groups, and where agent work runs.", | |
| 203 | 204 | default_action: None, | |
| 204 | 205 | actions: &[ | |
| 205 | 206 | a("list", Op::ListWorkflows, "Workflows on the default branch"), | |
| 210 | 211 | a("cancel", Op::CancelWorkflowRun, "Cancel a run"), | |
| 211 | 212 | a("rerun", Op::RerunWorkflowRun, "Run a finished run again"), | |
| 212 | 213 | a("update", Op::UpdateWorkflow, "Turn a workflow on or off"), | |
| 214 | + | a("combined_status", Op::Checks(ChecksOp::GetCombinedStatus), "A commit's statuses and the state they add up to"), | |
| 215 | + | a("list_statuses", Op::Checks(ChecksOp::ListCommitStatuses), "A commit's statuses, newest first"), | |
| 216 | + | a("set_status", Op::Checks(ChecksOp::CreateCommitStatus), "Set a status on a commit"), | |
| 217 | + | a("list_check_runs", Op::Checks(ChecksOp::ListCheckRunsForRef), "A commit's check runs, g1t Actions jobs included"), | |
| 218 | + | a("get_check_run", Op::Checks(ChecksOp::GetCheckRun), "One check run with its report"), | |
| 219 | + | a("check_run_annotations", Op::Checks(ChecksOp::ListCheckRunAnnotations), "What a check run says about lines of files"), | |
| 220 | + | a("create_check_run", Op::Checks(ChecksOp::CreateCheckRun), "Report a check run on a commit"), | |
| 221 | + | a("update_check_run", Op::Checks(ChecksOp::UpdateCheckRun), "Move a check run on, complete it, add annotations"), | |
| 222 | + | a("rerequest_check_run", Op::Checks(ChecksOp::RerequestCheckRun), "Ask for a check run to run again"), | |
| 223 | + | a("list_check_suites", Op::Checks(ChecksOp::ListCheckSuitesForRef), "A commit's check suites, one per reporter or workflow run"), | |
| 224 | + | a("get_check_suite", Op::Checks(ChecksOp::GetCheckSuite), "One check suite"), | |
| 225 | + | a("rerequest_check_suite", Op::Checks(ChecksOp::RerequestCheckSuite), "Ask for a check suite to run again"), | |
| 213 | 226 | a("list_deployments", Op::Deployments(DeploymentsOp::ListDeployments), "Deployments wherever they run, newest first, filtered"), | |
| 214 | 227 | a("get_deployment", Op::Deployments(DeploymentsOp::GetDeployment), "One deployment with every status it has had"), | |
| 215 | 228 | a("create_deployment", Op::Deployments(DeploymentsOp::CreateDeployment), "Report a deployment of a ref to an environment"), |
| 126 | 126 | label: 'Landing changes', | |
| 127 | 127 | items: [ | |
| 128 | 128 | { label: 'Pull requests and checks', slug: 'guides/pull-requests' }, | |
| 129 | + | { label: 'Checks', slug: 'guides/checks' }, | |
| 129 | 130 | { label: 'Pull requests into other branches', slug: 'guides/base-branches' }, | |
| 130 | 131 | { label: 'Labels', slug: 'guides/labels' }, | |
| 131 | 132 | { label: 'Milestones', slug: 'guides/milestones' }, |
| 209 | 209 | `pull_request` runs on every pull request's head, whoever opened it, a | |
| 210 | 210 | person or an agent, and its runs report a check named after the workflow: | |
| 211 | 211 | a workflow with `name: CI` reports `CI`, with the status context | |
| 212 | − | `CI / pull_request` (the workflow's name and the event). | |
| 212 | + | `CI / pull_request` (the workflow's name and the event). Each of its jobs | |
| 213 | + | is a [check run](/guides/checks/) on the commit, shown as | |
| 214 | + | `CI / test (pull_request)` beside it wherever it appears. | |
| 213 | 215 | ||
| 214 | 216 | - **Which checks a merge needs** is up to the [rules](/guides/rules/) of the branch it merges into, | |
| 215 | 217 | their [required status checks](/guides/pull-requests/#required-status-checks), |
| 321 | 321 | | Issues & pull requests | `issues:read`, `issues:write`, `pull_requests:read`, `pull_requests:write` | | |
| 322 | 322 | | Agents | `agents:run` | | |
| 323 | 323 | | Workflows | `workflows:read`, `workflows:write` | | |
| 324 | + | | Checks | `checks:read`, `checks:write` | | |
| 324 | 325 | | Deployments | `deployments:read`, `deployments:write` | | |
| 325 | 326 | | Memory & search | `memory:read`, `memory:write` | | |
| 326 | 327 | | Account | `account:read`, `account:write` | | |
| 355 | 356 | | `agents:run` | Put g1t to work and message it, which uses the workspace's money | | |
| 356 | 357 | | `workflows:read` | Read workflows, runs and logs | | |
| 357 | 358 | | `workflows:write` | Run, cancel, rerun and turn workflows on or off | | |
| 359 | + | | `checks:read` | Read commits' statuses, check runs, check suites and annotations | | |
| 360 | + | | `checks:write` | Report [statuses and check runs](/guides/checks/) on commits, and ask for checks to run again | | |
| 358 | 361 | | `deployments:read` | See [deployments](/guides/deployments-api/), their statuses and environments | | |
| 359 | 362 | | `deployments:write` | Report deployments and their statuses, from any CI | | |
| 360 | 363 | | `memory:read` | Recall memory and search the workspace's context | | |
| 414 | 417 | | --- | --- | | |
| 415 | 418 | | Read only | Every `read` scope. Changes nothing. | | |
| 416 | 419 | | Agent | Every `read` scope except `runners:read`, and `code:write`, `issues:write`, `pull_requests:write`, `agents:run`, `memory:write` and `notifications:write`. Reads everything, works on issues and pull requests, pushes code, puts g1t to work, and answers your inbox. No admin scope. | | |
| 417 | − | | CI | `repo:read`, `code:read`, `code:write`, `packages:read`, `packages:write`, `workflows:read`, `workflows:write`, `deployments:read` and `deployments:write`. Clones and pushes code, pushes and pulls packages, runs workflows and reports deployments. | | |
| 420 | + | | CI | `repo:read`, `code:read`, `code:write`, `packages:read`, `packages:write`, `workflows:read`, `workflows:write`, `checks:read`, `checks:write`, `deployments:read` and `deployments:write`. Clones and pushes code, pushes and pulls packages, runs workflows, and reports [checks](/guides/checks/) and deployments. | | |
| 418 | 421 | | Full access | Everything you can do, including deleting repositories and changing who has access. Marked **Dangerous**. | | |
| 419 | 422 | ||
| 420 | 423 | Admin scopes change things that are hard to undo, or decide who can reach |
| 1 | + | --- | |
| 2 | + | title: Checks | |
| 3 | + | description: Report what CI and integrations find on a commit as statuses and check runs, see them beside every commit, and require them before a merge. | |
| 4 | + | --- | |
| 5 | + | ||
| 6 | + | Every commit can carry checks: what your workflows, your CI, your | |
| 7 | + | deployments and any integration say about it. g1t shows them beside the | |
| 8 | + | commit wherever it appears, so you can tell at a glance whether a change | |
| 9 | + | works and how far along it is. | |
| 10 | + | ||
| 11 | + | - A green check: every check passed. | |
| 12 | + | - A red cross: at least one failed. | |
| 13 | + | - An amber dot: at least one is still running or queued. | |
| 14 | + | ||
| 15 | + | Select the mark to see the list: a headline ("All checks have passed", | |
| 16 | + | "Some checks were not successful" or "Some checks haven't completed yet"), | |
| 17 | + | how many passed, failed and are running, and each check with how it went, | |
| 18 | + | how long it took, and a **Details** link. | |
| 19 | + | ||
| 20 | + | ## Where checks show | |
| 21 | + | ||
| 22 | + | | Page | Where | | |
| 23 | + | | --- | --- | | |
| 24 | + | | **Code** | Beside the latest commit, above the files | | |
| 25 | + | | **Commits** | Beside each commit | | |
| 26 | + | | A commit's page | Beside its hash | | |
| 27 | + | | **Branches** | Beside each branch's latest commit | | |
| 28 | + | | **Tags** | Beside each tagged commit | | |
| 29 | + | | A pull request | Beside its head commit, in the sidebar; its merge box lists them too | | |
| 30 | + | | A project's overview | Beside the latest commit, and each active branch | | |
| 31 | + | ||
| 32 | + | A page reads the checks of all its commits at once, after the page itself | |
| 33 | + | has loaded: each mark shows a placeholder until they arrive. | |
| 34 | + | ||
| 35 | + | ## Statuses and check runs | |
| 36 | + | ||
| 37 | + | There are two ways to report a check. Use either, or both. | |
| 38 | + | ||
| 39 | + | | | A status | A check run | | |
| 40 | + | | --- | --- | --- | | |
| 41 | + | | What it is | A state for one context on a commit, such as `ci/build` | One run of one check, with a life of its own | | |
| 42 | + | | States | `pending`, `success`, `failure`, `error` | `status`: `queued`, `in_progress`, `completed`; once completed, a `conclusion`: `success`, `failure`, `neutral`, `cancelled`, `skipped`, `timed_out` or `action_required` | | |
| 43 | + | | Report | A short `description` and a `target_url` | A `title`, a Markdown `summary` and `text`, up to 1,000 annotations on lines of files, and up to 3 buttons | | |
| 44 | + | | On g1t | Listed with a link to `target_url` | Its own page under the repository, linked from the list | | |
| 45 | + | | Set again | Replaces the context's status on that commit | Update the same run, or create a new one | | |
| 46 | + | | Use it for | A quick pass or fail from any tool | Test, lint and scan results someone needs to read | | |
| 47 | + | ||
| 48 | + | Every job of a [workflow](/guides/actions/) run is a check run, named | |
| 49 | + | `Workflow / job (event)` in the list, such as `CI / test (push)`, and its | |
| 50 | + | **Details** opens the job's log. You do not report those: g1t does. | |
| 51 | + | ||
| 52 | + | ## Report a status | |
| 53 | + | ||
| 54 | + | You need a token with the `checks:write` scope (see | |
| 55 | + | [scopes](/guides/authentication/#scopes)) and the Write | |
| 56 | + | [role](/guides/access-and-roles/) on the repository. | |
| 57 | + | ||
| 58 | + | ```sh | |
| 59 | + | curl -X POST https://api.g1t.sh/repos/<workspace>/<repo>/statuses/<sha> \ | |
| 60 | + | -H "Authorization: Bearer $G1T_TOKEN" \ | |
| 61 | + | -H "Content-Type: application/json" \ | |
| 62 | + | -d '{ | |
| 63 | + | "state": "success", | |
| 64 | + | "context": "ci/build", | |
| 65 | + | "description": "Build #4821 passed", | |
| 66 | + | "target_url": "https://ci.example.com/builds/4821" | |
| 67 | + | }' | |
| 68 | + | ``` | |
| 69 | + | ||
| 70 | + | | Field | Required | What it is | | |
| 71 | + | | --- | --- | --- | | |
| 72 | + | | `state` | Yes | `pending`, `success`, `failure` or `error`. `error` counts as a failure. | | |
| 73 | + | | `context` | No | What reports it, such as `ci/build`; `default` when left out. At most 100 characters. | | |
| 74 | + | | `description` | No | A short word on it, at most 140 characters. | | |
| 75 | + | | `target_url` | No | Where to see more: an `http` or `https` address. | | |
| 76 | + | ||
| 77 | + | [`create_commit_status`](/reference/api/checks/create-commit-status/) | |
| 78 | + | returns the status. Set a context again to replace it: report `pending` | |
| 79 | + | when a build starts, then `success` or `failure` when it ends. | |
| 80 | + | [`get_combined_status`](/reference/api/checks/get-combined-status/) gives | |
| 81 | + | a commit's statuses and what they add up to: | |
| 82 | + | `GET /repos/<workspace>/<repo>/commits/<ref>/status`, where `<ref>` is a | |
| 83 | + | commit SHA, a branch or a tag. | |
| 84 | + | ||
| 85 | + | ## Report a check run | |
| 86 | + | ||
| 87 | + | A check run is reported in two steps: create it when the work starts, | |
| 88 | + | then complete it. | |
| 89 | + | ||
| 90 | + | 1. Create it, in progress: | |
| 91 | + | ||
| 92 | + | ```sh | |
| 93 | + | curl -X POST https://api.g1t.sh/repos/<workspace>/<repo>/check-runs \ | |
| 94 | + | -H "Authorization: Bearer $G1T_TOKEN" \ | |
| 95 | + | -H "Content-Type: application/json" \ | |
| 96 | + | -d '{"name": "lint", "head_sha": "<sha>", "status": "in_progress", "details_url": "https://ci.example.com/builds/4821"}' | |
| 97 | + | ``` | |
| 98 | + | ||
| 99 | + | The answer has its `id`, such as `cr_01kq4b7c8d9e0f1g2h3j4k5m6n`. | |
| 100 | + | ||
| 101 | + | 2. Complete it with a `conclusion`, a report and annotations: | |
| 102 | + | ||
| 103 | + | ```sh | |
| 104 | + | curl -X PATCH https://api.g1t.sh/repos/<workspace>/<repo>/check-runs/<id> \ | |
| 105 | + | -H "Authorization: Bearer $G1T_TOKEN" \ | |
| 106 | + | -H "Content-Type: application/json" \ | |
| 107 | + | -d '{ | |
| 108 | + | "conclusion": "failure", | |
| 109 | + | "output": { | |
| 110 | + | "title": "2 problems", | |
| 111 | + | "summary": "**2** problems in `src/parse.rs`.", | |
| 112 | + | "annotations": [ | |
| 113 | + | {"path": "src/parse.rs", "start_line": 42, "end_line": 42, "annotation_level": "warning", "message": "unused variable: `depth`"}, | |
| 114 | + | {"path": "src/parse.rs", "start_line": 57, "end_line": 60, "annotation_level": "failure", "message": "this match is not exhaustive"} | |
| 115 | + | ] | |
| 116 | + | } | |
| 117 | + | }' | |
| 118 | + | ``` | |
| 119 | + | ||
| 120 | + | Giving a `conclusion` completes the run; `started_at` and `completed_at` | |
| 121 | + | are filled in when you leave them out. A run can also be created already | |
| 122 | + | completed, in one call. | |
| 123 | + | ||
| 124 | + | | Field | What it is | | |
| 125 | + | | --- | --- | | |
| 126 | + | | `name` | The check's name, at most 100 characters. Required to create one. | | |
| 127 | + | | `head_sha` | The commit it is about. Required to create one. | | |
| 128 | + | | `status` | `queued` (the default), `in_progress` or `completed`. | | |
| 129 | + | | `conclusion` | `success`, `failure`, `neutral`, `cancelled`, `skipped`, `timed_out` or `action_required`. | | |
| 130 | + | | `started_at`, `completed_at` | When it started and ended, in RFC 3339. | | |
| 131 | + | | `details_url` | Your own page for it. | | |
| 132 | + | | `external_id` | Your own id for it. | | |
| 133 | + | | `output` | `title`, `summary` and `text` (Markdown, at most 65,535 characters each) and `annotations`. | | |
| 134 | + | | `actions` | Up to 3 buttons: `label` (20 characters), `description` (40) and `identifier` (20). | | |
| 135 | + | | `app` | Who reports it, such as `Codecov`. By default, your token's name. | | |
| 136 | + | ||
| 137 | + | ### A minimal reporter | |
| 138 | + | ||
| 139 | + | This script runs a command and reports it as a check run, from any CI that | |
| 140 | + | has `curl` and `jq`: | |
| 141 | + | ||
| 142 | + | ```sh | |
| 143 | + | #!/bin/sh | |
| 144 | + | # check.sh <name> <command…>: report a command as a g1t check run. | |
| 145 | + | set -u | |
| 146 | + | name=$1; shift | |
| 147 | + | api="https://api.g1t.sh/repos/$G1T_REPO" | |
| 148 | + | auth="Authorization: Bearer $G1T_TOKEN" | |
| 149 | + | ||
| 150 | + | id=$(curl -sf -X POST "$api/check-runs" -H "$auth" -H "Content-Type: application/json" \ | |
| 151 | + | -d "$(jq -n --arg name "$name" --arg sha "$G1T_SHA" '{name: $name, head_sha: $sha, status: "in_progress"}')" | jq -r .id) | |
| 152 | + | ||
| 153 | + | if output=$("$@" 2>&1); then conclusion=success; else conclusion=failure; fi | |
| 154 | + | ||
| 155 | + | curl -sf -X PATCH "$api/check-runs/$id" -H "$auth" -H "Content-Type: application/json" \ | |
| 156 | + | -d "$(jq -n --arg c "$conclusion" --arg log "$(printf '%s' "$output" | tail -c 60000)" \ | |
| 157 | + | '{conclusion: $c, output: {title: $c, summary: ("```\n" + $log + "\n```")}}')" > /dev/null | |
| 158 | + | [ "$conclusion" = success ] | |
| 159 | + | ``` | |
| 160 | + | ||
| 161 | + | ```sh | |
| 162 | + | G1T_REPO=acme/web G1T_SHA=$(git rev-parse HEAD) ./check.sh lint npm run lint | |
| 163 | + | ``` | |
| 164 | + | ||
| 165 | + | ### Annotations | |
| 166 | + | ||
| 167 | + | An annotation points at lines of a file at the run's commit: `path`, | |
| 168 | + | `start_line` and `end_line` (from 1), and for one line, `start_column` | |
| 169 | + | and `end_column`. `annotation_level` is `notice`, `warning` or `failure`; | |
| 170 | + | `message` says what is wrong, `title` names it, and `raw_details` holds | |
| 171 | + | anything longer. | |
| 172 | + | ||
| 173 | + | Send at most 50 in one request; each update adds to those the run has, | |
| 174 | + | up to 1,000. On the check run's page they are grouped by file, each | |
| 175 | + | linking to its lines. | |
| 176 | + | [`list_check_run_annotations`](/reference/api/checks/list-check-run-annotations/) | |
| 177 | + | returns them in the order they were reported. | |
| 178 | + | ||
| 179 | + | ### Buttons | |
| 180 | + | ||
| 181 | + | `actions` puts up to 3 buttons on the check run's page, such as **Fix | |
| 182 | + | this** or **Ignore**. When someone with the Write role presses one, g1t | |
| 183 | + | sends your webhook a `check_run.requested_action` event with the button's | |
| 184 | + | `identifier` in `data.requested_action`. What happens next is up to you. | |
| 185 | + | ||
| 186 | + | ### Check suites | |
| 187 | + | ||
| 188 | + | Each reporter's check runs on a commit form one check suite, with a | |
| 189 | + | status and conclusion worked out from its latest runs: in progress while any | |
| 190 | + | is, then the worst conclusion. A workflow run is the suite of its jobs. | |
| 191 | + | [`list_check_suites_for_ref`](/reference/api/checks/list-check-suites-for-ref/) | |
| 192 | + | lists a commit's suites. A suite completing sends `check_suite.completed`. | |
| 193 | + | ||
| 194 | + | ## From a workflow | |
| 195 | + | ||
| 196 | + | A workflow job reports extra check runs with its own `G1T_TOKEN`. They | |
| 197 | + | report as **g1t Actions**: | |
| 198 | + | ||
| 199 | + | ```yaml | |
| 200 | + | - name: Report coverage | |
| 201 | + | if: always() | |
| 202 | + | env: | |
| 203 | + | G1T_TOKEN: ${{ secrets.G1T_TOKEN }} | |
| 204 | + | run: | | |
| 205 | + | curl -sf -X POST "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/check-runs" \ | |
| 206 | + | -H "Authorization: Bearer $G1T_TOKEN" -H "Content-Type: application/json" \ | |
| 207 | + | -d "{\"name\": \"coverage\", \"head_sha\": \"$GITHUB_SHA\", \"conclusion\": \"neutral\", \"output\": {\"title\": \"81% covered\", \"summary\": \"Up 2% from main.\"}}" | |
| 208 | + | ``` | |
| 209 | + | ||
| 210 | + | A pull request's runs from someone without the Write role get no token | |
| 211 | + | that can write, so they cannot report checks. | |
| 212 | + | ||
| 213 | + | ## Required checks | |
| 214 | + | ||
| 215 | + | A [required status check](/guides/pull-requests/#required-status-checks), | |
| 216 | + | in branch protection or a [ruleset](/guides/rules/), is met by a status of | |
| 217 | + | its name or a check run of its name alike: | |
| 218 | + | ||
| 219 | + | | What reported it | Counts as | | |
| 220 | + | | --- | --- | | |
| 221 | + | | A status `success` | Passing | | |
| 222 | + | | A status `failure` or `error` | Failing | | |
| 223 | + | | A status `pending` | Running: the merge waits | | |
| 224 | + | | A check run not yet completed | Running: the merge waits | | |
| 225 | + | | A check run completed `success`, `neutral` or `skipped` | Passing | | |
| 226 | + | | A check run completed `failure`, `cancelled`, `timed_out` or `action_required` | Failing | | |
| 227 | + | | Nothing yet | Expected: the merge waits | | |
| 228 | + | ||
| 229 | + | A workflow is required by its name, such as `CI`, which all its jobs | |
| 230 | + | report under. To require only what was reported through the API, pin the | |
| 231 | + | check to the `api` integration in a ruleset: | |
| 232 | + | `{"context": "lint", "integration": "api"}`. | |
| 233 | + | ||
| 234 | + | A pull request [g1t is working on](/guides/working-with-g1t/#seeing-it-through) | |
| 235 | + | goes back to g1t when a check fails, whatever reported it. | |
| 236 | + | ||
| 237 | + | ## Run again | |
| 238 | + | ||
| 239 | + | [`rerequest_check_run`](/reference/api/checks/rerequest-check-run/) and | |
| 240 | + | [`rerequest_check_suite`](/reference/api/checks/rerequest-check-suite/), or | |
| 241 | + | **Re-run** on a check run's page, ask for it to run again. A check run | |
| 242 | + | reported through the API sends its reporter `check_run.rerequested` (or | |
| 243 | + | `check_suite.rerequested`): run it again and report a new check run. A | |
| 244 | + | workflow job's run runs again, which also needs `workflows:write`. | |
| 245 | + | ||
| 246 | + | ## Webhooks | |
| 247 | + | ||
| 248 | + | [Webhooks](/guides/webhooks/) can be sent: | |
| 249 | + | ||
| 250 | + | | Event | When | | |
| 251 | + | | --- | --- | | |
| 252 | + | | `status.created` | A status was set on a commit through the API. | | |
| 253 | + | | `check_run.created` | A check run was reported. | | |
| 254 | + | | `check_run.completed` | A check run completed. | | |
| 255 | + | | `check_run.rerequested` | Someone asked for a check run to run again. | | |
| 256 | + | | `check_run.requested_action` | Someone pressed one of a check run's buttons. | | |
| 257 | + | | `check_suite.completed` | Every latest check run of a suite completed. | | |
| 258 | + | | `check_suite.rerequested` | Someone asked for a check suite to run again. | | |
| 259 | + | ||
| 260 | + | These are left out of a repository's timeline. | |
| 261 | + | ||
| 262 | + | ## Who can report checks | |
| 263 | + | ||
| 264 | + | | | Can | | |
| 265 | + | | --- | --- | | |
| 266 | + | | Anyone who can read the repository | See its checks, and read them through the API with `checks:read` (no token for a public repository) | | |
| 267 | + | | The Write role and up, with `checks:write` | Report statuses and check runs, and ask for them to run again | | |
| 268 | + | | A workflow job, with `G1T_TOKEN` | The same, in its repository | | |
| 269 | + | | g1t's agents | Read checks, never report them | | |
| 270 | + | ||
| 271 | + | An agent's own work is never judged by checks it reported: what a check | |
| 272 | + | says is up to your CI and integrations. | |
| 273 | + | ||
| 274 | + | ## From the API and MCP | |
| 275 | + | ||
| 276 | + | | Route | Operation | | |
| 277 | + | | --- | --- | | |
| 278 | + | | `POST /repos/{owner}/{name}/statuses/{sha}` | [`create_commit_status`](/reference/api/checks/create-commit-status/) | | |
| 279 | + | | `GET /repos/{owner}/{name}/commits/{ref}/statuses` | [`list_commit_statuses`](/reference/api/checks/list-commit-statuses/) | | |
| 280 | + | | `GET /repos/{owner}/{name}/commits/{ref}/status` | [`get_combined_status`](/reference/api/checks/get-combined-status/) | | |
| 281 | + | | `POST /repos/{owner}/{name}/check-runs` | [`create_check_run`](/reference/api/checks/create-check-run/) | | |
| 282 | + | | `PATCH /repos/{owner}/{name}/check-runs/{id}` | [`update_check_run`](/reference/api/checks/update-check-run/) | | |
| 283 | + | | `GET /repos/{owner}/{name}/check-runs/{id}` | [`get_check_run`](/reference/api/checks/get-check-run/) | | |
| 284 | + | | `GET /repos/{owner}/{name}/check-runs/{id}/annotations` | [`list_check_run_annotations`](/reference/api/checks/list-check-run-annotations/) | | |
| 285 | + | | `POST /repos/{owner}/{name}/check-runs/{id}/rerequest` | [`rerequest_check_run`](/reference/api/checks/rerequest-check-run/) | | |
| 286 | + | | `GET /repos/{owner}/{name}/commits/{ref}/check-runs` | [`list_check_runs_for_ref`](/reference/api/checks/list-check-runs-for-ref/) | | |
| 287 | + | | `GET /repos/{owner}/{name}/commits/{ref}/check-suites` | [`list_check_suites_for_ref`](/reference/api/checks/list-check-suites-for-ref/) | | |
| 288 | + | | `GET /repos/{owner}/{name}/check-suites/{id}` | [`get_check_suite`](/reference/api/checks/get-check-suite/) | | |
| 289 | + | | `POST /repos/{owner}/{name}/check-suites/{id}/rerequest` | [`rerequest_check_suite`](/reference/api/checks/rerequest-check-suite/) | | |
| 290 | + | ||
| 291 | + | [`list_check_runs_for_ref`](/reference/api/checks/list-check-runs-for-ref/) | |
| 292 | + | gives each name's latest run and each workflow's latest run per event; | |
| 293 | + | `filter=all` gives every one. Narrow it with `check_name`, `status` and | |
| 294 | + | `app` (a reporter's slug; `actions` for workflow jobs). | |
| 295 | + | ||
| 296 | + | On the [MCP server](/reference/mcp/#workflow), the `workflow` tool has an | |
| 297 | + | action for each: `combined_status`, `list_statuses`, `set_status`, | |
| 298 | + | `list_check_runs`, `get_check_run`, `check_run_annotations`, | |
| 299 | + | `create_check_run`, `update_check_run`, `rerequest_check_run`, | |
| 300 | + | `list_check_suites`, `get_check_suite` and `rerequest_check_suite`. |
| 22 | 22 | workflow. A workflow named `CI` reports the check `CI`; its status context | |
| 23 | 23 | is `CI / pull_request`, the workflow's name and the event it ran for. Other | |
| 24 | 24 | parts of g1t report under their own names, such as `g1t / deploy` (or | |
| 25 | − | `g1t / deploy (<project>)`) for a [deployment](/guides/deployments/). | |
| 25 | + | `g1t / deploy (<project>)`) for a [deployment](/guides/deployments/), and | |
| 26 | + | your own CI and integrations report statuses and check runs through the | |
| 27 | + | API: see [Checks](/guides/checks/). | |
| 26 | 28 | ||
| 27 | 29 | Which checks a merge needs is up to the repository: its | |
| 28 | 30 | [required status checks](#required-status-checks). The merge box lists |
| 157 | 157 | ||
| 158 | 158 | | Parameter | Default | What it does | | |
| 159 | 159 | | --- | --- | --- | | |
| 160 | − | | `checks` | None | Each check has a `context`, such as `CI` (a workflow's name) or `g1t / deploy`, and an optional `integration`: `actions`, `deployments`, `security` or `g1t`. A check with an `integration` counts only when that integration reported it, so a workflow cannot stand in for a deployment. | | |
| 160 | + | | `checks` | None | Each check has a `context`, such as `CI` (a workflow's name) or `g1t / deploy`, and an optional `integration`: `actions`, `deployments`, `security`, `g1t` or `api` (a status or [check run](/guides/checks/) reported through the API). A check with an `integration` counts only when that integration reported it, so a workflow cannot stand in for a deployment. A check is met by a status or a check run of its name alike. | | |
| 161 | 161 | | `strict` | `false` | The pull request must contain the branch's latest commits, so what merges is exactly what was checked. | | |
| 162 | 162 | | `paths` | Always | The checks are required only when the pull request changes a file matching one of these patterns. | | |
| 163 | 163 | | `allow_bypass_on_merge` | `false` | Someone who may merge can merge past checks that have not passed by ticking **Bypass the required checks**. | |
| 92 | 92 | | `code_scanning_alert.created`, `.fixed`, `.dismissed`, `.reopened` | A [code scanning alert](/guides/security/code-scanning/) changed. `data.alert_id`, `data.alert_number`, `data.title`, `data.severity`, `data.path`, `data.line`, `data.state`, `data.link`. | | |
| 93 | 93 | | `vulnerability_alert.created`, `.fixed`, `.dismissed`, `.reopened` | A [vulnerability alert](/guides/security/supply-chain/) changed. `data.alert_id`, `data.title` (package, version, lockfile and advisory), `data.severity`, `data.state`, `data.link`. | | |
| 94 | 94 | | `checks.completed` | A pull request's checks finished: every status on its head has reported and none is still pending, or the merge queue took it out. `data.number`, `data.commit`, and `data.status`, `passed` or `failed`. | | |
| 95 | + | | `status.created` | A status was set on a commit through the API. `data.sha`, `data.context`, `data.state`, `data.description`, `data.target_url`. See [Checks](/guides/checks/). | | |
| 96 | + | | `check_run.created`, `check_run.completed` | A check run was reported on a commit, or completed. `data.check_run`: `id`, `name`, `head_sha`, `status`, `conclusion`, `details_url`, `external_id`, `html_url`, `output`, `actions`, `check_suite` and `app`. | | |
| 97 | + | | `check_run.rerequested`, `check_run.requested_action` | Someone asked for a check run to run again, or pressed one of its buttons: `data.requested_action` is the button's `identifier`. Report a new run, or do what the button says. | | |
| 98 | + | | `check_suite.completed`, `check_suite.rerequested` | Every latest check run of a reporter's suite on a commit completed, or someone asked for it to run again. `data.check_suite`: `id`, `head_sha`, `status`, `conclusion`, `app`. | | |
| 95 | 99 | | `review.completed` | g1t reviewed a pull request. `data.verdict`. | | |
| 96 | 100 | | `workflow.completed` | A [workflow](/guides/actions/) run finished. `data.workflow`, `data.conclusion`, `data.run_id`, `data.sha`, `data.pull`. | | |
| 97 | 101 | | `deployment.succeeded`, `deployment.failed` | A g1t.page build of a [project](/guides/deployments/) finished, for production or a pull request's preview. `data.deployment_id`, `data.project`, `data.kind` (`production` or `preview`), `data.number` for a preview, `data.commit`, `data.path`, `data.error` on failure, and `data.recovered` when a success follows a failure. | |
| 181 | 181 | ||
| 182 | 182 | 1. **Checks.** Every push the agent makes runs the repository's | |
| 183 | 183 | [workflows](/guides/actions/) on the pull request, as for anyone's pull | |
| 184 | − | request. g1t waits for them to finish. Only the workflow runs report, so | |
| 185 | − | the agent has no say in the result. | |
| 184 | + | request. g1t waits for them to finish. Only the workflow runs and your | |
| 185 | + | integrations report [checks](/guides/checks/), so the agent has no say | |
| 186 | + | in the result: it reads them, with each failing check run's annotations, | |
| 187 | + | and never reports one. | |
| 186 | 188 | 2. **Review.** A different agent reads the change and posts comments on | |
| 187 | 189 | lines, a summary and a verdict. | |
| 188 | 190 | 3. **Revision.** If a check fails or the review asks for changes, the |
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
Binary or large file; its contents are not shown.
This change is too large to show in full.