@attalabs/vinaya 0.12.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -2
- package/aeg-root/contracts/planner-brief.md +1 -1
- package/aeg-root/enforcement.md +5 -5
- package/aeg-root/roles/planner.md +2 -2
- package/aeg-root/roles/reviewer.md +17 -4
- package/aeg-root/roles/security.md +16 -5
- package/aeg-root/skills/aeg/SKILL.md +1 -1
- package/aeg-root/state-machine.md +2 -2
- package/aeg-root/tranche-model.md +1 -1
- package/dist/checks/bin/check-branch-topology.js +28 -5
- package/dist/checks/bin/check-brief-shape.js +28 -5
- package/dist/checks/bin/check-closes-n.js +28 -5
- package/dist/checks/bin/check-coherence.js +30 -6
- package/dist/checks/bin/check-dead-branch-push.js +28 -5
- package/dist/checks/bin/check-dispatch-readiness.js +30 -6
- package/dist/checks/bin/check-doc-coverage-push.js +30 -6
- package/dist/checks/bin/check-doc-coverage.js +30 -6
- package/dist/checks/bin/check-evidence-fresh.js +28 -5
- package/dist/checks/bin/check-first-push-dispatch.js +30 -6
- package/dist/checks/bin/check-issue-assignment.js +28 -5
- package/dist/checks/bin/check-no-disk-state.js +28 -5
- package/dist/checks/bin/check-reader-resolvable-prose.js +28 -5
- package/dist/checks/bin/check-registry-gates.js +28 -5
- package/dist/checks/bin/check-review-gate.js +33 -8
- package/dist/checks/bin/check-single-plan-pr.js +28 -5
- package/dist/checks/bin/check-test-plan.js +28 -5
- package/dist/index.js +777 -73
- package/package.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__0e95dv5._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/1q96_modules_@clerk_nextjs_dist_esm_app-router_client_keyless-creator-reader_0lom2js.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__029-1ch._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0q56ohl._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0zfjc2o._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1_ojzp1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1wsl6ro._.js → [root-of-the-server]__1qpl4m_._.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_00yjcv1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0o54suy._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1gw6vc9._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2ieu4oi-pp_bz.js → 0ek6qlqhokys0.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1khy4oilrab6g.js → 0fm4p9os2lsom.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1eo32v1tbjy7c.js → 0ywkq74falx-x.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1t7yy_s34022p.js → 1s9ixp5nhnevp.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/27sqffqm2wbxx.css +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0qu0xgvifbacu.css +0 -1
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{ZHVE9Y9G8XKR91g27SgVX → R7xvB2iuNFp_jxENpL7xv}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{ZHVE9Y9G8XKR91g27SgVX → R7xvB2iuNFp_jxENpL7xv}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{ZHVE9Y9G8XKR91g27SgVX → R7xvB2iuNFp_jxENpL7xv}/_ssgManifest.js +0 -0
|
@@ -2368,15 +2368,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2368
2368
|
return { ok: true, expectedIssue };
|
|
2369
2369
|
}
|
|
2370
2370
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2371
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2372
|
+
function extractHeadSha(comment) {
|
|
2373
|
+
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2374
|
+
return m ? m[1].toLowerCase() : null;
|
|
2375
|
+
}
|
|
2371
2376
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2372
2377
|
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2373
2378
|
if (clearHits.length > 0) {
|
|
2374
2379
|
const latest = clearHits[clearHits.length - 1];
|
|
2375
2380
|
const m = latest.match(valuePattern);
|
|
2376
|
-
return {
|
|
2381
|
+
return {
|
|
2382
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2383
|
+
headSha: extractHeadSha(latest),
|
|
2384
|
+
danglingNote: null
|
|
2385
|
+
};
|
|
2377
2386
|
}
|
|
2378
2387
|
return {
|
|
2379
2388
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2389
|
+
headSha: null,
|
|
2380
2390
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2381
2391
|
};
|
|
2382
2392
|
}
|
|
@@ -2390,6 +2400,11 @@ function extractSecurityReviewVerdict(comments) {
|
|
|
2390
2400
|
function isReviewGateExemptBranch(branch) {
|
|
2391
2401
|
return branch.startsWith("plan/");
|
|
2392
2402
|
}
|
|
2403
|
+
function isBoundToHead(extraction, headSha) {
|
|
2404
|
+
if (!extraction.headSha)
|
|
2405
|
+
return false;
|
|
2406
|
+
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2407
|
+
}
|
|
2393
2408
|
function checkReviewGate(input) {
|
|
2394
2409
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2395
2410
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2412,18 +2427,26 @@ function checkReviewGate(input) {
|
|
|
2412
2427
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2413
2428
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2414
2429
|
const securityClean = security.value === "PASS";
|
|
2415
|
-
|
|
2430
|
+
const codeReviewBound = isBoundToHead(codeReview, input.headSha);
|
|
2431
|
+
const securityBound = isBoundToHead(security, input.headSha);
|
|
2432
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
2416
2433
|
return {
|
|
2417
2434
|
verdict: "pass",
|
|
2418
|
-
reason:
|
|
2435
|
+
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
2419
2436
|
waived: false
|
|
2420
2437
|
};
|
|
2421
2438
|
}
|
|
2422
2439
|
const problems = [];
|
|
2423
|
-
if (!codeReviewClean)
|
|
2440
|
+
if (!codeReviewClean) {
|
|
2424
2441
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2425
|
-
if (!
|
|
2442
|
+
} else if (!codeReviewBound) {
|
|
2443
|
+
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2444
|
+
}
|
|
2445
|
+
if (!securityClean) {
|
|
2426
2446
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2447
|
+
} else if (!securityBound) {
|
|
2448
|
+
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2449
|
+
}
|
|
2427
2450
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2428
2451
|
return {
|
|
2429
2452
|
verdict: "fail",
|
|
@@ -3147,7 +3170,8 @@ var VinayaConfigSchema = z2.object({
|
|
|
3147
3170
|
briefSchema: BriefSchemaSchema.optional(),
|
|
3148
3171
|
managed: ManagedManifestSchema.optional(),
|
|
3149
3172
|
principals: z2.array(z2.string()).min(1).optional(),
|
|
3150
|
-
ci: z2.object({ setup: z2.string().min(1) }).optional()
|
|
3173
|
+
ci: z2.object({ setup: z2.string().min(1) }).optional(),
|
|
3174
|
+
blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional()
|
|
3151
3175
|
});
|
|
3152
3176
|
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
3153
3177
|
var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
|
|
@@ -2368,15 +2368,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2368
2368
|
return { ok: true, expectedIssue };
|
|
2369
2369
|
}
|
|
2370
2370
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2371
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2372
|
+
function extractHeadSha(comment) {
|
|
2373
|
+
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2374
|
+
return m ? m[1].toLowerCase() : null;
|
|
2375
|
+
}
|
|
2371
2376
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2372
2377
|
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2373
2378
|
if (clearHits.length > 0) {
|
|
2374
2379
|
const latest = clearHits[clearHits.length - 1];
|
|
2375
2380
|
const m = latest.match(valuePattern);
|
|
2376
|
-
return {
|
|
2381
|
+
return {
|
|
2382
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2383
|
+
headSha: extractHeadSha(latest),
|
|
2384
|
+
danglingNote: null
|
|
2385
|
+
};
|
|
2377
2386
|
}
|
|
2378
2387
|
return {
|
|
2379
2388
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2389
|
+
headSha: null,
|
|
2380
2390
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2381
2391
|
};
|
|
2382
2392
|
}
|
|
@@ -2390,6 +2400,11 @@ function extractSecurityReviewVerdict(comments) {
|
|
|
2390
2400
|
function isReviewGateExemptBranch(branch) {
|
|
2391
2401
|
return branch.startsWith("plan/");
|
|
2392
2402
|
}
|
|
2403
|
+
function isBoundToHead(extraction, headSha) {
|
|
2404
|
+
if (!extraction.headSha)
|
|
2405
|
+
return false;
|
|
2406
|
+
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2407
|
+
}
|
|
2393
2408
|
function checkReviewGate(input) {
|
|
2394
2409
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2395
2410
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2412,18 +2427,26 @@ function checkReviewGate(input) {
|
|
|
2412
2427
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2413
2428
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2414
2429
|
const securityClean = security.value === "PASS";
|
|
2415
|
-
|
|
2430
|
+
const codeReviewBound = isBoundToHead(codeReview, input.headSha);
|
|
2431
|
+
const securityBound = isBoundToHead(security, input.headSha);
|
|
2432
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
2416
2433
|
return {
|
|
2417
2434
|
verdict: "pass",
|
|
2418
|
-
reason:
|
|
2435
|
+
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
2419
2436
|
waived: false
|
|
2420
2437
|
};
|
|
2421
2438
|
}
|
|
2422
2439
|
const problems = [];
|
|
2423
|
-
if (!codeReviewClean)
|
|
2440
|
+
if (!codeReviewClean) {
|
|
2424
2441
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2425
|
-
if (!
|
|
2442
|
+
} else if (!codeReviewBound) {
|
|
2443
|
+
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2444
|
+
}
|
|
2445
|
+
if (!securityClean) {
|
|
2426
2446
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2447
|
+
} else if (!securityBound) {
|
|
2448
|
+
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2449
|
+
}
|
|
2427
2450
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2428
2451
|
return {
|
|
2429
2452
|
verdict: "fail",
|
|
@@ -2368,15 +2368,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2368
2368
|
return { ok: true, expectedIssue };
|
|
2369
2369
|
}
|
|
2370
2370
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2371
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2372
|
+
function extractHeadSha(comment) {
|
|
2373
|
+
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2374
|
+
return m ? m[1].toLowerCase() : null;
|
|
2375
|
+
}
|
|
2371
2376
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2372
2377
|
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2373
2378
|
if (clearHits.length > 0) {
|
|
2374
2379
|
const latest = clearHits[clearHits.length - 1];
|
|
2375
2380
|
const m = latest.match(valuePattern);
|
|
2376
|
-
return {
|
|
2381
|
+
return {
|
|
2382
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2383
|
+
headSha: extractHeadSha(latest),
|
|
2384
|
+
danglingNote: null
|
|
2385
|
+
};
|
|
2377
2386
|
}
|
|
2378
2387
|
return {
|
|
2379
2388
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2389
|
+
headSha: null,
|
|
2380
2390
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2381
2391
|
};
|
|
2382
2392
|
}
|
|
@@ -2390,6 +2400,11 @@ function extractSecurityReviewVerdict(comments) {
|
|
|
2390
2400
|
function isReviewGateExemptBranch(branch) {
|
|
2391
2401
|
return branch.startsWith("plan/");
|
|
2392
2402
|
}
|
|
2403
|
+
function isBoundToHead(extraction, headSha) {
|
|
2404
|
+
if (!extraction.headSha)
|
|
2405
|
+
return false;
|
|
2406
|
+
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2407
|
+
}
|
|
2393
2408
|
function checkReviewGate(input) {
|
|
2394
2409
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2395
2410
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2412,18 +2427,26 @@ function checkReviewGate(input) {
|
|
|
2412
2427
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2413
2428
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2414
2429
|
const securityClean = security.value === "PASS";
|
|
2415
|
-
|
|
2430
|
+
const codeReviewBound = isBoundToHead(codeReview, input.headSha);
|
|
2431
|
+
const securityBound = isBoundToHead(security, input.headSha);
|
|
2432
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
2416
2433
|
return {
|
|
2417
2434
|
verdict: "pass",
|
|
2418
|
-
reason:
|
|
2435
|
+
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
2419
2436
|
waived: false
|
|
2420
2437
|
};
|
|
2421
2438
|
}
|
|
2422
2439
|
const problems = [];
|
|
2423
|
-
if (!codeReviewClean)
|
|
2440
|
+
if (!codeReviewClean) {
|
|
2424
2441
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2425
|
-
if (!
|
|
2442
|
+
} else if (!codeReviewBound) {
|
|
2443
|
+
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2444
|
+
}
|
|
2445
|
+
if (!securityClean) {
|
|
2426
2446
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2447
|
+
} else if (!securityBound) {
|
|
2448
|
+
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2449
|
+
}
|
|
2427
2450
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2428
2451
|
return {
|
|
2429
2452
|
verdict: "fail",
|
|
@@ -2370,15 +2370,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2370
2370
|
return { ok: true, expectedIssue };
|
|
2371
2371
|
}
|
|
2372
2372
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2373
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2374
|
+
function extractHeadSha(comment) {
|
|
2375
|
+
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2376
|
+
return m ? m[1].toLowerCase() : null;
|
|
2377
|
+
}
|
|
2373
2378
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2374
2379
|
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2375
2380
|
if (clearHits.length > 0) {
|
|
2376
2381
|
const latest = clearHits[clearHits.length - 1];
|
|
2377
2382
|
const m = latest.match(valuePattern);
|
|
2378
|
-
return {
|
|
2383
|
+
return {
|
|
2384
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2385
|
+
headSha: extractHeadSha(latest),
|
|
2386
|
+
danglingNote: null
|
|
2387
|
+
};
|
|
2379
2388
|
}
|
|
2380
2389
|
return {
|
|
2381
2390
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2391
|
+
headSha: null,
|
|
2382
2392
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2383
2393
|
};
|
|
2384
2394
|
}
|
|
@@ -2392,6 +2402,11 @@ function extractSecurityReviewVerdict(comments) {
|
|
|
2392
2402
|
function isReviewGateExemptBranch(branch) {
|
|
2393
2403
|
return branch.startsWith("plan/");
|
|
2394
2404
|
}
|
|
2405
|
+
function isBoundToHead(extraction, headSha) {
|
|
2406
|
+
if (!extraction.headSha)
|
|
2407
|
+
return false;
|
|
2408
|
+
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2409
|
+
}
|
|
2395
2410
|
function checkReviewGate(input) {
|
|
2396
2411
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2397
2412
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2414,18 +2429,26 @@ function checkReviewGate(input) {
|
|
|
2414
2429
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2415
2430
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2416
2431
|
const securityClean = security.value === "PASS";
|
|
2417
|
-
|
|
2432
|
+
const codeReviewBound = isBoundToHead(codeReview, input.headSha);
|
|
2433
|
+
const securityBound = isBoundToHead(security, input.headSha);
|
|
2434
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
2418
2435
|
return {
|
|
2419
2436
|
verdict: "pass",
|
|
2420
|
-
reason:
|
|
2437
|
+
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
2421
2438
|
waived: false
|
|
2422
2439
|
};
|
|
2423
2440
|
}
|
|
2424
2441
|
const problems = [];
|
|
2425
|
-
if (!codeReviewClean)
|
|
2442
|
+
if (!codeReviewClean) {
|
|
2426
2443
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2427
|
-
if (!
|
|
2444
|
+
} else if (!codeReviewBound) {
|
|
2445
|
+
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2446
|
+
}
|
|
2447
|
+
if (!securityClean) {
|
|
2428
2448
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2449
|
+
} else if (!securityBound) {
|
|
2450
|
+
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2451
|
+
}
|
|
2429
2452
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2430
2453
|
return {
|
|
2431
2454
|
verdict: "fail",
|
|
@@ -2370,15 +2370,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2370
2370
|
return { ok: true, expectedIssue };
|
|
2371
2371
|
}
|
|
2372
2372
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2373
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2374
|
+
function extractHeadSha(comment) {
|
|
2375
|
+
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2376
|
+
return m ? m[1].toLowerCase() : null;
|
|
2377
|
+
}
|
|
2373
2378
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2374
2379
|
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2375
2380
|
if (clearHits.length > 0) {
|
|
2376
2381
|
const latest = clearHits[clearHits.length - 1];
|
|
2377
2382
|
const m = latest.match(valuePattern);
|
|
2378
|
-
return {
|
|
2383
|
+
return {
|
|
2384
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2385
|
+
headSha: extractHeadSha(latest),
|
|
2386
|
+
danglingNote: null
|
|
2387
|
+
};
|
|
2379
2388
|
}
|
|
2380
2389
|
return {
|
|
2381
2390
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2391
|
+
headSha: null,
|
|
2382
2392
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2383
2393
|
};
|
|
2384
2394
|
}
|
|
@@ -2392,6 +2402,11 @@ function extractSecurityReviewVerdict(comments) {
|
|
|
2392
2402
|
function isReviewGateExemptBranch(branch) {
|
|
2393
2403
|
return branch.startsWith("plan/");
|
|
2394
2404
|
}
|
|
2405
|
+
function isBoundToHead(extraction, headSha) {
|
|
2406
|
+
if (!extraction.headSha)
|
|
2407
|
+
return false;
|
|
2408
|
+
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2409
|
+
}
|
|
2395
2410
|
function checkReviewGate(input) {
|
|
2396
2411
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2397
2412
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2414,18 +2429,26 @@ function checkReviewGate(input) {
|
|
|
2414
2429
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2415
2430
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2416
2431
|
const securityClean = security.value === "PASS";
|
|
2417
|
-
|
|
2432
|
+
const codeReviewBound = isBoundToHead(codeReview, input.headSha);
|
|
2433
|
+
const securityBound = isBoundToHead(security, input.headSha);
|
|
2434
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
2418
2435
|
return {
|
|
2419
2436
|
verdict: "pass",
|
|
2420
|
-
reason:
|
|
2437
|
+
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
2421
2438
|
waived: false
|
|
2422
2439
|
};
|
|
2423
2440
|
}
|
|
2424
2441
|
const problems = [];
|
|
2425
|
-
if (!codeReviewClean)
|
|
2442
|
+
if (!codeReviewClean) {
|
|
2426
2443
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2427
|
-
if (!
|
|
2444
|
+
} else if (!codeReviewBound) {
|
|
2445
|
+
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2446
|
+
}
|
|
2447
|
+
if (!securityClean) {
|
|
2428
2448
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2449
|
+
} else if (!securityBound) {
|
|
2450
|
+
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2451
|
+
}
|
|
2429
2452
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2430
2453
|
return {
|
|
2431
2454
|
verdict: "fail",
|
|
@@ -2368,15 +2368,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2368
2368
|
return { ok: true, expectedIssue };
|
|
2369
2369
|
}
|
|
2370
2370
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2371
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2372
|
+
function extractHeadSha(comment) {
|
|
2373
|
+
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2374
|
+
return m ? m[1].toLowerCase() : null;
|
|
2375
|
+
}
|
|
2371
2376
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2372
2377
|
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2373
2378
|
if (clearHits.length > 0) {
|
|
2374
2379
|
const latest = clearHits[clearHits.length - 1];
|
|
2375
2380
|
const m = latest.match(valuePattern);
|
|
2376
|
-
return {
|
|
2381
|
+
return {
|
|
2382
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2383
|
+
headSha: extractHeadSha(latest),
|
|
2384
|
+
danglingNote: null
|
|
2385
|
+
};
|
|
2377
2386
|
}
|
|
2378
2387
|
return {
|
|
2379
2388
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2389
|
+
headSha: null,
|
|
2380
2390
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2381
2391
|
};
|
|
2382
2392
|
}
|
|
@@ -2390,6 +2400,11 @@ function extractSecurityReviewVerdict(comments) {
|
|
|
2390
2400
|
function isReviewGateExemptBranch(branch) {
|
|
2391
2401
|
return branch.startsWith("plan/");
|
|
2392
2402
|
}
|
|
2403
|
+
function isBoundToHead(extraction, headSha) {
|
|
2404
|
+
if (!extraction.headSha)
|
|
2405
|
+
return false;
|
|
2406
|
+
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2407
|
+
}
|
|
2393
2408
|
function checkReviewGate(input) {
|
|
2394
2409
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2395
2410
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2412,18 +2427,26 @@ function checkReviewGate(input) {
|
|
|
2412
2427
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2413
2428
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2414
2429
|
const securityClean = security.value === "PASS";
|
|
2415
|
-
|
|
2430
|
+
const codeReviewBound = isBoundToHead(codeReview, input.headSha);
|
|
2431
|
+
const securityBound = isBoundToHead(security, input.headSha);
|
|
2432
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
2416
2433
|
return {
|
|
2417
2434
|
verdict: "pass",
|
|
2418
|
-
reason:
|
|
2435
|
+
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
2419
2436
|
waived: false
|
|
2420
2437
|
};
|
|
2421
2438
|
}
|
|
2422
2439
|
const problems = [];
|
|
2423
|
-
if (!codeReviewClean)
|
|
2440
|
+
if (!codeReviewClean) {
|
|
2424
2441
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2425
|
-
if (!
|
|
2442
|
+
} else if (!codeReviewBound) {
|
|
2443
|
+
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2444
|
+
}
|
|
2445
|
+
if (!securityClean) {
|
|
2426
2446
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2447
|
+
} else if (!securityBound) {
|
|
2448
|
+
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2449
|
+
}
|
|
2427
2450
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2428
2451
|
return {
|
|
2429
2452
|
verdict: "fail",
|
|
@@ -3133,7 +3156,8 @@ var VinayaConfigSchema = z.object({
|
|
|
3133
3156
|
briefSchema: BriefSchemaSchema.optional(),
|
|
3134
3157
|
managed: ManagedManifestSchema.optional(),
|
|
3135
3158
|
principals: z.array(z.string()).min(1).optional(),
|
|
3136
|
-
ci: z.object({ setup: z.string().min(1) }).optional()
|
|
3159
|
+
ci: z.object({ setup: z.string().min(1) }).optional(),
|
|
3160
|
+
blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional()
|
|
3137
3161
|
});
|
|
3138
3162
|
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
3139
3163
|
var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
|
|
@@ -3219,7 +3243,7 @@ function git(args) {
|
|
|
3219
3243
|
}
|
|
3220
3244
|
function fetchPr(prNumber) {
|
|
3221
3245
|
try {
|
|
3222
|
-
const out = execFileSync3("gh", ["pr", "view", String(prNumber), "--json", "number,comments,labels"], {
|
|
3246
|
+
const out = execFileSync3("gh", ["pr", "view", String(prNumber), "--json", "number,comments,labels,headRefOid"], {
|
|
3223
3247
|
encoding: "utf8",
|
|
3224
3248
|
stdio: ["ignore", "pipe", "pipe"]
|
|
3225
3249
|
});
|
|
@@ -3269,7 +3293,8 @@ function main() {
|
|
|
3269
3293
|
comments: pr.comments.map((c) => ({ body: c.body, author: c.author?.login ?? null })),
|
|
3270
3294
|
labels,
|
|
3271
3295
|
waiverLabelActor,
|
|
3272
|
-
principalAllowlist: resolvePrincipalAllowlist(loadTrustAnchorConfig())
|
|
3296
|
+
principalAllowlist: resolvePrincipalAllowlist(loadTrustAnchorConfig()),
|
|
3297
|
+
headSha: pr.headRefOid
|
|
3273
3298
|
});
|
|
3274
3299
|
if (result.verdict === "fail") {
|
|
3275
3300
|
emitCheckError({
|
|
@@ -2368,15 +2368,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2368
2368
|
return { ok: true, expectedIssue };
|
|
2369
2369
|
}
|
|
2370
2370
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2371
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2372
|
+
function extractHeadSha(comment) {
|
|
2373
|
+
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2374
|
+
return m ? m[1].toLowerCase() : null;
|
|
2375
|
+
}
|
|
2371
2376
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2372
2377
|
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2373
2378
|
if (clearHits.length > 0) {
|
|
2374
2379
|
const latest = clearHits[clearHits.length - 1];
|
|
2375
2380
|
const m = latest.match(valuePattern);
|
|
2376
|
-
return {
|
|
2381
|
+
return {
|
|
2382
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2383
|
+
headSha: extractHeadSha(latest),
|
|
2384
|
+
danglingNote: null
|
|
2385
|
+
};
|
|
2377
2386
|
}
|
|
2378
2387
|
return {
|
|
2379
2388
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2389
|
+
headSha: null,
|
|
2380
2390
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2381
2391
|
};
|
|
2382
2392
|
}
|
|
@@ -2390,6 +2400,11 @@ function extractSecurityReviewVerdict(comments) {
|
|
|
2390
2400
|
function isReviewGateExemptBranch(branch) {
|
|
2391
2401
|
return branch.startsWith("plan/");
|
|
2392
2402
|
}
|
|
2403
|
+
function isBoundToHead(extraction, headSha) {
|
|
2404
|
+
if (!extraction.headSha)
|
|
2405
|
+
return false;
|
|
2406
|
+
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2407
|
+
}
|
|
2393
2408
|
function checkReviewGate(input) {
|
|
2394
2409
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2395
2410
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2412,18 +2427,26 @@ function checkReviewGate(input) {
|
|
|
2412
2427
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2413
2428
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2414
2429
|
const securityClean = security.value === "PASS";
|
|
2415
|
-
|
|
2430
|
+
const codeReviewBound = isBoundToHead(codeReview, input.headSha);
|
|
2431
|
+
const securityBound = isBoundToHead(security, input.headSha);
|
|
2432
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
2416
2433
|
return {
|
|
2417
2434
|
verdict: "pass",
|
|
2418
|
-
reason:
|
|
2435
|
+
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
2419
2436
|
waived: false
|
|
2420
2437
|
};
|
|
2421
2438
|
}
|
|
2422
2439
|
const problems = [];
|
|
2423
|
-
if (!codeReviewClean)
|
|
2440
|
+
if (!codeReviewClean) {
|
|
2424
2441
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2425
|
-
if (!
|
|
2442
|
+
} else if (!codeReviewBound) {
|
|
2443
|
+
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2444
|
+
}
|
|
2445
|
+
if (!securityClean) {
|
|
2426
2446
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2447
|
+
} else if (!securityBound) {
|
|
2448
|
+
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2449
|
+
}
|
|
2427
2450
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2428
2451
|
return {
|
|
2429
2452
|
verdict: "fail",
|
|
@@ -2365,15 +2365,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
2365
2365
|
return { ok: true, expectedIssue };
|
|
2366
2366
|
}
|
|
2367
2367
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
2368
|
+
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
2369
|
+
function extractHeadSha(comment) {
|
|
2370
|
+
const m = comment.match(HEAD_SHA_PATTERN);
|
|
2371
|
+
return m ? m[1].toLowerCase() : null;
|
|
2372
|
+
}
|
|
2368
2373
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
2369
2374
|
const clearHits = comments.filter((c) => valuePattern.test(c));
|
|
2370
2375
|
if (clearHits.length > 0) {
|
|
2371
2376
|
const latest = clearHits[clearHits.length - 1];
|
|
2372
2377
|
const m = latest.match(valuePattern);
|
|
2373
|
-
return {
|
|
2378
|
+
return {
|
|
2379
|
+
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
2380
|
+
headSha: extractHeadSha(latest),
|
|
2381
|
+
danglingNote: null
|
|
2382
|
+
};
|
|
2374
2383
|
}
|
|
2375
2384
|
return {
|
|
2376
2385
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
2386
|
+
headSha: null,
|
|
2377
2387
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
2378
2388
|
};
|
|
2379
2389
|
}
|
|
@@ -2387,6 +2397,11 @@ function extractSecurityReviewVerdict(comments) {
|
|
|
2387
2397
|
function isReviewGateExemptBranch(branch) {
|
|
2388
2398
|
return branch.startsWith("plan/");
|
|
2389
2399
|
}
|
|
2400
|
+
function isBoundToHead(extraction, headSha) {
|
|
2401
|
+
if (!extraction.headSha)
|
|
2402
|
+
return false;
|
|
2403
|
+
return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
|
|
2404
|
+
}
|
|
2390
2405
|
function checkReviewGate(input) {
|
|
2391
2406
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2392
2407
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -2409,18 +2424,26 @@ function checkReviewGate(input) {
|
|
|
2409
2424
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2410
2425
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2411
2426
|
const securityClean = security.value === "PASS";
|
|
2412
|
-
|
|
2427
|
+
const codeReviewBound = isBoundToHead(codeReview, input.headSha);
|
|
2428
|
+
const securityBound = isBoundToHead(security, input.headSha);
|
|
2429
|
+
if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
|
|
2413
2430
|
return {
|
|
2414
2431
|
verdict: "pass",
|
|
2415
|
-
reason:
|
|
2432
|
+
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
|
|
2416
2433
|
waived: false
|
|
2417
2434
|
};
|
|
2418
2435
|
}
|
|
2419
2436
|
const problems = [];
|
|
2420
|
-
if (!codeReviewClean)
|
|
2437
|
+
if (!codeReviewClean) {
|
|
2421
2438
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2422
|
-
if (!
|
|
2439
|
+
} else if (!codeReviewBound) {
|
|
2440
|
+
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2441
|
+
}
|
|
2442
|
+
if (!securityClean) {
|
|
2423
2443
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2444
|
+
} else if (!securityBound) {
|
|
2445
|
+
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
2446
|
+
}
|
|
2424
2447
|
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2425
2448
|
return {
|
|
2426
2449
|
verdict: "fail",
|