@attalabs/vinaya 0.12.0 → 0.17.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.
Files changed (128) hide show
  1. package/README.md +33 -3
  2. package/aeg-root/contracts/planner-brief.md +1 -1
  3. package/aeg-root/enforcement.md +5 -5
  4. package/aeg-root/roles/developer.md +1 -0
  5. package/aeg-root/roles/planner.md +2 -2
  6. package/aeg-root/roles/reviewer.md +17 -4
  7. package/aeg-root/roles/security.md +16 -5
  8. package/aeg-root/skills/aeg/SKILL.md +1 -1
  9. package/aeg-root/state-machine.md +2 -2
  10. package/aeg-root/templates/pr-report-template.md +2 -0
  11. package/aeg-root/tranche-model.md +1 -1
  12. package/dist/checks/bin/check-body-bare-digits.js +3545 -0
  13. package/dist/checks/bin/check-branch-topology.js +99 -10
  14. package/dist/checks/bin/check-brief-shape.js +99 -10
  15. package/dist/checks/bin/check-closes-n.js +99 -10
  16. package/dist/checks/bin/check-coherence.js +106 -12
  17. package/dist/checks/bin/check-dead-branch-push.js +99 -10
  18. package/dist/checks/bin/check-dispatch-readiness.js +106 -12
  19. package/dist/checks/bin/check-doc-coverage-push.js +106 -12
  20. package/dist/checks/bin/check-doc-coverage.js +109 -16
  21. package/dist/checks/bin/check-evidence-fresh.js +99 -10
  22. package/dist/checks/bin/check-first-push-dispatch.js +106 -12
  23. package/dist/checks/bin/check-issue-assignment.js +99 -10
  24. package/dist/checks/bin/check-no-disk-state.js +99 -10
  25. package/dist/checks/bin/check-reader-resolvable-prose.js +99 -10
  26. package/dist/checks/bin/check-registry-gates.js +99 -10
  27. package/dist/checks/bin/check-review-gate.js +109 -25
  28. package/dist/checks/bin/check-single-plan-pr.js +99 -10
  29. package/dist/checks/bin/check-test-plan.js +99 -10
  30. package/dist/index.js +1325 -236
  31. package/package.json +1 -1
  32. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  33. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  34. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  35. package/studio-standalone/apps/vinaya-studio/web/.next/required-server-files.json +1 -1
  36. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  37. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  38. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  39. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  40. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  41. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  42. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  43. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  44. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  45. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  46. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  47. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  48. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  49. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js +1 -1
  50. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  51. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  52. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js +1 -1
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js +1 -1
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__0e95dv5._.js +1 -1
  75. 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
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__029-1ch._.js +1 -1
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0gw1_g2._.js +1 -1
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0q56ohl._.js +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0rr_lq7._.js +1 -1
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0zfjc2o._.js +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__19jyp3m._.js +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1_ojzp1._.js +1 -1
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1wsl6ro._.js → [root-of-the-server]__1ccgndq._.js} +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1dfzjrd._.js +1 -1
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.js +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0-n77dx._.js +1 -1
  89. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_00yjcv1._.js +1 -1
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_01605g7._.js +1 -1
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0o54suy._.js +1 -1
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_1u3abhy._.js → _0x999pb._.js} +2 -2
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_0ndy563._.js → _1-6rzip._.js} +2 -2
  95. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_087xa25._.js → _1_talrs._.js} +2 -2
  97. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_11vnfnm._.js → _1ar5uqe._.js} +2 -2
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_02drwa_._.js → _1c9b9j-._.js} +2 -2
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1gw6vc9._.js +1 -1
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_app_studio_18kqbvs._.js +1 -1
  102. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_app_studio_projects_[name]_tranches_[slug]_20yap_f._.js +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  104. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_animate_components_index_ts_186fje1._.js +1 -1
  105. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_basic_components_index_ts_0xk1vda._.js +1 -1
  106. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  107. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  108. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  109. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  110. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1mexqnauhtbtp.js → 0_jnwbarv-cqk.js} +1 -1
  111. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2ieu4oi-pp_bz.js → 0l_vubs1ephe5.js} +1 -1
  112. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0ojfbn3yvy-8_.js +1 -0
  113. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1jx5_q1g9l-cm.js → 0phqfqwbnt_ey.js} +1 -1
  114. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0qcu17x3h94a9.js → 17r0szvqeeyhx.js} +1 -1
  115. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0ve1-nz-mu_lr.js → 18x3rbqzeutpz.js} +1 -1
  116. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{22y4d5yrovcv-.js → 1bqpfvromtwn1.js} +1 -1
  117. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{39w6jbw3rleez.js → 24ojyzmt_fkg3.js} +1 -1
  118. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/27sqffqm2wbxx.css +1 -0
  119. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0tmcnl-z8yt2_.js → 30lyvn0yzwdiz.js} +1 -1
  120. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1khy4oilrab6g.js → 3m0_gk59s-rbx.js} +1 -1
  121. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1eo32v1tbjy7c.js → 40x7f5hm7a5tk.js} +1 -1
  122. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0-08g_jjalnd9.js → 453zhecht_m1q.js} +1 -1
  123. package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
  124. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0qu0xgvifbacu.css +0 -1
  125. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1t7yy_s34022p.js +0 -1
  126. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{ZHVE9Y9G8XKR91g27SgVX → p5sWGp4ANwORiaX4Kk6U2}/_buildManifest.js +0 -0
  127. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{ZHVE9Y9G8XKR91g27SgVX → p5sWGp4ANwORiaX4Kk6U2}/_clientMiddlewareManifest.js +0 -0
  128. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{ZHVE9Y9G8XKR91g27SgVX → p5sWGp4ANwORiaX4Kk6U2}/_ssgManifest.js +0 -0
@@ -6,7 +6,27 @@ import { existsSync, readFileSync } from "node:fs";
6
6
  // ../../packages/aeg-forge-state/src/strip-code.ts
7
7
  function maskCode(body) {
8
8
  const fill = (line) => " ".repeat(line.length);
9
- return maskIndentedCode(maskFencedCode(body, fill), fill).replace(/(`+)[^\n]*?\1/g, (m) => " ".repeat(m.length));
9
+ const blocksMasked = maskIndentedCode(maskFencedCode(body, fill), fill);
10
+ return blocksMasked.split(`
11
+ `).map((line) => replaceInlineSpans(line, (span) => " ".repeat(span.length))).join(`
12
+ `);
13
+ }
14
+ var DETAILS_OPEN = /<details\b[^>]*>/gi;
15
+ var DETAILS_CLOSE = /<\/details\s*>/gi;
16
+ function maskDetailsBlocks(body) {
17
+ const fill = (line) => " ".repeat(line.length);
18
+ const lines = body.split(`
19
+ `);
20
+ let depth = 0;
21
+ const out = lines.map((line) => {
22
+ const opens = line.match(DETAILS_OPEN)?.length ?? 0;
23
+ const closes = line.match(DETAILS_CLOSE)?.length ?? 0;
24
+ const maskThisLine = depth > 0 || opens > 0;
25
+ depth = Math.max(0, depth + opens - closes);
26
+ return maskThisLine ? fill(line) : line;
27
+ });
28
+ return out.join(`
29
+ `);
10
30
  }
11
31
  function stripCode(body, options = {}) {
12
32
  const normalised = body.replace(/\r\n?/g, `
@@ -15,7 +35,50 @@ function stripCode(body, options = {}) {
15
35
  const blocksStripped = maskIndentedCode(maskFencedCode(normalised, blank), blank);
16
36
  if (options.inlineSpans === "keep")
17
37
  return blocksStripped;
18
- return blocksStripped.replace(/(`+)[^\n]*?\1/g, "");
38
+ return blocksStripped.split(`
39
+ `).map((line) => replaceInlineSpans(line, () => "")).join(`
40
+ `);
41
+ }
42
+ function replaceInlineSpans(line, replace) {
43
+ let result = "";
44
+ let i = 0;
45
+ const runLengthAt = (at) => {
46
+ let j = at;
47
+ while (j < line.length && line[j] === "`")
48
+ j++;
49
+ return j - at;
50
+ };
51
+ while (i < line.length) {
52
+ if (line[i] !== "`") {
53
+ result += line[i];
54
+ i++;
55
+ continue;
56
+ }
57
+ const openLen = runLengthAt(i);
58
+ const openEnd = i + openLen;
59
+ let j = openEnd;
60
+ let closeEnd = -1;
61
+ while (j < line.length) {
62
+ if (line[j] !== "`") {
63
+ j++;
64
+ continue;
65
+ }
66
+ const runLen = runLengthAt(j);
67
+ if (runLen === openLen) {
68
+ closeEnd = j + runLen;
69
+ break;
70
+ }
71
+ j += runLen;
72
+ }
73
+ if (closeEnd === -1) {
74
+ result += line.slice(i, openEnd);
75
+ i = openEnd;
76
+ } else {
77
+ result += replace(line.slice(i, closeEnd));
78
+ i = closeEnd;
79
+ }
80
+ }
81
+ return result;
19
82
  }
20
83
  var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
21
84
  var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
@@ -1754,9 +1817,10 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
1754
1817
  return out;
1755
1818
  }
1756
1819
  // ../../packages/aeg-core/src/pr-tier.ts
1820
+ var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
1757
1821
  function readTierFromPrBody(prBody) {
1758
1822
  const searchIn = anchoredRegion(prBody, "TIER") ?? prBody;
1759
- const m = searchIn.match(/(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i);
1823
+ const m = searchIn.match(TIER_FIELD);
1760
1824
  if (!m)
1761
1825
  return null;
1762
1826
  const t = Number(m[4]);
@@ -2369,15 +2433,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2369
2433
  return { ok: true, expectedIssue };
2370
2434
  }
2371
2435
  // ../../packages/aeg-core/src/verdict-extraction.ts
2436
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2437
+ function extractHeadSha(comment) {
2438
+ const m = comment.match(HEAD_SHA_PATTERN);
2439
+ return m ? m[1].toLowerCase() : null;
2440
+ }
2372
2441
  function extractVerdict(comments, valuePattern, missingLabel) {
2373
2442
  const clearHits = comments.filter((c) => valuePattern.test(c));
2374
2443
  if (clearHits.length > 0) {
2375
2444
  const latest = clearHits[clearHits.length - 1];
2376
2445
  const m = latest.match(valuePattern);
2377
- return { value: m[1].toUpperCase().replace(/[_-]/g, " "), danglingNote: null };
2446
+ return {
2447
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
2448
+ headSha: extractHeadSha(latest),
2449
+ danglingNote: null
2450
+ };
2378
2451
  }
2379
2452
  return {
2380
2453
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2454
+ headSha: null,
2381
2455
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
2382
2456
  };
2383
2457
  }
@@ -2388,8 +2462,10 @@ function extractSecurityReviewVerdict(comments) {
2388
2462
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2389
2463
  }
2390
2464
  // ../../packages/aeg-core/src/review-gate.ts
2391
- function isReviewGateExemptBranch(branch) {
2392
- return branch.startsWith("plan/");
2465
+ function isBoundToHead(extraction, headSha) {
2466
+ if (!extraction.headSha)
2467
+ return false;
2468
+ return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2393
2469
  }
2394
2470
  function checkReviewGate(input) {
2395
2471
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
@@ -2413,18 +2489,26 @@ function checkReviewGate(input) {
2413
2489
  const security = extractSecurityReviewVerdict(verifiedBodies);
2414
2490
  const codeReviewClean = codeReview.value === "APPROVE";
2415
2491
  const securityClean = security.value === "PASS";
2416
- if (codeReviewClean && securityClean) {
2492
+ const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2493
+ const securityBound = isBoundToHead(security, input.headSha);
2494
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
2417
2495
  return {
2418
2496
  verdict: "pass",
2419
- reason: "code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS.",
2497
+ reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
2420
2498
  waived: false
2421
2499
  };
2422
2500
  }
2423
2501
  const problems = [];
2424
- if (!codeReviewClean)
2502
+ if (!codeReviewClean) {
2425
2503
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
2426
- if (!securityClean)
2504
+ } else if (!codeReviewBound) {
2505
+ problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2506
+ }
2507
+ if (!securityClean) {
2427
2508
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
2509
+ } else if (!securityBound) {
2510
+ problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2511
+ }
2428
2512
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2429
2513
  return {
2430
2514
  verdict: "fail",
@@ -2432,6 +2516,11 @@ function checkReviewGate(input) {
2432
2516
  waived: false
2433
2517
  };
2434
2518
  }
2519
+ var CHANGESET_RELEASE_BRANCH = "changeset-release/main";
2520
+ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
2521
+ function isChangesetsReleasePr(branch, author, expectedAuthor) {
2522
+ return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
2523
+ }
2435
2524
  // ../../packages/aeg-core/src/markdown-table.ts
2436
2525
  function splitRow(line) {
2437
2526
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -3149,7 +3238,9 @@ var VinayaConfigSchema = z.object({
3149
3238
  briefSchema: BriefSchemaSchema.optional(),
3150
3239
  managed: ManagedManifestSchema.optional(),
3151
3240
  principals: z.array(z.string()).min(1).optional(),
3152
- ci: z.object({ setup: z.string().min(1) }).optional()
3241
+ releaseActor: z.string().min(1).optional(),
3242
+ ci: z.object({ setup: z.string().min(1) }).optional(),
3243
+ blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional()
3153
3244
  });
3154
3245
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
3155
3246
  var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
@@ -3157,6 +3248,9 @@ var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
3157
3248
  function resolvePrincipalAllowlist(config) {
3158
3249
  return config?.principals ?? PRINCIPAL_ALLOWLIST;
3159
3250
  }
3251
+ function resolveReleaseActor(config) {
3252
+ return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
3253
+ }
3160
3254
  function trustAnchorRepo() {
3161
3255
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
3162
3256
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
@@ -3199,7 +3293,7 @@ function isMissingFileError(err) {
3199
3293
  return /\b404\b|not found/i.test(haystack);
3200
3294
  }
3201
3295
  function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3202
- const warn = (why) => process.stdout.write(`⚠ could not read \`principals\` from the default branch (${why}) — falling back to vinaya's built-in principal allowlist.
3296
+ const warn = (why) => process.stdout.write(`⚠ could not read the trust-anchor config (\`principals\`/\`releaseActor\`) from the default branch (${why}) — falling back to vinaya's built-in defaults.
3203
3297
  `);
3204
3298
  let base64;
3205
3299
  try {
@@ -1,13 +1,32 @@
1
1
  #!/usr/bin/env node
2
- // @bun
3
2
 
4
3
  // src/checks/bin/check-doc-coverage.ts
5
- import { execFileSync as execFileSync4 } from "child_process";
6
- import { existsSync, readFileSync } from "fs";
4
+ import { execFileSync as execFileSync4 } from "node:child_process";
5
+ import { existsSync, readFileSync } from "node:fs";
7
6
  // ../../packages/aeg-forge-state/src/strip-code.ts
8
7
  function maskCode(body) {
9
8
  const fill = (line) => " ".repeat(line.length);
10
- return maskIndentedCode(maskFencedCode(body, fill), fill).replace(/(`+)[^\n]*?\1/g, (m) => " ".repeat(m.length));
9
+ const blocksMasked = maskIndentedCode(maskFencedCode(body, fill), fill);
10
+ return blocksMasked.split(`
11
+ `).map((line) => replaceInlineSpans(line, (span) => " ".repeat(span.length))).join(`
12
+ `);
13
+ }
14
+ var DETAILS_OPEN = /<details\b[^>]*>/gi;
15
+ var DETAILS_CLOSE = /<\/details\s*>/gi;
16
+ function maskDetailsBlocks(body) {
17
+ const fill = (line) => " ".repeat(line.length);
18
+ const lines = body.split(`
19
+ `);
20
+ let depth = 0;
21
+ const out = lines.map((line) => {
22
+ const opens = line.match(DETAILS_OPEN)?.length ?? 0;
23
+ const closes = line.match(DETAILS_CLOSE)?.length ?? 0;
24
+ const maskThisLine = depth > 0 || opens > 0;
25
+ depth = Math.max(0, depth + opens - closes);
26
+ return maskThisLine ? fill(line) : line;
27
+ });
28
+ return out.join(`
29
+ `);
11
30
  }
12
31
  function stripCode(body, options = {}) {
13
32
  const normalised = body.replace(/\r\n?/g, `
@@ -16,7 +35,50 @@ function stripCode(body, options = {}) {
16
35
  const blocksStripped = maskIndentedCode(maskFencedCode(normalised, blank), blank);
17
36
  if (options.inlineSpans === "keep")
18
37
  return blocksStripped;
19
- return blocksStripped.replace(/(`+)[^\n]*?\1/g, "");
38
+ return blocksStripped.split(`
39
+ `).map((line) => replaceInlineSpans(line, () => "")).join(`
40
+ `);
41
+ }
42
+ function replaceInlineSpans(line, replace) {
43
+ let result = "";
44
+ let i = 0;
45
+ const runLengthAt = (at) => {
46
+ let j = at;
47
+ while (j < line.length && line[j] === "`")
48
+ j++;
49
+ return j - at;
50
+ };
51
+ while (i < line.length) {
52
+ if (line[i] !== "`") {
53
+ result += line[i];
54
+ i++;
55
+ continue;
56
+ }
57
+ const openLen = runLengthAt(i);
58
+ const openEnd = i + openLen;
59
+ let j = openEnd;
60
+ let closeEnd = -1;
61
+ while (j < line.length) {
62
+ if (line[j] !== "`") {
63
+ j++;
64
+ continue;
65
+ }
66
+ const runLen = runLengthAt(j);
67
+ if (runLen === openLen) {
68
+ closeEnd = j + runLen;
69
+ break;
70
+ }
71
+ j += runLen;
72
+ }
73
+ if (closeEnd === -1) {
74
+ result += line.slice(i, openEnd);
75
+ i = openEnd;
76
+ } else {
77
+ result += replace(line.slice(i, closeEnd));
78
+ i = closeEnd;
79
+ }
80
+ }
81
+ return result;
20
82
  }
21
83
  var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
22
84
  var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
@@ -1755,9 +1817,10 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
1755
1817
  return out;
1756
1818
  }
1757
1819
  // ../../packages/aeg-core/src/pr-tier.ts
1820
+ var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
1758
1821
  function readTierFromPrBody(prBody) {
1759
1822
  const searchIn = anchoredRegion(prBody, "TIER") ?? prBody;
1760
- const m = searchIn.match(/(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i);
1823
+ const m = searchIn.match(TIER_FIELD);
1761
1824
  if (!m)
1762
1825
  return null;
1763
1826
  const t = Number(m[4]);
@@ -2370,15 +2433,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2370
2433
  return { ok: true, expectedIssue };
2371
2434
  }
2372
2435
  // ../../packages/aeg-core/src/verdict-extraction.ts
2436
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2437
+ function extractHeadSha(comment) {
2438
+ const m = comment.match(HEAD_SHA_PATTERN);
2439
+ return m ? m[1].toLowerCase() : null;
2440
+ }
2373
2441
  function extractVerdict(comments, valuePattern, missingLabel) {
2374
2442
  const clearHits = comments.filter((c) => valuePattern.test(c));
2375
2443
  if (clearHits.length > 0) {
2376
2444
  const latest = clearHits[clearHits.length - 1];
2377
2445
  const m = latest.match(valuePattern);
2378
- return { value: m[1].toUpperCase().replace(/[_-]/g, " "), danglingNote: null };
2446
+ return {
2447
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
2448
+ headSha: extractHeadSha(latest),
2449
+ danglingNote: null
2450
+ };
2379
2451
  }
2380
2452
  return {
2381
2453
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2454
+ headSha: null,
2382
2455
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
2383
2456
  };
2384
2457
  }
@@ -2389,8 +2462,10 @@ function extractSecurityReviewVerdict(comments) {
2389
2462
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2390
2463
  }
2391
2464
  // ../../packages/aeg-core/src/review-gate.ts
2392
- function isReviewGateExemptBranch(branch) {
2393
- return branch.startsWith("plan/");
2465
+ function isBoundToHead(extraction, headSha) {
2466
+ if (!extraction.headSha)
2467
+ return false;
2468
+ return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2394
2469
  }
2395
2470
  function checkReviewGate(input) {
2396
2471
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
@@ -2414,18 +2489,26 @@ function checkReviewGate(input) {
2414
2489
  const security = extractSecurityReviewVerdict(verifiedBodies);
2415
2490
  const codeReviewClean = codeReview.value === "APPROVE";
2416
2491
  const securityClean = security.value === "PASS";
2417
- if (codeReviewClean && securityClean) {
2492
+ const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2493
+ const securityBound = isBoundToHead(security, input.headSha);
2494
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
2418
2495
  return {
2419
2496
  verdict: "pass",
2420
- reason: "code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS.",
2497
+ reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
2421
2498
  waived: false
2422
2499
  };
2423
2500
  }
2424
2501
  const problems = [];
2425
- if (!codeReviewClean)
2502
+ if (!codeReviewClean) {
2426
2503
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
2427
- if (!securityClean)
2504
+ } else if (!codeReviewBound) {
2505
+ problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2506
+ }
2507
+ if (!securityClean) {
2428
2508
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
2509
+ } else if (!securityBound) {
2510
+ problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2511
+ }
2429
2512
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2430
2513
  return {
2431
2514
  verdict: "fail",
@@ -2433,6 +2516,11 @@ function checkReviewGate(input) {
2433
2516
  waived: false
2434
2517
  };
2435
2518
  }
2519
+ var CHANGESET_RELEASE_BRANCH = "changeset-release/main";
2520
+ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
2521
+ function isChangesetsReleasePr(branch, author, expectedAuthor) {
2522
+ return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
2523
+ }
2436
2524
  // ../../packages/aeg-core/src/markdown-table.ts
2437
2525
  function splitRow(line) {
2438
2526
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -3150,7 +3238,9 @@ var VinayaConfigSchema = z.object({
3150
3238
  briefSchema: BriefSchemaSchema.optional(),
3151
3239
  managed: ManagedManifestSchema.optional(),
3152
3240
  principals: z.array(z.string()).min(1).optional(),
3153
- ci: z.object({ setup: z.string().min(1) }).optional()
3241
+ releaseActor: z.string().min(1).optional(),
3242
+ ci: z.object({ setup: z.string().min(1) }).optional(),
3243
+ blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional()
3154
3244
  });
3155
3245
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
3156
3246
  var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
@@ -3158,6 +3248,9 @@ var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
3158
3248
  function resolvePrincipalAllowlist(config) {
3159
3249
  return config?.principals ?? PRINCIPAL_ALLOWLIST;
3160
3250
  }
3251
+ function resolveReleaseActor(config) {
3252
+ return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
3253
+ }
3161
3254
  function trustAnchorRepo() {
3162
3255
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
3163
3256
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
@@ -3200,7 +3293,7 @@ function isMissingFileError(err) {
3200
3293
  return /\b404\b|not found/i.test(haystack);
3201
3294
  }
3202
3295
  function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3203
- const warn = (why) => process.stdout.write(`⚠ could not read \`principals\` from the default branch (${why}) — falling back to vinaya's built-in principal allowlist.
3296
+ const warn = (why) => process.stdout.write(`⚠ could not read the trust-anchor config (\`principals\`/\`releaseActor\`) from the default branch (${why}) — falling back to vinaya's built-in defaults.
3204
3297
  `);
3205
3298
  let base64;
3206
3299
  try {
@@ -3310,7 +3403,7 @@ function main() {
3310
3403
  check: CHECK_NAME,
3311
3404
  severity: "error",
3312
3405
  message,
3313
- agent_recovery_prompt: "Read the doc pointer named in the finding above, apply the change your code edit implies, and commit both " + "files in this PR. If the doc genuinely does not need updating, ask a principal to apply the `vinaya/waiver:docs` " + "label rather than editing around this finding \u2014 you cannot self-serve that waiver."
3406
+ agent_recovery_prompt: "Read the doc pointer named in the finding above, apply the change your code edit implies, and commit both " + "files in this PR. If the doc genuinely does not need updating, ask a principal to apply the `vinaya/waiver:docs` " + "label rather than editing around this finding you cannot self-serve that waiver."
3314
3407
  });
3315
3408
  }
3316
3409
  process.exit(1);
@@ -5,7 +5,27 @@ import { execFileSync as execFileSync2 } from "node:child_process";
5
5
  // ../../packages/aeg-forge-state/src/strip-code.ts
6
6
  function maskCode(body) {
7
7
  const fill = (line) => " ".repeat(line.length);
8
- return maskIndentedCode(maskFencedCode(body, fill), fill).replace(/(`+)[^\n]*?\1/g, (m) => " ".repeat(m.length));
8
+ const blocksMasked = maskIndentedCode(maskFencedCode(body, fill), fill);
9
+ return blocksMasked.split(`
10
+ `).map((line) => replaceInlineSpans(line, (span) => " ".repeat(span.length))).join(`
11
+ `);
12
+ }
13
+ var DETAILS_OPEN = /<details\b[^>]*>/gi;
14
+ var DETAILS_CLOSE = /<\/details\s*>/gi;
15
+ function maskDetailsBlocks(body) {
16
+ const fill = (line) => " ".repeat(line.length);
17
+ const lines = body.split(`
18
+ `);
19
+ let depth = 0;
20
+ const out = lines.map((line) => {
21
+ const opens = line.match(DETAILS_OPEN)?.length ?? 0;
22
+ const closes = line.match(DETAILS_CLOSE)?.length ?? 0;
23
+ const maskThisLine = depth > 0 || opens > 0;
24
+ depth = Math.max(0, depth + opens - closes);
25
+ return maskThisLine ? fill(line) : line;
26
+ });
27
+ return out.join(`
28
+ `);
9
29
  }
10
30
  function stripCode(body, options = {}) {
11
31
  const normalised = body.replace(/\r\n?/g, `
@@ -14,7 +34,50 @@ function stripCode(body, options = {}) {
14
34
  const blocksStripped = maskIndentedCode(maskFencedCode(normalised, blank), blank);
15
35
  if (options.inlineSpans === "keep")
16
36
  return blocksStripped;
17
- return blocksStripped.replace(/(`+)[^\n]*?\1/g, "");
37
+ return blocksStripped.split(`
38
+ `).map((line) => replaceInlineSpans(line, () => "")).join(`
39
+ `);
40
+ }
41
+ function replaceInlineSpans(line, replace) {
42
+ let result = "";
43
+ let i = 0;
44
+ const runLengthAt = (at) => {
45
+ let j = at;
46
+ while (j < line.length && line[j] === "`")
47
+ j++;
48
+ return j - at;
49
+ };
50
+ while (i < line.length) {
51
+ if (line[i] !== "`") {
52
+ result += line[i];
53
+ i++;
54
+ continue;
55
+ }
56
+ const openLen = runLengthAt(i);
57
+ const openEnd = i + openLen;
58
+ let j = openEnd;
59
+ let closeEnd = -1;
60
+ while (j < line.length) {
61
+ if (line[j] !== "`") {
62
+ j++;
63
+ continue;
64
+ }
65
+ const runLen = runLengthAt(j);
66
+ if (runLen === openLen) {
67
+ closeEnd = j + runLen;
68
+ break;
69
+ }
70
+ j += runLen;
71
+ }
72
+ if (closeEnd === -1) {
73
+ result += line.slice(i, openEnd);
74
+ i = openEnd;
75
+ } else {
76
+ result += replace(line.slice(i, closeEnd));
77
+ i = closeEnd;
78
+ }
79
+ }
80
+ return result;
18
81
  }
19
82
  var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
20
83
  var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
@@ -1753,9 +1816,10 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
1753
1816
  return out;
1754
1817
  }
1755
1818
  // ../../packages/aeg-core/src/pr-tier.ts
1819
+ var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
1756
1820
  function readTierFromPrBody(prBody) {
1757
1821
  const searchIn = anchoredRegion(prBody, "TIER") ?? prBody;
1758
- const m = searchIn.match(/(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i);
1822
+ const m = searchIn.match(TIER_FIELD);
1759
1823
  if (!m)
1760
1824
  return null;
1761
1825
  const t = Number(m[4]);
@@ -2368,15 +2432,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2368
2432
  return { ok: true, expectedIssue };
2369
2433
  }
2370
2434
  // ../../packages/aeg-core/src/verdict-extraction.ts
2435
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2436
+ function extractHeadSha(comment) {
2437
+ const m = comment.match(HEAD_SHA_PATTERN);
2438
+ return m ? m[1].toLowerCase() : null;
2439
+ }
2371
2440
  function extractVerdict(comments, valuePattern, missingLabel) {
2372
2441
  const clearHits = comments.filter((c) => valuePattern.test(c));
2373
2442
  if (clearHits.length > 0) {
2374
2443
  const latest = clearHits[clearHits.length - 1];
2375
2444
  const m = latest.match(valuePattern);
2376
- return { value: m[1].toUpperCase().replace(/[_-]/g, " "), danglingNote: null };
2445
+ return {
2446
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
2447
+ headSha: extractHeadSha(latest),
2448
+ danglingNote: null
2449
+ };
2377
2450
  }
2378
2451
  return {
2379
2452
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2453
+ headSha: null,
2380
2454
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
2381
2455
  };
2382
2456
  }
@@ -2387,8 +2461,10 @@ function extractSecurityReviewVerdict(comments) {
2387
2461
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2388
2462
  }
2389
2463
  // ../../packages/aeg-core/src/review-gate.ts
2390
- function isReviewGateExemptBranch(branch) {
2391
- return branch.startsWith("plan/");
2464
+ function isBoundToHead(extraction, headSha) {
2465
+ if (!extraction.headSha)
2466
+ return false;
2467
+ return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2392
2468
  }
2393
2469
  function checkReviewGate(input) {
2394
2470
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
@@ -2412,18 +2488,26 @@ function checkReviewGate(input) {
2412
2488
  const security = extractSecurityReviewVerdict(verifiedBodies);
2413
2489
  const codeReviewClean = codeReview.value === "APPROVE";
2414
2490
  const securityClean = security.value === "PASS";
2415
- if (codeReviewClean && securityClean) {
2491
+ const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2492
+ const securityBound = isBoundToHead(security, input.headSha);
2493
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
2416
2494
  return {
2417
2495
  verdict: "pass",
2418
- reason: "code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS.",
2496
+ reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
2419
2497
  waived: false
2420
2498
  };
2421
2499
  }
2422
2500
  const problems = [];
2423
- if (!codeReviewClean)
2501
+ if (!codeReviewClean) {
2424
2502
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
2425
- if (!securityClean)
2503
+ } else if (!codeReviewBound) {
2504
+ problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2505
+ }
2506
+ if (!securityClean) {
2426
2507
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
2508
+ } else if (!securityBound) {
2509
+ problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2510
+ }
2427
2511
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2428
2512
  return {
2429
2513
  verdict: "fail",
@@ -2431,6 +2515,11 @@ function checkReviewGate(input) {
2431
2515
  waived: false
2432
2516
  };
2433
2517
  }
2518
+ var CHANGESET_RELEASE_BRANCH = "changeset-release/main";
2519
+ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
2520
+ function isChangesetsReleasePr(branch, author, expectedAuthor) {
2521
+ return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
2522
+ }
2434
2523
  // ../../packages/aeg-core/src/markdown-table.ts
2435
2524
  function splitRow(line) {
2436
2525
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");