Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

Make work items workspace-level in the contracts

Issues become work items numbered in their workspace and linked to the repositories they concern (origin, target, touched), with a resolution per repository they land in. Labels and milestones belong to the workspace; comments, reactions and labels name their thread by repository and pull request number or by workspace and work item number. Coordination (claims, sessions, get_work) is keyed by work item, events become work_item.* (with linked, unlinked and landed) and work.migrated, and #N references follow one shared lookup order (work::refs). The TypeScript mirrors follow.

G1T-Session:
ses_01m561b3rje8nvxjcdsz6frggj
syntaqxcommitted Parent1b57ffbBrowse files
20 files+2272−9210/20 viewed
+144−9
5454 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
5555 #[serde(rename_all = "snake_case")]
5656 pub enum RepoRole {
57− /// Read and clone; open issues and pull requests, and comment.
57+ /// Read and clone; open work items and pull requests, and comment.
5858 Read,
59− /// Read, and manage issues and pull requests: label, assign, close.
59+ /// Read, and manage work items and pull requests: label, assign, close.
6060 Triage,
6161 /// Triage, and push, merge, and put agents to work.
6262 Write,
165165 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
166166 #[serde(rename_all = "snake_case")]
167167 pub enum Capability {
168− /// See the code, issues and pull requests; clone and fetch.
168+ /// See the code, work items and pull requests; clone and fetch.
169169 Read,
170− /// Open issues and pull requests, and comment on them.
170+ /// Open work items and pull requests, and comment on them.
171171 Participate,
172− /// Apply labels and milestones; assign, close and reopen issues and
173− /// pull requests; ask for reviews.
172+ /// Apply labels and milestones; assign, close and reopen work items
173+ /// and pull requests; ask for reviews.
174174 Triage,
175175 /// Push to branches that are not protected, and edit files on the web.
176176 Push,
245245 /// follows GitHub's table of repository roles; where g1t differs, the
246246 /// access guide says so.
247247 pub const CAPABILITIES: [CapabilityRow; 16] = [
248− CapabilityRow { capability: Capability::Read, role: RepoRole::Read, about: "See code, issues and pull requests; clone and fetch" },
249− CapabilityRow { capability: Capability::Participate, role: RepoRole::Read, about: "Open issues and pull requests, and comment" },
250− CapabilityRow { capability: Capability::Triage, role: RepoRole::Triage, about: "Apply labels and milestones; assign, close and reopen issues and pull requests" },
248+ CapabilityRow { capability: Capability::Read, role: RepoRole::Read, about: "See code, work items and pull requests; clone and fetch" },
249+ CapabilityRow { capability: Capability::Participate, role: RepoRole::Read, about: "Open work items and pull requests, and comment" },
250+ CapabilityRow { capability: Capability::Triage, role: RepoRole::Triage, about: "Apply labels and milestones; assign, close and reopen work items and pull requests" },
251251 CapabilityRow { capability: Capability::Push, role: RepoRole::Write, about: "Push to branches that are not protected" },
252252 CapabilityRow { capability: Capability::Merge, role: RepoRole::Write, about: "Merge pull requests and use the merge queue" },
253253 CapabilityRow { capability: Capability::ManageLabels, role: RepoRole::Write, about: "Create, edit and delete labels and milestones" },
462462 }
463463 }
464464
465+// --- Work items -------------------------------------------------------------
466+
467+/// What a membership of `workspace` gives on its work items: what it gives
468+/// on its repositories, and at least reading and taking part, since every
469+/// member sees the workspace's work. A narrowed token that does not reach
470+/// the whole workspace gives none.
471+pub fn workspace_role(user: &User, workspace: &str) -> Option<RepoRole> {
472+ if user.token.as_deref().is_some_and(|token| !token.covers_workspace(workspace)) {
473+ return None;
474+ }
475+ let membership = user.workspaces.iter().find(|membership| membership.slug.eq_ignore_ascii_case(workspace))?;
476+ membership_role(user, membership).max(Some(RepoRole::Read))
477+}
478+
479+/// The viewer's role on a work item of `workspace` filed from `origin`
480+/// (none for one filed in the workspace itself): the better of what their
481+/// membership of the workspace gives ([`workspace_role`]) and their role
482+/// on the origin ([`permission`]). So members see all of a workspace's
483+/// work items, anyone who can read a repository sees those filed there
484+/// (on a public one, everyone), and one with no origin is for members
485+/// only. `None` means they may not see it at all.
486+pub fn item_role(viewer: Option<&User>, workspace: &str, origin: Option<RepoRef<'_>>) -> Option<RepoRole> {
487+ let member = viewer.and_then(|user| workspace_role(user, workspace));
488+ let repo = origin.and_then(|origin| permission(viewer, origin));
489+ member.max(repo)
490+}
491+
492+/// Whether the viewer may do `capability` on a work item, by the same
493+/// table as [`can`]: through their membership of the workspace, or their
494+/// role on its origin as [`can`] reads it.
495+pub fn can_item(viewer: Option<&User>, workspace: &str, origin: Option<RepoRef<'_>>, capability: Capability) -> bool {
496+ let member = viewer.and_then(|user| workspace_role(user, workspace));
497+ if member.is_some_and(|role| allows(role, capability)) {
498+ return true;
499+ }
500+ origin.is_some_and(|origin| can(viewer, origin, capability))
501+}
502+
503+/// `Ok` when the viewer may do `capability` on a work item; otherwise
504+/// whether to answer not found (they may not see it) or forbidden.
505+pub fn check_item(viewer: Option<&User>, workspace: &str, origin: Option<RepoRef<'_>>, capability: Capability) -> Result<(), Denied> {
506+ if item_role(viewer, workspace, origin).is_none() {
507+ return Err(Denied::NotFound);
508+ }
509+ if can_item(viewer, workspace, origin, capability) {
510+ Ok(())
511+ } else {
512+ Err(Denied::Forbidden)
513+ }
514+}
515+
516+/// The sentence a refusal of `capability` on a work item of `workspace`
517+/// gives.
518+pub fn needs_on_item(capability: Capability, workspace: &str) -> String {
519+ format!("You need the {} role or higher in {workspace} to do that.", least_role(capability).label())
520+}
521+
465522 /// The sentence a refusal of `capability` gives.
466523 pub fn needs(capability: Capability, repo: &str) -> String {
467524 let role = least_role(capability);
766823 }
767824
768825 #[cfg(test)]
826+mod item_tests {
827+ use super::*;
828+ use crate::scopes::{RepositorySelection, TokenAccess, TokenReach};
829+
830+ fn person(memberships: &[(&str, Role, Option<BasePermission>)], grants: &[(&str, &str, RepoRole)]) -> User {
831+ User {
832+ id: "usr_1".into(),
833+ username: "ana".into(),
834+ verified: true,
835+ workspaces: memberships
836+ .iter()
837+ .map(|(slug, role, base)| Membership { role: *role, base_permission: *base, ..Membership::member(*slug) })
838+ .collect(),
839+ grants: grants
840+ .iter()
841+ .map(|(id, workspace, role)| RepoGrant { repo_id: (*id).into(), workspace: (*workspace).into(), role: *role, team: None })
842+ .collect(),
843+ ..User::default()
844+ }
845+ }
846+
847+ fn origin(private: bool) -> Option<RepoRef<'static>> {
848+ Some(RepoRef { id: "rep_1", namespace: "acme", private })
849+ }
850+
851+ #[test]
852+ fn members_see_every_work_item_of_their_workspace() {
853+ let member = person(&[("acme", Role::Member, Some(BasePermission::None))], &[]);
854+ assert_eq!(item_role(Some(&member), "acme", None), Some(RepoRole::Read));
855+ assert_eq!(item_role(Some(&member), "acme", origin(true)), Some(RepoRole::Read));
856+ assert!(can_item(Some(&member), "acme", None, Capability::Participate));
857+ assert!(!can_item(Some(&member), "acme", None, Capability::Triage));
858+ let writer = person(&[("acme", Role::Member, Some(BasePermission::Write))], &[]);
859+ assert!(can_item(Some(&writer), "ACME", None, Capability::ManageLabels));
860+ let owner = person(&[("acme", Role::Owner, None)], &[]);
861+ assert_eq!(item_role(Some(&owner), "acme", None), Some(RepoRole::Admin));
862+ // Another workspace's member sees none of them.
863+ let other = person(&[("globex", Role::Owner, None)], &[]);
864+ assert_eq!(check_item(Some(&other), "acme", None, Capability::Read), Err(Denied::NotFound));
865+ }
866+
867+ #[test]
868+ fn anyone_who_can_read_the_origin_sees_what_was_filed_there() {
869+ // A public repository's work items are public, and anyone signed
870+ // in may take part in them, as in the repository.
871+ assert_eq!(item_role(None, "acme", origin(false)), Some(RepoRole::Read));
872+ let outsider = person(&[], &[]);
873+ assert!(can_item(Some(&outsider), "acme", origin(false), Capability::Participate));
874+ assert_eq!(check_item(Some(&outsider), "acme", origin(false), Capability::Triage), Err(Denied::Forbidden));
875+ // A private one's are for those it is shared with.
876+ assert_eq!(item_role(Some(&outsider), "acme", origin(true)), None);
877+ let collaborator = person(&[], &[("rep_1", "acme", RepoRole::Triage)]);
878+ assert_eq!(item_role(Some(&collaborator), "acme", origin(true)), Some(RepoRole::Triage));
879+ assert!(can_item(Some(&collaborator), "acme", origin(true), Capability::Triage));
880+ // With no origin, only members.
881+ assert_eq!(item_role(Some(&collaborator), "acme", None), None);
882+ }
883+
884+ #[test]
885+ fn a_narrowed_token_reaches_only_what_it_was_made_for() {
886+ let mut member = person(&[("acme", Role::Member, Some(BasePermission::Write))], &[]);
887+ member.token = Some(Box::new(TokenAccess {
888+ reach: Some(TokenReach { workspace: Some("acme".into()), repositories: RepositorySelection::Selected, repo_ids: vec!["rep_2".into()] }),
889+ ..TokenAccess::full()
890+ }));
891+ // Not the whole workspace: no member's view of it.
892+ assert_eq!(item_role(Some(&member), "acme", None), None);
893+ // A public origin still reads.
894+ assert_eq!(item_role(Some(&member), "acme", origin(false)), Some(RepoRole::Read));
895+ member.token = Some(Box::new(TokenAccess {
896+ reach: Some(TokenReach { workspace: Some("acme".into()), repositories: RepositorySelection::All, repo_ids: Vec::new() }),
897+ ..TokenAccess::full()
898+ }));
899+ assert_eq!(item_role(Some(&member), "acme", None), Some(RepoRole::Write));
900+ }
901+}
902+
903+#[cfg(test)]
769904 mod tests {
770905 use super::*;
771906
+17−16
1−//! Starting work on an issue in a session of its own, on the person's own
1+//! Starting work on a work item in a session of its own, on the person's own
22 //! Claude (the integrations service, `src/agent_sessions.rs`).
33 //!
4−//! g1t runs no model. Work on an issue happens in someone's own harness:
4+//! g1t runs no model. Work on a work item happens in someone's own harness:
55 //! their Claude Code with the g1t plugin, or a Claude Managed Agents
66 //! session that g1t starts with the workspace's own Anthropic key. Either
77 //! way the session is recorded on g1t (`work::Session`) and tied to the
8−//! issue and the pull requests it makes. `start_agent_session` is what
9−//! Ask, chat and the issue page call to hand work off.
8+//! work item and the pull requests it makes. `start_agent_session` is what
9+//! Ask, chat and the work item page call to hand work off.
1010
1111 use serde::{Deserialize, Serialize};
1212
1818 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1919 #[serde(rename_all = "snake_case")]
2020 pub enum AgentMode {
21− /// Work out how, and say so on the issue; change nothing.
21+ /// Work out how, and say so on the work item; change nothing.
2222 Plan,
2323 /// Make the change and open a pull request.
2424 #[default]
2525 Implement,
26− /// Review the issue's pull request.
26+ /// Review the work item's pull request.
2727 Review,
2828 }
2929
4949 /// The most characters of an instruction kept.
5050 pub const MAX_INSTRUCTION_CHARS: usize = 4_000;
5151
52−/// Who asked g1t in an issue or a pull request, rather than from a
53−/// button: by assigning it the issue, or by mentioning `@g1t` in a comment.
52+/// Who asked g1t in a work item or a pull request, rather than from a
53+/// button: by assigning it the work item, or by mentioning `@g1t` in a comment.
5454 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
5555 pub struct Asked {
5656 /// Their username.
5858 /// What their comment said; none for an assignment.
5959 #[serde(default)]
6060 pub comment: Option<String>,
61− /// Where they asked: the comment, or the issue.
61+ /// Where they asked: the comment, or the work item.
6262 pub url: String,
6363 }
6464
65−/// `start_agent_session`: work on `issue` (or a new issue from `title` and
65+/// `start_agent_session`: work on `item` (or a new work item from `title` and
6666 /// `body`), or carry on with pull request `pull`, as `actor`. Returns
6767 /// `Outcome<AgentSessionStarted>`.
6868 #[derive(Debug, Serialize, Deserialize)]
7070 pub actor: User,
7171 pub repo: RepoPath,
7272 #[serde(default)]
73− pub issue: Option<u32>,
74− /// A pull request to carry on with, on its branch: in place of `issue`.
73+ pub item: Option<u32>,
74+ /// A pull request to carry on with, on its branch: in place of `item`.
7575 #[serde(default)]
7676 pub pull: Option<u32>,
7777 /// Set when g1t was asked in the thread (integrations' summon.rs).
7878 #[serde(default)]
7979 pub asked: Option<Asked>,
80− /// Without `issue`: a new issue's title, opened first.
80+ /// Without `item`: a new work item's title, opened first.
8181 #[serde(default)]
8282 pub title: Option<String>,
8383 #[serde(default)]
8484 pub body: Option<String>,
85− /// What to do beyond what the issue says.
85+ /// What to do beyond what the work item says.
8686 #[serde(default)]
8787 pub instruction: Option<String>,
8888 #[serde(default)]
9898 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
9999 #[serde(rename_all = "camelCase")]
100100 pub struct AgentSessionStarted {
101− /// The issue it works on: for a pull request with none, its number.
102− pub issue: u32,
101+ /// The work item it works on, by number in the workspace: for a pull
102+ /// request with none, the pull request's number.
103+ pub item: u32,
103104 /// The pull request it carries on with, when it was started on one.
104105 #[serde(default)]
105106 pub pull: Option<u32>,
+22−18
8686 #[serde(rename_all = "camelCase")]
8787 pub struct Decision {
8888 pub id: String,
89− /// The issue it is about, when known.
90− pub issue: Option<u32>,
89+ /// The work item it is about, when known, by number in its workspace.
90+ pub item: Option<u32>,
9191 /// The pull request it is about, when known.
9292 pub pull: Option<u32>,
9393 /// The session it was recorded in, if one was named.
124124 pub session_id: Option<String>,
125125 }
126126
127−/// `list_decisions`: the decisions on an issue (and on its pull requests)
128−/// or on a pull request, oldest first. Returns `Outcome<Vec<Decision>>`.
127+/// `list_decisions`: the decisions on a thread, oldest first: with `repo`,
128+/// on its pull request `number`; without, on the work item `number` of
129+/// `workspace` and on its pull requests. Returns `Outcome<Vec<Decision>>`.
129130 #[derive(Debug, Serialize, Deserialize)]
130131 #[serde(rename_all = "camelCase")]
131132 pub struct ListDecisionsArgs {
132− pub repo: RepoPath,
133+ #[serde(default)]
134+ pub repo: Option<RepoPath>,
135+ #[serde(default)]
136+ pub workspace: String,
133137 pub viewer: Viewer,
134− /// An issue's or a pull request's number: they share a sequence.
135138 pub number: u32,
136139 }
137140
161164 /// The responsible pull request, as `pull`.
162165 pub number: u32,
163166 pub pull: u32,
164− /// Its issue, if it has one.
165− pub issue: Option<u32>,
167+ /// Its work item, if it has one.
168+ pub item: Option<u32>,
166169 /// The pull requests tested together, in queue order; the last is
167170 /// `pull`.
168171 pub combination: Vec<u32>,
192195 pub pull_id: String,
193196 pub number: u32,
194197 pub pull: u32,
195− pub issue: Option<u32>,
198+ pub item: Option<u32>,
196199 pub combination: Vec<u32>,
197200 pub commit: Option<String>,
198201 }
202205 #[serde(rename_all = "camelCase")]
203206 pub struct DecisionRecorded {
204207 pub repo_id: String,
205− /// The pull request it is about, or else its issue.
208+ /// The pull request it is about, or else its work item.
206209 pub number: u32,
207− pub issue: Option<u32>,
210+ pub item: Option<u32>,
208211 pub decision: Decision,
209212 }
210213
220223 pub merged_at: Option<String>,
221224 }
222225
223−/// The issue that pull request was for.
226+/// The work item that pull request was for.
224227 #[derive(Clone, Debug, Serialize, Deserialize)]
225228 #[serde(rename_all = "camelCase")]
226−pub struct WhyIssue {
229+pub struct WhyItem {
230+ pub workspace: String,
227231 pub number: u32,
228232 pub title: String,
229233 pub state: String,
271275 }
272276
273277 /// `why`: why a line is the way it is. The commit that last changed it,
274−/// the merged pull request it arrived in, that pull request's issue, the
278+/// the merged pull request it arrived in, that pull request's work item, the
275279 /// decisions recorded on them, and the session entries behind the commit.
276280 #[derive(Clone, Debug, Serialize, Deserialize)]
277281 #[serde(rename_all = "camelCase")]
280284 pub line: u32,
281285 pub commit: WhyCommit,
282286 pub pull: Option<WhyPull>,
283− pub issue: Option<WhyIssue>,
287+ pub item: Option<WhyItem>,
284288 pub decisions: Vec<Decision>,
285289 pub sessions: Vec<SessionEntry>,
286290 }
329333 }
330334
331335 #[test]
332− fn an_integration_failure_carries_number_and_issue_for_filters() {
336+ fn an_integration_failure_carries_number_and_item_for_filters() {
333337 let event = IntegrationFailed {
334338 repo_id: "rep_1".into(),
335339 pull_id: "pr_1".into(),
336340 number: 14,
337341 pull: 14,
338− issue: Some(9),
342+ item: Some(9),
339343 combination: vec![11, 12, 14],
340344 check: FailedCheck { name: "CI / merge_group".into(), conclusion: "failure".into(), url: None },
341345 bisected: true,
346350 };
347351 let sent = serde_json::to_value(&event).unwrap();
348352 assert_eq!(sent["number"], 14);
349− assert_eq!(sent["issue"], 9);
353+ assert_eq!(sent["item"], 9);
350354 assert_eq!(sent["otherPulls"], serde_json::json!([11, 12]));
351355 assert_eq!(sent["check"]["conclusion"], "failure");
352356 assert_eq!(sent["landed"], serde_json::json!([]));
+114−53
160160 pub caused_by_job: Option<String>,
161161 }
162162
163−/// The payload of `issue.opened`, `issue.updated`, `issue.assigned`,
164−/// `issue.labeled`, `issue.unlabeled`, `issue.milestoned`,
165−/// `issue.demilestoned`, `issue.closed` and `issue.reopened`; each uses
166−/// the fields that apply to it.
163+/// The work item events the work service publishes. Each one's payload is
164+/// a [`WorkItemEvent`]; the event's `repoId` is the work item's origin,
165+/// when it has one.
166+pub const WORK_ITEM_EVENTS: [&str; 12] = [
167+ "work_item.opened",
168+ "work_item.updated",
169+ "work_item.assigned",
170+ "work_item.labeled",
171+ "work_item.unlabeled",
172+ "work_item.milestoned",
173+ "work_item.demilestoned",
174+ "work_item.closed",
175+ "work_item.reopened",
176+ "work_item.linked",
177+ "work_item.unlinked",
178+ "work_item.landed",
179+];
180+
181+/// The payload of every `work_item.*` event ([`WORK_ITEM_EVENTS`]); each
182+/// uses the fields that apply to it. A repository's subscribers hear of
183+/// work items linked to it (`repo_ids`); a workspace's, of all of its.
167184 #[derive(Debug, Default, Serialize)]
168185 #[serde(rename_all = "camelCase")]
169−pub struct IssueEvent {
170− pub issue_id: String,
171− pub repo_id: String,
186+pub struct WorkItemEvent {
187+ pub item_id: String,
188+ /// The workspace's slug.
189+ pub workspace: String,
172190 pub number: u32,
191+ /// Every repository it is linked to, its origin first.
192+ pub repo_ids: Vec<String>,
173193 /// Who opened it: g1t, for one its agent filed while at work.
174194 #[serde(skip_serializing_if = "Option::is_none")]
175195 pub author: Option<crate::credentials::Principal>,
176− /// For an issue g1t's agent filed: the person it was working for.
196+ /// For one g1t's agent filed: the person it was working for.
177197 #[serde(skip_serializing_if = "Option::is_none")]
178198 pub requested_by: Option<crate::credentials::Principal>,
179199 #[serde(skip_serializing_if = "Option::is_none")]
181201 /// On close: `completed` or `not_planned`.
182202 #[serde(skip_serializing_if = "Option::is_none")]
183203 pub reason: Option<&'static str>,
184− /// On close: the number of the pull request whose merge closed it.
204+ /// On `work_item.landed` and on a close a landing caused: what landed,
205+ /// in which repository.
185206 #[serde(skip_serializing_if = "Option::is_none")]
186− pub resolved_by: Option<u32>,
187− /// On close: the commit pushed to the default branch whose message
188− /// closed it (`Fixes #12`), when one did.
207+ pub resolution: Option<crate::work::Resolution>,
208+ /// On `work_item.landed` and on close: the targets it has not landed
209+ /// in yet, by path. Empty on close.
189210 #[serde(skip_serializing_if = "Option::is_none")]
190− pub commit: Option<String>,
211+ pub missing: Option<Vec<String>>,
191212 /// On close: the session it was closed in, when it was: the one the
192213 /// closing commit names (`G1t-Session: ses_…`), or the caller's own
193− /// session on the issue when it closed it with a token.
214+ /// session on the work item when it closed it with a token.
194215 #[serde(skip_serializing_if = "Option::is_none")]
195216 pub session: Option<String>,
196− /// On `issue.assigned`: the people it is now assigned to.
217+ /// On `work_item.assigned`: the people it is now assigned to.
197218 #[serde(skip_serializing_if = "Option::is_none")]
198219 pub assignees: Option<Vec<String>>,
199− /// On `issue.assigned`: those of them who were not before.
220+ /// On `work_item.assigned`: those of them who were not before.
200221 #[serde(skip_serializing_if = "Option::is_none")]
201222 pub added: Option<Vec<String>>,
202− /// On `issue.labeled` and `issue.unlabeled`: the label put on or taken
203− /// off. One event for each.
223+ /// On `work_item.labeled` and `work_item.unlabeled`: the label put on
224+ /// or taken off. One event for each.
204225 #[serde(skip_serializing_if = "Option::is_none")]
205226 pub label: Option<EventLabel>,
206− /// On `issue.milestoned`: the milestone it was put in; on
207− /// `issue.demilestoned`, the one it was taken out of.
227+ /// On `work_item.milestoned`: the milestone it was put in; on
228+ /// `work_item.demilestoned`, the one it was taken out of.
208229 #[serde(skip_serializing_if = "Option::is_none")]
209230 pub milestone: Option<crate::work::MilestoneRef>,
231+ /// On `work_item.linked` and `work_item.unlinked`: the repository
232+ /// (`owner/name`) and how it is linked now, or was.
233+ #[serde(skip_serializing_if = "Option::is_none")]
234+ pub repo: Option<String>,
235+ #[serde(skip_serializing_if = "Option::is_none")]
236+ pub role: Option<crate::work::LinkRole>,
210237 }
211238
212−/// A label, as `issue.labeled`, `pull.labeled` and their `unlabeled` say.
239+/// `work.migrated`: a page of where repositories' issues went when they
240+/// became work items, for services that stored issue numbers to rewrite
241+/// them. Sent once per page by the migration job, then with `done`.
242+#[derive(Clone, Debug, Default, Serialize, serde::Deserialize)]
243+#[serde(rename_all = "camelCase")]
244+pub struct WorkMigrated {
245+ /// Counts from 1.
246+ pub page: u32,
247+ pub items: Vec<crate::work::MigratedItem>,
248+ /// The last page.
249+ pub done: bool,
250+}
251+
252+/// A label, as `work_item.labeled`, `pull.labeled` and their `unlabeled`
253+/// say.
213254 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
214255 pub struct EventLabel {
215256 pub name: String,
238279 /// For a change g1t made: the person who asked for it.
239280 #[serde(skip_serializing_if = "Option::is_none")]
240281 pub requested_by: Option<crate::credentials::Principal>,
241− /// The number of the issue it is for.
282+ /// The number of the work item it is for, in its workspace.
242283 #[serde(skip_serializing_if = "Option::is_none")]
243− pub issue: Option<u32>,
284+ pub item: Option<u32>,
244285 #[serde(skip_serializing_if = "Option::is_none")]
245286 pub agent: Option<String>,
246287 /// On merge: the commit the branch now points to. On update, when
374415 pub triggered_by: String,
375416 }
376417
377−/// `checks.completed`: a run of an issue's acceptance checks against a pull
418+/// `checks.completed`: a run of a work item's acceptance checks against a pull
378419 /// request finished.
379420 #[derive(Debug, Serialize)]
380421 #[serde(rename_all = "camelCase")]
411452 pub pull: Option<u32>,
412453 }
413454
414−/// `comment.created`. `number` is the issue or pull request commented on.
455+/// Where a comment is, as the `comment.*` events say: a pull request of a
456+/// repository (`repo_id`, `pull_id`), or a work item of a workspace
457+/// (`workspace`, `item_id`, and the repositories it is linked to in
458+/// `repo_ids`). `number` is the pull request's or the work item's.
415459 #[derive(Debug, Default, Serialize)]
416460 #[serde(rename_all = "camelCase")]
417−pub struct CommentCreated {
418− pub comment_id: String,
419− pub repo_id: String,
461+pub struct CommentOn {
462+ /// The pull request's repository; a work item's origin, if any.
463+ #[serde(skip_serializing_if = "Option::is_none")]
464+ pub repo_id: Option<String>,
420465 pub number: u32,
421466 /// Set when the comment is on a pull request.
422467 #[serde(skip_serializing_if = "Option::is_none")]
423468 pub pull_id: Option<String>,
469+ /// Set when the comment is on a work item.
470+ #[serde(skip_serializing_if = "Option::is_none")]
471+ pub item_id: Option<String>,
472+ #[serde(skip_serializing_if = "Option::is_none")]
473+ pub workspace: Option<String>,
474+ #[serde(skip_serializing_if = "Vec::is_empty")]
475+ pub repo_ids: Vec<String>,
476+}
477+
478+/// `comment.created`.
479+#[derive(Debug, Default, Serialize)]
480+#[serde(rename_all = "camelCase")]
481+pub struct CommentCreated {
482+ pub comment_id: String,
483+ #[serde(flatten)]
484+ pub on: CommentOn,
424485 /// Set when the comment is a review: approve or request changes.
425486 #[serde(skip_serializing_if = "Option::is_none")]
426487 pub verdict: Option<crate::work::Verdict>,
442503 #[serde(rename_all = "camelCase")]
443504 pub struct CommentEdited {
444505 pub comment_id: String,
445− pub repo_id: String,
446− pub number: u32,
447− /// Set when the comment is on a pull request.
448− #[serde(skip_serializing_if = "Option::is_none")]
449− pub pull_id: Option<String>,
506+ #[serde(flatten)]
507+ pub on: CommentOn,
450508 pub changes: CommentChanges,
451509 }
452510
468526 #[serde(rename_all = "camelCase")]
469527 pub struct CommentDeleted {
470528 pub comment_id: String,
471− pub repo_id: String,
472− pub number: u32,
473− /// Set when the comment was on a pull request.
474− #[serde(skip_serializing_if = "Option::is_none")]
475− pub pull_id: Option<String>,
529+ #[serde(flatten)]
530+ pub on: CommentOn,
476531 pub comment: DeletedComment,
477532 }
478533
663718 /// Only events by this account id.
664719 #[serde(default)]
665720 pub actor: Option<String>,
666− /// Only events about these issues or pull requests: their `number`, or
667− /// the `issue` a comment, review or link is on. All when empty.
721+ /// Only events about these work items or pull requests: their
722+ /// `number`, or the `item` a comment, review or link is on. All when
723+ /// empty.
668724 #[serde(default)]
669725 pub numbers: Vec<u32>,
670726 /// Only events at or after this RFC 3339 time.
828884 }
829885
830886 /// `repo.archived` and `repo.unarchived`: a repository became read-only,
831−/// or writable again. While archived, pushes are refused, issues and pull
832−/// requests are locked, and agents and workflows do not run for it; its
833−/// deployments keep serving.
887+/// or writable again. While archived, pushes are refused, its pull
888+/// requests are locked, no work items are filed from it, and agents and
889+/// workflows do not run for it; its deployments keep serving.
834890 #[derive(Clone, Debug, Serialize, serde::Deserialize)]
835891 #[serde(rename_all = "camelCase")]
836892 pub struct RepoArchived {
12481304 pub closed: Vec<ActorCount>,
12491305 }
12501306
1251−/// Issues opened and closed, each by who did it.
1307+/// Work items opened and closed, each by who did it.
12521308 #[derive(Debug, Default, Serialize, serde::Deserialize, PartialEq, Eq)]
1253−pub struct IssueDigest {
1309+pub struct ItemDigest {
12541310 pub opened: Vec<ActorCount>,
12551311 pub closed: Vec<ActorCount>,
12561312 }
12911347 pub repo_id: String,
12921348 pub pushes: PushDigest,
12931349 pub pulls: PullDigest,
1294− pub issues: IssueDigest,
1350+ pub items: ItemDigest,
12951351 /// Reviews: `review.completed` (g1t's), and a comment with a verdict (a person's).
12961352 pub reviews: Vec<ActorCount>,
12971353 /// Comments without a verdict; one by an agent as itself is the agent's.
13321388 fn comment_edits_and_deletes_say_what_it_was() {
13331389 let edited = CommentEdited {
13341390 comment_id: "cmt_1".into(),
1335− repo_id: "rep_1".into(),
1336− number: 7,
1337− pull_id: Some("pr_1".into()),
1391+ on: CommentOn { repo_id: Some("rep_1".into()), number: 7, pull_id: Some("pr_1".into()), ..CommentOn::default() },
13381392 changes: CommentChanges { body: ChangedFrom { from: "Before".into() } },
13391393 };
13401394 assert_eq!(
13441398 "changes": { "body": { "from": "Before" } },
13451399 })
13461400 );
1401+ // On a work item: its workspace and every repository it is linked to.
13471402 let deleted = CommentDeleted {
13481403 comment_id: "cmt_1".into(),
1349− repo_id: "rep_1".into(),
1350− number: 7,
1351− pull_id: None,
1404+ on: CommentOn {
1405+ repo_id: Some("rep_1".into()),
1406+ number: 7,
1407+ item_id: Some("wki_1".into()),
1408+ workspace: Some("acme".into()),
1409+ repo_ids: vec!["rep_1".into(), "rep_2".into()],
1410+ ..CommentOn::default()
1411+ },
13521412 comment: DeletedComment {
13531413 id: "cmt_1".into(),
13541414 body: "Gone".into(),
13611421 assert_eq!(
13621422 serde_json::to_value(deleted).unwrap(),
13631423 serde_json::json!({
1364− "commentId": "cmt_1", "repoId": "rep_1", "number": 7,
1424+ "commentId": "cmt_1", "repoId": "rep_1", "number": 7, "itemId": "wki_1", "workspace": "acme",
1425+ "repoIds": ["rep_1", "rep_2"],
13651426 "comment": {
13661427 "id": "cmt_1", "body": "Gone", "author": { "id": "usr_1", "username": "ana" },
13671428 "createdAt": "2026-10-08T00:00:00Z", "path": null, "line": null,
+26−19
33 //! The events service keeps it, beside the event log: as events arrive it
44 //! works out who should hear of each (see `services/events/src/inbox.rs`)
55 //! and why. Each person has one item per **thread**, the thing it is about
6−//! (an issue, a pull request, a workflow on a branch, a deployment): new
6+//! (a work item, a pull request, a workflow on a branch, a deployment): new
77 //! activity on a thread brings its item back to the top, unread, and adds
88 //! a line to its short history, rather than adding another item. Items are
99 //! kept by username, which never changes.
3535 //! `inbox_update_settings` takes `UpdateInboxSettingsArgs` and returns
3636 //! `InboxSettings`.
3737 //!
38−//! What an event is about (the issue or pull request, its people, the
38+//! What an event is about (the work item or pull request, its people, the
3939 //! comment) comes from the work service's `inbox_subject`, which takes
4040 //! `InboxSubjectArgs` and returns `Option<InboxSubject>`.
4141
187187 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
188188 #[serde(rename_all = "lowercase")]
189189 pub enum SubjectKind {
190− Issue,
190+ /// A work item.
191+ Item,
191192 Pull,
192193 /// A workflow's runs on one branch.
193194 Run,
196197 }
197198
198199 impl SubjectKind {
199− pub const ALL: [SubjectKind; 4] = [SubjectKind::Issue, SubjectKind::Pull, SubjectKind::Run, SubjectKind::Deploy];
200+ pub const ALL: [SubjectKind; 4] = [SubjectKind::Item, SubjectKind::Pull, SubjectKind::Run, SubjectKind::Deploy];
200201
201202 pub fn as_str(self) -> &'static str {
202203 match self {
203− SubjectKind::Issue => "issue",
204+ SubjectKind::Item => "item",
204205 SubjectKind::Pull => "pull",
205206 SubjectKind::Run => "run",
206207 SubjectKind::Deploy => "deploy",
235236 /// The workspace it happened in.
236237 pub workspace: Option<String>,
237238 pub subject: Option<SubjectKind>,
238− /// The issue or pull request's number.
239+ /// The work item's or pull request's number.
239240 pub number: Option<u32>,
240241 /// Where it is on g1t.sh: a path such as `/acme/rocket/pull/12`.
241242 pub url: String,
432433 pub item: InboxItem,
433434 /// Newest first, at most [`MAX_ACTIVITY`].
434435 pub activity: Vec<InboxActivity>,
435− /// For an issue or pull request; absent for a run or a deployment,
436+ /// For a work item or pull request; absent for a run or a deployment,
436437 /// which nobody subscribes to.
437438 pub subscription: Option<ThreadSubscription>,
438439 }
439440
440−/// A person's subscription to an issue or pull request.
441+/// A person's subscription to a work item or pull request.
441442 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
442443 #[serde(rename_all = "camelCase")]
443444 pub struct ThreadSubscription {
450451 /// (`comment`), were mentioned (`mention`) or subscribed by hand
451452 /// (`manual`). Absent when they are not.
452453 pub reason: Option<Reason>,
453− /// `owner/name`, and the issue or pull request's number.
454+ /// `owner/name` (a work item's workspace, for one), and the work
455+ /// item's or pull request's number.
454456 pub repo: Option<String>,
455457 pub number: Option<u32>,
456458 /// RFC 3339: when they last chose, or null if they never did.
457459 pub updated_at: Option<String>,
458460 }
459461
460−/// Which issue or pull request: by a thread's id, or by its repository and
462+/// Which work item or pull request: by a thread's id, or by its repository and
461463 /// number.
462464 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
463465 #[serde(rename_all = "camelCase")]
493495 /// Only what they take part in or are mentioned in: the default.
494496 #[default]
495497 Participating,
496− /// Everything: every issue and pull request opened, commented on,
498+ /// Everything: every work item and pull request opened, commented on,
497499 /// closed or merged, and every deployment.
498500 All,
499501 /// Nothing at all, not even a mention.
657659 }
658660
659661 /// `inbox_subject` on the work service: what an event names, for the
660−/// inbox. A service-to-service read: it checks nobody's access, and what
661−/// it returns is only ever shown to the people it names, or to those who
662−/// can read the repository.
662+/// inbox: a work item by `item_id` (from a `work_item.*` or comment
663+/// event's data), else the pull request `number` of `repo_id`. A
664+/// service-to-service read: it checks nobody's access, and what it returns
665+/// is only ever shown to the people it names, or to those who can read it.
663666 #[derive(Clone, Debug, Serialize, Deserialize)]
664667 #[serde(rename_all = "camelCase")]
665668 pub struct InboxSubjectArgs {
666− pub repo_id: String,
669+ #[serde(default)]
670+ pub repo_id: Option<String>,
671+ #[serde(default)]
667672 pub number: u32,
673+ #[serde(default)]
674+ pub item_id: Option<String>,
668675 /// The comment the event is about, if any.
669676 #[serde(default)]
670677 pub comment_id: Option<String>,
673680 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
674681 #[serde(rename_all = "camelCase")]
675682 pub struct InboxSubject {
676− /// `issue` or `pull`.
683+ /// `item` or `pull`.
677684 pub kind: Option<SubjectKind>,
678685 pub title: String,
679686 pub author: Principal,
687694 #[serde(default)]
688695 pub reviewers: Vec<String>,
689696 /// The people its description mentions by name, outside code and
690− /// quotes, for `issue.opened` and `pull.opened`. Never `g1t`.
697+ /// quotes, for `work_item.opened` and `pull.opened`. Never `g1t`.
691698 #[serde(default)]
692699 pub mentions: Vec<String>,
693700 /// The teams its description mentions, with the people each tells.
694701 #[serde(default)]
695702 pub team_mentions: Vec<TeamMentioned>,
696− /// For a pull request: the issue it is for, with that issue's people.
703+ /// For a pull request: the work item it is for, with its people.
697704 #[serde(default)]
698− pub issue: Option<Box<InboxSubject>>,
705+ pub item: Option<Box<InboxSubject>>,
699706 #[serde(default)]
700707 pub comment: Option<InboxComment>,
701708 }
+10−10
3636 /// The full ref a push or deletion moved (`refs/heads/main`), or a run's.
3737 #[serde(rename = "ref", default, skip_serializing_if = "Option::is_none")]
3838 pub git_ref: Option<String>,
39− /// The issue or pull request it is on, or the pull request a run ran for.
39+ /// The work item or pull request it is on, or the pull request a run ran for.
4040 #[serde(default, skip_serializing_if = "Option::is_none")]
4141 pub number: Option<u32>,
4242 /// The commit: where a push left its ref, the one checked or reported on.
6767 "branch.renamed",
6868 "repo.default_branch_changed",
6969 "pull.",
70− "issue.",
70+ "work_item.",
7171 "comment.",
7272 "review.",
7373 "checks.",
8282 "secret_scanning_alert.",
8383 "code_scanning_alert.",
8484 "vulnerability_alert.",
85− // Coordinating work: the issue, pull request and Work pages show them.
85+ // Coordinating work: the work item, pull request and Work pages show them.
8686 "work.",
8787 "session.started",
8888 "session.ended",
139139 "check_suite" => change.sha = text(&data["checkSuite"]["headSha"]),
140140 "code_scanning_alert" => change.number = number(&data["alertNumber"]),
141141 "secret_scanning_alert" | "vulnerability_alert" | "release" | "queue" | "deployment" | "deployment_status" | "repo" | "branch" => {}
142− // Issues, pull requests, comments and reviews name their number.
142+ // Work items, pull requests, comments and reviews name their number.
143143 _ => change.number = number(&data["number"]),
144144 }
145145 Some(change)
191191 }
192192
193193 #[test]
194− fn pull_requests_issues_comments_and_checks_name_their_number() {
195− for kind in ["pull.updated", "pull.merged", "issue.closed", "comment.created", "review.completed"] {
194+ fn pull_requests_work_items_comments_and_checks_name_their_number() {
195+ for kind in ["pull.updated", "pull.merged", "work_item.closed", "comment.created", "review.completed"] {
196196 let change = change_of(&event(kind, Some("rep_1"), json!({ "number": 7, "title": "secret plans" }))).unwrap();
197197 assert_eq!(change.number, Some(7), "{kind}");
198198 }
219219
220220 #[test]
221221 fn nothing_written_in_the_repository_goes_along() {
222− let change = change_of(&event("issue.opened", Some("rep_1"), json!({ "number": 1, "title": "Leaked title", "body": "Leaked body" }))).unwrap();
222+ let change = change_of(&event("work_item.opened", Some("rep_1"), json!({ "number": 1, "title": "Leaked title", "body": "Leaked body" }))).unwrap();
223223 let sent = serde_json::to_string(&change).unwrap();
224224 assert!(!sent.contains("Leaked"));
225− assert_eq!(sent, r#"{"repo_id":"rep_1","kind":"issue.opened","number":1,"at":"2026-10-11T10:00:00Z"}"#);
225+ assert_eq!(sent, r#"{"repo_id":"rep_1","kind":"work_item.opened","number":1,"at":"2026-10-11T10:00:00Z"}"#);
226226 }
227227
228228 #[test]
241241 }
242242
243243 #[test]
244− fn coordination_names_the_issue_or_pull_request_it_is_about() {
244+ fn coordination_names_the_work_item_or_pull_request_it_is_about() {
245245 for kind in ["work.claimed", "work.claim_expired", "session.started", "dependency.changed", "overlap.detected", "integration.failed", "integration.passed", "decision.recorded"] {
246− let change = change_of(&event(kind, None, json!({ "repoId": "rep_1", "number": 12, "issue": 4 }))).unwrap();
246+ let change = change_of(&event(kind, None, json!({ "repoId": "rep_1", "number": 12, "item": 4 }))).unwrap();
247247 assert_eq!((change.repo_id.as_str(), change.number), ("rep_1", Some(12)), "{kind}");
248248 }
249249 // Each recorded step of a session changes no page anyone is watching live.
+12−0
913913 pub fn covers_repo(&self, repo_id: &str, namespace: &str) -> bool {
914914 self.reach.as_ref().is_none_or(|reach| reach.covers(repo_id, namespace))
915915 }
916+
917+ /// Whether it reaches the whole workspace `namespace`, as its work
918+ /// items, labels and milestones need: every token but a workflow job's
919+ /// (its one repository) and a narrowed one made for another workspace
920+ /// or for some of this one's repositories.
921+ pub fn covers_workspace(&self, namespace: &str) -> bool {
922+ self.repo.is_none()
923+ && self
924+ .reach
925+ .as_ref()
926+ .is_none_or(|reach| reach.owned_by(namespace) && reach.repositories == RepositorySelection::All)
927+ }
916928 }
917929
918930 /// Every operation of the API and MCP server, with the scope it needs. An
+17−13
3636 "branch.renamed",
3737 "repo.created",
3838 "repo.archived",
39+ "repo.visibility_changed",
3940 "repo.default_branch_changed",
4041 "pull.merged",
4142 "comment.created",
4243 ],
4344 ),
44− // Pull requests g1t is seeing through, mentions, queued issues and the
45+ // Pull requests g1t is seeing through, mentions, queued work items and the
4546 // merge queue (runner/src/index.ts queue).
4647 (
4748 "SUBSCRIBER_RUNNER",
5758 "checks.completed",
5859 "queue.changed",
5960 "comment.created",
60− "issue.opened",
61− "issue.updated",
62− "issue.closed",
61+ "work_item.opened",
62+ "work_item.updated",
63+ "work_item.closed",
6364 "repo.archived",
6465 ],
6566 ),
6667 // Write-back to linked trackers (integrations/src lib.rs), and g1t
6768 // taking on what it is assigned or asked in a thread (summon.rs).
68− ("SUBSCRIBER_INTEGRATIONS", &["issue.closed", "pull.opened", "issue.assigned", "comment.created"]),
69+ // `work.migrated` rewrites links kept to issue numbers.
70+ ("SUBSCRIBER_INTEGRATIONS", &["work_item.closed", "pull.opened", "work_item.assigned", "comment.created", "work.migrated"]),
6971 // A hook may ask for any type, or for every one.
7072 ("SUBSCRIBER_WEBHOOKS", &["*"]),
7173 // Every event a workflow can run on (g1t_actions::events::github_events),
7678 "git.push",
7779 "git.deleted",
7880 "pull.*",
79− "issue.*",
81+ "work_item.*",
8082 "comment.*",
8183 "release.*",
8284 "deployment.created",
112114 &[
113115 "git.push",
114116 "repo.*",
115− "issue.opened",
116− "issue.closed",
117− "issue.reopened",
117+ "work_item.opened",
118+ "work_item.closed",
119+ "work_item.reopened",
118120 "pull.opened",
119121 "pull.updated",
120122 "pull.merged",
163165 ],
164166 ),
165167 // The site-wide search index (search/src/index.rs).
166− ("SUBSCRIBER_SEARCH", &["git.push", "repo.*", "issue.*", "pull.*", "user.*", "workspace.*"]),
168+ ("SUBSCRIBER_SEARCH", &["git.push", "repo.*", "work_item.*", "work.migrated", "pull.*", "user.*", "workspace.*"]),
167169 // Composer packages read from repositories, and packages that follow
168170 // their workspace, team or repository (packages/src/lib.rs on_event).
169171 (
206208 use super::*;
207209
208210 /// Types published on the bus that hooks are not offered.
209− const UNOFFERED: [&str; 28] = [
211+ const UNOFFERED: [&str; 29] = [
212+ // The one-off move of issues into work items.
213+ "work.migrated",
210214 "pull.mergecheck",
211215 "pull.mergeability",
212216 "deployment.review_requested",
281285 assert!(!routed("SUBSCRIBER_ACTIONS", "check_run.completed"));
282286 assert!(routed("SUBSCRIBER_RUNNER", "comment.created"));
283287 assert!(!routed("SUBSCRIBER_RUNNER", "status.created"));
284− assert!(routed("SUBSCRIBER_INTEGRATIONS", "issue.closed"));
285− assert!(routed("SUBSCRIBER_INTEGRATIONS", "issue.assigned"));
288+ assert!(routed("SUBSCRIBER_INTEGRATIONS", "work_item.closed"));
289+ assert!(routed("SUBSCRIBER_INTEGRATIONS", "work_item.assigned"));
286290 assert!(routed("SUBSCRIBER_INTEGRATIONS", "comment.created"));
287291 assert!(!routed("SUBSCRIBER_INTEGRATIONS", "git.push"));
288292 assert!(!routed("SUBSCRIBER_INTEGRATIONS", "check_run.completed"));
+13−10
1515 use crate::{User, Viewer};
1616
1717 /// Every event a webhook can be sent, in the order people are shown them.
18−pub const EVENT_TYPES: [&str; 127] = [
18+pub const EVENT_TYPES: [&str; 130] = [
1919 "git.push",
2020 "git.deleted",
2121 "branch.renamed",
4848 "ruleset.created",
4949 "ruleset.updated",
5050 "ruleset.deleted",
51− "issue.opened",
52− "issue.updated",
53− "issue.assigned",
54− "issue.labeled",
55− "issue.unlabeled",
56− "issue.milestoned",
57− "issue.demilestoned",
58− "issue.closed",
59− "issue.reopened",
51+ "work_item.opened",
52+ "work_item.updated",
53+ "work_item.assigned",
54+ "work_item.labeled",
55+ "work_item.unlabeled",
56+ "work_item.milestoned",
57+ "work_item.demilestoned",
58+ "work_item.closed",
59+ "work_item.reopened",
60+ "work_item.linked",
61+ "work_item.unlinked",
62+ "work_item.landed",
6063 "comment.created",
6164 "comment.edited",
6265 "comment.deleted",
+205−324
1−//! The work service: issues, pull requests, comments and sessions.
1+//! The work service: work items, pull requests, comments and sessions.
22 //!
33 //! Each `*Args` struct is the argument of the method of the same name,
44 //! served at `POST /rpc/<method>`.
55 //!
6−//! Issues and pull requests share one sequence of numbers per repository,
7−//! so `#12` names exactly one of them.
6+//! Work items belong to a workspace and link the repositories the work
7+//! touches. Work items and new pull requests share one sequence of numbers
8+//! per workspace, so `#12` in a workspace names exactly one of them; pull
9+//! requests still live in their repositories. See [`refs`] for how `#N`
10+//! in text resolves.
811
912 use serde::{Deserialize, Serialize};
1013
1922 mod coordination;
2023 pub use coordination::*;
2124
25+mod items;
26+pub use items::*;
27+
28+pub mod refs;
29+
2230 /// The labels a new repository starts with, and that "Add the default
2331 /// labels" adds to one that is missing some: `(name, color, description)`.
2432 /// Colors are six hex digits, without `#`.
2533 pub const DEFAULT_LABELS: [(&str, &str, &str); 11] = [
2634 ("bug", "d73a4a", "Something isn't working"),
2735 ("documentation", "0075ca", "Improvements or additions to documentation"),
28− ("duplicate", "cfd3d7", "This issue or pull request already exists"),
36+ ("duplicate", "cfd3d7", "This work item or pull request already exists"),
2937 ("enhancement", "a2eeef", "New feature or request"),
3038 ("good first issue", "7057ff", "Good for newcomers"),
3139 ("help wanted", "008672", "Extra attention is needed"),
6371 }
6472 }
6573
66−/// The most labels one issue or pull request can carry, and the longest
67−/// name and description of one.
74+/// The most labels one work item or pull request can carry, and the
75+/// longest name and description of one.
6876 pub const MAX_LABELS: usize = 20;
6977 pub const MAX_LABEL_CHARS: usize = 50;
7078 pub const MAX_LABEL_DESCRIPTION_CHARS: usize = 100;
7179
72−/// A label of a repository: a name, a color and what it means. Issues and
73−/// pull requests carry labels by name. Names are lowercase and unique in
74−/// a repository.
80+/// A label of a workspace: a name, a color and what it means. Work items
81+/// and the pull requests of its repositories carry labels by name. Names
82+/// are lowercase and unique in a workspace.
7583 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
7684 #[serde(rename_all = "camelCase")]
7785 pub struct Label {
8088 pub color: String,
8189 #[serde(default)]
8290 pub description: String,
83− /// How many issues carry it, open or closed.
91+ /// How many work items carry it, open or closed.
8492 #[serde(default)]
85− pub issues: u32,
93+ pub items: u32,
8694 /// How many pull requests carry it, in any state.
8795 #[serde(default)]
8896 pub pulls: u32,
8997 }
9098
91−/// A milestone, as an issue or pull request names it.
99+/// A milestone, as a work item or pull request names it.
92100 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
93101 #[serde(rename_all = "camelCase")]
94102 pub struct MilestoneRef {
96104 pub title: String,
97105 }
98106
99−/// A milestone: a goal, with an optional due date, that issues and pull
100−/// requests are gathered under. Its progress is how many of them are
101−/// closed (a merged pull request counts as closed).
107+/// A milestone of a workspace: a goal, with an optional due date, that
108+/// work items and pull requests are gathered under. Its progress is how
109+/// many of them are closed (a merged pull request counts as closed).
102110 #[derive(Clone, Debug, Serialize, Deserialize)]
103111 #[serde(rename_all = "camelCase")]
104112 pub struct Milestone {
105− /// Numbered from 1 in each repository, apart from issues.
113+ /// Numbered from 1 in each workspace, apart from work items.
106114 pub number: u32,
107115 pub title: String,
108116 /// Markdown.
112120 #[serde(default)]
113121 pub due_on: Option<String>,
114122 pub state: State,
115− /// Open issues and pull requests in it (drafts count as open).
123+ /// Open work items and pull requests in it (drafts count as open).
116124 #[serde(default)]
117125 pub open_items: u32,
118− /// Closed issues, and merged or closed pull requests, in it.
126+ /// Closed work items, and merged or closed pull requests, in it.
119127 #[serde(default)]
120128 pub closed_items: u32,
129+ /// For a milestone moved from a repository when work items moved to
130+ /// the workspace: that repository's id.
131+ #[serde(default, skip_serializing_if = "Option::is_none")]
132+ pub origin_repo_id: Option<String>,
121133 /// RFC 3339.
122134 pub created_at: String,
123135 /// RFC 3339.
152164 ((1..=12).contains(&month) && (1..=31).contains(&date)).then(|| day.to_owned())
153165 }
154166
155−/// `list_labels`: a repository's labels, by name, each with how many
156−/// issues and pull requests carry it. Takes `ViewArgs`; returns
167+/// `list_labels`: a workspace's labels, by name, each with how many work
168+/// items and pull requests carry it. Takes [`WorkspaceViewArgs`]; returns
157169 /// `Outcome<Vec<Label>>`.
158170 ///
159171 /// `save_label`: creates a label, or with `name` changes one (renaming it
160−/// renames it on every issue and pull request). Needs the Triage role.
161−/// Returns `Outcome<Label>`.
172+/// renames it on every work item and pull request). Needs the Write role
173+/// in the workspace (`Capability::ManageLabels`). Returns `Outcome<Label>`.
162174 #[derive(Debug, Serialize, Deserialize)]
163175 #[serde(rename_all = "camelCase")]
164176 pub struct SaveLabelArgs {
165177 pub actor: User,
166− pub repo: RepoPath,
178+ /// The workspace's slug.
179+ pub workspace: String,
167180 /// The label to change; absent to create one.
168181 #[serde(default)]
169182 pub name: Option<String>,
177190 pub description: Option<String>,
178191 }
179192
180−/// `delete_label`: removes a label from the repository and from every
181−/// issue and pull request that carries it. Needs the Triage role. Returns
182−/// `Outcome<bool>`.
193+/// `delete_label`: removes a label from the workspace and from every work
194+/// item and pull request that carries it. Needs `ManageLabels` in the
195+/// workspace. Returns `Outcome<bool>`.
183196 #[derive(Debug, Serialize, Deserialize)]
184197 pub struct DeleteLabelArgs {
185198 pub actor: User,
186− pub repo: RepoPath,
199+ pub workspace: String,
187200 pub name: String,
188201 }
189202
190−/// `add_default_labels`: adds those of [`DEFAULT_LABELS`] the repository
191−/// does not have yet. Needs the Triage role. Returns
203+/// `add_default_labels`: adds those of [`DEFAULT_LABELS`] the workspace
204+/// does not have yet. Needs `ManageLabels` in the workspace. Returns
192205 /// `Outcome<Vec<Label>>`, every label it has now.
193206 #[derive(Debug, Serialize, Deserialize)]
194−pub struct RepoActorArgs {
207+pub struct WorkspaceActorArgs {
195208 pub actor: User,
196− pub repo: RepoPath,
209+ pub workspace: String,
210+}
211+
212+/// `list_labels`: what a workspace keeps, for whoever may see one of its
213+/// work items or is a member.
214+#[derive(Debug, Serialize, Deserialize)]
215+pub struct WorkspaceViewArgs {
216+ pub workspace: String,
217+ pub viewer: Viewer,
197218 }
198219
199−/// How `set_labels` changes an issue's or pull request's labels.
220+/// How `set_labels` changes a work item's or pull request's labels.
200221 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
201222 #[serde(rename_all = "lowercase")]
202223 pub enum LabelChange {
209230 Remove,
210231 }
211232
212−/// `set_labels`: changes the labels of an issue or a pull request (they
213−/// share numbers). A label the repository does not have yet is created
214−/// when the actor has the Triage role; anyone else may only use existing
215−/// ones, on what they opened. Returns `Outcome<Vec<String>>`, its labels
216−/// now.
233+/// `set_labels`: changes the labels of a thread (see [`AddCommentArgs`]):
234+/// with `repo`, its pull request `number`; without, the work item `number`
235+/// of `workspace`. A label the workspace does not have yet is created when
236+/// the actor may manage labels; anyone else may only use existing ones,
237+/// and only on what they opened unless they may triage. Returns
238+/// `Outcome<Vec<String>>`, its labels now.
217239 #[derive(Debug, Serialize, Deserialize)]
218240 #[serde(rename_all = "camelCase")]
219241 pub struct SetLabelsArgs {
220242 pub actor: User,
221− pub repo: RepoPath,
243+ #[serde(default)]
244+ pub repo: Option<RepoPath>,
245+ #[serde(default)]
246+ pub workspace: String,
222247 pub number: u32,
223248 pub labels: Vec<String>,
224249 #[serde(default)]
225250 pub change: LabelChange,
226251 }
227252
228−/// `list_milestones`: a repository's milestones, open ones by due date
253+/// `list_milestones`: a workspace's milestones, open ones by due date
229254 /// (soonest first, those without one last), then closed ones most recently
230255 /// closed first. Returns `Outcome<Vec<Milestone>>`.
231256 #[derive(Debug, Serialize, Deserialize)]
232257 pub struct ListMilestonesArgs {
233− pub repo: RepoPath,
258+ pub workspace: String,
234259 pub viewer: Viewer,
235260 /// Both when absent.
236261 #[serde(default)]
237262 pub state: Option<State>,
238263 }
239264
240−/// `get_milestone`: one milestone and what is in it. Takes `ViewArgs`,
241−/// whose `number` is the milestone's. Returns `Outcome<MilestoneDetail>`.
265+/// `get_milestone`: one milestone and what is in it. Takes
266+/// [`WorkItemViewArgs`], whose `number` is the milestone's. Returns
267+/// `Outcome<MilestoneDetail>`.
242268 #[derive(Clone, Debug, Serialize, Deserialize)]
243269 pub struct MilestoneDetail {
244270 pub milestone: Milestone,
245− /// Newest first, open and closed.
246− pub issues: Vec<Issue>,
271+ /// Newest first, open and closed, those the viewer may see.
272+ pub items: Vec<WorkItem>,
247273 /// Newest first, in any state.
248274 pub pulls: Vec<Pull>,
249275 }
250276
251−/// `save_milestone`: creates a milestone, or with `number` changes the
252−/// fields given. Needs the Triage role. Returns `Outcome<Milestone>`.
277+/// `save_milestone`: creates a milestone of a workspace, or with `number`
278+/// changes the fields given. Needs `ManageLabels` in the workspace.
279+/// Returns `Outcome<Milestone>`.
253280 #[derive(Debug, Serialize, Deserialize)]
254281 #[serde(rename_all = "camelCase")]
255282 pub struct SaveMilestoneArgs {
256283 pub actor: User,
257− pub repo: RepoPath,
284+ pub workspace: String,
258285 /// The milestone to change; absent to create one.
259286 #[serde(default)]
260287 pub number: Option<u32>,
271298 }
272299
273300 /// `delete_milestone`: removes a milestone; what was in it is in none.
274−/// Needs the Triage role. Returns `Outcome<bool>`.
301+/// Needs `ManageLabels` in the workspace. Returns `Outcome<bool>`.
275302 #[derive(Debug, Serialize, Deserialize)]
276303 pub struct DeleteMilestoneArgs {
277304 pub actor: User,
278− pub repo: RepoPath,
305+ pub workspace: String,
279306 pub number: u32,
280307 }
281308
282−/// `open` or `closed`: the filter on lists of issues and pull requests. An
309+/// `open` or `closed`: the filter on lists of work items and pull requests. An
283310 /// open pull request is a draft or one ready for review; a closed one was
284311 /// merged or closed without merging.
285312 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
289316 Closed,
290317 }
291318
292−/// Why an issue was closed.
293319 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
294−#[serde(rename_all = "snake_case")]
295−pub enum IssueReason {
296− /// The work was done. If a pull request did it, `resolved_by` names it.
297− Completed,
298− NotPlanned,
299−}
300−
301−impl IssueReason {
302− pub fn as_str(self) -> &'static str {
303− match self {
304− IssueReason::Completed => "completed",
305− IssueReason::NotPlanned => "not_planned",
306− }
307− }
308−}
309−
310−/// Something that should change in a repository: a bug, a feature, a
311−/// question. Opened by a person, an agent or an integration. Pull requests
312−/// are made against it; the one that is merged resolves it.
313−#[derive(Clone, Debug, Serialize, Deserialize)]
314−#[serde(rename_all = "camelCase")]
315−pub struct Issue {
316− pub id: String,
317− pub repo_id: String,
318− /// Shown as `#12`.
319− pub number: u32,
320− pub title: String,
321− /// Markdown. Also what an agent is given to work from.
322− pub body: String,
323− pub labels: Vec<String>,
324− pub state: State,
325− /// Set when closed.
326− pub reason: Option<IssueReason>,
327− /// The number of the pull request whose merge closed this issue.
328− pub resolved_by: Option<u32>,
329− /// The commit pushed to the default branch whose message closed this
330− /// issue (`Closes #12`), when a commit rather than a pull request did.
331− #[serde(default)]
332− pub resolved_commit: Option<String>,
333− /// Who pushed that commit, by username.
334− #[serde(default)]
335− pub resolved_pushed_by: Option<String>,
336− /// Who opened it: a person, an integration, or g1t (`kind` `agent`)
337− /// for one its agent filed while at work.
338− pub author: User,
339− /// For an issue g1t's agent filed: the person it was working for. They
340− /// may manage it as its author could. See [`owner`](Issue::owner).
341− #[serde(default)]
342− pub requested_by: Option<User>,
343− /// RFC 3339.
344− pub created_at: String,
345− /// RFC 3339.
346− pub updated_at: String,
347− /// RFC 3339.
348− pub closed_at: Option<String>,
349− /// Pull requests made against this issue, in any state.
350− pub pull_count: u32,
351− pub comment_count: u32,
352− /// Usernames of the people it is assigned to.
353− #[serde(default)]
354− pub assignees: Vec<String>,
355− /// The numbers of the issues that have to be merged before this one is
356− /// worked on.
357− #[serde(default)]
358− pub blocked_by: Vec<u32>,
359− /// The agent working on it now: the one behind its newest pull request
360− /// that is still in progress in a fork, such as `g1t`.
361− #[serde(default)]
362− pub agent: Option<String>,
363− /// The milestone it is in, if any.
364− #[serde(default)]
365− pub milestone: Option<MilestoneRef>,
366−}
367−
368−#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
369320 #[serde(rename_all = "lowercase")]
370321 pub enum PullStatus {
371322 /// Still being worked on.
409360 pub struct Pull {
410361 pub id: String,
411362 pub repo_id: String,
412− /// Shown as `#12`.
363+ /// Shown as `#12`. New ones are numbered in their workspace's
364+ /// sequence, which work items share.
413365 pub number: u32,
414− /// The number of the issue this is for, if any.
415− pub issue: Option<u32>,
366+ /// The work item this is for, if any, by number in the workspace of
367+ /// its repository.
368+ pub item: Option<u32>,
416369 pub title: String,
417370 /// Markdown: what changed and why. Set when marked ready.
418371 pub body: Option<String>,
455408 /// asked: see [`AutoMerge`].
456409 #[serde(default)]
457410 pub auto_merge: Option<AutoMerge>,
458− /// The issues it closes when it merges into the default branch: `issue`
459− /// and those its title and description name after a closing keyword
460− /// (`Fixes #12`, or `closes owner/repo#3` naming this repository), in
461− /// the order named. Empty where the repository does not close issues.
411+ /// The work items it closes when it merges into the default branch,
412+ /// by number in its workspace: `item` and those its title and
413+ /// description name after a closing keyword (`Fixes #12`, or
414+ /// `closes owner/name#3` for a repository of the same workspace), in
415+ /// the order named. Merging marks this repository's link to each as
416+ /// landed. Empty where the repository does not close work items.
462417 #[serde(default)]
463418 pub closes: Vec<u32>,
419+ /// The work items it is part of (`Part of #12`, `Refs #12`): it is
420+ /// linked to them, without counting toward closing them.
421+ #[serde(default)]
422+ pub parts: Vec<u32>,
464423 /// Its branch was deleted after it merged or closed, and can be
465424 /// restored at `head_commit` (`restore_head_branch`).
466425 #[serde(default)]
467426 pub head_branch_deleted: bool,
468− /// Set on a pull request closed because another one for the same issue
469− /// was merged: that one's number.
427+ /// Set on a pull request closed because another one for the same work
428+ /// item was merged: that one's number.
470429 pub superseded_by: Option<u32>,
471430 /// `failed` when the merge queue took it out because its combined
472431 /// state failed, until its head moves. Its checks are the statuses
503462 /// for a change g1t made.
504463 pub author: User,
505464 /// For a change g1t made: the person who asked for it, by assigning
506− /// an issue or handing g1t the work. They answer for it as its author
465+ /// a work item or handing g1t the work. They answer for it as its author
507466 /// would. See [`owner`](Pull::owner).
508467 #[serde(default)]
509468 pub requested_by: Option<User>,
529488 }
530489
531490 /// Another pull request in progress that changes some of the same files.
532−/// Two for the same issue are alternatives; two for different issues are
533−/// heading for a conflict.
491+/// Two for the same work item are alternatives; two for different ones
492+/// are heading for a conflict.
534493 #[derive(Clone, Debug, Serialize, Deserialize)]
535494 pub struct Overlap {
536495 pub number: u32,
537496 pub title: String,
538− /// The number of the issue the other pull request is for.
539− pub issue: Option<u32>,
497+ /// The number of the work item the other pull request is for.
498+ pub item: Option<u32>,
540499 /// The files both change.
541500 pub paths: Vec<String>,
542501 }
543502
544−impl Issue {
545− /// Who the issue is theirs to manage: the person g1t's agent filed it
546− /// for, or its author. They may edit, close and reopen it without the
547− /// Triage role.
548− pub fn owner(&self) -> &User {
549− self.requested_by.as_ref().unwrap_or(&self.author)
550− }
551−}
552503
553504 impl Pull {
554505 /// Who the pull request is theirs to answer for: whoever asked g1t to
656607 }
657608 }
658609
659−/// Who is recorded as opening an issue or a pull request that `actor`
610+/// Who is recorded as opening a work item or a pull request that `actor`
660611 /// opens, and who asked for it: `(author, requested_by)`.
661612 ///
662613 /// - A session g1t started for someone, which acts as that person with
718669 }
719670
720671 /// How one command went. Recorded by earlier runs of commands written on
721−/// issues, which g1t no longer runs; kept so their history still reads.
672+/// work items, which g1t no longer runs; kept so their history still reads.
722673 #[derive(Clone, Debug, Serialize, Deserialize)]
723674 #[serde(rename_all = "camelCase")]
724675 pub struct CheckResult {
736687 }
737688
738689 /// A record against a pull request's head: the merge queue taking it out,
739−/// with why, or an earlier run of commands written on its issue.
690+/// with why, or an earlier run of commands written on its work item.
740691 #[derive(Clone, Debug, Serialize, Deserialize)]
741692 #[serde(rename_all = "camelCase")]
742693 pub struct CheckRun {
770721 }
771722 }
772723
773−/// An emoji reaction to an issue, a pull request or a comment: the same
724+/// An emoji reaction to a work item, a pull request or a comment: the same
774725 /// eight as everywhere else people react to code.
775726 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
776727 pub enum ReactionContent {
852803 groups
853804 }
854805
855−/// `react`: `actor` reacts with `content` to comment `comment_id` on issue
856−/// or pull request `number`, or to its description when `comment_id` is
857−/// unset; with `remove`, takes that reaction back. Anyone who can read the
858−/// repository may. Returns `Outcome<Vec<ReactionGroup>>`: what it has now.
806+/// `react`: `actor` reacts with `content` to comment `comment_id` on a
807+/// thread (see [`AddCommentArgs`]), or to its description when
808+/// `comment_id` is unset; with `remove`, takes that reaction back. Anyone
809+/// who can read it may. Returns `Outcome<Vec<ReactionGroup>>`: what it has
810+/// now.
859811 #[derive(Debug, Serialize, Deserialize)]
860812 #[serde(rename_all = "camelCase")]
861813 pub struct ReactArgs {
862814 pub actor: User,
863− pub repo: RepoPath,
815+ #[serde(default)]
816+ pub repo: Option<RepoPath>,
817+ #[serde(default)]
818+ pub workspace: String,
864819 pub number: u32,
865820 #[serde(default)]
866821 pub comment_id: Option<String>,
880835 Event,
881836 }
882837
883−/// A comment on an issue or a pull request. On a pull request it can sit
838+/// A comment on a work item or a pull request. On a pull request it can sit
884839 /// on one line of the change, and it can carry a reviewer's verdict.
885840 #[derive(Clone, Debug, Serialize, Deserialize)]
886841 #[serde(rename_all = "camelCase")]
1023978 pub commit: Option<String>,
1024979 }
1025980
1026−#[derive(Clone, Debug, Serialize, Deserialize)]
1027−pub struct IssueDetail {
1028− pub issue: Issue,
1029− /// Every pull request made against it, oldest first.
1030− pub pulls: Vec<Pull>,
1031− pub comments: Vec<Comment>,
1032− /// How people (and g1t) reacted to its description.
1033− #[serde(default)]
1034− pub reactions: Vec<ReactionGroup>,
1035−}
1036981
1037982 #[derive(Clone, Debug, Serialize, Deserialize)]
1038983 pub struct PullDetail {
1039984 pub pull: Pull,
1040− /// The issue it is for, if any.
1041− pub issue: Option<Issue>,
985+ /// The work item it is for, if any.
986+ pub item: Option<WorkItem>,
1042987 pub comments: Vec<Comment>,
1043988 /// How people (and g1t) reacted to its description.
1044989 #[serde(default)]
1045990 pub reactions: Vec<ReactionGroup>,
1046991 /// The latest record against its head: the merge queue taking it out,
1047− /// or (from before checks were workflows) a run of its issue's commands.
992+ /// or (from before checks were workflows) a run of its work item's commands.
1048993 pub checks: Option<CheckRun>,
1049994 /// Other pull requests in progress that change the same files.
1050995 #[serde(default)]
12291174 pub viewer: Viewer,
12301175 }
12311176
1232−/// The heading an issue's plain-words description of done goes under.
1177+/// The heading a work item's plain-words description of done goes under.
12331178 pub const DEFINITION_OF_DONE: &str = "## Definition of done";
12341179
1235−/// An issue's body with `items` added as a bulleted "Definition of done"
1180+/// A work item's body with `items` added as a bulleted "Definition of done"
12361181 /// section, for the agent and reviewers to read. Nothing is added when
12371182 /// `items` is empty, or when the body already has the section, so folding
12381183 /// the same items twice changes nothing.
13851330 pub source: Option<String>,
13861331 }
13871332
1388−/// A step on the way from an assigned issue to a pull request that is ready
1333+/// A step on the way from an assigned work item to a pull request that is ready
13891334 /// to merge. g1t takes each one without being asked.
13901335 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
13911336 #[serde(rename_all = "snake_case")]
14751420 /// (its CODEOWNERS file) have approved, as many as each section asks.
14761421 /// Only people count, and `g1t` only where the file names `@g1t`.
14771422 pub require_code_owner_review: bool,
1478− /// Whether issues can be opened in it: null for the default, which is
1479− /// on for a repository and off for a fork of one. Existing issues stay
1480− /// readable either way.
1481− pub has_issues: Option<bool>,
1482− /// Whether issues can be opened in it now: `has_issues`, or its
1483− /// default. Filled in by the service; `update_settings` ignores it.
1484− pub issues_enabled: bool,
1423+ /// Whether work items can be filed from it (with it as their origin):
1424+ /// null for the default, which is on for a repository and off for a
1425+ /// fork of one. Work items filed from it before stay readable either
1426+ /// way, and it can always be linked as a target.
1427+ pub has_work_items: Option<bool>,
1428+ /// Whether work items can be filed from it now: `has_work_items`, or
1429+ /// its default. Filled in by the service; `update_settings` ignores it.
1430+ pub work_items_enabled: bool,
14851431 /// Which merge methods a pull request may use (at least one). Rules
14861432 /// can narrow them for a branch (`allowed_merge_methods`).
14871433 #[serde(default = "yes")]
15021448 pub squash_commit_message: CommitMessageDefault,
15031449 /// For `Custom`: a template whose `{title}`, `{number}`, `{body}`,
15041450 /// `{branch}`, `{head_repo}`, `{base}`, `{author}`, `{commits}`,
1505− /// `{issues}` and `{co_authors}` are filled in.
1451+ /// `{items}` and `{co_authors}` are filled in.
15061452 #[serde(default)]
15071453 pub merge_commit_template: Option<String>,
15081454 #[serde(default)]
15261472 /// comments stay readable either way.
15271473 #[serde(default = "yes")]
15281474 pub allow_commit_comments: bool,
1529− /// Close the issues a pull request names (its issue, and `Fixes #12`
1530− /// in its title or description) when it merges into the default
1531− /// branch.
1475+ /// Mark the work items a pull request names (its item, and `Fixes #12`
1476+ /// in its title or description) landed in this repository when it
1477+ /// merges into the default branch, closing those with nothing else
1478+ /// left to land; and the same for commits pushed there.
15321479 #[serde(default = "yes")]
1533− pub auto_close_issues: bool,
1480+ pub auto_close_items: bool,
15341481 /// Username of the member who last changed the settings, if anyone has.
15351482 pub updated_by: Option<String>,
15361483 /// RFC 3339.
15481495 allow_ignoring_checks: true,
15491496 merge_queue: false,
15501497 require_code_owner_review: false,
1551− has_issues: None,
1552− issues_enabled: true,
1498+ has_work_items: None,
1499+ work_items_enabled: true,
15531500 allow_merge_commit: true,
15541501 allow_squash_merge: true,
15551502 allow_rebase_merge: true,
15641511 delete_branch_on_merge: false,
15651512 require_web_commit_sign_off: false,
15661513 allow_commit_comments: true,
1567− auto_close_issues: true,
1514+ auto_close_items: true,
15681515 updated_by: None,
15691516 updated_at: None,
15701517 }
15721519 }
15731520
15741521 impl RepoSettings {
1575− /// Whether issues can be opened: as `has_issues` says, else on unless
1576− /// the repository is a fork (`is_fork`).
1577− pub fn issues_on(&self, is_fork: bool) -> bool {
1578− self.has_issues.unwrap_or(!is_fork)
1522+ /// Whether work items can be filed from it: as `has_work_items` says,
1523+ /// else on unless the repository is a fork (`is_fork`).
1524+ pub fn work_items_on(&self, is_fork: bool) -> bool {
1525+ self.has_work_items.unwrap_or(!is_fork)
15791526 }
15801527
15811528 /// Whether the repository lets pull requests merge by `method`.
16381585 pub page: Option<u32>,
16391586 }
16401587
1641−/// `closed_by_commit`: the issues of a repository that a commit pushed to
1642−/// its default branch closed by naming them in its message, for anyone who
1643−/// may read the repository. Returns `Outcome<Vec<ClosedIssue>>`.
1588+/// `closed_by_commit`: the work items a commit pushed to a repository's
1589+/// default branch resolved by naming them in its message, for anyone who
1590+/// may read the repository. Returns `Outcome<Vec<ClosedItem>>`.
16441591 #[derive(Debug, Serialize, Deserialize)]
16451592 pub struct ClosedByCommitArgs {
16461593 pub viewer: Viewer,
16481595 pub commit: String,
16491596 }
16501597
1651−/// An issue a commit closed.
1598+/// A work item a commit resolved: it landed in the commit's repository,
1599+/// and closed once nothing else was left to land.
16521600 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
1653−pub struct ClosedIssue {
1601+pub struct ClosedItem {
1602+ pub workspace: String,
16541603 pub number: u32,
16551604 pub title: String,
1605+ /// Whether the work item is closed now.
1606+ #[serde(default)]
1607+ pub closed: bool,
16561608 }
16571609
16581610 /// `add_commit_comment`: needs Read (refused where the repository turned
17171669 pub struct ManagedPullsArgs {
17181670 #[serde(default)]
17191671 pub repo_id: Option<String>,
1720−}
1721−
1722−/// `open_issue`. Returns `Outcome<Issue>`.
1723−#[derive(Debug, Serialize, Deserialize)]
1724−pub struct OpenIssueArgs {
1725− pub actor: User,
1726− pub repo: RepoPath,
1727− pub title: String,
1728− #[serde(default)]
1729− pub body: String,
1730− #[serde(default)]
1731− pub labels: Vec<String>,
1732− /// Deprecated: commands, added to the body under "Definition of done".
1733− /// Checks are the workflows the branch's protection requires.
1734− #[serde(default)]
1735− pub checks: Vec<String>,
1736− /// The number of the milestone to put it in. Needs the Triage role.
1737− #[serde(default)]
1738− pub milestone: Option<u32>,
17391672 }
17401673
1741−/// `list_issues`, newest first. Returns `Outcome<Vec<Issue>>`.
1742−#[derive(Debug, Serialize, Deserialize)]
1743−pub struct ListIssuesArgs {
1744− pub repo: RepoPath,
1745− pub viewer: Viewer,
1746− #[serde(default)]
1747− pub state: Option<State>,
1748− /// Only issues carrying this label.
1749− #[serde(default)]
1750− pub label: Option<String>,
1751− /// Only issues in the milestone of this number.
1752− #[serde(default)]
1753− pub milestone: Option<u32>,
1754−}
1755−
17561674 /// `list_pulls`, newest first. Returns `Outcome<Vec<Pull>>`.
17571675 #[derive(Debug, Serialize, Deserialize)]
17581676 pub struct ListPullsArgs {
17991717 pub closed: Vec<Pull>,
18001718 }
18011719
1802−/// `get_issue` (`Outcome<IssueDetail>`), `get_pull` (`Outcome<PullDetail>`),
1803−/// `read_session` (`Outcome<Vec<SessionEntry>>`), `list_labels`
1804−/// (`Outcome<Vec<String>>`) and `counts` (`Outcome<Counts>`). The last two
1805−/// ignore `number`.
1720+/// `get_pull` (`Outcome<PullDetail>`), `read_session`
1721+/// (`Outcome<Vec<SessionEntry>>`) and `counts` (`Outcome<Counts>`, which
1722+/// ignores `number`).
18061723 #[derive(Debug, Serialize, Deserialize)]
18071724 #[serde(rename_all = "camelCase")]
18081725 pub struct ViewArgs {
18151732 pub after_seq: u32,
18161733 }
18171734
1818−/// How many issues and pull requests are open on a repository.
1735+/// How many work items linked to a repository, and how many of its pull
1736+/// requests, are open.
18191737 #[derive(Debug, Serialize, Deserialize)]
18201738 pub struct Counts {
1821− pub issues: u32,
1739+ pub items: u32,
18221740 pub pulls: u32,
18231741 }
18241742
1825−/// `update_issue`: changes whichever fields are given. The author or a
1826−/// member of the workspace may. Returns `Outcome<Issue>`.
1743+/// `add_comment`, on a thread: a pull request or a work item. Every method
1744+/// that takes a thread reads it the same way: with `repo`, the pull
1745+/// request `number` of that repository; without, the work item `number`
1746+/// of `workspace`. On a pull request a comment may name a line of the
1747+/// change, and may carry a verdict; nobody can give a verdict on their own
1748+/// pull request. Returns `Outcome<Comment>`.
18271749 #[derive(Debug, Serialize, Deserialize)]
1828−pub struct UpdateIssueArgs {
1750+pub struct AddCommentArgs {
18291751 pub actor: User,
1830− pub repo: RepoPath,
1831− pub number: u32,
18321752 #[serde(default)]
1833− pub title: Option<String>,
1834− #[serde(default)]
1835− pub body: Option<String>,
1836− #[serde(default)]
1837− pub labels: Option<Vec<String>>,
1838− /// Usernames of the people it is assigned to; replaces the whole set.
1839− #[serde(default)]
1840− pub assignees: Option<Vec<String>>,
1841− /// The number of the milestone to put it in; 0 takes it out of its
1842− /// milestone. Needs the Triage role.
1753+ pub repo: Option<RepoPath>,
18431754 #[serde(default)]
1844− pub milestone: Option<u32>,
1845− /// The numbers of the issues of the same repository that have to land
1846− /// first; replaces the whole set. Refused when one is not an issue, is
1847− /// the issue itself, or would make a cycle.
1848− #[serde(default)]
1849− pub blocked_by: Option<Vec<u32>>,
1850−}
1851−
1852−/// `close_issue` and `reopen_issue`. Each returns `Outcome<Issue>`.
1853−/// `close_issue` takes [`CloseIssueArgs`], which these are a part of.
1854−#[derive(Debug, Serialize, Deserialize)]
1855−pub struct IssueActionArgs {
1856− pub actor: User,
1857− pub repo: RepoPath,
1755+ pub workspace: String,
18581756 pub number: u32,
1859− /// For `close_issue`; `completed` if left out.
1860− #[serde(default)]
1861− pub reason: Option<IssueReason>,
1862−}
1863−
1864−/// `close_issue`: an [`IssueActionArgs`], and the commit that did it, if
1865−/// one did. With `commit`, a commit on the default branch whose message
1866−/// names the issue after a closing keyword (`Fixes #12`), it closes as the
1867−/// push of that commit would have: completed, in that commit, by the
1868−/// caller; a commit that is not on the default branch, or does not name
1869−/// the issue, is refused. Returns `Outcome<Issue>`.
1870−#[derive(Debug, Serialize, Deserialize)]
1871−pub struct CloseIssueArgs {
1872− #[serde(flatten)]
1873− pub issue: IssueActionArgs,
1874− #[serde(default, skip_serializing_if = "Option::is_none")]
1875− pub commit: Option<String>,
1876−}
1877−
1878−/// `add_comment`, on an issue or a pull request. On a pull request it may
1879−/// name a line of the change, and may carry a verdict; nobody can give a
1880−/// verdict on their own pull request. Returns `Outcome<Comment>`.
1881−#[derive(Debug, Serialize, Deserialize)]
1882−pub struct AddCommentArgs {
1883− pub actor: User,
1884− pub repo: RepoPath,
1885− pub number: u32,
18861757 /// May be empty when approving.
18871758 #[serde(default)]
18881759 pub body: String,
18941765 pub verdict: Option<Verdict>,
18951766 }
18961767
1897−/// `edit_comment` and `delete_comment`: change the text of a comment on an
1898−/// issue or a pull request, or delete it. Its author may, and so may anyone
1899−/// with the Maintain role or higher ([`may_change_comment`]). Notes of what
1768+/// `edit_comment` and `delete_comment`: change the text of a comment, or
1769+/// delete it: one on a pull request of `repo`, or without `repo`, one on a
1770+/// work item of `workspace`. Its author may, and so may anyone with the
1771+/// Maintain role or higher ([`may_change_comment`]). Notes of what
19001772 /// happened (`CommentKind::Event`) cannot be changed, and a review that
19011773 /// gave a verdict cannot be deleted, only edited. `edit_comment` returns
19021774 /// `Outcome<Comment>`; `delete_comment` returns `Outcome<()>`.
19041776 #[serde(rename_all = "camelCase")]
19051777 pub struct CommentActionArgs {
19061778 pub actor: User,
1907− pub repo: RepoPath,
1779+ #[serde(default)]
1780+ pub repo: Option<RepoPath>,
1781+ #[serde(default)]
1782+ pub workspace: String,
19081783 pub comment_id: String,
19091784 /// For `edit_comment`: the new text, in Markdown.
19101785 #[serde(default)]
19431818 /// `open_pull`. Without `branch`, forks the repo and returns a draft pull
19441819 /// request to push to. With it, opens a pull request, ready for review,
19451820 /// for a branch already pushed to the repo, or to `head_repo`, a fork of
1946−/// it. Returns `Outcome<Pull>`.
1821+/// it. Its number is the next in its workspace's sequence. Opening one for
1822+/// a work item links the repository to the item (`touched`) unless it is
1823+/// linked already. Returns `Outcome<Pull>`.
19471824 #[derive(Debug, Serialize, Deserialize)]
19481825 pub struct OpenPullArgs {
19491826 pub actor: User,
19501827 pub repo: RepoPath,
1951− /// The issue this is for.
1828+ /// The work item this is for, by number in the repository's workspace.
19521829 #[serde(default)]
1953− pub issue: Option<u32>,
1954− /// Defaults to the issue's title; required without an issue.
1830+ pub item: Option<u32>,
1831+ /// Defaults to the work item's title; required without one.
19551832 #[serde(default)]
19561833 pub title: String,
19571834 /// What changed and why. Usually set later, when a draft is marked ready.
21061983 /// For `ready_pull`: what changed and why.
21071984 #[serde(default)]
21081985 pub summary: String,
2109− /// For `merge_pull`: leave the issue open and the other pull requests
2110− /// for it untouched, because this one is only part of the work.
1986+ /// For `merge_pull`: leave the work item open and the other pull
1987+ /// requests for it untouched, because this one is only part of the
1988+ /// work: it is recorded as part of it, as `Part of #N` would be.
21111989 #[serde(default)]
2112− pub keep_issue_open: bool,
1990+ pub keep_item_open: bool,
21131991 /// For `merge_pull`: merge although required checks have not passed,
21141992 /// where the repository lets members bypass them.
21151993 #[serde(default)]
22132091 #[derive(Debug, Serialize, Deserialize)]
22142092 pub struct ActivePull {
22152093 pub pull: Pull,
2216− pub issue: Option<Issue>,
2094+ pub item: Option<WorkItem>,
22172095 /// Where it stands, for one g1t is seeing through.
22182096 #[serde(default)]
22192097 pub lifecycle: Option<Lifecycle>,
22202098 }
22212099
22222100 /// `list_active_pulls`: drafts and open pull requests the viewer started,
2223−/// most recently active first. Returns `Vec<ActivePull>`. Also
2224−/// `list_assigned_issues`: open issues assigned to the viewer, most
2225−/// recently changed first. Returns `Vec<Issue>`.
2101+/// most recently active first. Returns `Vec<ActivePull>`.
22262102 #[derive(Debug, Serialize, Deserialize)]
22272103 pub struct ViewerArgs {
22282104 pub viewer: Viewer,
25112387
25122388 // --- A person's work -------------------------------------------------------
25132389
2514−/// Issues or pull requests, on a person's profile.
2390+/// Work items or pull requests, on a person's profile.
25152391 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
25162392 #[serde(rename_all = "lowercase")]
25172393 pub enum AuthoredKind {
2518− Issue,
2394+ Item,
25192395 Pull,
25202396 }
25212397
25452421 /// The most items one `by_author` page holds.
25462422 pub const AUTHORED_PAGE: u32 = 25;
25472423
2548−/// `by_author`: the issues and pull requests a person opened, only on
2549−/// repositories `viewer` may read, so a private title never reaches anyone
2550−/// who could not open it. Returns `Outcome<Authored>`; not found for an
2424+/// `by_author`: the work items and pull requests a person opened, only
2425+/// those `viewer` may read, so a private title never reaches anyone who
2426+/// could not open it. Returns `Outcome<Authored>`; not found for an
25512427 /// account that does not exist.
25522428 #[derive(Debug, Serialize, Deserialize)]
25532429 pub struct ByAuthorArgs {
25572433 pub kind: Option<AuthoredKind>,
25582434 #[serde(default)]
25592435 pub state: Option<AuthoredState>,
2560− /// Only work on this repository: `namespace/name`.
2436+ /// Only work on this repository (`namespace/name`): its pull requests,
2437+ /// and the work items filed from it.
25612438 #[serde(default)]
25622439 pub repo: Option<String>,
25632440 #[serde(default)]
25702447 pub limit: Option<u32>,
25712448 }
25722449
2573−/// One issue or pull request a person opened.
2450+/// One work item or pull request a person opened.
25742451 #[derive(Clone, Debug, Serialize, Deserialize)]
25752452 #[serde(rename_all = "camelCase")]
25762453 pub struct AuthoredItem {
25772454 pub kind: AuthoredKind,
2578− pub repo: RepoPath,
2455+ /// The workspace it is numbered in.
2456+ pub workspace: String,
2457+ /// A pull request's repository, or the repository a work item was
2458+ /// filed from, if it was.
2459+ pub repo: Option<RepoPath>,
25792460 pub number: u32,
25802461 pub title: String,
25812462 /// Open or closed; a merged pull request is closed.
25822463 pub state: State,
25832464 /// A pull request's own status.
25842465 pub status: Option<PullStatus>,
2585− /// Why an issue was closed.
2586− pub reason: Option<IssueReason>,
2466+ /// Why a work item was closed.
2467+ pub reason: Option<CloseReason>,
25872468 pub draft: bool,
25882469 pub merged: bool,
25892470 /// RFC 3339.
26012482 pub pulls_merged: u32,
26022483 pub pulls_open: u32,
26032484 pub pulls: u32,
2604− pub issues: u32,
2605− pub issues_open: u32,
2485+ pub items: u32,
2486+ pub items_open: u32,
26062487 }
26072488
26082489 /// A repository a person has opened work on, with how much.
26292510 pub const CONTRIBUTION_DAYS: u64 = 365;
26302511
26312512 /// `contributions`: what a person did each day of the last year (commits
2632−/// they pushed to a default branch, issues and pull requests opened,
2513+/// they pushed to a default branch, work items and pull requests opened,
26332514 /// reviews given), only on repositories `viewer` may read, for the calendar on their profile. Returns
26342515 /// `Outcome<Contributions>`; not found for an account that does not exist.
26352516 #[derive(Debug, Serialize, Deserialize)]
27142595 #[test]
27152596 fn a_pull_request_merges_into_its_base_or_the_default_branch() {
27162597 let mut pull: Pull = serde_json::from_value(serde_json::json!({
2717− "id": "pr_1", "repoId": "rep_1", "number": 14, "issue": null, "title": "Fix it", "body": null,
2598+ "id": "pr_1", "repoId": "rep_1", "number": 14, "item": null, "title": "Fix it", "body": null,
27182599 "agent": "agent", "runtime": "external", "status": "open", "fork": null, "forkRepoId": null,
27192600 "branch": "fix", "headCommit": null, "mergeBase": null, "mergedBy": null, "mergedAt": null,
27202601 "supersededBy": null, "checkStatus": null, "author": { "id": "x", "username": "x" },
27852666
27862667 fn pull(author: User, requested_by: Option<User>) -> Pull {
27872668 let mut pull: Pull = serde_json::from_value(serde_json::json!({
2788− "id": "pr_1", "repoId": "rep_1", "number": 14, "issue": 12, "title": "Fix it", "body": null,
2669+ "id": "pr_1", "repoId": "rep_1", "number": 14, "item": 12, "title": "Fix it", "body": null,
27892670 "agent": "g1t", "runtime": "hosted", "status": "open", "fork": null, "forkRepoId": null,
27902671 "branch": null, "headCommit": null, "mergeBase": null, "mergedBy": null, "mergedAt": null,
27912672 "supersededBy": null, "checkStatus": null,
+219−135
Loading the diff…
+499−0
Loading the diff…
+437−0
Loading the diff…
+5−5
Loading the diff…
+7−6
Loading the diff…
+82−33
Loading the diff…
+100−66
Loading the diff…
+2−2
Loading the diff…
+12−9
Loading the diff…
+329−193
Loading the diff…

Comments

Sign in to comment.