From 0848ba01930162429e3f563d8209acbe87e3c4ce Mon Sep 17 00:00:00 2001 From: Yggdrasil Date: Wed, 14 Oct 2026 02:38:26 -0400 Subject: [PATCH 1/2] fix(api): GET /milestones + /maintenance-pools load the relations the UI renders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both list() were bare find() — no relations, so the frontend's adaptMilestone/adaptMaintenancePool got empty rows (no repository, sponsor, escrow, issues). Resolves #591 --- src/maintenance-pool/maintenance-pool.service.ts | 6 +++++- src/milestones/milestones.service.ts | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/maintenance-pool/maintenance-pool.service.ts b/src/maintenance-pool/maintenance-pool.service.ts index 13415a62..86ac588b 100644 --- a/src/maintenance-pool/maintenance-pool.service.ts +++ b/src/maintenance-pool/maintenance-pool.service.ts @@ -196,6 +196,10 @@ export class MaintenancePoolService { } async list(): Promise { - return this.poolRepo.find(); + // Load repository/sponsor/escrow — the frontend's adaptMaintenancePool + // needs them; a bare find() returned rows with no relations (#591). + return this.poolRepo.find({ + relations: ['repository', 'createdBy', 'escrow'], + }); } } diff --git a/src/milestones/milestones.service.ts b/src/milestones/milestones.service.ts index c2b7ee8b..fe21ca75 100644 --- a/src/milestones/milestones.service.ts +++ b/src/milestones/milestones.service.ts @@ -214,7 +214,12 @@ export class MilestonesService { } async list(): Promise { - return this.milestoneRepo.find(); + // Load the relations the frontend's adaptMilestone renders — repository + // (org/repo display), sponsor, escrow (fund status), issues (the linked + // issues). A bare find() returned empty rows (#591). + return this.milestoneRepo.find({ + relations: ['repository', 'sponsor', 'escrow', 'issues'], + }); } allocateBudget(id: string) { From d553cc1e1286c2d2d3eba96c679797ba7c988811 Mon Sep 17 00:00:00 2001 From: Yggdrasil Date: Wed, 14 Oct 2026 02:39:27 -0400 Subject: [PATCH 2/2] fix(milestones): a milestone can pay out more than its first issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolveIssue asserted status->IN_PROGRESS on every call — the first resolve moved FUNDED->IN_PROGRESS, so the second resolve hit IN_PROGRESS->IN_PROGRESS which isn't a valid transition and threw. Only transition when not already IN_PROGRESS. Resolves #578 --- src/milestones/milestones.service.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/milestones/milestones.service.ts b/src/milestones/milestones.service.ts index fe21ca75..f8f7d8ed 100644 --- a/src/milestones/milestones.service.ts +++ b/src/milestones/milestones.service.ts @@ -152,7 +152,14 @@ export class MilestonesService { `Milestone ${milestoneId} has not been funded yet`, ); } - assertTransition(milestone.status, MilestoneStatus.IN_PROGRESS); + // Only assert the transition when the milestone isn't already in-flight — + // the first resolution moves FUNDED -> IN_PROGRESS, so re-asserting on + // every resolveIssue made IN_PROGRESS->IN_PROGRESS fail and a milestone + // could only ever pay out its first issue (#578). An already-IN_PROGRESS + // milestone paying another issue needs no transition. + if (milestone.status !== MilestoneStatus.IN_PROGRESS) { + assertTransition(milestone.status, MilestoneStatus.IN_PROGRESS); + } // Verify the issue belongs to this milestone (#114). const issue = milestone.issues.find((i) => i.id === issueId);