@attalabs/vinaya 0.26.0 → 0.28.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 (50) hide show
  1. package/README.md +6 -3
  2. package/aeg-root/enforcement.md +5 -5
  3. package/aeg-root/milestone-model.md +2 -0
  4. package/aeg-root/process.md +1 -1
  5. package/aeg-root/roles/developer.md +23 -17
  6. package/aeg-root/roles/planner.md +4 -0
  7. package/aeg-root/roles/principal.md +8 -2
  8. package/aeg-root/roles/reviewer.md +9 -5
  9. package/aeg-root/roles/security.md +9 -5
  10. package/dist/checks/bin/check-body-bare-digits.js +1038 -257
  11. package/dist/checks/bin/check-branch-topology.js +963 -199
  12. package/dist/checks/bin/check-brief-shape.js +1276 -203
  13. package/dist/checks/bin/check-changeset-coverage.js +1677 -281
  14. package/dist/checks/bin/check-closes-n.js +967 -203
  15. package/dist/checks/bin/check-coherence.js +1127 -284
  16. package/dist/checks/bin/check-dead-branch-push.js +910 -193
  17. package/dist/checks/bin/check-dispatch-readiness.js +1189 -295
  18. package/dist/checks/bin/check-doc-coverage-push.js +1673 -277
  19. package/dist/checks/bin/check-doc-coverage.js +1675 -279
  20. package/dist/checks/bin/check-doctrine-no-procedures.js +1020 -257
  21. package/dist/checks/bin/check-doctrine-portability.js +1672 -276
  22. package/dist/checks/bin/check-evidence-fresh.js +2526 -322
  23. package/dist/checks/bin/check-exec-bits.js +1670 -274
  24. package/dist/checks/bin/check-first-push-dispatch.js +1075 -265
  25. package/dist/checks/bin/check-issue-assignment.js +965 -201
  26. package/dist/checks/bin/check-issue-milestone-attach.js +5734 -0
  27. package/dist/checks/bin/check-issue-objectives-numbering.js +5736 -0
  28. package/dist/checks/bin/check-issue-parts-coverage.js +5736 -0
  29. package/dist/checks/bin/check-issue-surface-globs.js +6910 -0
  30. package/dist/checks/bin/check-issue-title-grammar.js +5736 -0
  31. package/dist/checks/bin/check-issue-tranche-label.js +5736 -0
  32. package/dist/checks/bin/check-main-branch-refusal.js +910 -193
  33. package/dist/checks/bin/check-no-disk-state.js +910 -193
  34. package/dist/checks/bin/check-pr-premise-reassert.js +1020 -257
  35. package/dist/checks/bin/check-pr-report-density.js +910 -193
  36. package/dist/checks/bin/check-quoted-command.js +1651 -274
  37. package/dist/checks/bin/check-reader-resolvable-prose.js +1655 -278
  38. package/dist/checks/bin/check-registry-gates.js +997 -193
  39. package/dist/checks/bin/check-retired-vocabulary.js +1649 -272
  40. package/dist/checks/bin/check-review-gate.js +1163 -316
  41. package/dist/checks/bin/check-single-plan-pr.js +910 -193
  42. package/dist/checks/bin/check-surface-scope.js +973 -201
  43. package/dist/checks/bin/check-test-plan.js +939 -206
  44. package/dist/checks/bin/check-token-collection-wired.js +910 -193
  45. package/dist/checks/bin/check-token-report.js +910 -193
  46. package/dist/checks/bin/check-workspace-escape.js +1668 -272
  47. package/dist/index.js +10602 -5352
  48. package/dist/lib/pre-push-changed-files.js +57 -0
  49. package/dist/lib/pre-push-select-tests.js +1473 -0
  50. package/package.json +4 -2
@@ -300,7 +300,7 @@ function issueListByLabelArgs(owner, repo, label) {
300
300
  "--state",
301
301
  "all",
302
302
  "--json",
303
- "number,title,body,state,labels,milestone",
303
+ "number,title,body,state,labels,milestone,stateReason",
304
304
  "--limit",
305
305
  "200"
306
306
  ];
@@ -646,14 +646,19 @@ function resolveTaskIssueRef(title, labels) {
646
646
  var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
647
647
  var NEXT_HEADING = /^#{1,6}\s+\S/m;
648
648
  var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
649
- function intentGoalForSlug(description, slug) {
650
- const text = stripCode(description, { inlineSpans: "keep" });
649
+ function intentsSection(text) {
651
650
  const start = text.match(INTENTS_HEADING);
652
651
  if (!start || start.index === undefined)
653
- return "";
652
+ return null;
654
653
  const rest = text.slice(start.index + start[0].length);
655
654
  const next = rest.match(NEXT_HEADING);
656
- const section = rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
655
+ return rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
656
+ }
657
+ function intentGoalForSlug(description, slug) {
658
+ const text = stripCode(description, { inlineSpans: "keep" });
659
+ const section = intentsSection(text);
660
+ if (section === null)
661
+ return "";
657
662
  for (const line of section.split(`
658
663
  `)) {
659
664
  const trimmed = line.trim();
@@ -1945,6 +1950,9 @@ function parseInlineFieldList(section) {
1945
1950
  // ../../packages/aeg-core/src/verdict-extraction.ts
1946
1951
  var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1947
1952
  var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1953
+ var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
1954
+ var BRIEF_HASH_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Brief hash:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1955
+ var POLICY_DIGEST_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Policy digest:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1948
1956
  function firstFiveLines(comment) {
1949
1957
  return comment.split(`
1950
1958
  `).slice(0, 5).join(`
@@ -1958,6 +1966,35 @@ function extractObjectivesVersion(comment) {
1958
1966
  const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
1959
1967
  return m ? m[1].toLowerCase() : null;
1960
1968
  }
1969
+ function firstSevenLines(comment) {
1970
+ return comment.split(`
1971
+ `).slice(0, 7).join(`
1972
+ `);
1973
+ }
1974
+ function extractRulingOrdinal(comment) {
1975
+ const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
1976
+ return m ? Number.parseInt(m[1], 10) : null;
1977
+ }
1978
+ function firstElevenLines(comment) {
1979
+ return comment.split(`
1980
+ `).slice(0, 11).join(`
1981
+ `);
1982
+ }
1983
+ function extractBriefHash(comment) {
1984
+ const m = firstElevenLines(comment).match(BRIEF_HASH_PATTERN);
1985
+ return m ? m[1].toLowerCase() : null;
1986
+ }
1987
+ function extractPolicyDigest(comment) {
1988
+ const m = firstElevenLines(comment).match(POLICY_DIGEST_PATTERN);
1989
+ return m ? m[1].toLowerCase() : null;
1990
+ }
1991
+ var FINDING_SEVERITY_LINE = /^\d+\.\s+\[([A-Z][A-Z]*)\]\s+(.+?)\s+—/gm;
1992
+ function extractFindingSeverities(comment) {
1993
+ return [...comment.matchAll(FINDING_SEVERITY_LINE)].map((m) => ({
1994
+ severity: m[1],
1995
+ location: m[2]
1996
+ }));
1997
+ }
1961
1998
  function extractVerdict(comments, valuePattern, missingLabel) {
1962
1999
  const candidates = comments.filter((c) => valuePattern.test(c));
1963
2000
  if (candidates.length === 0) {
@@ -1965,6 +2002,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1965
2002
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
1966
2003
  headSha: null,
1967
2004
  objectivesVersion: null,
2005
+ rulingOrdinal: null,
2006
+ briefHash: null,
2007
+ policyDigest: null,
2008
+ findingSeverities: [],
1968
2009
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
1969
2010
  };
1970
2011
  }
@@ -1975,6 +2016,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1975
2016
  value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
1976
2017
  headSha: null,
1977
2018
  objectivesVersion: null,
2019
+ rulingOrdinal: null,
2020
+ briefHash: null,
2021
+ policyDigest: null,
2022
+ findingSeverities: [],
1978
2023
  danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
1979
2024
  };
1980
2025
  }
@@ -1982,6 +2027,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1982
2027
  value: m[1].toUpperCase().replace(/[_-]/g, " "),
1983
2028
  headSha: extractHeadSha(latest),
1984
2029
  objectivesVersion: extractObjectivesVersion(latest),
2030
+ rulingOrdinal: extractRulingOrdinal(latest),
2031
+ briefHash: extractBriefHash(latest),
2032
+ policyDigest: extractPolicyDigest(latest),
2033
+ findingSeverities: extractFindingSeverities(latest),
1985
2034
  danglingNote: null
1986
2035
  };
1987
2036
  }
@@ -2317,6 +2366,53 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
2317
2366
  }
2318
2367
  return out;
2319
2368
  }
2369
+ // ../../packages/aeg-core/src/review-policy.ts
2370
+ var CODE_REVIEW_SEVERITY_ORDER = ["BLOCKER", "MAJOR", "MINOR"];
2371
+ var SECURITY_SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
2372
+ var DEFAULT_MAX_ROUNDS = 3;
2373
+ var DEFAULT_REVIEW_POLICY = {
2374
+ codeReviewThreshold: "BLOCKER",
2375
+ securityThreshold: "HIGH",
2376
+ maxRounds: DEFAULT_MAX_ROUNDS
2377
+ };
2378
+ var FILE_SHAPED_LOCATION = /\.[a-zA-Z0-9]{1,10}(:\d+)?\s*$/;
2379
+ var PROSE_LOCATION_PATTERNS = [/\bpr\s*body\b/i, /\bcomment\b/i];
2380
+ var ROLE_FILE_LOCATION = /(^|\/)aeg-root\/roles\//i;
2381
+ function isProseLocation(location) {
2382
+ if (ROLE_FILE_LOCATION.test(location))
2383
+ return true;
2384
+ if (FILE_SHAPED_LOCATION.test(location))
2385
+ return false;
2386
+ return PROSE_LOCATION_PATTERNS.some((pattern) => pattern.test(location));
2387
+ }
2388
+ var PROSE_CAP_SEVERITY = "MINOR";
2389
+ function blockingSeverities(scale, threshold) {
2390
+ const idx = scale.indexOf(threshold);
2391
+ if (idx === -1) {
2392
+ throw new Error(`blockingSeverities: threshold "${threshold}" is not one of ${scale.join(" > ")}`);
2393
+ }
2394
+ return scale.slice(0, idx + 1);
2395
+ }
2396
+ function evaluateReviewFindings(findings, scale, threshold) {
2397
+ const blocking = new Set(blockingSeverities(scale, threshold));
2398
+ const blockingFindings = findings.filter((f) => {
2399
+ if (!scale.includes(f.severity)) {
2400
+ throw new Error(`evaluateReviewFindings: severity "${f.severity}" is not one of ${scale.join(" > ")}`);
2401
+ }
2402
+ const effectiveSeverity = f.location !== undefined && isProseLocation(f.location) ? PROSE_CAP_SEVERITY : f.severity;
2403
+ return blocking.has(effectiveSeverity);
2404
+ });
2405
+ return { outcome: blockingFindings.length > 0 ? "blocked" : "clean", blockingFindings };
2406
+ }
2407
+ function evaluateCodeReview(findings, policy) {
2408
+ return evaluateReviewFindings(findings, CODE_REVIEW_SEVERITY_ORDER, policy.codeReviewThreshold);
2409
+ }
2410
+ function evaluateSecurityReview(findings, policy) {
2411
+ return evaluateReviewFindings(findings, SECURITY_SEVERITY_ORDER, policy.securityThreshold);
2412
+ }
2413
+ function isKnownSeverity(scale, value) {
2414
+ return scale.includes(value);
2415
+ }
2320
2416
  // ../../packages/aeg-core/src/pr-tier.ts
2321
2417
  var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
2322
2418
  function readTierFromPrBody(prBody) {
@@ -2343,6 +2439,9 @@ import { createHash as createHash2 } from "node:crypto";
2343
2439
  var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
2344
2440
  var NEXT_HEADING_RE = /^##[ \t]/m;
2345
2441
  var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
2442
+ function maskedForHeadingSearch(body) {
2443
+ return maskDetailsBlocks(maskCode(body));
2444
+ }
2346
2445
  function hasBacktickedPath(text) {
2347
2446
  let i = 0;
2348
2447
  while (i < text.length) {
@@ -2365,16 +2464,22 @@ function stripObjectiveBackticks(text) {
2365
2464
  function wordCount(text) {
2366
2465
  return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
2367
2466
  }
2368
- function objectivesSectionText(body) {
2369
- const heading = HEADING_RE.exec(body);
2467
+ function objectivesSectionBounds(body) {
2468
+ const masked = maskedForHeadingSearch(body);
2469
+ const heading = HEADING_RE.exec(masked);
2370
2470
  if (!heading)
2371
2471
  return null;
2372
- const afterHeading = body.slice(heading.index + heading[0].length);
2472
+ const start = heading.index + heading[0].length;
2473
+ const afterHeading = masked.slice(start);
2373
2474
  const next = NEXT_HEADING_RE.exec(afterHeading);
2374
- return next ? afterHeading.slice(0, next.index) : afterHeading;
2475
+ return { start, end: next ? start + next.index : body.length };
2476
+ }
2477
+ function objectivesSectionText(body) {
2478
+ const bounds = objectivesSectionBounds(body);
2479
+ return bounds === null ? null : body.slice(bounds.start, bounds.end);
2375
2480
  }
2376
2481
  function hasObjectivesHeading(body) {
2377
- return HEADING_RE.test(body);
2482
+ return HEADING_RE.test(maskedForHeadingSearch(body));
2378
2483
  }
2379
2484
  function objectivesOf(body) {
2380
2485
  const section = objectivesSectionText(body);
@@ -2434,6 +2539,15 @@ function isIssueNotFoundError(err) {
2434
2539
  `);
2435
2540
  return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
2436
2541
  }
2542
+ function resolveObjectivesSource(prBody, issue, cutoverIssue) {
2543
+ if (issue !== null && issue < cutoverIssue)
2544
+ return { kind: "none" };
2545
+ if (issue !== null)
2546
+ return { kind: "issue", issue };
2547
+ if (hasObjectivesHeading(prBody))
2548
+ return { kind: "body" };
2549
+ return { kind: "none" };
2550
+ }
2437
2551
 
2438
2552
  // ../../packages/aeg-core/src/premise-check.ts
2439
2553
  import { createHash as createHash3 } from "node:crypto";
@@ -2669,7 +2783,7 @@ function checkForField(prBody) {
2669
2783
  ]
2670
2784
  };
2671
2785
  }
2672
- function checkClosesN(prBody) {
2786
+ function checkClosesNPresence(prBody) {
2673
2787
  const closesPattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s{0,8}:?\s{0,8}#\d+/i;
2674
2788
  if (closesPattern.test(stripCode(prBody))) {
2675
2789
  return { status: "pass", errors: [] };
@@ -2701,10 +2815,22 @@ var COMMIT_TYPES = [
2701
2815
  "Test"
2702
2816
  ];
2703
2817
  var COMMIT_TYPE_STYLE = new RegExp(`^(${COMMIT_TYPES.join("|")})(\\([a-z0-9-]+\\))?: \\S`);
2818
+ function checkForgeTitle(title) {
2819
+ const taskStyle = /^\[[a-z0-9._-]+\] \S+ — \S/;
2820
+ if (COMMIT_TYPE_STYLE.test(title) || taskStyle.test(title))
2821
+ return { status: "pass", errors: [] };
2822
+ return {
2823
+ status: "fail",
2824
+ errors: [
2825
+ `brief-validation title: "${title}" matches neither title grammar — expected \`Type: description\` / \`Type(scope): description\` (commitlint types + Plan) or \`[tranche] id — description\` (task form).`
2826
+ ]
2827
+ };
2828
+ }
2704
2829
  var BRIEF_SHAPE_MARKERS = [checkSurfaceMap, checkDocUpdateList, checkStopConditions, checkAutonomyClause];
2705
2830
  var TASK_BRANCH_PATTERN = /^task\/[^/]+\/[^/]+$/;
2831
+ var TASK_ISSUE_BRANCH_PATTERN = /^task\/issue-\d+$/;
2706
2832
  function isTaskBranch(branch) {
2707
- return TASK_BRANCH_PATTERN.test(branch);
2833
+ return TASK_BRANCH_PATTERN.test(branch) || TASK_ISSUE_BRANCH_PATTERN.test(branch);
2708
2834
  }
2709
2835
  function isBriefShaped(prBody) {
2710
2836
  const stripped = stripCode(prBody);
@@ -2826,8 +2952,9 @@ function packagesNamedIn(text) {
2826
2952
  }
2827
2953
  function hasTestPathForConsumer(text, consumerDir) {
2828
2954
  const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2829
- const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2830
- return re.test(text);
2955
+ const filePathRe = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2956
+ const testDirRe = new RegExp(`${escaped}\\/(?:[\\w-]+\\/)*(?:tests|specs)(?:\\/|\\b)`);
2957
+ return filePathRe.test(text) || testDirRe.test(text);
2831
2958
  }
2832
2959
  function checkConsumerTests(prBody, consumersOf) {
2833
2960
  const section4 = extractNumberedSection(prBody, 4);
@@ -2931,21 +3058,44 @@ function checkBriefSections(prBody, readTier, options = {}) {
2931
3058
  checkConsumerTests(prBody, consumersOf),
2932
3059
  checkDefeatCases(prBody),
2933
3060
  ...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
2934
- ...requireClosesN ? [checkClosesN(prBody)] : []
3061
+ ...requireClosesN ? [checkClosesNPresence(prBody)] : []
2935
3062
  ];
2936
3063
  return { errors: results.flatMap((r) => r.errors) };
2937
3064
  }
2938
- var AEG_BRIEF_V1_MARKER = "<!-- aeg:brief:v1 -->";
2939
- function contentAfterTwoLines(body) {
2940
- const first = body.indexOf(`
2941
- `);
2942
- if (first === -1)
2943
- return "";
2944
- const second = body.indexOf(`
2945
- `, first + 1);
2946
- if (second === -1)
2947
- return "";
2948
- return body.slice(second + 1);
3065
+ function contentAfterNLines(body, n) {
3066
+ let idx = -1;
3067
+ for (let i = 0;i < n; i++) {
3068
+ idx = body.indexOf(`
3069
+ `, idx + 1);
3070
+ if (idx === -1)
3071
+ return "";
3072
+ }
3073
+ return body.slice(idx + 1);
3074
+ }
3075
+ var BRIEF_MARKER_LINE_RE = /^<!-- aeg:brief:v(\d+) -->$/;
3076
+ function parseBriefMarkerVersion(firstLine) {
3077
+ const m = BRIEF_MARKER_LINE_RE.exec(firstLine.trim());
3078
+ if (!m)
3079
+ return null;
3080
+ const version = Number.parseInt(m[1], 10);
3081
+ return Number.isInteger(version) && version >= 1 ? version : null;
3082
+ }
3083
+ function frozenBriefContent(body, version) {
3084
+ return contentAfterNLines(body, version === 1 ? 2 : 3);
3085
+ }
3086
+ function resolveNewestFrozenBrief(comments, allowlist) {
3087
+ let best = null;
3088
+ for (const c of comments) {
3089
+ if (!isPrincipal(c.author, allowlist))
3090
+ continue;
3091
+ const version = parseBriefMarkerVersion(c.body.split(`
3092
+ `)[0] ?? "");
3093
+ if (version === null)
3094
+ continue;
3095
+ if (best === null || version > best.version)
3096
+ best = { ...c, version };
3097
+ }
3098
+ return best === null ? null : { ...best, content: frozenBriefContent(best.body, best.version) };
2949
3099
  }
2950
3100
  // ../../packages/aeg-core/src/doctrine-portability.ts
2951
3101
  var DEFAULT_SHIPS_PREFIX = "aeg-root/";
@@ -3198,6 +3348,28 @@ function topLevelSectionText(body, headingName) {
3198
3348
  const next = /^##[ \t]/m.exec(afterHeading);
3199
3349
  return next ? afterHeading.slice(0, next.index) : afterHeading;
3200
3350
  }
3351
+ function partHasBacktickedPath(text) {
3352
+ let i = 0;
3353
+ while (i < text.length) {
3354
+ const start = text.indexOf("`", i);
3355
+ if (start === -1)
3356
+ return false;
3357
+ const end = text.indexOf("`", start + 1);
3358
+ if (end === -1)
3359
+ return false;
3360
+ if (text.slice(start + 1, end).includes("/"))
3361
+ return true;
3362
+ i = end + 1;
3363
+ }
3364
+ return false;
3365
+ }
3366
+ var PART_MIN_WORDS_OUTSIDE_BACKTICKS = 3;
3367
+ function stripPartBackticks(text) {
3368
+ return text.replace(/`[^`\n]*`/g, " ");
3369
+ }
3370
+ function partWordCount(text) {
3371
+ return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
3372
+ }
3201
3373
  function looksLikeFilePath(entry2) {
3202
3374
  const stripped = entry2.replace(/\/\*\*?$/, "");
3203
3375
  const lastSegment = stripped.split("/").pop() ?? stripped;
@@ -3238,6 +3410,18 @@ function globCoversPath(glob, path) {
3238
3410
  const p = path.replace(/\/+$/, "");
3239
3411
  return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
3240
3412
  }
3413
+ function checkSurfaceGlobsResolve(body, resolvesToFile) {
3414
+ const surface = parseIssueSurface(body);
3415
+ if (!surface.ok)
3416
+ return { status: "pass", errors: [] };
3417
+ const errors = [];
3418
+ for (const glob of surface.value.in) {
3419
+ if (!resolvesToFile(glob)) {
3420
+ 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.`);
3421
+ }
3422
+ }
3423
+ return { status: errors.length > 0 ? "fail" : "pass", errors };
3424
+ }
3241
3425
  function checkSurfaceScope(changedFiles, outGlobs) {
3242
3426
  if (outGlobs.length === 0)
3243
3427
  return { ok: true };
@@ -3249,9 +3433,77 @@ function checkSurfaceScope(changedFiles, outGlobs) {
3249
3433
  }
3250
3434
  return violations.length > 0 ? { ok: false, violations } : { ok: true };
3251
3435
  }
3436
+ var ISSUE_PART_LINE_RE = /^Part\s+(\d+)\s*\(([^)]*)\)\s*[-—–]\s*(.*)$/i;
3437
+ function parseIssueParts(body) {
3438
+ const section = topLevelSectionText(body, "Parts");
3439
+ if (section === null)
3440
+ return { ok: false, errors: ["no `## Parts` heading found in the body."] };
3441
+ const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
3442
+ const parts = [];
3443
+ const errors = [];
3444
+ for (const line of lines) {
3445
+ const m = ISSUE_PART_LINE_RE.exec(line);
3446
+ if (!m) {
3447
+ errors.push(`"${line}" is not a well-formed Parts line — expected \`Part <n> (<refs>) — <outcome>\`.`);
3448
+ continue;
3449
+ }
3450
+ const n = Number.parseInt(m[1], 10);
3451
+ const refs = m[2];
3452
+ const text = m[3].trim();
3453
+ if (text.length === 0) {
3454
+ errors.push(`Part ${n} has no outcome text after the dash — every Part states one observable outcome.`);
3455
+ continue;
3456
+ }
3457
+ if (partHasBacktickedPath(text) && partWordCount(stripPartBackticks(text)) < PART_MIN_WORDS_OUTSIDE_BACKTICKS) {
3458
+ errors.push(`Part ${n} is little more than a file path — a Part names an outcome and symbols, never a bare path.`);
3459
+ continue;
3460
+ }
3461
+ const objectiveIds = [...refs.matchAll(/O(\d+)/g)].map((r) => Number.parseInt(r[1], 10));
3462
+ parts.push({ n, objectiveIds, text });
3463
+ }
3464
+ if (parts.length === 0) {
3465
+ errors.push("the `## Parts` section has no well-formed `Part <n> (<refs>) — <outcome>` lines.");
3466
+ }
3467
+ if (errors.length > 0)
3468
+ return { ok: false, errors };
3469
+ return { ok: true, value: parts };
3470
+ }
3471
+ function checkPartsCiteDefinedObjectives(body) {
3472
+ const parts = parseIssueParts(body);
3473
+ const objectives = objectivesOf(body);
3474
+ if (!parts.ok || !objectives.ok)
3475
+ return { status: "pass", errors: [] };
3476
+ const definedIds = new Set(objectives.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
3477
+ const errors = [];
3478
+ for (const part of parts.value) {
3479
+ for (const objectiveId of part.objectiveIds) {
3480
+ if (!definedIds.has(objectiveId)) {
3481
+ errors.push(`issue-validation Parts: Part ${part.n} cites O${objectiveId}, which the Issue's own \`## Objectives\` section does not define.`);
3482
+ }
3483
+ }
3484
+ }
3485
+ return { status: errors.length > 0 ? "fail" : "pass", errors };
3486
+ }
3252
3487
  function isTaskIssueLabelSet(labels) {
3253
3488
  return hasLabel("tranche", labels);
3254
3489
  }
3490
+ function checkTrancheLabelPresence(_body, _labels) {
3491
+ return { status: "pass", errors: [] };
3492
+ }
3493
+ function checkMilestoneAttach(labels, currentMilestoneTitle, resolvedMilestoneTitle) {
3494
+ if (!isTaskIssueLabelSet(labels))
3495
+ return { status: "pass", errors: [] };
3496
+ if (resolvedMilestoneTitle === null)
3497
+ return { status: "pass", errors: [] };
3498
+ if (currentMilestoneTitle === resolvedMilestoneTitle)
3499
+ return { status: "pass", errors: [] };
3500
+ return {
3501
+ status: "fail",
3502
+ errors: [
3503
+ `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}".`
3504
+ ]
3505
+ };
3506
+ }
3255
3507
  var TYPE_LABEL_IDS = LABELS.filter((l) => l.category === "type").map((l) => l.id);
3256
3508
  function isControlCodePoint(codePoint) {
3257
3509
  return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
@@ -3316,6 +3568,19 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
3316
3568
  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;
3317
3569
  var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
3318
3570
 
3571
+ // ../../packages/aeg-core/src/task-branch-identity.ts
3572
+ var ISSUE_BRANCH_PATTERN = /^task\/issue-(\d+)$/;
3573
+ var TRANCHE_BRANCH_PATTERN = /^task\/([^/]+)\/([^/]+)$/;
3574
+ function parseTaskBranchIdentity(branch) {
3575
+ const issueMatch = ISSUE_BRANCH_PATTERN.exec(branch);
3576
+ if (issueMatch)
3577
+ return { kind: "issue", issueNumber: Number(issueMatch[1]) };
3578
+ const trancheMatch = TRANCHE_BRANCH_PATTERN.exec(branch);
3579
+ if (trancheMatch)
3580
+ return { kind: "tranche", tranche: trancheMatch[1], taskId: trancheMatch[2] };
3581
+ return null;
3582
+ }
3583
+
3319
3584
  // ../../packages/aeg-core/src/coherence-checks.ts
3320
3585
  var COHERENCE_ENFORCED_FROM = "2026-07-01";
3321
3586
  function isGrandfathered(isoDate) {
@@ -3336,6 +3601,7 @@ function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
3336
3601
  if (handClosed)
3337
3602
  continue;
3338
3603
  failures.push({
3604
+ code: "closed-without-merge",
3339
3605
  issue: e.task.issue,
3340
3606
  tranche: e.trancheSlug,
3341
3607
  task: e.task.id,
@@ -3360,6 +3626,7 @@ function checkA3(entries) {
3360
3626
  continue;
3361
3627
  if (e.facts.prState === "merged" && e.facts.issueState !== "closed") {
3362
3628
  failures.push({
3629
+ code: "auto-close-misfire",
3363
3630
  issue: e.task.issue,
3364
3631
  tranche: e.trancheSlug,
3365
3632
  task: e.task.id,
@@ -3384,6 +3651,7 @@ function checkT1(entries) {
3384
3651
  continue;
3385
3652
  if (e.facts === undefined) {
3386
3653
  failures.push({
3654
+ code: "phantom-issue-ref",
3387
3655
  issue: e.task.issue,
3388
3656
  tranche: e.trancheSlug,
3389
3657
  task: e.task.id,
@@ -3402,6 +3670,7 @@ function checkT2(openIssuesBySlug, topologyIssuesBySlug, ciTrancheSlug) {
3402
3670
  for (const num of openNums) {
3403
3671
  if (!topologySet.has(num)) {
3404
3672
  failures.push({
3673
+ code: "orphan-task",
3405
3674
  issue: num,
3406
3675
  tranche: slug,
3407
3676
  reason: `Issue #${num} is open and labeled ${trancheLabel(slug)} but does not appear in the topology file`
@@ -3436,6 +3705,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
3436
3705
  continue;
3437
3706
  if (forgeUnavailableSlugs?.has(e.trancheSlug)) {
3438
3707
  failures.push({
3708
+ code: "tbd-in-active-tranche",
3439
3709
  issue: null,
3440
3710
  tranche: e.trancheSlug,
3441
3711
  task: e.task.id,
@@ -3445,6 +3715,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
3445
3715
  continue;
3446
3716
  }
3447
3717
  failures.push({
3718
+ code: "tbd-in-active-tranche",
3448
3719
  issue: null,
3449
3720
  tranche: e.trancheSlug,
3450
3721
  task: e.task.id,
@@ -3477,6 +3748,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
3477
3748
  const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
3478
3749
  if (sameTask || sameIssue) {
3479
3750
  failures.push({
3751
+ code: "d1-self-dependency",
3480
3752
  issue: e.task.issue,
3481
3753
  tranche: e.trancheSlug,
3482
3754
  task: e.task.id,
@@ -3488,6 +3760,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
3488
3760
  const depClosed = depFacts?.issueState === "closed";
3489
3761
  if (!depClosed) {
3490
3762
  failures.push({
3763
+ code: "dispatched-on-unmet-deps",
3491
3764
  issue: e.task.issue,
3492
3765
  tranche: e.trancheSlug,
3493
3766
  task: e.task.id,
@@ -3513,11 +3786,13 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
3513
3786
  const errors = [
3514
3787
  ...checkIssueRationale(issue.body).errors,
3515
3788
  ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
3516
- ...checkIssueObjectives(issue.body, issue.number).errors
3789
+ ...checkIssueObjectives(issue.body, issue.number).errors,
3790
+ ...checkPartsCiteDefinedObjectives(issue.body).errors
3517
3791
  ];
3518
3792
  if (errors.length === 0)
3519
3793
  continue;
3520
3794
  failures.push({
3795
+ code: "missing-rationale-field",
3521
3796
  issue: issue.number,
3522
3797
  tranche: slug,
3523
3798
  reason: `Issue #${issue.number} fails the rationale gate: ${errors.join(" | ")}`,
@@ -3546,6 +3821,7 @@ function checkL1(files, entriesBySlug) {
3546
3821
  const allClosed = withFacts.every((e) => e.facts?.issueState === "closed");
3547
3822
  if (allClosed) {
3548
3823
  failures.push({
3824
+ code: "archive-recommended",
3549
3825
  tranche: f.slug,
3550
3826
  reason: "Active tranche has no open task-Issues — consider archiving to completed/"
3551
3827
  });
@@ -3577,27 +3853,38 @@ function extractClosesReferences(prBody) {
3577
3853
  }
3578
3854
  return referenced;
3579
3855
  }
3580
- function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
3856
+ function checkClosesNTopology(branch, prBody, trancheFiles, taskIssueRefs) {
3581
3857
  const referenced = extractClosesReferences(prBody);
3582
3858
  if (taskIssueRefs) {
3583
3859
  for (const n of referenced) {
3584
- const ref = taskIssueRefs.get(n);
3585
- if (!ref)
3860
+ const ref2 = taskIssueRefs.get(n);
3861
+ if (!ref2)
3586
3862
  continue;
3587
- const expectedBranch = `task/${ref.trancheSlug}/${ref.taskId}`;
3863
+ const expectedBranch = `task/${ref2.trancheSlug}/${ref2.taskId}`;
3588
3864
  if (branch !== expectedBranch) {
3589
3865
  return {
3590
3866
  ok: false,
3591
- 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.`
3867
+ 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.`
3592
3868
  };
3593
3869
  }
3594
3870
  }
3595
3871
  }
3596
- const m = branch.match(/^task\/([^/]+)\/([^/]+)$/);
3597
- if (!m)
3872
+ const ref = parseTaskBranchIdentity(branch);
3873
+ if (!ref)
3598
3874
  return { ok: true };
3599
- const trancheSlug = m[1];
3600
- const taskId = m[2];
3875
+ if (ref.kind === "issue") {
3876
+ const expectedIssue2 = ref.issueNumber;
3877
+ if (!referenced.has(expectedIssue2)) {
3878
+ return {
3879
+ ok: false,
3880
+ expectedIssue: expectedIssue2,
3881
+ message: `closes-n: PR body does not contain \`Closes #${expectedIssue2}\` (required for branch "${branch}"). Add it to the PR body Summary section.`
3882
+ };
3883
+ }
3884
+ return { ok: true, expectedIssue: expectedIssue2 };
3885
+ }
3886
+ const trancheSlug = ref.tranche;
3887
+ const taskId = ref.taskId;
3601
3888
  const trancheFile = trancheFiles.find((f) => f.slug === trancheSlug);
3602
3889
  if (!trancheFile) {
3603
3890
  return {
@@ -3649,29 +3936,67 @@ function extractIssue(body) {
3649
3936
  const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
3650
3937
  return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
3651
3938
  }
3652
- // ../../packages/aeg-core/src/review-gate.ts
3653
- function isBoundToHead(extraction, headSha) {
3654
- if (!extraction.headSha)
3939
+ // ../../packages/aeg-core/src/review-input-manifest.ts
3940
+ import { createHash as createHash4 } from "node:crypto";
3941
+ function briefHash(brief) {
3942
+ return createHash4("sha256").update(`${brief}
3943
+ `).digest("hex");
3944
+ }
3945
+ function policyDigest(policy) {
3946
+ return createHash4("sha256").update(JSON.stringify({ codeReviewThreshold: policy.codeReviewThreshold, securityThreshold: policy.securityThreshold })).digest("hex");
3947
+ }
3948
+ function isBoundToHead(echoed, headSha) {
3949
+ if (!echoed.headSha)
3655
3950
  return false;
3656
- return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
3951
+ return headSha.toLowerCase().startsWith(echoed.headSha.toLowerCase());
3657
3952
  }
3658
- function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3659
- if (patchIdOf === undefined || !extraction.headSha)
3953
+ function isBoundByPatchIdentity(echoed, headSha, patchIdOf) {
3954
+ if (patchIdOf === undefined || !echoed.headSha)
3660
3955
  return false;
3661
- const judged = patchIdOf(extraction.headSha);
3956
+ const judged = patchIdOf(echoed.headSha);
3662
3957
  const current = patchIdOf(headSha);
3663
3958
  if (judged === null || current === null)
3664
3959
  return false;
3665
3960
  return judged === current;
3666
3961
  }
3667
- function isBoundToPatch(extraction, headSha, patchIdOf) {
3668
- return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3962
+ function isBoundToPatch(echoed, headSha, patchIdOf) {
3963
+ return isBoundToHead(echoed, headSha) || isBoundByPatchIdentity(echoed, headSha, patchIdOf);
3669
3964
  }
3670
- function isBoundToObjectives(extraction, currentVersion) {
3965
+ function isBoundToObjectives(echoed, currentVersion) {
3671
3966
  if (currentVersion === null)
3672
3967
  return true;
3673
- return extraction.objectivesVersion === currentVersion;
3968
+ return echoed.objectivesVersion === currentVersion;
3969
+ }
3970
+ function isBoundToRulings(echoed, currentOrdinal) {
3971
+ if (echoed.rulingOrdinal === null)
3972
+ return currentOrdinal === 0;
3973
+ return echoed.rulingOrdinal === currentOrdinal;
3974
+ }
3975
+ function isBoundToBriefHash(echoed, currentHash) {
3976
+ if (currentHash === null)
3977
+ return true;
3978
+ return echoed.briefHash === currentHash;
3979
+ }
3980
+ function isBoundToPolicy(echoed, currentDigest) {
3981
+ return echoed.policyDigest === currentDigest;
3674
3982
  }
3983
+ function compareManifest(echoed, current, patchIdOf) {
3984
+ const head = isBoundToPatch(echoed, current.headSha, patchIdOf);
3985
+ const briefHashBound = isBoundToBriefHash(echoed, current.briefHash);
3986
+ const objectivesVersion2 = isBoundToObjectives(echoed, current.objectivesVersion);
3987
+ const rulingOrdinal = isBoundToRulings(echoed, current.rulingOrdinal);
3988
+ const policyDigestBound = isBoundToPolicy(echoed, current.policyDigest);
3989
+ return {
3990
+ bound: head && briefHashBound && objectivesVersion2 && rulingOrdinal && policyDigestBound,
3991
+ head,
3992
+ briefHash: briefHashBound,
3993
+ objectivesVersion: objectivesVersion2,
3994
+ rulingOrdinal,
3995
+ policyDigest: policyDigestBound
3996
+ };
3997
+ }
3998
+
3999
+ // ../../packages/aeg-core/src/review-gate.ts
3675
4000
  function checkReviewGate(input) {
3676
4001
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
3677
4002
  const waived = isWaiverLabelActorVerified({
@@ -3694,13 +4019,55 @@ function checkReviewGate(input) {
3694
4019
  const verifiedBodies = verified.map((c) => c.body);
3695
4020
  const codeReview = extractCodeReviewVerdict(verifiedBodies);
3696
4021
  const security = extractSecurityReviewVerdict(verifiedBodies);
3697
- const codeReviewClean = codeReview.value === "APPROVE";
3698
- const securityClean = security.value === "PASS";
3699
- const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3700
- const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3701
- const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
3702
- const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
3703
- if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
4022
+ const policy = input.policy ?? DEFAULT_REVIEW_POLICY;
4023
+ let codeReviewPolicyEvaluation;
4024
+ let securityPolicyEvaluation;
4025
+ try {
4026
+ codeReviewPolicyEvaluation = evaluateCodeReview(codeReview.findingSeverities, policy);
4027
+ securityPolicyEvaluation = evaluateSecurityReview(security.findingSeverities, policy);
4028
+ } catch (err) {
4029
+ return {
4030
+ verdict: "fail",
4031
+ reason: `a verdict comment carries a finding severity this repository's policy does not recognize: ${err instanceof Error ? err.message : String(err)}`,
4032
+ waived: false
4033
+ };
4034
+ }
4035
+ const codeReviewTextClean = codeReview.value === "APPROVE";
4036
+ const securityTextClean = security.value === "PASS";
4037
+ const codeReviewPolicyClean = codeReviewPolicyEvaluation.outcome === "clean";
4038
+ const securityPolicyClean = securityPolicyEvaluation.outcome === "clean";
4039
+ const codeReviewClean = codeReviewTextClean && codeReviewPolicyClean;
4040
+ const securityClean = securityTextClean && securityPolicyClean;
4041
+ const currentManifest = {
4042
+ headSha: input.headSha,
4043
+ briefHash: input.briefHash ?? null,
4044
+ objectivesVersion: input.objectivesVersion,
4045
+ rulingOrdinal: input.rulingOrdinal,
4046
+ policyDigest: policyDigest(policy)
4047
+ };
4048
+ const codeReviewEchoed = {
4049
+ headSha: codeReview.headSha,
4050
+ briefHash: codeReview.briefHash,
4051
+ objectivesVersion: codeReview.objectivesVersion,
4052
+ rulingOrdinal: codeReview.rulingOrdinal,
4053
+ policyDigest: codeReview.policyDigest
4054
+ };
4055
+ const securityEchoed = {
4056
+ headSha: security.headSha,
4057
+ briefHash: security.briefHash,
4058
+ objectivesVersion: security.objectivesVersion,
4059
+ rulingOrdinal: security.rulingOrdinal,
4060
+ policyDigest: security.policyDigest
4061
+ };
4062
+ const codeReviewBinding = compareManifest(codeReviewEchoed, currentManifest, input.patchIdOf);
4063
+ const securityBinding = compareManifest(securityEchoed, currentManifest, input.patchIdOf);
4064
+ const codeReviewBound = codeReviewBinding.head;
4065
+ const securityBound = securityBinding.head;
4066
+ const codeReviewObjectivesBound = codeReviewBinding.objectivesVersion;
4067
+ const securityObjectivesBound = securityBinding.objectivesVersion;
4068
+ const codeReviewRulingsBound = codeReviewBinding.rulingOrdinal;
4069
+ const securityRulingsBound = securityBinding.rulingOrdinal;
4070
+ if (codeReviewClean && codeReviewBinding.bound && securityClean && securityBinding.bound && mechanicalChecksClean) {
3704
4071
  return {
3705
4072
  verdict: "pass",
3706
4073
  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.`,
@@ -3708,19 +4075,35 @@ function checkReviewGate(input) {
3708
4075
  };
3709
4076
  }
3710
4077
  const problems = [];
3711
- if (!codeReviewClean) {
4078
+ if (!codeReviewTextClean) {
3712
4079
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
4080
+ } else if (!codeReviewPolicyClean) {
4081
+ 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`);
3713
4082
  } else if (!codeReviewBound) {
3714
4083
  problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3715
4084
  } else if (!codeReviewObjectivesBound) {
3716
4085
  problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3717
- }
3718
- if (!securityClean) {
4086
+ } else if (!codeReviewRulingsBound) {
4087
+ 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`);
4088
+ } else if (!codeReviewBinding.briefHash) {
4089
+ 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"}`);
4090
+ } else if (!codeReviewBinding.policyDigest) {
4091
+ 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}`);
4092
+ }
4093
+ if (!securityTextClean) {
3719
4094
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
4095
+ } else if (!securityPolicyClean) {
4096
+ 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`);
3720
4097
  } else if (!securityBound) {
3721
4098
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3722
4099
  } else if (!securityObjectivesBound) {
3723
4100
  problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
4101
+ } else if (!securityRulingsBound) {
4102
+ 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`);
4103
+ } else if (!securityBinding.briefHash) {
4104
+ 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"}`);
4105
+ } else if (!securityBinding.policyDigest) {
4106
+ 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}`);
3724
4107
  }
3725
4108
  if (!mechanicalChecksClean) {
3726
4109
  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(", ")}`);
@@ -3737,6 +4120,24 @@ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
3737
4120
  function isChangesetsReleasePr(branch, author, expectedAuthor) {
3738
4121
  return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
3739
4122
  }
4123
+ // ../../packages/aeg-core/src/ruling-ordinal.ts
4124
+ var RULING_MARKER_ORDINAL = /^<!-- aeg:principal:ruling:\d+-(\d+) -->$/;
4125
+ function newestPrincipalRulingOrdinal(comments, allowlist) {
4126
+ let best = 0;
4127
+ for (const c of comments) {
4128
+ if (!isPrincipal(c.author, allowlist))
4129
+ continue;
4130
+ const firstLine = (c.body.split(`
4131
+ `)[0] ?? "").trim();
4132
+ const m = RULING_MARKER_ORDINAL.exec(firstLine);
4133
+ if (!m)
4134
+ continue;
4135
+ const k = Number.parseInt(m[1], 10);
4136
+ if (k > best)
4137
+ best = k;
4138
+ }
4139
+ return best;
4140
+ }
3740
4141
  // ../../packages/aeg-core/src/markdown-table.ts
3741
4142
  function splitRow(line) {
3742
4143
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -4394,42 +4795,45 @@ function checkDispatchReadiness(input) {
4394
4795
  const { trancheSlug, task } = input;
4395
4796
  const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
4396
4797
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
4397
- const blockers = [];
4798
+ const blockerDetails = [];
4799
+ const push = (blockerClass, message) => {
4800
+ blockerDetails.push({ class: blockerClass, message });
4801
+ };
4398
4802
  if (task.issue === null) {
4399
- blockers.push(`dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
4803
+ 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.`);
4400
4804
  } else if (input.issue === null) {
4401
- blockers.push(`dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
4805
+ push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
4402
4806
  }
4403
4807
  if (input.issue !== null && !input.issueRationalePass) {
4404
- 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.`);
4808
+ 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.`);
4405
4809
  }
4406
4810
  for (const dep of input.dependsOn) {
4407
4811
  if (isSelfDependency(dep, task, input.issue)) {
4408
4812
  const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
4409
- 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.`);
4813
+ 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.`);
4410
4814
  continue;
4411
4815
  }
4412
4816
  if (dep.resolved === false) {
4413
- 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.`);
4817
+ 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.`);
4414
4818
  continue;
4415
4819
  }
4416
4820
  if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
4417
4821
  const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
4418
- blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
4822
+ 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.`);
4419
4823
  }
4420
4824
  }
4421
4825
  for (const c of input.conflictsWith) {
4422
4826
  if (c.openOrInFlight) {
4423
4827
  const issueStr = c.issue !== null ? ` (#${c.issue})` : "";
4424
- blockers.push(`dispatch-gate conflicts-with: ${taskLabel} conflicts with ${c.id}${issueStr}, whose PR is open or in-flight — not dispatchable until it merges.`);
4828
+ 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.`);
4425
4829
  }
4426
4830
  }
4427
4831
  for (const proj of input.priorTrancheArchival) {
4428
4832
  if (proj.priorTrancheSlug !== null && !proj.archived) {
4429
- 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.`);
4833
+ 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.`);
4430
4834
  }
4431
4835
  }
4432
- return { ready: blockers.length === 0, blockers };
4836
+ return { ready: blockerDetails.length === 0, blockers: blockerDetails.map((b) => b.message), blockerDetails };
4433
4837
  }
4434
4838
  // ../../packages/aeg-core/src/single-plan-pr.ts
4435
4839
  function trancheSlugFromTopologyPath(path) {
@@ -4531,6 +4935,91 @@ function checkBranchTopology(input) {
4531
4935
  reason: `Branch \`${branch}\` matches topology row \`${taskId}\` in ${topoPath}.`
4532
4936
  };
4533
4937
  }
4938
+ // ../../packages/aeg-core/src/task-tools.ts
4939
+ import { z } from "zod";
4940
+ var TASK_TOOL_ERROR_KINDS = [
4941
+ "validation",
4942
+ "authority",
4943
+ "precondition",
4944
+ "capability",
4945
+ "infrastructure",
4946
+ "cancellation",
4947
+ "timeout",
4948
+ "uncertain_effect"
4949
+ ];
4950
+ var TaskToolErrorSchema = z.object({
4951
+ kind: z.enum(TASK_TOOL_ERROR_KINDS),
4952
+ message: z.string().min(1),
4953
+ detail: z.string().optional()
4954
+ });
4955
+ var TaskToolRefSchema = z.union([
4956
+ z.object({ tranche: z.string().min(1), id: z.string().min(1) }),
4957
+ z.object({ issue: z.number().int().positive() })
4958
+ ]);
4959
+ var MAX_PAGE_LIMIT = 100;
4960
+ var PageRequestSchema = z.object({
4961
+ cursor: z.string().optional(),
4962
+ limit: z.number().int().positive().max(MAX_PAGE_LIMIT).optional()
4963
+ });
4964
+ var FreshnessSchema = z.enum(["fresh", "stale", "unknown"]);
4965
+ var ObservedSchema = z.object({
4966
+ observedAt: z.string(),
4967
+ freshness: FreshnessSchema
4968
+ });
4969
+ var TaskStatusInputSchema = z.object({
4970
+ task: TaskToolRefSchema.optional()
4971
+ }).merge(PageRequestSchema);
4972
+ var TaskStatusItemSchema = z.object({
4973
+ task: TaskToolRefSchema,
4974
+ issue: z.number().int().positive(),
4975
+ pr: z.number().int().positive().nullable(),
4976
+ state: z.string()
4977
+ }).merge(ObservedSchema);
4978
+ var TaskStatusResultSchema = z.object({
4979
+ items: z.array(TaskStatusItemSchema),
4980
+ nextCursor: z.string().nullable()
4981
+ });
4982
+ var RequestedAuthoritySchema = z.enum(["planner", "principal", "operator", "self"]);
4983
+ var EscalationInputsSchema = z.object({
4984
+ task: z.number().int().positive(),
4985
+ round: z.number().int().nonnegative(),
4986
+ head: z.string(),
4987
+ branch: z.string(),
4988
+ prNumber: z.number().int().positive()
4989
+ });
4990
+ var EscalationEvidenceSchema = z.object({
4991
+ round: z.number().int().nonnegative(),
4992
+ reviewer: z.string().nullable(),
4993
+ security: z.string().nullable()
4994
+ });
4995
+ var TaskEscalationReadInputSchema = z.object({
4996
+ task: TaskToolRefSchema
4997
+ }).merge(PageRequestSchema);
4998
+ var TaskEscalationPacketSchema = z.object({
4999
+ reason: z.string(),
5000
+ detail: z.string().nullable(),
5001
+ inputs: EscalationInputsSchema.nullable(),
5002
+ evidence: EscalationEvidenceSchema.nullable(),
5003
+ attemptedRecovery: z.string(),
5004
+ requestedAuthority: RequestedAuthoritySchema,
5005
+ permittedNextActions: z.array(z.string())
5006
+ }).merge(ObservedSchema);
5007
+ var TaskEscalationReadResultSchema = z.object({
5008
+ items: z.array(TaskEscalationPacketSchema),
5009
+ nextCursor: z.string().nullable()
5010
+ }).merge(ObservedSchema);
5011
+ var TaskStartInputSchema = z.object({
5012
+ tranche: z.string().min(1),
5013
+ id: z.string().min(1)
5014
+ });
5015
+ var TaskResumeInputSchema = z.object({
5016
+ task: TaskToolRefSchema
5017
+ });
5018
+ var TaskCancelInputSchema = z.object({
5019
+ task: TaskToolRefSchema,
5020
+ reason: z.string().min(1)
5021
+ });
5022
+ var NoResultSchema = z.never();
4534
5023
  // ../../packages/aeg-core/src/first-push-dispatch-gate.ts
4535
5024
  function parseTaskBranch(branch) {
4536
5025
  const m = /^task\/([^/]+)\/([^/]+)$/.exec(branch);
@@ -4614,7 +5103,6 @@ function decideIssueAssignment(input) {
4614
5103
  };
4615
5104
  }
4616
5105
  // ../../packages/aeg-core/src/test-plan-gate.ts
4617
- var TASK_BRANCH_PATTERN2 = /^task\/[^/]+\/[^/]+$/;
4618
5106
  function evaluateTestPlanGate(body, branch) {
4619
5107
  if (!body) {
4620
5108
  return {
@@ -4627,7 +5115,7 @@ function evaluateTestPlanGate(body, branch) {
4627
5115
  }
4628
5116
  const located = locateTestPlanSection(body);
4629
5117
  if (!located.found) {
4630
- if (TASK_BRANCH_PATTERN2.test(branch)) {
5118
+ if (parseTaskBranchIdentity(branch) !== null) {
4631
5119
  return {
4632
5120
  verdict: "fail",
4633
5121
  messages: [
@@ -4755,7 +5243,7 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4755
5243
  return findings;
4756
5244
  }
4757
5245
  // ../../packages/aeg-core/src/log/schema.ts
4758
- import { z } from "zod";
5246
+ import { z as z2 } from "zod";
4759
5247
  var ROLE_VALUES = [
4760
5248
  "planner",
4761
5249
  "developer",
@@ -4765,136 +5253,172 @@ var ROLE_VALUES = [
4765
5253
  "archivist",
4766
5254
  "architect"
4767
5255
  ];
4768
- var RoleSchema = z.enum(ROLE_VALUES);
5256
+ var RoleSchema = z2.enum(ROLE_VALUES);
4769
5257
  var HOST_VALUES = ["hook", "ci", "cli", "loop"];
4770
- var HostSchema = z.enum(HOST_VALUES);
5258
+ var HostSchema = z2.enum(HOST_VALUES);
4771
5259
  var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
4772
- var HeaderMetaSchema = z.object({
4773
- schema: z.literal(1),
4774
- ts: z.string(),
4775
- run_id: z.string().regex(RUN_ID_PATTERN),
4776
- seq: z.number().int().nonnegative(),
4777
- repo: z.string().nullable(),
4778
- vinaya: z.string(),
4779
- doctrine: z.string(),
5260
+ var headerMetaCore = {
5261
+ ts: z2.string(),
5262
+ run_id: z2.string().regex(RUN_ID_PATTERN),
5263
+ seq: z2.number().int().nonnegative(),
5264
+ repo: z2.string().nullable(),
5265
+ vinaya: z2.string(),
5266
+ doctrine: z2.string(),
4780
5267
  host: HostSchema,
4781
- machine: z.string()
5268
+ machine: z2.string()
5269
+ };
5270
+ var HeaderMetaV1Schema = z2.object({
5271
+ schema: z2.literal(1),
5272
+ ...headerMetaCore
5273
+ }).strict();
5274
+ var LineageSchema = z2.object({
5275
+ run: z2.string().nullable(),
5276
+ attempt: z2.number().int().nullable(),
5277
+ parent: z2.string().nullable()
4782
5278
  }).strict();
4783
- var SubjectSchema = z.object({
4784
- issue: z.number().int().nullable(),
4785
- pr: z.number().int().optional(),
4786
- sha: z.string().optional(),
4787
- role: z.union([RoleSchema, z.literal("unattributed")]),
4788
- round: z.number().int().optional(),
4789
- objectives_version: z.string().optional()
5279
+ var InputVersionsSchema = z2.object({
5280
+ objectives_version: z2.string().nullable(),
5281
+ brief_hash: z2.string().nullable(),
5282
+ ruling_ordinal: z2.number().int().nullable(),
5283
+ policy_digest: z2.string().nullable()
4790
5284
  }).strict();
4791
- var HeaderSchema = z.object({
5285
+ var ProvenanceSchema = z2.enum(["parent_attributed", "env_correlated", "self_reported", "unavailable"]);
5286
+ var HeaderMetaV2Schema = z2.object({
5287
+ schema: z2.literal(2),
5288
+ ...headerMetaCore,
5289
+ event_id: z2.string().min(1),
5290
+ process_id: z2.string().min(1),
5291
+ actor_id: z2.string().nullable(),
5292
+ lineage: LineageSchema,
5293
+ input_versions: InputVersionsSchema,
5294
+ provenance: ProvenanceSchema
5295
+ }).strict();
5296
+ var HeaderMetaSchema = z2.discriminatedUnion("schema", [HeaderMetaV1Schema, HeaderMetaV2Schema]);
5297
+ var SubjectSchema = z2.object({
5298
+ issue: z2.number().int().nullable(),
5299
+ pr: z2.number().int().optional(),
5300
+ sha: z2.string().optional(),
5301
+ role: z2.union([RoleSchema, z2.literal("unattributed")]),
5302
+ round: z2.number().int().optional(),
5303
+ objectives_version: z2.string().optional()
5304
+ }).strict();
5305
+ var HeaderSchema = z2.object({
4792
5306
  meta: HeaderMetaSchema,
4793
5307
  subject: SubjectSchema
4794
5308
  }).strict();
4795
5309
  var envelopeTail = {
4796
- duration_ms: z.number().nonnegative().optional(),
4797
- payload: z.object({}).strict()
5310
+ duration_ms: z2.number().nonnegative().optional(),
5311
+ payload: z2.object({}).strict()
4798
5312
  };
4799
- var DispatchOutcomeSchema = z.discriminatedUnion("type", [
4800
- z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
4801
- z.object({
4802
- type: z.literal("round_pushed"),
4803
- pr: z.number().int(),
4804
- head: z.string(),
4805
- comment_id: z.number().int()
5313
+ var ReviewFindingSchema = z2.object({
5314
+ id: z2.string(),
5315
+ severity: z2.string(),
5316
+ state: z2.string().optional(),
5317
+ severity_scale: z2.string().optional(),
5318
+ policy_treatment: z2.enum(["blocking", "non_blocking", "unavailable"]).optional(),
5319
+ confidence: z2.number().min(0).max(1).optional(),
5320
+ confidence_scale: z2.string().optional(),
5321
+ confidence_source: z2.string().optional()
5322
+ }).strict();
5323
+ var DispatchOutcomeSchema = z2.discriminatedUnion("type", [
5324
+ z2.object({ type: z2.literal("pr_opened"), pr: z2.number().int(), head: z2.string() }).strict(),
5325
+ z2.object({
5326
+ type: z2.literal("round_pushed"),
5327
+ pr: z2.number().int(),
5328
+ head: z2.string(),
5329
+ comment_id: z2.number().int()
4806
5330
  }).strict(),
4807
- z.object({
4808
- type: z.literal("verdict"),
4809
- verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
4810
- head: z.string(),
4811
- comment_id: z.number().int(),
4812
- objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
4813
- findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
5331
+ z2.object({
5332
+ type: z2.literal("verdict"),
5333
+ verdict: z2.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
5334
+ head: z2.string(),
5335
+ comment_id: z2.number().int(),
5336
+ objectives: z2.array(z2.object({ id: z2.string(), met: z2.boolean() }).strict()),
5337
+ findings: z2.array(ReviewFindingSchema)
4814
5338
  }).strict(),
4815
- z.object({
4816
- type: z.literal("escalation"),
4817
- class: z.enum(["authority", "strategy", "product"]),
4818
- comment_id: z.number().int()
5339
+ z2.object({
5340
+ type: z2.literal("escalation"),
5341
+ class: z2.enum(["authority", "strategy", "product"]),
5342
+ comment_id: z2.number().int()
4819
5343
  }).strict(),
4820
- z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
4821
- z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
4822
- z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
5344
+ z2.object({ type: z2.literal("brief"), comment_id: z2.number().int(), hash: z2.string() }).strict(),
5345
+ z2.object({ type: z2.literal("plan"), issues: z2.array(z2.number().int()) }).strict(),
5346
+ z2.object({ type: z2.literal("archive"), provenance_comment_id: z2.number().int() }).strict()
4823
5347
  ]);
4824
5348
  var dispatchShared = {
4825
5349
  meta: HeaderMetaSchema,
4826
5350
  subject: SubjectSchema,
4827
- kind: z.literal("dispatch"),
5351
+ kind: z2.literal("dispatch"),
4828
5352
  ...envelopeTail,
4829
5353
  target_role: RoleSchema,
4830
- model: z.string(),
4831
- round: z.number().int().optional(),
4832
- effect_id: z.string()
5354
+ model: z2.string(),
5355
+ round: z2.number().int().optional(),
5356
+ effect_id: z2.string()
4833
5357
  };
4834
- var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
4835
- var DispatchEventSchema = z.discriminatedUnion("event", [
4836
- z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
4837
- z.object({
5358
+ var dispatchUsageField = z2.object({ input: z2.number().nonnegative(), output: z2.number().nonnegative() }).strict().nullable();
5359
+ var DispatchEventSchema = z2.discriminatedUnion("event", [
5360
+ z2.object({ ...dispatchShared, event: z2.literal("dispatched"), prompt_hash: z2.string() }).strict(),
5361
+ z2.object({
4838
5362
  ...dispatchShared,
4839
- event: z.literal("outcome_received"),
5363
+ event: z2.literal("outcome_received"),
4840
5364
  outcome: DispatchOutcomeSchema,
4841
5365
  usage: dispatchUsageField
4842
5366
  }).strict(),
4843
- z.object({
5367
+ z2.object({
4844
5368
  ...dispatchShared,
4845
- event: z.literal("dispatch_failed"),
4846
- reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
5369
+ event: z2.literal("dispatch_failed"),
5370
+ reason: z2.enum(["timeout", "crash", "refused", "unattributed_write"]),
4847
5371
  usage: dispatchUsageField
4848
5372
  }).strict()
4849
5373
  ]);
4850
5374
  var loopShared = {
4851
5375
  meta: HeaderMetaSchema,
4852
5376
  subject: SubjectSchema,
4853
- kind: z.literal("dev_review_loop"),
5377
+ kind: z2.literal("dev_review_loop"),
4854
5378
  ...envelopeTail,
4855
- loop_id: z.string()
5379
+ loop_id: z2.string()
4856
5380
  };
4857
- var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4858
- z.object({
5381
+ var DevReviewLoopEventSchema = z2.discriminatedUnion("event", [
5382
+ z2.object({
4859
5383
  ...loopShared,
4860
- event: z.literal("loop_started"),
4861
- task: z.number().int(),
4862
- policy: z.object({
4863
- max_rounds: z.number().int().nonnegative(),
4864
- reviewers: z.array(RoleSchema),
4865
- models: z.record(RoleSchema, z.string())
5384
+ event: z2.literal("loop_started"),
5385
+ task: z2.number().int(),
5386
+ policy: z2.object({
5387
+ max_rounds: z2.number().int().nonnegative(),
5388
+ reviewers: z2.array(RoleSchema),
5389
+ models: z2.record(RoleSchema, z2.string())
4866
5390
  }).strict()
4867
5391
  }).strict(),
4868
- z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
4869
- z.object({
5392
+ z2.object({ ...loopShared, event: z2.literal("round_started"), round: z2.number().int(), base_head: z2.string() }).strict(),
5393
+ z2.object({
4870
5394
  ...loopShared,
4871
- event: z.literal("gate_result_read"),
4872
- round: z.number().int(),
4873
- head: z.string(),
4874
- green: z.boolean()
5395
+ event: z2.literal("gate_result_read"),
5396
+ round: z2.number().int(),
5397
+ head: z2.string(),
5398
+ green: z2.boolean()
4875
5399
  }).strict(),
4876
- z.object({
5400
+ z2.object({
4877
5401
  ...loopShared,
4878
- event: z.literal("verdicts_read"),
4879
- round: z.number().int(),
4880
- head: z.string(),
4881
- all_approve: z.boolean(),
4882
- blockers: z.number().int().nonnegative()
5402
+ event: z2.literal("verdicts_read"),
5403
+ round: z2.number().int(),
5404
+ head: z2.string(),
5405
+ all_approve: z2.boolean(),
5406
+ blockers: z2.number().int().nonnegative()
4883
5407
  }).strict(),
4884
- z.object({
5408
+ z2.object({
4885
5409
  ...loopShared,
4886
- event: z.literal("findings_compared"),
4887
- round: z.number().int(),
4888
- open: z.array(z.string()),
4889
- resolved: z.array(z.string()),
4890
- new: z.array(z.string()),
4891
- recurring: z.array(z.string())
5410
+ event: z2.literal("findings_compared"),
5411
+ round: z2.number().int(),
5412
+ open: z2.array(z2.string()),
5413
+ resolved: z2.array(z2.string()),
5414
+ new: z2.array(z2.string()),
5415
+ recurring: z2.array(z2.string())
4892
5416
  }).strict(),
4893
- z.object({
5417
+ z2.object({
4894
5418
  ...loopShared,
4895
- event: z.literal("stop_condition_met"),
4896
- round: z.number().int(),
4897
- condition: z.enum([
5419
+ event: z2.literal("stop_condition_met"),
5420
+ round: z2.number().int(),
5421
+ condition: z2.enum([
4898
5422
  "green",
4899
5423
  "max_rounds",
4900
5424
  "no_progress",
@@ -4904,42 +5428,49 @@ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4904
5428
  "reappearance"
4905
5429
  ])
4906
5430
  }).strict(),
4907
- z.object({
5431
+ z2.object({
4908
5432
  ...loopShared,
4909
- event: z.literal("paused"),
4910
- round: z.number().int(),
4911
- reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
5433
+ event: z2.literal("paused"),
5434
+ round: z2.number().int(),
5435
+ reason: z2.enum(["escalation", "principal_item", "refreeze_needed"])
4912
5436
  }).strict(),
4913
- z.object({
5437
+ z2.object({
4914
5438
  ...loopShared,
4915
- event: z.literal("resumed"),
4916
- round: z.number().int(),
4917
- by: z.literal("principal")
5439
+ event: z2.literal("resumed"),
5440
+ round: z2.number().int(),
5441
+ by: z2.literal("principal")
4918
5442
  }).strict(),
4919
- z.object({
5443
+ z2.object({
4920
5444
  ...loopShared,
4921
- event: z.literal("round_ended"),
4922
- round: z.number().int(),
4923
- base_head: z.string(),
4924
- head: z.string(),
4925
- files_changed: z.number().int().nonnegative(),
4926
- insertions: z.number().int().nonnegative(),
4927
- deletions: z.number().int().nonnegative(),
4928
- wall_ms: z.number().nonnegative(),
4929
- outcome: z.enum(["green", "changes_requested", "escalated"])
5445
+ event: z2.literal("unpushed_work_resume"),
5446
+ round: z2.number().int(),
5447
+ branch: z2.string(),
5448
+ detail: z2.string()
4930
5449
  }).strict(),
4931
- z.object({
5450
+ z2.object({
4932
5451
  ...loopShared,
4933
- event: z.literal("journal_finalized"),
4934
- rounds: z.number().int().nonnegative(),
4935
- total_wall_ms: z.number().nonnegative(),
4936
- time_to_green_ms: z.number().nonnegative().nullable(),
4937
- files_changed_total: z.number().int().nonnegative(),
4938
- final_head: z.string(),
4939
- result: z.enum(["merged_ready", "stopped"])
5452
+ event: z2.literal("round_ended"),
5453
+ round: z2.number().int(),
5454
+ base_head: z2.string(),
5455
+ head: z2.string(),
5456
+ files_changed: z2.number().int().nonnegative(),
5457
+ insertions: z2.number().int().nonnegative(),
5458
+ deletions: z2.number().int().nonnegative(),
5459
+ wall_ms: z2.number().nonnegative(),
5460
+ outcome: z2.enum(["green", "changes_requested", "escalated"])
5461
+ }).strict(),
5462
+ z2.object({
5463
+ ...loopShared,
5464
+ event: z2.literal("journal_finalized"),
5465
+ rounds: z2.number().int().nonnegative(),
5466
+ total_wall_ms: z2.number().nonnegative(),
5467
+ time_to_green_ms: z2.number().nonnegative().nullable(),
5468
+ files_changed_total: z2.number().int().nonnegative(),
5469
+ final_head: z2.string(),
5470
+ result: z2.enum(["merged_ready", "stopped"])
4940
5471
  }).strict()
4941
5472
  ]);
4942
- var ForgeOpSchema = z.enum([
5473
+ var ForgeOpSchema = z2.enum([
4943
5474
  "pr.create",
4944
5475
  "pr.comment",
4945
5476
  "pr.body.replace",
@@ -4953,24 +5484,210 @@ var ForgeOpSchema = z.enum([
4953
5484
  "label.add",
4954
5485
  "label.remove"
4955
5486
  ]);
4956
- var ForgeWriteTargetSchema = z.object({
4957
- issue: z.number().int().optional(),
4958
- pr: z.number().int().optional()
5487
+ var ForgeWriteTargetSchema = z2.object({
5488
+ issue: z2.number().int().optional(),
5489
+ pr: z2.number().int().optional()
4959
5490
  }).strict();
4960
5491
  var forgeWriteShared = {
4961
5492
  meta: HeaderMetaSchema,
4962
5493
  subject: SubjectSchema,
4963
- kind: z.literal("forge_write"),
5494
+ kind: z2.literal("forge_write"),
4964
5495
  ...envelopeTail,
4965
5496
  op: ForgeOpSchema,
4966
5497
  target: ForgeWriteTargetSchema
4967
5498
  };
4968
- var ForgeWriteEventSchema = z.discriminatedUnion("event", [
4969
- z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
4970
- z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
4971
- z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
5499
+ var ForgeWriteEventSchema = z2.discriminatedUnion("event", [
5500
+ z2.object({ ...forgeWriteShared, event: z2.literal("validated") }).strict(),
5501
+ z2.object({ ...forgeWriteShared, event: z2.literal("refused"), reason: z2.string() }).strict(),
5502
+ z2.object({ ...forgeWriteShared, event: z2.literal("written"), comment_ids: z2.array(z2.string()) }).strict()
5503
+ ]);
5504
+ var GateOutcomeSchema = z2.enum([
5505
+ "pass",
5506
+ "fail",
5507
+ "wait",
5508
+ "skip",
5509
+ "invalid_input",
5510
+ "unavailable_dependency",
5511
+ "timeout",
5512
+ "cancelled"
5513
+ ]);
5514
+ var gateShared = {
5515
+ meta: HeaderMetaSchema,
5516
+ subject: SubjectSchema,
5517
+ kind: z2.literal("gate"),
5518
+ ...envelopeTail,
5519
+ check: z2.string(),
5520
+ check_version: z2.string().nullable(),
5521
+ policy_version: z2.string().nullable(),
5522
+ input_fingerprint: z2.string().nullable()
5523
+ };
5524
+ var GateEventSchema = z2.discriminatedUnion("event", [
5525
+ z2.object({
5526
+ ...gateShared,
5527
+ event: z2.literal("checked"),
5528
+ outcome: GateOutcomeSchema,
5529
+ reason: z2.string().optional()
5530
+ }).strict()
5531
+ ]);
5532
+ var OperationResultSchema = z2.enum(["ok", "error", "refused", "timeout", "cancelled", "unavailable"]);
5533
+ var operationShared = {
5534
+ meta: HeaderMetaSchema,
5535
+ subject: SubjectSchema,
5536
+ kind: z2.literal("operation"),
5537
+ ...envelopeTail,
5538
+ operation: z2.string(),
5539
+ target: z2.string().nullable()
5540
+ };
5541
+ var OperationEventSchema = z2.discriminatedUnion("event", [
5542
+ z2.object({
5543
+ ...operationShared,
5544
+ event: z2.literal("completed"),
5545
+ result: OperationResultSchema,
5546
+ error_class: z2.string().nullable()
5547
+ }).strict()
4972
5548
  ]);
4973
- var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
5549
+ var UsageUnitsSchema = z2.object({
5550
+ input: z2.number().nonnegative().nullable(),
5551
+ output: z2.number().nonnegative().nullable(),
5552
+ cache: z2.number().nonnegative().nullable()
5553
+ }).strict();
5554
+ var usageShared = {
5555
+ meta: HeaderMetaSchema,
5556
+ subject: SubjectSchema,
5557
+ kind: z2.literal("usage"),
5558
+ ...envelopeTail,
5559
+ model: z2.string().nullable(),
5560
+ source: z2.string(),
5561
+ semantics: z2.enum(["cumulative", "delta"])
5562
+ };
5563
+ var UsageEventSchema = z2.discriminatedUnion("event", [
5564
+ z2.object({
5565
+ ...usageShared,
5566
+ event: z2.literal("observed"),
5567
+ units: UsageUnitsSchema,
5568
+ unknown_reason: z2.string().nullable()
5569
+ }).strict()
5570
+ ]);
5571
+ var RoleAttemptOutcomeSchema = z2.enum([
5572
+ "completed",
5573
+ "incomplete",
5574
+ "infrastructure_failed",
5575
+ "cancelled",
5576
+ "timed_out",
5577
+ "capability_refused"
5578
+ ]);
5579
+ var roleAttemptShared = {
5580
+ meta: HeaderMetaSchema,
5581
+ subject: SubjectSchema,
5582
+ kind: z2.literal("role_attempt"),
5583
+ ...envelopeTail,
5584
+ actor: z2.string().nullable(),
5585
+ attempt: z2.number().int().nullable()
5586
+ };
5587
+ var RoleAttemptEventSchema = z2.discriminatedUnion("event", [
5588
+ z2.object({
5589
+ ...roleAttemptShared,
5590
+ event: z2.literal("attempted"),
5591
+ outcome: RoleAttemptOutcomeSchema,
5592
+ usage: dispatchUsageField
5593
+ }).strict()
5594
+ ]);
5595
+ var handoffShared = {
5596
+ meta: HeaderMetaSchema,
5597
+ subject: SubjectSchema,
5598
+ kind: z2.literal("handoff"),
5599
+ ...envelopeTail,
5600
+ class: z2.enum(["authority", "strategy", "product"]),
5601
+ reason: z2.string()
5602
+ };
5603
+ var HandoffEventSchema = z2.discriminatedUnion("event", [
5604
+ z2.object({
5605
+ ...handoffShared,
5606
+ event: z2.literal("raised"),
5607
+ requested_decision: z2.string().nullable()
5608
+ }).strict(),
5609
+ z2.object({
5610
+ ...handoffShared,
5611
+ event: z2.literal("resolved"),
5612
+ resolution: z2.string().nullable(),
5613
+ resolved_by: z2.string().nullable()
5614
+ }).strict()
5615
+ ]);
5616
+ var EffectTargetSchema = z2.object({
5617
+ kind: z2.string(),
5618
+ ref: z2.string()
5619
+ }).strict();
5620
+ var effectShared = {
5621
+ meta: HeaderMetaSchema,
5622
+ subject: SubjectSchema,
5623
+ kind: z2.literal("effect"),
5624
+ ...envelopeTail,
5625
+ effect_id: z2.string(),
5626
+ target: EffectTargetSchema
5627
+ };
5628
+ var EffectEventSchema = z2.discriminatedUnion("event", [
5629
+ z2.object({ ...effectShared, event: z2.literal("attempted") }).strict(),
5630
+ z2.object({ ...effectShared, event: z2.literal("observed"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict(),
5631
+ z2.object({ ...effectShared, event: z2.literal("verified"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict()
5632
+ ]);
5633
+ var LogEventSchema = z2.union([
5634
+ DispatchEventSchema,
5635
+ DevReviewLoopEventSchema,
5636
+ ForgeWriteEventSchema,
5637
+ GateEventSchema,
5638
+ OperationEventSchema,
5639
+ UsageEventSchema,
5640
+ RoleAttemptEventSchema,
5641
+ HandoffEventSchema,
5642
+ EffectEventSchema
5643
+ ]);
5644
+ // ../../packages/aeg-core/src/dev-review-loop/journal-reconstruction.ts
5645
+ var STOPPED_CONDITIONS = new Set(["confidence", "reappearance", "no_progress", "max_rounds"]);
5646
+ // ../../packages/aeg-core/src/control-store/records.ts
5647
+ import { z as z3 } from "zod";
5648
+ var isoTimestamp = z3.string().min(1);
5649
+ var taskId = z3.number().int().positive();
5650
+ var epochNumber = z3.number().int().nonnegative();
5651
+ var RunRecordSchema = z3.object({
5652
+ version: z3.literal(1),
5653
+ kind: z3.literal("run"),
5654
+ task: taskId,
5655
+ runId: z3.string().min(1),
5656
+ pid: z3.number().int().positive(),
5657
+ host: z3.string().min(1),
5658
+ startedAt: isoTimestamp
5659
+ }).strict();
5660
+ var InputRecordSchema = z3.object({
5661
+ version: z3.literal(1),
5662
+ kind: z3.literal("input"),
5663
+ task: taskId,
5664
+ runId: z3.string().min(1),
5665
+ source: z3.union([z3.literal("fresh"), z3.literal("resume")]),
5666
+ pr: z3.number().int().positive().nullable(),
5667
+ round: z3.number().int().nonnegative(),
5668
+ recordedAt: isoTimestamp
5669
+ }).strict();
5670
+ var OwnershipRecordSchema = z3.object({
5671
+ version: z3.literal(1),
5672
+ kind: z3.literal("ownership"),
5673
+ task: taskId,
5674
+ epoch: epochNumber,
5675
+ ownerId: z3.string().min(1),
5676
+ pid: z3.number().int().positive(),
5677
+ host: z3.string().min(1),
5678
+ acquiredAt: isoTimestamp
5679
+ }).strict();
5680
+ var TransitionRecordSchema = z3.object({
5681
+ version: z3.literal(1),
5682
+ kind: z3.literal("transition"),
5683
+ task: taskId,
5684
+ epoch: epochNumber,
5685
+ seq: z3.number().int().nonnegative(),
5686
+ from: z3.string().min(1),
5687
+ to: z3.string().min(1),
5688
+ detail: z3.string().optional(),
5689
+ at: isoTimestamp
5690
+ }).strict();
4974
5691
  // src/checks/contract.ts
4975
5692
  var CHECK_SCHEMA_VERSION = 1;
4976
5693
  function emitCheckError(error) {