@attalabs/vinaya 0.27.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 (48) hide show
  1. package/README.md +4 -3
  2. package/aeg-root/enforcement.md +4 -4
  3. package/aeg-root/roles/developer.md +23 -17
  4. package/aeg-root/roles/planner.md +2 -0
  5. package/aeg-root/roles/principal.md +4 -0
  6. package/aeg-root/roles/reviewer.md +3 -3
  7. package/aeg-root/roles/security.md +3 -3
  8. package/dist/checks/bin/check-body-bare-digits.js +915 -240
  9. package/dist/checks/bin/check-branch-topology.js +820 -180
  10. package/dist/checks/bin/check-brief-shape.js +915 -240
  11. package/dist/checks/bin/check-changeset-coverage.js +1534 -262
  12. package/dist/checks/bin/check-closes-n.js +824 -184
  13. package/dist/checks/bin/check-coherence.js +984 -265
  14. package/dist/checks/bin/check-dead-branch-push.js +805 -176
  15. package/dist/checks/bin/check-dispatch-readiness.js +1046 -276
  16. package/dist/checks/bin/check-doc-coverage-push.js +1530 -258
  17. package/dist/checks/bin/check-doc-coverage.js +1532 -260
  18. package/dist/checks/bin/check-doctrine-no-procedures.js +915 -240
  19. package/dist/checks/bin/check-doctrine-portability.js +1529 -257
  20. package/dist/checks/bin/check-evidence-fresh.js +1875 -258
  21. package/dist/checks/bin/check-exec-bits.js +1527 -255
  22. package/dist/checks/bin/check-first-push-dispatch.js +932 -246
  23. package/dist/checks/bin/check-issue-assignment.js +822 -182
  24. package/dist/checks/bin/check-issue-milestone-attach.js +5734 -0
  25. package/dist/checks/bin/check-issue-objectives-numbering.js +5736 -0
  26. package/dist/checks/bin/check-issue-parts-coverage.js +5736 -0
  27. package/dist/checks/bin/check-issue-surface-globs.js +6910 -0
  28. package/dist/checks/bin/check-issue-title-grammar.js +5736 -0
  29. package/dist/checks/bin/check-issue-tranche-label.js +5736 -0
  30. package/dist/checks/bin/check-main-branch-refusal.js +805 -176
  31. package/dist/checks/bin/check-no-disk-state.js +805 -176
  32. package/dist/checks/bin/check-pr-premise-reassert.js +915 -240
  33. package/dist/checks/bin/check-pr-report-density.js +805 -176
  34. package/dist/checks/bin/check-quoted-command.js +1508 -255
  35. package/dist/checks/bin/check-reader-resolvable-prose.js +1512 -259
  36. package/dist/checks/bin/check-registry-gates.js +892 -176
  37. package/dist/checks/bin/check-retired-vocabulary.js +1506 -253
  38. package/dist/checks/bin/check-review-gate.js +1000 -245
  39. package/dist/checks/bin/check-single-plan-pr.js +805 -176
  40. package/dist/checks/bin/check-surface-scope.js +830 -182
  41. package/dist/checks/bin/check-test-plan.js +834 -189
  42. package/dist/checks/bin/check-token-collection-wired.js +805 -176
  43. package/dist/checks/bin/check-token-report.js +805 -176
  44. package/dist/checks/bin/check-workspace-escape.js +1525 -253
  45. package/dist/index.js +9835 -5597
  46. package/dist/lib/pre-push-changed-files.js +57 -0
  47. package/dist/lib/pre-push-select-tests.js +1473 -0
  48. package/package.json +4 -2
@@ -1955,6 +1955,8 @@ function parseInlineFieldList(section) {
1955
1955
  var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
1956
1956
  var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1957
1957
  var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
1958
+ var BRIEF_HASH_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Brief hash:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1959
+ var POLICY_DIGEST_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Policy digest:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
1958
1960
  function firstFiveLines(comment) {
1959
1961
  return comment.split(`
1960
1962
  `).slice(0, 5).join(`
@@ -1977,6 +1979,26 @@ function extractRulingOrdinal(comment) {
1977
1979
  const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
1978
1980
  return m ? Number.parseInt(m[1], 10) : null;
1979
1981
  }
1982
+ function firstElevenLines(comment) {
1983
+ return comment.split(`
1984
+ `).slice(0, 11).join(`
1985
+ `);
1986
+ }
1987
+ function extractBriefHash(comment) {
1988
+ const m = firstElevenLines(comment).match(BRIEF_HASH_PATTERN);
1989
+ return m ? m[1].toLowerCase() : null;
1990
+ }
1991
+ function extractPolicyDigest(comment) {
1992
+ const m = firstElevenLines(comment).match(POLICY_DIGEST_PATTERN);
1993
+ return m ? m[1].toLowerCase() : null;
1994
+ }
1995
+ var FINDING_SEVERITY_LINE = /^\d+\.\s+\[([A-Z][A-Z]*)\]\s+(.+?)\s+—/gm;
1996
+ function extractFindingSeverities(comment) {
1997
+ return [...comment.matchAll(FINDING_SEVERITY_LINE)].map((m) => ({
1998
+ severity: m[1],
1999
+ location: m[2]
2000
+ }));
2001
+ }
1980
2002
  function extractVerdict(comments, valuePattern, missingLabel) {
1981
2003
  const candidates = comments.filter((c) => valuePattern.test(c));
1982
2004
  if (candidates.length === 0) {
@@ -1985,6 +2007,9 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1985
2007
  headSha: null,
1986
2008
  objectivesVersion: null,
1987
2009
  rulingOrdinal: null,
2010
+ briefHash: null,
2011
+ policyDigest: null,
2012
+ findingSeverities: [],
1988
2013
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
1989
2014
  };
1990
2015
  }
@@ -1996,6 +2021,9 @@ function extractVerdict(comments, valuePattern, missingLabel) {
1996
2021
  headSha: null,
1997
2022
  objectivesVersion: null,
1998
2023
  rulingOrdinal: null,
2024
+ briefHash: null,
2025
+ policyDigest: null,
2026
+ findingSeverities: [],
1999
2027
  danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
2000
2028
  };
2001
2029
  }
@@ -2004,6 +2032,9 @@ function extractVerdict(comments, valuePattern, missingLabel) {
2004
2032
  headSha: extractHeadSha(latest),
2005
2033
  objectivesVersion: extractObjectivesVersion(latest),
2006
2034
  rulingOrdinal: extractRulingOrdinal(latest),
2035
+ briefHash: extractBriefHash(latest),
2036
+ policyDigest: extractPolicyDigest(latest),
2037
+ findingSeverities: extractFindingSeverities(latest),
2007
2038
  danglingNote: null
2008
2039
  };
2009
2040
  }
@@ -2339,6 +2370,53 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
2339
2370
  }
2340
2371
  return out;
2341
2372
  }
2373
+ // ../../packages/aeg-core/src/review-policy.ts
2374
+ var CODE_REVIEW_SEVERITY_ORDER = ["BLOCKER", "MAJOR", "MINOR"];
2375
+ var SECURITY_SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
2376
+ var DEFAULT_MAX_ROUNDS = 3;
2377
+ var DEFAULT_REVIEW_POLICY = {
2378
+ codeReviewThreshold: "BLOCKER",
2379
+ securityThreshold: "HIGH",
2380
+ maxRounds: DEFAULT_MAX_ROUNDS
2381
+ };
2382
+ var FILE_SHAPED_LOCATION = /\.[a-zA-Z0-9]{1,10}(:\d+)?\s*$/;
2383
+ var PROSE_LOCATION_PATTERNS = [/\bpr\s*body\b/i, /\bcomment\b/i];
2384
+ var ROLE_FILE_LOCATION = /(^|\/)aeg-root\/roles\//i;
2385
+ function isProseLocation(location) {
2386
+ if (ROLE_FILE_LOCATION.test(location))
2387
+ return true;
2388
+ if (FILE_SHAPED_LOCATION.test(location))
2389
+ return false;
2390
+ return PROSE_LOCATION_PATTERNS.some((pattern) => pattern.test(location));
2391
+ }
2392
+ var PROSE_CAP_SEVERITY = "MINOR";
2393
+ function blockingSeverities(scale, threshold) {
2394
+ const idx = scale.indexOf(threshold);
2395
+ if (idx === -1) {
2396
+ throw new Error(`blockingSeverities: threshold "${threshold}" is not one of ${scale.join(" > ")}`);
2397
+ }
2398
+ return scale.slice(0, idx + 1);
2399
+ }
2400
+ function evaluateReviewFindings(findings, scale, threshold) {
2401
+ const blocking = new Set(blockingSeverities(scale, threshold));
2402
+ const blockingFindings = findings.filter((f) => {
2403
+ if (!scale.includes(f.severity)) {
2404
+ throw new Error(`evaluateReviewFindings: severity "${f.severity}" is not one of ${scale.join(" > ")}`);
2405
+ }
2406
+ const effectiveSeverity = f.location !== undefined && isProseLocation(f.location) ? PROSE_CAP_SEVERITY : f.severity;
2407
+ return blocking.has(effectiveSeverity);
2408
+ });
2409
+ return { outcome: blockingFindings.length > 0 ? "blocked" : "clean", blockingFindings };
2410
+ }
2411
+ function evaluateCodeReview(findings, policy) {
2412
+ return evaluateReviewFindings(findings, CODE_REVIEW_SEVERITY_ORDER, policy.codeReviewThreshold);
2413
+ }
2414
+ function evaluateSecurityReview(findings, policy) {
2415
+ return evaluateReviewFindings(findings, SECURITY_SEVERITY_ORDER, policy.securityThreshold);
2416
+ }
2417
+ function isKnownSeverity(scale, value) {
2418
+ return scale.includes(value);
2419
+ }
2342
2420
  // ../../packages/aeg-core/src/pr-tier.ts
2343
2421
  var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
2344
2422
  function readTierFromPrBody(prBody) {
@@ -2709,7 +2787,7 @@ function checkForField(prBody) {
2709
2787
  ]
2710
2788
  };
2711
2789
  }
2712
- function checkClosesN(prBody) {
2790
+ function checkClosesNPresence(prBody) {
2713
2791
  const closesPattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s{0,8}:?\s{0,8}#\d+/i;
2714
2792
  if (closesPattern.test(stripCode(prBody))) {
2715
2793
  return { status: "pass", errors: [] };
@@ -2741,10 +2819,22 @@ var COMMIT_TYPES = [
2741
2819
  "Test"
2742
2820
  ];
2743
2821
  var COMMIT_TYPE_STYLE = new RegExp(`^(${COMMIT_TYPES.join("|")})(\\([a-z0-9-]+\\))?: \\S`);
2822
+ function checkForgeTitle(title) {
2823
+ const taskStyle = /^\[[a-z0-9._-]+\] \S+ — \S/;
2824
+ if (COMMIT_TYPE_STYLE.test(title) || taskStyle.test(title))
2825
+ return { status: "pass", errors: [] };
2826
+ return {
2827
+ status: "fail",
2828
+ errors: [
2829
+ `brief-validation title: "${title}" matches neither title grammar — expected \`Type: description\` / \`Type(scope): description\` (commitlint types + Plan) or \`[tranche] id — description\` (task form).`
2830
+ ]
2831
+ };
2832
+ }
2744
2833
  var BRIEF_SHAPE_MARKERS = [checkSurfaceMap, checkDocUpdateList, checkStopConditions, checkAutonomyClause];
2745
2834
  var TASK_BRANCH_PATTERN = /^task\/[^/]+\/[^/]+$/;
2835
+ var TASK_ISSUE_BRANCH_PATTERN = /^task\/issue-\d+$/;
2746
2836
  function isTaskBranch(branch) {
2747
- return TASK_BRANCH_PATTERN.test(branch);
2837
+ return TASK_BRANCH_PATTERN.test(branch) || TASK_ISSUE_BRANCH_PATTERN.test(branch);
2748
2838
  }
2749
2839
  function isBriefShaped(prBody) {
2750
2840
  const stripped = stripCode(prBody);
@@ -2866,8 +2956,9 @@ function packagesNamedIn(text) {
2866
2956
  }
2867
2957
  function hasTestPathForConsumer(text, consumerDir) {
2868
2958
  const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2869
- const re = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2870
- return re.test(text);
2959
+ const filePathRe = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
2960
+ const testDirRe = new RegExp(`${escaped}\\/(?:[\\w-]+\\/)*(?:tests|specs)(?:\\/|\\b)`);
2961
+ return filePathRe.test(text) || testDirRe.test(text);
2871
2962
  }
2872
2963
  function checkConsumerTests(prBody, consumersOf) {
2873
2964
  const section4 = extractNumberedSection(prBody, 4);
@@ -2971,7 +3062,7 @@ function checkBriefSections(prBody, readTier, options = {}) {
2971
3062
  checkConsumerTests(prBody, consumersOf),
2972
3063
  checkDefeatCases(prBody),
2973
3064
  ...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
2974
- ...requireClosesN ? [checkClosesN(prBody)] : []
3065
+ ...requireClosesN ? [checkClosesNPresence(prBody)] : []
2975
3066
  ];
2976
3067
  return { errors: results.flatMap((r) => r.errors) };
2977
3068
  }
@@ -3261,6 +3352,28 @@ function topLevelSectionText(body, headingName) {
3261
3352
  const next = /^##[ \t]/m.exec(afterHeading);
3262
3353
  return next ? afterHeading.slice(0, next.index) : afterHeading;
3263
3354
  }
3355
+ function partHasBacktickedPath(text) {
3356
+ let i = 0;
3357
+ while (i < text.length) {
3358
+ const start = text.indexOf("`", i);
3359
+ if (start === -1)
3360
+ return false;
3361
+ const end = text.indexOf("`", start + 1);
3362
+ if (end === -1)
3363
+ return false;
3364
+ if (text.slice(start + 1, end).includes("/"))
3365
+ return true;
3366
+ i = end + 1;
3367
+ }
3368
+ return false;
3369
+ }
3370
+ var PART_MIN_WORDS_OUTSIDE_BACKTICKS = 3;
3371
+ function stripPartBackticks(text) {
3372
+ return text.replace(/`[^`\n]*`/g, " ");
3373
+ }
3374
+ function partWordCount(text) {
3375
+ return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
3376
+ }
3264
3377
  function looksLikeFilePath(entry2) {
3265
3378
  const stripped = entry2.replace(/\/\*\*?$/, "");
3266
3379
  const lastSegment = stripped.split("/").pop() ?? stripped;
@@ -3301,6 +3414,18 @@ function globCoversPath(glob, path) {
3301
3414
  const p = path.replace(/\/+$/, "");
3302
3415
  return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
3303
3416
  }
3417
+ function checkSurfaceGlobsResolve(body, resolvesToFile) {
3418
+ const surface = parseIssueSurface(body);
3419
+ if (!surface.ok)
3420
+ return { status: "pass", errors: [] };
3421
+ const errors = [];
3422
+ for (const glob of surface.value.in) {
3423
+ if (!resolvesToFile(glob)) {
3424
+ 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.`);
3425
+ }
3426
+ }
3427
+ return { status: errors.length > 0 ? "fail" : "pass", errors };
3428
+ }
3304
3429
  function checkSurfaceScope(changedFiles, outGlobs) {
3305
3430
  if (outGlobs.length === 0)
3306
3431
  return { ok: true };
@@ -3312,9 +3437,77 @@ function checkSurfaceScope(changedFiles, outGlobs) {
3312
3437
  }
3313
3438
  return violations.length > 0 ? { ok: false, violations } : { ok: true };
3314
3439
  }
3440
+ var ISSUE_PART_LINE_RE = /^Part\s+(\d+)\s*\(([^)]*)\)\s*[-—–]\s*(.*)$/i;
3441
+ function parseIssueParts(body) {
3442
+ const section = topLevelSectionText(body, "Parts");
3443
+ if (section === null)
3444
+ return { ok: false, errors: ["no `## Parts` heading found in the body."] };
3445
+ const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
3446
+ const parts = [];
3447
+ const errors = [];
3448
+ for (const line of lines) {
3449
+ const m = ISSUE_PART_LINE_RE.exec(line);
3450
+ if (!m) {
3451
+ errors.push(`"${line}" is not a well-formed Parts line — expected \`Part <n> (<refs>) — <outcome>\`.`);
3452
+ continue;
3453
+ }
3454
+ const n = Number.parseInt(m[1], 10);
3455
+ const refs = m[2];
3456
+ const text = m[3].trim();
3457
+ if (text.length === 0) {
3458
+ errors.push(`Part ${n} has no outcome text after the dash — every Part states one observable outcome.`);
3459
+ continue;
3460
+ }
3461
+ if (partHasBacktickedPath(text) && partWordCount(stripPartBackticks(text)) < PART_MIN_WORDS_OUTSIDE_BACKTICKS) {
3462
+ errors.push(`Part ${n} is little more than a file path — a Part names an outcome and symbols, never a bare path.`);
3463
+ continue;
3464
+ }
3465
+ const objectiveIds = [...refs.matchAll(/O(\d+)/g)].map((r) => Number.parseInt(r[1], 10));
3466
+ parts.push({ n, objectiveIds, text });
3467
+ }
3468
+ if (parts.length === 0) {
3469
+ errors.push("the `## Parts` section has no well-formed `Part <n> (<refs>) — <outcome>` lines.");
3470
+ }
3471
+ if (errors.length > 0)
3472
+ return { ok: false, errors };
3473
+ return { ok: true, value: parts };
3474
+ }
3475
+ function checkPartsCiteDefinedObjectives(body) {
3476
+ const parts = parseIssueParts(body);
3477
+ const objectives = objectivesOf(body);
3478
+ if (!parts.ok || !objectives.ok)
3479
+ return { status: "pass", errors: [] };
3480
+ const definedIds = new Set(objectives.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
3481
+ const errors = [];
3482
+ for (const part of parts.value) {
3483
+ for (const objectiveId of part.objectiveIds) {
3484
+ if (!definedIds.has(objectiveId)) {
3485
+ errors.push(`issue-validation Parts: Part ${part.n} cites O${objectiveId}, which the Issue's own \`## Objectives\` section does not define.`);
3486
+ }
3487
+ }
3488
+ }
3489
+ return { status: errors.length > 0 ? "fail" : "pass", errors };
3490
+ }
3315
3491
  function isTaskIssueLabelSet(labels) {
3316
3492
  return hasLabel("tranche", labels);
3317
3493
  }
3494
+ function checkTrancheLabelPresence(_body, _labels) {
3495
+ return { status: "pass", errors: [] };
3496
+ }
3497
+ function checkMilestoneAttach(labels, currentMilestoneTitle, resolvedMilestoneTitle) {
3498
+ if (!isTaskIssueLabelSet(labels))
3499
+ return { status: "pass", errors: [] };
3500
+ if (resolvedMilestoneTitle === null)
3501
+ return { status: "pass", errors: [] };
3502
+ if (currentMilestoneTitle === resolvedMilestoneTitle)
3503
+ return { status: "pass", errors: [] };
3504
+ return {
3505
+ status: "fail",
3506
+ errors: [
3507
+ `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}".`
3508
+ ]
3509
+ };
3510
+ }
3318
3511
  var TYPE_LABEL_IDS = LABELS.filter((l) => l.category === "type").map((l) => l.id);
3319
3512
  function isControlCodePoint(codePoint) {
3320
3513
  return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
@@ -3379,6 +3572,19 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
3379
3572
  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;
3380
3573
  var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
3381
3574
 
3575
+ // ../../packages/aeg-core/src/task-branch-identity.ts
3576
+ var ISSUE_BRANCH_PATTERN = /^task\/issue-(\d+)$/;
3577
+ var TRANCHE_BRANCH_PATTERN = /^task\/([^/]+)\/([^/]+)$/;
3578
+ function parseTaskBranchIdentity(branch) {
3579
+ const issueMatch = ISSUE_BRANCH_PATTERN.exec(branch);
3580
+ if (issueMatch)
3581
+ return { kind: "issue", issueNumber: Number(issueMatch[1]) };
3582
+ const trancheMatch = TRANCHE_BRANCH_PATTERN.exec(branch);
3583
+ if (trancheMatch)
3584
+ return { kind: "tranche", tranche: trancheMatch[1], taskId: trancheMatch[2] };
3585
+ return null;
3586
+ }
3587
+
3382
3588
  // ../../packages/aeg-core/src/coherence-checks.ts
3383
3589
  var COHERENCE_ENFORCED_FROM = "2026-07-01";
3384
3590
  function isGrandfathered(isoDate) {
@@ -3399,6 +3605,7 @@ function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
3399
3605
  if (handClosed)
3400
3606
  continue;
3401
3607
  failures.push({
3608
+ code: "closed-without-merge",
3402
3609
  issue: e.task.issue,
3403
3610
  tranche: e.trancheSlug,
3404
3611
  task: e.task.id,
@@ -3423,6 +3630,7 @@ function checkA3(entries) {
3423
3630
  continue;
3424
3631
  if (e.facts.prState === "merged" && e.facts.issueState !== "closed") {
3425
3632
  failures.push({
3633
+ code: "auto-close-misfire",
3426
3634
  issue: e.task.issue,
3427
3635
  tranche: e.trancheSlug,
3428
3636
  task: e.task.id,
@@ -3447,6 +3655,7 @@ function checkT1(entries) {
3447
3655
  continue;
3448
3656
  if (e.facts === undefined) {
3449
3657
  failures.push({
3658
+ code: "phantom-issue-ref",
3450
3659
  issue: e.task.issue,
3451
3660
  tranche: e.trancheSlug,
3452
3661
  task: e.task.id,
@@ -3465,6 +3674,7 @@ function checkT2(openIssuesBySlug, topologyIssuesBySlug, ciTrancheSlug) {
3465
3674
  for (const num of openNums) {
3466
3675
  if (!topologySet.has(num)) {
3467
3676
  failures.push({
3677
+ code: "orphan-task",
3468
3678
  issue: num,
3469
3679
  tranche: slug,
3470
3680
  reason: `Issue #${num} is open and labeled ${trancheLabel(slug)} but does not appear in the topology file`
@@ -3499,6 +3709,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
3499
3709
  continue;
3500
3710
  if (forgeUnavailableSlugs?.has(e.trancheSlug)) {
3501
3711
  failures.push({
3712
+ code: "tbd-in-active-tranche",
3502
3713
  issue: null,
3503
3714
  tranche: e.trancheSlug,
3504
3715
  task: e.task.id,
@@ -3508,6 +3719,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
3508
3719
  continue;
3509
3720
  }
3510
3721
  failures.push({
3722
+ code: "tbd-in-active-tranche",
3511
3723
  issue: null,
3512
3724
  tranche: e.trancheSlug,
3513
3725
  task: e.task.id,
@@ -3540,6 +3752,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
3540
3752
  const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
3541
3753
  if (sameTask || sameIssue) {
3542
3754
  failures.push({
3755
+ code: "d1-self-dependency",
3543
3756
  issue: e.task.issue,
3544
3757
  tranche: e.trancheSlug,
3545
3758
  task: e.task.id,
@@ -3551,6 +3764,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
3551
3764
  const depClosed = depFacts?.issueState === "closed";
3552
3765
  if (!depClosed) {
3553
3766
  failures.push({
3767
+ code: "dispatched-on-unmet-deps",
3554
3768
  issue: e.task.issue,
3555
3769
  tranche: e.trancheSlug,
3556
3770
  task: e.task.id,
@@ -3576,11 +3790,13 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
3576
3790
  const errors = [
3577
3791
  ...checkIssueRationale(issue.body).errors,
3578
3792
  ...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
3579
- ...checkIssueObjectives(issue.body, issue.number).errors
3793
+ ...checkIssueObjectives(issue.body, issue.number).errors,
3794
+ ...checkPartsCiteDefinedObjectives(issue.body).errors
3580
3795
  ];
3581
3796
  if (errors.length === 0)
3582
3797
  continue;
3583
3798
  failures.push({
3799
+ code: "missing-rationale-field",
3584
3800
  issue: issue.number,
3585
3801
  tranche: slug,
3586
3802
  reason: `Issue #${issue.number} fails the rationale gate: ${errors.join(" | ")}`,
@@ -3609,6 +3825,7 @@ function checkL1(files, entriesBySlug) {
3609
3825
  const allClosed = withFacts.every((e) => e.facts?.issueState === "closed");
3610
3826
  if (allClosed) {
3611
3827
  failures.push({
3828
+ code: "archive-recommended",
3612
3829
  tranche: f.slug,
3613
3830
  reason: "Active tranche has no open task-Issues — consider archiving to completed/"
3614
3831
  });
@@ -3640,27 +3857,38 @@ function extractClosesReferences(prBody) {
3640
3857
  }
3641
3858
  return referenced;
3642
3859
  }
3643
- function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
3860
+ function checkClosesNTopology(branch, prBody, trancheFiles, taskIssueRefs) {
3644
3861
  const referenced = extractClosesReferences(prBody);
3645
3862
  if (taskIssueRefs) {
3646
3863
  for (const n of referenced) {
3647
- const ref = taskIssueRefs.get(n);
3648
- if (!ref)
3864
+ const ref2 = taskIssueRefs.get(n);
3865
+ if (!ref2)
3649
3866
  continue;
3650
- const expectedBranch = `task/${ref.trancheSlug}/${ref.taskId}`;
3867
+ const expectedBranch = `task/${ref2.trancheSlug}/${ref2.taskId}`;
3651
3868
  if (branch !== expectedBranch) {
3652
3869
  return {
3653
3870
  ok: false,
3654
- 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.`
3871
+ 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.`
3655
3872
  };
3656
3873
  }
3657
3874
  }
3658
3875
  }
3659
- const m = branch.match(/^task\/([^/]+)\/([^/]+)$/);
3660
- if (!m)
3876
+ const ref = parseTaskBranchIdentity(branch);
3877
+ if (!ref)
3661
3878
  return { ok: true };
3662
- const trancheSlug = m[1];
3663
- const taskId = m[2];
3879
+ if (ref.kind === "issue") {
3880
+ const expectedIssue2 = ref.issueNumber;
3881
+ if (!referenced.has(expectedIssue2)) {
3882
+ return {
3883
+ ok: false,
3884
+ expectedIssue: expectedIssue2,
3885
+ message: `closes-n: PR body does not contain \`Closes #${expectedIssue2}\` (required for branch "${branch}"). Add it to the PR body Summary section.`
3886
+ };
3887
+ }
3888
+ return { ok: true, expectedIssue: expectedIssue2 };
3889
+ }
3890
+ const trancheSlug = ref.tranche;
3891
+ const taskId = ref.taskId;
3664
3892
  const trancheFile = trancheFiles.find((f) => f.slug === trancheSlug);
3665
3893
  if (!trancheFile) {
3666
3894
  return {
@@ -3712,34 +3940,67 @@ function extractIssue(body) {
3712
3940
  const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
3713
3941
  return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
3714
3942
  }
3715
- // ../../packages/aeg-core/src/review-gate.ts
3716
- function isBoundToHead(extraction, headSha) {
3717
- if (!extraction.headSha)
3943
+ // ../../packages/aeg-core/src/review-input-manifest.ts
3944
+ import { createHash as createHash4 } from "node:crypto";
3945
+ function briefHash(brief) {
3946
+ return createHash4("sha256").update(`${brief}
3947
+ `).digest("hex");
3948
+ }
3949
+ function policyDigest(policy) {
3950
+ return createHash4("sha256").update(JSON.stringify({ codeReviewThreshold: policy.codeReviewThreshold, securityThreshold: policy.securityThreshold })).digest("hex");
3951
+ }
3952
+ function isBoundToHead(echoed, headSha) {
3953
+ if (!echoed.headSha)
3718
3954
  return false;
3719
- return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
3955
+ return headSha.toLowerCase().startsWith(echoed.headSha.toLowerCase());
3720
3956
  }
3721
- function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
3722
- if (patchIdOf === undefined || !extraction.headSha)
3957
+ function isBoundByPatchIdentity(echoed, headSha, patchIdOf) {
3958
+ if (patchIdOf === undefined || !echoed.headSha)
3723
3959
  return false;
3724
- const judged = patchIdOf(extraction.headSha);
3960
+ const judged = patchIdOf(echoed.headSha);
3725
3961
  const current = patchIdOf(headSha);
3726
3962
  if (judged === null || current === null)
3727
3963
  return false;
3728
3964
  return judged === current;
3729
3965
  }
3730
- function isBoundToPatch(extraction, headSha, patchIdOf) {
3731
- return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
3966
+ function isBoundToPatch(echoed, headSha, patchIdOf) {
3967
+ return isBoundToHead(echoed, headSha) || isBoundByPatchIdentity(echoed, headSha, patchIdOf);
3732
3968
  }
3733
- function isBoundToObjectives(extraction, currentVersion) {
3969
+ function isBoundToObjectives(echoed, currentVersion) {
3734
3970
  if (currentVersion === null)
3735
3971
  return true;
3736
- return extraction.objectivesVersion === currentVersion;
3972
+ return echoed.objectivesVersion === currentVersion;
3737
3973
  }
3738
- function isBoundToRulings(extraction, currentOrdinal) {
3739
- if (extraction.rulingOrdinal === null)
3974
+ function isBoundToRulings(echoed, currentOrdinal) {
3975
+ if (echoed.rulingOrdinal === null)
3740
3976
  return currentOrdinal === 0;
3741
- return extraction.rulingOrdinal === currentOrdinal;
3977
+ return echoed.rulingOrdinal === currentOrdinal;
3742
3978
  }
3979
+ function isBoundToBriefHash(echoed, currentHash) {
3980
+ if (currentHash === null)
3981
+ return true;
3982
+ return echoed.briefHash === currentHash;
3983
+ }
3984
+ function isBoundToPolicy(echoed, currentDigest) {
3985
+ return echoed.policyDigest === currentDigest;
3986
+ }
3987
+ function compareManifest(echoed, current, patchIdOf) {
3988
+ const head = isBoundToPatch(echoed, current.headSha, patchIdOf);
3989
+ const briefHashBound = isBoundToBriefHash(echoed, current.briefHash);
3990
+ const objectivesVersion2 = isBoundToObjectives(echoed, current.objectivesVersion);
3991
+ const rulingOrdinal = isBoundToRulings(echoed, current.rulingOrdinal);
3992
+ const policyDigestBound = isBoundToPolicy(echoed, current.policyDigest);
3993
+ return {
3994
+ bound: head && briefHashBound && objectivesVersion2 && rulingOrdinal && policyDigestBound,
3995
+ head,
3996
+ briefHash: briefHashBound,
3997
+ objectivesVersion: objectivesVersion2,
3998
+ rulingOrdinal,
3999
+ policyDigest: policyDigestBound
4000
+ };
4001
+ }
4002
+
4003
+ // ../../packages/aeg-core/src/review-gate.ts
3743
4004
  function checkReviewGate(input) {
3744
4005
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
3745
4006
  const waived = isWaiverLabelActorVerified({
@@ -3762,15 +4023,55 @@ function checkReviewGate(input) {
3762
4023
  const verifiedBodies = verified.map((c) => c.body);
3763
4024
  const codeReview = extractCodeReviewVerdict(verifiedBodies);
3764
4025
  const security = extractSecurityReviewVerdict(verifiedBodies);
3765
- const codeReviewClean = codeReview.value === "APPROVE";
3766
- const securityClean = security.value === "PASS";
3767
- const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
3768
- const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
3769
- const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
3770
- const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
3771
- const codeReviewRulingsBound = isBoundToRulings(codeReview, input.rulingOrdinal);
3772
- const securityRulingsBound = isBoundToRulings(security, input.rulingOrdinal);
3773
- if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && codeReviewRulingsBound && securityClean && securityBound && securityObjectivesBound && securityRulingsBound && mechanicalChecksClean) {
4026
+ const policy = input.policy ?? DEFAULT_REVIEW_POLICY;
4027
+ let codeReviewPolicyEvaluation;
4028
+ let securityPolicyEvaluation;
4029
+ try {
4030
+ codeReviewPolicyEvaluation = evaluateCodeReview(codeReview.findingSeverities, policy);
4031
+ securityPolicyEvaluation = evaluateSecurityReview(security.findingSeverities, policy);
4032
+ } catch (err) {
4033
+ return {
4034
+ verdict: "fail",
4035
+ reason: `a verdict comment carries a finding severity this repository's policy does not recognize: ${err instanceof Error ? err.message : String(err)}`,
4036
+ waived: false
4037
+ };
4038
+ }
4039
+ const codeReviewTextClean = codeReview.value === "APPROVE";
4040
+ const securityTextClean = security.value === "PASS";
4041
+ const codeReviewPolicyClean = codeReviewPolicyEvaluation.outcome === "clean";
4042
+ const securityPolicyClean = securityPolicyEvaluation.outcome === "clean";
4043
+ const codeReviewClean = codeReviewTextClean && codeReviewPolicyClean;
4044
+ const securityClean = securityTextClean && securityPolicyClean;
4045
+ const currentManifest = {
4046
+ headSha: input.headSha,
4047
+ briefHash: input.briefHash ?? null,
4048
+ objectivesVersion: input.objectivesVersion,
4049
+ rulingOrdinal: input.rulingOrdinal,
4050
+ policyDigest: policyDigest(policy)
4051
+ };
4052
+ const codeReviewEchoed = {
4053
+ headSha: codeReview.headSha,
4054
+ briefHash: codeReview.briefHash,
4055
+ objectivesVersion: codeReview.objectivesVersion,
4056
+ rulingOrdinal: codeReview.rulingOrdinal,
4057
+ policyDigest: codeReview.policyDigest
4058
+ };
4059
+ const securityEchoed = {
4060
+ headSha: security.headSha,
4061
+ briefHash: security.briefHash,
4062
+ objectivesVersion: security.objectivesVersion,
4063
+ rulingOrdinal: security.rulingOrdinal,
4064
+ policyDigest: security.policyDigest
4065
+ };
4066
+ const codeReviewBinding = compareManifest(codeReviewEchoed, currentManifest, input.patchIdOf);
4067
+ const securityBinding = compareManifest(securityEchoed, currentManifest, input.patchIdOf);
4068
+ const codeReviewBound = codeReviewBinding.head;
4069
+ const securityBound = securityBinding.head;
4070
+ const codeReviewObjectivesBound = codeReviewBinding.objectivesVersion;
4071
+ const securityObjectivesBound = securityBinding.objectivesVersion;
4072
+ const codeReviewRulingsBound = codeReviewBinding.rulingOrdinal;
4073
+ const securityRulingsBound = securityBinding.rulingOrdinal;
4074
+ if (codeReviewClean && codeReviewBinding.bound && securityClean && securityBinding.bound && mechanicalChecksClean) {
3774
4075
  return {
3775
4076
  verdict: "pass",
3776
4077
  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.`,
@@ -3778,23 +4079,35 @@ function checkReviewGate(input) {
3778
4079
  };
3779
4080
  }
3780
4081
  const problems = [];
3781
- if (!codeReviewClean) {
4082
+ if (!codeReviewTextClean) {
3782
4083
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
4084
+ } else if (!codeReviewPolicyClean) {
4085
+ 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`);
3783
4086
  } else if (!codeReviewBound) {
3784
4087
  problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3785
4088
  } else if (!codeReviewObjectivesBound) {
3786
4089
  problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3787
4090
  } else if (!codeReviewRulingsBound) {
3788
4091
  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`);
4092
+ } else if (!codeReviewBinding.briefHash) {
4093
+ 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"}`);
4094
+ } else if (!codeReviewBinding.policyDigest) {
4095
+ 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}`);
3789
4096
  }
3790
- if (!securityClean) {
4097
+ if (!securityTextClean) {
3791
4098
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
4099
+ } else if (!securityPolicyClean) {
4100
+ 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`);
3792
4101
  } else if (!securityBound) {
3793
4102
  problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
3794
4103
  } else if (!securityObjectivesBound) {
3795
4104
  problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
3796
4105
  } else if (!securityRulingsBound) {
3797
4106
  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`);
4107
+ } else if (!securityBinding.briefHash) {
4108
+ 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"}`);
4109
+ } else if (!securityBinding.policyDigest) {
4110
+ 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}`);
3798
4111
  }
3799
4112
  if (!mechanicalChecksClean) {
3800
4113
  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(", ")}`);
@@ -4486,42 +4799,45 @@ function checkDispatchReadiness(input) {
4486
4799
  const { trancheSlug, task } = input;
4487
4800
  const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
4488
4801
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
4489
- const blockers = [];
4802
+ const blockerDetails = [];
4803
+ const push = (blockerClass, message) => {
4804
+ blockerDetails.push({ class: blockerClass, message });
4805
+ };
4490
4806
  if (task.issue === null) {
4491
- blockers.push(`dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
4807
+ 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.`);
4492
4808
  } else if (input.issue === null) {
4493
- blockers.push(`dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
4809
+ push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
4494
4810
  }
4495
4811
  if (input.issue !== null && !input.issueRationalePass) {
4496
- 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.`);
4812
+ 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.`);
4497
4813
  }
4498
4814
  for (const dep of input.dependsOn) {
4499
4815
  if (isSelfDependency(dep, task, input.issue)) {
4500
4816
  const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
4501
- 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.`);
4817
+ 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.`);
4502
4818
  continue;
4503
4819
  }
4504
4820
  if (dep.resolved === false) {
4505
- 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.`);
4821
+ 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.`);
4506
4822
  continue;
4507
4823
  }
4508
4824
  if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
4509
4825
  const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
4510
- blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
4826
+ 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.`);
4511
4827
  }
4512
4828
  }
4513
4829
  for (const c of input.conflictsWith) {
4514
4830
  if (c.openOrInFlight) {
4515
4831
  const issueStr = c.issue !== null ? ` (#${c.issue})` : "";
4516
- blockers.push(`dispatch-gate conflicts-with: ${taskLabel} conflicts with ${c.id}${issueStr}, whose PR is open or in-flight — not dispatchable until it merges.`);
4832
+ 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.`);
4517
4833
  }
4518
4834
  }
4519
4835
  for (const proj of input.priorTrancheArchival) {
4520
4836
  if (proj.priorTrancheSlug !== null && !proj.archived) {
4521
- 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.`);
4837
+ 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.`);
4522
4838
  }
4523
4839
  }
4524
- return { ready: blockers.length === 0, blockers };
4840
+ return { ready: blockerDetails.length === 0, blockers: blockerDetails.map((b) => b.message), blockerDetails };
4525
4841
  }
4526
4842
  // ../../packages/aeg-core/src/single-plan-pr.ts
4527
4843
  function trancheSlugFromTopologyPath(path) {
@@ -4623,6 +4939,91 @@ function checkBranchTopology(input) {
4623
4939
  reason: `Branch \`${branch}\` matches topology row \`${taskId}\` in ${topoPath}.`
4624
4940
  };
4625
4941
  }
4942
+ // ../../packages/aeg-core/src/task-tools.ts
4943
+ import { z } from "zod";
4944
+ var TASK_TOOL_ERROR_KINDS = [
4945
+ "validation",
4946
+ "authority",
4947
+ "precondition",
4948
+ "capability",
4949
+ "infrastructure",
4950
+ "cancellation",
4951
+ "timeout",
4952
+ "uncertain_effect"
4953
+ ];
4954
+ var TaskToolErrorSchema = z.object({
4955
+ kind: z.enum(TASK_TOOL_ERROR_KINDS),
4956
+ message: z.string().min(1),
4957
+ detail: z.string().optional()
4958
+ });
4959
+ var TaskToolRefSchema = z.union([
4960
+ z.object({ tranche: z.string().min(1), id: z.string().min(1) }),
4961
+ z.object({ issue: z.number().int().positive() })
4962
+ ]);
4963
+ var MAX_PAGE_LIMIT = 100;
4964
+ var PageRequestSchema = z.object({
4965
+ cursor: z.string().optional(),
4966
+ limit: z.number().int().positive().max(MAX_PAGE_LIMIT).optional()
4967
+ });
4968
+ var FreshnessSchema = z.enum(["fresh", "stale", "unknown"]);
4969
+ var ObservedSchema = z.object({
4970
+ observedAt: z.string(),
4971
+ freshness: FreshnessSchema
4972
+ });
4973
+ var TaskStatusInputSchema = z.object({
4974
+ task: TaskToolRefSchema.optional()
4975
+ }).merge(PageRequestSchema);
4976
+ var TaskStatusItemSchema = z.object({
4977
+ task: TaskToolRefSchema,
4978
+ issue: z.number().int().positive(),
4979
+ pr: z.number().int().positive().nullable(),
4980
+ state: z.string()
4981
+ }).merge(ObservedSchema);
4982
+ var TaskStatusResultSchema = z.object({
4983
+ items: z.array(TaskStatusItemSchema),
4984
+ nextCursor: z.string().nullable()
4985
+ });
4986
+ var RequestedAuthoritySchema = z.enum(["planner", "principal", "operator", "self"]);
4987
+ var EscalationInputsSchema = z.object({
4988
+ task: z.number().int().positive(),
4989
+ round: z.number().int().nonnegative(),
4990
+ head: z.string(),
4991
+ branch: z.string(),
4992
+ prNumber: z.number().int().positive()
4993
+ });
4994
+ var EscalationEvidenceSchema = z.object({
4995
+ round: z.number().int().nonnegative(),
4996
+ reviewer: z.string().nullable(),
4997
+ security: z.string().nullable()
4998
+ });
4999
+ var TaskEscalationReadInputSchema = z.object({
5000
+ task: TaskToolRefSchema
5001
+ }).merge(PageRequestSchema);
5002
+ var TaskEscalationPacketSchema = z.object({
5003
+ reason: z.string(),
5004
+ detail: z.string().nullable(),
5005
+ inputs: EscalationInputsSchema.nullable(),
5006
+ evidence: EscalationEvidenceSchema.nullable(),
5007
+ attemptedRecovery: z.string(),
5008
+ requestedAuthority: RequestedAuthoritySchema,
5009
+ permittedNextActions: z.array(z.string())
5010
+ }).merge(ObservedSchema);
5011
+ var TaskEscalationReadResultSchema = z.object({
5012
+ items: z.array(TaskEscalationPacketSchema),
5013
+ nextCursor: z.string().nullable()
5014
+ }).merge(ObservedSchema);
5015
+ var TaskStartInputSchema = z.object({
5016
+ tranche: z.string().min(1),
5017
+ id: z.string().min(1)
5018
+ });
5019
+ var TaskResumeInputSchema = z.object({
5020
+ task: TaskToolRefSchema
5021
+ });
5022
+ var TaskCancelInputSchema = z.object({
5023
+ task: TaskToolRefSchema,
5024
+ reason: z.string().min(1)
5025
+ });
5026
+ var NoResultSchema = z.never();
4626
5027
  // ../../packages/aeg-core/src/first-push-dispatch-gate.ts
4627
5028
  function parseTaskBranch(branch) {
4628
5029
  const m = /^task\/([^/]+)\/([^/]+)$/.exec(branch);
@@ -4706,7 +5107,6 @@ function decideIssueAssignment(input) {
4706
5107
  };
4707
5108
  }
4708
5109
  // ../../packages/aeg-core/src/test-plan-gate.ts
4709
- var TASK_BRANCH_PATTERN2 = /^task\/[^/]+\/[^/]+$/;
4710
5110
  function evaluateTestPlanGate(body, branch) {
4711
5111
  if (!body) {
4712
5112
  return {
@@ -4719,7 +5119,7 @@ function evaluateTestPlanGate(body, branch) {
4719
5119
  }
4720
5120
  const located = locateTestPlanSection(body);
4721
5121
  if (!located.found) {
4722
- if (TASK_BRANCH_PATTERN2.test(branch)) {
5122
+ if (parseTaskBranchIdentity(branch) !== null) {
4723
5123
  return {
4724
5124
  verdict: "fail",
4725
5125
  messages: [
@@ -4847,7 +5247,7 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
4847
5247
  return findings;
4848
5248
  }
4849
5249
  // ../../packages/aeg-core/src/log/schema.ts
4850
- import { z } from "zod";
5250
+ import { z as z2 } from "zod";
4851
5251
  var ROLE_VALUES = [
4852
5252
  "planner",
4853
5253
  "developer",
@@ -4857,136 +5257,172 @@ var ROLE_VALUES = [
4857
5257
  "archivist",
4858
5258
  "architect"
4859
5259
  ];
4860
- var RoleSchema = z.enum(ROLE_VALUES);
5260
+ var RoleSchema = z2.enum(ROLE_VALUES);
4861
5261
  var HOST_VALUES = ["hook", "ci", "cli", "loop"];
4862
- var HostSchema = z.enum(HOST_VALUES);
5262
+ var HostSchema = z2.enum(HOST_VALUES);
4863
5263
  var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
4864
- var HeaderMetaSchema = z.object({
4865
- schema: z.literal(1),
4866
- ts: z.string(),
4867
- run_id: z.string().regex(RUN_ID_PATTERN),
4868
- seq: z.number().int().nonnegative(),
4869
- repo: z.string().nullable(),
4870
- vinaya: z.string(),
4871
- doctrine: z.string(),
5264
+ var headerMetaCore = {
5265
+ ts: z2.string(),
5266
+ run_id: z2.string().regex(RUN_ID_PATTERN),
5267
+ seq: z2.number().int().nonnegative(),
5268
+ repo: z2.string().nullable(),
5269
+ vinaya: z2.string(),
5270
+ doctrine: z2.string(),
4872
5271
  host: HostSchema,
4873
- machine: z.string()
5272
+ machine: z2.string()
5273
+ };
5274
+ var HeaderMetaV1Schema = z2.object({
5275
+ schema: z2.literal(1),
5276
+ ...headerMetaCore
5277
+ }).strict();
5278
+ var LineageSchema = z2.object({
5279
+ run: z2.string().nullable(),
5280
+ attempt: z2.number().int().nullable(),
5281
+ parent: z2.string().nullable()
4874
5282
  }).strict();
4875
- var SubjectSchema = z.object({
4876
- issue: z.number().int().nullable(),
4877
- pr: z.number().int().optional(),
4878
- sha: z.string().optional(),
4879
- role: z.union([RoleSchema, z.literal("unattributed")]),
4880
- round: z.number().int().optional(),
4881
- objectives_version: z.string().optional()
5283
+ var InputVersionsSchema = z2.object({
5284
+ objectives_version: z2.string().nullable(),
5285
+ brief_hash: z2.string().nullable(),
5286
+ ruling_ordinal: z2.number().int().nullable(),
5287
+ policy_digest: z2.string().nullable()
4882
5288
  }).strict();
4883
- var HeaderSchema = z.object({
5289
+ var ProvenanceSchema = z2.enum(["parent_attributed", "env_correlated", "self_reported", "unavailable"]);
5290
+ var HeaderMetaV2Schema = z2.object({
5291
+ schema: z2.literal(2),
5292
+ ...headerMetaCore,
5293
+ event_id: z2.string().min(1),
5294
+ process_id: z2.string().min(1),
5295
+ actor_id: z2.string().nullable(),
5296
+ lineage: LineageSchema,
5297
+ input_versions: InputVersionsSchema,
5298
+ provenance: ProvenanceSchema
5299
+ }).strict();
5300
+ var HeaderMetaSchema = z2.discriminatedUnion("schema", [HeaderMetaV1Schema, HeaderMetaV2Schema]);
5301
+ var SubjectSchema = z2.object({
5302
+ issue: z2.number().int().nullable(),
5303
+ pr: z2.number().int().optional(),
5304
+ sha: z2.string().optional(),
5305
+ role: z2.union([RoleSchema, z2.literal("unattributed")]),
5306
+ round: z2.number().int().optional(),
5307
+ objectives_version: z2.string().optional()
5308
+ }).strict();
5309
+ var HeaderSchema = z2.object({
4884
5310
  meta: HeaderMetaSchema,
4885
5311
  subject: SubjectSchema
4886
5312
  }).strict();
4887
5313
  var envelopeTail = {
4888
- duration_ms: z.number().nonnegative().optional(),
4889
- payload: z.object({}).strict()
5314
+ duration_ms: z2.number().nonnegative().optional(),
5315
+ payload: z2.object({}).strict()
4890
5316
  };
4891
- var DispatchOutcomeSchema = z.discriminatedUnion("type", [
4892
- z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
4893
- z.object({
4894
- type: z.literal("round_pushed"),
4895
- pr: z.number().int(),
4896
- head: z.string(),
4897
- comment_id: z.number().int()
5317
+ var ReviewFindingSchema = z2.object({
5318
+ id: z2.string(),
5319
+ severity: z2.string(),
5320
+ state: z2.string().optional(),
5321
+ severity_scale: z2.string().optional(),
5322
+ policy_treatment: z2.enum(["blocking", "non_blocking", "unavailable"]).optional(),
5323
+ confidence: z2.number().min(0).max(1).optional(),
5324
+ confidence_scale: z2.string().optional(),
5325
+ confidence_source: z2.string().optional()
5326
+ }).strict();
5327
+ var DispatchOutcomeSchema = z2.discriminatedUnion("type", [
5328
+ z2.object({ type: z2.literal("pr_opened"), pr: z2.number().int(), head: z2.string() }).strict(),
5329
+ z2.object({
5330
+ type: z2.literal("round_pushed"),
5331
+ pr: z2.number().int(),
5332
+ head: z2.string(),
5333
+ comment_id: z2.number().int()
4898
5334
  }).strict(),
4899
- z.object({
4900
- type: z.literal("verdict"),
4901
- verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
4902
- head: z.string(),
4903
- comment_id: z.number().int(),
4904
- objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
4905
- findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
5335
+ z2.object({
5336
+ type: z2.literal("verdict"),
5337
+ verdict: z2.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
5338
+ head: z2.string(),
5339
+ comment_id: z2.number().int(),
5340
+ objectives: z2.array(z2.object({ id: z2.string(), met: z2.boolean() }).strict()),
5341
+ findings: z2.array(ReviewFindingSchema)
4906
5342
  }).strict(),
4907
- z.object({
4908
- type: z.literal("escalation"),
4909
- class: z.enum(["authority", "strategy", "product"]),
4910
- comment_id: z.number().int()
5343
+ z2.object({
5344
+ type: z2.literal("escalation"),
5345
+ class: z2.enum(["authority", "strategy", "product"]),
5346
+ comment_id: z2.number().int()
4911
5347
  }).strict(),
4912
- z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
4913
- z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
4914
- z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
5348
+ z2.object({ type: z2.literal("brief"), comment_id: z2.number().int(), hash: z2.string() }).strict(),
5349
+ z2.object({ type: z2.literal("plan"), issues: z2.array(z2.number().int()) }).strict(),
5350
+ z2.object({ type: z2.literal("archive"), provenance_comment_id: z2.number().int() }).strict()
4915
5351
  ]);
4916
5352
  var dispatchShared = {
4917
5353
  meta: HeaderMetaSchema,
4918
5354
  subject: SubjectSchema,
4919
- kind: z.literal("dispatch"),
5355
+ kind: z2.literal("dispatch"),
4920
5356
  ...envelopeTail,
4921
5357
  target_role: RoleSchema,
4922
- model: z.string(),
4923
- round: z.number().int().optional(),
4924
- effect_id: z.string()
5358
+ model: z2.string(),
5359
+ round: z2.number().int().optional(),
5360
+ effect_id: z2.string()
4925
5361
  };
4926
- var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
4927
- var DispatchEventSchema = z.discriminatedUnion("event", [
4928
- z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
4929
- z.object({
5362
+ var dispatchUsageField = z2.object({ input: z2.number().nonnegative(), output: z2.number().nonnegative() }).strict().nullable();
5363
+ var DispatchEventSchema = z2.discriminatedUnion("event", [
5364
+ z2.object({ ...dispatchShared, event: z2.literal("dispatched"), prompt_hash: z2.string() }).strict(),
5365
+ z2.object({
4930
5366
  ...dispatchShared,
4931
- event: z.literal("outcome_received"),
5367
+ event: z2.literal("outcome_received"),
4932
5368
  outcome: DispatchOutcomeSchema,
4933
5369
  usage: dispatchUsageField
4934
5370
  }).strict(),
4935
- z.object({
5371
+ z2.object({
4936
5372
  ...dispatchShared,
4937
- event: z.literal("dispatch_failed"),
4938
- reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
5373
+ event: z2.literal("dispatch_failed"),
5374
+ reason: z2.enum(["timeout", "crash", "refused", "unattributed_write"]),
4939
5375
  usage: dispatchUsageField
4940
5376
  }).strict()
4941
5377
  ]);
4942
5378
  var loopShared = {
4943
5379
  meta: HeaderMetaSchema,
4944
5380
  subject: SubjectSchema,
4945
- kind: z.literal("dev_review_loop"),
5381
+ kind: z2.literal("dev_review_loop"),
4946
5382
  ...envelopeTail,
4947
- loop_id: z.string()
5383
+ loop_id: z2.string()
4948
5384
  };
4949
- var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4950
- z.object({
5385
+ var DevReviewLoopEventSchema = z2.discriminatedUnion("event", [
5386
+ z2.object({
4951
5387
  ...loopShared,
4952
- event: z.literal("loop_started"),
4953
- task: z.number().int(),
4954
- policy: z.object({
4955
- max_rounds: z.number().int().nonnegative(),
4956
- reviewers: z.array(RoleSchema),
4957
- models: z.record(RoleSchema, z.string())
5388
+ event: z2.literal("loop_started"),
5389
+ task: z2.number().int(),
5390
+ policy: z2.object({
5391
+ max_rounds: z2.number().int().nonnegative(),
5392
+ reviewers: z2.array(RoleSchema),
5393
+ models: z2.record(RoleSchema, z2.string())
4958
5394
  }).strict()
4959
5395
  }).strict(),
4960
- z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
4961
- z.object({
5396
+ z2.object({ ...loopShared, event: z2.literal("round_started"), round: z2.number().int(), base_head: z2.string() }).strict(),
5397
+ z2.object({
4962
5398
  ...loopShared,
4963
- event: z.literal("gate_result_read"),
4964
- round: z.number().int(),
4965
- head: z.string(),
4966
- green: z.boolean()
5399
+ event: z2.literal("gate_result_read"),
5400
+ round: z2.number().int(),
5401
+ head: z2.string(),
5402
+ green: z2.boolean()
4967
5403
  }).strict(),
4968
- z.object({
5404
+ z2.object({
4969
5405
  ...loopShared,
4970
- event: z.literal("verdicts_read"),
4971
- round: z.number().int(),
4972
- head: z.string(),
4973
- all_approve: z.boolean(),
4974
- blockers: z.number().int().nonnegative()
5406
+ event: z2.literal("verdicts_read"),
5407
+ round: z2.number().int(),
5408
+ head: z2.string(),
5409
+ all_approve: z2.boolean(),
5410
+ blockers: z2.number().int().nonnegative()
4975
5411
  }).strict(),
4976
- z.object({
5412
+ z2.object({
4977
5413
  ...loopShared,
4978
- event: z.literal("findings_compared"),
4979
- round: z.number().int(),
4980
- open: z.array(z.string()),
4981
- resolved: z.array(z.string()),
4982
- new: z.array(z.string()),
4983
- recurring: z.array(z.string())
5414
+ event: z2.literal("findings_compared"),
5415
+ round: z2.number().int(),
5416
+ open: z2.array(z2.string()),
5417
+ resolved: z2.array(z2.string()),
5418
+ new: z2.array(z2.string()),
5419
+ recurring: z2.array(z2.string())
4984
5420
  }).strict(),
4985
- z.object({
5421
+ z2.object({
4986
5422
  ...loopShared,
4987
- event: z.literal("stop_condition_met"),
4988
- round: z.number().int(),
4989
- condition: z.enum([
5423
+ event: z2.literal("stop_condition_met"),
5424
+ round: z2.number().int(),
5425
+ condition: z2.enum([
4990
5426
  "green",
4991
5427
  "max_rounds",
4992
5428
  "no_progress",
@@ -4996,42 +5432,49 @@ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
4996
5432
  "reappearance"
4997
5433
  ])
4998
5434
  }).strict(),
4999
- z.object({
5435
+ z2.object({
5000
5436
  ...loopShared,
5001
- event: z.literal("paused"),
5002
- round: z.number().int(),
5003
- reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
5437
+ event: z2.literal("paused"),
5438
+ round: z2.number().int(),
5439
+ reason: z2.enum(["escalation", "principal_item", "refreeze_needed"])
5004
5440
  }).strict(),
5005
- z.object({
5441
+ z2.object({
5006
5442
  ...loopShared,
5007
- event: z.literal("resumed"),
5008
- round: z.number().int(),
5009
- by: z.literal("principal")
5443
+ event: z2.literal("resumed"),
5444
+ round: z2.number().int(),
5445
+ by: z2.literal("principal")
5010
5446
  }).strict(),
5011
- z.object({
5447
+ z2.object({
5012
5448
  ...loopShared,
5013
- event: z.literal("round_ended"),
5014
- round: z.number().int(),
5015
- base_head: z.string(),
5016
- head: z.string(),
5017
- files_changed: z.number().int().nonnegative(),
5018
- insertions: z.number().int().nonnegative(),
5019
- deletions: z.number().int().nonnegative(),
5020
- wall_ms: z.number().nonnegative(),
5021
- outcome: z.enum(["green", "changes_requested", "escalated"])
5449
+ event: z2.literal("unpushed_work_resume"),
5450
+ round: z2.number().int(),
5451
+ branch: z2.string(),
5452
+ detail: z2.string()
5022
5453
  }).strict(),
5023
- z.object({
5454
+ z2.object({
5024
5455
  ...loopShared,
5025
- event: z.literal("journal_finalized"),
5026
- rounds: z.number().int().nonnegative(),
5027
- total_wall_ms: z.number().nonnegative(),
5028
- time_to_green_ms: z.number().nonnegative().nullable(),
5029
- files_changed_total: z.number().int().nonnegative(),
5030
- final_head: z.string(),
5031
- result: z.enum(["merged_ready", "stopped"])
5456
+ event: z2.literal("round_ended"),
5457
+ round: z2.number().int(),
5458
+ base_head: z2.string(),
5459
+ head: z2.string(),
5460
+ files_changed: z2.number().int().nonnegative(),
5461
+ insertions: z2.number().int().nonnegative(),
5462
+ deletions: z2.number().int().nonnegative(),
5463
+ wall_ms: z2.number().nonnegative(),
5464
+ outcome: z2.enum(["green", "changes_requested", "escalated"])
5465
+ }).strict(),
5466
+ z2.object({
5467
+ ...loopShared,
5468
+ event: z2.literal("journal_finalized"),
5469
+ rounds: z2.number().int().nonnegative(),
5470
+ total_wall_ms: z2.number().nonnegative(),
5471
+ time_to_green_ms: z2.number().nonnegative().nullable(),
5472
+ files_changed_total: z2.number().int().nonnegative(),
5473
+ final_head: z2.string(),
5474
+ result: z2.enum(["merged_ready", "stopped"])
5032
5475
  }).strict()
5033
5476
  ]);
5034
- var ForgeOpSchema = z.enum([
5477
+ var ForgeOpSchema = z2.enum([
5035
5478
  "pr.create",
5036
5479
  "pr.comment",
5037
5480
  "pr.body.replace",
@@ -5045,50 +5488,238 @@ var ForgeOpSchema = z.enum([
5045
5488
  "label.add",
5046
5489
  "label.remove"
5047
5490
  ]);
5048
- var ForgeWriteTargetSchema = z.object({
5049
- issue: z.number().int().optional(),
5050
- pr: z.number().int().optional()
5491
+ var ForgeWriteTargetSchema = z2.object({
5492
+ issue: z2.number().int().optional(),
5493
+ pr: z2.number().int().optional()
5051
5494
  }).strict();
5052
5495
  var forgeWriteShared = {
5053
5496
  meta: HeaderMetaSchema,
5054
5497
  subject: SubjectSchema,
5055
- kind: z.literal("forge_write"),
5498
+ kind: z2.literal("forge_write"),
5056
5499
  ...envelopeTail,
5057
5500
  op: ForgeOpSchema,
5058
5501
  target: ForgeWriteTargetSchema
5059
5502
  };
5060
- var ForgeWriteEventSchema = z.discriminatedUnion("event", [
5061
- z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
5062
- z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
5063
- z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
5503
+ var ForgeWriteEventSchema = z2.discriminatedUnion("event", [
5504
+ z2.object({ ...forgeWriteShared, event: z2.literal("validated") }).strict(),
5505
+ z2.object({ ...forgeWriteShared, event: z2.literal("refused"), reason: z2.string() }).strict(),
5506
+ z2.object({ ...forgeWriteShared, event: z2.literal("written"), comment_ids: z2.array(z2.string()) }).strict()
5507
+ ]);
5508
+ var GateOutcomeSchema = z2.enum([
5509
+ "pass",
5510
+ "fail",
5511
+ "wait",
5512
+ "skip",
5513
+ "invalid_input",
5514
+ "unavailable_dependency",
5515
+ "timeout",
5516
+ "cancelled"
5517
+ ]);
5518
+ var gateShared = {
5519
+ meta: HeaderMetaSchema,
5520
+ subject: SubjectSchema,
5521
+ kind: z2.literal("gate"),
5522
+ ...envelopeTail,
5523
+ check: z2.string(),
5524
+ check_version: z2.string().nullable(),
5525
+ policy_version: z2.string().nullable(),
5526
+ input_fingerprint: z2.string().nullable()
5527
+ };
5528
+ var GateEventSchema = z2.discriminatedUnion("event", [
5529
+ z2.object({
5530
+ ...gateShared,
5531
+ event: z2.literal("checked"),
5532
+ outcome: GateOutcomeSchema,
5533
+ reason: z2.string().optional()
5534
+ }).strict()
5535
+ ]);
5536
+ var OperationResultSchema = z2.enum(["ok", "error", "refused", "timeout", "cancelled", "unavailable"]);
5537
+ var operationShared = {
5538
+ meta: HeaderMetaSchema,
5539
+ subject: SubjectSchema,
5540
+ kind: z2.literal("operation"),
5541
+ ...envelopeTail,
5542
+ operation: z2.string(),
5543
+ target: z2.string().nullable()
5544
+ };
5545
+ var OperationEventSchema = z2.discriminatedUnion("event", [
5546
+ z2.object({
5547
+ ...operationShared,
5548
+ event: z2.literal("completed"),
5549
+ result: OperationResultSchema,
5550
+ error_class: z2.string().nullable()
5551
+ }).strict()
5552
+ ]);
5553
+ var UsageUnitsSchema = z2.object({
5554
+ input: z2.number().nonnegative().nullable(),
5555
+ output: z2.number().nonnegative().nullable(),
5556
+ cache: z2.number().nonnegative().nullable()
5557
+ }).strict();
5558
+ var usageShared = {
5559
+ meta: HeaderMetaSchema,
5560
+ subject: SubjectSchema,
5561
+ kind: z2.literal("usage"),
5562
+ ...envelopeTail,
5563
+ model: z2.string().nullable(),
5564
+ source: z2.string(),
5565
+ semantics: z2.enum(["cumulative", "delta"])
5566
+ };
5567
+ var UsageEventSchema = z2.discriminatedUnion("event", [
5568
+ z2.object({
5569
+ ...usageShared,
5570
+ event: z2.literal("observed"),
5571
+ units: UsageUnitsSchema,
5572
+ unknown_reason: z2.string().nullable()
5573
+ }).strict()
5574
+ ]);
5575
+ var RoleAttemptOutcomeSchema = z2.enum([
5576
+ "completed",
5577
+ "incomplete",
5578
+ "infrastructure_failed",
5579
+ "cancelled",
5580
+ "timed_out",
5581
+ "capability_refused"
5582
+ ]);
5583
+ var roleAttemptShared = {
5584
+ meta: HeaderMetaSchema,
5585
+ subject: SubjectSchema,
5586
+ kind: z2.literal("role_attempt"),
5587
+ ...envelopeTail,
5588
+ actor: z2.string().nullable(),
5589
+ attempt: z2.number().int().nullable()
5590
+ };
5591
+ var RoleAttemptEventSchema = z2.discriminatedUnion("event", [
5592
+ z2.object({
5593
+ ...roleAttemptShared,
5594
+ event: z2.literal("attempted"),
5595
+ outcome: RoleAttemptOutcomeSchema,
5596
+ usage: dispatchUsageField
5597
+ }).strict()
5598
+ ]);
5599
+ var handoffShared = {
5600
+ meta: HeaderMetaSchema,
5601
+ subject: SubjectSchema,
5602
+ kind: z2.literal("handoff"),
5603
+ ...envelopeTail,
5604
+ class: z2.enum(["authority", "strategy", "product"]),
5605
+ reason: z2.string()
5606
+ };
5607
+ var HandoffEventSchema = z2.discriminatedUnion("event", [
5608
+ z2.object({
5609
+ ...handoffShared,
5610
+ event: z2.literal("raised"),
5611
+ requested_decision: z2.string().nullable()
5612
+ }).strict(),
5613
+ z2.object({
5614
+ ...handoffShared,
5615
+ event: z2.literal("resolved"),
5616
+ resolution: z2.string().nullable(),
5617
+ resolved_by: z2.string().nullable()
5618
+ }).strict()
5064
5619
  ]);
5065
- var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
5620
+ var EffectTargetSchema = z2.object({
5621
+ kind: z2.string(),
5622
+ ref: z2.string()
5623
+ }).strict();
5624
+ var effectShared = {
5625
+ meta: HeaderMetaSchema,
5626
+ subject: SubjectSchema,
5627
+ kind: z2.literal("effect"),
5628
+ ...envelopeTail,
5629
+ effect_id: z2.string(),
5630
+ target: EffectTargetSchema
5631
+ };
5632
+ var EffectEventSchema = z2.discriminatedUnion("event", [
5633
+ z2.object({ ...effectShared, event: z2.literal("attempted") }).strict(),
5634
+ z2.object({ ...effectShared, event: z2.literal("observed"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict(),
5635
+ z2.object({ ...effectShared, event: z2.literal("verified"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict()
5636
+ ]);
5637
+ var LogEventSchema = z2.union([
5638
+ DispatchEventSchema,
5639
+ DevReviewLoopEventSchema,
5640
+ ForgeWriteEventSchema,
5641
+ GateEventSchema,
5642
+ OperationEventSchema,
5643
+ UsageEventSchema,
5644
+ RoleAttemptEventSchema,
5645
+ HandoffEventSchema,
5646
+ EffectEventSchema
5647
+ ]);
5648
+ // ../../packages/aeg-core/src/dev-review-loop/journal-reconstruction.ts
5649
+ var STOPPED_CONDITIONS = new Set(["confidence", "reappearance", "no_progress", "max_rounds"]);
5650
+ // ../../packages/aeg-core/src/control-store/records.ts
5651
+ import { z as z3 } from "zod";
5652
+ var isoTimestamp = z3.string().min(1);
5653
+ var taskId = z3.number().int().positive();
5654
+ var epochNumber = z3.number().int().nonnegative();
5655
+ var RunRecordSchema = z3.object({
5656
+ version: z3.literal(1),
5657
+ kind: z3.literal("run"),
5658
+ task: taskId,
5659
+ runId: z3.string().min(1),
5660
+ pid: z3.number().int().positive(),
5661
+ host: z3.string().min(1),
5662
+ startedAt: isoTimestamp
5663
+ }).strict();
5664
+ var InputRecordSchema = z3.object({
5665
+ version: z3.literal(1),
5666
+ kind: z3.literal("input"),
5667
+ task: taskId,
5668
+ runId: z3.string().min(1),
5669
+ source: z3.union([z3.literal("fresh"), z3.literal("resume")]),
5670
+ pr: z3.number().int().positive().nullable(),
5671
+ round: z3.number().int().nonnegative(),
5672
+ recordedAt: isoTimestamp
5673
+ }).strict();
5674
+ var OwnershipRecordSchema = z3.object({
5675
+ version: z3.literal(1),
5676
+ kind: z3.literal("ownership"),
5677
+ task: taskId,
5678
+ epoch: epochNumber,
5679
+ ownerId: z3.string().min(1),
5680
+ pid: z3.number().int().positive(),
5681
+ host: z3.string().min(1),
5682
+ acquiredAt: isoTimestamp
5683
+ }).strict();
5684
+ var TransitionRecordSchema = z3.object({
5685
+ version: z3.literal(1),
5686
+ kind: z3.literal("transition"),
5687
+ task: taskId,
5688
+ epoch: epochNumber,
5689
+ seq: z3.number().int().nonnegative(),
5690
+ from: z3.string().min(1),
5691
+ to: z3.string().min(1),
5692
+ detail: z3.string().optional(),
5693
+ at: isoTimestamp
5694
+ }).strict();
5066
5695
  // src/lib/config.ts
5067
5696
  import { execFileSync as execFileSync2 } from "node:child_process";
5068
5697
  import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
5069
5698
  import { homedir } from "node:os";
5070
5699
  import { dirname, join, resolve } from "node:path";
5071
- import { z as z2 } from "zod";
5700
+ import { z as z4 } from "zod";
5072
5701
 
5073
5702
  // src/lib/agent-vendors.ts
5074
5703
  var AGENT_VENDORS = ["skills", "claude", "gemini"];
5075
5704
 
5076
5705
  // src/lib/config.ts
5077
- var EnvEntrySchema = z2.union([
5078
- z2.literal(true),
5079
- z2.object({ optional: z2.literal(true) }),
5080
- z2.object({ anyOf: z2.array(z2.string()).min(2) }),
5081
- z2.string()
5706
+ var EnvEntrySchema = z4.union([
5707
+ z4.literal(true),
5708
+ z4.object({ optional: z4.literal(true) }),
5709
+ z4.object({ anyOf: z4.array(z4.string()).min(2) }),
5710
+ z4.string()
5082
5711
  ]);
5083
- var CheckEntrySchema = z2.object({
5084
- run: z2.string(),
5085
- scope: z2.enum(["diff", "full"]),
5086
- include: z2.array(z2.string()).optional(),
5087
- args: z2.array(z2.string()).optional(),
5088
- timeoutMs: z2.number().optional(),
5089
- env: z2.record(z2.string(), EnvEntrySchema).optional(),
5090
- requiresOpenPr: z2.boolean().optional(),
5091
- ownWorkflow: z2.boolean().optional()
5712
+ var CheckEntrySchema = z4.object({
5713
+ run: z4.string(),
5714
+ scope: z4.enum(["diff", "full"]),
5715
+ include: z4.array(z4.string()).optional(),
5716
+ args: z4.array(z4.string()).optional(),
5717
+ timeoutMs: z4.number().optional(),
5718
+ env: z4.record(z4.string(), EnvEntrySchema).optional(),
5719
+ requiresOpenPr: z4.boolean().optional(),
5720
+ ownWorkflow: z4.boolean().optional(),
5721
+ principalOwed: z4.literal(true).optional(),
5722
+ validates: z4.enum(["body", "issue"]).optional()
5092
5723
  }).superRefine((entry2, ctx) => {
5093
5724
  if (!entry2.env)
5094
5725
  return;
@@ -5098,25 +5729,26 @@ var CheckEntrySchema = z2.object({
5098
5729
  const members = value.anyOf;
5099
5730
  if (new Set(members).size !== members.length) {
5100
5731
  ctx.addIssue({
5101
- code: z2.ZodIssueCode.custom,
5732
+ code: z4.ZodIssueCode.custom,
5102
5733
  message: `env.${key}.anyOf has duplicate members`,
5103
5734
  path: ["env", key, "anyOf"]
5104
5735
  });
5105
5736
  }
5106
5737
  if (!members.includes(key)) {
5107
5738
  ctx.addIssue({
5108
- code: z2.ZodIssueCode.custom,
5739
+ code: z4.ZodIssueCode.custom,
5109
5740
  message: `env.${key}.anyOf must include "${key}" itself as a member`,
5110
5741
  path: ["env", key, "anyOf"]
5111
5742
  });
5112
5743
  }
5113
5744
  }
5114
5745
  });
5115
- var RoleEntrySchema = z2.object({
5116
- contract: z2.string().refine((p) => p.includes("/"), {
5746
+ var RoleEntrySchema = z4.object({
5747
+ contract: z4.string().refine((p) => p.includes("/"), {
5117
5748
  message: 'must be a path (contain at least one "/"), not a bare filename'
5118
5749
  })
5119
5750
  });
5751
+ var MAX_REPORT_COMMAND_TIMEOUT_MS = 3600000;
5120
5752
  var BRIEF_BUILTINS = [
5121
5753
  "tier",
5122
5754
  "testPlan",
@@ -5136,17 +5768,17 @@ var BRIEF_BUILTINS = [
5136
5768
  "briefSections",
5137
5769
  "milestoneShape"
5138
5770
  ];
5139
- var BriefSectionSchema = z2.union([
5140
- z2.object({ builtin: z2.enum(BRIEF_BUILTINS) }),
5141
- z2.object({ heading: z2.string().min(1), name: z2.string().optional() }),
5142
- z2.object({ field: z2.string().min(1), name: z2.string().optional() }),
5143
- z2.object({ phrase: z2.string().min(1), name: z2.string().optional() })
5771
+ var BriefSectionSchema = z4.union([
5772
+ z4.object({ builtin: z4.enum(BRIEF_BUILTINS) }),
5773
+ z4.object({ heading: z4.string().min(1), name: z4.string().optional() }),
5774
+ z4.object({ field: z4.string().min(1), name: z4.string().optional() }),
5775
+ z4.object({ phrase: z4.string().min(1), name: z4.string().optional() })
5144
5776
  ]);
5145
- var BriefSchemaSchema = z2.object({
5146
- pr: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
5147
- issue: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
5148
- milestone: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
5149
- ack: z2.array(z2.enum(BRIEF_BUILTINS)).optional()
5777
+ var BriefSchemaSchema = z4.object({
5778
+ pr: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
5779
+ issue: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
5780
+ milestone: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
5781
+ ack: z4.array(z4.enum(BRIEF_BUILTINS)).optional()
5150
5782
  });
5151
5783
  function isSafeRepoRelPath(p) {
5152
5784
  if (p.length === 0)
@@ -5155,7 +5787,7 @@ function isSafeRepoRelPath(p) {
5155
5787
  return false;
5156
5788
  return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
5157
5789
  }
5158
- var SafeRepoRelPath = z2.string().refine(isSafeRepoRelPath, {
5790
+ var SafeRepoRelPath = z4.string().refine(isSafeRepoRelPath, {
5159
5791
  message: "must be a repo-root-relative path with no `..` segment or absolute root"
5160
5792
  });
5161
5793
  var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
@@ -5165,22 +5797,22 @@ function isCanonicalHookBlockPath(p) {
5165
5797
  var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
5166
5798
  message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
5167
5799
  });
5168
- var ManagedBlockRecordSchema = z2.object({
5800
+ var ManagedBlockRecordSchema = z4.object({
5169
5801
  path: ManagedHookBlockPath,
5170
- marker: z2.string(),
5171
- comment: z2.enum(["hash", "html"])
5802
+ marker: z4.string(),
5803
+ comment: z4.enum(["hash", "html"])
5172
5804
  });
5173
- var ManagedManifestSchema = z2.object({
5174
- version: z2.number().int().positive(),
5175
- files: z2.array(SafeRepoRelPath),
5176
- blocks: z2.array(ManagedBlockRecordSchema),
5177
- labels: z2.array(z2.string()),
5178
- agents: z2.array(z2.enum(AGENT_VENDORS)).optional()
5805
+ var ManagedManifestSchema = z4.object({
5806
+ version: z4.number().int().positive(),
5807
+ files: z4.array(SafeRepoRelPath),
5808
+ blocks: z4.array(ManagedBlockRecordSchema),
5809
+ labels: z4.array(z4.string()),
5810
+ agents: z4.array(z4.enum(AGENT_VENDORS)).optional()
5179
5811
  });
5180
- var ProjectEntrySchema = z2.object({
5181
- name: z2.string().min(1),
5182
- description: z2.string().min(1).optional(),
5183
- path: z2.string().min(1).optional()
5812
+ var ProjectEntrySchema = z4.object({
5813
+ name: z4.string().min(1),
5814
+ description: z4.string().min(1).optional(),
5815
+ path: z4.string().min(1).optional()
5184
5816
  });
5185
5817
  function parseTokensCollectDeclaration(value) {
5186
5818
  const m = value.trim().match(/^(\S+)\s+(\S+)$/);
@@ -5191,34 +5823,55 @@ function parseTokensCollectDeclaration(value) {
5191
5823
  return null;
5192
5824
  return { interpreter, script };
5193
5825
  }
5194
- var VinayaConfigSchema = z2.object({
5195
- rings: z2.object({
5196
- ring1_forgeWriteInterception: z2.boolean(),
5197
- ring2_asyncAudits: z2.boolean()
5826
+ var VinayaConfigSchema = z4.object({
5827
+ rings: z4.object({
5828
+ ring1_forgeWriteInterception: z4.boolean(),
5829
+ ring2_asyncAudits: z4.boolean()
5198
5830
  }).optional(),
5199
- checks: z2.record(z2.string(), CheckEntrySchema).optional(),
5200
- roles: z2.record(z2.string(), RoleEntrySchema).optional(),
5831
+ checks: z4.record(z4.string(), CheckEntrySchema).optional(),
5832
+ roles: z4.record(z4.string(), RoleEntrySchema).optional(),
5201
5833
  briefSchema: BriefSchemaSchema.optional(),
5202
5834
  managed: ManagedManifestSchema.optional(),
5203
- principals: z2.array(z2.string()).min(1).optional(),
5204
- releaseActor: z2.string().min(1).optional(),
5205
- ci: z2.object({ setup: z2.string().min(1) }).optional(),
5206
- tokens: z2.object({
5207
- collect: z2.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
5835
+ principals: z4.array(z4.string()).min(1).optional(),
5836
+ releaseActor: z4.string().min(1).optional(),
5837
+ ci: z4.object({ setup: z4.string().min(1) }).optional(),
5838
+ tokens: z4.object({
5839
+ collect: z4.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
5208
5840
  message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
5209
5841
  })
5210
5842
  }).optional(),
5211
- blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional(),
5212
- proseGates: z2.object({
5213
- doctrineRoot: z2.string().min(1).optional(),
5214
- readerFacingPrefix: z2.string().min(1).optional(),
5215
- readerFacingSuffix: z2.string().min(1).optional(),
5216
- legacySlugDir: z2.string().min(1).optional()
5843
+ blastRadius: z4.object({ extraDomains: z4.array(z4.string()).optional() }).optional(),
5844
+ proseGates: z4.object({
5845
+ doctrineRoot: z4.string().min(1).optional(),
5846
+ readerFacingPrefix: z4.string().min(1).optional(),
5847
+ readerFacingSuffix: z4.string().min(1).optional(),
5848
+ legacySlugDir: z4.string().min(1).optional()
5217
5849
  }).optional(),
5218
- projects: z2.array(ProjectEntrySchema).optional(),
5219
- dispatch: z2.object({
5220
- timeoutMs: z2.number().int().positive().optional(),
5221
- agent: z2.enum(["claude", "codex", "gemini"]).optional()
5850
+ projects: z4.array(ProjectEntrySchema).optional(),
5851
+ dispatch: z4.object({
5852
+ timeoutMs: z4.number().int().positive().optional(),
5853
+ killGraceMs: z4.number().int().positive().optional(),
5854
+ agent: z4.enum(["claude", "codex", "gemini"]).optional()
5855
+ }).optional(),
5856
+ reviewPolicy: z4.object({
5857
+ codeReviewThreshold: z4.string().min(1).optional(),
5858
+ securityThreshold: z4.string().min(1).optional(),
5859
+ maxRounds: z4.number().optional()
5860
+ }).optional(),
5861
+ prePush: z4.object({
5862
+ alwaysRun: z4.array(z4.string()).optional()
5863
+ }).optional(),
5864
+ report: z4.object({
5865
+ commandTimeoutMs: z4.number().int().positive().optional()
5866
+ }).superRefine((report, ctx) => {
5867
+ const commandTimeoutMs = report.commandTimeoutMs;
5868
+ if (commandTimeoutMs !== undefined && commandTimeoutMs > MAX_REPORT_COMMAND_TIMEOUT_MS) {
5869
+ ctx.addIssue({
5870
+ code: z4.ZodIssueCode.custom,
5871
+ path: ["commandTimeoutMs"],
5872
+ message: "must be at most 3600000 (1 hour)"
5873
+ });
5874
+ }
5222
5875
  }).optional()
5223
5876
  });
5224
5877
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
@@ -5293,6 +5946,28 @@ function resolvePrincipalAllowlist(config) {
5293
5946
  function resolveReleaseActor(config) {
5294
5947
  return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
5295
5948
  }
5949
+ function resolveReviewPolicy(config) {
5950
+ const raw = config?.reviewPolicy;
5951
+ if (!raw)
5952
+ return DEFAULT_REVIEW_POLICY;
5953
+ const codeReviewThreshold = raw.codeReviewThreshold ?? DEFAULT_REVIEW_POLICY.codeReviewThreshold;
5954
+ if (!isKnownSeverity(CODE_REVIEW_SEVERITY_ORDER, codeReviewThreshold)) {
5955
+ 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.`);
5956
+ }
5957
+ const securityThreshold = raw.securityThreshold ?? DEFAULT_REVIEW_POLICY.securityThreshold;
5958
+ if (!isKnownSeverity(SECURITY_SEVERITY_ORDER, securityThreshold)) {
5959
+ 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.`);
5960
+ }
5961
+ const maxRounds = raw.maxRounds ?? DEFAULT_REVIEW_POLICY.maxRounds;
5962
+ if (!Number.isInteger(maxRounds) || maxRounds < 1) {
5963
+ throw new Error(`vinaya.config.json: reviewPolicy.maxRounds "${maxRounds}" is not a positive integer — fix the config, this never falls back to a default.`);
5964
+ }
5965
+ return {
5966
+ codeReviewThreshold,
5967
+ securityThreshold,
5968
+ maxRounds
5969
+ };
5970
+ }
5296
5971
  function trustAnchorRepo() {
5297
5972
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
5298
5973
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();