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

Rename the issues scopes to work:read and work:write

Tokens' scopes follow work items: the issues resource is now work (shown as Work), and the operation table names the work item operations, with comments on pull requests needing pull_requests:write. Identity rewrites every stored scope list and run token's operations so tokens keep the access they had.

G1T-Session:
ses_01m566353xebrscb7pf471bnn1
syntaqxcommitted Parentda5dec5Browse files
9 files+238−1790/9 viewed
+33−29
5656 /// and pushing that to a branch of its own. Not an agent.
5757 Bump,
5858 /// A session g1t started on an agent's harness (Claude Managed Agents)
59− /// for the person who asked, on one issue or pull request. It works
59+ /// for the person who asked, on one work item or pull request. It works
6060 /// through the API, MCP and the CLI with what [`SESSION_SCOPES`] allow,
6161 /// in its repository only, and pushes to that repository and its pull
6262 /// requests' working copies. What it writes (comments, reviews, closes,
63− /// the issues and pull requests it opens) is g1t's, for that person;
63+ /// the work items and pull requests it opens) is g1t's, for that person;
6464 /// what it may do is that person's role there, never beyond
6565 /// [`AGENT_CEILING`].
6666 Session,
289289 "list_check_names",
290290 "get_merge_queue",
291291 "get_pull_request_merge_options",
292− "list_issues",
293− "get_issue",
292+ "list_work_items",
293+ "list_repo_work_items",
294+ "get_work_item",
295+ "list_work_item_links",
296+ "resolve_ref",
294297 "list_labels",
295− "list_issue_labels",
298+ "list_work_item_labels",
299+ "list_work_item_decisions",
296300 "list_milestones",
297301 "get_milestone",
298302 "list_pull_requests",
398402 "disable_pull_request_auto_merge",
399403 "delete_pull_request_head_branch",
400404 "restore_pull_request_head_branch",
401− "import_issue",
405+ "import_work_items",
402406 "list_integrations",
403407 "connect_integration",
404408 "update_integration",
537541 }
538542
539543 /// The scopes of a session's credential ([`RunCredentialKind::Session`]):
540−/// what working on an issue needs, named as an access token's are.
544+/// what working on a work item needs, named as an access token's are.
541545 pub const SESSION_SCOPES: [&str; 8] = [
542546 "repo:read",
543547 "code:write",
544− "issues:write",
548+ "work:write",
545549 "pull_requests:write",
546550 "checks:read",
547551 "workflows:read",
641645
642646 /// Whether `user`, resolved from a run credential with `scope`, may use
643647 /// `operation`. `repo` is the repository the call names, if any, and
644−/// `needs_repo` whether the operation is about one; `number` the issue or
648+/// `needs_repo` whether the operation is about one; `number` the work item or
645649 /// pull request it names.
646650 pub fn decide_operation(
647651 user: &User,
956960 _ => vec![],
957961 },
958962 CredentialUse::Tools => match kind {
959− K::Implement | K::Revise | K::Answer => vec!["get_issue", "get_pull_request", "create_issue", "add_comment", "remember"],
960− K::Review => vec!["get_issue", "get_pull_request", "add_comment", "review_pull_request"],
961− K::Plan => vec!["get_issue", "get_pull_request", "create_issue"],
962− K::Update => vec!["get_issue", "get_pull_request"],
963+ K::Implement | K::Revise | K::Answer => vec!["get_work_item", "get_pull_request", "open_work_item", "add_comment", "remember"],
964+ K::Review => vec!["get_work_item", "get_pull_request", "add_comment", "review_pull_request"],
965+ K::Plan => vec!["get_work_item", "get_pull_request", "open_work_item"],
966+ K::Update => vec!["get_work_item", "get_pull_request"],
963967 _ => vec![],
964968 },
965969 };
10421046 /// may.
10431047 #[test]
10441048 fn a_stored_token_uses_only_what_it_lists() {
1045− assert!(op(K::Implement, Tools, "create_issue").allowed);
1049+ assert!(op(K::Implement, Tools, "open_work_item").allowed);
10461050 assert!(op(K::Review, Tools, "review_pull_request").allowed);
1047− assert!(!op(K::Review, Tools, "create_issue").allowed);
1051+ assert!(!op(K::Review, Tools, "open_work_item").allowed);
10481052 assert!(!op(K::Plan, Tools, "add_comment").allowed);
1049− assert!(!op(K::Checks, Tools, "get_issue").allowed);
1053+ assert!(!op(K::Checks, Tools, "get_work_item").allowed);
10501054 for operation in ["record_session", "merge_pull_request", "update_repo_settings", "create_webhook", "set_actions_secret"] {
10511055 for kind in [K::Implement, K::Revise, K::Answer, K::Review, K::Plan, K::Checks] {
10521056 assert!(!op(kind, Tools, operation).allowed, "{operation} by a {} run's tools", kind.as_str());
10601064 assert!(op(K::Implement, Runner, "mark_pull_request_ready").allowed);
10611065 assert!(op(K::Revise, Runner, "record_session").allowed);
10621066 assert!(!op(K::Revise, Runner, "mark_pull_request_ready").allowed);
1063− assert!(!op(K::Implement, Runner, "create_issue").allowed);
1067+ assert!(!op(K::Implement, Runner, "open_work_item").allowed);
10641068 assert!(!op(K::Review, Runner, "record_session").allowed);
1065− assert!(!op(K::Checks, Runner, "get_issue").allowed);
1069+ assert!(!op(K::Checks, Runner, "get_work_item").allowed);
10661070 }
10671071
10681072 #[test]
10981102 let decision = decide_operation(
10991103 &user,
11001104 &scope,
1101− "create_issue",
1105+ "open_work_item",
11021106 Some(&path("acme", "other")),
11031107 true,
11041108 None,
11051109 );
11061110 assert!(!decision.allowed);
11071111 assert_eq!(decision.rule, "scope:repository");
1108− let decision = decide_operation(&user, &scope, "create_issue", None, true, None);
1112+ let decision = decide_operation(&user, &scope, "open_work_item", None, true, None);
11091113 assert_eq!(decision.rule, "scope:repository");
11101114 // The repository's name is matched without regard to case.
11111115 let decision = decide_operation(
11121116 &user,
11131117 &scope,
1114− "create_issue",
1118+ "open_work_item",
11151119 Some(&path("Acme", "Rocket")),
11161120 true,
11171121 None,
11281132 let decision = decide_operation(
11291133 &user,
11301134 &scope,
1131− "get_issue",
1135+ "get_work_item",
11321136 Some(&path("acme", "rocket")),
11331137 true,
11341138 Some(1),
11901194 assert_eq!(permission(Some(&agent_user), rocket), Some(RepoRole::Write));
11911195 assert_eq!(permission(Some(&agent_user), other), None);
11921196 assert_eq!(permission(Some(&agent_user), elsewhere), None);
1193− let decision = decide_operation(&agent_user, &tools, "get_issue", Some(&path("acme", "rocket")), true, Some(1));
1197+ let decision = decide_operation(&agent_user, &tools, "get_work_item", Some(&path("acme", "rocket")), true, Some(1));
11941198 assert!(decision.allowed, "{}", decision.reason.unwrap_or_default());
11951199 // The person, downstream of a runner's credential, carries the same.
11961200 let person = as_person(&agent_user).expect("a runner acts as the person");
12391243 // A tools token made before run credentials never reaches git.
12401244 let old = AgentScope {
12411245 repo: upstream.clone(),
1242− operations: vec!["get_issue".to_owned()],
1246+ operations: vec!["get_work_item".to_owned()],
12431247 run: None,
12441248 };
12451249 assert_eq!(decide_git(&old, &upstream, false).rule, "git:not-a-run");
13021306 #[test]
13031307 fn scopes_without_a_run_still_parse() {
13041308 let old: AgentScope = serde_json::from_str(
1305− r#"{"repo":{"namespace":"acme","name":"rocket"},"operations":["get_issue"]}"#,
1309+ r#"{"repo":{"namespace":"acme","name":"rocket"},"operations":["get_work_item"]}"#,
13061310 )
13071311 .unwrap();
13081312 assert!(old.run.is_none());
13381342 }
13391343 }
13401344
1341− /// A session works on its issue through the API and MCP with what its
1345+ /// A session works on its work item through the API and MCP with what its
13421346 /// scopes allow, in its repository, and never with what no run may.
13431347 #[test]
13441348 fn a_session_does_what_its_scopes_allow_in_its_repository() {
13491353 for operation in [
13501354 "whoami",
13511355 "get_work",
1352− "claim_issue",
1356+ "claim_work_item",
13531357 "add_comment",
13541358 // A screenshot of its preview, for a pull request or a comment.
13551359 "upload_attachment",
1356− "close_issue",
1360+ "close_work_item",
13571361 "create_pull_request",
13581362 "review_pull_request",
13591363 "mark_pull_request_ready",
13761380 }
13771381 use crate::scopes::Scope;
13781382 let scopes = run_scopes(K::Session, Runner);
1379− assert!(scopes.contains(&Scope::CodeWrite) && scopes.contains(&Scope::IssuesWrite));
1383+ assert!(scopes.contains(&Scope::CodeWrite) && scopes.contains(&Scope::WorkWrite));
13801384 assert!(scopes.iter().all(|scope| !scope.dangerous()));
13811385 }
13821386
+1−1
243243 let token = access("dk_1", "CI", "acme/web", true);
244244 assert_eq!(token.deploy_key.as_deref(), Some("dk_1"));
245245 assert!(!token.admin);
246− assert!(!token.allows(Scope::IssuesWrite));
246+ assert!(!token.allows(Scope::WorkWrite));
247247 assert!(!token.allows(Scope::AccessAdmin));
248248 }
249249
+106−93
88 //! (see [`crate::access`]) and the token's scopes.
99 //!
1010 //! Each scope is a resource and a level, written `resource:level`, such as
11−//! `issues:write`. A higher level of a resource includes the lower ones:
11+//! `work:write`. A higher level of a resource includes the lower ones:
1212 //! `repo:admin` includes `repo:write`, which includes `repo:create`, which
1313 //! includes `repo:read`.
1414 //!
3535 Code,
3636 Security,
3737 Packages,
38− Issues,
38+ Work,
3939 PullRequests,
4040 Agents,
4141 Workflows,
6464 Resource::Code,
6565 Resource::Security,
6666 Resource::Packages,
67− Resource::Issues,
67+ Resource::Work,
6868 Resource::PullRequests,
6969 Resource::Agents,
7070 Resource::Workflows,
9797 Resource::Code => "code",
9898 Resource::Security => "security",
9999 Resource::Packages => "packages",
100− Resource::Issues => "issues",
100+ Resource::Work => "work",
101101 Resource::PullRequests => "pull_requests",
102102 Resource::Agents => "agents",
103103 Resource::Workflows => "workflows",
127127 Resource::Code => "Code",
128128 Resource::Security => "Security",
129129 Resource::Packages => "Packages",
130− Resource::Issues => "Issues",
130+ Resource::Work => "Work",
131131 Resource::PullRequests => "Pull requests",
132132 Resource::Agents => "g1t agents",
133133 Resource::Workflows => "Workflows",
212212 PackagesRead,
213213 PackagesWrite,
214214 PackagesDelete,
215− IssuesRead,
216− IssuesWrite,
215+ WorkRead,
216+ WorkWrite,
217217 PullRequestsRead,
218218 PullRequestsWrite,
219219 /// Retired with g1t's own agents ([`Resource::retired`]): parses, so
273273 Scope::PackagesRead,
274274 Scope::PackagesWrite,
275275 Scope::PackagesDelete,
276− Scope::IssuesRead,
277− Scope::IssuesWrite,
276+ Scope::WorkRead,
277+ Scope::WorkWrite,
278278 Scope::PullRequestsRead,
279279 Scope::PullRequestsWrite,
280280 Scope::AgentsRun,
327327 Scope::PackagesRead => "packages:read",
328328 Scope::PackagesWrite => "packages:write",
329329 Scope::PackagesDelete => "packages:delete",
330− Scope::IssuesRead => "issues:read",
331− Scope::IssuesWrite => "issues:write",
330+ Scope::WorkRead => "work:read",
331+ Scope::WorkWrite => "work:write",
332332 Scope::PullRequestsRead => "pull_requests:read",
333333 Scope::PullRequestsWrite => "pull_requests:write",
334334 Scope::AgentsRun => "agents:run",
408408 /// What it lets a token do, in plain words.
409409 pub fn describe(self) -> &'static str {
410410 match self {
411− Scope::RepoRead => "See repositories, their settings, labels, timelines, releases, languages, contributors and security alerts, and search",
411+ Scope::RepoRead => "See repositories, their settings, timelines, releases, languages, contributors and security alerts, and search",
412412 Scope::RepoCreate => "Create repositories, and nothing that changes or deletes one",
413413 Scope::RepoWrite => "Create repositories, create, rename and delete branches, change how pull requests merge and publish releases",
414414 Scope::RepoAdmin => "Rename, archive, transfer, delete or change who can see a repository, change its rulesets, and dismiss security alerts",
419419 Scope::PackagesRead => "Pull container images and install private packages",
420420 Scope::PackagesWrite => "Push container images and publish packages",
421421 Scope::PackagesDelete => "Delete and restore packages and their versions",
422− Scope::IssuesRead => "Read issues and comments",
423− Scope::IssuesWrite => "Open, edit, close and comment on issues",
422+ Scope::WorkRead => "Read work items, their comments, labels, milestones and claims",
423+ Scope::WorkWrite => "Open, edit, link, close and comment on work items, claim them, and manage labels and milestones",
424424 Scope::PullRequestsRead => "Read pull requests, their changes, sessions and merge queues",
425− Scope::PullRequestsWrite => "Open, review, close and merge pull requests",
425+ Scope::PullRequestsWrite => "Open, review, comment on, close and merge pull requests",
426426 Scope::AgentsRun => "Retired: put g1t's own agents to work. Grants nothing now",
427427 Scope::WorkflowsRead => "Read workflows, runs and logs",
428428 Scope::WorkflowsWrite => "Run, cancel, rerun and turn workflows on or off",
556556 // --- Permissions --------------------------------------------------------------
557557 //
558558 // A token's permissions are its scopes read per resource: each resource
559−// at none or one level (`{"issues": "write", "repo": "read"}`). A level
559+// at none or one level (`{"work": "write", "repo": "read"}`). A level
560560 // includes the ones below it, so the highest scope held of each resource
561561 // says everything; that is what a token stores. Personal tokens and a
562562 // workspace's own tokens are made, shown and checked this way alike.
588588 .collect()
589589 }
590590
591−/// Permissions as asked for (`{"issues": "write"}`, `none` or empty left
591+/// Permissions as asked for (`{"work": "write"}`, `none` or empty left
592592 /// out) into the scopes a token stores, or why they cannot be. `personal`
593593 /// is whether the token is a person's: only theirs may hold account ones.
594594 pub fn resolve_permissions(asked: &std::collections::BTreeMap<String, String>, personal: bool) -> Result<Vec<Scope>, String> {
670670 scopes.extend([
671671 Scope::RepoCreate,
672672 Scope::CodeWrite,
673− Scope::IssuesWrite,
673+ Scope::WorkWrite,
674674 Scope::PullRequestsWrite,
675675 Scope::NotificationsWrite,
676676 ]);
10301030 ("get_repo", Scope::RepoRead),
10311031 ("list_forks", Scope::RepoRead),
10321032 // Comments on commits are read with the repository, and written as
1033− // comments on issues are.
1033+ // comments on work items are.
10341034 ("list_repo_commit_comments", Scope::RepoRead),
10351035 ("list_commit_comments", Scope::RepoRead),
10361036 // A repository's files, as clone and fetch need.
10601060 ("create_release", Scope::RepoWrite),
10611061 ("update_release", Scope::RepoWrite),
10621062 ("delete_release", Scope::RepoWrite),
1063− ("list_labels", Scope::RepoRead),
1064− ("list_milestones", Scope::RepoRead),
1065− ("get_milestone", Scope::RepoRead),
1066− ("create_label", Scope::IssuesWrite),
1067− ("update_label", Scope::IssuesWrite),
1068− ("delete_label", Scope::IssuesWrite),
1069− ("add_default_labels", Scope::IssuesWrite),
1070− ("create_milestone", Scope::IssuesWrite),
1071− ("update_milestone", Scope::IssuesWrite),
1072− ("delete_milestone", Scope::IssuesWrite),
1063+ // A workspace's labels and milestones, which its work items and pull
1064+ // requests carry.
1065+ ("list_labels", Scope::WorkRead),
1066+ ("list_milestones", Scope::WorkRead),
1067+ ("get_milestone", Scope::WorkRead),
1068+ ("create_label", Scope::WorkWrite),
1069+ ("update_label", Scope::WorkWrite),
1070+ ("delete_label", Scope::WorkWrite),
1071+ ("add_default_labels", Scope::WorkWrite),
1072+ ("create_milestone", Scope::WorkWrite),
1073+ ("update_milestone", Scope::WorkWrite),
1074+ ("delete_milestone", Scope::WorkWrite),
10731075 ("get_repo_settings", Scope::RepoRead),
10741076 ("list_check_names", Scope::RepoRead),
10751077 ("list_deleted_repos", Scope::RepoRead),
11501152 ("set_security_advisory_state", Scope::SecurityWrite),
11511153 ("report_vulnerability", Scope::SecurityWrite),
11521154 ("review_vulnerability_report", Scope::SecurityWrite),
1153− // Issues.
1154− ("list_issues", Scope::IssuesRead),
1155− ("get_issue", Scope::IssuesRead),
1156− ("create_issue", Scope::IssuesWrite),
1157− ("update_issue", Scope::IssuesWrite),
1158− ("list_issue_labels", Scope::IssuesRead),
1159− ("add_issue_labels", Scope::IssuesWrite),
1160− ("set_issue_labels", Scope::IssuesWrite),
1161− ("remove_issue_labels", Scope::IssuesWrite),
1162− ("close_issue", Scope::IssuesWrite),
1163− ("reopen_issue", Scope::IssuesWrite),
1164− ("add_comment", Scope::IssuesWrite),
1155+ // Work items: a workspace's tracked work, its comments and labels.
1156+ ("list_work_items", Scope::WorkRead),
1157+ ("list_repo_work_items", Scope::WorkRead),
1158+ ("get_work_item", Scope::WorkRead),
1159+ ("list_work_item_links", Scope::WorkRead),
1160+ ("resolve_ref", Scope::WorkRead),
1161+ ("open_work_item", Scope::WorkWrite),
1162+ ("update_work_item", Scope::WorkWrite),
1163+ ("close_work_item", Scope::WorkWrite),
1164+ ("reopen_work_item", Scope::WorkWrite),
1165+ ("link_work_item", Scope::WorkWrite),
1166+ ("unlink_work_item", Scope::WorkWrite),
1167+ ("list_work_item_labels", Scope::WorkRead),
1168+ ("add_work_item_labels", Scope::WorkWrite),
1169+ ("set_work_item_labels", Scope::WorkWrite),
1170+ ("remove_work_item_labels", Scope::WorkWrite),
1171+ ("list_work_item_decisions", Scope::WorkRead),
1172+ ("add_work_item_comment", Scope::WorkWrite),
1173+ ("edit_work_item_comment", Scope::WorkWrite),
1174+ ("delete_work_item_comment", Scope::WorkWrite),
1175+ ("react_to_work_item", Scope::WorkWrite),
11651176 // An image for a description or a comment: whoever may comment.
1166− ("upload_attachment", Scope::IssuesWrite),
1167− ("edit_comment", Scope::IssuesWrite),
1168− ("delete_comment", Scope::IssuesWrite),
1169− ("react", Scope::IssuesWrite),
1170− ("create_commit_comment", Scope::IssuesWrite),
1171− ("update_commit_comment", Scope::IssuesWrite),
1172− ("delete_commit_comment", Scope::IssuesWrite),
1177+ ("upload_attachment", Scope::WorkWrite),
1178+ ("create_commit_comment", Scope::WorkWrite),
1179+ ("update_commit_comment", Scope::WorkWrite),
1180+ ("delete_commit_comment", Scope::WorkWrite),
11731181 // Looking up a ticket the work refers to, through the workspace's
1174− // integrations, and bringing one in as an issue.
1175− ("get_context", Scope::IssuesRead),
1176− ("import_issue", Scope::IssuesWrite),
1177− // Coordinating work on issues: claims, and the context to pick one up.
1178− ("list_claims", Scope::IssuesRead),
1179− ("get_work", Scope::IssuesRead),
1180− ("claim_issue", Scope::IssuesWrite),
1181− ("release_claim", Scope::IssuesWrite),
1182− ("hand_off_issue", Scope::IssuesWrite),
1182+ // integrations, and bringing one in as a work item.
1183+ ("get_context", Scope::WorkRead),
1184+ ("import_work_items", Scope::WorkWrite),
1185+ // Coordinating work items: claims, and the context to pick one up.
1186+ ("list_claims", Scope::WorkRead),
1187+ ("get_work", Scope::WorkRead),
1188+ ("claim_work_item", Scope::WorkWrite),
1189+ ("release_claim", Scope::WorkWrite),
1190+ ("hand_off_work_item", Scope::WorkWrite),
11831191 // Pull requests.
11841192 ("list_pull_requests", Scope::PullRequestsRead),
11851193 ("get_pull_request", Scope::PullRequestsRead),
12041212 ("delete_pull_request_head_branch", Scope::PullRequestsWrite),
12051213 ("restore_pull_request_head_branch", Scope::PullRequestsWrite),
12061214 ("request_reviewers", Scope::PullRequestsWrite),
1215+ // Comments on pull requests, and reactions to them.
1216+ ("add_comment", Scope::PullRequestsWrite),
1217+ ("edit_comment", Scope::PullRequestsWrite),
1218+ ("delete_comment", Scope::PullRequestsWrite),
1219+ ("react", Scope::PullRequestsWrite),
12071220 ("remove_requested_reviewers", Scope::PullRequestsWrite),
12081221 // Harness sessions, which record how pull requests are made.
12091222 ("get_session", Scope::PullRequestsRead),
16221635 assert!(scope.as_str().starts_with(scope.resource().as_str()));
16231636 assert!(scope.includes(scope));
16241637 }
1625− assert_eq!(Scope::parse(" Issues:Write "), Some(Scope::IssuesWrite));
1626− assert_eq!(Scope::parse("issues"), None);
1638+ assert_eq!(Scope::parse(" Work:Write "), Some(Scope::WorkWrite));
1639+ assert_eq!(Scope::parse("work"), None);
16271640 }
16281641
16291642 #[test]
16331646 assert!(Scope::RepoWrite.includes(Scope::RepoCreate));
16341647 assert!(Scope::RepoCreate.includes(Scope::RepoRead));
16351648 assert!(!Scope::RepoCreate.includes(Scope::RepoWrite));
1636− assert!(Scope::IssuesWrite.includes(Scope::IssuesRead));
1637− assert!(!Scope::IssuesRead.includes(Scope::IssuesWrite));
1649+ assert!(Scope::WorkWrite.includes(Scope::WorkRead));
1650+ assert!(!Scope::WorkRead.includes(Scope::WorkWrite));
16381651 assert!(!Scope::RepoAdmin.includes(Scope::CodeWrite));
1639− assert!(!Scope::PullRequestsWrite.includes(Scope::IssuesWrite));
1652+ assert!(!Scope::PullRequestsWrite.includes(Scope::WorkWrite));
16401653 }
16411654
16421655 #[test]
16511664 #[test]
16521665 fn scopes_are_parsed_from_oauth_text_leaving_out_unknown_ones() {
16531666 assert_eq!(
1654− parse_scopes("issues:write repo:read,bogus:thing issues:write"),
1655− vec![Scope::RepoRead, Scope::IssuesWrite]
1667+ parse_scopes("work:write repo:read,bogus:thing work:write"),
1668+ vec![Scope::RepoRead, Scope::WorkWrite]
16561669 );
1657− assert_eq!(scopes_text(&[Scope::RepoRead, Scope::IssuesWrite]), "repo:read issues:write");
1670+ assert_eq!(scopes_text(&[Scope::RepoRead, Scope::WorkWrite]), "repo:read work:write");
16581671 }
16591672
16601673 #[test]
16611674 fn the_oauth_default_is_the_agent_preset_and_never_admin() {
16621675 let scopes = oauth_default();
1663− assert!(scopes.contains(&Scope::IssuesWrite));
1676+ assert!(scopes.contains(&Scope::WorkWrite));
16641677 assert!(scopes.contains(&Scope::PullRequestsWrite));
16651678 assert!(!scopes.contains(&Scope::AgentsRun));
16661679 assert!(scopes.iter().all(|scope| !scope.dangerous()), "{scopes:?}");
16821695 assert_eq!(back, scope);
16831696 }
16841697 // But nothing hands them out, and asking for them gets the rest.
1685− assert_eq!(parse_scopes("agents:run issues:read memory:write models:write"), vec![Scope::IssuesRead]);
1698+ assert_eq!(parse_scopes("agents:run work:read memory:write models:write"), vec![Scope::WorkRead]);
16861699 assert!(!offered_scopes().iter().any(|scope| scope.resource().retired()));
16871700 assert!(!everything().iter().any(|scope| scope.resource().retired()));
16881701 for preset in Preset::ALL {
18291842 let job = TokenAccess {
18301843 repo: Some("acme/web".into()),
18311844 job: Some(JobToken { run_id: "run_1".into(), job_id: "job_1".into(), pull_requests: false }),
1832− ..token(&[Scope::RepoRead, Scope::IssuesWrite, Scope::IssuesRead, Scope::PullRequestsWrite])
1845+ ..token(&[Scope::RepoRead, Scope::WorkWrite, Scope::WorkRead, Scope::PullRequestsWrite])
18331846 };
1834− assert!(decide(&job, "create_issue", &json!({ "repo": "acme/web" })).allowed);
1835− assert!(decide(&job, "create_issue", &json!({ "repo": "Acme/Web" })).allowed, "names compare without case");
1836− let elsewhere = decide(&job, "create_issue", &json!({ "repo": "acme/api" }));
1847+ assert!(decide(&job, "open_work_item", &json!({ "repo": "acme/web" })).allowed);
1848+ assert!(decide(&job, "open_work_item", &json!({ "repo": "Acme/Web" })).allowed, "names compare without case");
1849+ let elsewhere = decide(&job, "open_work_item", &json!({ "repo": "acme/api" }));
18371850 assert!(!elsewhere.allowed);
18381851 assert_eq!(elsewhere.rule, "token:repository");
18391852 // Nothing beyond the one repository, a workspace's listing included.
18821895 #[test]
18831896 fn a_narrowed_token_only_reads_outside_its_workspace() {
18841897 let reach = TokenReach { workspace: Some("acme".into()), repositories: RepositorySelection::All, repo_ids: Vec::new() };
1885− let fine = TokenAccess { reach: Some(reach), ..token(&[Scope::RepoRead, Scope::IssuesRead, Scope::IssuesWrite]) };
1886− assert!(decide(&fine, "create_issue", &json!({ "repo": "acme/web" })).allowed);
1887− assert!(decide(&fine, "create_issue", &json!({ "repo": "Acme/web" })).allowed);
1888− let elsewhere = decide(&fine, "create_issue", &json!({ "repo": "globex/site" }));
1898+ let fine = TokenAccess { reach: Some(reach), ..token(&[Scope::RepoRead, Scope::WorkRead, Scope::WorkWrite]) };
1899+ assert!(decide(&fine, "open_work_item", &json!({ "repo": "acme/web" })).allowed);
1900+ assert!(decide(&fine, "open_work_item", &json!({ "repo": "Acme/web" })).allowed);
1901+ let elsewhere = decide(&fine, "open_work_item", &json!({ "repo": "globex/site" }));
18891902 assert_eq!(elsewhere.rule, "token:resource-owner");
18901903 assert!(elsewhere.reason.unwrap().contains("acme"));
1891− assert!(decide(&fine, "get_issue", &json!({ "repo": "globex/site" })).allowed, "public repositories elsewhere read");
1904+ assert!(decide(&fine, "get_work_item", &json!({ "repo": "globex/site" })).allowed, "public repositories elsewhere read");
18921905 assert!(!decide(&fine, "create_pull_request", &json!({ "repo": "acme/web" })).allowed, "its scopes still hold");
1893− let mine = TokenAccess { reach: Some(TokenReach::default()), ..token(&[Scope::IssuesWrite]) };
1894− assert!(decide(&mine, "create_issue", &json!({ "repo": "acme/web" })).reason.unwrap().contains("your account"));
1906+ let mine = TokenAccess { reach: Some(TokenReach::default()), ..token(&[Scope::WorkWrite]) };
1907+ assert!(decide(&mine, "open_work_item", &json!({ "repo": "acme/web" })).reason.unwrap().contains("your account"));
18951908 assert!(fine.covers_repo("rep_1", "acme") && !fine.covers_repo("rep_1", "globex"));
18961909 let selected = TokenReach { workspace: Some("acme".into()), repositories: RepositorySelection::Selected, repo_ids: vec!["rep_1".into()] };
18971910 assert!(selected.covers("rep_1", "ACME") && !selected.covers("rep_2", "acme"));
19281941 #[test]
19291942 fn permissions_are_scopes_read_per_resource() {
19301943 let asked: std::collections::BTreeMap<String, String> =
1931− [("issues", "write"), ("repo", "read"), ("code", "none"), ("packages", "delete")].iter().map(|(a, b)| ((*a).to_owned(), (*b).to_owned())).collect();
1944+ [("work", "write"), ("repo", "read"), ("code", "none"), ("packages", "delete")].iter().map(|(a, b)| ((*a).to_owned(), (*b).to_owned())).collect();
19321945 let scopes = resolve_permissions(&asked, true).unwrap();
1933− assert_eq!(scopes, vec![Scope::RepoRead, Scope::PackagesDelete, Scope::IssuesWrite]);
1946+ assert_eq!(scopes, vec![Scope::RepoRead, Scope::PackagesDelete, Scope::WorkWrite]);
19341947 let back = permissions_of(&scopes);
1935− assert_eq!(back.get("issues").map(String::as_str), Some("write"));
1948+ assert_eq!(back.get("work").map(String::as_str), Some("write"));
19361949 assert_eq!(back.get("packages").map(String::as_str), Some("delete"));
19371950 assert!(!back.contains_key("code"));
19381951 // Lower levels held beside a higher one say nothing more.
19491962 fn permissions_are_checked_by_name_level_and_owner() {
19501963 let one = |name: &str, level: &str| -> std::collections::BTreeMap<String, String> { [(name.to_owned(), level.to_owned())].into() };
19511964 assert!(resolve_permissions(&one("wiki", "read"), true).unwrap_err().contains("wiki"));
1952− assert!(resolve_permissions(&one("issues", "admin"), true).unwrap_err().contains("read, write"));
1965+ assert!(resolve_permissions(&one("work", "admin"), true).unwrap_err().contains("read, write"));
19531966 assert!(resolve_permissions(&one("workflow_files", "read"), true).is_err(), "workflow files are written only");
19541967 assert!(resolve_permissions(&one("notifications", "read"), false).unwrap_err().contains("account"));
19551968 assert_eq!(resolve_permissions(&one("notifications", "read"), true).unwrap(), vec![Scope::NotificationsRead]);
19721985
19731986 #[test]
19741987 fn a_missing_scope_is_named() {
1975− let read = token(&[Scope::IssuesRead]);
1976− assert!(decide(&read, "get_issue", &json!({})).allowed);
1988+ let read = token(&[Scope::WorkRead]);
1989+ assert!(decide(&read, "get_work_item", &json!({})).allowed);
19771990 assert!(decide(&read, "whoami", &json!({})).allowed);
1978− let refused = decide(&read, "create_issue", &json!({}));
1991+ let refused = decide(&read, "open_work_item", &json!({}));
19791992 assert!(!refused.allowed);
1980− assert_eq!(refused.reason.as_deref(), Some("This access token needs the issues:write scope to use create_issue."));
1993+ assert_eq!(refused.reason.as_deref(), Some("This access token needs the work:write scope to use open_work_item."));
19811994 // An operation the table does not know needs full access.
19821995 assert!(!decide(&read, "something_new", &json!({})).allowed);
19831996 }
20072020 let refused = decide_git(&reader, true, false);
20082021 assert!(!refused.allowed);
20092022 assert!(refused.reason.unwrap().contains("code:write"));
2010− let issues = token(&[Scope::IssuesWrite]);
2011− assert!(!decide_git(&issues, false, false).allowed);
2012− assert!(decide_git(&issues, false, true).allowed, "public code needs no scope");
2013− assert!(!decide_git(&issues, true, true).allowed, "pushing to public code still needs code:write");
2023+ let work = token(&[Scope::WorkWrite]);
2024+ assert!(!decide_git(&work, false, false).allowed);
2025+ assert!(decide_git(&work, false, true).allowed, "public code needs no scope");
2026+ assert!(!decide_git(&work, true, true).allowed, "pushing to public code still needs code:write");
20142027 let writer = token(&[Scope::CodeWrite]);
20152028 assert!(decide_git(&writer, true, false).allowed);
20162029 assert!(decide_git(&writer, false, false).allowed, "code:write includes code:read");
20402053
20412054 #[test]
20422055 fn token_access_travels_as_json() {
2043− let access = token(&[Scope::IssuesRead]);
2056+ let access = token(&[Scope::WorkRead]);
20442057 let wire = serde_json::to_value(&access).unwrap();
2045− assert_eq!(wire["scopes"], json!(["issues:read"]));
2058+ assert_eq!(wire["scopes"], json!(["work:read"]));
20462059 assert!(wire.get("resources").is_none());
20472060 let back: TokenAccess = serde_json::from_value(wire).unwrap();
20482061 assert_eq!(back, access);
20522065 // whatever its owner can.
20532066 let older: TokenAccess = serde_json::from_value(json!({
20542067 "token_id": "tok_1",
2055− "scopes": ["issues:read"],
2068+ "scopes": ["work:read"],
20562069 "resources": { "kind": "repositories", "repositories": ["acme/rocket"] },
20572070 }))
20582071 .unwrap();
+56−46
44 * test keeps the tables here the same.
55 *
66 * A token's permissions are its scopes read per resource: each resource
7− * at none or one level, such as issues: write. What a request may do is
7+ * at none or one level, such as work: write. What a request may do is
88 * the intersection of the owner's role, the token's reach (the workspace
99 * it is made for, and its repositories) and its permissions.
1010 */
1414 | "code"
1515 | "security"
1616 | "packages"
17− | "issues"
17+ | "work"
1818 | "pull_requests"
1919 | "agents"
2020 | "workflows"
3939
4040 /** Every scope, grouped by resource, least first. */
4141 export const SCOPES = [
42− { scope: "repo:read", description: "See repositories, their settings, labels, timelines, releases, languages, contributors and security alerts, and search" },
42+ { scope: "repo:read", description: "See repositories, their settings, timelines, releases, languages, contributors and security alerts, and search" },
4343 { scope: "repo:create", description: "Create repositories, and nothing that changes or deletes one" },
4444 { scope: "repo:write", description: "Create repositories, create, rename and delete branches, change how pull requests merge and publish releases" },
4545 { scope: "repo:admin", description: "Rename, archive, transfer, delete or change who can see a repository, change its rulesets, and dismiss security alerts" },
5050 { scope: "packages:read", description: "Pull container images and install private packages" },
5151 { scope: "packages:write", description: "Push container images and publish packages" },
5252 { scope: "packages:delete", description: "Delete and restore packages and their versions" },
53− { scope: "issues:read", description: "Read issues and comments" },
54− { scope: "issues:write", description: "Open, edit, close and comment on issues" },
53+ { scope: "work:read", description: "Read work items, their comments, labels, milestones and claims" },
54+ { scope: "work:write", description: "Open, edit, link, close and comment on work items, claim them, and manage labels and milestones" },
5555 { scope: "pull_requests:read", description: "Read pull requests, their changes, sessions and merge queues" },
56− { scope: "pull_requests:write", description: "Open, review, close and merge pull requests" },
56+ { scope: "pull_requests:write", description: "Open, review, comment on, close and merge pull requests" },
5757 { scope: "workflows:read", description: "Read workflows, runs and logs" },
5858 { scope: "workflows:write", description: "Run, cancel, rerun and turn workflows on or off" },
5959 { scope: "workflow_files:write", description: "Add, change and delete workflow files under .g1t/workflows and .github/workflows, with git or the API" },
123123 { resource: "code", label: "Code", group: "repository" },
124124 { resource: "security", label: "Security", group: "repository" },
125125 { resource: "packages", label: "Packages", group: "repository" },
126− { resource: "issues", label: "Issues", group: "repository" },
126+ { resource: "work", label: "Work", group: "repository" },
127127 { resource: "pull_requests", label: "Pull requests", group: "repository" },
128128 { resource: "workflows", label: "Workflows", group: "repository" },
129129 { resource: "workflow_files", label: "Workflow files", group: "repository" },
252252 /** Starting points for choosing scopes. `*` is full access. No preset gives chat: an application gets it only by asking for it. */
253253 export const PRESET_SCOPES = {
254254 read_only: [
255− "repo:read", "code:read", "security:read", "packages:read", "issues:read", "pull_requests:read", "workflows:read", "checks:read", "deployments:read", "observability:read", "account:read", "notifications:read", "workspace:read", "billing:read", "access:read", "webhooks:read", "secrets:read", "runners:read", "artifacts:read",
255+ "repo:read", "code:read", "security:read", "packages:read", "work:read", "pull_requests:read", "workflows:read", "checks:read", "deployments:read", "observability:read", "account:read", "notifications:read", "workspace:read", "billing:read", "access:read", "webhooks:read", "secrets:read", "runners:read", "artifacts:read",
256256 ] as const,
257257 agent: [
258− "repo:read", "repo:create", "code:read", "code:write", "security:read", "packages:read", "issues:read", "issues:write", "pull_requests:read", "pull_requests:write", "workflows:read", "checks:read", "deployments:read", "observability:read", "account:read", "notifications:read", "notifications:write", "workspace:read", "billing:read", "access:read", "webhooks:read", "secrets:read", "artifacts:read",
258+ "repo:read", "repo:create", "code:read", "code:write", "security:read", "packages:read", "work:read", "work:write", "pull_requests:read", "pull_requests:write", "workflows:read", "checks:read", "deployments:read", "observability:read", "account:read", "notifications:read", "notifications:write", "workspace:read", "billing:read", "access:read", "webhooks:read", "secrets:read", "artifacts:read",
259259 ] as const,
260260 ci: [
261261 "repo:read", "code:read", "code:write", "packages:read", "packages:write", "workflows:read", "workflows:write", "checks:read", "checks:write", "deployments:read", "deployments:write",
267267
268268 export const PRESETS: { id: PresetId; label: string; description: string }[] = [
269269 { id: "read_only", label: "Read only", description: "Read everything you can read; change nothing." },
270− { id: "agent", label: "Agent", description: "Read everything, work on issues and pull requests, push code, and create repositories." },
270+ { id: "agent", label: "Agent", description: "Read everything, work on work items and pull requests, push code, and create repositories." },
271271 { id: "ci", label: "CI", description: "Clone and push code, push and pull packages, run workflows, and report checks and deployments." },
272272 { id: "full", label: "Full access", description: "Everything you can do, including deleting repositories and changing who has access." },
273273 ];
402402 ["create_release", "repo:write"],
403403 ["update_release", "repo:write"],
404404 ["delete_release", "repo:write"],
405− ["list_labels", "repo:read"],
406− ["list_milestones", "repo:read"],
407− ["get_milestone", "repo:read"],
408− ["create_label", "issues:write"],
409− ["update_label", "issues:write"],
410− ["delete_label", "issues:write"],
411− ["add_default_labels", "issues:write"],
412− ["create_milestone", "issues:write"],
413− ["update_milestone", "issues:write"],
414− ["delete_milestone", "issues:write"],
405+ ["list_labels", "work:read"],
406+ ["list_milestones", "work:read"],
407+ ["get_milestone", "work:read"],
408+ ["create_label", "work:write"],
409+ ["update_label", "work:write"],
410+ ["delete_label", "work:write"],
411+ ["add_default_labels", "work:write"],
412+ ["create_milestone", "work:write"],
413+ ["update_milestone", "work:write"],
414+ ["delete_milestone", "work:write"],
415415 ["get_repo_settings", "repo:read"],
416416 ["list_check_names", "repo:read"],
417417 ["list_deleted_repos", "repo:read"],
484484 ["set_security_advisory_state", "security:write"],
485485 ["report_vulnerability", "security:write"],
486486 ["review_vulnerability_report", "security:write"],
487− ["list_issues", "issues:read"],
488− ["get_issue", "issues:read"],
489− ["create_issue", "issues:write"],
490− ["update_issue", "issues:write"],
491− ["list_issue_labels", "issues:read"],
492− ["add_issue_labels", "issues:write"],
493− ["set_issue_labels", "issues:write"],
494− ["remove_issue_labels", "issues:write"],
495− ["close_issue", "issues:write"],
496− ["reopen_issue", "issues:write"],
497− ["add_comment", "issues:write"],
498− ["upload_attachment", "issues:write"],
499− ["edit_comment", "issues:write"],
500− ["delete_comment", "issues:write"],
501− ["react", "issues:write"],
502− ["create_commit_comment", "issues:write"],
503− ["update_commit_comment", "issues:write"],
504− ["delete_commit_comment", "issues:write"],
505− ["get_context", "issues:read"],
506− ["import_issue", "issues:write"],
507− ["list_claims", "issues:read"],
508− ["get_work", "issues:read"],
509− ["claim_issue", "issues:write"],
510− ["release_claim", "issues:write"],
511− ["hand_off_issue", "issues:write"],
487+ ["list_work_items", "work:read"],
488+ ["list_repo_work_items", "work:read"],
489+ ["get_work_item", "work:read"],
490+ ["list_work_item_links", "work:read"],
491+ ["resolve_ref", "work:read"],
492+ ["open_work_item", "work:write"],
493+ ["update_work_item", "work:write"],
494+ ["close_work_item", "work:write"],
495+ ["reopen_work_item", "work:write"],
496+ ["link_work_item", "work:write"],
497+ ["unlink_work_item", "work:write"],
498+ ["list_work_item_labels", "work:read"],
499+ ["add_work_item_labels", "work:write"],
500+ ["set_work_item_labels", "work:write"],
501+ ["remove_work_item_labels", "work:write"],
502+ ["list_work_item_decisions", "work:read"],
503+ ["add_work_item_comment", "work:write"],
504+ ["edit_work_item_comment", "work:write"],
505+ ["delete_work_item_comment", "work:write"],
506+ ["react_to_work_item", "work:write"],
507+ ["upload_attachment", "work:write"],
508+ ["create_commit_comment", "work:write"],
509+ ["update_commit_comment", "work:write"],
510+ ["delete_commit_comment", "work:write"],
511+ ["get_context", "work:read"],
512+ ["import_work_items", "work:write"],
513+ ["list_claims", "work:read"],
514+ ["get_work", "work:read"],
515+ ["claim_work_item", "work:write"],
516+ ["release_claim", "work:write"],
517+ ["hand_off_work_item", "work:write"],
512518 ["list_pull_requests", "pull_requests:read"],
513519 ["get_pull_request", "pull_requests:read"],
514520 ["get_pull_request_changes", "pull_requests:read"],
532538 ["delete_pull_request_head_branch", "pull_requests:write"],
533539 ["restore_pull_request_head_branch", "pull_requests:write"],
534540 ["request_reviewers", "pull_requests:write"],
541+ ["add_comment", "pull_requests:write"],
542+ ["edit_comment", "pull_requests:write"],
543+ ["delete_comment", "pull_requests:write"],
544+ ["react", "pull_requests:write"],
535545 ["remove_requested_reviewers", "pull_requests:write"],
536546 ["get_session", "pull_requests:read"],
537547 ["list_sessions", "pull_requests:read"],
748758 { id: "code", label: "Repositories & code", scopes: ["repo:read", "repo:create", "repo:write", "code:read", "code:write"] },
749759 { id: "security", label: "Security", scopes: ["security:read", "security:write"] },
750760 { id: "packages", label: "Packages", scopes: ["packages:read", "packages:write"] },
751− { id: "work", label: "Issues & pull requests", scopes: ["issues:read", "issues:write", "pull_requests:read", "pull_requests:write"] },
761+ { id: "work", label: "Work & pull requests", scopes: ["work:read", "work:write", "pull_requests:read", "pull_requests:write"] },
752762 { id: "workflows", label: "Workflows", scopes: ["workflows:read", "workflows:write", "workflow_files:write"] },
753763 { id: "checks", label: "Checks", scopes: ["checks:read", "checks:write"] },
754764 { id: "deployments", label: "Deployments", scopes: ["deployments:read", "deployments:write"] },
+1−0
14141414 assert!(restricted.allows(Scope::CodeRead));
14151415 assert!(!restricted.allows(Scope::CodeWrite));
14161416 assert!(!restricted.allows(Scope::PullRequestsWrite));
1417+ assert!(!restricted.allows(Scope::WorkWrite));
14171418 // A pull request from outside reads, whatever it asks for.
14181419 let outside = token(&Permissions::all(Access::Write).read_only());
14191420 assert!(Scope::ALL.iter().filter(|scope| outside.allows(**scope)).all(|scope| scope.level() == Level::Read));
+30−0
1+-- Issues became work items (g1t #57): the `issues` resource is now `work`,
2+-- so `issues:read` and `issues:write` are `work:read` and `work:write`.
3+-- Every stored scope list is rewritten, so tokens, OAuth grants and codes
4+-- not yet exchanged keep exactly the access they had. A fine-grained
5+-- token's old named permissions (retired in 0041, read by nothing) follow
6+-- for tidiness.
7+UPDATE access_tokens SET scopes = REPLACE(scopes, 'issues:', 'work:') WHERE scopes LIKE '%issues:%';
8+UPDATE oauth_grants SET scopes = REPLACE(scopes, 'issues:', 'work:') WHERE scopes LIKE '%issues:%';
9+UPDATE oauth_codes SET scopes = REPLACE(scopes, 'issues:', 'work:') WHERE scopes LIKE '%issues:%';
10+UPDATE access_tokens SET permissions = REPLACE(permissions, '"issues":', '"work":') WHERE permissions LIKE '%"issues":%';
11+-- A run's token lists the operations it may use by name: the work item
12+-- operations took the issue ones' places, and commenting on a work item
13+-- is its own operation beside commenting on a pull request.
14+UPDATE access_tokens SET agent_scope =
15+ REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(agent_scope,
16+ '"get_issue"', '"get_work_item"'),
17+ '"list_issues"', '"list_work_items"'),
18+ '"create_issue"', '"open_work_item"'),
19+ '"update_issue"', '"update_work_item"'),
20+ '"close_issue"', '"close_work_item"'),
21+ '"reopen_issue"', '"reopen_work_item"'),
22+ '"claim_issue"', '"claim_work_item"'),
23+ '"hand_off_issue"', '"hand_off_work_item"'),
24+ '"import_issue"', '"import_work_items"'),
25+ '"list_issue_labels"', '"list_work_item_labels"'),
26+ '"add_issue_labels"', '"add_work_item_labels"'),
27+ '"set_issue_labels"', '"set_work_item_labels"'),
28+ '"remove_issue_labels"', '"remove_work_item_labels"'),
29+ '"add_comment"', '"add_comment","add_work_item_comment"')
30+WHERE agent_scope IS NOT NULL;
+1−1
8989 assert!(access.allows(Scope::PullRequestsRead));
9090 assert!(access.allows(Scope::RepoRead));
9191 assert!(!access.allows(Scope::PullRequestsWrite));
92− assert!(!access.allows(Scope::IssuesWrite));
92+ assert!(!access.allows(Scope::WorkWrite));
9393 assert!(!access.allows(Scope::CodeWrite));
9494 // A person's conversations are never in it.
9595 assert!(!access.allows(Scope::ChatRead));
+6−5
12751275
12761276 fn workspace_token(selection: RepositorySelection) -> g1t_contracts::scopes::TokenAccess {
12771277 g1t_contracts::scopes::TokenAccess {
1278− scopes: Some(vec!["repo:read".into(), "code:write".into(), "issues:write".into()]),
1278+ scopes: Some(vec!["repo:read".into(), "code:write".into(), "work:write".into()]),
12791279 reach: Some(workspace_token_reach("acme".into(), selection, vec!["rep_1".into()])),
12801280 ..Default::default()
12811281 }
12861286 use g1t_contracts::scopes::decide;
12871287 for selection in [RepositorySelection::All, RepositorySelection::Selected] {
12881288 let token = workspace_token(selection);
1289− let issue = |repo: &str| serde_json::json!({ "repo": repo, "title": "spam" });
1290− let elsewhere = decide(&token, "create_issue", &issue("other/public"));
1289+ let item = |repo: &str| serde_json::json!({ "repo": repo, "title": "spam" });
1290+ let elsewhere = decide(&token, "open_work_item", &item("other/public"));
12911291 assert!(!elsewhere.allowed, "{selection:?}");
12921292 assert_eq!(elsewhere.rule, "token:resource-owner");
1293− assert!(decide(&token, "create_issue", &issue("acme/rocket")).allowed, "{selection:?}");
1293+ assert!(decide(&token, "open_work_item", &item("acme/rocket")).allowed, "{selection:?}");
12941294 // Reading a public repository elsewhere is as anyone may.
12951295 assert!(decide(&token, "get_repo", &serde_json::json!({ "repo": "other/public" })).allowed, "{selection:?}");
12961296 }
14061406 #[test]
14071407 fn the_migration_sets_full_access_out_as_every_permission() {
14081408 use g1t_contracts::scopes::{Resource, ResourceGroup, top_scopes};
1409− let sql = include_str!("../migrations/0041_one_kind_of_token.sql");
1409+ // As 0054 rewrote them, when `issues` became `work`.
1410+ let sql = include_str!("../migrations/0041_one_kind_of_token.sql").replace("issues:", "work:");
14101411 let then: Vec<Scope> = top_scopes(&Scope::ALL)
14111412 .into_iter()
14121413 .filter(|scope| !matches!(scope.resource(), Resource::Artifacts | Resource::Chat | Resource::Observability))
+4−4
516516 assert_eq!(stored_scopes(None), (None, true));
517517 assert_eq!(stored_scopes(Some("*")), (None, false));
518518 assert_eq!(
519− stored_scopes(Some("issues:write repo:read nonsense")),
520− (Some(vec!["repo:read".to_owned(), "issues:write".to_owned()]), false)
519+ stored_scopes(Some("work:write repo:read nonsense")),
520+ (Some(vec!["repo:read".to_owned(), "work:write".to_owned()]), false)
521521 );
522522 }
523523
524524 #[test]
525525 fn scopes_are_stored_as_text() {
526− let grant = Grant::asked(&Some(vec!["issues:read".to_owned(), "bogus".to_owned()]));
527− assert_eq!(grant.scopes_column(), "issues:read");
526+ let grant = Grant::asked(&Some(vec!["work:read".to_owned(), "bogus".to_owned()]));
527+ assert_eq!(grant.scopes_column(), "work:read");
528528 assert_eq!(Grant::asked(&None).scopes_column(), "*");
529529 assert_eq!(Grant::asked(&Some(vec![])).scopes_column(), "");
530530 }

Comments

Sign in to comment.