@attalabs/vinaya 0.27.0 → 0.29.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 (143) hide show
  1. package/README.md +5 -4
  2. package/aeg-root/contracts/planner-developer.md +4 -1
  3. package/aeg-root/contracts/planner-operator.md +86 -0
  4. package/aeg-root/contracts/principal-operator.md +88 -0
  5. package/aeg-root/enforcement.md +4 -4
  6. package/aeg-root/roles/developer.md +25 -18
  7. package/aeg-root/roles/operator.md +133 -0
  8. package/aeg-root/roles/planner.md +6 -0
  9. package/aeg-root/roles/principal.md +4 -0
  10. package/aeg-root/roles/reviewer.md +3 -3
  11. package/aeg-root/roles/security.md +3 -3
  12. package/aeg-root/skills/aeg-context-packets/SKILL.md +123 -0
  13. package/aeg-root/skills/aeg-context-packets/examples/operator-start.md +30 -0
  14. package/aeg-root/templates/brief-template.md +6 -2
  15. package/dist/checks/bin/check-body-bare-digits.js +1275 -240
  16. package/dist/checks/bin/check-branch-topology.js +1207 -180
  17. package/dist/checks/bin/check-brief-shape.js +1279 -243
  18. package/dist/checks/bin/check-changeset-coverage.js +2176 -264
  19. package/dist/checks/bin/check-closes-n.js +1211 -184
  20. package/dist/checks/bin/check-coherence.js +1378 -265
  21. package/dist/checks/bin/check-dead-branch-push.js +1158 -176
  22. package/dist/checks/bin/check-dispatch-readiness.js +1440 -276
  23. package/dist/checks/bin/check-doc-coverage-push.js +2171 -259
  24. package/dist/checks/bin/check-doc-coverage.js +2173 -261
  25. package/dist/checks/bin/check-doctrine-no-procedures.js +1275 -240
  26. package/dist/checks/bin/check-doctrine-portability.js +2171 -259
  27. package/dist/checks/bin/check-evidence-fresh.js +2613 -347
  28. package/dist/checks/bin/check-exec-bits.js +2169 -257
  29. package/dist/checks/bin/check-first-push-dispatch.js +1326 -246
  30. package/dist/checks/bin/check-issue-assignment.js +1209 -182
  31. package/dist/checks/bin/check-issue-milestone-attach.js +6087 -0
  32. package/dist/checks/bin/check-issue-objectives-numbering.js +6089 -0
  33. package/dist/checks/bin/check-issue-parts-coverage.js +6089 -0
  34. package/dist/checks/bin/check-issue-surface-globs.js +7304 -0
  35. package/dist/checks/bin/check-issue-title-grammar.js +6089 -0
  36. package/dist/checks/bin/check-issue-tranche-label.js +6089 -0
  37. package/dist/checks/bin/check-main-branch-refusal.js +1158 -176
  38. package/dist/checks/bin/check-no-disk-state.js +1158 -176
  39. package/dist/checks/bin/check-pr-premise-reassert.js +1275 -240
  40. package/dist/checks/bin/check-pr-report-density.js +1158 -176
  41. package/dist/checks/bin/check-quoted-command.js +2150 -257
  42. package/dist/checks/bin/check-reader-resolvable-prose.js +2153 -260
  43. package/dist/checks/bin/check-registry-gates.js +1245 -176
  44. package/dist/checks/bin/check-retired-vocabulary.js +2148 -255
  45. package/dist/checks/bin/check-review-gate.js +1375 -245
  46. package/dist/checks/bin/check-single-plan-pr.js +1158 -176
  47. package/dist/checks/bin/check-surface-scope.js +1217 -182
  48. package/dist/checks/bin/check-test-plan.js +1187 -189
  49. package/dist/checks/bin/check-token-collection-wired.js +1158 -176
  50. package/dist/checks/bin/check-token-report.js +1171 -179
  51. package/dist/checks/bin/check-workspace-escape.js +2167 -255
  52. package/dist/index.js +17322 -8709
  53. package/dist/lib/pre-push-changed-files.js +57 -0
  54. package/dist/lib/pre-push-select-tests.js +1616 -0
  55. package/package.json +4 -2
  56. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  58. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error/page.js +2 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error/page.js.nft.json +1 -1
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js +8 -7
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js +8 -7
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js +9 -8
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js +9 -8
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js +8 -7
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +16 -4
  89. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js +10 -8
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js +8 -7
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  95. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  97. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js +8 -7
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js +9 -8
  102. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  104. 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
  105. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__08f5pba._.js +3 -0
  106. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1o_0tjy._.js → [root-of-the-server]__0h9yhwx._.js} +2 -2
  107. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__0053k9k._.js → [root-of-the-server]__0ls23oh._.js} +3 -2
  108. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__0puovz5._.js → [root-of-the-server]__0nye8tb._.js} +3 -2
  109. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__18kqy4q._.js → [root-of-the-server]__0tfykr-._.js} +2 -2
  110. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__0o771t1._.js → [root-of-the-server]__0th3gyp._.js} +3 -2
  111. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1-zcufa._.js +51 -0
  112. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__0p8q38b._.js → [root-of-the-server]__11svemm._.js} +1 -1
  113. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__02c71kv._.js → [root-of-the-server]__140m1i_._.js} +2 -2
  114. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1bh2awk._.js +3 -0
  115. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__0112h-k._.js → [root-of-the-server]__1gjqmhq._.js} +3 -2
  116. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1qmxez3._.js +4 -0
  117. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1r47dr7._.js +28 -0
  118. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  119. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_01fv4fj._.js +1 -1
  120. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_0gvm3og._.js → _096f46i._.js} +3 -2
  121. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_0lwxg63._.js → _0af2one._.js} +3 -2
  122. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_1n0cnq-._.js → _0zf4d27._.js} +45 -2
  123. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_03x_w6q._.js → _1-wo68i._.js} +3 -2
  124. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_app_studio_projects_[name]_tranches_[slug]_05ufo8i._.js +1 -12
  125. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1wdusod._.js +3 -0
  126. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{node_modules_1vo08dj._.js → node_modules_@clerk_nextjs_dist_esm_app-router_server-actions_1x7vfhn.js} +3 -4
  127. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_next_1khj-jh._.js +3 -0
  128. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  129. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  130. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  131. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +17 -5
  132. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3_9xytdmiv1xi.js → 25f3-jtiv8p5n.js} +4 -4
  133. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0bn8c8v5q429o.js → 27-6ym7lp2v8o.js} +1 -1
  134. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3-6a3tinbdb-7.js → 3daxns3o--9me.js} +1 -1
  135. package/studio-standalone/apps/vinaya-studio/web/tsconfig.json +12 -0
  136. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0z6dav-._.js +0 -51
  137. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__13rrmhr._.js +0 -3
  138. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__17h4sy8._.js +0 -45
  139. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +0 -3
  140. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_next_17sz44y._.js +0 -3
  141. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{DMofV2-9FrHE7100GfrpI → -UC6GS6zDHeiawEYgYbTN}/_buildManifest.js +0 -0
  142. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{DMofV2-9FrHE7100GfrpI → -UC6GS6zDHeiawEYgYbTN}/_clientMiddlewareManifest.js +0 -0
  143. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{DMofV2-9FrHE7100GfrpI → -UC6GS6zDHeiawEYgYbTN}/_ssgManifest.js +0 -0
@@ -1647,6 +1647,52 @@ var systemEnv3 = {
1647
1647
  ...process.env,
1648
1648
  PATH: [process.env.PATH, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].filter(Boolean).join(":")
1649
1649
  };
1650
+ var defaultExec = () => execFileAsync3("git", ["remote", "get-url", "origin"], { env: systemEnv3, timeout: 5000 });
1651
+ var cached;
1652
+ var inflight;
1653
+ async function resolveRepo(exec = defaultExec) {
1654
+ if (cached !== undefined)
1655
+ return cached;
1656
+ const fromEnv = process.env.AEG_REPO;
1657
+ if (fromEnv) {
1658
+ const parsed = parseOwnerRepo(fromEnv);
1659
+ if (parsed) {
1660
+ cached = parsed;
1661
+ return cached;
1662
+ }
1663
+ }
1664
+ if (inflight)
1665
+ return inflight;
1666
+ inflight = (async () => {
1667
+ try {
1668
+ const { stdout } = await exec();
1669
+ cached = parseGitRemoteUrl(stdout.trim());
1670
+ return cached;
1671
+ } catch (err) {
1672
+ const message = err instanceof Error ? err.message : String(err);
1673
+ console.warn(`[aeg-forge-state] resolveRepo: git remote lookup failed (will retry): ${message}`);
1674
+ return null;
1675
+ } finally {
1676
+ inflight = undefined;
1677
+ }
1678
+ })();
1679
+ return inflight;
1680
+ }
1681
+ function parseGitRemoteUrl(url) {
1682
+ const ssh = url.match(/^git@github\.com:([^/]+)\/(.+?)(?:\.git)?$/);
1683
+ if (ssh?.[1] && ssh[2])
1684
+ return { owner: ssh[1], repo: ssh[2] };
1685
+ const https = url.match(/^https?:\/\/(?:[^@]+@)?github\.com\/([^/]+)\/(.+?)(?:\.git)?\/?$/);
1686
+ if (https?.[1] && https[2])
1687
+ return { owner: https[1], repo: https[2] };
1688
+ return null;
1689
+ }
1690
+ function parseOwnerRepo(value) {
1691
+ const match = value.match(/^([^/]+)\/(.+)$/);
1692
+ if (!match?.[1] || !match[2])
1693
+ return null;
1694
+ return { owner: match[1], repo: match[2] };
1695
+ }
1650
1696
  // ../../packages/aeg-core/src/anchored-region.ts
1651
1697
  function anchoredRegionBounds(body, field) {
1652
1698
  const masked = maskCode(body);
@@ -1957,6 +2003,9 @@ function parseInlineFieldList(section) {
1957
2003
  var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1958
2004
  var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1959
2005
  var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
2006
+ var BRIEF_HASH_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Brief hash:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
2007
+ var POLICY_DIGEST_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Policy digest:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
2008
+ var BASE_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged base:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1960
2009
  function firstFiveLines(comment) {
1961
2010
  return comment.split(`
1962
2011
  `).slice(0, 5).join(`
@@ -1979,6 +2028,35 @@ function extractRulingOrdinal(comment) {
1979
2028
  const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
1980
2029
  return m ? Number.parseInt(m[1], 10) : null;
1981
2030
  }
2031
+ function firstElevenLines(comment) {
2032
+ return comment.split(`
2033
+ `).slice(0, 11).join(`
2034
+ `);
2035
+ }
2036
+ function extractBriefHash(comment) {
2037
+ const m = firstElevenLines(comment).match(BRIEF_HASH_PATTERN);
2038
+ return m ? m[1].toLowerCase() : null;
2039
+ }
2040
+ function extractPolicyDigest(comment) {
2041
+ const m = firstElevenLines(comment).match(POLICY_DIGEST_PATTERN);
2042
+ return m ? m[1].toLowerCase() : null;
2043
+ }
2044
+ function firstThirteenLines(comment) {
2045
+ return comment.split(`
2046
+ `).slice(0, 13).join(`
2047
+ `);
2048
+ }
2049
+ function extractBaseSha(comment) {
2050
+ const m = firstThirteenLines(comment).match(BASE_SHA_PATTERN);
2051
+ return m ? m[1].toLowerCase() : null;
2052
+ }
2053
+ var FINDING_SEVERITY_LINE = /^\d+\.\s+\[([A-Z][A-Z]*)\]\s+(.+?)\s+—/gm;
2054
+ function extractFindingSeverities(comment) {
2055
+ return [...comment.matchAll(FINDING_SEVERITY_LINE)].map((m) => ({
2056
+ severity: m[1],
2057
+ location: m[2]
2058
+ }));
2059
+ }
1982
2060
  function extractVerdict(comments, valuePattern, missingLabel) {
1983
2061
  const candidates = comments.filter((c) => valuePattern.test(c));
1984
2062
  if (candidates.length === 0) {
@@ -1987,6 +2065,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1987
2065
  headSha: null,
1988
2066
  objectivesVersion: null,
1989
2067
  rulingOrdinal: null,
2068
+ briefHash: null,
2069
+ policyDigest: null,
2070
+ baseSha: null,
2071
+ findingSeverities: [],
1990
2072
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
1991
2073
  };
1992
2074
  }
@@ -1998,6 +2080,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1998
2080
  headSha: null,
1999
2081
  objectivesVersion: null,
2000
2082
  rulingOrdinal: null,
2083
+ briefHash: null,
2084
+ policyDigest: null,
2085
+ baseSha: null,
2086
+ findingSeverities: [],
2001
2087
  danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
2002
2088
  };
2003
2089
  }
@@ -2006,6 +2092,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
2006
2092
  headSha: extractHeadSha(latest),
2007
2093
  objectivesVersion: extractObjectivesVersion(latest),
2008
2094
  rulingOrdinal: extractRulingOrdinal(latest),
2095
+ briefHash: extractBriefHash(latest),
2096
+ policyDigest: extractPolicyDigest(latest),
2097
+ baseSha: extractBaseSha(latest),
2098
+ findingSeverities: extractFindingSeverities(latest),
2009
2099
  danglingNote: null
2010
2100
  };
2011
2101
  }
@@ -2341,6 +2431,53 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
2341
2431
  }
2342
2432
  return out;
2343
2433
  }
2434
+ // ../../packages/aeg-core/src/review-policy.ts
2435
+ var CODE_REVIEW_SEVERITY_ORDER = ["BLOCKER", "MAJOR", "MINOR"];
2436
+ var SECURITY_SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
2437
+ var DEFAULT_MAX_ROUNDS = 3;
2438
+ var DEFAULT_REVIEW_POLICY = {
2439
+ codeReviewThreshold: "BLOCKER",
2440
+ securityThreshold: "HIGH",
2441
+ maxRounds: DEFAULT_MAX_ROUNDS
2442
+ };
2443
+ var FILE_SHAPED_LOCATION = /\.[a-zA-Z0-9]{1,10}(:\d+)?\s*$/;
2444
+ var PROSE_LOCATION_PATTERNS = [/\bpr\s*body\b/i, /\bcomment\b/i];
2445
+ var ROLE_FILE_LOCATION = /(^|\/)aeg-root\/roles\//i;
2446
+ function isProseLocation(location) {
2447
+ if (ROLE_FILE_LOCATION.test(location))
2448
+ return true;
2449
+ if (FILE_SHAPED_LOCATION.test(location))
2450
+ return false;
2451
+ return PROSE_LOCATION_PATTERNS.some((pattern) => pattern.test(location));
2452
+ }
2453
+ var PROSE_CAP_SEVERITY = "MINOR";
2454
+ function blockingSeverities(scale, threshold) {
2455
+ const idx = scale.indexOf(threshold);
2456
+ if (idx === -1) {
2457
+ throw new Error(`blockingSeverities: threshold "${threshold}" is not one of ${scale.join(" > ")}`);
2458
+ }
2459
+ return scale.slice(0, idx + 1);
2460
+ }
2461
+ function evaluateReviewFindings(findings, scale, threshold) {
2462
+ const blocking = new Set(blockingSeverities(scale, threshold));
2463
+ const blockingFindings = findings.filter((f) => {
2464
+ if (!scale.includes(f.severity)) {
2465
+ throw new Error(`evaluateReviewFindings: severity "${f.severity}" is not one of ${scale.join(" > ")}`);
2466
+ }
2467
+ const effectiveSeverity = f.location !== undefined && isProseLocation(f.location) ? PROSE_CAP_SEVERITY : f.severity;
2468
+ return blocking.has(effectiveSeverity);
2469
+ });
2470
+ return { outcome: blockingFindings.length > 0 ? "blocked" : "clean", blockingFindings };
2471
+ }
2472
+ function evaluateCodeReview(findings, policy) {
2473
+ return evaluateReviewFindings(findings, CODE_REVIEW_SEVERITY_ORDER, policy.codeReviewThreshold);
2474
+ }
2475
+ function evaluateSecurityReview(findings, policy) {
2476
+ return evaluateReviewFindings(findings, SECURITY_SEVERITY_ORDER, policy.securityThreshold);
2477
+ }
2478
+ function isKnownSeverity(scale, value) {
2479
+ return scale.includes(value);
2480
+ }
2344
2481
  // ../../packages/aeg-core/src/pr-tier.ts
2345
2482
  var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
2346
2483
  function readTierFromPrBody(prBody) {
@@ -2711,7 +2848,7 @@ function checkForField(prBody) {
2711
2848
  ]
2712
2849
  };
2713
2850
  }
2714
- function checkClosesN(prBody) {
2851
+ function checkClosesNPresence(prBody) {
2715
2852
  const closesPattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s{0,8}:?\s{0,8}#\d+/i;
2716
2853
  if (closesPattern.test(stripCode(prBody))) {
2717
2854
  return { status: "pass", errors: [] };
@@ -2743,10 +2880,22 @@ var COMMIT_TYPES = [
2743
2880
  "Test"
2744
2881
  ];
2745
2882
  var COMMIT_TYPE_STYLE = new RegExp(`^(${COMMIT_TYPES.join("|")})(\\([a-z0-9-]+\\))?: \\S`);
2883
+ function checkForgeTitle(title) {
2884
+ const taskStyle = /^\[[a-z0-9._-]+\] \S+ — \S/;
2885
+ if (COMMIT_TYPE_STYLE.test(title) || taskStyle.test(title))
2886
+ return { status: "pass", errors: [] };
2887
+ return {
2888
+ status: "fail",
2889
+ errors: [
2890
+ `brief-validation title: "${title}" matches neither title grammar — expected \`Type: description\` / \`Type(scope): description\` (commitlint types + Plan) or \`[tranche] id — description\` (task form).`
2891
+ ]
2892
+ };
2893
+ }
2746
2894
  var BRIEF_SHAPE_MARKERS = [checkSurfaceMap, checkDocUpdateList, checkStopConditions, checkAutonomyClause];
2747
2895
  var TASK_BRANCH_PATTERN = /^task\/[^/]+\/[^/]+$/;
2896
+ var TASK_ISSUE_BRANCH_PATTERN = /^task\/issue-\d+$/;
2748
2897
  function isTaskBranch(branch) {
2749
- return TASK_BRANCH_PATTERN.test(branch);
2898
+ return TASK_BRANCH_PATTERN.test(branch) || TASK_ISSUE_BRANCH_PATTERN.test(branch);
2750
2899
  }
2751
2900
  function isBriefShaped(prBody) {
2752
2901
  const stripped = stripCode(prBody);
@@ -2868,8 +3017,9 @@ function packagesNamedIn(text) {
2868
3017
  }
2869
3018
  function hasTestPathForConsumer(text, consumerDir) {
2870
3019
  const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2871
- const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2872
- return re.test(text);
3020
+ const filePathRe = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
3021
+ const testDirRe = new RegExp(`${escaped}\\/(?:[\\w-]+\\/)*(?:tests|specs)(?:\\/|\\b)`);
3022
+ return filePathRe.test(text) || testDirRe.test(text);
2873
3023
  }
2874
3024
  function checkConsumerTests(prBody, consumersOf) {
2875
3025
  const section4 = extractNumberedSection(prBody, 4);
@@ -2973,7 +3123,7 @@ function checkBriefSections(prBody, readTier, options = {}) {
2973
3123
  checkConsumerTests(prBody, consumersOf),
2974
3124
  checkDefeatCases(prBody),
2975
3125
  ...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
2976
- ...requireClosesN ? [checkClosesN(prBody)] : []
3126
+ ...requireClosesN ? [checkClosesNPresence(prBody)] : []
2977
3127
  ];
2978
3128
  return { errors: results.flatMap((r) => r.errors) };
2979
3129
  }
@@ -3263,6 +3413,28 @@ function topLevelSectionText(body, headingName) {
3263
3413
  const next = /^##[ \t]/m.exec(afterHeading);
3264
3414
  return next ? afterHeading.slice(0, next.index) : afterHeading;
3265
3415
  }
3416
+ function partHasBacktickedPath(text) {
3417
+ let i = 0;
3418
+ while (i < text.length) {
3419
+ const start = text.indexOf("`", i);
3420
+ if (start === -1)
3421
+ return false;
3422
+ const end = text.indexOf("`", start + 1);
3423
+ if (end === -1)
3424
+ return false;
3425
+ if (text.slice(start + 1, end).includes("/"))
3426
+ return true;
3427
+ i = end + 1;
3428
+ }
3429
+ return false;
3430
+ }
3431
+ var PART_MIN_WORDS_OUTSIDE_BACKTICKS = 3;
3432
+ function stripPartBackticks(text) {
3433
+ return text.replace(/`[^`\n]*`/g, " ");
3434
+ }
3435
+ function partWordCount(text) {
3436
+ return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
3437
+ }
3266
3438
  function looksLikeFilePath(entry2) {
3267
3439
  const stripped = entry2.replace(/\/\*\*?$/, "");
3268
3440
  const lastSegment = stripped.split("/").pop() ?? stripped;
@@ -3303,6 +3475,18 @@ function globCoversPath(glob, path) {
3303
3475
  const p = path.replace(/\/+$/, "");
3304
3476
  return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
3305
3477
  }
3478
+ function checkSurfaceGlobsResolve(body, resolvesToFile) {
3479
+ const surface = parseIssueSurface(body);
3480
+ if (!surface.ok)
3481
+ return { status: "pass", errors: [] };
3482
+ const errors = [];
3483
+ for (const glob of surface.value.in) {
3484
+ if (!resolvesToFile(glob)) {
3485
+ errors.push(`issue-validation Surface: \`${glob}\` in \`## Surface\`'s \`in:\` list matches no tracked file — a Surface that cannot resolve cannot render a brief's file list.`);
3486
+ }
3487
+ }
3488
+ return { status: errors.length > 0 ? "fail" : "pass", errors };
3489
+ }
3306
3490
  function checkSurfaceScope(changedFiles, outGlobs) {
3307
3491
  if (outGlobs.length === 0)
3308
3492
  return { ok: true };
@@ -3314,9 +3498,77 @@ function checkSurfaceScope(changedFiles, outGlobs) {
3314
3498
  }
3315
3499
  return violations.length > 0 ? { ok: false, violations } : { ok: true };
3316
3500
  }
3501
+ var ISSUE_PART_LINE_RE = /^Part\s+(\d+)\s*\(([^)]*)\)\s*[-—–]\s*(.*)$/i;
3502
+ function parseIssueParts(body) {
3503
+ const section = topLevelSectionText(body, "Parts");
3504
+ if (section === null)
3505
+ return { ok: false, errors: ["no `## Parts` heading found in the body."] };
3506
+ const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
3507
+ const parts = [];
3508
+ const errors = [];
3509
+ for (const line of lines) {
3510
+ const m = ISSUE_PART_LINE_RE.exec(line);
3511
+ if (!m) {
3512
+ errors.push(`"${line}" is not a well-formed Parts line — expected \`Part <n> (<refs>) — <outcome>\`.`);
3513
+ continue;
3514
+ }
3515
+ const n = Number.parseInt(m[1], 10);
3516
+ const refs = m[2];
3517
+ const text = m[3].trim();
3518
+ if (text.length === 0) {
3519
+ errors.push(`Part ${n} has no outcome text after the dash — every Part states one observable outcome.`);
3520
+ continue;
3521
+ }
3522
+ if (partHasBacktickedPath(text) && partWordCount(stripPartBackticks(text)) < PART_MIN_WORDS_OUTSIDE_BACKTICKS) {
3523
+ errors.push(`Part ${n} is little more than a file path — a Part names an outcome and symbols, never a bare path.`);
3524
+ continue;
3525
+ }
3526
+ const objectiveIds = [...refs.matchAll(/O(\d+)/g)].map((r) => Number.parseInt(r[1], 10));
3527
+ parts.push({ n, objectiveIds, text });
3528
+ }
3529
+ if (parts.length === 0) {
3530
+ errors.push("the `## Parts` section has no well-formed `Part <n> (<refs>) — <outcome>` lines.");
3531
+ }
3532
+ if (errors.length > 0)
3533
+ return { ok: false, errors };
3534
+ return { ok: true, value: parts };
3535
+ }
3536
+ function checkPartsCiteDefinedObjectives(body) {
3537
+ const parts = parseIssueParts(body);
3538
+ const objectives = objectivesOf(body);
3539
+ if (!parts.ok || !objectives.ok)
3540
+ return { status: "pass", errors: [] };
3541
+ const definedIds = new Set(objectives.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
3542
+ const errors = [];
3543
+ for (const part of parts.value) {
3544
+ for (const objectiveId of part.objectiveIds) {
3545
+ if (!definedIds.has(objectiveId)) {
3546
+ errors.push(`issue-validation Parts: Part ${part.n} cites O${objectiveId}, which the Issue's own \`## Objectives\` section does not define.`);
3547
+ }
3548
+ }
3549
+ }
3550
+ return { status: errors.length > 0 ? "fail" : "pass", errors };
3551
+ }
3317
3552
  function isTaskIssueLabelSet(labels) {
3318
3553
  return hasLabel("tranche", labels);
3319
3554
  }
3555
+ function checkTrancheLabelPresence(_body, _labels) {
3556
+ return { status: "pass", errors: [] };
3557
+ }
3558
+ function checkMilestoneAttach(labels, currentMilestoneTitle, resolvedMilestoneTitle) {
3559
+ if (!isTaskIssueLabelSet(labels))
3560
+ return { status: "pass", errors: [] };
3561
+ if (resolvedMilestoneTitle === null)
3562
+ return { status: "pass", errors: [] };
3563
+ if (currentMilestoneTitle === resolvedMilestoneTitle)
3564
+ return { status: "pass", errors: [] };
3565
+ return {
3566
+ status: "fail",
3567
+ errors: [
3568
+ `issue-validation milestone attach: this task Issue's tranche label resolves to Milestone "${resolvedMilestoneTitle}", but its live Milestone is ${currentMilestoneTitle === null ? "unset" : `"${currentMilestoneTitle}"`} — attach it to "${resolvedMilestoneTitle}".`
3569
+ ]
3570
+ };
3571
+ }
3320
3572
  var TYPE_LABEL_IDS = LABELS.filter((l) => l.category === "type").map((l) => l.id);
3321
3573
  function isControlCodePoint(codePoint) {
3322
3574
  return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
@@ -3381,6 +3633,19 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
3381
3633
  var DOC_PATH_RE = /(?:(?:aeg-root|apps|packages|specs|docs|tools|\.claude|\.github)\/[\w./@-]*\.(?:md|mdx)|\.claude\/(?:skills|rules)\/[\w./-]+|\b(?:docs-index|decisions|projects|state-machine|enforcement|process|README|CLAUDE)\.md\b|\b[\w-]+-(?:spec|decisions|backlog)\.md\b)/i;
3382
3634
  var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
3383
3635
 
3636
+ // ../../packages/aeg-core/src/task-branch-identity.ts
3637
+ var ISSUE_BRANCH_PATTERN = /^task\/issue-(\d+)$/;
3638
+ var TRANCHE_BRANCH_PATTERN = /^task\/([^/]+)\/([^/]+)$/;
3639
+ function parseTaskBranchIdentity(branch) {
3640
+ const issueMatch = ISSUE_BRANCH_PATTERN.exec(branch);
3641
+ if (issueMatch)
3642
+ return { kind: "issue", issueNumber: Number(issueMatch[1]) };
3643
+ const trancheMatch = TRANCHE_BRANCH_PATTERN.exec(branch);
3644
+ if (trancheMatch)
3645
+ return { kind: "tranche", tranche: trancheMatch[1], taskId: trancheMatch[2] };
3646
+ return null;
3647
+ }
3648
+
3384
3649
  // ../../packages/aeg-core/src/coherence-checks.ts
3385
3650
  var COHERENCE_ENFORCED_FROM = "2026-07-01";
3386
3651
  function isGrandfathered(isoDate) {
@@ -3401,6 +3666,7 @@ function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
3401
3666
  if (handClosed)
3402
3667
  continue;
3403
3668
  failures.push({
3669
+ code: "closed-without-merge",
3404
3670
  issue: e.task.issue,
3405
3671
  tranche: e.trancheSlug,
3406
3672
  task: e.task.id,
@@ -3425,6 +3691,7 @@ function checkA3(entries) {
3425
3691
  continue;
3426
3692
  if (e.facts.prState === "merged" && e.facts.issueState !== "closed") {
3427
3693
  failures.push({
3694
+ code: "auto-close-misfire",
3428
3695
  issue: e.task.issue,
3429
3696
  tranche: e.trancheSlug,
3430
3697
  task: e.task.id,
@@ -3449,6 +3716,7 @@ function checkT1(entries) {
3449
3716
  continue;
3450
3717
  if (e.facts === undefined) {
3451
3718
  failures.push({
3719
+ code: "phantom-issue-ref",
3452
3720
  issue: e.task.issue,
3453
3721
  tranche: e.trancheSlug,
3454
3722
  task: e.task.id,
@@ -3467,6 +3735,7 @@ function checkT2(openIssuesBySlug, topologyIssuesBySlug, ciTrancheSlug) {
3467
3735
  for (const num of openNums) {
3468
3736
  if (!topologySet.has(num)) {
3469
3737
  failures.push({
3738
+ code: "orphan-task",
3470
3739
  issue: num,
3471
3740
  tranche: slug,
3472
3741
  reason: `Issue #${num} is open and labeled ${trancheLabel(slug)} but does not appear in the topology file`
@@ -3501,6 +3770,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
3501
3770
  continue;
3502
3771
  if (forgeUnavailableSlugs?.has(e.trancheSlug)) {
3503
3772
  failures.push({
3773
+ code: "tbd-in-active-tranche",
3504
3774
  issue: null,
3505
3775
  tranche: e.trancheSlug,
3506
3776
  task: e.task.id,
@@ -3510,6 +3780,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
3510
3780
  continue;
3511
3781
  }
3512
3782
  failures.push({
3783
+ code: "tbd-in-active-tranche",
3513
3784
  issue: null,
3514
3785
  tranche: e.trancheSlug,
3515
3786
  task: e.task.id,
@@ -3542,6 +3813,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
3542
3813
  const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
3543
3814
  if (sameTask || sameIssue) {
3544
3815
  failures.push({
3816
+ code: "d1-self-dependency",
3545
3817
  issue: e.task.issue,
3546
3818
  tranche: e.trancheSlug,
3547
3819
  task: e.task.id,
@@ -3553,6 +3825,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
3553
3825
  const depClosed = depFacts?.issueState === "closed";
3554
3826
  if (!depClosed) {
3555
3827
  failures.push({
3828
+ code: "dispatched-on-unmet-deps",
3556
3829
  issue: e.task.issue,
3557
3830
  tranche: e.trancheSlug,
3558
3831
  task: e.task.id,
@@ -3578,11 +3851,13 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
3578
3851
  const errors = [
3579
3852
  ...checkIssueRationale(issue.body).errors,
3580
3853
  ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
3581
- ...checkIssueObjectives(issue.body, issue.number).errors
3854
+ ...checkIssueObjectives(issue.body, issue.number).errors,
3855
+ ...checkPartsCiteDefinedObjectives(issue.body).errors
3582
3856
  ];
3583
3857
  if (errors.length === 0)
3584
3858
  continue;
3585
3859
  failures.push({
3860
+ code: "missing-rationale-field",
3586
3861
  issue: issue.number,
3587
3862
  tranche: slug,
3588
3863
  reason: `Issue #${issue.number} fails the rationale gate: ${errors.join(" | ")}`,
@@ -3611,6 +3886,7 @@ function checkL1(files, entriesBySlug) {
3611
3886
  const allClosed = withFacts.every((e) => e.facts?.issueState === "closed");
3612
3887
  if (allClosed) {
3613
3888
  failures.push({
3889
+ code: "archive-recommended",
3614
3890
  tranche: f.slug,
3615
3891
  reason: "Active tranche has no open task-Issues — consider archiving to completed/"
3616
3892
  });
@@ -3642,27 +3918,38 @@ function extractClosesReferences(prBody) {
3642
3918
  }
3643
3919
  return referenced;
3644
3920
  }
3645
- function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
3921
+ function checkClosesNTopology(branch, prBody, trancheFiles, taskIssueRefs) {
3646
3922
  const referenced = extractClosesReferences(prBody);
3647
3923
  if (taskIssueRefs) {
3648
3924
  for (const n of referenced) {
3649
- const ref = taskIssueRefs.get(n);
3650
- if (!ref)
3925
+ const ref2 = taskIssueRefs.get(n);
3926
+ if (!ref2)
3651
3927
  continue;
3652
- const expectedBranch = `task/${ref.trancheSlug}/${ref.taskId}`;
3928
+ const expectedBranch = `task/${ref2.trancheSlug}/${ref2.taskId}`;
3653
3929
  if (branch !== expectedBranch) {
3654
3930
  return {
3655
3931
  ok: false,
3656
- message: `closes-n-reverse: branch "${branch}" closes #${n} (task ${ref.taskId} of tranche "${ref.trancheSlug}") but is not named "${expectedBranch}" — rename the branch and re-push, or if this work is intentionally outside AEG's dispatch flow, remove the Closes reference.`
3932
+ message: `closes-n-reverse: branch "${branch}" closes #${n} (task ${ref2.taskId} of tranche "${ref2.trancheSlug}") but is not named "${expectedBranch}" — rename the branch and re-push, or if this work is intentionally outside AEG's dispatch flow, remove the Closes reference.`
3657
3933
  };
3658
3934
  }
3659
3935
  }
3660
3936
  }
3661
- const m = branch.match(/^task\/([^/]+)\/([^/]+)$/);
3662
- if (!m)
3937
+ const ref = parseTaskBranchIdentity(branch);
3938
+ if (!ref)
3663
3939
  return { ok: true };
3664
- const trancheSlug = m[1];
3665
- const taskId = m[2];
3940
+ if (ref.kind === "issue") {
3941
+ const expectedIssue2 = ref.issueNumber;
3942
+ if (!referenced.has(expectedIssue2)) {
3943
+ return {
3944
+ ok: false,
3945
+ expectedIssue: expectedIssue2,
3946
+ message: `closes-n: PR body does not contain \`Closes #${expectedIssue2}\` (required for branch "${branch}"). Add it to the PR body Summary section.`
3947
+ };
3948
+ }
3949
+ return { ok: true, expectedIssue: expectedIssue2 };
3950
+ }
3951
+ const trancheSlug = ref.tranche;
3952
+ const taskId = ref.taskId;
3666
3953
  const trancheFile = trancheFiles.find((f) => f.slug === trancheSlug);
3667
3954
  if (!trancheFile) {
3668
3955
  return {
@@ -3714,34 +4001,79 @@ function extractIssue(body) {
3714
4001
  const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
3715
4002
  return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
3716
4003
  }
3717
- // ../../packages/aeg-core/src/review-gate.ts
3718
- function isBoundToHead(extraction, headSha) {
3719
- if (!extraction.headSha)
4004
+ // ../../packages/aeg-core/src/review-input-manifest.ts
4005
+ import { createHash as createHash4 } from "node:crypto";
4006
+ function briefHash(brief) {
4007
+ return createHash4("sha256").update(`${brief}
4008
+ `).digest("hex");
4009
+ }
4010
+ function policyDigest(policy) {
4011
+ return createHash4("sha256").update(JSON.stringify({
4012
+ codeReviewThreshold: policy.codeReviewThreshold,
4013
+ securityThreshold: policy.securityThreshold,
4014
+ maxRounds: policy.maxRounds
4015
+ })).digest("hex");
4016
+ }
4017
+ function isBoundToHead(echoed, headSha) {
4018
+ if (!echoed.headSha)
3720
4019
  return false;
3721
- return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
4020
+ return headSha.toLowerCase().startsWith(echoed.headSha.toLowerCase());
3722
4021
  }
3723
- function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3724
- if (patchIdOf === undefined || !extraction.headSha)
4022
+ function isBoundByPatchIdentity(echoed, headSha, patchIdOf) {
4023
+ if (patchIdOf === undefined || !echoed.headSha)
3725
4024
  return false;
3726
- const judged = patchIdOf(extraction.headSha);
4025
+ const judged = patchIdOf(echoed.headSha);
3727
4026
  const current = patchIdOf(headSha);
3728
4027
  if (judged === null || current === null)
3729
4028
  return false;
3730
4029
  return judged === current;
3731
4030
  }
3732
- function isBoundToPatch(extraction, headSha, patchIdOf) {
3733
- return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
4031
+ function isBoundToBase(echoed, currentBase) {
4032
+ if (currentBase === null)
4033
+ return true;
4034
+ if (!echoed.baseSha)
4035
+ return false;
4036
+ return currentBase.toLowerCase().startsWith(echoed.baseSha.toLowerCase());
3734
4037
  }
3735
- function isBoundToObjectives(extraction, currentVersion) {
4038
+ function isBoundToObjectives(echoed, currentVersion) {
3736
4039
  if (currentVersion === null)
3737
4040
  return true;
3738
- return extraction.objectivesVersion === currentVersion;
4041
+ return echoed.objectivesVersion === currentVersion;
3739
4042
  }
3740
- function isBoundToRulings(extraction, currentOrdinal) {
3741
- if (extraction.rulingOrdinal === null)
4043
+ function isBoundToRulings(echoed, currentOrdinal) {
4044
+ if (echoed.rulingOrdinal === null)
3742
4045
  return currentOrdinal === 0;
3743
- return extraction.rulingOrdinal === currentOrdinal;
4046
+ return echoed.rulingOrdinal === currentOrdinal;
3744
4047
  }
4048
+ function isBoundToBriefHash(echoed, currentHash) {
4049
+ if (currentHash === null)
4050
+ return true;
4051
+ return echoed.briefHash === currentHash;
4052
+ }
4053
+ function isBoundToPolicy(echoed, currentDigest) {
4054
+ return echoed.policyDigest === currentDigest;
4055
+ }
4056
+ function compareManifest(echoed, current, patchIdOf) {
4057
+ const exactHead = isBoundToHead(echoed, current.headSha);
4058
+ const patchHead = exactHead ? false : isBoundByPatchIdentity(echoed, current.headSha, patchIdOf);
4059
+ const head = exactHead || patchHead;
4060
+ const base = patchHead ? true : isBoundToBase(echoed, current.baseSha);
4061
+ const briefHashBound = isBoundToBriefHash(echoed, current.briefHash);
4062
+ const objectivesVersion2 = isBoundToObjectives(echoed, current.objectivesVersion);
4063
+ const rulingOrdinal = isBoundToRulings(echoed, current.rulingOrdinal);
4064
+ const policyDigestBound = isBoundToPolicy(echoed, current.policyDigest);
4065
+ return {
4066
+ bound: head && base && briefHashBound && objectivesVersion2 && rulingOrdinal && policyDigestBound,
4067
+ head,
4068
+ base,
4069
+ briefHash: briefHashBound,
4070
+ objectivesVersion: objectivesVersion2,
4071
+ rulingOrdinal,
4072
+ policyDigest: policyDigestBound
4073
+ };
4074
+ }
4075
+
4076
+ // ../../packages/aeg-core/src/review-gate.ts
3745
4077
  function checkReviewGate(input) {
3746
4078
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
3747
4079
  const waived = isWaiverLabelActorVerified({
@@ -3764,15 +4096,58 @@ function checkReviewGate(input) {
3764
4096
  const verifiedBodies = verified.map((c) => c.body);
3765
4097
  const codeReview = extractCodeReviewVerdict(verifiedBodies);
3766
4098
  const security = extractSecurityReviewVerdict(verifiedBodies);
3767
- const codeReviewClean = codeReview.value === "APPROVE";
3768
- const securityClean = security.value === "PASS";
3769
- const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3770
- const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3771
- const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
3772
- const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
3773
- const codeReviewRulingsBound = isBoundToRulings(codeReview, input.rulingOrdinal);
3774
- const securityRulingsBound = isBoundToRulings(security, input.rulingOrdinal);
3775
- if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && codeReviewRulingsBound && securityClean && securityBound && securityObjectivesBound && securityRulingsBound && mechanicalChecksClean) {
4099
+ const policy = input.policy ?? DEFAULT_REVIEW_POLICY;
4100
+ let codeReviewPolicyEvaluation;
4101
+ let securityPolicyEvaluation;
4102
+ try {
4103
+ codeReviewPolicyEvaluation = evaluateCodeReview(codeReview.findingSeverities, policy);
4104
+ securityPolicyEvaluation = evaluateSecurityReview(security.findingSeverities, policy);
4105
+ } catch (err) {
4106
+ return {
4107
+ verdict: "fail",
4108
+ reason: `a verdict comment carries a finding severity this repository's policy does not recognize: ${err instanceof Error ? err.message : String(err)}`,
4109
+ waived: false
4110
+ };
4111
+ }
4112
+ const codeReviewTextClean = codeReview.value === "APPROVE";
4113
+ const securityTextClean = security.value === "PASS";
4114
+ const codeReviewPolicyClean = codeReviewPolicyEvaluation.outcome === "clean";
4115
+ const securityPolicyClean = securityPolicyEvaluation.outcome === "clean";
4116
+ const codeReviewClean = codeReviewTextClean && codeReviewPolicyClean;
4117
+ const securityClean = securityTextClean && securityPolicyClean;
4118
+ const currentManifest = {
4119
+ headSha: input.headSha,
4120
+ baseSha: input.baseSha ?? null,
4121
+ briefHash: input.briefHash ?? null,
4122
+ objectivesVersion: input.objectivesVersion,
4123
+ rulingOrdinal: input.rulingOrdinal,
4124
+ policyDigest: policyDigest(policy)
4125
+ };
4126
+ const codeReviewEchoed = {
4127
+ headSha: codeReview.headSha,
4128
+ baseSha: codeReview.baseSha,
4129
+ briefHash: codeReview.briefHash,
4130
+ objectivesVersion: codeReview.objectivesVersion,
4131
+ rulingOrdinal: codeReview.rulingOrdinal,
4132
+ policyDigest: codeReview.policyDigest
4133
+ };
4134
+ const securityEchoed = {
4135
+ headSha: security.headSha,
4136
+ baseSha: security.baseSha,
4137
+ briefHash: security.briefHash,
4138
+ objectivesVersion: security.objectivesVersion,
4139
+ rulingOrdinal: security.rulingOrdinal,
4140
+ policyDigest: security.policyDigest
4141
+ };
4142
+ const codeReviewBinding = compareManifest(codeReviewEchoed, currentManifest, input.patchIdOf);
4143
+ const securityBinding = compareManifest(securityEchoed, currentManifest, input.patchIdOf);
4144
+ const codeReviewBound = codeReviewBinding.head;
4145
+ const securityBound = securityBinding.head;
4146
+ const codeReviewObjectivesBound = codeReviewBinding.objectivesVersion;
4147
+ const securityObjectivesBound = securityBinding.objectivesVersion;
4148
+ const codeReviewRulingsBound = codeReviewBinding.rulingOrdinal;
4149
+ const securityRulingsBound = securityBinding.rulingOrdinal;
4150
+ if (codeReviewClean && codeReviewBinding.bound && securityClean && securityBinding.bound && mechanicalChecksClean) {
3776
4151
  return {
3777
4152
  verdict: "pass",
3778
4153
  reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}, and every reported mechanical check is green.`,
@@ -3780,23 +4155,39 @@ function checkReviewGate(input) {
3780
4155
  };
3781
4156
  }
3782
4157
  const problems = [];
3783
- if (!codeReviewClean) {
4158
+ if (!codeReviewTextClean) {
3784
4159
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
4160
+ } else if (!codeReviewPolicyClean) {
4161
+ problems.push(`code-reviewer verdict says APPROVE but carries a finding (${codeReviewPolicyEvaluation.blockingFindings.map((f) => f.severity).join(", ")}) at or above this repository's code-review policy threshold (${policy.codeReviewThreshold}) — a reviewer's own APPROVE never overrides policy`);
3785
4162
  } else if (!codeReviewBound) {
3786
4163
  problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
4164
+ } else if (!codeReviewBinding.base) {
4165
+ problems.push(`the newest code-review verdict was cast against base ${codeReview.baseSha ?? "no recorded base"}, the PR's base is now ${currentManifest.baseSha ?? "unresolved"} (a base-only change under an unchanged candidate — same patch text on a new base is not automatically equivalent)`);
3787
4166
  } else if (!codeReviewObjectivesBound) {
3788
4167
  problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3789
4168
  } else if (!codeReviewRulingsBound) {
3790
4169
  problems.push(`the newest code-review verdict was cast against ruling ordinal ${codeReview.rulingOrdinal ?? "none"}, a newer ruling (ruling ${input.rulingOrdinal}) is now posted on this PR`);
4170
+ } else if (!codeReviewBinding.briefHash) {
4171
+ problems.push(`the newest code-review verdict was cast against brief hash ${codeReview.briefHash ?? "none"}, the frozen brief's current hash is ${currentManifest.briefHash ?? "none"}`);
4172
+ } else if (!codeReviewBinding.policyDigest) {
4173
+ problems.push(`the newest code-review verdict was cast against review policy digest ${codeReview.policyDigest ?? "none"}, this repository's current policy digest is ${currentManifest.policyDigest}`);
3791
4174
  }
3792
- if (!securityClean) {
4175
+ if (!securityTextClean) {
3793
4176
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
4177
+ } else if (!securityPolicyClean) {
4178
+ problems.push(`security-review verdict says PASS but carries a finding (${securityPolicyEvaluation.blockingFindings.map((f) => f.severity).join(", ")}) at or above this repository's security policy threshold (${policy.securityThreshold}) — a reviewer's own PASS never overrides policy`);
3794
4179
  } else if (!securityBound) {
3795
4180
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
4181
+ } else if (!securityBinding.base) {
4182
+ problems.push(`the newest security-review verdict was cast against base ${security.baseSha ?? "no recorded base"}, the PR's base is now ${currentManifest.baseSha ?? "unresolved"} (a base-only change under an unchanged candidate — same patch text on a new base is not automatically equivalent)`);
3796
4183
  } else if (!securityObjectivesBound) {
3797
4184
  problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3798
4185
  } else if (!securityRulingsBound) {
3799
4186
  problems.push(`the newest security-review verdict was cast against ruling ordinal ${security.rulingOrdinal ?? "none"}, a newer ruling (ruling ${input.rulingOrdinal}) is now posted on this PR`);
4187
+ } else if (!securityBinding.briefHash) {
4188
+ problems.push(`the newest security-review verdict was cast against brief hash ${security.briefHash ?? "none"}, the frozen brief's current hash is ${currentManifest.briefHash ?? "none"}`);
4189
+ } else if (!securityBinding.policyDigest) {
4190
+ problems.push(`the newest security-review verdict was cast against review policy digest ${security.policyDigest ?? "none"}, this repository's current policy digest is ${currentManifest.policyDigest}`);
3800
4191
  }
3801
4192
  if (!mechanicalChecksClean) {
3802
4193
  problems.push(reportedMechanicalChecks.length === 0 ? "no mechanical checks have reported for this head yet" : `mechanical check(s) not green: ${reportedMechanicalChecks.filter((c) => c.bucket !== "pass").map((c) => `${c.name} (${c.bucket})`).join(", ")}`);
@@ -4488,42 +4879,45 @@ function checkDispatchReadiness(input) {
4488
4879
  const { trancheSlug, task } = input;
4489
4880
  const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
4490
4881
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
4491
- const blockers = [];
4882
+ const blockerDetails = [];
4883
+ const push = (blockerClass, message) => {
4884
+ blockerDetails.push({ class: blockerClass, message });
4885
+ };
4492
4886
  if (task.issue === null) {
4493
- blockers.push(`dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
4887
+ push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
4494
4888
  } else if (input.issue === null) {
4495
- blockers.push(`dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
4889
+ push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
4496
4890
  }
4497
4891
  if (input.issue !== null && !input.issueRationalePass) {
4498
- blockers.push(`dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
4892
+ push("rationale", `dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
4499
4893
  }
4500
4894
  for (const dep of input.dependsOn) {
4501
4895
  if (isSelfDependency(dep, task, input.issue)) {
4502
4896
  const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
4503
- blockers.push(`dispatch-gate INTERNAL: parsed a self-dependency for task ${task.id} (${issueStr}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream. Re-run once the rationale is corrected or the fix ships.`);
4897
+ push("internal-self-dependency", `dispatch-gate INTERNAL: parsed a self-dependency for task ${task.id} (${issueStr}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream. Re-run once the rationale is corrected or the fix ships.`);
4504
4898
  continue;
4505
4899
  }
4506
4900
  if (dep.resolved === false) {
4507
- blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on "${dep.id}", which is UNRESOLVABLE — the resolver could not find a matching tranche/task/Issue for this edge (not a claim about merge status). Not dispatchable until the edge is corrected.`);
4901
+ push("depends-on-unresolvable", `dispatch-gate depends-on: ${taskLabel} depends on "${dep.id}", which is UNRESOLVABLE — the resolver could not find a matching tranche/task/Issue for this edge (not a claim about merge status). Not dispatchable until the edge is corrected.`);
4508
4902
  continue;
4509
4903
  }
4510
4904
  if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
4511
4905
  const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
4512
- blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
4906
+ push("depends-on-not-merged", `dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
4513
4907
  }
4514
4908
  }
4515
4909
  for (const c of input.conflictsWith) {
4516
4910
  if (c.openOrInFlight) {
4517
4911
  const issueStr = c.issue !== null ? ` (#${c.issue})` : "";
4518
- blockers.push(`dispatch-gate conflicts-with: ${taskLabel} conflicts with ${c.id}${issueStr}, whose PR is open or in-flight — not dispatchable until it merges.`);
4912
+ push("conflicts-with", `dispatch-gate conflicts-with: ${taskLabel} conflicts with ${c.id}${issueStr}, whose PR is open or in-flight — not dispatchable until it merges.`);
4519
4913
  }
4520
4914
  }
4521
4915
  for (const proj of input.priorTrancheArchival) {
4522
4916
  if (proj.priorTrancheSlug !== null && !proj.archived) {
4523
- blockers.push(`dispatch-gate prior-tranche-archival: project \`${proj.project}\`'s previous tranche \`${proj.priorTrancheSlug}\` is not archived — the Tranche Archivist must run before new work on this product.`);
4917
+ push("prior-tranche-archival", `dispatch-gate prior-tranche-archival: project \`${proj.project}\`'s previous tranche \`${proj.priorTrancheSlug}\` is not archived — the Tranche Archivist must run before new work on this product.`);
4524
4918
  }
4525
4919
  }
4526
- return { ready: blockers.length === 0, blockers };
4920
+ return { ready: blockerDetails.length === 0, blockers: blockerDetails.map((b) => b.message), blockerDetails };
4527
4921
  }
4528
4922
  // ../../packages/aeg-core/src/single-plan-pr.ts
4529
4923
  function trancheSlugFromTopologyPath(path) {
@@ -4625,6 +5019,137 @@ function checkBranchTopology(input) {
4625
5019
  reason: `Branch \`${branch}\` matches topology row \`${taskId}\` in ${topoPath}.`
4626
5020
  };
4627
5021
  }
5022
+ // ../../packages/aeg-core/src/task-tools.ts
5023
+ import { z } from "zod";
5024
+ var TASK_TOOL_ERROR_KINDS = [
5025
+ "validation",
5026
+ "authority",
5027
+ "precondition",
5028
+ "capability",
5029
+ "infrastructure",
5030
+ "cancellation",
5031
+ "timeout",
5032
+ "uncertain_effect"
5033
+ ];
5034
+ var TaskToolErrorSchema = z.object({
5035
+ kind: z.enum(TASK_TOOL_ERROR_KINDS),
5036
+ message: z.string().min(1),
5037
+ detail: z.string().optional()
5038
+ });
5039
+ var TaskToolRefSchema = z.union([
5040
+ z.object({ tranche: z.string().min(1), id: z.string().min(1) }).strict(),
5041
+ z.object({ issue: z.number().int().positive() }).strict()
5042
+ ]);
5043
+ var MAX_PAGE_LIMIT = 100;
5044
+ var PageRequestSchema = z.object({
5045
+ cursor: z.string().optional(),
5046
+ limit: z.number().int().positive().max(MAX_PAGE_LIMIT).optional()
5047
+ });
5048
+ var FreshnessSchema = z.enum(["fresh", "stale", "unknown"]);
5049
+ var ObservedSchema = z.object({
5050
+ observedAt: z.string(),
5051
+ freshness: FreshnessSchema
5052
+ });
5053
+ var TaskStatusInputSchema = z.object({
5054
+ task: TaskToolRefSchema.optional()
5055
+ }).merge(PageRequestSchema).strict();
5056
+ var TaskStatusItemSchema = z.object({
5057
+ task: TaskToolRefSchema,
5058
+ issue: z.number().int().positive(),
5059
+ pr: z.number().int().positive().nullable(),
5060
+ state: z.string()
5061
+ }).merge(ObservedSchema);
5062
+ var TaskStatusResultSchema = z.object({
5063
+ items: z.array(TaskStatusItemSchema),
5064
+ nextCursor: z.string().nullable()
5065
+ });
5066
+ var RequestedAuthoritySchema = z.enum(["planner", "principal", "operator", "self"]);
5067
+ var EscalationInputsSchema = z.object({
5068
+ task: z.number().int().positive(),
5069
+ round: z.number().int().nonnegative(),
5070
+ head: z.string(),
5071
+ branch: z.string(),
5072
+ prNumber: z.number().int().positive()
5073
+ });
5074
+ var EscalationEvidenceSchema = z.object({
5075
+ round: z.number().int().nonnegative(),
5076
+ reviewer: z.string().nullable(),
5077
+ security: z.string().nullable()
5078
+ });
5079
+ var EscalationRunIdentitySchema = z.object({
5080
+ runId: z.string(),
5081
+ pid: z.number().int().positive(),
5082
+ host: z.string()
5083
+ });
5084
+ var EscalationInputVersionsSchema = z.object({
5085
+ briefHash: z.string().nullable(),
5086
+ objectivesVersion: z.string().nullable(),
5087
+ rulingOrdinal: z.number().int().nonnegative(),
5088
+ policyDigest: z.string()
5089
+ });
5090
+ var TaskEscalationReadInputSchema = z.object({
5091
+ task: TaskToolRefSchema
5092
+ }).merge(PageRequestSchema).strict();
5093
+ var TaskEscalationPacketSchema = z.object({
5094
+ reason: z.string(),
5095
+ detail: z.string().nullable(),
5096
+ inputs: EscalationInputsSchema.nullable(),
5097
+ evidence: EscalationEvidenceSchema.nullable(),
5098
+ attemptedRecovery: z.string(),
5099
+ requestedAuthority: RequestedAuthoritySchema,
5100
+ permittedNextActions: z.array(z.string()),
5101
+ runIdentity: EscalationRunIdentitySchema.nullable(),
5102
+ inputVersions: EscalationInputVersionsSchema.nullable()
5103
+ }).merge(ObservedSchema);
5104
+ var TaskEscalationReadResultSchema = z.object({
5105
+ items: z.array(TaskEscalationPacketSchema),
5106
+ nextCursor: z.string().nullable()
5107
+ }).merge(ObservedSchema);
5108
+ var TaskStartInputSchema = z.object({
5109
+ tranche: z.string().min(1),
5110
+ id: z.string().min(1)
5111
+ }).strict();
5112
+ var TaskStartResultSchema = z.object({
5113
+ requestId: z.string().min(1),
5114
+ run: z.object({ tranche: z.string().min(1), id: z.string().min(1) }),
5115
+ started: z.boolean(),
5116
+ startedAt: z.string(),
5117
+ mode: z.literal("attended")
5118
+ });
5119
+ var TaskResumeInputSchema = z.object({
5120
+ task: TaskToolRefSchema
5121
+ }).strict();
5122
+ var TaskCancelInputSchema = z.object({
5123
+ task: TaskToolRefSchema,
5124
+ reason: z.string().min(1)
5125
+ }).strict();
5126
+ var TaskResumeResultSchema = z.object({
5127
+ task: z.number().int().positive(),
5128
+ pr: z.number().int().positive(),
5129
+ escalationId: z.string().min(1),
5130
+ outcome: z.enum(["started", "already_resumed"]),
5131
+ authenticatedBy: z.string().min(1),
5132
+ authenticatedFrom: z.string().min(1)
5133
+ });
5134
+ var TaskCancelOutcomeSchema = z.enum(["confirmed", "pending", "uncertain"]);
5135
+ var TaskCancelResultSchema = z.object({
5136
+ task: z.number().int().positive(),
5137
+ pr: z.number().int().positive(),
5138
+ escalationId: z.string().min(1),
5139
+ outcome: TaskCancelOutcomeSchema,
5140
+ authenticatedBy: z.string().min(1),
5141
+ authenticatedFrom: z.string().min(1),
5142
+ fencedEffectKeys: z.array(z.string())
5143
+ });
5144
+ var TASK_TOOL_NAMES = [
5145
+ "task_start",
5146
+ "task_status",
5147
+ "task_escalation_read",
5148
+ "task_resume",
5149
+ "task_cancel"
5150
+ ];
5151
+ var OPERATOR_STATUS_FOLLOW = "task_status_follow";
5152
+ var OPERATOR_TOOL_GRANT = [...TASK_TOOL_NAMES, OPERATOR_STATUS_FOLLOW];
4628
5153
  // ../../packages/aeg-core/src/first-push-dispatch-gate.ts
4629
5154
  function parseTaskBranch(branch) {
4630
5155
  const m = /^task\/([^/]+)\/([^/]+)$/.exec(branch);
@@ -4708,7 +5233,6 @@ function decideIssueAssignment(input) {
4708
5233
  };
4709
5234
  }
4710
5235
  // ../../packages/aeg-core/src/test-plan-gate.ts
4711
- var TASK_BRANCH_PATTERN2 = /^task\/[^/]+\/[^/]+$/;
4712
5236
  function evaluateTestPlanGate(body, branch) {
4713
5237
  if (!body) {
4714
5238
  return {
@@ -4721,7 +5245,7 @@ function evaluateTestPlanGate(body, branch) {
4721
5245
  }
4722
5246
  const located = locateTestPlanSection(body);
4723
5247
  if (!located.found) {
4724
- if (TASK_BRANCH_PATTERN2.test(branch)) {
5248
+ if (parseTaskBranchIdentity(branch) !== null) {
4725
5249
  return {
4726
5250
  verdict: "fail",
4727
5251
  messages: [
@@ -4848,8 +5372,11 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4848
5372
  }
4849
5373
  return findings;
4850
5374
  }
5375
+ // ../../packages/aeg-core/src/log/envelope.ts
5376
+ import { createHash as createHash5 } from "node:crypto";
5377
+
4851
5378
  // ../../packages/aeg-core/src/log/schema.ts
4852
- import { z } from "zod";
5379
+ import { z as z2 } from "zod";
4853
5380
  var ROLE_VALUES = [
4854
5381
  "planner",
4855
5382
  "developer",
@@ -4859,136 +5386,174 @@ var ROLE_VALUES = [
4859
5386
  "archivist",
4860
5387
  "architect"
4861
5388
  ];
4862
- var RoleSchema = z.enum(ROLE_VALUES);
5389
+ var RoleSchema = z2.enum(ROLE_VALUES);
4863
5390
  var HOST_VALUES = ["hook", "ci", "cli", "loop"];
4864
- var HostSchema = z.enum(HOST_VALUES);
5391
+ var HostSchema = z2.enum(HOST_VALUES);
4865
5392
  var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
4866
- var HeaderMetaSchema = z.object({
4867
- schema: z.literal(1),
4868
- ts: z.string(),
4869
- run_id: z.string().regex(RUN_ID_PATTERN),
4870
- seq: z.number().int().nonnegative(),
4871
- repo: z.string().nullable(),
4872
- vinaya: z.string(),
4873
- doctrine: z.string(),
5393
+ var headerMetaCore = {
5394
+ ts: z2.string(),
5395
+ run_id: z2.string().regex(RUN_ID_PATTERN),
5396
+ seq: z2.number().int().nonnegative(),
5397
+ repo: z2.string().nullable(),
5398
+ vinaya: z2.string(),
5399
+ doctrine: z2.string(),
4874
5400
  host: HostSchema,
4875
- machine: z.string()
5401
+ machine: z2.string()
5402
+ };
5403
+ var HeaderMetaV1Schema = z2.object({
5404
+ schema: z2.literal(1),
5405
+ ...headerMetaCore
5406
+ }).strict();
5407
+ var LineageSchema = z2.object({
5408
+ run: z2.string().nullable(),
5409
+ attempt: z2.number().int().nullable(),
5410
+ parent: z2.string().nullable()
4876
5411
  }).strict();
4877
- var SubjectSchema = z.object({
4878
- issue: z.number().int().nullable(),
4879
- pr: z.number().int().optional(),
4880
- sha: z.string().optional(),
4881
- role: z.union([RoleSchema, z.literal("unattributed")]),
4882
- round: z.number().int().optional(),
4883
- objectives_version: z.string().optional()
5412
+ var InputVersionsSchema = z2.object({
5413
+ objectives_version: z2.string().nullable(),
5414
+ brief_hash: z2.string().nullable(),
5415
+ ruling_ordinal: z2.number().int().nullable(),
5416
+ policy_digest: z2.string().nullable()
4884
5417
  }).strict();
4885
- var HeaderSchema = z.object({
5418
+ var ProvenanceSchema = z2.enum(["parent_attributed", "env_correlated", "self_reported", "unavailable"]);
5419
+ var HeaderMetaV2Schema = z2.object({
5420
+ schema: z2.literal(2),
5421
+ ...headerMetaCore,
5422
+ event_id: z2.string().min(1),
5423
+ process_id: z2.string().min(1),
5424
+ actor_id: z2.string().nullable(),
5425
+ lineage: LineageSchema,
5426
+ input_versions: InputVersionsSchema,
5427
+ provenance: ProvenanceSchema
5428
+ }).strict();
5429
+ var HeaderMetaSchema = z2.discriminatedUnion("schema", [HeaderMetaV1Schema, HeaderMetaV2Schema]);
5430
+ var SubjectSchema = z2.object({
5431
+ issue: z2.number().int().nullable(),
5432
+ pr: z2.number().int().optional(),
5433
+ sha: z2.string().optional(),
5434
+ role: z2.union([RoleSchema, z2.literal("unattributed")]),
5435
+ round: z2.number().int().optional(),
5436
+ objectives_version: z2.string().optional()
5437
+ }).strict();
5438
+ var HeaderSchema = z2.object({
4886
5439
  meta: HeaderMetaSchema,
4887
5440
  subject: SubjectSchema
4888
5441
  }).strict();
4889
5442
  var envelopeTail = {
4890
- duration_ms: z.number().nonnegative().optional(),
4891
- payload: z.object({}).strict()
5443
+ duration_ms: z2.number().nonnegative().optional(),
5444
+ payload: z2.object({}).strict()
4892
5445
  };
4893
- var DispatchOutcomeSchema = z.discriminatedUnion("type", [
4894
- z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
4895
- z.object({
4896
- type: z.literal("round_pushed"),
4897
- pr: z.number().int(),
4898
- head: z.string(),
4899
- comment_id: z.number().int()
5446
+ var ReviewFindingSchema = z2.object({
5447
+ id: z2.string(),
5448
+ severity: z2.string(),
5449
+ state: z2.string().optional(),
5450
+ severity_scale: z2.string().optional(),
5451
+ policy_treatment: z2.enum(["blocking", "non_blocking", "unavailable"]).optional(),
5452
+ confidence: z2.number().min(0).max(1).optional(),
5453
+ confidence_scale: z2.string().optional(),
5454
+ confidence_source: z2.string().optional()
5455
+ }).strict();
5456
+ var DispatchOutcomeSchema = z2.discriminatedUnion("type", [
5457
+ z2.object({ type: z2.literal("pr_opened"), pr: z2.number().int(), head: z2.string() }).strict(),
5458
+ z2.object({
5459
+ type: z2.literal("round_pushed"),
5460
+ pr: z2.number().int(),
5461
+ head: z2.string(),
5462
+ comment_id: z2.number().int()
4900
5463
  }).strict(),
4901
- z.object({
4902
- type: z.literal("verdict"),
4903
- verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
4904
- head: z.string(),
4905
- comment_id: z.number().int(),
4906
- objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
4907
- findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
5464
+ z2.object({
5465
+ type: z2.literal("verdict"),
5466
+ verdict: z2.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
5467
+ head: z2.string(),
5468
+ comment_id: z2.number().int(),
5469
+ objectives: z2.array(z2.object({ id: z2.string(), met: z2.boolean() }).strict()),
5470
+ findings: z2.array(ReviewFindingSchema)
4908
5471
  }).strict(),
4909
- z.object({
4910
- type: z.literal("escalation"),
4911
- class: z.enum(["authority", "strategy", "product"]),
4912
- comment_id: z.number().int()
5472
+ z2.object({
5473
+ type: z2.literal("escalation"),
5474
+ class: z2.enum(["authority", "strategy", "product"]),
5475
+ comment_id: z2.number().int()
4913
5476
  }).strict(),
4914
- z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
4915
- z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
4916
- z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
5477
+ z2.object({ type: z2.literal("brief"), comment_id: z2.number().int(), hash: z2.string() }).strict(),
5478
+ z2.object({ type: z2.literal("plan"), issues: z2.array(z2.number().int()) }).strict(),
5479
+ z2.object({ type: z2.literal("archive"), provenance_comment_id: z2.number().int() }).strict(),
5480
+ z2.object({ type: z2.literal("completed") }).strict()
4917
5481
  ]);
4918
5482
  var dispatchShared = {
4919
5483
  meta: HeaderMetaSchema,
4920
5484
  subject: SubjectSchema,
4921
- kind: z.literal("dispatch"),
5485
+ kind: z2.literal("dispatch"),
4922
5486
  ...envelopeTail,
4923
5487
  target_role: RoleSchema,
4924
- model: z.string(),
4925
- round: z.number().int().optional(),
4926
- effect_id: z.string()
5488
+ model: z2.string(),
5489
+ round: z2.number().int().optional(),
5490
+ effect_id: z2.string()
4927
5491
  };
4928
- var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
4929
- var DispatchEventSchema = z.discriminatedUnion("event", [
4930
- z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
4931
- z.object({
5492
+ var dispatchUsageField = z2.object({ input: z2.number().nonnegative(), output: z2.number().nonnegative() }).strict().nullable();
5493
+ var DispatchEventSchema = z2.discriminatedUnion("event", [
5494
+ z2.object({ ...dispatchShared, event: z2.literal("dispatched"), prompt_hash: z2.string() }).strict(),
5495
+ z2.object({
4932
5496
  ...dispatchShared,
4933
- event: z.literal("outcome_received"),
5497
+ event: z2.literal("outcome_received"),
4934
5498
  outcome: DispatchOutcomeSchema,
4935
5499
  usage: dispatchUsageField
4936
5500
  }).strict(),
4937
- z.object({
5501
+ z2.object({
4938
5502
  ...dispatchShared,
4939
- event: z.literal("dispatch_failed"),
4940
- reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
5503
+ event: z2.literal("dispatch_failed"),
5504
+ reason: z2.enum(["timeout", "crash", "refused", "unattributed_write"]),
4941
5505
  usage: dispatchUsageField
4942
5506
  }).strict()
4943
5507
  ]);
4944
5508
  var loopShared = {
4945
5509
  meta: HeaderMetaSchema,
4946
5510
  subject: SubjectSchema,
4947
- kind: z.literal("dev_review_loop"),
5511
+ kind: z2.literal("dev_review_loop"),
4948
5512
  ...envelopeTail,
4949
- loop_id: z.string()
5513
+ loop_id: z2.string()
4950
5514
  };
4951
- var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4952
- z.object({
5515
+ var DevReviewLoopEventSchema = z2.discriminatedUnion("event", [
5516
+ z2.object({
4953
5517
  ...loopShared,
4954
- event: z.literal("loop_started"),
4955
- task: z.number().int(),
4956
- policy: z.object({
4957
- max_rounds: z.number().int().nonnegative(),
4958
- reviewers: z.array(RoleSchema),
4959
- models: z.record(RoleSchema, z.string())
5518
+ event: z2.literal("loop_started"),
5519
+ task: z2.number().int(),
5520
+ policy: z2.object({
5521
+ max_rounds: z2.number().int().nonnegative(),
5522
+ reviewers: z2.array(RoleSchema),
5523
+ models: z2.record(RoleSchema, z2.string())
4960
5524
  }).strict()
4961
5525
  }).strict(),
4962
- z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
4963
- z.object({
5526
+ z2.object({ ...loopShared, event: z2.literal("round_started"), round: z2.number().int(), base_head: z2.string() }).strict(),
5527
+ z2.object({
4964
5528
  ...loopShared,
4965
- event: z.literal("gate_result_read"),
4966
- round: z.number().int(),
4967
- head: z.string(),
4968
- green: z.boolean()
5529
+ event: z2.literal("gate_result_read"),
5530
+ round: z2.number().int(),
5531
+ head: z2.string(),
5532
+ green: z2.boolean()
4969
5533
  }).strict(),
4970
- z.object({
5534
+ z2.object({
4971
5535
  ...loopShared,
4972
- event: z.literal("verdicts_read"),
4973
- round: z.number().int(),
4974
- head: z.string(),
4975
- all_approve: z.boolean(),
4976
- blockers: z.number().int().nonnegative()
5536
+ event: z2.literal("verdicts_read"),
5537
+ round: z2.number().int(),
5538
+ head: z2.string(),
5539
+ all_approve: z2.boolean(),
5540
+ blockers: z2.number().int().nonnegative(),
5541
+ findings: z2.array(ReviewFindingSchema).optional()
4977
5542
  }).strict(),
4978
- z.object({
5543
+ z2.object({
4979
5544
  ...loopShared,
4980
- event: z.literal("findings_compared"),
4981
- round: z.number().int(),
4982
- open: z.array(z.string()),
4983
- resolved: z.array(z.string()),
4984
- new: z.array(z.string()),
4985
- recurring: z.array(z.string())
5545
+ event: z2.literal("findings_compared"),
5546
+ round: z2.number().int(),
5547
+ open: z2.array(z2.string()),
5548
+ resolved: z2.array(z2.string()),
5549
+ new: z2.array(z2.string()),
5550
+ recurring: z2.array(z2.string())
4986
5551
  }).strict(),
4987
- z.object({
5552
+ z2.object({
4988
5553
  ...loopShared,
4989
- event: z.literal("stop_condition_met"),
4990
- round: z.number().int(),
4991
- condition: z.enum([
5554
+ event: z2.literal("stop_condition_met"),
5555
+ round: z2.number().int(),
5556
+ condition: z2.enum([
4992
5557
  "green",
4993
5558
  "max_rounds",
4994
5559
  "no_progress",
@@ -4998,42 +5563,49 @@ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4998
5563
  "reappearance"
4999
5564
  ])
5000
5565
  }).strict(),
5001
- z.object({
5566
+ z2.object({
5567
+ ...loopShared,
5568
+ event: z2.literal("paused"),
5569
+ round: z2.number().int(),
5570
+ reason: z2.enum(["escalation", "principal_item", "refreeze_needed"])
5571
+ }).strict(),
5572
+ z2.object({
5002
5573
  ...loopShared,
5003
- event: z.literal("paused"),
5004
- round: z.number().int(),
5005
- reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
5574
+ event: z2.literal("resumed"),
5575
+ round: z2.number().int(),
5576
+ by: z2.literal("principal")
5006
5577
  }).strict(),
5007
- z.object({
5578
+ z2.object({
5008
5579
  ...loopShared,
5009
- event: z.literal("resumed"),
5010
- round: z.number().int(),
5011
- by: z.literal("principal")
5580
+ event: z2.literal("unpushed_work_resume"),
5581
+ round: z2.number().int(),
5582
+ branch: z2.string(),
5583
+ detail: z2.string()
5012
5584
  }).strict(),
5013
- z.object({
5585
+ z2.object({
5014
5586
  ...loopShared,
5015
- event: z.literal("round_ended"),
5016
- round: z.number().int(),
5017
- base_head: z.string(),
5018
- head: z.string(),
5019
- files_changed: z.number().int().nonnegative(),
5020
- insertions: z.number().int().nonnegative(),
5021
- deletions: z.number().int().nonnegative(),
5022
- wall_ms: z.number().nonnegative(),
5023
- outcome: z.enum(["green", "changes_requested", "escalated"])
5587
+ event: z2.literal("round_ended"),
5588
+ round: z2.number().int(),
5589
+ base_head: z2.string(),
5590
+ head: z2.string(),
5591
+ files_changed: z2.number().int().nonnegative(),
5592
+ insertions: z2.number().int().nonnegative(),
5593
+ deletions: z2.number().int().nonnegative(),
5594
+ wall_ms: z2.number().nonnegative(),
5595
+ outcome: z2.enum(["green", "changes_requested", "escalated"])
5024
5596
  }).strict(),
5025
- z.object({
5597
+ z2.object({
5026
5598
  ...loopShared,
5027
- event: z.literal("journal_finalized"),
5028
- rounds: z.number().int().nonnegative(),
5029
- total_wall_ms: z.number().nonnegative(),
5030
- time_to_green_ms: z.number().nonnegative().nullable(),
5031
- files_changed_total: z.number().int().nonnegative(),
5032
- final_head: z.string(),
5033
- result: z.enum(["merged_ready", "stopped"])
5599
+ event: z2.literal("journal_finalized"),
5600
+ rounds: z2.number().int().nonnegative(),
5601
+ total_wall_ms: z2.number().nonnegative(),
5602
+ time_to_green_ms: z2.number().nonnegative().nullable(),
5603
+ files_changed_total: z2.number().int().nonnegative(),
5604
+ final_head: z2.string(),
5605
+ result: z2.enum(["merged_ready", "stopped"])
5034
5606
  }).strict()
5035
5607
  ]);
5036
- var ForgeOpSchema = z.enum([
5608
+ var ForgeOpSchema = z2.enum([
5037
5609
  "pr.create",
5038
5610
  "pr.comment",
5039
5611
  "pr.body.replace",
@@ -5047,24 +5619,434 @@ var ForgeOpSchema = z.enum([
5047
5619
  "label.add",
5048
5620
  "label.remove"
5049
5621
  ]);
5050
- var ForgeWriteTargetSchema = z.object({
5051
- issue: z.number().int().optional(),
5052
- pr: z.number().int().optional()
5622
+ var ForgeWriteTargetSchema = z2.object({
5623
+ issue: z2.number().int().optional(),
5624
+ pr: z2.number().int().optional()
5053
5625
  }).strict();
5054
5626
  var forgeWriteShared = {
5055
5627
  meta: HeaderMetaSchema,
5056
5628
  subject: SubjectSchema,
5057
- kind: z.literal("forge_write"),
5629
+ kind: z2.literal("forge_write"),
5058
5630
  ...envelopeTail,
5059
5631
  op: ForgeOpSchema,
5060
5632
  target: ForgeWriteTargetSchema
5061
5633
  };
5062
- var ForgeWriteEventSchema = z.discriminatedUnion("event", [
5063
- z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
5064
- z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
5065
- z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
5634
+ var ForgeWriteEventSchema = z2.discriminatedUnion("event", [
5635
+ z2.object({ ...forgeWriteShared, event: z2.literal("validated") }).strict(),
5636
+ z2.object({ ...forgeWriteShared, event: z2.literal("refused"), reason: z2.string() }).strict(),
5637
+ z2.object({ ...forgeWriteShared, event: z2.literal("written"), comment_ids: z2.array(z2.string()) }).strict()
5638
+ ]);
5639
+ var GateOutcomeSchema = z2.enum([
5640
+ "pass",
5641
+ "fail",
5642
+ "wait",
5643
+ "skip",
5644
+ "invalid_input",
5645
+ "unavailable_dependency",
5646
+ "timeout",
5647
+ "cancelled"
5648
+ ]);
5649
+ var gateShared = {
5650
+ meta: HeaderMetaSchema,
5651
+ subject: SubjectSchema,
5652
+ kind: z2.literal("gate"),
5653
+ ...envelopeTail,
5654
+ check: z2.string(),
5655
+ check_version: z2.string().nullable(),
5656
+ policy_version: z2.string().nullable(),
5657
+ input_fingerprint: z2.string().nullable()
5658
+ };
5659
+ var GateEventSchema = z2.discriminatedUnion("event", [
5660
+ z2.object({
5661
+ ...gateShared,
5662
+ event: z2.literal("checked"),
5663
+ outcome: GateOutcomeSchema,
5664
+ reason: z2.string().optional()
5665
+ }).strict()
5666
+ ]);
5667
+ var OperationResultSchema = z2.enum(["ok", "error", "refused", "timeout", "cancelled", "unavailable"]);
5668
+ var operationShared = {
5669
+ meta: HeaderMetaSchema,
5670
+ subject: SubjectSchema,
5671
+ kind: z2.literal("operation"),
5672
+ ...envelopeTail,
5673
+ operation: z2.string(),
5674
+ target: z2.string().nullable()
5675
+ };
5676
+ var OperationEventSchema = z2.discriminatedUnion("event", [
5677
+ z2.object({
5678
+ ...operationShared,
5679
+ event: z2.literal("completed"),
5680
+ result: OperationResultSchema,
5681
+ error_class: z2.string().nullable()
5682
+ }).strict()
5683
+ ]);
5684
+ var UsageUnitsSchema = z2.object({
5685
+ input: z2.number().nonnegative().nullable(),
5686
+ output: z2.number().nonnegative().nullable(),
5687
+ cache: z2.number().nonnegative().nullable()
5688
+ }).strict();
5689
+ var usageShared = {
5690
+ meta: HeaderMetaSchema,
5691
+ subject: SubjectSchema,
5692
+ kind: z2.literal("usage"),
5693
+ ...envelopeTail,
5694
+ model: z2.string().nullable(),
5695
+ source: z2.string(),
5696
+ semantics: z2.enum(["cumulative", "delta"])
5697
+ };
5698
+ var UsageEventSchema = z2.discriminatedUnion("event", [
5699
+ z2.object({
5700
+ ...usageShared,
5701
+ event: z2.literal("observed"),
5702
+ units: UsageUnitsSchema,
5703
+ unknown_reason: z2.string().nullable()
5704
+ }).strict()
5705
+ ]);
5706
+ var RoleAttemptOutcomeSchema = z2.enum([
5707
+ "completed",
5708
+ "incomplete",
5709
+ "infrastructure_failed",
5710
+ "cancelled",
5711
+ "timed_out",
5712
+ "capability_refused"
5713
+ ]);
5714
+ var roleAttemptShared = {
5715
+ meta: HeaderMetaSchema,
5716
+ subject: SubjectSchema,
5717
+ kind: z2.literal("role_attempt"),
5718
+ ...envelopeTail,
5719
+ actor: z2.string().nullable(),
5720
+ attempt: z2.number().int().nullable(),
5721
+ effect_id: z2.string(),
5722
+ model: z2.string().nullable()
5723
+ };
5724
+ var RoleAttemptEventSchema = z2.discriminatedUnion("event", [
5725
+ z2.object({
5726
+ ...roleAttemptShared,
5727
+ event: z2.literal("attempted"),
5728
+ outcome: RoleAttemptOutcomeSchema,
5729
+ usage: dispatchUsageField
5730
+ }).strict()
5731
+ ]);
5732
+ var handoffShared = {
5733
+ meta: HeaderMetaSchema,
5734
+ subject: SubjectSchema,
5735
+ kind: z2.literal("handoff"),
5736
+ ...envelopeTail,
5737
+ class: z2.enum(["authority", "strategy", "product"]),
5738
+ reason: z2.string()
5739
+ };
5740
+ var HandoffEventSchema = z2.discriminatedUnion("event", [
5741
+ z2.object({
5742
+ ...handoffShared,
5743
+ event: z2.literal("raised"),
5744
+ requested_decision: z2.string().nullable()
5745
+ }).strict(),
5746
+ z2.object({
5747
+ ...handoffShared,
5748
+ event: z2.literal("resolved"),
5749
+ resolution: z2.string().nullable(),
5750
+ resolved_by: z2.string().nullable()
5751
+ }).strict()
5752
+ ]);
5753
+ var EffectTargetSchema = z2.object({
5754
+ kind: z2.string(),
5755
+ ref: z2.string()
5756
+ }).strict();
5757
+ var effectShared = {
5758
+ meta: HeaderMetaSchema,
5759
+ subject: SubjectSchema,
5760
+ kind: z2.literal("effect"),
5761
+ ...envelopeTail,
5762
+ effect_id: z2.string(),
5763
+ target: EffectTargetSchema
5764
+ };
5765
+ var EffectEventSchema = z2.discriminatedUnion("event", [
5766
+ z2.object({ ...effectShared, event: z2.literal("attempted") }).strict(),
5767
+ z2.object({ ...effectShared, event: z2.literal("observed"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict(),
5768
+ z2.object({ ...effectShared, event: z2.literal("verified"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict()
5769
+ ]);
5770
+ var LogEventSchema = z2.union([
5771
+ DispatchEventSchema,
5772
+ DevReviewLoopEventSchema,
5773
+ ForgeWriteEventSchema,
5774
+ GateEventSchema,
5775
+ OperationEventSchema,
5776
+ UsageEventSchema,
5777
+ RoleAttemptEventSchema,
5778
+ HandoffEventSchema,
5779
+ EffectEventSchema
5066
5780
  ]);
5067
- var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
5781
+
5782
+ // ../../packages/aeg-core/src/log/envelope.ts
5783
+ function isRole(value) {
5784
+ return value !== undefined && ROLE_VALUES.includes(value);
5785
+ }
5786
+ function actorIdFromEnv(role) {
5787
+ return role === undefined || role === "" ? null : role;
5788
+ }
5789
+ function lineageFieldFromEnv(value) {
5790
+ return value === undefined || value === "" ? null : value;
5791
+ }
5792
+ function attemptFromEnv(value) {
5793
+ if (value === undefined || value === "")
5794
+ return null;
5795
+ const n = Number(value);
5796
+ return Number.isInteger(n) ? n : null;
5797
+ }
5798
+ function provenanceFor(input) {
5799
+ if (input.provenance)
5800
+ return input.provenance;
5801
+ if (input.env.role || input.env.task)
5802
+ return "env_correlated";
5803
+ return "unavailable";
5804
+ }
5805
+ function issueFromTask(task) {
5806
+ if (task === undefined || task === "")
5807
+ return null;
5808
+ const n = Number(task);
5809
+ return Number.isInteger(n) ? n : null;
5810
+ }
5811
+ function roundFromEnv(round) {
5812
+ if (round === undefined || round === "")
5813
+ return;
5814
+ const n = Number(round);
5815
+ return Number.isInteger(n) ? n : undefined;
5816
+ }
5817
+ function buildHeader(input) {
5818
+ const role = isRole(input.env.role) ? input.env.role : "unattributed";
5819
+ const round = roundFromEnv(input.env.round);
5820
+ const subject = {
5821
+ issue: issueFromTask(input.env.task),
5822
+ role,
5823
+ ...round !== undefined ? { round } : {},
5824
+ ...input.subject ?? {}
5825
+ };
5826
+ return {
5827
+ meta: {
5828
+ schema: 2,
5829
+ ts: input.now.toISOString(),
5830
+ run_id: input.runId,
5831
+ seq: input.seq,
5832
+ repo: input.repo,
5833
+ vinaya: input.vinaya,
5834
+ doctrine: input.doctrine,
5835
+ host: input.host,
5836
+ machine: createHash5("sha256").update(input.hostname).digest("hex"),
5837
+ event_id: input.eventId,
5838
+ process_id: input.processId,
5839
+ actor_id: actorIdFromEnv(input.env.role),
5840
+ lineage: {
5841
+ run: lineageFieldFromEnv(input.env.run),
5842
+ attempt: attemptFromEnv(input.env.attempt),
5843
+ parent: lineageFieldFromEnv(input.env.parent)
5844
+ },
5845
+ input_versions: {
5846
+ objectives_version: input.inputVersions?.objectivesVersion ?? null,
5847
+ brief_hash: input.inputVersions?.briefHash ?? null,
5848
+ ruling_ordinal: input.inputVersions?.rulingOrdinal ?? null,
5849
+ policy_digest: input.inputVersions?.policyDigest ?? null
5850
+ },
5851
+ provenance: provenanceFor(input)
5852
+ },
5853
+ subject
5854
+ };
5855
+ }
5856
+ // ../../packages/aeg-core/src/log/redact.ts
5857
+ var GITHUB_TOKEN_PATTERN = /\b(?:gho_|ghp_|ghs_|ghu_|ghr_|github_pat_)[A-Za-z0-9_]+\b/g;
5858
+ var CLASSIC_GITHUB_PAT_PATTERN = /\b((?:token|GITHUB_TOKEN|PAT|api[_-]?key|api[_-]?token)\s*[:=]\s*)[a-f0-9]{40}\b/gi;
5859
+ var BEARER_TOKEN_PATTERN = /\bBearer\s+\S+/g;
5860
+ var AWS_ACCESS_KEY_PATTERN = /\bAKIA[0-9A-Z]{16}\b/g;
5861
+ var SLACK_TOKEN_PATTERN = /\bxox[baprs]-[0-9A-Za-z-]+\b/g;
5862
+ var STRIPE_KEY_PATTERN = /\b(?:sk|pk|rk)_(?:live|test)_[0-9A-Za-z]{10,}\b/g;
5863
+ var ANTHROPIC_KEY_PATTERN = /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g;
5864
+ var OPENAI_KEY_PATTERN = /\bsk-(?!ant-)[A-Za-z0-9]{20,}\b/g;
5865
+ var NPM_TOKEN_PATTERN = /\bnpm_[A-Za-z0-9]{36}\b/g;
5866
+ var JWT_PATTERN = /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g;
5867
+ var PEM_BLOCK_PATTERN = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g;
5868
+ var BASIC_AUTH_URL_PATTERN = /(https?:\/\/)[^/\s:@]+:[^/\s@]+@/g;
5869
+ var GENERIC_SECRET_ASSIGNMENT_PATTERN = /\b((?:secret|password|passwd|api[_-]?key|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[:=]\s*)[A-Za-z0-9+/_=-]{16,}\b/gi;
5870
+ function redactString(value, home) {
5871
+ let out = value.replace(GITHUB_TOKEN_PATTERN, "<redacted>").replace(CLASSIC_GITHUB_PAT_PATTERN, "$1<redacted>").replace(BEARER_TOKEN_PATTERN, "<redacted>").replace(AWS_ACCESS_KEY_PATTERN, "<redacted>").replace(SLACK_TOKEN_PATTERN, "<redacted>").replace(STRIPE_KEY_PATTERN, "<redacted>").replace(ANTHROPIC_KEY_PATTERN, "<redacted>").replace(OPENAI_KEY_PATTERN, "<redacted>").replace(NPM_TOKEN_PATTERN, "<redacted>").replace(JWT_PATTERN, "<redacted>").replace(PEM_BLOCK_PATTERN, "<redacted>").replace(BASIC_AUTH_URL_PATTERN, "$1<redacted>@").replace(GENERIC_SECRET_ASSIGNMENT_PATTERN, "$1<redacted>");
5872
+ if (home.length > 0 && out.startsWith(home)) {
5873
+ const rest = out.slice(home.length);
5874
+ if (rest === "" || rest.startsWith("/"))
5875
+ out = `~${rest}`;
5876
+ }
5877
+ return out;
5878
+ }
5879
+ function redact(value, home) {
5880
+ if (typeof value === "string")
5881
+ return redactString(value, home);
5882
+ if (Array.isArray(value))
5883
+ return value.map((v) => redact(v, home));
5884
+ if (value !== null && typeof value === "object") {
5885
+ const out = {};
5886
+ for (const [k, v] of Object.entries(value)) {
5887
+ out[k] = redact(v, home);
5888
+ }
5889
+ return out;
5890
+ }
5891
+ return value;
5892
+ }
5893
+ // ../../packages/aeg-core/src/log/store.ts
5894
+ function metaOf(obj) {
5895
+ if (obj === null || typeof obj !== "object")
5896
+ return null;
5897
+ const meta = obj.meta;
5898
+ if (meta === null || typeof meta !== "object")
5899
+ return null;
5900
+ return meta;
5901
+ }
5902
+ function recordIdentity(obj) {
5903
+ const meta = metaOf(obj);
5904
+ if (meta === null)
5905
+ return null;
5906
+ const eventId = meta.event_id;
5907
+ if (typeof eventId === "string" && eventId.length > 0)
5908
+ return eventId;
5909
+ const runId = meta.run_id;
5910
+ const seq = meta.seq;
5911
+ if (typeof runId === "string" && runId.length > 0 && typeof seq === "number" && Number.isInteger(seq)) {
5912
+ return `${runId}:${seq}`;
5913
+ }
5914
+ return null;
5915
+ }
5916
+
5917
+ // ../../packages/aeg-core/src/log/artifact.ts
5918
+ var TASK_LOG_ARTIFACT_MAX_BYTES = 8 * 1024 * 1024;
5919
+ // ../../packages/aeg-core/src/dev-review-loop/journal-reconstruction.ts
5920
+ var STOPPED_CONDITIONS = new Set(["confidence", "reappearance", "no_progress", "max_rounds"]);
5921
+ // ../../packages/aeg-core/src/control-store/records.ts
5922
+ import { z as z3 } from "zod";
5923
+ var isoTimestamp = z3.string().min(1);
5924
+ var taskId = z3.number().int().positive();
5925
+ var epochNumber = z3.number().int().nonnegative();
5926
+ var RunRecordSchema = z3.object({
5927
+ version: z3.literal(1),
5928
+ kind: z3.literal("run"),
5929
+ task: taskId,
5930
+ runId: z3.string().min(1),
5931
+ pid: z3.number().int().positive(),
5932
+ host: z3.string().min(1),
5933
+ startedAt: isoTimestamp,
5934
+ childStartedAt: z3.string().min(1).nullable().optional(),
5935
+ childCommand: z3.string().min(1).nullable().optional()
5936
+ }).strict();
5937
+ var InputRecordSchema = z3.object({
5938
+ version: z3.literal(1),
5939
+ kind: z3.literal("input"),
5940
+ task: taskId,
5941
+ runId: z3.string().min(1),
5942
+ source: z3.union([z3.literal("fresh"), z3.literal("resume")]),
5943
+ pr: z3.number().int().positive().nullable(),
5944
+ round: z3.number().int().nonnegative(),
5945
+ recordedAt: isoTimestamp
5946
+ }).strict();
5947
+ var OwnershipRecordSchema = z3.object({
5948
+ version: z3.literal(1),
5949
+ kind: z3.literal("ownership"),
5950
+ task: taskId,
5951
+ epoch: epochNumber,
5952
+ ownerId: z3.string().min(1),
5953
+ pid: z3.number().int().positive(),
5954
+ host: z3.string().min(1),
5955
+ acquiredAt: isoTimestamp
5956
+ }).strict();
5957
+ var TransitionRecordSchema = z3.object({
5958
+ version: z3.literal(1),
5959
+ kind: z3.literal("transition"),
5960
+ task: taskId,
5961
+ epoch: epochNumber,
5962
+ seq: z3.number().int().nonnegative(),
5963
+ from: z3.string().min(1),
5964
+ to: z3.string().min(1),
5965
+ detail: z3.string().optional(),
5966
+ at: isoTimestamp
5967
+ }).strict();
5968
+ var ManifestRecordSchema = z3.object({
5969
+ version: z3.literal(1),
5970
+ kind: z3.literal("manifest"),
5971
+ task: taskId,
5972
+ round: epochNumber,
5973
+ repository: z3.string().min(1),
5974
+ pr: z3.number().int().positive(),
5975
+ branch: z3.string().min(1),
5976
+ baseSha: z3.string().min(1).nullable(),
5977
+ headSha: z3.string().min(1),
5978
+ briefHash: z3.string().min(1).nullable(),
5979
+ objectivesVersion: z3.string().min(1).nullable(),
5980
+ rulingOrdinal: epochNumber,
5981
+ policyDigest: z3.string().min(1),
5982
+ recordedAt: isoTimestamp
5983
+ }).strict();
5984
+ var roundHeadIdentity = z3.object({ round: z3.number().int().positive(), head: z3.string().min(1) }).strict();
5985
+ var LoopBudgetsSchema = z3.object({
5986
+ mechanicalRetries: z3.number().int().nonnegative(),
5987
+ reviewRounds: z3.number().int().nonnegative(),
5988
+ infrastructureRetries: z3.number().int().nonnegative()
5989
+ }).strict();
5990
+ var LoopStateRecordSchema = z3.object({
5991
+ version: z3.literal(1),
5992
+ kind: z3.literal("loop_state"),
5993
+ task: taskId,
5994
+ round: z3.number().int().positive(),
5995
+ phase: z3.string().min(1),
5996
+ pauseReason: z3.string().min(1).nullable(),
5997
+ budgets: LoopBudgetsSchema,
5998
+ heldResult: roundHeadIdentity.nullable(),
5999
+ deliveredFindings: roundHeadIdentity.nullable(),
6000
+ recordedAt: isoTimestamp
6001
+ }).strict();
6002
+ var EffectRecordSchema = z3.object({
6003
+ version: z3.literal(1),
6004
+ kind: z3.literal("effect"),
6005
+ task: taskId,
6006
+ key: z3.string().min(1),
6007
+ operation: z3.string().min(1),
6008
+ target: z3.string().min(1),
6009
+ inputVersion: z3.number().int().nonnegative(),
6010
+ payloadDigest: z3.string().min(1),
6011
+ status: z3.union([z3.literal("started"), z3.literal("verified"), z3.literal("uncertain")]),
6012
+ url: z3.string().optional(),
6013
+ recordedAt: isoTimestamp
6014
+ }).strict();
6015
+ var EscalationRecordSchema = z3.object({
6016
+ version: z3.literal(1),
6017
+ kind: z3.literal("escalation"),
6018
+ task: taskId,
6019
+ escalationId: z3.string().min(1),
6020
+ round: epochNumber,
6021
+ head: z3.string().min(1),
6022
+ branch: z3.string().min(1),
6023
+ pr: z3.number().int().positive().nullable(),
6024
+ runId: z3.string().min(1),
6025
+ pid: z3.number().int().positive(),
6026
+ host: z3.string().min(1),
6027
+ agent: z3.string().min(1).optional(),
6028
+ reason: z3.string().min(1),
6029
+ detail: z3.string().optional(),
6030
+ evidence: z3.string().optional(),
6031
+ attemptedRecovery: z3.string().min(1),
6032
+ requestedDecision: z3.string().min(1),
6033
+ recipient: z3.string().min(1),
6034
+ briefHash: z3.string().min(1).nullable(),
6035
+ objectivesVersion: z3.string().min(1).nullable(),
6036
+ rulingOrdinal: epochNumber,
6037
+ policyDigest: z3.string().min(1),
6038
+ recordedAt: isoTimestamp
6039
+ }).strict();
6040
+ var ResolutionRecordSchema = z3.object({
6041
+ version: z3.literal(1),
6042
+ kind: z3.literal("resolution"),
6043
+ task: taskId,
6044
+ escalationId: z3.string().min(1),
6045
+ decision: z3.union([z3.literal("resume"), z3.literal("cancel")]),
6046
+ authenticatedBy: z3.string().min(1),
6047
+ authenticatedFrom: z3.string().min(1),
6048
+ consumedAt: isoTimestamp
6049
+ }).strict();
5068
6050
  // ../../packages/sources/src/commands.ts
5069
6051
  var COMMANDS = [
5070
6052
  {
@@ -5223,6 +6205,27 @@ var COMMANDS = [
5223
6205
  ],
5224
6206
  status: "shipped"
5225
6207
  },
6208
+ {
6209
+ name: "task status",
6210
+ description: "Every open task with a frozen brief, its pull request, and whether its loop is running, paused, or published",
6211
+ flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
6212
+ details: [
6213
+ "Read-only: one `gh issue list` for every open task Issue across every tranche (title/label resolved through the same `resolveTaskIssueRef` `list-tasks.ts` already uses), the open pull request per branch, and the driver pid record / pause record / publish effect markers under `<outboxRoot>/dev-review-loop/<task>/` — never a `ps` scan, never a re-parse of posted verdict comments to decide `published`.",
6214
+ "`running` names the driver's pid (`review-validity-v1` task 7's pid record); `paused` names the reason from the pause record; `published` means the newest round's reviewer and security verdict effect markers both read `posted`; `no driver` is the fallback when none of the above holds.",
6215
+ "`vinaya task status <tranche> <n>` narrows to one task and adds the last round's held or published verdict lines (`round-<n>-reviewer.md`/`round-<n>-security.md`, whichever their outbox carries) plus the exact `vinaya dev-review-loop --resume <pr>` command when paused."
6216
+ ],
6217
+ status: "shipped"
6218
+ },
6219
+ {
6220
+ name: "task-tools serve",
6221
+ description: "Run the task-operator MCP tool server over stdio — the command both runtime adapters register",
6222
+ details: [
6223
+ "Exposes the task-operator tool catalog (`packages/aeg-core/src/task-tools.ts`) to an agent runtime as one MCP server speaking newline-delimited JSON-RPC 2.0 (`initialize`/`tools/list`/`tools/call`). Claude registers it through the generated `.mcp.json`; Codex through its documented `~/.codex/config.toml` `[mcp_servers]` table — both point at this same command.",
6224
+ "stdout carries JSON-RPC and nothing else: any stray write from the CLI (a forge-read warning) is redirected to stderr, so the protocol stream is never corrupted.",
6225
+ "`task_status`/`task_escalation_read` read; `task_start` starts a run in attended mode only, requiring an authenticated caller from the invocation context (`VINAYA_MCP_CALLER`) and refusing without one — MCP is a transport, not authorization. `task_resume`/`task_cancel` require the same authenticated caller, plus a current Principal ruling read fresh from the run's own PR — never a caller-supplied approval; `task_resume` triggers the existing `dev-review-loop --resume` continuation exactly once per escalation, and `task_cancel` reports a truthful `confirmed`/`pending`/`uncertain` outcome, idempotently, across repeated calls. No unattended start until the worker-isolation boundary and a capability flag land."
6226
+ ],
6227
+ status: "shipped"
6228
+ },
5226
6229
  {
5227
6230
  name: "pr create",
5228
6231
  description: "Open a pull request after full brief-schema validation",
@@ -5367,6 +6370,30 @@ var COMMANDS = [
5367
6370
  ],
5368
6371
  status: "shipped"
5369
6372
  },
6373
+ {
6374
+ name: "log export-artifact",
6375
+ description: "Copy this run's own local outbox into a file, for the generated task-path workflow's own upload step",
6376
+ flags: [],
6377
+ details: [
6378
+ "Concatenates every `*.ndjson` file under `~/.vinaya/outbox/` into `<dest>`, verbatim — no filtering, no second redaction pass (lines are already redacted at `log()`'s own write boundary). Writes nothing when the outbox is empty, and exits 0 either way: no gate events this run is a legitimate outcome, never a failure.",
6379
+ "Runs inside the generated `vinaya-checks.yml` job, which holds no forge-write credential — this command never calls the forge. Both this step and the `actions/upload-artifact` step after it run `if: always()`, so a cancelled or failed job still exports whatever partial outbox it has."
6380
+ ],
6381
+ status: "shipped"
6382
+ },
6383
+ {
6384
+ name: "log collect-artifact",
6385
+ description: "Validate a downloaded task-log artifact and publish the valid records through the existing flush path",
6386
+ flags: [
6387
+ { flag: "--pr", description: "The pull request this artifact belongs to" },
6388
+ { flag: "--repo", description: "Expected `owner/repo` — a provenance cross-check against each record" }
6389
+ ],
6390
+ details: [
6391
+ "Runs inside the generated `vinaya-task-log-collector.yml` workflow — a `workflow_run`-triggered job on the default branch, holding the write credential the task-path job that produced the artifact never gets. `<path>` is already downloaded by that workflow, scoped to a specific, API-verified run id — a provenance guarantee this command never re-derives from the artifact bytes.",
6392
+ "Never executes, imports, or evaluates the artifact's content: every line is read as text and validated (schema, an 8 MiB whole-artifact size cap, redaction, and a repo cross-check) before anything is trusted. A rejected line becomes a named gap — printed, never a silent drop — and the artifact is never partially trusted past that check into executing anything.",
6393
+ "The valid, re-redacted survivors are appended into the same local outbox `vinaya log flush` would read for that PR's Issue, then published through the existing `flushOutbox` unmodified — reusing its chunking, its `<!-- aeg:log:… -->` marker, and its idempotent-retry read, rather than a second publisher. A retried collector run (a fresh runner, the same forge state) therefore dedupes for free: the marker already on the forge is acknowledged, never re-posted."
6394
+ ],
6395
+ status: "shipped"
6396
+ },
5370
6397
  {
5371
6398
  name: "milestone create",
5372
6399
  description: "Create a GitHub Milestone from a validated body",
@@ -5732,10 +6759,10 @@ function createForgeSource(config) {
5732
6759
  };
5733
6760
  }
5734
6761
  // ../../packages/sources/src/select-source.ts
5735
- import { z as z2 } from "zod";
5736
- var StateSourceConfigSchema = z2.discriminatedUnion("kind", [
5737
- z2.object({ kind: z2.literal("forge"), owner: z2.string(), repo: z2.string() }),
5738
- z2.object({ kind: z2.literal("file"), root: z2.string().optional() })
6762
+ import { z as z4 } from "zod";
6763
+ var StateSourceConfigSchema = z4.discriminatedUnion("kind", [
6764
+ z4.object({ kind: z4.literal("forge"), owner: z4.string(), repo: z4.string() }),
6765
+ z4.object({ kind: z4.literal("file"), root: z4.string().optional() })
5739
6766
  ]);
5740
6767
  // src/checks/contract.ts
5741
6768
  var CHECK_SCHEMA_VERSION = 1;
@@ -5749,27 +6776,29 @@ import { execFileSync as execFileSync2 } from "node:child_process";
5749
6776
  import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
5750
6777
  import { homedir } from "node:os";
5751
6778
  import { dirname, join, resolve } from "node:path";
5752
- import { z as z3 } from "zod";
6779
+ import { z as z5 } from "zod";
5753
6780
 
5754
6781
  // src/lib/agent-vendors.ts
5755
6782
  var AGENT_VENDORS = ["skills", "claude", "gemini"];
5756
6783
 
5757
6784
  // src/lib/config.ts
5758
- var EnvEntrySchema = z3.union([
5759
- z3.literal(true),
5760
- z3.object({ optional: z3.literal(true) }),
5761
- z3.object({ anyOf: z3.array(z3.string()).min(2) }),
5762
- z3.string()
6785
+ var EnvEntrySchema = z5.union([
6786
+ z5.literal(true),
6787
+ z5.object({ optional: z5.literal(true) }),
6788
+ z5.object({ anyOf: z5.array(z5.string()).min(2) }),
6789
+ z5.string()
5763
6790
  ]);
5764
- var CheckEntrySchema = z3.object({
5765
- run: z3.string(),
5766
- scope: z3.enum(["diff", "full"]),
5767
- include: z3.array(z3.string()).optional(),
5768
- args: z3.array(z3.string()).optional(),
5769
- timeoutMs: z3.number().optional(),
5770
- env: z3.record(z3.string(), EnvEntrySchema).optional(),
5771
- requiresOpenPr: z3.boolean().optional(),
5772
- ownWorkflow: z3.boolean().optional()
6791
+ var CheckEntrySchema = z5.object({
6792
+ run: z5.string(),
6793
+ scope: z5.enum(["diff", "full"]),
6794
+ include: z5.array(z5.string()).optional(),
6795
+ args: z5.array(z5.string()).optional(),
6796
+ timeoutMs: z5.number().optional(),
6797
+ env: z5.record(z5.string(), EnvEntrySchema).optional(),
6798
+ requiresOpenPr: z5.boolean().optional(),
6799
+ ownWorkflow: z5.boolean().optional(),
6800
+ principalOwed: z5.literal(true).optional(),
6801
+ validates: z5.enum(["body", "issue"]).optional()
5773
6802
  }).superRefine((entry2, ctx) => {
5774
6803
  if (!entry2.env)
5775
6804
  return;
@@ -5779,25 +6808,26 @@ var CheckEntrySchema = z3.object({
5779
6808
  const members = value.anyOf;
5780
6809
  if (new Set(members).size !== members.length) {
5781
6810
  ctx.addIssue({
5782
- code: z3.ZodIssueCode.custom,
6811
+ code: z5.ZodIssueCode.custom,
5783
6812
  message: `env.${key}.anyOf has duplicate members`,
5784
6813
  path: ["env", key, "anyOf"]
5785
6814
  });
5786
6815
  }
5787
6816
  if (!members.includes(key)) {
5788
6817
  ctx.addIssue({
5789
- code: z3.ZodIssueCode.custom,
6818
+ code: z5.ZodIssueCode.custom,
5790
6819
  message: `env.${key}.anyOf must include "${key}" itself as a member`,
5791
6820
  path: ["env", key, "anyOf"]
5792
6821
  });
5793
6822
  }
5794
6823
  }
5795
6824
  });
5796
- var RoleEntrySchema = z3.object({
5797
- contract: z3.string().refine((p) => p.includes("/"), {
6825
+ var RoleEntrySchema = z5.object({
6826
+ contract: z5.string().refine((p) => p.includes("/"), {
5798
6827
  message: 'must be a path (contain at least one "/"), not a bare filename'
5799
6828
  })
5800
6829
  });
6830
+ var MAX_REPORT_COMMAND_TIMEOUT_MS = 3600000;
5801
6831
  var BRIEF_BUILTINS = [
5802
6832
  "tier",
5803
6833
  "testPlan",
@@ -5817,17 +6847,17 @@ var BRIEF_BUILTINS = [
5817
6847
  "briefSections",
5818
6848
  "milestoneShape"
5819
6849
  ];
5820
- var BriefSectionSchema = z3.union([
5821
- z3.object({ builtin: z3.enum(BRIEF_BUILTINS) }),
5822
- z3.object({ heading: z3.string().min(1), name: z3.string().optional() }),
5823
- z3.object({ field: z3.string().min(1), name: z3.string().optional() }),
5824
- z3.object({ phrase: z3.string().min(1), name: z3.string().optional() })
6850
+ var BriefSectionSchema = z5.union([
6851
+ z5.object({ builtin: z5.enum(BRIEF_BUILTINS) }),
6852
+ z5.object({ heading: z5.string().min(1), name: z5.string().optional() }),
6853
+ z5.object({ field: z5.string().min(1), name: z5.string().optional() }),
6854
+ z5.object({ phrase: z5.string().min(1), name: z5.string().optional() })
5825
6855
  ]);
5826
- var BriefSchemaSchema = z3.object({
5827
- pr: z3.object({ sections: z3.array(BriefSectionSchema) }).optional(),
5828
- issue: z3.object({ sections: z3.array(BriefSectionSchema) }).optional(),
5829
- milestone: z3.object({ sections: z3.array(BriefSectionSchema) }).optional(),
5830
- ack: z3.array(z3.enum(BRIEF_BUILTINS)).optional()
6856
+ var BriefSchemaSchema = z5.object({
6857
+ pr: z5.object({ sections: z5.array(BriefSectionSchema) }).optional(),
6858
+ issue: z5.object({ sections: z5.array(BriefSectionSchema) }).optional(),
6859
+ milestone: z5.object({ sections: z5.array(BriefSectionSchema) }).optional(),
6860
+ ack: z5.array(z5.enum(BRIEF_BUILTINS)).optional()
5831
6861
  });
5832
6862
  function isSafeRepoRelPath(p) {
5833
6863
  if (p.length === 0)
@@ -5836,7 +6866,7 @@ function isSafeRepoRelPath(p) {
5836
6866
  return false;
5837
6867
  return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
5838
6868
  }
5839
- var SafeRepoRelPath = z3.string().refine(isSafeRepoRelPath, {
6869
+ var SafeRepoRelPath = z5.string().refine(isSafeRepoRelPath, {
5840
6870
  message: "must be a repo-root-relative path with no `..` segment or absolute root"
5841
6871
  });
5842
6872
  var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
@@ -5846,22 +6876,22 @@ function isCanonicalHookBlockPath(p) {
5846
6876
  var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
5847
6877
  message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
5848
6878
  });
5849
- var ManagedBlockRecordSchema = z3.object({
6879
+ var ManagedBlockRecordSchema = z5.object({
5850
6880
  path: ManagedHookBlockPath,
5851
- marker: z3.string(),
5852
- comment: z3.enum(["hash", "html"])
6881
+ marker: z5.string(),
6882
+ comment: z5.enum(["hash", "html"])
5853
6883
  });
5854
- var ManagedManifestSchema = z3.object({
5855
- version: z3.number().int().positive(),
5856
- files: z3.array(SafeRepoRelPath),
5857
- blocks: z3.array(ManagedBlockRecordSchema),
5858
- labels: z3.array(z3.string()),
5859
- agents: z3.array(z3.enum(AGENT_VENDORS)).optional()
6884
+ var ManagedManifestSchema = z5.object({
6885
+ version: z5.number().int().positive(),
6886
+ files: z5.array(SafeRepoRelPath),
6887
+ blocks: z5.array(ManagedBlockRecordSchema),
6888
+ labels: z5.array(z5.string()),
6889
+ agents: z5.array(z5.enum(AGENT_VENDORS)).optional()
5860
6890
  });
5861
- var ProjectEntrySchema = z3.object({
5862
- name: z3.string().min(1),
5863
- description: z3.string().min(1).optional(),
5864
- path: z3.string().min(1).optional()
6891
+ var ProjectEntrySchema = z5.object({
6892
+ name: z5.string().min(1),
6893
+ description: z5.string().min(1).optional(),
6894
+ path: z5.string().min(1).optional()
5865
6895
  });
5866
6896
  function parseTokensCollectDeclaration(value) {
5867
6897
  const m = value.trim().match(/^(\S+)\s+(\S+)$/);
@@ -5872,34 +6902,62 @@ function parseTokensCollectDeclaration(value) {
5872
6902
  return null;
5873
6903
  return { interpreter, script };
5874
6904
  }
5875
- var VinayaConfigSchema = z3.object({
5876
- rings: z3.object({
5877
- ring1_forgeWriteInterception: z3.boolean(),
5878
- ring2_asyncAudits: z3.boolean()
6905
+ var VinayaConfigSchema = z5.object({
6906
+ rings: z5.object({
6907
+ ring1_forgeWriteInterception: z5.boolean(),
6908
+ ring2_asyncAudits: z5.boolean()
5879
6909
  }).optional(),
5880
- checks: z3.record(z3.string(), CheckEntrySchema).optional(),
5881
- roles: z3.record(z3.string(), RoleEntrySchema).optional(),
6910
+ checks: z5.record(z5.string(), CheckEntrySchema).optional(),
6911
+ roles: z5.record(z5.string(), RoleEntrySchema).optional(),
5882
6912
  briefSchema: BriefSchemaSchema.optional(),
5883
6913
  managed: ManagedManifestSchema.optional(),
5884
- principals: z3.array(z3.string()).min(1).optional(),
5885
- releaseActor: z3.string().min(1).optional(),
5886
- ci: z3.object({ setup: z3.string().min(1) }).optional(),
5887
- tokens: z3.object({
5888
- collect: z3.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
6914
+ principals: z5.array(z5.string()).min(1).optional(),
6915
+ releaseActor: z5.string().min(1).optional(),
6916
+ ci: z5.object({ setup: z5.string().min(1) }).optional(),
6917
+ tokens: z5.object({
6918
+ collect: z5.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
5889
6919
  message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
5890
6920
  })
5891
6921
  }).optional(),
5892
- blastRadius: z3.object({ extraDomains: z3.array(z3.string()).optional() }).optional(),
5893
- proseGates: z3.object({
5894
- doctrineRoot: z3.string().min(1).optional(),
5895
- readerFacingPrefix: z3.string().min(1).optional(),
5896
- readerFacingSuffix: z3.string().min(1).optional(),
5897
- legacySlugDir: z3.string().min(1).optional()
6922
+ blastRadius: z5.object({ extraDomains: z5.array(z5.string()).optional() }).optional(),
6923
+ proseGates: z5.object({
6924
+ doctrineRoot: z5.string().min(1).optional(),
6925
+ readerFacingPrefix: z5.string().min(1).optional(),
6926
+ readerFacingSuffix: z5.string().min(1).optional(),
6927
+ legacySlugDir: z5.string().min(1).optional()
6928
+ }).optional(),
6929
+ projects: z5.array(ProjectEntrySchema).optional(),
6930
+ dispatch: z5.object({
6931
+ timeoutMs: z5.number().int().positive().optional(),
6932
+ killGraceMs: z5.number().int().positive().optional(),
6933
+ agent: z5.enum(["claude", "codex", "gemini"]).optional()
5898
6934
  }).optional(),
5899
- projects: z3.array(ProjectEntrySchema).optional(),
5900
- dispatch: z3.object({
5901
- timeoutMs: z3.number().int().positive().optional(),
5902
- agent: z3.enum(["claude", "codex", "gemini"]).optional()
6935
+ reviewPolicy: z5.object({
6936
+ codeReviewThreshold: z5.string().min(1).optional(),
6937
+ securityThreshold: z5.string().min(1).optional(),
6938
+ maxRounds: z5.number().optional()
6939
+ }).optional(),
6940
+ prePush: z5.object({
6941
+ alwaysRun: z5.array(z5.string()).optional()
6942
+ }).optional(),
6943
+ report: z5.object({
6944
+ commandTimeoutMs: z5.number().int().positive().optional()
6945
+ }).superRefine((report, ctx) => {
6946
+ const commandTimeoutMs = report.commandTimeoutMs;
6947
+ if (commandTimeoutMs !== undefined && commandTimeoutMs > MAX_REPORT_COMMAND_TIMEOUT_MS) {
6948
+ ctx.addIssue({
6949
+ code: z5.ZodIssueCode.custom,
6950
+ path: ["commandTimeoutMs"],
6951
+ message: "must be at most 3600000 (1 hour)"
6952
+ });
6953
+ }
6954
+ }).optional(),
6955
+ logPublish: z5.object({
6956
+ issue: z5.number().int().positive().optional(),
6957
+ pr: z5.number().int().positive().optional(),
6958
+ maxChunksPerFlush: z5.number().int().positive().optional()
6959
+ }).refine((v) => !(v.issue !== undefined && v.pr !== undefined), {
6960
+ message: "logPublish: set at most one of issue/pr, not both"
5903
6961
  }).optional()
5904
6962
  });
5905
6963
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
@@ -5974,6 +7032,28 @@ function resolvePrincipalAllowlist(config) {
5974
7032
  function resolveReleaseActor(config) {
5975
7033
  return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
5976
7034
  }
7035
+ function resolveReviewPolicy(config) {
7036
+ const raw = config?.reviewPolicy;
7037
+ if (!raw)
7038
+ return DEFAULT_REVIEW_POLICY;
7039
+ const codeReviewThreshold = raw.codeReviewThreshold ?? DEFAULT_REVIEW_POLICY.codeReviewThreshold;
7040
+ if (!isKnownSeverity(CODE_REVIEW_SEVERITY_ORDER, codeReviewThreshold)) {
7041
+ throw new Error(`vinaya.config.json: reviewPolicy.codeReviewThreshold "${codeReviewThreshold}" is not one of ${CODE_REVIEW_SEVERITY_ORDER.join(" > ")} — fix the config, this never falls back to a default.`);
7042
+ }
7043
+ const securityThreshold = raw.securityThreshold ?? DEFAULT_REVIEW_POLICY.securityThreshold;
7044
+ if (!isKnownSeverity(SECURITY_SEVERITY_ORDER, securityThreshold)) {
7045
+ throw new Error(`vinaya.config.json: reviewPolicy.securityThreshold "${securityThreshold}" is not one of ${SECURITY_SEVERITY_ORDER.join(" > ")} — fix the config, this never falls back to a default.`);
7046
+ }
7047
+ const maxRounds = raw.maxRounds ?? DEFAULT_REVIEW_POLICY.maxRounds;
7048
+ if (!Number.isInteger(maxRounds) || maxRounds < 1) {
7049
+ throw new Error(`vinaya.config.json: reviewPolicy.maxRounds "${maxRounds}" is not a positive integer — fix the config, this never falls back to a default.`);
7050
+ }
7051
+ return {
7052
+ codeReviewThreshold,
7053
+ securityThreshold,
7054
+ maxRounds
7055
+ };
7056
+ }
5977
7057
  function trustAnchorRepo() {
5978
7058
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
5979
7059
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
@@ -6119,38 +7199,71 @@ function taskToEntry(entries, slug) {
6119
7199
  m.set(`${slug}/${e.task.id}`, e);
6120
7200
  return m;
6121
7201
  }
6122
- function recoveryPromptFor(checkCode, detail = "") {
6123
- if (detail.includes("INTERNAL:")) {
6124
- return "This is an INTERNAL parser-bug report, not a real coherence failure — a task cannot depend on itself, so there is nothing here to close or resolve. Do NOT work around it. Report it upstream, and re-run once the rationale text is corrected or the parser fix ships.";
6125
- }
6126
- switch (checkCode) {
6127
- case "A1":
7202
+ function recoveryPromptFor(code) {
7203
+ switch (code) {
7204
+ case "d1-self-dependency":
7205
+ return "This is an INTERNAL parser-bug report, not a real coherence failure — a task cannot depend on itself, so there is nothing here to close or resolve. Do NOT work around it. Report it upstream, and re-run once the rationale text is corrected or the parser fix ships.";
7206
+ case "closed-without-merge":
6128
7207
  return "The task's Issue is closed but its closing PR is not merged. Verify the PR actually merged (or reopen the Issue if it was closed in error), then re-run `vinaya check coherence`.";
6129
- case "A3":
7208
+ case "archived-without-provenance":
7209
+ return "The closing PR merged but carries no `### AEG provenance` comment. Ask the Archivist to post it, then re-run `vinaya check coherence`.";
7210
+ case "auto-close-misfire":
6130
7211
  return "The closing PR merged but the Issue is still open (a GitHub auto-close misfire). Manually close the Issue, then re-run `vinaya check coherence`.";
6131
- case "T1":
7212
+ case "phantom-issue-ref":
6132
7213
  return "The topology names an Issue number that doesn't resolve on the forge. Fix the Issue number in the topology, or ask the Planner to re-cut it, then re-run `vinaya check coherence`.";
6133
- case "T2":
7214
+ case "orphan-task":
6134
7215
  return "An open Issue under this tranche's label is missing from the topology. Add its row to the tranche's task list, then re-run `vinaya check coherence`.";
6135
- case "T3":
7216
+ case "tbd-in-active-tranche":
6136
7217
  return "A task in this active tranche has no Issue (#TBD). Ask the Planner to cut the Issue, then re-run `vinaya check coherence`.";
6137
- case "D1":
7218
+ case "dispatched-on-unmet-deps":
6138
7219
  return "This task has an open PR but a declared dependency isn't closed. Close the dependency first (or verify it truly is), then re-run `vinaya check coherence`.";
6139
- case "R1":
7220
+ case "missing-rationale-field":
6140
7221
  return "The Issue fails the rationale gate. Ask the Planner to complete the eight-field rationale on the Issue body, then re-run `vinaya check coherence`.";
7222
+ case "surface-excludes-bound-doc":
7223
+ return "The Issue's Surface `out:` list excludes a doc-owners-bound document its `in:` list otherwise covers. Fix the Surface split, or update the bound doc in the same PR, then re-run `vinaya check coherence`.";
7224
+ case "surface-overlap":
7225
+ return "This Issue overlaps another open task Issue sharing the same Milestone. Resolve the overlap (split scope or serialize the tasks), then re-run `vinaya check coherence`.";
7226
+ case "archive-recommended":
7227
+ return "This active tranche has no open task-Issues left (advisory). Archive it to `completed/`, or confirm it should stay open, then re-run `vinaya check coherence`.";
7228
+ case "premature-archive":
7229
+ return "This archived tranche still has an open task-Issue (advisory). Investigate whether the archive was premature, then re-run `vinaya check coherence`.";
7230
+ case "milestone-drift":
7231
+ return "This Issue's GitHub-native milestone doesn't match its `vinaya/tranche:` label (advisory, cosmetic). Re-attach the Issue to the correct Milestone, then re-run `vinaya check coherence`.";
7232
+ case "tranche-not-archived":
7233
+ return "Every task Issue in this tranche is closed but it was never archived. Ask the Tranche Archivist to run, then re-run `vinaya check coherence`.";
7234
+ case "doc-owners-dangling-pointer":
7235
+ return "A `.vinaya/doc-owners` entry points at a file that no longer exists in-repo. Fix or remove the dangling entry, then re-run `vinaya check coherence`.";
7236
+ case "doc-owners-duplicate-glob":
7237
+ return "A `.vinaya/doc-owners` glob is registered more than once. Remove the duplicate entry, then re-run `vinaya check coherence`.";
7238
+ case "forge-read-unavailable":
7239
+ return "A forge read failed while assembling coherence facts (severity:infra) — this is an outage, not a drift finding. Confirm `gh auth status` passes and the forge is reachable, then re-run `vinaya check coherence`.";
6141
7240
  default:
6142
- return "Read the named coherence failure and resolve the underlying forge/topology drift it names, then re-run `vinaya check coherence`.";
7241
+ return assertNeverCoherenceCode(code);
6143
7242
  }
6144
7243
  }
7244
+ function assertNeverCoherenceCode(x) {
7245
+ throw new Error(`Unhandled CoherenceFailureCode: ${JSON.stringify(x)}`);
7246
+ }
6145
7247
  function emitFailure(result) {
6146
- const detail = result.failures.map((f) => f.reason).join(" | ") || result.note || "see check output";
6147
- emitCheckError({
6148
- schema: CHECK_SCHEMA_VERSION,
6149
- check: CHECK_NAME,
6150
- severity: "error",
6151
- message: `${result.check}: ${detail}`,
6152
- agent_recovery_prompt: recoveryPromptFor(result.check, detail)
6153
- });
7248
+ if (result.failures.length === 0) {
7249
+ emitCheckError({
7250
+ schema: CHECK_SCHEMA_VERSION,
7251
+ check: CHECK_NAME,
7252
+ severity: "error",
7253
+ message: `${result.check}: ${result.note ?? "see check output"}`,
7254
+ agent_recovery_prompt: "Read the named coherence failure and resolve the underlying forge/topology drift it names, then re-run `vinaya check coherence`."
7255
+ });
7256
+ return;
7257
+ }
7258
+ for (const failure of result.failures) {
7259
+ emitCheckError({
7260
+ schema: CHECK_SCHEMA_VERSION,
7261
+ check: CHECK_NAME,
7262
+ severity: "error",
7263
+ message: `${result.check}: ${failure.reason}`,
7264
+ agent_recovery_prompt: recoveryPromptFor(failure.code)
7265
+ });
7266
+ }
6154
7267
  }
6155
7268
  async function main() {
6156
7269
  const slug = currentTrancheSlug();