diff --git a/.agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts b/.agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts index 900aef405db..7a9f8f14f4f 100644 --- a/.agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts +++ b/.agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts @@ -1865,6 +1865,29 @@ function currentCheckRollup( return { runId: segments[4], jobId: segments[6] }; }; + const associationLessHeadBinding = ( + metadata: ActionRunMetadata, + ): "current" | "other" | "unknown" => { + if ( + metadata.hasPullRequests !== false || + (metadata.event !== "pull_request" && metadata.event !== "pull_request_target") + ) { + return "unknown"; + } + if ( + metadata.headShaMatches === false || + metadata.headRefNameMatches === false || + metadata.headRepositoryMatches === false + ) { + return "other"; + } + return metadata.headShaMatches === true && + metadata.headRefNameMatches === true && + metadata.headRepositoryMatches === true + ? "current" + : "unknown"; + }; + const isAuthenticatedAdvisoryPrReviewCheck = (check: StatusCheck): boolean => { const checkName = check.name ?? ""; if ( @@ -1880,13 +1903,20 @@ function currentCheckRollup( const job = latestAttemptJobs(identity.runId)?.get(identity.jobId); const checkStatus = check.status?.toUpperCase() ?? null; const checkConclusion = check.conclusion?.toUpperCase() ?? null; + const currentPrBinding = + run?.hasPullRequests === true && run.exactDiff === true + ? true + : exactDiff.headRepository !== repo && + run !== null && + run.status === "COMPLETED" && + run.conclusion !== null && + associationLessHeadBinding(run) === "current"; return Boolean( run && job && run.event === "pull_request_target" && run.path === PR_REVIEW_ADVISOR_WORKFLOW_PATH && - run.hasPullRequests === true && - run.exactDiff === true && + currentPrBinding && job.name === checkName && job.status === checkStatus && job.conclusion === checkConclusion, @@ -1899,29 +1929,6 @@ function currentCheckRollup( check.detailsUrl?.match(/\/runs\/(\d+)(?:[/?#]|$)/u)?.[1] === String(e2eCoordinationEvidence.trustedCustomCheckId); - const associationLessHeadBinding = ( - metadata: ActionRunMetadata, - ): "current" | "other" | "unknown" => { - if ( - metadata.hasPullRequests !== false || - (metadata.event !== "pull_request" && metadata.event !== "pull_request_target") - ) { - return "unknown"; - } - if ( - metadata.headShaMatches === false || - metadata.headRefNameMatches === false || - metadata.headRepositoryMatches === false - ) { - return "other"; - } - return metadata.headShaMatches === true && - metadata.headRefNameMatches === true && - metadata.headRepositoryMatches === true - ? "current" - : "unknown"; - }; - function runIdentityEvidence( runId: string, requiresExactDiff: boolean, diff --git a/test/skills/check-gates-actions-evidence.test.ts b/test/skills/check-gates-actions-evidence.test.ts index 2bc3b508d9d..cbce16d0c8a 100644 --- a/test/skills/check-gates-actions-evidence.test.ts +++ b/test/skills/check-gates-actions-evidence.test.ts @@ -35,6 +35,9 @@ interface AdvisorRunOptions { jobName?: string; path?: string; event?: string; + headSha?: string; + headBranch?: string; + headRepository?: string; pullRequests?: unknown[]; status?: string; conclusion?: string | null; @@ -165,6 +168,101 @@ describe("maintainer merge-gate contributor compliance", () => { }); }); + it("keeps a current fork Advisor lane advisory without a REST PR association", () => { + const runId = 9003; + const jobId = 9103; + const forkRepository = "contributor/NemoClaw"; + const result = runGate({ + body: "Signed-off-by: Example User ", + verified: true, + headRepository: forkRepository, + statusChecks: [...successfulRequiredChecks(), advisorCheck(runId, jobId)], + actionRunAttempts: { + [String(runId)]: advisorRun(jobId, { + headSha: HEAD_SHA, + headBranch: "feature-branch", + headRepository: forkRepository, + pullRequests: [], + status: "completed", + conclusion: "failure", + }), + }, + }); + + expect(JSON.parse(result.stdout)).toMatchObject({ + allPass: true, + gates: { ci: { pass: true } }, + }); + }); + + it.each([ + { + evidence: "the workflow run is in progress", + run: { status: "in_progress", conclusion: "failure" }, + }, + { + evidence: "the completed workflow run has no conclusion", + run: { status: "completed", conclusion: null }, + }, + ])("keeps an association-less fork Advisor lane merge-relevant when $evidence", ({ run }) => { + const runId = 9005; + const jobId = 9105; + const forkRepository = "contributor/NemoClaw"; + const result = runGate({ + body: "Signed-off-by: Example User ", + verified: true, + headRepository: forkRepository, + statusChecks: [...successfulRequiredChecks(), advisorCheck(runId, jobId)], + actionRunAttempts: { + [String(runId)]: advisorRun(jobId, { + headSha: HEAD_SHA, + headBranch: "feature-branch", + headRepository: forkRepository, + pullRequests: [], + ...run, + }), + }, + }); + + expect(JSON.parse(result.stdout)).toMatchObject({ + allPass: false, + gates: { ci: { pass: false } }, + }); + }); + + it.each([ + { evidence: "the head SHA differs", run: { headSha: BASE_SHA } }, + { evidence: "the head ref differs", run: { headBranch: "other-branch" } }, + { + evidence: "the head repository differs", + run: { headRepository: "attacker/NemoClaw" }, + }, + ])("keeps an association-less fork Advisor lane merge-relevant when $evidence", ({ run }) => { + const runId = 9004; + const jobId = 9104; + const forkRepository = "contributor/NemoClaw"; + const result = runGate({ + body: "Signed-off-by: Example User ", + verified: true, + headRepository: forkRepository, + statusChecks: [...successfulRequiredChecks(), advisorCheck(runId, jobId)], + actionRunAttempts: { + [String(runId)]: advisorRun(jobId, { + headSha: HEAD_SHA, + headBranch: "feature-branch", + headRepository: forkRepository, + pullRequests: [], + ...run, + }), + }, + }); + + expect(JSON.parse(result.stdout)).toMatchObject({ + allPass: false, + gates: { ci: { pass: false } }, + }); + }); + it.each([ { evidence: "the REST job has another name", run: { jobName: "unrelated job" } }, {