fix(external-plugins): block approval during intake reruns

Remove stale ready-for-review state before evaluating edited submissions or running quality gates. Require a current ready state before fast-tracking or approving, and cover the pending transition with focused tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 821f4796-505e-4fb8-96cf-0b5bbf80a636
This commit is contained in:
Aaron Powell
2026-10-13 11:58:49 +11:00
co-authored by Copilot App
parent f4088ee185
commit a48dab5961
7 changed files with 119 additions and 7 deletions
@@ -134,8 +134,11 @@ async function postDecision({ number, kind, comment }) {
const status = await gh.getIssueStatus(store.state.repo, number);
if (status.state !== "OPEN") throw new Error(`#${number} is ${status.state.toLowerCase()}. Refresh the board.`);
if (kind === "approve" && !status.labels.includes("ready-for-review")) {
throw new Error(`#${number} is no longer labelled ready-for-review, so /approve would be ignored.`);
if (
kind === "approve" &&
(!status.labels.includes("ready-for-review") || status.labels.includes("awaiting-review"))
) {
throw new Error(`#${number} is pending intake or is no longer ready-for-review, so /approve would be ignored.`);
}
const text = stripCommand(comment, kind);
@@ -17,12 +17,14 @@ export const COLUMNS = [
const HISTORY_LIMIT = 200;
export const FIRST_PARTY_LABEL = "first party";
// Submissions from first-party organizations skip AI triage and land in Accept. Only cards without
// an AI review, queued review, or manual bucket are fast-tracked so maintainer choices are kept;
// dragging a card back to Unreviewed opts it out so it gets a normal AI review.
// Validated submissions from first-party organizations skip AI triage and land in Accept. Only
// cards without an AI review, queued review, or manual bucket are fast-tracked so maintainer
// choices are kept; dragging a card back to Unreviewed opts it out so it gets a normal AI review.
function applyFirstPartyFastTrack(item) {
if (
!item.labels?.includes(FIRST_PARTY_LABEL) ||
!item.labels?.includes("ready-for-review") ||
item.labels?.includes("awaiting-review") ||
item.fastTrackDismissed ||
item.decision ||
item.review ||
@@ -44,7 +46,13 @@ function applyFirstPartyFastTrack(item) {
}
function clearFirstPartyFastTrack(item) {
if (item.labels?.includes(FIRST_PARTY_LABEL) || !item.review?.fastTracked || item.reviewStatus === "queued") {
if (
(item.labels?.includes(FIRST_PARTY_LABEL) &&
item.labels?.includes("ready-for-review") &&
!item.labels?.includes("awaiting-review")) ||
!item.review?.fastTracked ||
item.reviewStatus === "queued"
) {
return;
}
item.review = null;
@@ -85,7 +85,8 @@ jobs:
});
const fallbackName = evaluation.plugin?.name ?? `issue-${context.issue.number}`;
const canApprove = labelNames.has('ready-for-review') || labelNames.has('approved');
const canApprove = labelNames.has('approved') ||
(labelNames.has('ready-for-review') && !labelNames.has('awaiting-review'));
const canReject = !labelNames.has('approved');
if (parsedCommand.command === 'approve' && !canApprove) {
@@ -48,6 +48,22 @@ jobs:
core.setOutput('issue-labels', JSON.stringify(labels));
core.setOutput('should-sync', (!isApproved && !isClosedWithoutReopen) ? 'true' : 'false');
- name: Mark submission as awaiting review
if: steps.guard.outputs.should-sync == 'true'
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
with:
script: |
const path = require('path');
const { pathToFileURL } = require('url');
const intakeState = await import(pathToFileURL(path.join(process.env.GITHUB_WORKSPACE, 'eng', 'external-plugin-intake-state.mjs')).href);
await intakeState.markExternalPluginIntakeAwaitingReview({
github,
owner: context.repo.owner,
repo: context.repo.repo,
issueNumber: context.issue.number
});
- name: Evaluate submission
id: evaluation
env: