@@ -62,176 +62,208 @@ export async function generateSignalSnapshots(
6262 ( repo ) =>
6363 repo . isInstalled && ( ! repoFullName || repo . fullName === repoFullName ) ,
6464 ) ;
65- for ( const repo of repositories ) {
66- const trendSince = new Date (
67- Date . now ( ) - QUEUE_TREND_HISTORY_DAYS * 24 * 60 * 60 * 1000 ,
68- ) . toISOString ( ) ;
69- const [
70- issues ,
71- pullRequests ,
72- recentMergedPullRequests ,
73- labels ,
74- queueCounts ,
75- bounties ,
76- totalsHistory ,
77- queueHealthHistory ,
78- ] = await Promise . all ( [
79- listIssueSignalSample ( env , repo . fullName ) ,
80- listOpenPullRequests ( env , repo . fullName ) ,
81- listRecentMergedPullRequests ( env , repo . fullName ) ,
82- listRepoLabels ( env , repo . fullName ) ,
83- loadOpenQueueCounts ( env , repo . fullName ) ,
84- listBountiesByRepo ( env , repo . fullName ) ,
85- listRepoGithubTotalsSnapshotHistory ( env , repo . fullName , {
86- sinceIso : trendSince ,
87- limit : 120 ,
88- } ) ,
89- listSignalSnapshots ( env , "queue-health" , repo . fullName ) ,
90- ] ) ;
91- const collisions = buildCollisionReport (
92- repo . fullName ,
93- issues ,
94- pullRequests ,
95- recentMergedPullRequests ,
96- ) ;
97- const queueHealth = buildQueueHealth (
98- repo ,
99- issues ,
100- pullRequests ,
101- collisions ,
102- queueCounts ,
103- ) ;
104- const configQuality = buildConfigQuality (
105- repo ,
106- issues ,
107- pullRequests ,
108- repo . fullName ,
109- ) ;
110- const labelAudit = buildLabelAudit (
111- repo ,
112- labels ,
113- issues ,
114- pullRequests ,
115- repo . fullName ,
116- ) ;
117- const maintainerLane = buildMaintainerLaneReport (
118- repo ,
119- issues ,
120- pullRequests ,
121- repo . fullName ,
122- collisions ,
123- queueCounts ,
124- ) ;
125- const maintainerCutReadiness = buildMaintainerCutReadiness (
126- repo ,
127- issues ,
128- pullRequests ,
129- repo . fullName ,
130- queueCounts ,
131- collisions ,
132- ) ;
133- const contributorIntakeHealth = buildContributorIntakeHealth (
134- repo ,
135- issues ,
136- pullRequests ,
137- repo . fullName ,
138- collisions ,
139- queueCounts ,
140- ) ;
141- const issueQuality = buildIssueQualityReport (
142- repo ,
143- issues ,
144- pullRequests ,
145- repo . fullName ,
146- bounties ,
147- collisions ,
148- recentMergedPullRequests ,
149- ) ;
150- await replaceCollisionEdges (
151- env ,
152- repo . fullName ,
153- buildCollisionEdges ( collisions ) ,
154- ) ;
155- const generatedAt = new Date ( ) . toISOString ( ) ;
156- await persistSignalSnapshot ( env , {
157- id : crypto . randomUUID ( ) ,
158- signalType : "queue-health" ,
159- targetKey : repo . fullName ,
160- repoFullName : repo . fullName ,
161- payload : queueHealth as unknown as Record < string , never > ,
162- generatedAt,
163- } ) ;
164- await upsertRepoQueueTrendSnapshot ( env , {
165- repoFullName : repo . fullName ,
166- payload : buildQueueTrendReport ( {
167- repoFullName : repo . fullName ,
168- totalsSnapshots : totalsHistory ,
169- queueHealthSnapshots : queueHealthHistory ,
170- currentQueueHealth : queueHealth ,
171- generatedAt,
172- } ) as unknown as Record < string , never > ,
173- generatedAt,
174- } ) ;
175- await persistSignalSnapshot ( env , {
176- id : crypto . randomUUID ( ) ,
177- signalType : "config-quality" ,
178- targetKey : repo . fullName ,
179- repoFullName : repo . fullName ,
180- payload : configQuality as unknown as Record < string , never > ,
181- generatedAt,
182- } ) ;
183- await persistSignalSnapshot ( env , {
184- id : crypto . randomUUID ( ) ,
185- signalType : "label-audit" ,
186- targetKey : repo . fullName ,
187- repoFullName : repo . fullName ,
188- payload : labelAudit as unknown as Record < string , never > ,
189- generatedAt,
190- } ) ;
191- await persistSignalSnapshot ( env , {
192- id : crypto . randomUUID ( ) ,
193- signalType : "maintainer-lane" ,
194- targetKey : repo . fullName ,
195- repoFullName : repo . fullName ,
196- payload : maintainerLane as unknown as Record < string , never > ,
197- generatedAt,
198- } ) ;
199- await persistSignalSnapshot ( env , {
200- id : crypto . randomUUID ( ) ,
201- signalType : "maintainer-cut-readiness" ,
202- targetKey : repo . fullName ,
203- repoFullName : repo . fullName ,
204- payload : maintainerCutReadiness as unknown as Record < string , never > ,
205- generatedAt,
206- } ) ;
207- await persistSignalSnapshot ( env , {
208- id : crypto . randomUUID ( ) ,
209- signalType : "contributor-intake-health" ,
210- targetKey : repo . fullName ,
211- repoFullName : repo . fullName ,
212- payload : contributorIntakeHealth as unknown as Record < string , never > ,
213- generatedAt,
214- } ) ;
215- await persistSignalSnapshot ( env , {
216- id : crypto . randomUUID ( ) ,
217- signalType : "issue-quality" ,
218- targetKey : repo . fullName ,
219- repoFullName : repo . fullName ,
220- payload : issueQuality as unknown as Record < string , never > ,
221- generatedAt,
222- } ) ;
223- const repoOutcomePatterns = await computeRepoOutcomePatterns (
224- env ,
225- repo . fullName ,
226- repo ,
65+ // #9293: Promise.allSettled (not a bare sequential for-loop) so one repo's data-gathering or
66+ // persist failure never silently skips every subsequent repo in the same multi-repo invocation —
67+ // same isolation shape as job-dispatch.ts's "backfill-registered-repos" fan-out (#8355). Every
68+ // repo is attempted exactly once; successes persist regardless of a sibling's outcome; failures
69+ // are collected and rethrown as one aggregate error so the invocation stays observably failed.
70+ const settled = await Promise . allSettled (
71+ repositories . map ( ( repo ) => generateSignalSnapshotForRepo ( env , repo ) ) ,
72+ ) ;
73+ const failedRepoFullNames : string [ ] = [ ] ;
74+ settled . forEach ( ( result , index ) => {
75+ if ( result . status === "rejected" ) {
76+ const failedRepoFullName = repositories [ index ] ! . fullName ;
77+ failedRepoFullNames . push ( failedRepoFullName ) ;
78+ console . error (
79+ JSON . stringify ( {
80+ level : "error" ,
81+ event : "generate_signal_snapshots_repo_failed" ,
82+ repoFullName : failedRepoFullName ,
83+ reason : String ( result . reason ) ,
84+ } ) ,
85+ ) ;
86+ }
87+ } ) ;
88+ if ( failedRepoFullNames . length > 0 ) {
89+ throw new Error (
90+ `generate-signal-snapshots: ${ failedRepoFullNames . length } /${ repositories . length } repo(s) failed: ${ failedRepoFullNames . join ( ", " ) } ` ,
22791 ) ;
228- await persistSignalSnapshot ( env , {
229- id : crypto . randomUUID ( ) ,
230- signalType : REPO_OUTCOME_PATTERNS_SIGNAL ,
231- targetKey : repo . fullName ,
92+ }
93+ }
94+
95+ async function generateSignalSnapshotForRepo (
96+ env : Env ,
97+ repo : Awaited < ReturnType < typeof listRepositories > > [ number ] ,
98+ ) : Promise < void > {
99+ const trendSince = new Date (
100+ Date . now ( ) - QUEUE_TREND_HISTORY_DAYS * 24 * 60 * 60 * 1000 ,
101+ ) . toISOString ( ) ;
102+ const [
103+ issues ,
104+ pullRequests ,
105+ recentMergedPullRequests ,
106+ labels ,
107+ queueCounts ,
108+ bounties ,
109+ totalsHistory ,
110+ queueHealthHistory ,
111+ ] = await Promise . all ( [
112+ listIssueSignalSample ( env , repo . fullName ) ,
113+ listOpenPullRequests ( env , repo . fullName ) ,
114+ listRecentMergedPullRequests ( env , repo . fullName ) ,
115+ listRepoLabels ( env , repo . fullName ) ,
116+ loadOpenQueueCounts ( env , repo . fullName ) ,
117+ listBountiesByRepo ( env , repo . fullName ) ,
118+ listRepoGithubTotalsSnapshotHistory ( env , repo . fullName , {
119+ sinceIso : trendSince ,
120+ limit : 120 ,
121+ } ) ,
122+ listSignalSnapshots ( env , "queue-health" , repo . fullName ) ,
123+ ] ) ;
124+ const collisions = buildCollisionReport (
125+ repo . fullName ,
126+ issues ,
127+ pullRequests ,
128+ recentMergedPullRequests ,
129+ ) ;
130+ const queueHealth = buildQueueHealth (
131+ repo ,
132+ issues ,
133+ pullRequests ,
134+ collisions ,
135+ queueCounts ,
136+ ) ;
137+ const configQuality = buildConfigQuality (
138+ repo ,
139+ issues ,
140+ pullRequests ,
141+ repo . fullName ,
142+ ) ;
143+ const labelAudit = buildLabelAudit (
144+ repo ,
145+ labels ,
146+ issues ,
147+ pullRequests ,
148+ repo . fullName ,
149+ ) ;
150+ const maintainerLane = buildMaintainerLaneReport (
151+ repo ,
152+ issues ,
153+ pullRequests ,
154+ repo . fullName ,
155+ collisions ,
156+ queueCounts ,
157+ ) ;
158+ const maintainerCutReadiness = buildMaintainerCutReadiness (
159+ repo ,
160+ issues ,
161+ pullRequests ,
162+ repo . fullName ,
163+ queueCounts ,
164+ collisions ,
165+ ) ;
166+ const contributorIntakeHealth = buildContributorIntakeHealth (
167+ repo ,
168+ issues ,
169+ pullRequests ,
170+ repo . fullName ,
171+ collisions ,
172+ queueCounts ,
173+ ) ;
174+ const issueQuality = buildIssueQualityReport (
175+ repo ,
176+ issues ,
177+ pullRequests ,
178+ repo . fullName ,
179+ bounties ,
180+ collisions ,
181+ recentMergedPullRequests ,
182+ ) ;
183+ await replaceCollisionEdges (
184+ env ,
185+ repo . fullName ,
186+ buildCollisionEdges ( collisions ) ,
187+ ) ;
188+ const generatedAt = new Date ( ) . toISOString ( ) ;
189+ await persistSignalSnapshot ( env , {
190+ id : crypto . randomUUID ( ) ,
191+ signalType : "queue-health" ,
192+ targetKey : repo . fullName ,
193+ repoFullName : repo . fullName ,
194+ payload : queueHealth as unknown as Record < string , never > ,
195+ generatedAt,
196+ } ) ;
197+ await upsertRepoQueueTrendSnapshot ( env , {
198+ repoFullName : repo . fullName ,
199+ payload : buildQueueTrendReport ( {
232200 repoFullName : repo . fullName ,
233- payload : repoOutcomePatterns as unknown as Record < string , never > ,
201+ totalsSnapshots : totalsHistory ,
202+ queueHealthSnapshots : queueHealthHistory ,
203+ currentQueueHealth : queueHealth ,
234204 generatedAt,
235- } ) ;
236- }
205+ } ) as unknown as Record < string , never > ,
206+ generatedAt,
207+ } ) ;
208+ await persistSignalSnapshot ( env , {
209+ id : crypto . randomUUID ( ) ,
210+ signalType : "config-quality" ,
211+ targetKey : repo . fullName ,
212+ repoFullName : repo . fullName ,
213+ payload : configQuality as unknown as Record < string , never > ,
214+ generatedAt,
215+ } ) ;
216+ await persistSignalSnapshot ( env , {
217+ id : crypto . randomUUID ( ) ,
218+ signalType : "label-audit" ,
219+ targetKey : repo . fullName ,
220+ repoFullName : repo . fullName ,
221+ payload : labelAudit as unknown as Record < string , never > ,
222+ generatedAt,
223+ } ) ;
224+ await persistSignalSnapshot ( env , {
225+ id : crypto . randomUUID ( ) ,
226+ signalType : "maintainer-lane" ,
227+ targetKey : repo . fullName ,
228+ repoFullName : repo . fullName ,
229+ payload : maintainerLane as unknown as Record < string , never > ,
230+ generatedAt,
231+ } ) ;
232+ await persistSignalSnapshot ( env , {
233+ id : crypto . randomUUID ( ) ,
234+ signalType : "maintainer-cut-readiness" ,
235+ targetKey : repo . fullName ,
236+ repoFullName : repo . fullName ,
237+ payload : maintainerCutReadiness as unknown as Record < string , never > ,
238+ generatedAt,
239+ } ) ;
240+ await persistSignalSnapshot ( env , {
241+ id : crypto . randomUUID ( ) ,
242+ signalType : "contributor-intake-health" ,
243+ targetKey : repo . fullName ,
244+ repoFullName : repo . fullName ,
245+ payload : contributorIntakeHealth as unknown as Record < string , never > ,
246+ generatedAt,
247+ } ) ;
248+ await persistSignalSnapshot ( env , {
249+ id : crypto . randomUUID ( ) ,
250+ signalType : "issue-quality" ,
251+ targetKey : repo . fullName ,
252+ repoFullName : repo . fullName ,
253+ payload : issueQuality as unknown as Record < string , never > ,
254+ generatedAt,
255+ } ) ;
256+ const repoOutcomePatterns = await computeRepoOutcomePatterns (
257+ env ,
258+ repo . fullName ,
259+ repo ,
260+ ) ;
261+ await persistSignalSnapshot ( env , {
262+ id : crypto . randomUUID ( ) ,
263+ signalType : REPO_OUTCOME_PATTERNS_SIGNAL ,
264+ targetKey : repo . fullName ,
265+ repoFullName : repo . fullName ,
266+ payload : repoOutcomePatterns as unknown as Record < string , never > ,
267+ generatedAt,
268+ } ) ;
237269}
0 commit comments