@@ -8,15 +8,16 @@ import type { GitHubClient } from "../github/client.js";
88
99/**
1010 * The root team whose members (including all subteam members) can sponsor SEPs.
11+ * The GitHub API's getMembershipForUserInOrg checks nested team membership automatically.
1112 */
1213const SPONSOR_ROOT_TEAM = "steering-committee" ;
1314
1415export class MaintainerResolver {
1516 private readonly config : Config ;
1617 private readonly github : GitHubClient ;
1718 private readonly logger : Logger | undefined ;
18- private maintainerSet : Set < string > | null = null ;
19- private loadAttempted = false ;
19+ // Cache of username -> canSponsor result
20+ private readonly sponsorCache : Map < string , boolean > = new Map ( ) ;
2021
2122 constructor ( config : Config , github : GitHubClient , logger ?: Logger ) {
2223 this . config = config ;
@@ -25,81 +26,32 @@ export class MaintainerResolver {
2526 }
2627
2728 /**
28- * Load allowed sponsors from the API by recursively traversing
29- * all subteams of steering-committee.
29+ * Check if a user can sponsor SEPs.
30+ * Uses getMembershipForUserInOrg which automatically checks nested team membership,
31+ * avoiding the need for admin permissions to enumerate child teams.
3032 */
31- private async ensureSponsorsLoaded ( ) : Promise < Set < string > > {
32- if ( this . maintainerSet ) {
33- return this . maintainerSet ;
34- }
35-
36- if ( this . loadAttempted ) {
37- // Already tried and failed, return empty set
38- return new Set ( ) ;
33+ async canSponsor ( username : string ) : Promise < boolean > {
34+ // Check cache first
35+ const cached = this . sponsorCache . get ( username ) ;
36+ if ( cached !== undefined ) {
37+ return cached ;
3938 }
4039
41- this . loadAttempted = true ;
42-
43- try {
44- // Discover all teams recursively from the root team
45- const allTeams = await this . github . getAllDescendantTeams (
46- this . config . targetOwner ,
47- SPONSOR_ROOT_TEAM ,
48- ) ;
49-
50- this . logger ?. debug (
51- { teams : allTeams } ,
52- "Discovered sponsor teams from steering-committee" ,
53- ) ;
54-
55- const allMembers = new Set < string > ( ) ;
56-
57- // Fetch members from all discovered teams
58- for ( const team of allTeams ) {
59- try {
60- const members = await this . github . getTeamMembers (
61- this . config . targetOwner ,
62- team ,
63- ) ;
64- for ( const member of members ) {
65- allMembers . add ( member ) ;
66- }
67- } catch ( error ) {
68- this . logger ?. debug (
69- { team, error : String ( error ) } ,
70- "Failed to load team members, continuing with others" ,
71- ) ;
72- }
73- }
40+ const isMember = await this . github . isTeamMember (
41+ this . config . targetOwner ,
42+ SPONSOR_ROOT_TEAM ,
43+ username ,
44+ ) ;
7445
75- if ( allMembers . size > 0 ) {
76- this . maintainerSet = allMembers ;
77- this . logger ?. info (
78- { count : allMembers . size , teamCount : allTeams . length } ,
79- "Loaded allowed sponsors from API" ,
80- ) ;
81- return this . maintainerSet ;
82- }
46+ // Cache the result
47+ this . sponsorCache . set ( username , isMember ) ;
8348
84- throw new Error ( "No team members loaded from any team" ) ;
85- } catch ( error ) {
86- this . logger ?. error (
87- { error : String ( error ) } ,
88- "Failed to load sponsors from API" ,
89- ) ;
90- // No fallback - return empty set
91- this . maintainerSet = new Set ( ) ;
92- return this . maintainerSet ;
93- }
94- }
49+ this . logger ?. debug (
50+ { username, isMember, team : SPONSOR_ROOT_TEAM } ,
51+ "Checked sponsor eligibility" ,
52+ ) ;
9553
96- /**
97- * Check if a user can sponsor SEPs.
98- * Any member of a steering-committee subteam can sponsor.
99- */
100- async canSponsor ( username : string ) : Promise < boolean > {
101- const sponsors = await this . ensureSponsorsLoaded ( ) ;
102- return sponsors . has ( username ) ;
54+ return isMember ;
10355 }
10456
10557 /**
@@ -115,10 +67,9 @@ export class MaintainerResolver {
11567 }
11668
11769 /**
118- * Clear the cached sponsor list (useful for testing)
70+ * Clear the cached sponsor results (useful for testing)
11971 */
12072 clearCache ( ) : void {
121- this . maintainerSet = null ;
122- this . loadAttempted = false ;
73+ this . sponsorCache . clear ( ) ;
12374 }
12475}
0 commit comments