Skip to content

g1t/crates/contracts/src/work.rs

2,275 lines79,350 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Issues and pull requests replace intents and attempts1//! The work service: issues, pull requests, comments and sessions.
Work service in Rust, with RFC 3339 timestamps2//!
3//! Each `*Args` struct is the argument of the method of the same name,
4//! served at `POST /rpc/<method>`.
Issues and pull requests replace intents and attempts5//!
6//! Issues and pull requests share one sequence of numbers per repository,
7//! so `#12` names exactly one of them.
Work service in Rust, with RFC 3339 timestamps8
9use serde::{Deserialize, Serialize};
10
Agents as a team: lifecycle, merge queue, billing and a new shell11use crate::repos::{CompareArgs, RepoPath};
Work service in Rust, with RFC 3339 timestamps12use crate::{User, Viewer};
13
Issues and pull requests replace intents and attempts14/// Labels every repository starts with. Any other label comes into being
15/// the first time it is put on an issue.
16pub const DEFAULT_LABELS: [&str; 5] = ["bug", "feature", "docs", "chore", "question"];
17
18/// `open` or `closed`: the filter on lists of issues and pull requests. An
19/// open pull request is a draft or one ready for review; a closed one was
20/// merged or closed without merging.
Work service in Rust, with RFC 3339 timestamps21#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
Issues and pull requests replace intents and attempts23pub enum State {
Work service in Rust, with RFC 3339 timestamps24 Open,
Issues and pull requests replace intents and attempts25 Closed,
26}
27
28/// Why an issue was closed.
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum IssueReason {
32 /// The work was done. If a pull request did it, `resolved_by` names it.
33 Completed,
34 NotPlanned,
Work service in Rust, with RFC 3339 timestamps35}
36
Issues and pull requests replace intents and attempts37impl IssueReason {
38 pub fn as_str(self) -> &'static str {
39 match self {
40 IssueReason::Completed => "completed",
41 IssueReason::NotPlanned => "not_planned",
42 }
43 }
44}
45
46/// Something that should change in a repository: a bug, a feature, a
47/// question. Opened by a person, an agent or an integration. Pull requests
48/// are made against it; the one that is merged resolves it.
Work service in Rust, with RFC 3339 timestamps49#[derive(Clone, Debug, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
Issues and pull requests replace intents and attempts51pub struct Issue {
Work service in Rust, with RFC 3339 timestamps52 pub id: String,
53 pub repo_id: String,
Issues and pull requests replace intents and attempts54 /// Shown as `#12`.
Work service in Rust, with RFC 3339 timestamps55 pub number: u32,
56 pub title: String,
Issues and pull requests replace intents and attempts57 /// Markdown. Also what an agent is given to work from.
58 pub body: String,
59 pub labels: Vec<String>,
60 pub state: State,
61 /// Set when closed.
62 pub reason: Option<IssueReason>,
63 /// The number of the pull request whose merge closed this issue.
64 pub resolved_by: Option<u32>,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights65 /// Who opened it: a person, an integration, or g1t (`kind` `agent`)
66 /// for one its agent filed while at work.
Work service in Rust, with RFC 3339 timestamps67 pub author: User,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights68 /// For an issue g1t's agent filed: the person it was working for. They
69 /// may manage it as its author could. See [`owner`](Issue::owner).
70 #[serde(default)]
71 pub requested_by: Option<User>,
Work service in Rust, with RFC 3339 timestamps72 /// RFC 3339.
73 pub created_at: String,
Issues and pull requests replace intents and attempts74 /// RFC 3339.
75 pub updated_at: String,
76 /// RFC 3339.
77 pub closed_at: Option<String>,
78 /// Pull requests made against this issue, in any state.
79 pub pull_count: u32,
80 pub comment_count: u32,
Agents as a team: lifecycle, merge queue, billing and a new shell81 /// Usernames of the people it is assigned to.
82 #[serde(default)]
83 pub assignees: Vec<String>,
84 /// The numbers of the issues that have to be merged before this one is
85 /// worked on.
86 #[serde(default)]
87 pub blocked_by: Vec<u32>,
88 /// Whether a g1t agent takes it as soon as it can: at once, or when
89 /// what it is blocked by has merged.
90 #[serde(default)]
91 pub queued: bool,
92 /// The agent working on it now: the one behind its newest pull request
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent93 /// that is still in progress in a fork, such as `g1t`.
Agents as a team: lifecycle, merge queue, billing and a new shell94 #[serde(default)]
95 pub agent: Option<String>,
Work service in Rust, with RFC 3339 timestamps96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "lowercase")]
Issues and pull requests replace intents and attempts100pub enum PullStatus {
101 /// Still being worked on.
102 Draft,
103 /// Ready for review.
104 Open,
105 Merged,
106 /// Closed without merging.
107 Closed,
Work service in Rust, with RFC 3339 timestamps108}
109
Issues and pull requests replace intents and attempts110impl PullStatus {
Work service in Rust, with RFC 3339 timestamps111 pub fn as_str(self) -> &'static str {
112 match self {
Issues and pull requests replace intents and attempts113 PullStatus::Draft => "draft",
114 PullStatus::Open => "open",
115 PullStatus::Merged => "merged",
116 PullStatus::Closed => "closed",
Work service in Rust, with RFC 3339 timestamps117 }
118 }
119
Issues and pull requests replace intents and attempts120 /// Whether the pull request can still be changed or merged.
Work service in Rust, with RFC 3339 timestamps121 pub fn is_active(self) -> bool {
Issues and pull requests replace intents and attempts122 matches!(self, PullStatus::Draft | PullStatus::Open)
Work service in Rust, with RFC 3339 timestamps123 }
124}
125
126/// Where the agent runs: on g1t's sandboxes, or in someone's own session.
127#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
128#[serde(rename_all = "lowercase")]
Issues and pull requests replace intents and attempts129pub enum Runtime {
Work service in Rust, with RFC 3339 timestamps130 Hosted,
131 External,
132}
133
Pull requests from branches134/// A proposed change. It is made either in a fork created for it, which is
135/// how agents work, or on a branch pushed to the repository itself.
Work service in Rust, with RFC 3339 timestamps136#[derive(Clone, Debug, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
Issues and pull requests replace intents and attempts138pub struct Pull {
Work service in Rust, with RFC 3339 timestamps139 pub id: String,
140 pub repo_id: String,
Issues and pull requests replace intents and attempts141 /// Shown as `#12`.
Work service in Rust, with RFC 3339 timestamps142 pub number: u32,
Issues and pull requests replace intents and attempts143 /// The number of the issue this is for, if any.
144 pub issue: Option<u32>,
145 pub title: String,
146 /// Markdown: what changed and why. Set when marked ready.
147 pub body: Option<String>,
Work service in Rust, with RFC 3339 timestamps148 /// A label for the agent doing the work, e.g. `claude-code`.
149 pub agent: String,
Issues and pull requests replace intents and attempts150 pub runtime: Runtime,
151 pub status: PullStatus,
Pull requests from branches152 /// The fork holding the change, unless it is on a branch.
153 pub fork: Option<RepoPath>,
Work service in Rust, with RFC 3339 timestamps154 /// The fork's repository id.
Pull requests from branches155 pub fork_repo_id: Option<String>,
156 /// The branch of the repository holding the change, unless it is in a
157 /// fork.
158 pub branch: Option<String>,
Work service in Rust, with RFC 3339 timestamps159 pub head_commit: Option<String>,
Issues and pull requests replace intents and attempts160 /// For a merged pull request, what the branch pointed to before the
161 /// merge. Comparing against it shows what the pull request changed.
162 pub merge_base: Option<String>,
163 /// Username of whoever merged it.
164 pub merged_by: Option<String>,
165 /// RFC 3339.
166 pub merged_at: Option<String>,
167 /// Set on a pull request closed because another one for the same issue
168 /// was merged: that one's number.
169 pub superseded_by: Option<u32>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents170 /// `failed` when the merge queue took it out because its combined
171 /// state failed, until its head moves. Its checks are the statuses
172 /// workflows report on its head: see `PullDetail::statuses` and
173 /// `PullDetail::required_checks`.
Acceptance checks in sandboxes, line comments and review verdicts174 pub check_status: Option<CheckStatus>,
Agents as a team: lifecycle, merge queue, billing and a new shell175 /// The files it changes, as of its latest push.
176 #[serde(default)]
177 pub files: Vec<ChangedFile>,
178 /// Usernames of the people it is assigned to.
179 #[serde(default)]
180 pub assignees: Vec<String>,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent181 /// Those whose review was asked for: usernames, and `g1t` when a
Agents as a team: lifecycle, merge queue, billing and a new shell182 /// g1t agent was asked.
183 #[serde(default)]
184 pub reviewers: Vec<String>,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights185 /// Who opened it: a person, or g1t (`kind` `agent`, username `g1t`)
186 /// for a change g1t made.
Issues and pull requests replace intents and attempts187 pub author: User,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights188 /// For a change g1t made: the person who asked for it, by assigning
189 /// an issue or handing g1t the work. They answer for it as its author
190 /// would. See [`owner`](Pull::owner).
191 #[serde(default)]
192 pub requested_by: Option<User>,
Work service in Rust, with RFC 3339 timestamps193 /// RFC 3339.
194 pub created_at: String,
195 /// RFC 3339.
196 pub updated_at: String,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step197 /// How sure g1t is of a g1t agent's change, from what it can observe,
198 /// once the agent has finished it. Absent before then, and on changes
199 /// g1t is not seeing through.
200 #[serde(default)]
201 pub confidence: Option<Confidence>,
202}
203
204/// How sure g1t is that an agent's change is right. Low is below medium,
205/// which is below high, so the lower of two is their minimum.
206#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
207#[serde(rename_all = "lowercase")]
208pub enum ConfidenceLevel {
209 Low,
210 Medium,
211 High,
Work service in Rust, with RFC 3339 timestamps212}
213
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step214impl ConfidenceLevel {
215 pub fn as_str(self) -> &'static str {
216 match self {
217 ConfidenceLevel::Low => "low",
218 ConfidenceLevel::Medium => "medium",
219 ConfidenceLevel::High => "high",
220 }
221 }
222
223 pub fn parse(value: &str) -> Option<ConfidenceLevel> {
224 match value.trim().to_ascii_lowercase().as_str() {
225 "low" => Some(ConfidenceLevel::Low),
226 "medium" => Some(ConfidenceLevel::Medium),
227 "high" => Some(ConfidenceLevel::High),
228 _ => None,
229 }
230 }
231}
232
233/// How sure g1t is of a change an agent made, worked out from what can be
234/// observed: its checks, how often it was sent back, the reviewer agent's
235/// verdict, whether it touched tests, its size, where it reached, how close
236/// it came to its guardrails, and what it asked and was not answered. The
237/// agent may say how sure it is too; what g1t observes can only lower that.
238#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
239#[serde(rename_all = "camelCase")]
240pub struct Confidence {
241 pub level: ConfidenceLevel,
242 /// A few words each, most telling first: what lowered it, or for
243 /// `high`, what it rests on.
244 pub reasons: Vec<String>,
245 /// What the agent said of its own change, if it said.
246 #[serde(default)]
247 pub self_reported: Option<ConfidenceLevel>,
248 /// What the agent said it was unsure about.
249 #[serde(default)]
250 pub uncertain_about: Vec<String>,
251 /// The agent run it was worked out after.
252 #[serde(default)]
253 pub run_id: Option<String>,
254 /// RFC 3339.
255 pub assessed_at: String,
256}
257
Agents as a team: lifecycle, merge queue, billing and a new shell258/// One file a pull request changes, and by how much.
259#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
260pub struct ChangedFile {
261 pub path: String,
262 pub additions: u32,
263 pub deletions: u32,
264}
265
266/// Another pull request in progress that changes some of the same files.
267/// Two for the same issue are alternatives; two for different issues are
268/// heading for a conflict.
269#[derive(Clone, Debug, Serialize, Deserialize)]
270pub struct Overlap {
271 pub number: u32,
272 pub title: String,
273 /// The number of the issue the other pull request is for.
274 pub issue: Option<u32>,
275 /// The files both change.
276 pub paths: Vec<String>,
277}
278
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights279impl Issue {
280 /// Who the issue is theirs to manage: the person g1t's agent filed it
281 /// for, or its author. They may edit, close and reopen it without the
282 /// Triage role.
283 pub fn owner(&self) -> &User {
284 self.requested_by.as_ref().unwrap_or(&self.author)
285 }
286}
287
Agents as a team: lifecycle, merge queue, billing and a new shell288impl Pull {
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights289 /// Who the pull request is theirs to answer for: whoever asked g1t to
290 /// make it, or its author. Every rule that once read "its author" reads
291 /// this: they may update, close and steer it, are never asked to review
292 /// it and cannot approve it, see it as theirs, and a sandbox at work on
293 /// it acts as them.
294 pub fn owner(&self) -> &User {
295 self.requested_by.as_ref().unwrap_or(&self.author)
296 }
297
298 /// Whether `id` is its [`owner`](Pull::owner)'s.
299 pub fn is_owned_by(&self, id: &str) -> bool {
300 self.owner().id == id
301 }
302
Agents as a team: lifecycle, merge queue, billing and a new shell303 /// What to ask the repos service to see what this pull request changes.
304 ///
305 /// A fork is compared as a whole. A branch is compared by name while
306 /// the pull request is open, and by the commit it was merged or closed
307 /// at afterwards, so later pushes to the branch do not change the record.
308 pub fn comparison(&self, viewer: &Viewer) -> CompareArgs {
309 let settled = matches!(self.status, PullStatus::Merged | PullStatus::Closed);
310 let (repo_id, head) = match &self.fork_repo_id {
311 Some(fork) => (fork.clone(), None),
312 None => (
313 self.repo_id.clone(),
314 self.head_commit
315 .clone()
316 .filter(|_| settled)
317 .or_else(|| self.branch.clone()),
318 ),
319 };
320 CompareArgs {
321 repo_id,
322 viewer: viewer.clone(),
323 base: self.merge_base.clone(),
324 head,
325 }
326 }
327}
328
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights329/// g1t's agent, as the author of what it opens: stored by
330/// [`AGENT_ID`](crate::identity::AGENT_ID), shown as `g1t`.
331pub fn g1t_author() -> User {
332 User {
333 id: crate::identity::AGENT_ID.to_owned(),
334 username: crate::identity::AGENT_NAME.to_owned(),
335 kind: crate::PrincipalKind::Agent,
336 ..User::default()
337 }
338}
339
340/// Who is recorded as opening an issue or a pull request that `actor`
341/// opens, and who asked for it: `(author, requested_by)`.
342///
343/// - g1t's agent, acting for someone through its token (an issue it files
344/// while at work): g1t, requested by that person.
345/// - A person having g1t make the change (`by_g1t`: a hosted g1t agent in
346/// a fork of its own): g1t, requested by them.
347/// - Anyone else, and g1t's own work that nobody asked for: the actor, and
348/// nobody asking.
349pub fn authorship(actor: &User, by_g1t: bool) -> (User, Option<User>) {
350 let person = |id: &str, username: &str| User {
351 id: id.to_owned(),
352 username: username.to_owned(),
353 kind: crate::PrincipalKind::User,
354 ..User::default()
355 };
356 if actor.kind == crate::PrincipalKind::Agent {
357 let asked = actor
358 .acting
359 .as_ref()
360 .map(|acting| &acting.on_behalf_of)
361 .filter(|on_behalf_of| !crate::system::is_system_id(&on_behalf_of.id))
362 .map(|on_behalf_of| person(&on_behalf_of.id, &on_behalf_of.username));
363 return (g1t_author(), asked);
364 }
365 if by_g1t && actor.kind != crate::PrincipalKind::System {
366 return (g1t_author(), Some(person(&actor.id, &actor.username)));
367 }
368 let author = User {
369 id: actor.id.clone(),
370 username: actor.username.clone(),
371 kind: actor.kind,
372 ..User::default()
373 };
374 (author, None)
375}
376
Acceptance checks in sandboxes, line comments and review verdicts377#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
378#[serde(rename_all = "lowercase")]
379pub enum CheckStatus {
380 /// Waiting for a sandbox.
381 Queued,
382 Running,
383 Passed,
384 Failed,
385 /// The checks could not be run at all.
386 Errored,
387}
388
389impl CheckStatus {
390 pub fn as_str(self) -> &'static str {
391 match self {
392 CheckStatus::Queued => "queued",
393 CheckStatus::Running => "running",
394 CheckStatus::Passed => "passed",
395 CheckStatus::Failed => "failed",
396 CheckStatus::Errored => "errored",
397 }
398 }
399}
400
Fast pages, required checks on the branch, self-hosted runners, honest incidents401/// How one command went. Recorded by earlier runs of commands written on
402/// issues, which g1t no longer runs; kept so their history still reads.
Acceptance checks in sandboxes, line comments and review verdicts403#[derive(Clone, Debug, Serialize, Deserialize)]
404#[serde(rename_all = "camelCase")]
405pub struct CheckResult {
406 pub command: String,
407 pub passed: bool,
408 /// Absent when the command was stopped for taking too long.
409 #[serde(default)]
410 pub exit_code: Option<i32>,
411 /// What the command printed, standard output and error together. The
412 /// end of it, when there was a lot.
413 #[serde(default)]
414 pub output: String,
415 #[serde(default)]
416 pub duration_ms: u64,
417}
418
Fast pages, required checks on the branch, self-hosted runners, honest incidents419/// A record against a pull request's head: the merge queue taking it out,
420/// with why, or an earlier run of commands written on its issue.
Acceptance checks in sandboxes, line comments and review verdicts421#[derive(Clone, Debug, Serialize, Deserialize)]
422#[serde(rename_all = "camelCase")]
423pub struct CheckRun {
424 pub id: String,
425 /// The commit that was checked.
426 pub head_commit: String,
427 pub status: CheckStatus,
428 pub results: Vec<CheckResult>,
429 /// Why the checks could not be run, when `status` is `errored`.
430 pub error: Option<String>,
431 /// RFC 3339.
432 pub created_at: String,
433 /// RFC 3339.
434 pub finished_at: Option<String>,
435}
436
437/// A reviewer's decision on a pull request.
438#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
439#[serde(rename_all = "snake_case")]
440pub enum Verdict {
441 Approve,
442 RequestChanges,
443}
444
445impl Verdict {
446 pub fn as_str(self) -> &'static str {
447 match self {
448 Verdict::Approve => "approve",
449 Verdict::RequestChanges => "request_changes",
450 }
451 }
452}
453
Agents as a team: lifecycle, merge queue, billing and a new shell454/// What an entry in a conversation is.
455#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
456#[serde(rename_all = "lowercase")]
457pub enum CommentKind {
458 /// Something a person or an agent wrote.
459 #[default]
460 Comment,
461 /// Something that happened: an assignment, a review asked for, a close.
462 Event,
463}
464
Acceptance checks in sandboxes, line comments and review verdicts465/// A comment on an issue or a pull request. On a pull request it can sit
466/// on one line of the change, and it can carry a reviewer's verdict.
Issues and pull requests replace intents and attempts467#[derive(Clone, Debug, Serialize, Deserialize)]
468#[serde(rename_all = "camelCase")]
469pub struct Comment {
470 pub id: String,
Agents as a team: lifecycle, merge queue, billing and a new shell471 /// Something a person wrote, or something that happened.
472 #[serde(default)]
473 pub kind: CommentKind,
Issues and pull requests replace intents and attempts474 pub author: User,
Agents as a team: lifecycle, merge queue, billing and a new shell475 /// Markdown. For an event, what its author did, as the rest of a
476 /// sentence that starts with their name: "assigned ana".
Issues and pull requests replace intents and attempts477 pub body: String,
Acceptance checks in sandboxes, line comments and review verdicts478 /// The file commented on, for a comment on a line.
479 pub path: Option<String>,
480 /// The line of that file, as numbered after the change.
481 pub line: Option<u32>,
482 pub verdict: Option<Verdict>,
Issues and pull requests replace intents and attempts483 /// RFC 3339.
484 pub created_at: String,
485}
486
Work service in Rust, with RFC 3339 timestamps487#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
488#[serde(rename_all = "snake_case")]
489pub enum SessionEntryKind {
490 Prompt,
491 Message,
492 ToolCall,
493 ToolResult,
494 Note,
495}
496
Issues and pull requests replace intents and attempts497/// One step of an agent's session: the "why" behind a pull request's commits.
Work service in Rust, with RFC 3339 timestamps498#[derive(Clone, Debug, Serialize, Deserialize)]
499pub struct SessionEntry {
500 pub seq: u32,
501 pub kind: SessionEntryKind,
502 pub text: String,
503 /// For tool calls and results.
504 pub tool: Option<String>,
505 /// The fork's head commit when this entry was recorded, if known.
506 pub commit: Option<String>,
507 /// RFC 3339.
508 pub at: String,
509}
510
511#[derive(Clone, Debug, Serialize, Deserialize)]
512pub struct NewSessionEntry {
513 pub kind: SessionEntryKind,
514 pub text: String,
515 #[serde(default)]
516 pub tool: Option<String>,
517 #[serde(default)]
518 pub commit: Option<String>,
519}
520
521#[derive(Clone, Debug, Serialize, Deserialize)]
Issues and pull requests replace intents and attempts522pub struct IssueDetail {
523 pub issue: Issue,
524 /// Every pull request made against it, oldest first.
525 pub pulls: Vec<Pull>,
526 pub comments: Vec<Comment>,
Work service in Rust, with RFC 3339 timestamps527}
528
529#[derive(Clone, Debug, Serialize, Deserialize)]
Issues and pull requests replace intents and attempts530pub struct PullDetail {
531 pub pull: Pull,
532 /// The issue it is for, if any.
533 pub issue: Option<Issue>,
534 pub comments: Vec<Comment>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents535 /// The latest record against its head: the merge queue taking it out,
536 /// or (from before checks were workflows) a run of its issue's commands.
Acceptance checks in sandboxes, line comments and review verdicts537 pub checks: Option<CheckRun>,
Agents as a team: lifecycle, merge queue, billing and a new shell538 /// Other pull requests in progress that change the same files.
539 #[serde(default)]
540 pub overlaps: Vec<Overlap>,
541 /// Whether the branch it would merge into has moved on without it, so
542 /// that it has to catch up before it can merge.
543 #[serde(default)]
544 pub behind: bool,
545 /// Whether a g1t agent is reviewing it right now.
546 #[serde(default)]
547 pub review_pending: bool,
548 /// Where it stands on its way to being merged, for a pull request g1t
549 /// is seeing through. Absent on anyone else's.
550 #[serde(default)]
551 pub lifecycle: Option<Lifecycle>,
552 /// A merge was asked for while it was behind: g1t is bringing it up to
553 /// date and will then land it.
554 #[serde(default)]
555 pub landing: bool,
556 /// Why g1t stopped working on it, if it did: a catch-up that could not
557 /// be completed, for example.
558 #[serde(default)]
559 pub stalled: Option<String>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request560 /// Messages people sent the agent while it worked, oldest first.
561 #[serde(default)]
562 pub messages: Vec<AgentMessage>,
GitHub Actions on g1t, part two: running workflows563 /// What workflow runs said about its head commit, one per workflow.
564 #[serde(default)]
565 pub statuses: Vec<CommitStatus>,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains566 /// Whether it merges cleanly into the branch it targets, worked out
567 /// ahead of time whenever either side moves.
568 #[serde(default)]
569 pub mergeable: Mergeable,
570 /// When `mergeable` is `conflicting`: the files that conflict.
571 #[serde(default)]
572 pub conflicts: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents573 /// Earlier records like `checks`, newest first, without their output.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains574 #[serde(default)]
575 pub earlier_checks: Vec<CheckRun>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents576 /// The checks the default branch's protection requires, each as it
577 /// stands on the head commit. Empty when none are required.
578 #[serde(default, alias = "requiredChecks")]
579 pub required_checks: Vec<RequiredCheck>,
580}
581
582/// Where a required check stands on a commit.
583#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
584#[serde(rename_all = "lowercase")]
585pub enum RequiredState {
586 Success,
587 Failure,
588 /// Reported and still running.
589 Pending,
590 /// Nothing has reported it on this commit yet.
591 Expected,
592}
593
594impl RequiredState {
595 pub fn as_str(self) -> &'static str {
596 match self {
597 RequiredState::Success => "success",
598 RequiredState::Failure => "failure",
599 RequiredState::Pending => "pending",
600 RequiredState::Expected => "expected",
601 }
602 }
603}
604
605/// One check a branch's protection requires, as it stands on a commit.
606#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
607#[serde(rename_all = "camelCase")]
608pub struct RequiredCheck {
609 /// The check's name, such as `CI` or `g1t / deploy`.
610 pub name: String,
611 pub state: RequiredState,
612 /// What the status that decided it says, if one did.
613 #[serde(default)]
614 pub description: Option<String>,
615 /// Where to see more: the workflow run, for one a workflow reported.
616 #[serde(default)]
617 pub target_url: Option<String>,
618}
619
620/// The events a workflow status's context can end in: `CI / pull_request`
621/// is the `CI` check, reported by a run for a `pull_request` event.
622const STATUS_EVENTS: &[&str] = &[
623 "push",
624 "pull_request",
625 "pull_request_target",
626 "pull_request_review",
627 "merge_group",
628 "workflow_dispatch",
629 "workflow_run",
630 "workflow_call",
631 "schedule",
632 "release",
633 "issues",
634 "issue_comment",
635 "repository_dispatch",
636];
637
638/// A status context's check name and the event it was reported for:
639/// `CI / pull_request` is `("CI", Some("pull_request"))`. A context that
640/// does not end in an event, such as `g1t / deploy`, is its own name.
641pub fn check_name(context: &str) -> (&str, Option<&str>) {
642 match context.rsplit_once(" / ") {
643 Some((name, event)) if STATUS_EVENTS.contains(&event) && !name.trim().is_empty() => (name, Some(event)),
644 _ => (context, None),
645 }
646}
647
648/// Where each required check stands among a commit's statuses. A check is
649/// met by any status of that name, for any event: one that failed fails
650/// it, one still running holds it, and with neither, one that passed
651/// passes it. Names compare without regard to case.
652pub fn required_checks(required: &[String], statuses: &[CommitStatus]) -> Vec<RequiredCheck> {
653 required
654 .iter()
655 .map(|name| {
656 let matching: Vec<&CommitStatus> = statuses
657 .iter()
658 .filter(|status| check_name(&status.context).0.eq_ignore_ascii_case(name.trim()))
659 .collect();
660 let failed = matching.iter().find(|s| s.state == "failure" || s.state == "error");
661 let pending = matching.iter().find(|s| s.state == "pending");
662 let passed = matching.iter().find(|s| s.state == "success");
663 let (state, decided) = match (failed, pending, passed) {
664 (Some(status), _, _) => (RequiredState::Failure, Some(*status)),
665 (None, Some(status), _) => (RequiredState::Pending, Some(*status)),
666 (None, None, Some(status)) => (RequiredState::Success, Some(*status)),
667 _ => (RequiredState::Expected, None),
668 };
669 RequiredCheck {
670 name: name.trim().to_owned(),
671 state,
672 description: decided.and_then(|status| status.description.clone()),
673 target_url: decided.and_then(|status| status.target_url.clone()),
674 }
675 })
676 .collect()
677}
678
679/// A repository's required check names, tidied: trimmed, without blanks
680/// or repeats (ignoring case), at most [`MAX_REQUIRED_CHECKS`].
681pub fn tidy_required(names: &[String]) -> Vec<String> {
682 let mut out: Vec<String> = Vec::new();
683 for name in names {
684 let name: String = name.trim().chars().take(MAX_CHECK_NAME_CHARS).collect();
685 if !name.is_empty() && !out.iter().any(|kept| kept.eq_ignore_ascii_case(&name)) {
686 out.push(name);
687 }
688 }
689 out.truncate(MAX_REQUIRED_CHECKS);
690 out
691}
692
693/// The most checks a branch can require, and the longest name of one.
694pub const MAX_REQUIRED_CHECKS: usize = 20;
695pub const MAX_CHECK_NAME_CHARS: usize = 100;
696
697/// A check name seen on the repository's commits recently, for choosing
698/// required checks: what reported it, and for which events.
699#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
700#[serde(rename_all = "camelCase")]
701pub struct SeenCheck {
702 pub name: String,
703 /// The events it was reported for, such as `pull_request`; empty for a
704 /// status that names none, such as a deployment's.
705 pub events: Vec<String>,
706 /// RFC 3339. The latest report.
707 pub last_seen: String,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains708}
709
Fast pages, required checks on the branch, self-hosted runners, honest incidents710/// `seen_checks`: the check names reported on a repository's commits in
711/// the last 30 days, most recent first. Returns `Outcome<Vec<SeenCheck>>`.
712#[derive(Debug, Serialize, Deserialize)]
713pub struct SeenChecksArgs {
714 pub repo: RepoPath,
715 pub viewer: Viewer,
716}
717
718/// The heading an issue's plain-words description of done goes under.
719pub const DEFINITION_OF_DONE: &str = "## Definition of done";
720
721/// An issue's body with `items` added as a bulleted "Definition of done"
722/// section, for the agent and reviewers to read. Nothing is added when
723/// `items` is empty, or when the body already has the section, so folding
724/// the same items twice changes nothing.
725pub fn with_definition_of_done(body: &str, items: &[String]) -> String {
726 let items: Vec<&str> = items.iter().map(|item| item.trim()).filter(|item| !item.is_empty()).collect();
727 let body = body.trim();
728 if items.is_empty() || body.contains(DEFINITION_OF_DONE) {
729 return body.to_owned();
730 }
731 let list = items.iter().map(|item| format!("- {item}")).collect::<Vec<_>>().join("\n");
732 if body.is_empty() {
733 format!("{DEFINITION_OF_DONE}\n\n{list}")
734 } else {
735 format!("{body}\n\n{DEFINITION_OF_DONE}\n\n{list}")
736 }
737}
738
739/// Commands, as items of a definition of done: "`npm test` passes."
740pub fn commands_pass(commands: &[String]) -> Vec<String> {
741 commands
742 .iter()
743 .map(|command| command.trim())
744 .filter(|command| !command.is_empty())
745 .map(|command| format!("`{}` passes.", command.replace('`', "'")))
746 .collect()
747}
748
Agents and memory, checks and conflicts, profiles, slug renames, custom domains749/// Whether a pull request's change merges cleanly into the branch it
750/// targets.
751#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
752#[serde(rename_all = "lowercase")]
753pub enum Mergeable {
754 /// It merges without conflicts.
755 Clean,
756 /// Some files conflict: see `PullDetail::conflicts`.
757 Conflicting,
758 /// Not known: never worked out, or it could not be.
759 #[default]
760 Unknown,
761 /// Being worked out now.
762 Checking,
GitHub Actions on g1t, part two: running workflows763}
764
Agents and memory, checks and conflicts, profiles, slug renames, custom domains765impl Mergeable {
766 pub fn as_str(self) -> &'static str {
767 match self {
768 Mergeable::Clean => "clean",
769 Mergeable::Conflicting => "conflicting",
770 Mergeable::Unknown => "unknown",
771 Mergeable::Checking => "checking",
772 }
773 }
774
775 pub fn parse(value: Option<&str>) -> Mergeable {
776 match value {
777 Some("clean") => Mergeable::Clean,
778 Some("conflicting") => Mergeable::Conflicting,
779 Some("checking") => Mergeable::Checking,
780 _ => Mergeable::Unknown,
781 }
782 }
783}
784
785/// `start_mergecheck`: claims the probe of whether a pull request merges
786/// cleanly, which the work service asked for with a `pull.mergecheck`
787/// event. Called by the runner service, which starts the sandbox. Refused
788/// when it is no longer wanted, or when the repository already has as many
789/// probes running as it may. Returns `Outcome<MergecheckJob>`.
790#[derive(Debug, Serialize, Deserialize)]
791#[serde(rename_all = "camelCase")]
792pub struct StartMergecheckArgs {
793 pub pull_id: String,
794}
795
796/// What a sandbox needs to find out whether a pull request merges cleanly.
797#[derive(Debug, Serialize, Deserialize)]
798#[serde(rename_all = "camelCase")]
799pub struct MergecheckJob {
800 pub pull_id: String,
801 /// Lets the sandbox, and nothing else, report this probe.
802 pub token: String,
803 pub repo: RepoPath,
804 pub number: u32,
805 pub default_branch: String,
806 /// The default branch's commit to merge into.
807 pub base: String,
808 /// The repository holding the change: its fork, or the repository.
809 pub source: RepoPath,
810 /// The branch of `source` holding it.
811 pub branch: String,
812 /// The change's commit.
813 pub head: String,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights814 /// Who the pull request is for ([`Pull::owner`]: whoever asked g1t for
815 /// it, or its author), and so can read its source.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains816 pub author: User,
817}
818
819/// `report_mergecheck`: what a sandbox found. Returns `Outcome<Mergeable>`.
820#[derive(Debug, Serialize, Deserialize)]
821#[serde(rename_all = "camelCase")]
822pub struct ReportMergecheckArgs {
823 pub pull_id: String,
824 pub token: String,
825 /// The files that conflict; empty when it merges cleanly.
826 #[serde(default)]
827 pub conflicts: Vec<String>,
828 /// Why it could not be found out.
829 #[serde(default)]
830 pub error: Option<String>,
831}
832
GitHub Actions on g1t, part two: running workflows833/// What a workflow run (or another tool) says about a commit.
834#[derive(Clone, Debug, Serialize, Deserialize)]
835#[serde(rename_all = "camelCase")]
836pub struct CommitStatus {
837 /// What reported it, such as `CI / push`.
838 pub context: String,
839 /// `pending`, `success`, `failure` or `error`.
840 pub state: String,
841 pub description: Option<String>,
842 /// Where to see more, such as the run's page.
843 pub target_url: Option<String>,
844 pub updated_at: String,
845}
846
847/// `set_commit_status`: for services only. Returns `Outcome<bool>`.
848#[derive(Debug, Serialize, Deserialize)]
849#[serde(rename_all = "camelCase")]
850pub struct SetCommitStatusArgs {
851 pub repo_id: String,
852 pub sha: String,
853 pub context: String,
854 pub state: String,
855 #[serde(default)]
856 pub description: Option<String>,
857 #[serde(default)]
858 pub target_url: Option<String>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request859}
860
861/// A message a person sent an agent at work on a pull request. The agent
862/// receives it at its next step.
863#[derive(Clone, Debug, Serialize, Deserialize)]
864#[serde(rename_all = "camelCase")]
865pub struct AgentMessage {
866 pub id: String,
867 pub author: String,
868 pub body: String,
869 /// RFC 3339.
870 pub created_at: String,
871 /// RFC 3339. When the agent received it; null until then.
872 pub delivered_at: Option<String>,
Agents ask each other, hand each other work, and answer873 /// `message` from a person, or from another pull request's agent a
874 /// `question`, a `handoff` of work, or the `answer` to one.
875 #[serde(default = "message_kind")]
876 pub kind: String,
877 /// The pull request whose agent sent it, when an agent did.
878 #[serde(default)]
879 pub from_number: Option<u32>,
880 /// The pull request it was sent to.
881 #[serde(default)]
882 pub to_number: u32,
883 /// For a question or handoff: the reply, once there is one.
884 #[serde(default)]
885 pub answer: Option<String>,
886 /// For a handoff: whether it was declined.
887 #[serde(default)]
888 pub declined: bool,
889 /// For the agent that sent it: what to expect, when the agent it asked
890 /// is not at work and will not answer soon.
891 #[serde(default, skip_serializing_if = "Option::is_none")]
892 pub hint: Option<String>,
893}
894
895fn message_kind() -> String {
896 "message".to_owned()
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request897}
898
899/// `message_agent`: sends the agent working on a pull request a message.
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights900/// The pull request's owner ([`Pull::owner`]: whoever asked g1t for it, or
901/// its author) and members of the workspace may. Returns
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request902/// `Outcome<AgentMessage>`.
903#[derive(Debug, Serialize, Deserialize)]
904pub struct MessageAgentArgs {
905 pub actor: User,
906 pub repo: RepoPath,
907 pub number: u32,
908 pub body: String,
Agents ask each other, hand each other work, and answer909 /// For an agent: `question` or `handoff`; a person's is a `message`.
910 #[serde(default)]
911 pub kind: Option<String>,
912 /// For an agent: the pull request it is working on, which the reply
913 /// goes back to.
914 #[serde(default)]
915 pub from_number: Option<u32>,
916}
917
918/// `answer_message`: replies to a question or a handoff an agent received,
919/// accepting or declining a handoff. The reply reaches the asking agent at
920/// its next step. Returns `Outcome<AgentMessage>`, the message answered.
921#[derive(Debug, Serialize, Deserialize)]
922pub struct AnswerMessageArgs {
923 pub actor: User,
924 pub repo: RepoPath,
925 pub id: String,
926 pub body: String,
927 #[serde(default)]
928 pub decline: bool,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request929}
930
931/// `take_messages`: the messages not yet delivered to the agent working on
932/// a pull request, marked delivered. Only g1t's agents may. Returns
933/// `Outcome<Vec<AgentMessage>>`.
934#[derive(Debug, Serialize, Deserialize)]
935pub struct TakeMessagesArgs {
936 pub actor: User,
937 pub repo: RepoPath,
938 pub number: u32,
Agents as a team: lifecycle, merge queue, billing and a new shell939}
940
941/// A step on the way from an assigned issue to a pull request that is ready
942/// to merge. g1t takes each one without being asked.
943#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
944#[serde(rename_all = "snake_case")]
945pub enum Stage {
946 /// The agent is making the change.
947 Working,
Fast pages, required checks on the branch, self-hosted runners, honest incidents948 /// Waiting for the checks workflows report on its head.
Agents as a team: lifecycle, merge queue, billing and a new shell949 Checking,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent950 /// g1t is reviewing it.
Agents as a team: lifecycle, merge queue, billing and a new shell951 Reviewing,
952 /// The agent is addressing failed checks or a review.
953 Revising,
954 /// The agent is merging in the branch it would land on, which moved.
955 CatchingUp,
Agents asked while not at work are woken to answer956 /// Woken to answer a question another agent asked it, or a handoff.
957 Answering,
Agents as a team: lifecycle, merge queue, billing and a new shell958 /// In the repository's merge queue, being tested with what is ahead of
959 /// it before it lands.
960 Queued,
Fast pages, required checks on the branch, self-hosted runners, honest incidents961 /// Required checks passed, reviewed and approved, up to date. A
962 /// person merges.
Agents as a team: lifecycle, merge queue, billing and a new shell963 Ready,
964 /// g1t has stopped and a person has to decide what happens next.
965 NeedsYou,
966}
967
968/// Where a pull request made by a g1t agent stands. See [`Stage`].
969#[derive(Clone, Debug, Serialize, Deserialize)]
970pub struct Lifecycle {
971 pub stage: Stage,
972 /// One sentence saying what is happening, or why it stopped.
973 pub detail: String,
974 /// How many times the agent has been sent back to revise it.
975 pub revisions: u32,
976}
977
978/// `advance`: works out the next step for a pull request g1t is seeing
979/// through and, if there is one to take now, claims it, so that it is
980/// taken once however many times this is called. Called by the runner
981/// service, which carries the step out. Returns `Advance`.
982#[derive(Debug, Serialize, Deserialize)]
983#[serde(rename_all = "camelCase")]
984pub struct AdvanceArgs {
985 pub pull_id: String,
986}
987
988#[derive(Debug, Serialize, Deserialize)]
989#[serde(tag = "action", rename_all = "snake_case")]
990pub enum Advance {
991 /// Nothing to do now: a step is under way, or it is a person's turn.
992 None,
993 /// Have a g1t agent review it.
994 Review { job: LifecycleJob },
995 /// Send the agent back to address `job.feedback`.
996 Revise { job: LifecycleJob },
997 /// Merge in the branch it would land on.
998 CatchUp { job: LifecycleJob },
Work service in Rust, with RFC 3339 timestamps999}
1000
Agents as a team: lifecycle, merge queue, billing and a new shell1001/// What the runner needs to carry out a step of a pull request's lifecycle.
1002#[derive(Debug, Serialize, Deserialize)]
1003#[serde(rename_all = "camelCase")]
1004pub struct LifecycleJob {
1005 pub pull_id: String,
1006 pub repo: RepoPath,
1007 pub number: u32,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1008 /// Who the pull request belongs to ([`Pull::owner`]: whoever asked g1t
1009 /// for it, or its author). Sandboxes act as them.
Agents as a team: lifecycle, merge queue, billing and a new shell1010 pub author: User,
1011 /// The repository holding the change: its fork, or the repository
1012 /// itself for one made on a branch.
1013 pub source: RepoPath,
1014 /// The branch of the source holding the change; its default branch
1015 /// when absent.
1016 #[serde(default)]
1017 pub branch: Option<String>,
1018 pub default_branch: String,
1019 pub title: String,
1020 pub description: String,
1021 pub issue: Option<Issue>,
1022 /// For a revision: the failed checks or the review to address.
1023 pub feedback: String,
1024 /// For a revision: which one this is, from 1.
1025 pub round: u32,
1026}
1027
1028/// How a repository wants its pull requests handled. A repository that has
1029/// changed nothing has the defaults.
1030#[derive(Clone, Debug, Serialize, Deserialize)]
1031#[serde(rename_all = "camelCase", default)]
1032pub struct RepoSettings {
1033 /// Land a g1t agent's pull request without a person once it is ready:
Fast pages, required checks on the branch, self-hosted runners, honest incidents1034 /// required checks passed and approved as the settings below require.
Agents as a team: lifecycle, merge queue, billing and a new shell1035 pub auto_merge: bool,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1036 /// The checks that must pass on a pull request's head before it may
1037 /// merge into the default branch, by name: a workflow's name (`CI`), or
1038 /// the context of another status (`g1t / deploy`). The same for a
1039 /// person's pull request and an agent's, and for the merge queue.
1040 pub required_checks: Vec<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell1041 /// Refuse to merge a pull request that does not contain the default
1042 /// branch's latest commits, so that what merges is what was checked.
1043 /// When off, merging one that is behind brings it up to date first.
1044 pub require_up_to_date: bool,
1045 /// How many approving reviews a pull request needs before it may
1046 /// merge. A reviewer who has since asked for changes blocks it.
1047 pub required_approvals: u32,
1048 /// Whether a g1t agent's approval counts towards `required_approvals`.
1049 pub count_agent_approvals: bool,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1050 /// Whether someone who may merge can bypass required checks that have
1051 /// not passed, by saying so as they merge.
Agents as a team: lifecycle, merge queue, billing and a new shell1052 pub allow_ignoring_checks: bool,
1053 /// Whether a g1t agent's pull request is reviewed by a second agent
1054 /// without being asked.
1055 pub agent_review: bool,
1056 /// How many times a g1t agent is sent back to its pull request before
1057 /// a person is asked instead.
1058 pub max_revisions: u32,
1059 /// Merge through a queue: pull requests are tested together with those
1060 /// ahead of them, and only a combination that passed reaches the default
1061 /// branch.
1062 pub merge_queue: bool,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1063 /// Ask a person before merging a g1t agent's change whose confidence is
1064 /// low: auto-merge and the merge queue leave it, and it needs someone,
1065 /// until a person approves it.
1066 pub hold_low_confidence: bool,
Agents as a team: lifecycle, merge queue, billing and a new shell1067 /// Username of the member who last changed the settings, if anyone has.
1068 pub updated_by: Option<String>,
1069 /// RFC 3339.
1070 pub updated_at: Option<String>,
1071}
1072
1073impl Default for RepoSettings {
1074 fn default() -> Self {
1075 RepoSettings {
1076 auto_merge: false,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1077 required_checks: Vec::new(),
Agents as a team: lifecycle, merge queue, billing and a new shell1078 require_up_to_date: false,
1079 required_approvals: 0,
1080 count_agent_approvals: true,
1081 allow_ignoring_checks: true,
1082 agent_review: true,
1083 max_revisions: 2,
1084 merge_queue: false,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1085 hold_low_confidence: true,
Agents as a team: lifecycle, merge queue, billing and a new shell1086 updated_by: None,
1087 updated_at: None,
1088 }
1089 }
1090}
1091
1092/// `update_settings`: replaces a repository's settings. Members of its
1093/// workspace only. Returns `Outcome<RepoSettings>`. `get_settings` takes
1094/// `ViewArgs` and returns the same.
1095#[derive(Debug, Serialize, Deserialize)]
1096pub struct UpdateSettingsArgs {
1097 pub actor: User,
1098 pub repo: RepoPath,
1099 /// Who changed them and when are filled in by the service.
1100 pub settings: RepoSettings,
1101}
1102
1103/// `catch_up_job`: what the runner needs to bring a pull request up to date
1104/// because a merge of it was asked for. Null if none was. Returns
1105/// `Option<LifecycleJob>`.
1106#[derive(Debug, Serialize, Deserialize)]
1107#[serde(rename_all = "camelCase")]
1108pub struct CatchUpJobArgs {
1109 pub pull_id: String,
1110}
1111
Agents asked while not at work are woken to answer1112/// `wake_for_messages`: the agent on a pull request was asked a question
1113/// or handed work while it was not at work. Claims a short step for it to
1114/// answer, and hands over what it was sent, marked read. Null when there
1115/// is nothing waiting, or the pull request cannot take a step now.
1116/// Returns `Option<Wake>`.
1117#[derive(Debug, Serialize, Deserialize)]
1118#[serde(rename_all = "camelCase")]
1119pub struct WakeForMessagesArgs {
1120 pub pull_id: String,
1121}
1122
1123/// What an agent woken to answer needs: its pull request, and what it was
1124/// sent, oldest first.
1125#[derive(Debug, Serialize, Deserialize)]
1126#[serde(rename_all = "camelCase")]
1127pub struct Wake {
1128 pub job: LifecycleJob,
1129 pub messages: Vec<AgentMessage>,
1130}
1131
Agents as a team: lifecycle, merge queue, billing and a new shell1132/// `stall`: records that a step could not be carried out, so that g1t
1133/// stops and a person is asked. Returns `bool`.
1134#[derive(Debug, Serialize, Deserialize)]
1135#[serde(rename_all = "camelCase")]
1136pub struct StallArgs {
1137 pub pull_id: String,
1138 pub reason: String,
Events: review requests, assignments, stops and deployments are published1139 /// The person who stopped it, by id, when someone did: they are not
1140 /// told it needs them.
1141 #[serde(default)]
1142 pub by: Option<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell1143}
1144
1145/// `managed_pulls`: ids of the open pull requests g1t is seeing through,
1146/// in one repository or in all of them. Returns `Vec<String>`.
1147#[derive(Debug, Default, Serialize, Deserialize)]
1148#[serde(rename_all = "camelCase")]
1149pub struct ManagedPullsArgs {
1150 #[serde(default)]
1151 pub repo_id: Option<String>,
1152}
1153
Issues and pull requests replace intents and attempts1154/// `open_issue`. Returns `Outcome<Issue>`.
Work service in Rust, with RFC 3339 timestamps1155#[derive(Debug, Serialize, Deserialize)]
Issues and pull requests replace intents and attempts1156pub struct OpenIssueArgs {
Work service in Rust, with RFC 3339 timestamps1157 pub actor: User,
1158 pub repo: RepoPath,
1159 pub title: String,
1160 #[serde(default)]
Issues and pull requests replace intents and attempts1161 pub body: String,
1162 #[serde(default)]
1163 pub labels: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1164 /// Deprecated: commands, added to the body under "Definition of done".
1165 /// Checks are the workflows the branch's protection requires.
Work service in Rust, with RFC 3339 timestamps1166 #[serde(default)]
1167 pub checks: Vec<String>,
1168}
1169
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1170/// `delegate_issue`: opens an issue to put g1t on at once, refused
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1171/// before anything is opened unless `actor` may put agents to work in the
1172/// repository (Run, which the Write role has). The runner service's
1173/// `delegate` calls it and then starts the agent. Returns `Outcome<Issue>`.
1174#[derive(Debug, Serialize, Deserialize)]
1175pub struct DelegateIssueArgs {
1176 pub actor: User,
1177 pub repo: RepoPath,
1178 pub title: String,
1179 #[serde(default)]
1180 pub body: String,
1181 #[serde(default)]
1182 pub labels: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1183 /// Deprecated, as on `OpenIssueArgs`.
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1184 #[serde(default)]
1185 pub checks: Vec<String>,
1186}
1187
1188/// What became of the agent when an issue was opened and handed to it in
1189/// one step.
1190#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1191#[serde(rename_all = "snake_case")]
1192pub enum AgentStartStatus {
1193 /// It is at work on the issue's pull request.
1194 Started,
1195 /// Every agent slot of the workspace is busy: it starts on its own when
1196 /// one frees up.
1197 Queued,
1198 /// It did not start, and will not until someone fixes what `code` says.
1199 NotStarted,
1200}
1201
1202/// Whether the agent started, and if not, why and what fixes it.
1203#[derive(Clone, Debug, Serialize, Deserialize)]
1204#[serde(rename_all = "camelCase")]
1205pub struct AgentStart {
1206 pub status: AgentStartStatus,
1207 /// Why it did not start: `not_paid`, `trial_used`, `limit`, `paused`,
1208 /// `issue_cap`, `billing_unavailable` or `no_model`; `waiting` when
1209 /// queued.
1210 #[serde(default)]
1211 pub code: Option<String>,
1212 /// What happened, in a sentence or two, with what to do.
1213 #[serde(default)]
1214 pub message: Option<String>,
1215 /// Where the fix is: the workspace's billing or model settings.
1216 #[serde(default)]
1217 pub fix_url: Option<String>,
1218}
1219
1220/// The runner service's `delegate`: the issue opened, and the agent put on
1221/// it. The issue exists whatever became of the agent.
1222#[derive(Clone, Debug, Serialize, Deserialize)]
1223pub struct Delegated {
1224 pub issue: Issue,
1225 /// The pull request the agent opened, when it started.
1226 #[serde(default)]
1227 pub pull: Option<Pull>,
1228 pub agent: AgentStart,
1229}
1230
1231/// `report_confidence`: what the agent of a run says of its own change,
1232/// with the run's own token. Kept with the run, and the pull request's
1233/// confidence is worked out again with it. Returns `Outcome<bool>`.
1234#[derive(Debug, Serialize, Deserialize)]
1235#[serde(rename_all = "camelCase")]
1236pub struct ReportConfidenceArgs {
1237 pub run_id: String,
1238 pub token: String,
1239 /// `high`, `medium` or `low`.
1240 pub confidence: String,
1241 #[serde(default)]
1242 pub uncertain_about: Vec<String>,
1243}
1244
Issues and pull requests replace intents and attempts1245/// `list_issues`, newest first. Returns `Outcome<Vec<Issue>>`.
Work service in Rust, with RFC 3339 timestamps1246#[derive(Debug, Serialize, Deserialize)]
Issues and pull requests replace intents and attempts1247pub struct ListIssuesArgs {
Work service in Rust, with RFC 3339 timestamps1248 pub repo: RepoPath,
1249 pub viewer: Viewer,
1250 #[serde(default)]
Issues and pull requests replace intents and attempts1251 pub state: Option<State>,
1252 /// Only issues carrying this label.
1253 #[serde(default)]
1254 pub label: Option<String>,
Work service in Rust, with RFC 3339 timestamps1255}
1256
Issues and pull requests replace intents and attempts1257/// `list_pulls`, newest first. Returns `Outcome<Vec<Pull>>`.
Work service in Rust, with RFC 3339 timestamps1258#[derive(Debug, Serialize, Deserialize)]
Issues and pull requests replace intents and attempts1259pub struct ListPullsArgs {
1260 pub repo: RepoPath,
1261 pub viewer: Viewer,
1262 #[serde(default)]
1263 pub state: Option<State>,
1264}
1265
Fast pages, required checks on the branch, self-hosted runners, honest incidents1266/// `pulls_for_repos`: the newest open and the newest closed pull requests
1267/// of many repositories, in one call, for pages that show several projects
1268/// at once. Repositories the viewer cannot read are left out, as are forks
1269/// (ask those with `list_pulls`). Returns `Vec<RepoPulls>`.
1270#[derive(Debug, Serialize, Deserialize)]
1271#[serde(rename_all = "camelCase")]
1272pub struct PullsForReposArgs {
1273 /// At most [`MAX_PULLS_FOR_REPOS`] are looked at.
1274 pub repo_ids: Vec<String>,
1275 pub viewer: Viewer,
1276 /// How many of each, open and closed, per repository (at most 100).
1277 pub limit: u32,
1278}
1279
1280/// The most repositories one `pulls_for_repos` call looks at.
1281pub const MAX_PULLS_FOR_REPOS: usize = 50;
1282
1283/// One repository's pull requests from `pulls_for_repos`, newest first.
1284#[derive(Debug, Serialize, Deserialize)]
1285#[serde(rename_all = "camelCase")]
1286pub struct RepoPulls {
1287 pub repo_id: String,
1288 /// Draft and open.
1289 pub open: Vec<Pull>,
1290 /// Merged and closed.
1291 pub closed: Vec<Pull>,
1292}
1293
Issues and pull requests replace intents and attempts1294/// `get_issue` (`Outcome<IssueDetail>`), `get_pull` (`Outcome<PullDetail>`),
1295/// `read_session` (`Outcome<Vec<SessionEntry>>`), `list_labels`
1296/// (`Outcome<Vec<String>>`) and `counts` (`Outcome<Counts>`). The last two
1297/// ignore `number`.
1298#[derive(Debug, Serialize, Deserialize)]
1299#[serde(rename_all = "camelCase")]
1300pub struct ViewArgs {
Work service in Rust, with RFC 3339 timestamps1301 pub repo: RepoPath,
Issues and pull requests replace intents and attempts1302 #[serde(default)]
Work service in Rust, with RFC 3339 timestamps1303 pub number: u32,
1304 pub viewer: Viewer,
Issues and pull requests replace intents and attempts1305 /// For `read_session`: only entries after this sequence number.
1306 #[serde(default)]
1307 pub after_seq: u32,
1308}
1309
1310/// How many issues and pull requests are open on a repository.
1311#[derive(Debug, Serialize, Deserialize)]
1312pub struct Counts {
1313 pub issues: u32,
1314 pub pulls: u32,
1315}
1316
1317/// `update_issue`: changes whichever fields are given. The author or a
1318/// member of the workspace may. Returns `Outcome<Issue>`.
1319#[derive(Debug, Serialize, Deserialize)]
1320pub struct UpdateIssueArgs {
1321 pub actor: User,
1322 pub repo: RepoPath,
1323 pub number: u32,
1324 #[serde(default)]
1325 pub title: Option<String>,
1326 #[serde(default)]
1327 pub body: Option<String>,
1328 #[serde(default)]
1329 pub labels: Option<Vec<String>>,
Agents as a team: lifecycle, merge queue, billing and a new shell1330 /// Usernames of the people it is assigned to; replaces the whole set.
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1331 /// Assigning it to g1t is the runner's `run`, not this.
Agents as a team: lifecycle, merge queue, billing and a new shell1332 #[serde(default)]
1333 pub assignees: Option<Vec<String>>,
Work service in Rust, with RFC 3339 timestamps1334}
1335
Issues and pull requests replace intents and attempts1336/// `close_issue` and `reopen_issue`. Each returns `Outcome<Issue>`.
Work service in Rust, with RFC 3339 timestamps1337#[derive(Debug, Serialize, Deserialize)]
Issues and pull requests replace intents and attempts1338pub struct IssueActionArgs {
Work service in Rust, with RFC 3339 timestamps1339 pub actor: User,
Issues and pull requests replace intents and attempts1340 pub repo: RepoPath,
1341 pub number: u32,
1342 /// For `close_issue`; `completed` if left out.
1343 #[serde(default)]
1344 pub reason: Option<IssueReason>,
Work service in Rust, with RFC 3339 timestamps1345}
1346
Acceptance checks in sandboxes, line comments and review verdicts1347/// `add_comment`, on an issue or a pull request. On a pull request it may
1348/// name a line of the change, and may carry a verdict; nobody can give a
1349/// verdict on their own pull request. Returns `Outcome<Comment>`.
Work service in Rust, with RFC 3339 timestamps1350#[derive(Debug, Serialize, Deserialize)]
Issues and pull requests replace intents and attempts1351pub struct AddCommentArgs {
Work service in Rust, with RFC 3339 timestamps1352 pub actor: User,
Issues and pull requests replace intents and attempts1353 pub repo: RepoPath,
1354 pub number: u32,
Acceptance checks in sandboxes, line comments and review verdicts1355 /// May be empty when approving.
1356 #[serde(default)]
Issues and pull requests replace intents and attempts1357 pub body: String,
Acceptance checks in sandboxes, line comments and review verdicts1358 #[serde(default)]
1359 pub path: Option<String>,
1360 #[serde(default)]
1361 pub line: Option<u32>,
1362 #[serde(default)]
1363 pub verdict: Option<Verdict>,
Work service in Rust, with RFC 3339 timestamps1364}
1365
Pull requests from branches1366/// `open_pull`. Without `branch`, forks the repo and returns a draft pull
1367/// request to push to. With it, opens a pull request, ready for review,
1368/// for a branch already pushed to the repo. Returns `Outcome<Pull>`.
Work service in Rust, with RFC 3339 timestamps1369#[derive(Debug, Serialize, Deserialize)]
Issues and pull requests replace intents and attempts1370pub struct OpenPullArgs {
1371 pub actor: User,
1372 pub repo: RepoPath,
1373 /// The issue this is for.
Work service in Rust, with RFC 3339 timestamps1374 #[serde(default)]
Issues and pull requests replace intents and attempts1375 pub issue: Option<u32>,
1376 /// Defaults to the issue's title; required without an issue.
1377 #[serde(default)]
1378 pub title: String,
Pull requests from branches1379 /// What changed and why. Usually set later, when a draft is marked ready.
1380 #[serde(default)]
1381 pub body: String,
1382 /// A branch of the repository that already holds the change.
1383 #[serde(default)]
1384 pub branch: Option<String>,
Issues and pull requests replace intents and attempts1385 #[serde(default)]
1386 pub agent: String,
1387 pub runtime: Runtime,
Work service in Rust, with RFC 3339 timestamps1388}
1389
Issues and pull requests replace intents and attempts1390/// `ready_pull`, `close_pull` and `merge_pull`. Each returns `Outcome<Pull>`.
Catching up with main takes seconds when the two sides touched different files1391///
1392/// Also `catch_up_pull`: brings the pull request up to date with the
1393/// default branch without a sandbox where that is safe, as the repos
1394/// service's `update_pull_branch` does, after checking that `actor` may
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1395/// update it: its owner ([`Pull::owner`]) for a fork, any member for a
1396/// branch.
Catching up with main takes seconds when the two sides touched different files1397/// Returns `Outcome<repos::PullBranchUpdate>`; on `needs_agent` nothing was
1398/// pushed and the runner's `update` is the way on.
Work service in Rust, with RFC 3339 timestamps1399#[derive(Debug, Serialize, Deserialize)]
1400#[serde(rename_all = "camelCase")]
Issues and pull requests replace intents and attempts1401pub struct PullActionArgs {
Work service in Rust, with RFC 3339 timestamps1402 pub actor: User,
Issues and pull requests replace intents and attempts1403 pub repo: RepoPath,
1404 pub number: u32,
1405 /// For `ready_pull`: what changed and why.
Work service in Rust, with RFC 3339 timestamps1406 #[serde(default)]
1407 pub summary: String,
Issues and pull requests replace intents and attempts1408 /// For `merge_pull`: leave the issue open and the other pull requests
1409 /// for it untouched, because this one is only part of the work.
1410 #[serde(default)]
1411 pub keep_issue_open: bool,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1412 /// For `merge_pull`: merge although required checks have not passed,
1413 /// where the repository lets members bypass them.
Acceptance checks in sandboxes, line comments and review verdicts1414 #[serde(default)]
1415 pub ignore_checks: bool,
1416}
1417
Agents as a team: lifecycle, merge queue, billing and a new shell1418/// Where a plan stands.
1419#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1420#[serde(rename_all = "lowercase")]
1421pub enum PlanStatus {
1422 /// An agent is reading the repository and writing it.
1423 Planning,
1424 /// Written, and waiting for a person to read and apply it.
1425 Ready,
1426 /// It could not be written.
1427 Failed,
1428 /// Its issues have been opened.
1429 Applied,
1430}
1431
1432/// One issue a plan proposes.
1433#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1434#[serde(rename_all = "camelCase", default)]
1435pub struct PlannedIssue {
1436 pub title: String,
1437 /// Markdown: what to change, where, and why.
1438 pub body: String,
1439 pub labels: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1440 /// What is true once it is done, in plain words. Added to the issue's
1441 /// body under "Definition of done". Plans written before this was
1442 /// called `done` named it `checks`.
1443 #[serde(alias = "checks")]
1444 pub done: Vec<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell1445 /// The files it will most likely change.
1446 pub files: Vec<String>,
1447 /// The positions, counting from 1, of earlier issues in the plan that
1448 /// have to be merged first. An agent writes this as `depends_on`.
1449 #[serde(alias = "depends_on")]
1450 pub depends_on: Vec<u32>,
1451 /// Its number, once the plan has been applied and it was kept.
1452 pub number: Option<u32>,
1453}
1454
1455/// An outcome someone wrote, and the issues an agent proposes to get there.
1456#[derive(Clone, Debug, Serialize, Deserialize)]
1457#[serde(rename_all = "camelCase")]
1458pub struct Plan {
1459 pub id: String,
1460 pub repo_id: String,
1461 /// The outcome wanted, as written.
1462 pub brief: String,
1463 pub status: PlanStatus,
1464 /// The agent's account of how it split the work.
1465 pub summary: String,
1466 pub issues: Vec<PlannedIssue>,
1467 /// Why it could not be written, when `status` is `failed`.
1468 pub error: Option<String>,
1469 pub author: User,
1470 /// RFC 3339.
1471 pub created_at: String,
1472 /// RFC 3339.
1473 pub finished_at: Option<String>,
Dark gray base with lavender as an accent, and a live outcome view for plans1474 /// Once applied: where each issue it opened stands now, in plan order.
1475 /// Filled in by `get_plan` only.
1476 #[serde(default)]
1477 pub progress: Vec<IssueProgress>,
Agents ask each other, hand each other work, and answer1478 /// Questions and handoffs between the agents on its pull requests,
1479 /// newest first. Filled in by `get_plan` only.
1480 #[serde(default)]
1481 pub exchanges: Vec<AgentMessage>,
Dark gray base with lavender as an accent, and a live outcome view for plans1482}
1483
1484/// Where one issue of an applied plan stands.
1485#[derive(Clone, Debug, Serialize, Deserialize)]
1486#[serde(rename_all = "camelCase")]
1487pub struct IssueProgress {
1488 pub number: u32,
1489 pub title: String,
1490 /// `blocked` (waiting on issues it depends on), `waiting` (for an
1491 /// agent), `open` (nobody on it), one of the lifecycle stages
1492 /// (`working`, `checking`, `reviewing`, `revising`, `catching_up`,
Agents asked while not at work are woken to answer1493 /// `answering`, `queued`, `ready`, `needs_you`), `landed` or `closed`.
Dark gray base with lavender as an accent, and a live outcome view for plans1494 pub state: String,
1495 /// One sentence about where it stands.
1496 pub detail: String,
1497 /// The issues it is waiting on that are still open.
1498 pub blocked_by: Vec<u32>,
1499 /// The pull request carrying it, the newest if several.
1500 pub pull: Option<u32>,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1501 /// Who or what is working on it, e.g. `g1t`.
Dark gray base with lavender as an accent, and a live outcome view for plans1502 pub agent: Option<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell1503}
1504
1505/// `start_plan`: records an outcome to plan for. Members of the
1506/// repository's workspace only. Called by the runner service, which starts
1507/// the sandbox. Returns `Outcome<PlanJob>`.
1508#[derive(Debug, Serialize, Deserialize)]
1509pub struct StartPlanArgs {
1510 pub actor: User,
1511 pub repo: RepoPath,
1512 pub brief: String,
1513}
1514
1515/// What a sandbox needs to write a plan.
1516#[derive(Debug, Serialize, Deserialize)]
1517#[serde(rename_all = "camelCase")]
1518pub struct PlanJob {
1519 pub plan_id: String,
1520 /// Lets the sandbox, and nothing else, report this plan.
1521 pub token: String,
1522 pub brief: String,
1523 pub repo: RepoPath,
1524}
1525
1526/// `report_plan`: the plan a sandbox's agent wrote, or why it could not
1527/// write one. Returns `Outcome<bool>`.
1528#[derive(Debug, Serialize, Deserialize)]
1529#[serde(rename_all = "camelCase")]
1530pub struct ReportPlanArgs {
1531 pub plan_id: String,
1532 pub token: String,
1533 #[serde(default)]
1534 pub summary: String,
1535 #[serde(default)]
1536 pub issues: Vec<PlannedIssue>,
1537 #[serde(default)]
1538 pub error: Option<String>,
1539}
1540
1541/// `get_plan`. Members only. Returns `Outcome<Plan>`. `list_plans` takes
1542/// `ViewArgs` and returns `Outcome<Vec<Plan>>`, newest first.
1543#[derive(Debug, Serialize, Deserialize)]
1544pub struct PlanArgs {
1545 pub repo: RepoPath,
1546 pub viewer: Viewer,
1547 pub id: String,
1548}
1549
1550/// `apply_plan`: opens a plan's issues, each blocked by the ones it depends
1551/// on. Members only, and once. Returns `Outcome<Plan>`, its issues now
1552/// carrying their numbers.
1553#[derive(Debug, Serialize, Deserialize)]
1554pub struct ApplyPlanArgs {
1555 pub actor: User,
1556 pub repo: RepoPath,
1557 pub id: String,
1558 /// Queue every issue for a g1t agent.
1559 #[serde(default)]
1560 pub assign: bool,
1561 /// The positions, counting from 1, of the issues to open. All of them
1562 /// when absent.
1563 #[serde(default)]
1564 pub keep: Option<Vec<u32>>,
1565}
1566
1567/// `queue_issue`: asks for a g1t agent to take an issue as soon as it can,
1568/// or withdraws that. The author or a member may. Returns `Outcome<bool>`.
1569#[derive(Debug, Serialize, Deserialize)]
1570pub struct QueueIssueArgs {
1571 pub actor: User,
1572 pub repo: RepoPath,
1573 pub number: u32,
1574 pub queued: bool,
1575}
1576
1577/// `ready_issues`: issues waiting for a g1t agent that can be given one
1578/// now, in one repository or in all. Called by the runner service. Returns
1579/// `Vec<ReadyIssue>`.
1580#[derive(Debug, Default, Serialize, Deserialize)]
1581#[serde(rename_all = "camelCase")]
1582pub struct ReadyIssuesArgs {
1583 #[serde(default)]
1584 pub repo_id: Option<String>,
1585}
1586
1587#[derive(Debug, Serialize, Deserialize)]
1588pub struct ReadyIssue {
1589 pub repo: RepoPath,
1590 pub number: u32,
1591 /// Who queued it, on whose say-so the agent works.
1592 pub actor: User,
1593}
1594
1595/// `update_pull`: changes who a pull request is assigned to and whose
1596/// review is asked for. Each list given replaces the whole set. Whoever
1597/// opened it, or a member of the workspace, may. Returns `Outcome<Pull>`.
1598#[derive(Debug, Serialize, Deserialize)]
1599pub struct UpdatePullArgs {
1600 pub actor: User,
1601 pub repo: RepoPath,
1602 pub number: u32,
1603 #[serde(default)]
1604 pub assignees: Option<Vec<String>>,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1605 /// May include `g1t`. Asking for its review does not by itself
Agents as a team: lifecycle, merge queue, billing and a new shell1606 /// start one; the runner's `review` does.
1607 #[serde(default)]
1608 pub reviewers: Option<Vec<String>>,
1609}
1610
Fast pages, required checks on the branch, self-hosted runners, honest incidents1611/// `start_checks`: always refused now; a pull request's checks are the
1612/// workflows run on it. Kept so that a runner from before is answered.
1613/// Returns `Outcome<CheckJob>`.
Acceptance checks in sandboxes, line comments and review verdicts1614#[derive(Debug, Serialize, Deserialize)]
1615#[serde(rename_all = "camelCase")]
1616pub struct StartChecksArgs {
1617 pub pull_id: String,
1618}
1619
1620/// What a sandbox needs to carry out a check run.
1621#[derive(Debug, Serialize, Deserialize)]
1622#[serde(rename_all = "camelCase")]
1623pub struct CheckJob {
1624 pub run_id: String,
1625 /// Lets the sandbox, and nothing else, report this run's results.
1626 pub token: String,
1627 pub commands: Vec<String>,
1628 /// The repository holding the commit: the fork, or the repository itself.
1629 pub source: RepoPath,
1630 pub commit: String,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1631 /// Who the pull request is for ([`Pull::owner`]: whoever asked g1t for
1632 /// it, or its author), and so can read its source.
Acceptance checks in sandboxes, line comments and review verdicts1633 pub author: User,
1634 /// Username of whoever wrote the checks: the issue's author.
1635 pub requested_by: String,
1636 pub repo: RepoPath,
1637 pub number: u32,
1638}
1639
1640/// `report_checks`: what a sandbox says about its run. With no results and
1641/// no error it has started. `skip` forgets the run, for one that will not
1642/// be carried out. Returns `Outcome<CheckRun>`.
1643#[derive(Debug, Serialize, Deserialize)]
1644#[serde(rename_all = "camelCase")]
1645pub struct ReportChecksArgs {
1646 pub run_id: String,
1647 pub token: String,
1648 #[serde(default)]
1649 pub results: Vec<CheckResult>,
1650 #[serde(default)]
1651 pub error: Option<String>,
1652 #[serde(default)]
1653 pub skip: bool,
Work service in Rust, with RFC 3339 timestamps1654}
1655
Issues and pull requests replace intents and attempts1656/// A pull request in progress, with where it lives.
1657#[derive(Debug, Serialize, Deserialize)]
1658pub struct ActivePull {
1659 pub pull: Pull,
1660 pub issue: Option<Issue>,
Agents as a team: lifecycle, merge queue, billing and a new shell1661 /// Where it stands, for one g1t is seeing through.
1662 #[serde(default)]
1663 pub lifecycle: Option<Lifecycle>,
Issues and pull requests replace intents and attempts1664}
1665
1666/// `list_active_pulls`: drafts and open pull requests the viewer started,
Agents as a team: lifecycle, merge queue, billing and a new shell1667/// most recently active first. Returns `Vec<ActivePull>`. Also
1668/// `list_assigned_issues`: open issues assigned to the viewer, most
1669/// recently changed first. Returns `Vec<Issue>`.
Work service in Rust, with RFC 3339 timestamps1670#[derive(Debug, Serialize, Deserialize)]
1671pub struct ViewerArgs {
1672 pub viewer: Viewer,
1673}
1674
1675/// `append_session`. Returns `Outcome<Appended>`.
1676#[derive(Debug, Serialize, Deserialize)]
1677pub struct AppendSessionArgs {
1678 pub actor: User,
Issues and pull requests replace intents and attempts1679 pub repo: RepoPath,
1680 pub number: u32,
Work service in Rust, with RFC 3339 timestamps1681 pub entries: Vec<NewSessionEntry>,
1682}
1683
1684#[derive(Debug, Serialize, Deserialize)]
1685pub struct Appended {
1686 pub count: u32,
1687}
Issues and pull requests replace intents and attempts1688
Agents as a team: lifecycle, merge queue, billing and a new shell1689/// `start_review`: begins a review of a pull request by a g1t agent. Called
1690/// by the runner service, which starts the sandbox. Returns
1691/// `Outcome<ReviewJob>`.
1692#[derive(Debug, Serialize, Deserialize)]
1693#[serde(rename_all = "camelCase")]
1694pub struct StartReviewArgs {
1695 pub pull_id: String,
1696}
1697
1698/// What a sandbox needs to review a pull request.
1699#[derive(Debug, Serialize, Deserialize)]
1700#[serde(rename_all = "camelCase")]
1701pub struct ReviewJob {
1702 pub run_id: String,
1703 /// Lets the sandbox, and nothing else, report this review.
1704 pub token: String,
1705 /// The repository holding the commit: the fork, or the repository itself.
1706 pub source: RepoPath,
1707 pub commit: String,
1708 pub repo: RepoPath,
1709 pub default_branch: String,
1710 pub number: u32,
1711 pub title: String,
1712 pub description: String,
1713 /// The issue the pull request is for, which says what it should achieve.
1714 pub issue: Option<Issue>,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1715 /// Who the pull request is for ([`Pull::owner`]: whoever asked g1t for
1716 /// it, or its author), and so can read its source.
Agents as a team: lifecycle, merge queue, billing and a new shell1717 pub author: User,
Auto model routing: the cheapest tier that can do each piece of work, a retry goes up a tier, and each run records its tier1718 /// The files it changes, as of its latest push: how large the change
1719 /// is, which decides the model that reviews it.
1720 #[serde(default)]
1721 pub files: Vec<ChangedFile>,
1722 /// What among them runs, configures or guards things (CI workflows,
1723 /// secrets, infrastructure), once each. Any sends the review to the
1724 /// larger model.
1725 #[serde(default)]
1726 pub sensitive: Vec<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell1727}
1728
1729/// A comment on one line, as a reviewing agent reports it.
1730#[derive(Debug, Serialize, Deserialize)]
1731pub struct ReviewComment {
1732 pub path: String,
1733 #[serde(default)]
1734 pub line: u32,
1735 pub body: String,
1736}
1737
1738/// `report_review`: the review a sandbox's agent wrote, or why it could not
1739/// write one. Returns `Outcome<bool>`.
1740#[derive(Debug, Serialize, Deserialize)]
1741#[serde(rename_all = "camelCase")]
1742pub struct ReportReviewArgs {
1743 pub run_id: String,
1744 pub token: String,
1745 #[serde(default)]
1746 pub verdict: Option<Verdict>,
1747 #[serde(default)]
1748 pub body: String,
1749 #[serde(default)]
1750 pub comments: Vec<ReviewComment>,
1751 /// The model that wrote it, by its public name.
1752 #[serde(default)]
1753 pub model: Option<String>,
1754 #[serde(default)]
1755 pub error: Option<String>,
1756}
1757
Issues and pull requests replace intents and attempts1758/// Lowercases, trims and de-duplicates labels, dropping empty ones.
1759/// Returns `None` if there are too many or one is too long.
1760pub fn normalize_labels(labels: &[String]) -> Option<Vec<String>> {
1761 const MAX_LABELS: usize = 10;
1762 const MAX_LABEL_CHARS: usize = 40;
1763 let mut normalized: Vec<String> = Vec::new();
1764 for label in labels {
1765 let label = label
1766 .split_whitespace()
1767 .collect::<Vec<_>>()
1768 .join(" ")
1769 .to_lowercase();
1770 if label.is_empty() || normalized.contains(&label) {
1771 continue;
1772 }
1773 if label.chars().count() > MAX_LABEL_CHARS {
1774 return None;
1775 }
1776 normalized.push(label);
1777 }
1778 (normalized.len() <= MAX_LABELS).then_some(normalized)
1779}
1780
1781#[cfg(test)]
1782mod tests {
1783 use super::normalize_labels;
1784
1785 fn labels(names: &[&str]) -> Vec<String> {
1786 names.iter().map(|name| (*name).to_owned()).collect()
1787 }
1788
1789 #[test]
1790 fn labels_are_lowercased_trimmed_and_unique() {
1791 assert_eq!(
1792 normalize_labels(&labels(&[" Bug ", "bug", "", "Good First Issue"])),
1793 Some(labels(&["bug", "good first issue"]))
1794 );
1795 }
1796
1797 #[test]
1798 fn too_long_or_too_many_labels_are_refused() {
1799 assert_eq!(normalize_labels(&["x".repeat(41)]), None);
1800 let many: Vec<String> = (0..11).map(|i| format!("label-{i}")).collect();
1801 assert_eq!(normalize_labels(&many), None);
1802 }
1803}
Agents as a team: lifecycle, merge queue, billing and a new shell1804
1805
1806// --- Merge queue ----------------------------------------------------------
1807
1808/// Where a pull request in a merge queue stands.
1809#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1810#[serde(rename_all = "snake_case")]
1811pub enum QueueState {
1812 /// Waiting for its turn to be tested.
1813 Waiting,
1814 /// Its combined state is being built and checked.
1815 Testing,
1816 /// Its combined state passed; it lands once everything ahead has.
1817 Passed,
1818 /// Its combined state failed, or would not merge. It left the queue.
1819 Failed,
1820 /// On the default branch.
1821 Landed,
1822 /// Taken out of the queue by a person, or closed.
1823 Removed,
1824}
1825
1826impl QueueState {
1827 pub fn as_str(self) -> &'static str {
1828 match self {
1829 QueueState::Waiting => "waiting",
1830 QueueState::Testing => "testing",
1831 QueueState::Passed => "passed",
1832 QueueState::Failed => "failed",
1833 QueueState::Landed => "landed",
1834 QueueState::Removed => "removed",
1835 }
1836 }
1837
1838 /// Still in the queue.
1839 pub fn is_active(self) -> bool {
1840 matches!(
1841 self,
1842 QueueState::Waiting | QueueState::Testing | QueueState::Passed
1843 )
1844 }
1845}
1846
1847/// One pull request's place in a merge queue.
1848#[derive(Clone, Debug, Serialize, Deserialize)]
1849#[serde(rename_all = "camelCase")]
1850pub struct QueueEntry {
1851 pub id: String,
1852 pub number: u32,
1853 pub title: String,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1854 /// Who or what made the pull request, e.g. `g1t`.
Agents as a team: lifecycle, merge queue, billing and a new shell1855 pub agent: String,
1856 pub state: QueueState,
1857 /// The pull requests merged ahead of it in the state being tested, in
1858 /// queue order. Empty when it was tested on the default branch alone.
1859 pub ahead: Vec<u32>,
1860 /// The default branch's commit the tested state was built on.
1861 pub base_commit: Option<String>,
1862 /// The tested state: the default branch with everything ahead and this.
1863 pub combined_commit: Option<String>,
1864 /// Why it failed: a merge conflict or what could not be run.
1865 pub error: Option<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1866 /// Commands run against the tested state by queues from before its
1867 /// checks were workflows. Empty since.
Agents as a team: lifecycle, merge queue, billing and a new shell1868 pub results: Vec<CheckResult>,
1869 /// Username of whoever merged it into the queue: a person, or `g1t`.
1870 pub enqueued_by: String,
1871 /// RFC 3339.
1872 pub created_at: String,
1873 /// RFC 3339. When it landed or left.
1874 pub finished_at: Option<String>,
1875}
1876
1877/// A repository's merge queue: what is in it, in order, and what recently
1878/// left it.
1879#[derive(Clone, Debug, Serialize, Deserialize)]
1880#[serde(rename_all = "camelCase")]
1881pub struct QueueView {
1882 /// Whether the repository merges through the queue.
1883 pub enabled: bool,
1884 pub active: Vec<QueueEntry>,
1885 /// Newest first.
1886 pub recent: Vec<QueueEntry>,
1887}
1888
1889/// `queue`: a repository's merge queue. Returns `Outcome<QueueView>`.
1890#[derive(Debug, Serialize, Deserialize)]
1891pub struct QueueArgs {
1892 pub repo: RepoPath,
1893 pub viewer: Viewer,
1894}
1895
1896/// `queue_build`: the next batch to test for a repository, if nothing is
1897/// being tested now. Returns `Vec<QueueJob>`, one per entry, each testing
1898/// the default branch with that entry and everything ahead of it.
1899#[derive(Debug, Serialize, Deserialize)]
1900#[serde(rename_all = "camelCase")]
1901pub struct QueueBuildArgs {
1902 pub repo_id: String,
1903}
1904
1905/// One pull request in a state being tested: where its change is.
1906#[derive(Clone, Debug, Serialize, Deserialize)]
1907#[serde(rename_all = "camelCase")]
1908pub struct QueueStackItem {
1909 pub number: u32,
1910 pub title: String,
1911 /// The repository holding the change: its fork, or the repository.
1912 pub source: RepoPath,
1913 /// The branch of `source` holding it.
1914 pub branch: String,
1915 pub commit: String,
1916}
1917
1918/// What a sandbox needs to build and check one combined state.
1919#[derive(Clone, Debug, Serialize, Deserialize)]
1920#[serde(rename_all = "camelCase")]
1921pub struct QueueJob {
1922 pub entry_id: String,
1923 /// Lets the sandbox, and nothing else, report this state's result.
1924 pub token: String,
1925 pub repo: RepoPath,
1926 pub default_branch: String,
1927 /// The default branch's commit to build on.
1928 pub base_commit: String,
1929 /// Where to push the tested state, in the repository itself.
1930 pub branch: String,
1931 /// The pull requests to merge in, in order; the last is the entry.
1932 pub stack: Vec<QueueStackItem>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1933 /// Commands to run on the built state. Always empty: the state is
1934 /// checked by the `merge_group` workflows run on it, and the default
1935 /// branch's required checks must pass there.
Agents as a team: lifecycle, merge queue, billing and a new shell1936 pub checks: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1937 /// Always empty, as `checks`.
Agents as a team: lifecycle, merge queue, billing and a new shell1938 #[serde(default)]
1939 pub contract_checks: Vec<String>,
1940 /// Who the sandbox acts as: a member who can push the tested state.
1941 pub actor: User,
1942}
1943
1944/// `report_queue`: a sandbox's result for one combined state. Returns
1945/// `Outcome<QueueState>`.
1946#[derive(Debug, Serialize, Deserialize)]
1947#[serde(rename_all = "camelCase")]
1948pub struct ReportQueueArgs {
1949 pub entry_id: String,
1950 pub token: String,
1951 #[serde(default)]
1952 pub combined_commit: Option<String>,
1953 #[serde(default)]
1954 pub results: Vec<CheckResult>,
1955 /// Set when the state could not be built or checked.
1956 #[serde(default)]
1957 pub error: Option<String>,
1958 /// For a merge conflict: the pull request whose change it collided with.
1959 #[serde(default)]
1960 pub conflict_with: Option<u32>,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1961 /// For a merge conflict: the files that conflicted.
1962 #[serde(default)]
1963 pub conflicts: Vec<String>,
Agents as a team: lifecycle, merge queue, billing and a new shell1964}
Record your own agent's sessions automatically1965
1966
1967/// `locate_pull`: where a pull request lives, by its id, for a tool that
1968/// knows only the fork it is working in (`g1t.sh/pulls/<id>`). Returns
1969/// `Outcome<LocatedPull>`; not found for anyone who cannot see it.
1970#[derive(Debug, Serialize, Deserialize)]
1971pub struct LocatePullArgs {
1972 pub id: String,
1973 pub viewer: Viewer,
1974}
1975
1976#[derive(Clone, Debug, Serialize, Deserialize)]
1977#[serde(rename_all = "camelCase")]
1978pub struct LocatedPull {
1979 pub repo: RepoPath,
1980 pub number: u32,
1981 pub title: String,
1982 pub status: PullStatus,
1983}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1984
1985// --- A person's work -------------------------------------------------------
1986
1987/// Issues or pull requests, on a person's profile.
1988#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1989#[serde(rename_all = "lowercase")]
1990pub enum AuthoredKind {
1991 Issue,
1992 Pull,
1993}
1994
1995/// The state filter on a person's work. `Closed` takes in merged pull
1996/// requests too; `Merged` is only those.
1997#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1998#[serde(rename_all = "lowercase")]
1999pub enum AuthoredState {
2000 Open,
2001 Closed,
2002 Merged,
2003}
2004
2005/// How a person's work is ordered.
2006#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
2007#[serde(rename_all = "lowercase")]
2008pub enum AuthoredSort {
2009 /// Newest first.
2010 #[default]
2011 Created,
2012 /// Most recently changed first.
2013 Updated,
2014 /// Oldest first.
2015 Oldest,
2016}
2017
2018/// The most items one `by_author` page holds.
2019pub const AUTHORED_PAGE: u32 = 25;
2020
2021/// `by_author`: the issues and pull requests a person opened, only on
2022/// repositories `viewer` may read, so a private title never reaches anyone
2023/// who could not open it. Returns `Outcome<Authored>`; not found for an
2024/// account that does not exist.
2025#[derive(Debug, Serialize, Deserialize)]
2026pub struct ByAuthorArgs {
2027 pub username: String,
2028 pub viewer: Viewer,
2029 #[serde(default)]
2030 pub kind: Option<AuthoredKind>,
2031 #[serde(default)]
2032 pub state: Option<AuthoredState>,
2033 /// Only work on this repository: `namespace/name`.
2034 #[serde(default)]
2035 pub repo: Option<String>,
2036 #[serde(default)]
2037 pub sort: AuthoredSort,
2038 /// The `next` of the page before, to read on from there.
2039 #[serde(default)]
2040 pub before: Option<String>,
2041 /// At most [`AUTHORED_PAGE`]; that when absent.
2042 #[serde(default)]
2043 pub limit: Option<u32>,
2044}
2045
2046/// One issue or pull request a person opened.
2047#[derive(Clone, Debug, Serialize, Deserialize)]
2048#[serde(rename_all = "camelCase")]
2049pub struct AuthoredItem {
2050 pub kind: AuthoredKind,
2051 pub repo: RepoPath,
2052 pub number: u32,
2053 pub title: String,
2054 /// Open or closed; a merged pull request is closed.
2055 pub state: State,
2056 /// A pull request's own status.
2057 pub status: Option<PullStatus>,
2058 /// Why an issue was closed.
2059 pub reason: Option<IssueReason>,
2060 pub draft: bool,
2061 pub merged: bool,
2062 /// RFC 3339.
2063 pub created_at: String,
2064 /// RFC 3339.
2065 pub updated_at: String,
2066 /// When a pull request was merged. RFC 3339.
2067 pub merged_at: Option<String>,
2068}
2069
2070/// What a person has done, as far as the viewer may see.
2071#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2072#[serde(rename_all = "camelCase")]
2073pub struct AuthoredCounts {
2074 pub pulls_merged: u32,
2075 pub pulls_open: u32,
2076 pub pulls: u32,
2077 pub issues: u32,
2078 pub issues_open: u32,
2079}
2080
2081/// A repository a person has opened work on, with how much.
2082#[derive(Clone, Debug, Serialize, Deserialize)]
2083pub struct AuthoredRepo {
2084 pub repo: RepoPath,
2085 pub count: u32,
2086}
2087
2088/// A page of a person's work.
2089#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2090#[serde(rename_all = "camelCase")]
2091pub struct Authored {
2092 pub items: Vec<AuthoredItem>,
2093 /// Pass as `before` for the next page; null on the last.
2094 pub next: Option<String>,
2095 /// Over every repository the viewer may read, whatever the filters.
2096 pub counts: AuthoredCounts,
2097 /// Those repositories, most work first.
2098 pub repos: Vec<AuthoredRepo>,
2099}
Fast pages, required checks on the branch, self-hosted runners, honest incidents2100
2101#[cfg(test)]
2102mod required_tests {
2103 use super::*;
2104
2105 fn status(context: &str, state: &str) -> CommitStatus {
2106 CommitStatus {
2107 context: context.into(),
2108 state: state.into(),
2109 description: Some(format!("{context} {state}")),
2110 target_url: None,
2111 updated_at: String::new(),
2112 }
2113 }
2114
2115 #[test]
2116 fn a_context_names_its_check_and_event() {
2117 assert_eq!(check_name("CI / pull_request"), ("CI", Some("pull_request")));
2118 assert_eq!(check_name("Build and test / merge_group"), ("Build and test", Some("merge_group")));
2119 assert_eq!(check_name("g1t / deploy"), ("g1t / deploy", None));
2120 assert_eq!(check_name("g1t / deploy (docs)"), ("g1t / deploy (docs)", None));
2121 assert_eq!(check_name("lint"), ("lint", None));
2122 }
2123
2124 #[test]
2125 fn required_checks_are_missing_pending_failed_or_passed() {
2126 let required = vec!["CI".to_owned(), "Lint".to_owned(), "g1t / deploy".to_owned(), "Docs".to_owned()];
2127 let statuses = [
2128 status("CI / pull_request", "success"),
2129 status("Lint / pull_request", "pending"),
2130 status("g1t / deploy", "failure"),
2131 ];
2132 let states: Vec<RequiredState> = required_checks(&required, &statuses).into_iter().map(|c| c.state).collect();
2133 assert_eq!(
2134 states,
2135 [RequiredState::Success, RequiredState::Pending, RequiredState::Failure, RequiredState::Expected]
2136 );
2137 }
2138
2139 #[test]
2140 fn any_event_reports_a_check_and_a_failure_wins() {
2141 let required = vec!["ci".to_owned()];
2142 let both = [status("CI / push", "failure"), status("CI / pull_request", "success")];
2143 let check = &required_checks(&required, &both)[0];
2144 assert_eq!(check.state, RequiredState::Failure);
2145 assert_eq!(check.description.as_deref(), Some("CI / push failure"));
2146 let queue = [status("CI / merge_group", "success")];
2147 assert_eq!(required_checks(&required, &queue)[0].state, RequiredState::Success);
2148 }
2149
2150 #[test]
2151 fn required_names_are_tidied() {
2152 let names = vec![" CI ".to_owned(), "ci".to_owned(), String::new(), "Lint".to_owned()];
2153 assert_eq!(tidy_required(&names), ["CI", "Lint"]);
2154 }
2155
2156 #[test]
2157 fn a_definition_of_done_is_added_once() {
2158 let items = commands_pass(&["cargo test".to_owned(), " ".to_owned()]);
2159 assert_eq!(items, ["`cargo test` passes."]);
2160 let body = with_definition_of_done("Fix the greeting.", &items);
2161 assert_eq!(body, "Fix the greeting.\n\n## Definition of done\n\n- `cargo test` passes.");
2162 assert_eq!(with_definition_of_done(&body, &items), body);
2163 assert_eq!(with_definition_of_done("", &items), "## Definition of done\n\n- `cargo test` passes.");
2164 assert_eq!(with_definition_of_done(" Text ", &[]), "Text");
2165 }
2166}
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights2167
2168#[cfg(test)]
2169mod authorship_tests {
2170 use super::*;
2171 use crate::PrincipalKind;
2172 use crate::credentials::{Acting, Principal};
2173 use crate::identity::{AGENT_ID, AgentScope};
2174
2175 fn person() -> User {
2176 User {
2177 id: "usr_1".into(),
2178 username: "syntaqx".into(),
2179 verified: true,
2180 ..User::default()
2181 }
2182 }
2183
2184 fn agent_for(id: &str, username: &str) -> User {
2185 User {
2186 id: AGENT_ID.into(),
2187 username: "g1t".into(),
2188 kind: PrincipalKind::Agent,
2189 acting: Some(Box::new(Acting {
2190 credential_id: "tok_1".into(),
2191 agent: "g1t".into(),
2192 on_behalf_of: Principal { id: id.into(), username: username.into() },
2193 scope: AgentScope {
2194 repo: RepoPath { namespace: "acme".into(), name: "web".into() },
2195 operations: Vec::new(),
2196 run: None,
2197 },
2198 })),
2199 ..User::default()
2200 }
2201 }
2202
2203 fn pull(author: User, requested_by: Option<User>) -> Pull {
2204 let mut pull: Pull = serde_json::from_value(serde_json::json!({
2205 "id": "pr_1", "repoId": "rep_1", "number": 14, "issue": 12, "title": "Fix it", "body": null,
2206 "agent": "g1t", "runtime": "hosted", "status": "open", "fork": null, "forkRepoId": null,
2207 "branch": null, "headCommit": null, "mergeBase": null, "mergedBy": null, "mergedAt": null,
2208 "supersededBy": null, "checkStatus": null,
2209 "author": { "id": "x", "username": "x" },
2210 "createdAt": "", "updatedAt": ""
2211 }))
2212 .unwrap();
2213 pull.author = author;
2214 pull.requested_by = requested_by;
2215 pull
2216 }
2217
2218 #[test]
2219 fn a_change_a_person_has_g1t_make_is_g1t_s_requested_by_them() {
2220 let (author, asked) = authorship(&person(), true);
2221 assert_eq!((author.id.as_str(), author.username.as_str(), author.kind), (AGENT_ID, "g1t", PrincipalKind::Agent));
2222 let asked = asked.expect("the person asked for it");
2223 assert_eq!((asked.id.as_str(), asked.username.as_str(), asked.kind), ("usr_1", "syntaqx", PrincipalKind::User));
2224 }
2225
2226 #[test]
2227 fn what_g1t_s_agent_files_at_work_is_g1t_s_requested_by_whoever_it_works_for() {
2228 let (author, asked) = authorship(&agent_for("usr_1", "syntaqx"), false);
2229 assert_eq!(author.id, AGENT_ID);
2230 assert_eq!(asked.map(|user| user.username), Some("syntaqx".into()));
2231 }
2232
2233 #[test]
2234 fn nobody_asked_for_g1t_s_own_work() {
2235 // A run g1t started itself acts for g1t, not for a person.
2236 let (author, asked) = authorship(&agent_for(crate::system::ID, "g1t"), false);
2237 assert_eq!(author.id, AGENT_ID);
2238 assert!(asked.is_none());
2239 // A security update g1t opens is g1t's own, as before.
2240 let (author, asked) = authorship(&User::system("acme"), true);
2241 assert_eq!((author.id.as_str(), author.kind), (crate::system::ID, PrincipalKind::System));
2242 assert!(asked.is_none());
2243 }
2244
2245 #[test]
2246 fn anyone_else_opens_their_own() {
2247 let (author, asked) = authorship(&person(), false);
2248 assert_eq!((author.id.as_str(), author.kind), ("usr_1", PrincipalKind::User));
2249 assert!(asked.is_none());
2250 // Nothing but who they are is kept.
2251 assert!(author.workspaces.is_empty() && author.acting.is_none());
2252 }
2253
2254 #[test]
2255 fn the_requester_owns_g1t_s_pull_request_and_an_author_their_own() {
2256 let made = pull(g1t_author(), Some(person()));
2257 assert_eq!(made.owner().id, "usr_1");
2258 assert!(made.is_owned_by("usr_1"));
2259 assert!(!made.is_owned_by(AGENT_ID), "g1t's agent does not answer for its own change");
2260 let own = pull(person(), None);
2261 assert_eq!(own.owner().id, "usr_1");
2262 assert!(own.is_owned_by("usr_1"));
2263 }
2264
2265 #[test]
2266 fn requested_by_is_null_when_nobody_asked_and_read_as_absent_from_older_senders() {
2267 let own = pull(person(), None);
2268 let sent = serde_json::to_value(&own).unwrap();
2269 assert!(sent["requestedBy"].is_null());
2270 let mut older = sent.clone();
2271 older.as_object_mut().unwrap().remove("requestedBy");
2272 let read: Pull = serde_json::from_value(older).unwrap();
2273 assert!(read.requested_by.is_none());
2274 }
2275}