This commit is contained in:
Toni Cardoso 2026-09-13 08:40:00 +01:00 committed by GitHub
commit 577e7e52c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 48 additions and 1 deletions

View File

@ -773,6 +773,49 @@ describe("issue execution policy transitions", () => {
// No error — just no patch modifications
expect(result.patch).toEqual({});
});
it("board operator can reconcile a stuck active review stage back to the return assignee", () => {
const result = applyIssueExecutionPolicyTransition({
issue: {
status: "in_review",
assigneeAgentId: qaAgentId,
assigneeUserId: null,
executionPolicy: policy,
executionState: {
status: "pending",
currentStageId: reviewStageId,
currentStageIndex: 0,
currentStageType: "review",
currentParticipant: { type: "agent", agentId: qaAgentId },
returnAssignee: { type: "agent", agentId: coderAgentId },
completedStageIds: [],
lastDecisionId: null,
lastDecisionOutcome: null,
},
},
policy,
requestedStatus: "in_progress",
requestedAssigneePatch: { assigneeAgentId: coderAgentId },
actor: { userId: boardUserId },
commentBody: "Operator reconciliation: reviewer already requested changes but the runtime failed to patch state.",
});
expect(result.patch.status).toBe("in_progress");
expect(result.patch.assigneeAgentId).toBe(coderAgentId);
expect(result.patch.executionState).toMatchObject({
status: "changes_requested",
currentStageId: reviewStageId,
currentStageType: "review",
currentParticipant: { type: "agent", agentId: qaAgentId },
returnAssignee: { type: "agent", agentId: coderAgentId },
lastDecisionOutcome: "changes_requested",
});
expect(result.decision).toMatchObject({
stageId: reviewStageId,
stageType: "review",
outcome: "changes_requested",
});
});
});
describe("comment requirements", () => {

View File

@ -76,6 +76,10 @@ const MONITOR_BOUNDS_EXHAUSTED_MESSAGE = "Monitor bounds are already exhausted";
const STAGE_DECISION_COMMENT_HINT = "Include the decision comment in the same PATCH request; prior comments are not considered.";
export const REDACTED_ISSUE_MONITOR_EXTERNAL_REF = "[redacted]";
function isBoardActor(actor: ActorLike) {
return Boolean(actor.userId) && !actor.agentId;
}
function normalizeMonitorNotes(notes: string | null | undefined) {
if (typeof notes !== "string") return null;
const trimmed = notes.trim();
@ -783,7 +787,7 @@ function applyIssueExecutionStageTransition(input: TransitionInput): TransitionR
};
}
if (principalsEqual(currentParticipant, actor)) {
if ((actor && principalsEqual(currentParticipant, actor)) || isBoardActor(input.actor)) {
if (requestedStatus === "done") {
if (!input.commentBody?.trim()) {
throw unprocessable(`Approving a review or approval stage requires a comment. ${STAGE_DECISION_COMMENT_HINT}`);