@attalabs/vinaya 0.16.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 (121) hide show
  1. package/README.md +8 -2
  2. package/aeg-root/contracts/planner-brief.md +1 -1
  3. package/aeg-root/enforcement.md +2 -2
  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 +1 -1
  7. package/aeg-root/skills/aeg/SKILL.md +1 -1
  8. package/aeg-root/state-machine.md +1 -1
  9. package/aeg-root/templates/pr-report-template.md +2 -0
  10. package/aeg-root/tranche-model.md +1 -1
  11. package/dist/checks/bin/check-body-bare-digits.js +3545 -0
  12. package/dist/checks/bin/check-branch-topology.js +72 -6
  13. package/dist/checks/bin/check-brief-shape.js +72 -6
  14. package/dist/checks/bin/check-closes-n.js +72 -6
  15. package/dist/checks/bin/check-coherence.js +77 -7
  16. package/dist/checks/bin/check-dead-branch-push.js +72 -6
  17. package/dist/checks/bin/check-dispatch-readiness.js +77 -7
  18. package/dist/checks/bin/check-doc-coverage-push.js +77 -7
  19. package/dist/checks/bin/check-doc-coverage.js +80 -11
  20. package/dist/checks/bin/check-evidence-fresh.js +72 -6
  21. package/dist/checks/bin/check-first-push-dispatch.js +77 -7
  22. package/dist/checks/bin/check-issue-assignment.js +72 -6
  23. package/dist/checks/bin/check-no-disk-state.js +72 -6
  24. package/dist/checks/bin/check-reader-resolvable-prose.js +72 -6
  25. package/dist/checks/bin/check-registry-gates.js +72 -6
  26. package/dist/checks/bin/check-review-gate.js +77 -18
  27. package/dist/checks/bin/check-single-plan-pr.js +72 -6
  28. package/dist/checks/bin/check-test-plan.js +72 -6
  29. package/dist/index.js +591 -206
  30. package/package.json +1 -1
  31. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  32. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  33. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  34. package/studio-standalone/apps/vinaya-studio/web/.next/required-server-files.json +1 -1
  35. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  36. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  37. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  38. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  39. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  40. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  41. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  42. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  43. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  44. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  45. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  46. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  47. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js +1 -1
  48. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  49. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  50. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  51. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js +1 -1
  52. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js +1 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  72. 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
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__029-1ch._.js +1 -1
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0gw1_g2._.js +1 -1
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0q56ohl._.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0rr_lq7._.js +1 -1
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__19jyp3m._.js +1 -1
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1_ojzp1._.js +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1qpl4m_._.js → [root-of-the-server]__1ccgndq._.js} +1 -1
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1dfzjrd._.js +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.js +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0-n77dx._.js +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_00yjcv1._.js +1 -1
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_01605g7._.js +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0o54suy._.js +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
  89. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_1u3abhy._.js → _0x999pb._.js} +2 -2
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_0ndy563._.js → _1-6rzip._.js} +2 -2
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_087xa25._.js → _1_talrs._.js} +2 -2
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_11vnfnm._.js → _1ar5uqe._.js} +2 -2
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_02drwa_._.js → _1c9b9j-._.js} +2 -2
  95. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1gw6vc9._.js +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_app_studio_18kqbvs._.js +1 -1
  97. 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
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_animate_components_index_ts_186fje1._.js +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_basic_components_index_ts_0xk1vda._.js +1 -1
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  102. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  104. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  105. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1mexqnauhtbtp.js → 0_jnwbarv-cqk.js} +1 -1
  106. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0ek6qlqhokys0.js → 0l_vubs1ephe5.js} +1 -1
  107. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0ojfbn3yvy-8_.js +1 -0
  108. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1jx5_q1g9l-cm.js → 0phqfqwbnt_ey.js} +1 -1
  109. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0qcu17x3h94a9.js → 17r0szvqeeyhx.js} +1 -1
  110. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0ve1-nz-mu_lr.js → 18x3rbqzeutpz.js} +1 -1
  111. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{22y4d5yrovcv-.js → 1bqpfvromtwn1.js} +1 -1
  112. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{39w6jbw3rleez.js → 24ojyzmt_fkg3.js} +1 -1
  113. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0tmcnl-z8yt2_.js → 30lyvn0yzwdiz.js} +1 -1
  114. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0fm4p9os2lsom.js → 3m0_gk59s-rbx.js} +1 -1
  115. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0ywkq74falx-x.js → 40x7f5hm7a5tk.js} +1 -1
  116. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0-08g_jjalnd9.js → 453zhecht_m1q.js} +1 -1
  117. package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
  118. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1s9ixp5nhnevp.js +0 -1
  119. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{R7xvB2iuNFp_jxENpL7xv → p5sWGp4ANwORiaX4Kk6U2}/_buildManifest.js +0 -0
  120. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{R7xvB2iuNFp_jxENpL7xv → p5sWGp4ANwORiaX4Kk6U2}/_clientMiddlewareManifest.js +0 -0
  121. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{R7xvB2iuNFp_jxENpL7xv → p5sWGp4ANwORiaX4Kk6U2}/_ssgManifest.js +0 -0
@@ -6,7 +6,27 @@ import { promisify as promisify4 } from "node:util";
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]);
@@ -2398,9 +2462,6 @@ function extractSecurityReviewVerdict(comments) {
2398
2462
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2399
2463
  }
2400
2464
  // ../../packages/aeg-core/src/review-gate.ts
2401
- function isReviewGateExemptBranch(branch) {
2402
- return branch.startsWith("plan/");
2403
- }
2404
2465
  function isBoundToHead(extraction, headSha) {
2405
2466
  if (!extraction.headSha)
2406
2467
  return false;
@@ -2455,6 +2516,11 @@ function checkReviewGate(input) {
2455
2516
  waived: false
2456
2517
  };
2457
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
+ }
2458
2524
  // ../../packages/aeg-core/src/markdown-table.ts
2459
2525
  function splitRow(line) {
2460
2526
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -3171,6 +3237,7 @@ var VinayaConfigSchema = z2.object({
3171
3237
  briefSchema: BriefSchemaSchema.optional(),
3172
3238
  managed: ManagedManifestSchema.optional(),
3173
3239
  principals: z2.array(z2.string()).min(1).optional(),
3240
+ releaseActor: z2.string().min(1).optional(),
3174
3241
  ci: z2.object({ setup: z2.string().min(1) }).optional(),
3175
3242
  blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional()
3176
3243
  });
@@ -3180,6 +3247,9 @@ var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
3180
3247
  function resolvePrincipalAllowlist(config) {
3181
3248
  return config?.principals ?? PRINCIPAL_ALLOWLIST;
3182
3249
  }
3250
+ function resolveReleaseActor(config) {
3251
+ return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
3252
+ }
3183
3253
  function trustAnchorRepo() {
3184
3254
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
3185
3255
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
@@ -3222,7 +3292,7 @@ function isMissingFileError(err) {
3222
3292
  return /\b404\b|not found/i.test(haystack);
3223
3293
  }
3224
3294
  function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3225
- const warn = (why) => process.stdout.write(`⚠ could not read \`principals\` from the default branch (${why}) — falling back to vinaya's built-in principal allowlist.
3295
+ 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.
3226
3296
  `);
3227
3297
  let base64;
3228
3298
  try {
@@ -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]);
@@ -2398,9 +2462,6 @@ function extractSecurityReviewVerdict(comments) {
2398
2462
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2399
2463
  }
2400
2464
  // ../../packages/aeg-core/src/review-gate.ts
2401
- function isReviewGateExemptBranch(branch) {
2402
- return branch.startsWith("plan/");
2403
- }
2404
2465
  function isBoundToHead(extraction, headSha) {
2405
2466
  if (!extraction.headSha)
2406
2467
  return false;
@@ -2455,6 +2516,11 @@ function checkReviewGate(input) {
2455
2516
  waived: false
2456
2517
  };
2457
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
+ }
2458
2524
  // ../../packages/aeg-core/src/markdown-table.ts
2459
2525
  function splitRow(line) {
2460
2526
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -3172,6 +3238,7 @@ var VinayaConfigSchema = z.object({
3172
3238
  briefSchema: BriefSchemaSchema.optional(),
3173
3239
  managed: ManagedManifestSchema.optional(),
3174
3240
  principals: z.array(z.string()).min(1).optional(),
3241
+ releaseActor: z.string().min(1).optional(),
3175
3242
  ci: z.object({ setup: z.string().min(1) }).optional(),
3176
3243
  blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional()
3177
3244
  });
@@ -3181,6 +3248,9 @@ var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
3181
3248
  function resolvePrincipalAllowlist(config) {
3182
3249
  return config?.principals ?? PRINCIPAL_ALLOWLIST;
3183
3250
  }
3251
+ function resolveReleaseActor(config) {
3252
+ return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
3253
+ }
3184
3254
  function trustAnchorRepo() {
3185
3255
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
3186
3256
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
@@ -3223,7 +3293,7 @@ function isMissingFileError(err) {
3223
3293
  return /\b404\b|not found/i.test(haystack);
3224
3294
  }
3225
3295
  function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3226
- 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.
3227
3297
  `);
3228
3298
  let base64;
3229
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]);
@@ -2399,9 +2462,6 @@ function extractSecurityReviewVerdict(comments) {
2399
2462
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2400
2463
  }
2401
2464
  // ../../packages/aeg-core/src/review-gate.ts
2402
- function isReviewGateExemptBranch(branch) {
2403
- return branch.startsWith("plan/");
2404
- }
2405
2465
  function isBoundToHead(extraction, headSha) {
2406
2466
  if (!extraction.headSha)
2407
2467
  return false;
@@ -2456,6 +2516,11 @@ function checkReviewGate(input) {
2456
2516
  waived: false
2457
2517
  };
2458
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
+ }
2459
2524
  // ../../packages/aeg-core/src/markdown-table.ts
2460
2525
  function splitRow(line) {
2461
2526
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -3173,6 +3238,7 @@ var VinayaConfigSchema = z.object({
3173
3238
  briefSchema: BriefSchemaSchema.optional(),
3174
3239
  managed: ManagedManifestSchema.optional(),
3175
3240
  principals: z.array(z.string()).min(1).optional(),
3241
+ releaseActor: z.string().min(1).optional(),
3176
3242
  ci: z.object({ setup: z.string().min(1) }).optional(),
3177
3243
  blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional()
3178
3244
  });
@@ -3182,6 +3248,9 @@ var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
3182
3248
  function resolvePrincipalAllowlist(config) {
3183
3249
  return config?.principals ?? PRINCIPAL_ALLOWLIST;
3184
3250
  }
3251
+ function resolveReleaseActor(config) {
3252
+ return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
3253
+ }
3185
3254
  function trustAnchorRepo() {
3186
3255
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
3187
3256
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
@@ -3224,7 +3293,7 @@ function isMissingFileError(err) {
3224
3293
  return /\b404\b|not found/i.test(haystack);
3225
3294
  }
3226
3295
  function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3227
- 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.
3228
3297
  `);
3229
3298
  let base64;
3230
3299
  try {
@@ -3334,7 +3403,7 @@ function main() {
3334
3403
  check: CHECK_NAME,
3335
3404
  severity: "error",
3336
3405
  message,
3337
- 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."
3338
3407
  });
3339
3408
  }
3340
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]);
@@ -2397,9 +2461,6 @@ function extractSecurityReviewVerdict(comments) {
2397
2461
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2398
2462
  }
2399
2463
  // ../../packages/aeg-core/src/review-gate.ts
2400
- function isReviewGateExemptBranch(branch) {
2401
- return branch.startsWith("plan/");
2402
- }
2403
2464
  function isBoundToHead(extraction, headSha) {
2404
2465
  if (!extraction.headSha)
2405
2466
  return false;
@@ -2454,6 +2515,11 @@ function checkReviewGate(input) {
2454
2515
  waived: false
2455
2516
  };
2456
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
+ }
2457
2523
  // ../../packages/aeg-core/src/markdown-table.ts
2458
2524
  function splitRow(line) {
2459
2525
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -5,7 +5,27 @@ import { execFileSync as execFileSync3 } 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]);
@@ -2397,9 +2461,6 @@ function extractSecurityReviewVerdict(comments) {
2397
2461
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2398
2462
  }
2399
2463
  // ../../packages/aeg-core/src/review-gate.ts
2400
- function isReviewGateExemptBranch(branch) {
2401
- return branch.startsWith("plan/");
2402
- }
2403
2464
  function isBoundToHead(extraction, headSha) {
2404
2465
  if (!extraction.headSha)
2405
2466
  return false;
@@ -2454,6 +2515,11 @@ function checkReviewGate(input) {
2454
2515
  waived: false
2455
2516
  };
2456
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
+ }
2457
2523
  // ../../packages/aeg-core/src/markdown-table.ts
2458
2524
  function splitRow(line) {
2459
2525
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -3170,6 +3236,7 @@ var VinayaConfigSchema = z2.object({
3170
3236
  briefSchema: BriefSchemaSchema.optional(),
3171
3237
  managed: ManagedManifestSchema.optional(),
3172
3238
  principals: z2.array(z2.string()).min(1).optional(),
3239
+ releaseActor: z2.string().min(1).optional(),
3173
3240
  ci: z2.object({ setup: z2.string().min(1) }).optional(),
3174
3241
  blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional()
3175
3242
  });
@@ -3179,6 +3246,9 @@ var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
3179
3246
  function resolvePrincipalAllowlist(config) {
3180
3247
  return config?.principals ?? PRINCIPAL_ALLOWLIST;
3181
3248
  }
3249
+ function resolveReleaseActor(config) {
3250
+ return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
3251
+ }
3182
3252
  function trustAnchorRepo() {
3183
3253
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
3184
3254
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
@@ -3221,7 +3291,7 @@ function isMissingFileError(err) {
3221
3291
  return /\b404\b|not found/i.test(haystack);
3222
3292
  }
3223
3293
  function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3224
- const warn = (why) => process.stdout.write(`⚠ could not read \`principals\` from the default branch (${why}) — falling back to vinaya's built-in principal allowlist.
3294
+ 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.
3225
3295
  `);
3226
3296
  let base64;
3227
3297
  try {