@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.
- package/README.md +4 -3
- package/aeg-root/enforcement.md +4 -4
- package/aeg-root/roles/developer.md +23 -17
- package/aeg-root/roles/planner.md +2 -0
- package/aeg-root/roles/principal.md +4 -0
- package/aeg-root/roles/reviewer.md +3 -3
- package/aeg-root/roles/security.md +3 -3
- package/dist/checks/bin/check-body-bare-digits.js +915 -240
- package/dist/checks/bin/check-branch-topology.js +820 -180
- package/dist/checks/bin/check-brief-shape.js +915 -240
- package/dist/checks/bin/check-changeset-coverage.js +1534 -262
- package/dist/checks/bin/check-closes-n.js +824 -184
- package/dist/checks/bin/check-coherence.js +984 -265
- package/dist/checks/bin/check-dead-branch-push.js +805 -176
- package/dist/checks/bin/check-dispatch-readiness.js +1046 -276
- package/dist/checks/bin/check-doc-coverage-push.js +1530 -258
- package/dist/checks/bin/check-doc-coverage.js +1532 -260
- package/dist/checks/bin/check-doctrine-no-procedures.js +915 -240
- package/dist/checks/bin/check-doctrine-portability.js +1529 -257
- package/dist/checks/bin/check-evidence-fresh.js +1875 -258
- package/dist/checks/bin/check-exec-bits.js +1527 -255
- package/dist/checks/bin/check-first-push-dispatch.js +932 -246
- package/dist/checks/bin/check-issue-assignment.js +822 -182
- package/dist/checks/bin/check-issue-milestone-attach.js +5734 -0
- package/dist/checks/bin/check-issue-objectives-numbering.js +5736 -0
- package/dist/checks/bin/check-issue-parts-coverage.js +5736 -0
- package/dist/checks/bin/check-issue-surface-globs.js +6910 -0
- package/dist/checks/bin/check-issue-title-grammar.js +5736 -0
- package/dist/checks/bin/check-issue-tranche-label.js +5736 -0
- package/dist/checks/bin/check-main-branch-refusal.js +805 -176
- package/dist/checks/bin/check-no-disk-state.js +805 -176
- package/dist/checks/bin/check-pr-premise-reassert.js +915 -240
- package/dist/checks/bin/check-pr-report-density.js +805 -176
- package/dist/checks/bin/check-quoted-command.js +1508 -255
- package/dist/checks/bin/check-reader-resolvable-prose.js +1512 -259
- package/dist/checks/bin/check-registry-gates.js +892 -176
- package/dist/checks/bin/check-retired-vocabulary.js +1506 -253
- package/dist/checks/bin/check-review-gate.js +1000 -245
- package/dist/checks/bin/check-single-plan-pr.js +805 -176
- package/dist/checks/bin/check-surface-scope.js +830 -182
- package/dist/checks/bin/check-test-plan.js +834 -189
- package/dist/checks/bin/check-token-collection-wired.js +805 -176
- package/dist/checks/bin/check-token-report.js +805 -176
- package/dist/checks/bin/check-workspace-escape.js +1525 -253
- package/dist/index.js +9835 -5597
- package/dist/lib/pre-push-changed-files.js +57 -0
- package/dist/lib/pre-push-select-tests.js +1473 -0
- package/package.json +4 -2
|
@@ -1957,6 +1957,8 @@ function parseInlineFieldList(section) {
|
|
|
1957
1957
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1958
1958
|
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1959
1959
|
var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
|
|
1960
|
+
var BRIEF_HASH_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Brief hash:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1961
|
+
var POLICY_DIGEST_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Policy digest:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1960
1962
|
function firstFiveLines(comment) {
|
|
1961
1963
|
return comment.split(`
|
|
1962
1964
|
`).slice(0, 5).join(`
|
|
@@ -1979,6 +1981,26 @@ function extractRulingOrdinal(comment) {
|
|
|
1979
1981
|
const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
|
|
1980
1982
|
return m ? Number.parseInt(m[1], 10) : null;
|
|
1981
1983
|
}
|
|
1984
|
+
function firstElevenLines(comment) {
|
|
1985
|
+
return comment.split(`
|
|
1986
|
+
`).slice(0, 11).join(`
|
|
1987
|
+
`);
|
|
1988
|
+
}
|
|
1989
|
+
function extractBriefHash(comment) {
|
|
1990
|
+
const m = firstElevenLines(comment).match(BRIEF_HASH_PATTERN);
|
|
1991
|
+
return m ? m[1].toLowerCase() : null;
|
|
1992
|
+
}
|
|
1993
|
+
function extractPolicyDigest(comment) {
|
|
1994
|
+
const m = firstElevenLines(comment).match(POLICY_DIGEST_PATTERN);
|
|
1995
|
+
return m ? m[1].toLowerCase() : null;
|
|
1996
|
+
}
|
|
1997
|
+
var FINDING_SEVERITY_LINE = /^\d+\.\s+\[([A-Z][A-Z]*)\]\s+(.+?)\s+—/gm;
|
|
1998
|
+
function extractFindingSeverities(comment) {
|
|
1999
|
+
return [...comment.matchAll(FINDING_SEVERITY_LINE)].map((m) => ({
|
|
2000
|
+
severity: m[1],
|
|
2001
|
+
location: m[2]
|
|
2002
|
+
}));
|
|
2003
|
+
}
|
|
1982
2004
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1983
2005
|
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1984
2006
|
if (candidates.length === 0) {
|
|
@@ -1987,6 +2009,9 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1987
2009
|
headSha: null,
|
|
1988
2010
|
objectivesVersion: null,
|
|
1989
2011
|
rulingOrdinal: null,
|
|
2012
|
+
briefHash: null,
|
|
2013
|
+
policyDigest: null,
|
|
2014
|
+
findingSeverities: [],
|
|
1990
2015
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1991
2016
|
};
|
|
1992
2017
|
}
|
|
@@ -1998,6 +2023,9 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1998
2023
|
headSha: null,
|
|
1999
2024
|
objectivesVersion: null,
|
|
2000
2025
|
rulingOrdinal: null,
|
|
2026
|
+
briefHash: null,
|
|
2027
|
+
policyDigest: null,
|
|
2028
|
+
findingSeverities: [],
|
|
2001
2029
|
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
2002
2030
|
};
|
|
2003
2031
|
}
|
|
@@ -2006,6 +2034,9 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
2006
2034
|
headSha: extractHeadSha(latest),
|
|
2007
2035
|
objectivesVersion: extractObjectivesVersion(latest),
|
|
2008
2036
|
rulingOrdinal: extractRulingOrdinal(latest),
|
|
2037
|
+
briefHash: extractBriefHash(latest),
|
|
2038
|
+
policyDigest: extractPolicyDigest(latest),
|
|
2039
|
+
findingSeverities: extractFindingSeverities(latest),
|
|
2009
2040
|
danglingNote: null
|
|
2010
2041
|
};
|
|
2011
2042
|
}
|
|
@@ -2341,6 +2372,53 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
|
|
|
2341
2372
|
}
|
|
2342
2373
|
return out;
|
|
2343
2374
|
}
|
|
2375
|
+
// ../../packages/aeg-core/src/review-policy.ts
|
|
2376
|
+
var CODE_REVIEW_SEVERITY_ORDER = ["BLOCKER", "MAJOR", "MINOR"];
|
|
2377
|
+
var SECURITY_SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
|
|
2378
|
+
var DEFAULT_MAX_ROUNDS = 3;
|
|
2379
|
+
var DEFAULT_REVIEW_POLICY = {
|
|
2380
|
+
codeReviewThreshold: "BLOCKER",
|
|
2381
|
+
securityThreshold: "HIGH",
|
|
2382
|
+
maxRounds: DEFAULT_MAX_ROUNDS
|
|
2383
|
+
};
|
|
2384
|
+
var FILE_SHAPED_LOCATION = /\.[a-zA-Z0-9]{1,10}(:\d+)?\s*$/;
|
|
2385
|
+
var PROSE_LOCATION_PATTERNS = [/\bpr\s*body\b/i, /\bcomment\b/i];
|
|
2386
|
+
var ROLE_FILE_LOCATION = /(^|\/)aeg-root\/roles\//i;
|
|
2387
|
+
function isProseLocation(location) {
|
|
2388
|
+
if (ROLE_FILE_LOCATION.test(location))
|
|
2389
|
+
return true;
|
|
2390
|
+
if (FILE_SHAPED_LOCATION.test(location))
|
|
2391
|
+
return false;
|
|
2392
|
+
return PROSE_LOCATION_PATTERNS.some((pattern) => pattern.test(location));
|
|
2393
|
+
}
|
|
2394
|
+
var PROSE_CAP_SEVERITY = "MINOR";
|
|
2395
|
+
function blockingSeverities(scale, threshold) {
|
|
2396
|
+
const idx = scale.indexOf(threshold);
|
|
2397
|
+
if (idx === -1) {
|
|
2398
|
+
throw new Error(`blockingSeverities: threshold "${threshold}" is not one of ${scale.join(" > ")}`);
|
|
2399
|
+
}
|
|
2400
|
+
return scale.slice(0, idx + 1);
|
|
2401
|
+
}
|
|
2402
|
+
function evaluateReviewFindings(findings, scale, threshold) {
|
|
2403
|
+
const blocking = new Set(blockingSeverities(scale, threshold));
|
|
2404
|
+
const blockingFindings = findings.filter((f) => {
|
|
2405
|
+
if (!scale.includes(f.severity)) {
|
|
2406
|
+
throw new Error(`evaluateReviewFindings: severity "${f.severity}" is not one of ${scale.join(" > ")}`);
|
|
2407
|
+
}
|
|
2408
|
+
const effectiveSeverity = f.location !== undefined && isProseLocation(f.location) ? PROSE_CAP_SEVERITY : f.severity;
|
|
2409
|
+
return blocking.has(effectiveSeverity);
|
|
2410
|
+
});
|
|
2411
|
+
return { outcome: blockingFindings.length > 0 ? "blocked" : "clean", blockingFindings };
|
|
2412
|
+
}
|
|
2413
|
+
function evaluateCodeReview(findings, policy) {
|
|
2414
|
+
return evaluateReviewFindings(findings, CODE_REVIEW_SEVERITY_ORDER, policy.codeReviewThreshold);
|
|
2415
|
+
}
|
|
2416
|
+
function evaluateSecurityReview(findings, policy) {
|
|
2417
|
+
return evaluateReviewFindings(findings, SECURITY_SEVERITY_ORDER, policy.securityThreshold);
|
|
2418
|
+
}
|
|
2419
|
+
function isKnownSeverity(scale, value) {
|
|
2420
|
+
return scale.includes(value);
|
|
2421
|
+
}
|
|
2344
2422
|
// ../../packages/aeg-core/src/pr-tier.ts
|
|
2345
2423
|
var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
|
|
2346
2424
|
function readTierFromPrBody(prBody) {
|
|
@@ -2711,7 +2789,7 @@ function checkForField(prBody) {
|
|
|
2711
2789
|
]
|
|
2712
2790
|
};
|
|
2713
2791
|
}
|
|
2714
|
-
function
|
|
2792
|
+
function checkClosesNPresence(prBody) {
|
|
2715
2793
|
const closesPattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s{0,8}:?\s{0,8}#\d+/i;
|
|
2716
2794
|
if (closesPattern.test(stripCode(prBody))) {
|
|
2717
2795
|
return { status: "pass", errors: [] };
|
|
@@ -2743,10 +2821,22 @@ var COMMIT_TYPES = [
|
|
|
2743
2821
|
"Test"
|
|
2744
2822
|
];
|
|
2745
2823
|
var COMMIT_TYPE_STYLE = new RegExp(`^(${COMMIT_TYPES.join("|")})(\\([a-z0-9-]+\\))?: \\S`);
|
|
2824
|
+
function checkForgeTitle(title) {
|
|
2825
|
+
const taskStyle = /^\[[a-z0-9._-]+\] \S+ — \S/;
|
|
2826
|
+
if (COMMIT_TYPE_STYLE.test(title) || taskStyle.test(title))
|
|
2827
|
+
return { status: "pass", errors: [] };
|
|
2828
|
+
return {
|
|
2829
|
+
status: "fail",
|
|
2830
|
+
errors: [
|
|
2831
|
+
`brief-validation title: "${title}" matches neither title grammar — expected \`Type: description\` / \`Type(scope): description\` (commitlint types + Plan) or \`[tranche] id — description\` (task form).`
|
|
2832
|
+
]
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2746
2835
|
var BRIEF_SHAPE_MARKERS = [checkSurfaceMap, checkDocUpdateList, checkStopConditions, checkAutonomyClause];
|
|
2747
2836
|
var TASK_BRANCH_PATTERN = /^task\/[^/]+\/[^/]+$/;
|
|
2837
|
+
var TASK_ISSUE_BRANCH_PATTERN = /^task\/issue-\d+$/;
|
|
2748
2838
|
function isTaskBranch(branch) {
|
|
2749
|
-
return TASK_BRANCH_PATTERN.test(branch);
|
|
2839
|
+
return TASK_BRANCH_PATTERN.test(branch) || TASK_ISSUE_BRANCH_PATTERN.test(branch);
|
|
2750
2840
|
}
|
|
2751
2841
|
function isBriefShaped(prBody) {
|
|
2752
2842
|
const stripped = stripCode(prBody);
|
|
@@ -2868,8 +2958,9 @@ function packagesNamedIn(text) {
|
|
|
2868
2958
|
}
|
|
2869
2959
|
function hasTestPathForConsumer(text, consumerDir) {
|
|
2870
2960
|
const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2871
|
-
const
|
|
2872
|
-
|
|
2961
|
+
const filePathRe = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
|
|
2962
|
+
const testDirRe = new RegExp(`${escaped}\\/(?:[\\w-]+\\/)*(?:tests|specs)(?:\\/|\\b)`);
|
|
2963
|
+
return filePathRe.test(text) || testDirRe.test(text);
|
|
2873
2964
|
}
|
|
2874
2965
|
function checkConsumerTests(prBody, consumersOf) {
|
|
2875
2966
|
const section4 = extractNumberedSection(prBody, 4);
|
|
@@ -2973,7 +3064,7 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2973
3064
|
checkConsumerTests(prBody, consumersOf),
|
|
2974
3065
|
checkDefeatCases(prBody),
|
|
2975
3066
|
...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
|
|
2976
|
-
...requireClosesN ? [
|
|
3067
|
+
...requireClosesN ? [checkClosesNPresence(prBody)] : []
|
|
2977
3068
|
];
|
|
2978
3069
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2979
3070
|
}
|
|
@@ -3263,6 +3354,28 @@ function topLevelSectionText(body, headingName) {
|
|
|
3263
3354
|
const next = /^##[ \t]/m.exec(afterHeading);
|
|
3264
3355
|
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
3265
3356
|
}
|
|
3357
|
+
function partHasBacktickedPath(text) {
|
|
3358
|
+
let i = 0;
|
|
3359
|
+
while (i < text.length) {
|
|
3360
|
+
const start = text.indexOf("`", i);
|
|
3361
|
+
if (start === -1)
|
|
3362
|
+
return false;
|
|
3363
|
+
const end = text.indexOf("`", start + 1);
|
|
3364
|
+
if (end === -1)
|
|
3365
|
+
return false;
|
|
3366
|
+
if (text.slice(start + 1, end).includes("/"))
|
|
3367
|
+
return true;
|
|
3368
|
+
i = end + 1;
|
|
3369
|
+
}
|
|
3370
|
+
return false;
|
|
3371
|
+
}
|
|
3372
|
+
var PART_MIN_WORDS_OUTSIDE_BACKTICKS = 3;
|
|
3373
|
+
function stripPartBackticks(text) {
|
|
3374
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
3375
|
+
}
|
|
3376
|
+
function partWordCount(text) {
|
|
3377
|
+
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
3378
|
+
}
|
|
3266
3379
|
function looksLikeFilePath(entry2) {
|
|
3267
3380
|
const stripped = entry2.replace(/\/\*\*?$/, "");
|
|
3268
3381
|
const lastSegment = stripped.split("/").pop() ?? stripped;
|
|
@@ -3303,6 +3416,18 @@ function globCoversPath(glob, path) {
|
|
|
3303
3416
|
const p = path.replace(/\/+$/, "");
|
|
3304
3417
|
return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
|
|
3305
3418
|
}
|
|
3419
|
+
function checkSurfaceGlobsResolve(body, resolvesToFile) {
|
|
3420
|
+
const surface = parseIssueSurface(body);
|
|
3421
|
+
if (!surface.ok)
|
|
3422
|
+
return { status: "pass", errors: [] };
|
|
3423
|
+
const errors = [];
|
|
3424
|
+
for (const glob of surface.value.in) {
|
|
3425
|
+
if (!resolvesToFile(glob)) {
|
|
3426
|
+
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.`);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3430
|
+
}
|
|
3306
3431
|
function checkSurfaceScope(changedFiles, outGlobs) {
|
|
3307
3432
|
if (outGlobs.length === 0)
|
|
3308
3433
|
return { ok: true };
|
|
@@ -3314,9 +3439,77 @@ function checkSurfaceScope(changedFiles, outGlobs) {
|
|
|
3314
3439
|
}
|
|
3315
3440
|
return violations.length > 0 ? { ok: false, violations } : { ok: true };
|
|
3316
3441
|
}
|
|
3442
|
+
var ISSUE_PART_LINE_RE = /^Part\s+(\d+)\s*\(([^)]*)\)\s*[-—–]\s*(.*)$/i;
|
|
3443
|
+
function parseIssueParts(body) {
|
|
3444
|
+
const section = topLevelSectionText(body, "Parts");
|
|
3445
|
+
if (section === null)
|
|
3446
|
+
return { ok: false, errors: ["no `## Parts` heading found in the body."] };
|
|
3447
|
+
const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
3448
|
+
const parts = [];
|
|
3449
|
+
const errors = [];
|
|
3450
|
+
for (const line of lines) {
|
|
3451
|
+
const m = ISSUE_PART_LINE_RE.exec(line);
|
|
3452
|
+
if (!m) {
|
|
3453
|
+
errors.push(`"${line}" is not a well-formed Parts line — expected \`Part <n> (<refs>) — <outcome>\`.`);
|
|
3454
|
+
continue;
|
|
3455
|
+
}
|
|
3456
|
+
const n = Number.parseInt(m[1], 10);
|
|
3457
|
+
const refs = m[2];
|
|
3458
|
+
const text = m[3].trim();
|
|
3459
|
+
if (text.length === 0) {
|
|
3460
|
+
errors.push(`Part ${n} has no outcome text after the dash — every Part states one observable outcome.`);
|
|
3461
|
+
continue;
|
|
3462
|
+
}
|
|
3463
|
+
if (partHasBacktickedPath(text) && partWordCount(stripPartBackticks(text)) < PART_MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
3464
|
+
errors.push(`Part ${n} is little more than a file path — a Part names an outcome and symbols, never a bare path.`);
|
|
3465
|
+
continue;
|
|
3466
|
+
}
|
|
3467
|
+
const objectiveIds = [...refs.matchAll(/O(\d+)/g)].map((r) => Number.parseInt(r[1], 10));
|
|
3468
|
+
parts.push({ n, objectiveIds, text });
|
|
3469
|
+
}
|
|
3470
|
+
if (parts.length === 0) {
|
|
3471
|
+
errors.push("the `## Parts` section has no well-formed `Part <n> (<refs>) — <outcome>` lines.");
|
|
3472
|
+
}
|
|
3473
|
+
if (errors.length > 0)
|
|
3474
|
+
return { ok: false, errors };
|
|
3475
|
+
return { ok: true, value: parts };
|
|
3476
|
+
}
|
|
3477
|
+
function checkPartsCiteDefinedObjectives(body) {
|
|
3478
|
+
const parts = parseIssueParts(body);
|
|
3479
|
+
const objectives = objectivesOf(body);
|
|
3480
|
+
if (!parts.ok || !objectives.ok)
|
|
3481
|
+
return { status: "pass", errors: [] };
|
|
3482
|
+
const definedIds = new Set(objectives.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
|
|
3483
|
+
const errors = [];
|
|
3484
|
+
for (const part of parts.value) {
|
|
3485
|
+
for (const objectiveId of part.objectiveIds) {
|
|
3486
|
+
if (!definedIds.has(objectiveId)) {
|
|
3487
|
+
errors.push(`issue-validation Parts: Part ${part.n} cites O${objectiveId}, which the Issue's own \`## Objectives\` section does not define.`);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3492
|
+
}
|
|
3317
3493
|
function isTaskIssueLabelSet(labels) {
|
|
3318
3494
|
return hasLabel("tranche", labels);
|
|
3319
3495
|
}
|
|
3496
|
+
function checkTrancheLabelPresence(_body, _labels) {
|
|
3497
|
+
return { status: "pass", errors: [] };
|
|
3498
|
+
}
|
|
3499
|
+
function checkMilestoneAttach(labels, currentMilestoneTitle, resolvedMilestoneTitle) {
|
|
3500
|
+
if (!isTaskIssueLabelSet(labels))
|
|
3501
|
+
return { status: "pass", errors: [] };
|
|
3502
|
+
if (resolvedMilestoneTitle === null)
|
|
3503
|
+
return { status: "pass", errors: [] };
|
|
3504
|
+
if (currentMilestoneTitle === resolvedMilestoneTitle)
|
|
3505
|
+
return { status: "pass", errors: [] };
|
|
3506
|
+
return {
|
|
3507
|
+
status: "fail",
|
|
3508
|
+
errors: [
|
|
3509
|
+
`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}".`
|
|
3510
|
+
]
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3320
3513
|
var TYPE_LABEL_IDS = LABELS.filter((l) => l.category === "type").map((l) => l.id);
|
|
3321
3514
|
function isControlCodePoint(codePoint) {
|
|
3322
3515
|
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
@@ -3381,6 +3574,19 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
|
|
|
3381
3574
|
var DOC_PATH_RE = /(?:(?:aeg-root|apps|packages|specs|docs|tools|\.claude|\.github)\/[\w./@-]*\.(?:md|mdx)|\.claude\/(?:skills|rules)\/[\w./-]+|\b(?:docs-index|decisions|projects|state-machine|enforcement|process|README|CLAUDE)\.md\b|\b[\w-]+-(?:spec|decisions|backlog)\.md\b)/i;
|
|
3382
3575
|
var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
|
|
3383
3576
|
|
|
3577
|
+
// ../../packages/aeg-core/src/task-branch-identity.ts
|
|
3578
|
+
var ISSUE_BRANCH_PATTERN = /^task\/issue-(\d+)$/;
|
|
3579
|
+
var TRANCHE_BRANCH_PATTERN = /^task\/([^/]+)\/([^/]+)$/;
|
|
3580
|
+
function parseTaskBranchIdentity(branch) {
|
|
3581
|
+
const issueMatch = ISSUE_BRANCH_PATTERN.exec(branch);
|
|
3582
|
+
if (issueMatch)
|
|
3583
|
+
return { kind: "issue", issueNumber: Number(issueMatch[1]) };
|
|
3584
|
+
const trancheMatch = TRANCHE_BRANCH_PATTERN.exec(branch);
|
|
3585
|
+
if (trancheMatch)
|
|
3586
|
+
return { kind: "tranche", tranche: trancheMatch[1], taskId: trancheMatch[2] };
|
|
3587
|
+
return null;
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3384
3590
|
// ../../packages/aeg-core/src/coherence-checks.ts
|
|
3385
3591
|
var COHERENCE_ENFORCED_FROM = "2026-07-01";
|
|
3386
3592
|
function isGrandfathered(isoDate) {
|
|
@@ -3401,6 +3607,7 @@ function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
|
|
|
3401
3607
|
if (handClosed)
|
|
3402
3608
|
continue;
|
|
3403
3609
|
failures.push({
|
|
3610
|
+
code: "closed-without-merge",
|
|
3404
3611
|
issue: e.task.issue,
|
|
3405
3612
|
tranche: e.trancheSlug,
|
|
3406
3613
|
task: e.task.id,
|
|
@@ -3425,6 +3632,7 @@ function checkA3(entries) {
|
|
|
3425
3632
|
continue;
|
|
3426
3633
|
if (e.facts.prState === "merged" && e.facts.issueState !== "closed") {
|
|
3427
3634
|
failures.push({
|
|
3635
|
+
code: "auto-close-misfire",
|
|
3428
3636
|
issue: e.task.issue,
|
|
3429
3637
|
tranche: e.trancheSlug,
|
|
3430
3638
|
task: e.task.id,
|
|
@@ -3449,6 +3657,7 @@ function checkT1(entries) {
|
|
|
3449
3657
|
continue;
|
|
3450
3658
|
if (e.facts === undefined) {
|
|
3451
3659
|
failures.push({
|
|
3660
|
+
code: "phantom-issue-ref",
|
|
3452
3661
|
issue: e.task.issue,
|
|
3453
3662
|
tranche: e.trancheSlug,
|
|
3454
3663
|
task: e.task.id,
|
|
@@ -3467,6 +3676,7 @@ function checkT2(openIssuesBySlug, topologyIssuesBySlug, ciTrancheSlug) {
|
|
|
3467
3676
|
for (const num of openNums) {
|
|
3468
3677
|
if (!topologySet.has(num)) {
|
|
3469
3678
|
failures.push({
|
|
3679
|
+
code: "orphan-task",
|
|
3470
3680
|
issue: num,
|
|
3471
3681
|
tranche: slug,
|
|
3472
3682
|
reason: `Issue #${num} is open and labeled ${trancheLabel(slug)} but does not appear in the topology file`
|
|
@@ -3501,6 +3711,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
|
|
|
3501
3711
|
continue;
|
|
3502
3712
|
if (forgeUnavailableSlugs?.has(e.trancheSlug)) {
|
|
3503
3713
|
failures.push({
|
|
3714
|
+
code: "tbd-in-active-tranche",
|
|
3504
3715
|
issue: null,
|
|
3505
3716
|
tranche: e.trancheSlug,
|
|
3506
3717
|
task: e.task.id,
|
|
@@ -3510,6 +3721,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
|
|
|
3510
3721
|
continue;
|
|
3511
3722
|
}
|
|
3512
3723
|
failures.push({
|
|
3724
|
+
code: "tbd-in-active-tranche",
|
|
3513
3725
|
issue: null,
|
|
3514
3726
|
tranche: e.trancheSlug,
|
|
3515
3727
|
task: e.task.id,
|
|
@@ -3542,6 +3754,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
3542
3754
|
const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
|
|
3543
3755
|
if (sameTask || sameIssue) {
|
|
3544
3756
|
failures.push({
|
|
3757
|
+
code: "d1-self-dependency",
|
|
3545
3758
|
issue: e.task.issue,
|
|
3546
3759
|
tranche: e.trancheSlug,
|
|
3547
3760
|
task: e.task.id,
|
|
@@ -3553,6 +3766,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
3553
3766
|
const depClosed = depFacts?.issueState === "closed";
|
|
3554
3767
|
if (!depClosed) {
|
|
3555
3768
|
failures.push({
|
|
3769
|
+
code: "dispatched-on-unmet-deps",
|
|
3556
3770
|
issue: e.task.issue,
|
|
3557
3771
|
tranche: e.trancheSlug,
|
|
3558
3772
|
task: e.task.id,
|
|
@@ -3578,11 +3792,13 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
|
|
|
3578
3792
|
const errors = [
|
|
3579
3793
|
...checkIssueRationale(issue.body).errors,
|
|
3580
3794
|
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
|
|
3581
|
-
...checkIssueObjectives(issue.body, issue.number).errors
|
|
3795
|
+
...checkIssueObjectives(issue.body, issue.number).errors,
|
|
3796
|
+
...checkPartsCiteDefinedObjectives(issue.body).errors
|
|
3582
3797
|
];
|
|
3583
3798
|
if (errors.length === 0)
|
|
3584
3799
|
continue;
|
|
3585
3800
|
failures.push({
|
|
3801
|
+
code: "missing-rationale-field",
|
|
3586
3802
|
issue: issue.number,
|
|
3587
3803
|
tranche: slug,
|
|
3588
3804
|
reason: `Issue #${issue.number} fails the rationale gate: ${errors.join(" | ")}`,
|
|
@@ -3611,6 +3827,7 @@ function checkL1(files, entriesBySlug) {
|
|
|
3611
3827
|
const allClosed = withFacts.every((e) => e.facts?.issueState === "closed");
|
|
3612
3828
|
if (allClosed) {
|
|
3613
3829
|
failures.push({
|
|
3830
|
+
code: "archive-recommended",
|
|
3614
3831
|
tranche: f.slug,
|
|
3615
3832
|
reason: "Active tranche has no open task-Issues — consider archiving to completed/"
|
|
3616
3833
|
});
|
|
@@ -3642,27 +3859,38 @@ function extractClosesReferences(prBody) {
|
|
|
3642
3859
|
}
|
|
3643
3860
|
return referenced;
|
|
3644
3861
|
}
|
|
3645
|
-
function
|
|
3862
|
+
function checkClosesNTopology(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
3646
3863
|
const referenced = extractClosesReferences(prBody);
|
|
3647
3864
|
if (taskIssueRefs) {
|
|
3648
3865
|
for (const n of referenced) {
|
|
3649
|
-
const
|
|
3650
|
-
if (!
|
|
3866
|
+
const ref2 = taskIssueRefs.get(n);
|
|
3867
|
+
if (!ref2)
|
|
3651
3868
|
continue;
|
|
3652
|
-
const expectedBranch = `task/${
|
|
3869
|
+
const expectedBranch = `task/${ref2.trancheSlug}/${ref2.taskId}`;
|
|
3653
3870
|
if (branch !== expectedBranch) {
|
|
3654
3871
|
return {
|
|
3655
3872
|
ok: false,
|
|
3656
|
-
message: `closes-n-reverse: branch "${branch}" closes #${n} (task ${
|
|
3873
|
+
message: `closes-n-reverse: branch "${branch}" closes #${n} (task ${ref2.taskId} of tranche "${ref2.trancheSlug}") but is not named "${expectedBranch}" — rename the branch and re-push, or if this work is intentionally outside AEG's dispatch flow, remove the Closes reference.`
|
|
3657
3874
|
};
|
|
3658
3875
|
}
|
|
3659
3876
|
}
|
|
3660
3877
|
}
|
|
3661
|
-
const
|
|
3662
|
-
if (!
|
|
3878
|
+
const ref = parseTaskBranchIdentity(branch);
|
|
3879
|
+
if (!ref)
|
|
3663
3880
|
return { ok: true };
|
|
3664
|
-
|
|
3665
|
-
|
|
3881
|
+
if (ref.kind === "issue") {
|
|
3882
|
+
const expectedIssue2 = ref.issueNumber;
|
|
3883
|
+
if (!referenced.has(expectedIssue2)) {
|
|
3884
|
+
return {
|
|
3885
|
+
ok: false,
|
|
3886
|
+
expectedIssue: expectedIssue2,
|
|
3887
|
+
message: `closes-n: PR body does not contain \`Closes #${expectedIssue2}\` (required for branch "${branch}"). Add it to the PR body Summary section.`
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
return { ok: true, expectedIssue: expectedIssue2 };
|
|
3891
|
+
}
|
|
3892
|
+
const trancheSlug = ref.tranche;
|
|
3893
|
+
const taskId = ref.taskId;
|
|
3666
3894
|
const trancheFile = trancheFiles.find((f) => f.slug === trancheSlug);
|
|
3667
3895
|
if (!trancheFile) {
|
|
3668
3896
|
return {
|
|
@@ -3714,34 +3942,67 @@ function extractIssue(body) {
|
|
|
3714
3942
|
const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
|
|
3715
3943
|
return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
|
|
3716
3944
|
}
|
|
3717
|
-
// ../../packages/aeg-core/src/review-
|
|
3718
|
-
|
|
3719
|
-
|
|
3945
|
+
// ../../packages/aeg-core/src/review-input-manifest.ts
|
|
3946
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3947
|
+
function briefHash(brief) {
|
|
3948
|
+
return createHash4("sha256").update(`${brief}
|
|
3949
|
+
`).digest("hex");
|
|
3950
|
+
}
|
|
3951
|
+
function policyDigest(policy) {
|
|
3952
|
+
return createHash4("sha256").update(JSON.stringify({ codeReviewThreshold: policy.codeReviewThreshold, securityThreshold: policy.securityThreshold })).digest("hex");
|
|
3953
|
+
}
|
|
3954
|
+
function isBoundToHead(echoed, headSha) {
|
|
3955
|
+
if (!echoed.headSha)
|
|
3720
3956
|
return false;
|
|
3721
|
-
return headSha.toLowerCase().startsWith(
|
|
3957
|
+
return headSha.toLowerCase().startsWith(echoed.headSha.toLowerCase());
|
|
3722
3958
|
}
|
|
3723
|
-
function isBoundByPatchIdentity(
|
|
3724
|
-
if (patchIdOf === undefined || !
|
|
3959
|
+
function isBoundByPatchIdentity(echoed, headSha, patchIdOf) {
|
|
3960
|
+
if (patchIdOf === undefined || !echoed.headSha)
|
|
3725
3961
|
return false;
|
|
3726
|
-
const judged = patchIdOf(
|
|
3962
|
+
const judged = patchIdOf(echoed.headSha);
|
|
3727
3963
|
const current = patchIdOf(headSha);
|
|
3728
3964
|
if (judged === null || current === null)
|
|
3729
3965
|
return false;
|
|
3730
3966
|
return judged === current;
|
|
3731
3967
|
}
|
|
3732
|
-
function isBoundToPatch(
|
|
3733
|
-
return isBoundToHead(
|
|
3968
|
+
function isBoundToPatch(echoed, headSha, patchIdOf) {
|
|
3969
|
+
return isBoundToHead(echoed, headSha) || isBoundByPatchIdentity(echoed, headSha, patchIdOf);
|
|
3734
3970
|
}
|
|
3735
|
-
function isBoundToObjectives(
|
|
3971
|
+
function isBoundToObjectives(echoed, currentVersion) {
|
|
3736
3972
|
if (currentVersion === null)
|
|
3737
3973
|
return true;
|
|
3738
|
-
return
|
|
3974
|
+
return echoed.objectivesVersion === currentVersion;
|
|
3739
3975
|
}
|
|
3740
|
-
function isBoundToRulings(
|
|
3741
|
-
if (
|
|
3976
|
+
function isBoundToRulings(echoed, currentOrdinal) {
|
|
3977
|
+
if (echoed.rulingOrdinal === null)
|
|
3742
3978
|
return currentOrdinal === 0;
|
|
3743
|
-
return
|
|
3979
|
+
return echoed.rulingOrdinal === currentOrdinal;
|
|
3980
|
+
}
|
|
3981
|
+
function isBoundToBriefHash(echoed, currentHash) {
|
|
3982
|
+
if (currentHash === null)
|
|
3983
|
+
return true;
|
|
3984
|
+
return echoed.briefHash === currentHash;
|
|
3985
|
+
}
|
|
3986
|
+
function isBoundToPolicy(echoed, currentDigest) {
|
|
3987
|
+
return echoed.policyDigest === currentDigest;
|
|
3988
|
+
}
|
|
3989
|
+
function compareManifest(echoed, current, patchIdOf) {
|
|
3990
|
+
const head = isBoundToPatch(echoed, current.headSha, patchIdOf);
|
|
3991
|
+
const briefHashBound = isBoundToBriefHash(echoed, current.briefHash);
|
|
3992
|
+
const objectivesVersion2 = isBoundToObjectives(echoed, current.objectivesVersion);
|
|
3993
|
+
const rulingOrdinal = isBoundToRulings(echoed, current.rulingOrdinal);
|
|
3994
|
+
const policyDigestBound = isBoundToPolicy(echoed, current.policyDigest);
|
|
3995
|
+
return {
|
|
3996
|
+
bound: head && briefHashBound && objectivesVersion2 && rulingOrdinal && policyDigestBound,
|
|
3997
|
+
head,
|
|
3998
|
+
briefHash: briefHashBound,
|
|
3999
|
+
objectivesVersion: objectivesVersion2,
|
|
4000
|
+
rulingOrdinal,
|
|
4001
|
+
policyDigest: policyDigestBound
|
|
4002
|
+
};
|
|
3744
4003
|
}
|
|
4004
|
+
|
|
4005
|
+
// ../../packages/aeg-core/src/review-gate.ts
|
|
3745
4006
|
function checkReviewGate(input) {
|
|
3746
4007
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3747
4008
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3764,15 +4025,55 @@ function checkReviewGate(input) {
|
|
|
3764
4025
|
const verifiedBodies = verified.map((c) => c.body);
|
|
3765
4026
|
const codeReview = extractCodeReviewVerdict(verifiedBodies);
|
|
3766
4027
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
3767
|
-
const
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
4028
|
+
const policy = input.policy ?? DEFAULT_REVIEW_POLICY;
|
|
4029
|
+
let codeReviewPolicyEvaluation;
|
|
4030
|
+
let securityPolicyEvaluation;
|
|
4031
|
+
try {
|
|
4032
|
+
codeReviewPolicyEvaluation = evaluateCodeReview(codeReview.findingSeverities, policy);
|
|
4033
|
+
securityPolicyEvaluation = evaluateSecurityReview(security.findingSeverities, policy);
|
|
4034
|
+
} catch (err) {
|
|
4035
|
+
return {
|
|
4036
|
+
verdict: "fail",
|
|
4037
|
+
reason: `a verdict comment carries a finding severity this repository's policy does not recognize: ${err instanceof Error ? err.message : String(err)}`,
|
|
4038
|
+
waived: false
|
|
4039
|
+
};
|
|
4040
|
+
}
|
|
4041
|
+
const codeReviewTextClean = codeReview.value === "APPROVE";
|
|
4042
|
+
const securityTextClean = security.value === "PASS";
|
|
4043
|
+
const codeReviewPolicyClean = codeReviewPolicyEvaluation.outcome === "clean";
|
|
4044
|
+
const securityPolicyClean = securityPolicyEvaluation.outcome === "clean";
|
|
4045
|
+
const codeReviewClean = codeReviewTextClean && codeReviewPolicyClean;
|
|
4046
|
+
const securityClean = securityTextClean && securityPolicyClean;
|
|
4047
|
+
const currentManifest = {
|
|
4048
|
+
headSha: input.headSha,
|
|
4049
|
+
briefHash: input.briefHash ?? null,
|
|
4050
|
+
objectivesVersion: input.objectivesVersion,
|
|
4051
|
+
rulingOrdinal: input.rulingOrdinal,
|
|
4052
|
+
policyDigest: policyDigest(policy)
|
|
4053
|
+
};
|
|
4054
|
+
const codeReviewEchoed = {
|
|
4055
|
+
headSha: codeReview.headSha,
|
|
4056
|
+
briefHash: codeReview.briefHash,
|
|
4057
|
+
objectivesVersion: codeReview.objectivesVersion,
|
|
4058
|
+
rulingOrdinal: codeReview.rulingOrdinal,
|
|
4059
|
+
policyDigest: codeReview.policyDigest
|
|
4060
|
+
};
|
|
4061
|
+
const securityEchoed = {
|
|
4062
|
+
headSha: security.headSha,
|
|
4063
|
+
briefHash: security.briefHash,
|
|
4064
|
+
objectivesVersion: security.objectivesVersion,
|
|
4065
|
+
rulingOrdinal: security.rulingOrdinal,
|
|
4066
|
+
policyDigest: security.policyDigest
|
|
4067
|
+
};
|
|
4068
|
+
const codeReviewBinding = compareManifest(codeReviewEchoed, currentManifest, input.patchIdOf);
|
|
4069
|
+
const securityBinding = compareManifest(securityEchoed, currentManifest, input.patchIdOf);
|
|
4070
|
+
const codeReviewBound = codeReviewBinding.head;
|
|
4071
|
+
const securityBound = securityBinding.head;
|
|
4072
|
+
const codeReviewObjectivesBound = codeReviewBinding.objectivesVersion;
|
|
4073
|
+
const securityObjectivesBound = securityBinding.objectivesVersion;
|
|
4074
|
+
const codeReviewRulingsBound = codeReviewBinding.rulingOrdinal;
|
|
4075
|
+
const securityRulingsBound = securityBinding.rulingOrdinal;
|
|
4076
|
+
if (codeReviewClean && codeReviewBinding.bound && securityClean && securityBinding.bound && mechanicalChecksClean) {
|
|
3776
4077
|
return {
|
|
3777
4078
|
verdict: "pass",
|
|
3778
4079
|
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}, and every reported mechanical check is green.`,
|
|
@@ -3780,23 +4081,35 @@ function checkReviewGate(input) {
|
|
|
3780
4081
|
};
|
|
3781
4082
|
}
|
|
3782
4083
|
const problems = [];
|
|
3783
|
-
if (!
|
|
4084
|
+
if (!codeReviewTextClean) {
|
|
3784
4085
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
4086
|
+
} else if (!codeReviewPolicyClean) {
|
|
4087
|
+
problems.push(`code-reviewer verdict says APPROVE but carries a finding (${codeReviewPolicyEvaluation.blockingFindings.map((f) => f.severity).join(", ")}) at or above this repository's code-review policy threshold (${policy.codeReviewThreshold}) — a reviewer's own APPROVE never overrides policy`);
|
|
3785
4088
|
} else if (!codeReviewBound) {
|
|
3786
4089
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3787
4090
|
} else if (!codeReviewObjectivesBound) {
|
|
3788
4091
|
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3789
4092
|
} else if (!codeReviewRulingsBound) {
|
|
3790
4093
|
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`);
|
|
4094
|
+
} else if (!codeReviewBinding.briefHash) {
|
|
4095
|
+
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"}`);
|
|
4096
|
+
} else if (!codeReviewBinding.policyDigest) {
|
|
4097
|
+
problems.push(`the newest code-review verdict was cast against review policy digest ${codeReview.policyDigest ?? "none"}, this repository's current policy digest is ${currentManifest.policyDigest}`);
|
|
3791
4098
|
}
|
|
3792
|
-
if (!
|
|
4099
|
+
if (!securityTextClean) {
|
|
3793
4100
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
4101
|
+
} else if (!securityPolicyClean) {
|
|
4102
|
+
problems.push(`security-review verdict says PASS but carries a finding (${securityPolicyEvaluation.blockingFindings.map((f) => f.severity).join(", ")}) at or above this repository's security policy threshold (${policy.securityThreshold}) — a reviewer's own PASS never overrides policy`);
|
|
3794
4103
|
} else if (!securityBound) {
|
|
3795
4104
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3796
4105
|
} else if (!securityObjectivesBound) {
|
|
3797
4106
|
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3798
4107
|
} else if (!securityRulingsBound) {
|
|
3799
4108
|
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`);
|
|
4109
|
+
} else if (!securityBinding.briefHash) {
|
|
4110
|
+
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"}`);
|
|
4111
|
+
} else if (!securityBinding.policyDigest) {
|
|
4112
|
+
problems.push(`the newest security-review verdict was cast against review policy digest ${security.policyDigest ?? "none"}, this repository's current policy digest is ${currentManifest.policyDigest}`);
|
|
3800
4113
|
}
|
|
3801
4114
|
if (!mechanicalChecksClean) {
|
|
3802
4115
|
problems.push(reportedMechanicalChecks.length === 0 ? "no mechanical checks have reported for this head yet" : `mechanical check(s) not green: ${reportedMechanicalChecks.filter((c) => c.bucket !== "pass").map((c) => `${c.name} (${c.bucket})`).join(", ")}`);
|
|
@@ -4488,42 +4801,45 @@ function checkDispatchReadiness(input) {
|
|
|
4488
4801
|
const { trancheSlug, task } = input;
|
|
4489
4802
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
4490
4803
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
4491
|
-
const
|
|
4804
|
+
const blockerDetails = [];
|
|
4805
|
+
const push = (blockerClass, message) => {
|
|
4806
|
+
blockerDetails.push({ class: blockerClass, message });
|
|
4807
|
+
};
|
|
4492
4808
|
if (task.issue === null) {
|
|
4493
|
-
|
|
4809
|
+
push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
|
|
4494
4810
|
} else if (input.issue === null) {
|
|
4495
|
-
|
|
4811
|
+
push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
|
|
4496
4812
|
}
|
|
4497
4813
|
if (input.issue !== null && !input.issueRationalePass) {
|
|
4498
|
-
|
|
4814
|
+
push("rationale", `dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
|
|
4499
4815
|
}
|
|
4500
4816
|
for (const dep of input.dependsOn) {
|
|
4501
4817
|
if (isSelfDependency(dep, task, input.issue)) {
|
|
4502
4818
|
const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
|
|
4503
|
-
|
|
4819
|
+
push("internal-self-dependency", `dispatch-gate INTERNAL: parsed a self-dependency for task ${task.id} (${issueStr}) — this is a parser bug in parseRationaleDeps, not a real dependency. Please report it upstream. Re-run once the rationale is corrected or the fix ships.`);
|
|
4504
4820
|
continue;
|
|
4505
4821
|
}
|
|
4506
4822
|
if (dep.resolved === false) {
|
|
4507
|
-
|
|
4823
|
+
push("depends-on-unresolvable", `dispatch-gate depends-on: ${taskLabel} depends on "${dep.id}", which is UNRESOLVABLE — the resolver could not find a matching tranche/task/Issue for this edge (not a claim about merge status). Not dispatchable until the edge is corrected.`);
|
|
4508
4824
|
continue;
|
|
4509
4825
|
}
|
|
4510
4826
|
if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
|
|
4511
4827
|
const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
|
|
4512
|
-
|
|
4828
|
+
push("depends-on-not-merged", `dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
|
|
4513
4829
|
}
|
|
4514
4830
|
}
|
|
4515
4831
|
for (const c of input.conflictsWith) {
|
|
4516
4832
|
if (c.openOrInFlight) {
|
|
4517
4833
|
const issueStr = c.issue !== null ? ` (#${c.issue})` : "";
|
|
4518
|
-
|
|
4834
|
+
push("conflicts-with", `dispatch-gate conflicts-with: ${taskLabel} conflicts with ${c.id}${issueStr}, whose PR is open or in-flight — not dispatchable until it merges.`);
|
|
4519
4835
|
}
|
|
4520
4836
|
}
|
|
4521
4837
|
for (const proj of input.priorTrancheArchival) {
|
|
4522
4838
|
if (proj.priorTrancheSlug !== null && !proj.archived) {
|
|
4523
|
-
|
|
4839
|
+
push("prior-tranche-archival", `dispatch-gate prior-tranche-archival: project \`${proj.project}\`'s previous tranche \`${proj.priorTrancheSlug}\` is not archived — the Tranche Archivist must run before new work on this product.`);
|
|
4524
4840
|
}
|
|
4525
4841
|
}
|
|
4526
|
-
return { ready:
|
|
4842
|
+
return { ready: blockerDetails.length === 0, blockers: blockerDetails.map((b) => b.message), blockerDetails };
|
|
4527
4843
|
}
|
|
4528
4844
|
// ../../packages/aeg-core/src/single-plan-pr.ts
|
|
4529
4845
|
function trancheSlugFromTopologyPath(path) {
|
|
@@ -4625,6 +4941,91 @@ function checkBranchTopology(input) {
|
|
|
4625
4941
|
reason: `Branch \`${branch}\` matches topology row \`${taskId}\` in ${topoPath}.`
|
|
4626
4942
|
};
|
|
4627
4943
|
}
|
|
4944
|
+
// ../../packages/aeg-core/src/task-tools.ts
|
|
4945
|
+
import { z } from "zod";
|
|
4946
|
+
var TASK_TOOL_ERROR_KINDS = [
|
|
4947
|
+
"validation",
|
|
4948
|
+
"authority",
|
|
4949
|
+
"precondition",
|
|
4950
|
+
"capability",
|
|
4951
|
+
"infrastructure",
|
|
4952
|
+
"cancellation",
|
|
4953
|
+
"timeout",
|
|
4954
|
+
"uncertain_effect"
|
|
4955
|
+
];
|
|
4956
|
+
var TaskToolErrorSchema = z.object({
|
|
4957
|
+
kind: z.enum(TASK_TOOL_ERROR_KINDS),
|
|
4958
|
+
message: z.string().min(1),
|
|
4959
|
+
detail: z.string().optional()
|
|
4960
|
+
});
|
|
4961
|
+
var TaskToolRefSchema = z.union([
|
|
4962
|
+
z.object({ tranche: z.string().min(1), id: z.string().min(1) }),
|
|
4963
|
+
z.object({ issue: z.number().int().positive() })
|
|
4964
|
+
]);
|
|
4965
|
+
var MAX_PAGE_LIMIT = 100;
|
|
4966
|
+
var PageRequestSchema = z.object({
|
|
4967
|
+
cursor: z.string().optional(),
|
|
4968
|
+
limit: z.number().int().positive().max(MAX_PAGE_LIMIT).optional()
|
|
4969
|
+
});
|
|
4970
|
+
var FreshnessSchema = z.enum(["fresh", "stale", "unknown"]);
|
|
4971
|
+
var ObservedSchema = z.object({
|
|
4972
|
+
observedAt: z.string(),
|
|
4973
|
+
freshness: FreshnessSchema
|
|
4974
|
+
});
|
|
4975
|
+
var TaskStatusInputSchema = z.object({
|
|
4976
|
+
task: TaskToolRefSchema.optional()
|
|
4977
|
+
}).merge(PageRequestSchema);
|
|
4978
|
+
var TaskStatusItemSchema = z.object({
|
|
4979
|
+
task: TaskToolRefSchema,
|
|
4980
|
+
issue: z.number().int().positive(),
|
|
4981
|
+
pr: z.number().int().positive().nullable(),
|
|
4982
|
+
state: z.string()
|
|
4983
|
+
}).merge(ObservedSchema);
|
|
4984
|
+
var TaskStatusResultSchema = z.object({
|
|
4985
|
+
items: z.array(TaskStatusItemSchema),
|
|
4986
|
+
nextCursor: z.string().nullable()
|
|
4987
|
+
});
|
|
4988
|
+
var RequestedAuthoritySchema = z.enum(["planner", "principal", "operator", "self"]);
|
|
4989
|
+
var EscalationInputsSchema = z.object({
|
|
4990
|
+
task: z.number().int().positive(),
|
|
4991
|
+
round: z.number().int().nonnegative(),
|
|
4992
|
+
head: z.string(),
|
|
4993
|
+
branch: z.string(),
|
|
4994
|
+
prNumber: z.number().int().positive()
|
|
4995
|
+
});
|
|
4996
|
+
var EscalationEvidenceSchema = z.object({
|
|
4997
|
+
round: z.number().int().nonnegative(),
|
|
4998
|
+
reviewer: z.string().nullable(),
|
|
4999
|
+
security: z.string().nullable()
|
|
5000
|
+
});
|
|
5001
|
+
var TaskEscalationReadInputSchema = z.object({
|
|
5002
|
+
task: TaskToolRefSchema
|
|
5003
|
+
}).merge(PageRequestSchema);
|
|
5004
|
+
var TaskEscalationPacketSchema = z.object({
|
|
5005
|
+
reason: z.string(),
|
|
5006
|
+
detail: z.string().nullable(),
|
|
5007
|
+
inputs: EscalationInputsSchema.nullable(),
|
|
5008
|
+
evidence: EscalationEvidenceSchema.nullable(),
|
|
5009
|
+
attemptedRecovery: z.string(),
|
|
5010
|
+
requestedAuthority: RequestedAuthoritySchema,
|
|
5011
|
+
permittedNextActions: z.array(z.string())
|
|
5012
|
+
}).merge(ObservedSchema);
|
|
5013
|
+
var TaskEscalationReadResultSchema = z.object({
|
|
5014
|
+
items: z.array(TaskEscalationPacketSchema),
|
|
5015
|
+
nextCursor: z.string().nullable()
|
|
5016
|
+
}).merge(ObservedSchema);
|
|
5017
|
+
var TaskStartInputSchema = z.object({
|
|
5018
|
+
tranche: z.string().min(1),
|
|
5019
|
+
id: z.string().min(1)
|
|
5020
|
+
});
|
|
5021
|
+
var TaskResumeInputSchema = z.object({
|
|
5022
|
+
task: TaskToolRefSchema
|
|
5023
|
+
});
|
|
5024
|
+
var TaskCancelInputSchema = z.object({
|
|
5025
|
+
task: TaskToolRefSchema,
|
|
5026
|
+
reason: z.string().min(1)
|
|
5027
|
+
});
|
|
5028
|
+
var NoResultSchema = z.never();
|
|
4628
5029
|
// ../../packages/aeg-core/src/first-push-dispatch-gate.ts
|
|
4629
5030
|
function parseTaskBranch(branch) {
|
|
4630
5031
|
const m = /^task\/([^/]+)\/([^/]+)$/.exec(branch);
|
|
@@ -4708,7 +5109,6 @@ function decideIssueAssignment(input) {
|
|
|
4708
5109
|
};
|
|
4709
5110
|
}
|
|
4710
5111
|
// ../../packages/aeg-core/src/test-plan-gate.ts
|
|
4711
|
-
var TASK_BRANCH_PATTERN2 = /^task\/[^/]+\/[^/]+$/;
|
|
4712
5112
|
function evaluateTestPlanGate(body, branch) {
|
|
4713
5113
|
if (!body) {
|
|
4714
5114
|
return {
|
|
@@ -4721,7 +5121,7 @@ function evaluateTestPlanGate(body, branch) {
|
|
|
4721
5121
|
}
|
|
4722
5122
|
const located = locateTestPlanSection(body);
|
|
4723
5123
|
if (!located.found) {
|
|
4724
|
-
if (
|
|
5124
|
+
if (parseTaskBranchIdentity(branch) !== null) {
|
|
4725
5125
|
return {
|
|
4726
5126
|
verdict: "fail",
|
|
4727
5127
|
messages: [
|
|
@@ -4849,7 +5249,7 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4849
5249
|
return findings;
|
|
4850
5250
|
}
|
|
4851
5251
|
// ../../packages/aeg-core/src/log/schema.ts
|
|
4852
|
-
import { z } from "zod";
|
|
5252
|
+
import { z as z2 } from "zod";
|
|
4853
5253
|
var ROLE_VALUES = [
|
|
4854
5254
|
"planner",
|
|
4855
5255
|
"developer",
|
|
@@ -4859,136 +5259,172 @@ var ROLE_VALUES = [
|
|
|
4859
5259
|
"archivist",
|
|
4860
5260
|
"architect"
|
|
4861
5261
|
];
|
|
4862
|
-
var RoleSchema =
|
|
5262
|
+
var RoleSchema = z2.enum(ROLE_VALUES);
|
|
4863
5263
|
var HOST_VALUES = ["hook", "ci", "cli", "loop"];
|
|
4864
|
-
var HostSchema =
|
|
5264
|
+
var HostSchema = z2.enum(HOST_VALUES);
|
|
4865
5265
|
var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
4866
|
-
var
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
doctrine: z.string(),
|
|
5266
|
+
var headerMetaCore = {
|
|
5267
|
+
ts: z2.string(),
|
|
5268
|
+
run_id: z2.string().regex(RUN_ID_PATTERN),
|
|
5269
|
+
seq: z2.number().int().nonnegative(),
|
|
5270
|
+
repo: z2.string().nullable(),
|
|
5271
|
+
vinaya: z2.string(),
|
|
5272
|
+
doctrine: z2.string(),
|
|
4874
5273
|
host: HostSchema,
|
|
4875
|
-
machine:
|
|
5274
|
+
machine: z2.string()
|
|
5275
|
+
};
|
|
5276
|
+
var HeaderMetaV1Schema = z2.object({
|
|
5277
|
+
schema: z2.literal(1),
|
|
5278
|
+
...headerMetaCore
|
|
4876
5279
|
}).strict();
|
|
4877
|
-
var
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
role: z.union([RoleSchema, z.literal("unattributed")]),
|
|
4882
|
-
round: z.number().int().optional(),
|
|
4883
|
-
objectives_version: z.string().optional()
|
|
5280
|
+
var LineageSchema = z2.object({
|
|
5281
|
+
run: z2.string().nullable(),
|
|
5282
|
+
attempt: z2.number().int().nullable(),
|
|
5283
|
+
parent: z2.string().nullable()
|
|
4884
5284
|
}).strict();
|
|
4885
|
-
var
|
|
5285
|
+
var InputVersionsSchema = z2.object({
|
|
5286
|
+
objectives_version: z2.string().nullable(),
|
|
5287
|
+
brief_hash: z2.string().nullable(),
|
|
5288
|
+
ruling_ordinal: z2.number().int().nullable(),
|
|
5289
|
+
policy_digest: z2.string().nullable()
|
|
5290
|
+
}).strict();
|
|
5291
|
+
var ProvenanceSchema = z2.enum(["parent_attributed", "env_correlated", "self_reported", "unavailable"]);
|
|
5292
|
+
var HeaderMetaV2Schema = z2.object({
|
|
5293
|
+
schema: z2.literal(2),
|
|
5294
|
+
...headerMetaCore,
|
|
5295
|
+
event_id: z2.string().min(1),
|
|
5296
|
+
process_id: z2.string().min(1),
|
|
5297
|
+
actor_id: z2.string().nullable(),
|
|
5298
|
+
lineage: LineageSchema,
|
|
5299
|
+
input_versions: InputVersionsSchema,
|
|
5300
|
+
provenance: ProvenanceSchema
|
|
5301
|
+
}).strict();
|
|
5302
|
+
var HeaderMetaSchema = z2.discriminatedUnion("schema", [HeaderMetaV1Schema, HeaderMetaV2Schema]);
|
|
5303
|
+
var SubjectSchema = z2.object({
|
|
5304
|
+
issue: z2.number().int().nullable(),
|
|
5305
|
+
pr: z2.number().int().optional(),
|
|
5306
|
+
sha: z2.string().optional(),
|
|
5307
|
+
role: z2.union([RoleSchema, z2.literal("unattributed")]),
|
|
5308
|
+
round: z2.number().int().optional(),
|
|
5309
|
+
objectives_version: z2.string().optional()
|
|
5310
|
+
}).strict();
|
|
5311
|
+
var HeaderSchema = z2.object({
|
|
4886
5312
|
meta: HeaderMetaSchema,
|
|
4887
5313
|
subject: SubjectSchema
|
|
4888
5314
|
}).strict();
|
|
4889
5315
|
var envelopeTail = {
|
|
4890
|
-
duration_ms:
|
|
4891
|
-
payload:
|
|
5316
|
+
duration_ms: z2.number().nonnegative().optional(),
|
|
5317
|
+
payload: z2.object({}).strict()
|
|
4892
5318
|
};
|
|
4893
|
-
var
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
5319
|
+
var ReviewFindingSchema = z2.object({
|
|
5320
|
+
id: z2.string(),
|
|
5321
|
+
severity: z2.string(),
|
|
5322
|
+
state: z2.string().optional(),
|
|
5323
|
+
severity_scale: z2.string().optional(),
|
|
5324
|
+
policy_treatment: z2.enum(["blocking", "non_blocking", "unavailable"]).optional(),
|
|
5325
|
+
confidence: z2.number().min(0).max(1).optional(),
|
|
5326
|
+
confidence_scale: z2.string().optional(),
|
|
5327
|
+
confidence_source: z2.string().optional()
|
|
5328
|
+
}).strict();
|
|
5329
|
+
var DispatchOutcomeSchema = z2.discriminatedUnion("type", [
|
|
5330
|
+
z2.object({ type: z2.literal("pr_opened"), pr: z2.number().int(), head: z2.string() }).strict(),
|
|
5331
|
+
z2.object({
|
|
5332
|
+
type: z2.literal("round_pushed"),
|
|
5333
|
+
pr: z2.number().int(),
|
|
5334
|
+
head: z2.string(),
|
|
5335
|
+
comment_id: z2.number().int()
|
|
4900
5336
|
}).strict(),
|
|
4901
|
-
|
|
4902
|
-
type:
|
|
4903
|
-
verdict:
|
|
4904
|
-
head:
|
|
4905
|
-
comment_id:
|
|
4906
|
-
objectives:
|
|
4907
|
-
findings:
|
|
5337
|
+
z2.object({
|
|
5338
|
+
type: z2.literal("verdict"),
|
|
5339
|
+
verdict: z2.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
|
|
5340
|
+
head: z2.string(),
|
|
5341
|
+
comment_id: z2.number().int(),
|
|
5342
|
+
objectives: z2.array(z2.object({ id: z2.string(), met: z2.boolean() }).strict()),
|
|
5343
|
+
findings: z2.array(ReviewFindingSchema)
|
|
4908
5344
|
}).strict(),
|
|
4909
|
-
|
|
4910
|
-
type:
|
|
4911
|
-
class:
|
|
4912
|
-
comment_id:
|
|
5345
|
+
z2.object({
|
|
5346
|
+
type: z2.literal("escalation"),
|
|
5347
|
+
class: z2.enum(["authority", "strategy", "product"]),
|
|
5348
|
+
comment_id: z2.number().int()
|
|
4913
5349
|
}).strict(),
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
5350
|
+
z2.object({ type: z2.literal("brief"), comment_id: z2.number().int(), hash: z2.string() }).strict(),
|
|
5351
|
+
z2.object({ type: z2.literal("plan"), issues: z2.array(z2.number().int()) }).strict(),
|
|
5352
|
+
z2.object({ type: z2.literal("archive"), provenance_comment_id: z2.number().int() }).strict()
|
|
4917
5353
|
]);
|
|
4918
5354
|
var dispatchShared = {
|
|
4919
5355
|
meta: HeaderMetaSchema,
|
|
4920
5356
|
subject: SubjectSchema,
|
|
4921
|
-
kind:
|
|
5357
|
+
kind: z2.literal("dispatch"),
|
|
4922
5358
|
...envelopeTail,
|
|
4923
5359
|
target_role: RoleSchema,
|
|
4924
|
-
model:
|
|
4925
|
-
round:
|
|
4926
|
-
effect_id:
|
|
5360
|
+
model: z2.string(),
|
|
5361
|
+
round: z2.number().int().optional(),
|
|
5362
|
+
effect_id: z2.string()
|
|
4927
5363
|
};
|
|
4928
|
-
var dispatchUsageField =
|
|
4929
|
-
var DispatchEventSchema =
|
|
4930
|
-
|
|
4931
|
-
|
|
5364
|
+
var dispatchUsageField = z2.object({ input: z2.number().nonnegative(), output: z2.number().nonnegative() }).strict().nullable();
|
|
5365
|
+
var DispatchEventSchema = z2.discriminatedUnion("event", [
|
|
5366
|
+
z2.object({ ...dispatchShared, event: z2.literal("dispatched"), prompt_hash: z2.string() }).strict(),
|
|
5367
|
+
z2.object({
|
|
4932
5368
|
...dispatchShared,
|
|
4933
|
-
event:
|
|
5369
|
+
event: z2.literal("outcome_received"),
|
|
4934
5370
|
outcome: DispatchOutcomeSchema,
|
|
4935
5371
|
usage: dispatchUsageField
|
|
4936
5372
|
}).strict(),
|
|
4937
|
-
|
|
5373
|
+
z2.object({
|
|
4938
5374
|
...dispatchShared,
|
|
4939
|
-
event:
|
|
4940
|
-
reason:
|
|
5375
|
+
event: z2.literal("dispatch_failed"),
|
|
5376
|
+
reason: z2.enum(["timeout", "crash", "refused", "unattributed_write"]),
|
|
4941
5377
|
usage: dispatchUsageField
|
|
4942
5378
|
}).strict()
|
|
4943
5379
|
]);
|
|
4944
5380
|
var loopShared = {
|
|
4945
5381
|
meta: HeaderMetaSchema,
|
|
4946
5382
|
subject: SubjectSchema,
|
|
4947
|
-
kind:
|
|
5383
|
+
kind: z2.literal("dev_review_loop"),
|
|
4948
5384
|
...envelopeTail,
|
|
4949
|
-
loop_id:
|
|
5385
|
+
loop_id: z2.string()
|
|
4950
5386
|
};
|
|
4951
|
-
var DevReviewLoopEventSchema =
|
|
4952
|
-
|
|
5387
|
+
var DevReviewLoopEventSchema = z2.discriminatedUnion("event", [
|
|
5388
|
+
z2.object({
|
|
4953
5389
|
...loopShared,
|
|
4954
|
-
event:
|
|
4955
|
-
task:
|
|
4956
|
-
policy:
|
|
4957
|
-
max_rounds:
|
|
4958
|
-
reviewers:
|
|
4959
|
-
models:
|
|
5390
|
+
event: z2.literal("loop_started"),
|
|
5391
|
+
task: z2.number().int(),
|
|
5392
|
+
policy: z2.object({
|
|
5393
|
+
max_rounds: z2.number().int().nonnegative(),
|
|
5394
|
+
reviewers: z2.array(RoleSchema),
|
|
5395
|
+
models: z2.record(RoleSchema, z2.string())
|
|
4960
5396
|
}).strict()
|
|
4961
5397
|
}).strict(),
|
|
4962
|
-
|
|
4963
|
-
|
|
5398
|
+
z2.object({ ...loopShared, event: z2.literal("round_started"), round: z2.number().int(), base_head: z2.string() }).strict(),
|
|
5399
|
+
z2.object({
|
|
4964
5400
|
...loopShared,
|
|
4965
|
-
event:
|
|
4966
|
-
round:
|
|
4967
|
-
head:
|
|
4968
|
-
green:
|
|
5401
|
+
event: z2.literal("gate_result_read"),
|
|
5402
|
+
round: z2.number().int(),
|
|
5403
|
+
head: z2.string(),
|
|
5404
|
+
green: z2.boolean()
|
|
4969
5405
|
}).strict(),
|
|
4970
|
-
|
|
5406
|
+
z2.object({
|
|
4971
5407
|
...loopShared,
|
|
4972
|
-
event:
|
|
4973
|
-
round:
|
|
4974
|
-
head:
|
|
4975
|
-
all_approve:
|
|
4976
|
-
blockers:
|
|
5408
|
+
event: z2.literal("verdicts_read"),
|
|
5409
|
+
round: z2.number().int(),
|
|
5410
|
+
head: z2.string(),
|
|
5411
|
+
all_approve: z2.boolean(),
|
|
5412
|
+
blockers: z2.number().int().nonnegative()
|
|
4977
5413
|
}).strict(),
|
|
4978
|
-
|
|
5414
|
+
z2.object({
|
|
4979
5415
|
...loopShared,
|
|
4980
|
-
event:
|
|
4981
|
-
round:
|
|
4982
|
-
open:
|
|
4983
|
-
resolved:
|
|
4984
|
-
new:
|
|
4985
|
-
recurring:
|
|
5416
|
+
event: z2.literal("findings_compared"),
|
|
5417
|
+
round: z2.number().int(),
|
|
5418
|
+
open: z2.array(z2.string()),
|
|
5419
|
+
resolved: z2.array(z2.string()),
|
|
5420
|
+
new: z2.array(z2.string()),
|
|
5421
|
+
recurring: z2.array(z2.string())
|
|
4986
5422
|
}).strict(),
|
|
4987
|
-
|
|
5423
|
+
z2.object({
|
|
4988
5424
|
...loopShared,
|
|
4989
|
-
event:
|
|
4990
|
-
round:
|
|
4991
|
-
condition:
|
|
5425
|
+
event: z2.literal("stop_condition_met"),
|
|
5426
|
+
round: z2.number().int(),
|
|
5427
|
+
condition: z2.enum([
|
|
4992
5428
|
"green",
|
|
4993
5429
|
"max_rounds",
|
|
4994
5430
|
"no_progress",
|
|
@@ -4998,42 +5434,49 @@ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
|
|
|
4998
5434
|
"reappearance"
|
|
4999
5435
|
])
|
|
5000
5436
|
}).strict(),
|
|
5001
|
-
|
|
5437
|
+
z2.object({
|
|
5002
5438
|
...loopShared,
|
|
5003
|
-
event:
|
|
5004
|
-
round:
|
|
5005
|
-
reason:
|
|
5439
|
+
event: z2.literal("paused"),
|
|
5440
|
+
round: z2.number().int(),
|
|
5441
|
+
reason: z2.enum(["escalation", "principal_item", "refreeze_needed"])
|
|
5006
5442
|
}).strict(),
|
|
5007
|
-
|
|
5443
|
+
z2.object({
|
|
5008
5444
|
...loopShared,
|
|
5009
|
-
event:
|
|
5010
|
-
round:
|
|
5011
|
-
by:
|
|
5445
|
+
event: z2.literal("resumed"),
|
|
5446
|
+
round: z2.number().int(),
|
|
5447
|
+
by: z2.literal("principal")
|
|
5012
5448
|
}).strict(),
|
|
5013
|
-
|
|
5449
|
+
z2.object({
|
|
5014
5450
|
...loopShared,
|
|
5015
|
-
event:
|
|
5016
|
-
round:
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
files_changed: z.number().int().nonnegative(),
|
|
5020
|
-
insertions: z.number().int().nonnegative(),
|
|
5021
|
-
deletions: z.number().int().nonnegative(),
|
|
5022
|
-
wall_ms: z.number().nonnegative(),
|
|
5023
|
-
outcome: z.enum(["green", "changes_requested", "escalated"])
|
|
5451
|
+
event: z2.literal("unpushed_work_resume"),
|
|
5452
|
+
round: z2.number().int(),
|
|
5453
|
+
branch: z2.string(),
|
|
5454
|
+
detail: z2.string()
|
|
5024
5455
|
}).strict(),
|
|
5025
|
-
|
|
5456
|
+
z2.object({
|
|
5026
5457
|
...loopShared,
|
|
5027
|
-
event:
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5458
|
+
event: z2.literal("round_ended"),
|
|
5459
|
+
round: z2.number().int(),
|
|
5460
|
+
base_head: z2.string(),
|
|
5461
|
+
head: z2.string(),
|
|
5462
|
+
files_changed: z2.number().int().nonnegative(),
|
|
5463
|
+
insertions: z2.number().int().nonnegative(),
|
|
5464
|
+
deletions: z2.number().int().nonnegative(),
|
|
5465
|
+
wall_ms: z2.number().nonnegative(),
|
|
5466
|
+
outcome: z2.enum(["green", "changes_requested", "escalated"])
|
|
5467
|
+
}).strict(),
|
|
5468
|
+
z2.object({
|
|
5469
|
+
...loopShared,
|
|
5470
|
+
event: z2.literal("journal_finalized"),
|
|
5471
|
+
rounds: z2.number().int().nonnegative(),
|
|
5472
|
+
total_wall_ms: z2.number().nonnegative(),
|
|
5473
|
+
time_to_green_ms: z2.number().nonnegative().nullable(),
|
|
5474
|
+
files_changed_total: z2.number().int().nonnegative(),
|
|
5475
|
+
final_head: z2.string(),
|
|
5476
|
+
result: z2.enum(["merged_ready", "stopped"])
|
|
5034
5477
|
}).strict()
|
|
5035
5478
|
]);
|
|
5036
|
-
var ForgeOpSchema =
|
|
5479
|
+
var ForgeOpSchema = z2.enum([
|
|
5037
5480
|
"pr.create",
|
|
5038
5481
|
"pr.comment",
|
|
5039
5482
|
"pr.body.replace",
|
|
@@ -5047,24 +5490,210 @@ var ForgeOpSchema = z.enum([
|
|
|
5047
5490
|
"label.add",
|
|
5048
5491
|
"label.remove"
|
|
5049
5492
|
]);
|
|
5050
|
-
var ForgeWriteTargetSchema =
|
|
5051
|
-
issue:
|
|
5052
|
-
pr:
|
|
5493
|
+
var ForgeWriteTargetSchema = z2.object({
|
|
5494
|
+
issue: z2.number().int().optional(),
|
|
5495
|
+
pr: z2.number().int().optional()
|
|
5053
5496
|
}).strict();
|
|
5054
5497
|
var forgeWriteShared = {
|
|
5055
5498
|
meta: HeaderMetaSchema,
|
|
5056
5499
|
subject: SubjectSchema,
|
|
5057
|
-
kind:
|
|
5500
|
+
kind: z2.literal("forge_write"),
|
|
5058
5501
|
...envelopeTail,
|
|
5059
5502
|
op: ForgeOpSchema,
|
|
5060
5503
|
target: ForgeWriteTargetSchema
|
|
5061
5504
|
};
|
|
5062
|
-
var ForgeWriteEventSchema =
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5505
|
+
var ForgeWriteEventSchema = z2.discriminatedUnion("event", [
|
|
5506
|
+
z2.object({ ...forgeWriteShared, event: z2.literal("validated") }).strict(),
|
|
5507
|
+
z2.object({ ...forgeWriteShared, event: z2.literal("refused"), reason: z2.string() }).strict(),
|
|
5508
|
+
z2.object({ ...forgeWriteShared, event: z2.literal("written"), comment_ids: z2.array(z2.string()) }).strict()
|
|
5509
|
+
]);
|
|
5510
|
+
var GateOutcomeSchema = z2.enum([
|
|
5511
|
+
"pass",
|
|
5512
|
+
"fail",
|
|
5513
|
+
"wait",
|
|
5514
|
+
"skip",
|
|
5515
|
+
"invalid_input",
|
|
5516
|
+
"unavailable_dependency",
|
|
5517
|
+
"timeout",
|
|
5518
|
+
"cancelled"
|
|
5519
|
+
]);
|
|
5520
|
+
var gateShared = {
|
|
5521
|
+
meta: HeaderMetaSchema,
|
|
5522
|
+
subject: SubjectSchema,
|
|
5523
|
+
kind: z2.literal("gate"),
|
|
5524
|
+
...envelopeTail,
|
|
5525
|
+
check: z2.string(),
|
|
5526
|
+
check_version: z2.string().nullable(),
|
|
5527
|
+
policy_version: z2.string().nullable(),
|
|
5528
|
+
input_fingerprint: z2.string().nullable()
|
|
5529
|
+
};
|
|
5530
|
+
var GateEventSchema = z2.discriminatedUnion("event", [
|
|
5531
|
+
z2.object({
|
|
5532
|
+
...gateShared,
|
|
5533
|
+
event: z2.literal("checked"),
|
|
5534
|
+
outcome: GateOutcomeSchema,
|
|
5535
|
+
reason: z2.string().optional()
|
|
5536
|
+
}).strict()
|
|
5537
|
+
]);
|
|
5538
|
+
var OperationResultSchema = z2.enum(["ok", "error", "refused", "timeout", "cancelled", "unavailable"]);
|
|
5539
|
+
var operationShared = {
|
|
5540
|
+
meta: HeaderMetaSchema,
|
|
5541
|
+
subject: SubjectSchema,
|
|
5542
|
+
kind: z2.literal("operation"),
|
|
5543
|
+
...envelopeTail,
|
|
5544
|
+
operation: z2.string(),
|
|
5545
|
+
target: z2.string().nullable()
|
|
5546
|
+
};
|
|
5547
|
+
var OperationEventSchema = z2.discriminatedUnion("event", [
|
|
5548
|
+
z2.object({
|
|
5549
|
+
...operationShared,
|
|
5550
|
+
event: z2.literal("completed"),
|
|
5551
|
+
result: OperationResultSchema,
|
|
5552
|
+
error_class: z2.string().nullable()
|
|
5553
|
+
}).strict()
|
|
5554
|
+
]);
|
|
5555
|
+
var UsageUnitsSchema = z2.object({
|
|
5556
|
+
input: z2.number().nonnegative().nullable(),
|
|
5557
|
+
output: z2.number().nonnegative().nullable(),
|
|
5558
|
+
cache: z2.number().nonnegative().nullable()
|
|
5559
|
+
}).strict();
|
|
5560
|
+
var usageShared = {
|
|
5561
|
+
meta: HeaderMetaSchema,
|
|
5562
|
+
subject: SubjectSchema,
|
|
5563
|
+
kind: z2.literal("usage"),
|
|
5564
|
+
...envelopeTail,
|
|
5565
|
+
model: z2.string().nullable(),
|
|
5566
|
+
source: z2.string(),
|
|
5567
|
+
semantics: z2.enum(["cumulative", "delta"])
|
|
5568
|
+
};
|
|
5569
|
+
var UsageEventSchema = z2.discriminatedUnion("event", [
|
|
5570
|
+
z2.object({
|
|
5571
|
+
...usageShared,
|
|
5572
|
+
event: z2.literal("observed"),
|
|
5573
|
+
units: UsageUnitsSchema,
|
|
5574
|
+
unknown_reason: z2.string().nullable()
|
|
5575
|
+
}).strict()
|
|
5576
|
+
]);
|
|
5577
|
+
var RoleAttemptOutcomeSchema = z2.enum([
|
|
5578
|
+
"completed",
|
|
5579
|
+
"incomplete",
|
|
5580
|
+
"infrastructure_failed",
|
|
5581
|
+
"cancelled",
|
|
5582
|
+
"timed_out",
|
|
5583
|
+
"capability_refused"
|
|
5584
|
+
]);
|
|
5585
|
+
var roleAttemptShared = {
|
|
5586
|
+
meta: HeaderMetaSchema,
|
|
5587
|
+
subject: SubjectSchema,
|
|
5588
|
+
kind: z2.literal("role_attempt"),
|
|
5589
|
+
...envelopeTail,
|
|
5590
|
+
actor: z2.string().nullable(),
|
|
5591
|
+
attempt: z2.number().int().nullable()
|
|
5592
|
+
};
|
|
5593
|
+
var RoleAttemptEventSchema = z2.discriminatedUnion("event", [
|
|
5594
|
+
z2.object({
|
|
5595
|
+
...roleAttemptShared,
|
|
5596
|
+
event: z2.literal("attempted"),
|
|
5597
|
+
outcome: RoleAttemptOutcomeSchema,
|
|
5598
|
+
usage: dispatchUsageField
|
|
5599
|
+
}).strict()
|
|
5600
|
+
]);
|
|
5601
|
+
var handoffShared = {
|
|
5602
|
+
meta: HeaderMetaSchema,
|
|
5603
|
+
subject: SubjectSchema,
|
|
5604
|
+
kind: z2.literal("handoff"),
|
|
5605
|
+
...envelopeTail,
|
|
5606
|
+
class: z2.enum(["authority", "strategy", "product"]),
|
|
5607
|
+
reason: z2.string()
|
|
5608
|
+
};
|
|
5609
|
+
var HandoffEventSchema = z2.discriminatedUnion("event", [
|
|
5610
|
+
z2.object({
|
|
5611
|
+
...handoffShared,
|
|
5612
|
+
event: z2.literal("raised"),
|
|
5613
|
+
requested_decision: z2.string().nullable()
|
|
5614
|
+
}).strict(),
|
|
5615
|
+
z2.object({
|
|
5616
|
+
...handoffShared,
|
|
5617
|
+
event: z2.literal("resolved"),
|
|
5618
|
+
resolution: z2.string().nullable(),
|
|
5619
|
+
resolved_by: z2.string().nullable()
|
|
5620
|
+
}).strict()
|
|
5621
|
+
]);
|
|
5622
|
+
var EffectTargetSchema = z2.object({
|
|
5623
|
+
kind: z2.string(),
|
|
5624
|
+
ref: z2.string()
|
|
5625
|
+
}).strict();
|
|
5626
|
+
var effectShared = {
|
|
5627
|
+
meta: HeaderMetaSchema,
|
|
5628
|
+
subject: SubjectSchema,
|
|
5629
|
+
kind: z2.literal("effect"),
|
|
5630
|
+
...envelopeTail,
|
|
5631
|
+
effect_id: z2.string(),
|
|
5632
|
+
target: EffectTargetSchema
|
|
5633
|
+
};
|
|
5634
|
+
var EffectEventSchema = z2.discriminatedUnion("event", [
|
|
5635
|
+
z2.object({ ...effectShared, event: z2.literal("attempted") }).strict(),
|
|
5636
|
+
z2.object({ ...effectShared, event: z2.literal("observed"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict(),
|
|
5637
|
+
z2.object({ ...effectShared, event: z2.literal("verified"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict()
|
|
5638
|
+
]);
|
|
5639
|
+
var LogEventSchema = z2.union([
|
|
5640
|
+
DispatchEventSchema,
|
|
5641
|
+
DevReviewLoopEventSchema,
|
|
5642
|
+
ForgeWriteEventSchema,
|
|
5643
|
+
GateEventSchema,
|
|
5644
|
+
OperationEventSchema,
|
|
5645
|
+
UsageEventSchema,
|
|
5646
|
+
RoleAttemptEventSchema,
|
|
5647
|
+
HandoffEventSchema,
|
|
5648
|
+
EffectEventSchema
|
|
5066
5649
|
]);
|
|
5067
|
-
|
|
5650
|
+
// ../../packages/aeg-core/src/dev-review-loop/journal-reconstruction.ts
|
|
5651
|
+
var STOPPED_CONDITIONS = new Set(["confidence", "reappearance", "no_progress", "max_rounds"]);
|
|
5652
|
+
// ../../packages/aeg-core/src/control-store/records.ts
|
|
5653
|
+
import { z as z3 } from "zod";
|
|
5654
|
+
var isoTimestamp = z3.string().min(1);
|
|
5655
|
+
var taskId = z3.number().int().positive();
|
|
5656
|
+
var epochNumber = z3.number().int().nonnegative();
|
|
5657
|
+
var RunRecordSchema = z3.object({
|
|
5658
|
+
version: z3.literal(1),
|
|
5659
|
+
kind: z3.literal("run"),
|
|
5660
|
+
task: taskId,
|
|
5661
|
+
runId: z3.string().min(1),
|
|
5662
|
+
pid: z3.number().int().positive(),
|
|
5663
|
+
host: z3.string().min(1),
|
|
5664
|
+
startedAt: isoTimestamp
|
|
5665
|
+
}).strict();
|
|
5666
|
+
var InputRecordSchema = z3.object({
|
|
5667
|
+
version: z3.literal(1),
|
|
5668
|
+
kind: z3.literal("input"),
|
|
5669
|
+
task: taskId,
|
|
5670
|
+
runId: z3.string().min(1),
|
|
5671
|
+
source: z3.union([z3.literal("fresh"), z3.literal("resume")]),
|
|
5672
|
+
pr: z3.number().int().positive().nullable(),
|
|
5673
|
+
round: z3.number().int().nonnegative(),
|
|
5674
|
+
recordedAt: isoTimestamp
|
|
5675
|
+
}).strict();
|
|
5676
|
+
var OwnershipRecordSchema = z3.object({
|
|
5677
|
+
version: z3.literal(1),
|
|
5678
|
+
kind: z3.literal("ownership"),
|
|
5679
|
+
task: taskId,
|
|
5680
|
+
epoch: epochNumber,
|
|
5681
|
+
ownerId: z3.string().min(1),
|
|
5682
|
+
pid: z3.number().int().positive(),
|
|
5683
|
+
host: z3.string().min(1),
|
|
5684
|
+
acquiredAt: isoTimestamp
|
|
5685
|
+
}).strict();
|
|
5686
|
+
var TransitionRecordSchema = z3.object({
|
|
5687
|
+
version: z3.literal(1),
|
|
5688
|
+
kind: z3.literal("transition"),
|
|
5689
|
+
task: taskId,
|
|
5690
|
+
epoch: epochNumber,
|
|
5691
|
+
seq: z3.number().int().nonnegative(),
|
|
5692
|
+
from: z3.string().min(1),
|
|
5693
|
+
to: z3.string().min(1),
|
|
5694
|
+
detail: z3.string().optional(),
|
|
5695
|
+
at: isoTimestamp
|
|
5696
|
+
}).strict();
|
|
5068
5697
|
// ../../packages/sources/src/commands.ts
|
|
5069
5698
|
var COMMANDS = [
|
|
5070
5699
|
{
|
|
@@ -5223,6 +5852,17 @@ var COMMANDS = [
|
|
|
5223
5852
|
],
|
|
5224
5853
|
status: "shipped"
|
|
5225
5854
|
},
|
|
5855
|
+
{
|
|
5856
|
+
name: "task status",
|
|
5857
|
+
description: "Every open task with a frozen brief, its pull request, and whether its loop is running, paused, or published",
|
|
5858
|
+
flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
|
|
5859
|
+
details: [
|
|
5860
|
+
"Read-only: one `gh issue list` for every open task Issue across every tranche (title/label resolved through the same `resolveTaskIssueRef` `list-tasks.ts` already uses), the open pull request per branch, and the driver pid record / pause record / publish effect markers under `<outboxRoot>/dev-review-loop/<task>/` — never a `ps` scan, never a re-parse of posted verdict comments to decide `published`.",
|
|
5861
|
+
"`running` names the driver's pid (`review-validity-v1` task 7's pid record); `paused` names the reason from the pause record; `published` means the newest round's reviewer and security verdict effect markers both read `posted`; `no driver` is the fallback when none of the above holds.",
|
|
5862
|
+
"`vinaya task status <tranche> <n>` narrows to one task and adds the last round's held or published verdict lines (`round-<n>-reviewer.md`/`round-<n>-security.md`, whichever their outbox carries) plus the exact `vinaya dev-review-loop --resume <pr>` command when paused."
|
|
5863
|
+
],
|
|
5864
|
+
status: "shipped"
|
|
5865
|
+
},
|
|
5226
5866
|
{
|
|
5227
5867
|
name: "pr create",
|
|
5228
5868
|
description: "Open a pull request after full brief-schema validation",
|
|
@@ -5732,10 +6372,10 @@ function createForgeSource(config) {
|
|
|
5732
6372
|
};
|
|
5733
6373
|
}
|
|
5734
6374
|
// ../../packages/sources/src/select-source.ts
|
|
5735
|
-
import { z as
|
|
5736
|
-
var StateSourceConfigSchema =
|
|
5737
|
-
|
|
5738
|
-
|
|
6375
|
+
import { z as z4 } from "zod";
|
|
6376
|
+
var StateSourceConfigSchema = z4.discriminatedUnion("kind", [
|
|
6377
|
+
z4.object({ kind: z4.literal("forge"), owner: z4.string(), repo: z4.string() }),
|
|
6378
|
+
z4.object({ kind: z4.literal("file"), root: z4.string().optional() })
|
|
5739
6379
|
]);
|
|
5740
6380
|
// src/checks/contract.ts
|
|
5741
6381
|
var CHECK_SCHEMA_VERSION = 1;
|
|
@@ -5749,27 +6389,29 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
5749
6389
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
|
|
5750
6390
|
import { homedir } from "node:os";
|
|
5751
6391
|
import { dirname, join, resolve } from "node:path";
|
|
5752
|
-
import { z as
|
|
6392
|
+
import { z as z5 } from "zod";
|
|
5753
6393
|
|
|
5754
6394
|
// src/lib/agent-vendors.ts
|
|
5755
6395
|
var AGENT_VENDORS = ["skills", "claude", "gemini"];
|
|
5756
6396
|
|
|
5757
6397
|
// src/lib/config.ts
|
|
5758
|
-
var EnvEntrySchema =
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
6398
|
+
var EnvEntrySchema = z5.union([
|
|
6399
|
+
z5.literal(true),
|
|
6400
|
+
z5.object({ optional: z5.literal(true) }),
|
|
6401
|
+
z5.object({ anyOf: z5.array(z5.string()).min(2) }),
|
|
6402
|
+
z5.string()
|
|
5763
6403
|
]);
|
|
5764
|
-
var CheckEntrySchema =
|
|
5765
|
-
run:
|
|
5766
|
-
scope:
|
|
5767
|
-
include:
|
|
5768
|
-
args:
|
|
5769
|
-
timeoutMs:
|
|
5770
|
-
env:
|
|
5771
|
-
requiresOpenPr:
|
|
5772
|
-
ownWorkflow:
|
|
6404
|
+
var CheckEntrySchema = z5.object({
|
|
6405
|
+
run: z5.string(),
|
|
6406
|
+
scope: z5.enum(["diff", "full"]),
|
|
6407
|
+
include: z5.array(z5.string()).optional(),
|
|
6408
|
+
args: z5.array(z5.string()).optional(),
|
|
6409
|
+
timeoutMs: z5.number().optional(),
|
|
6410
|
+
env: z5.record(z5.string(), EnvEntrySchema).optional(),
|
|
6411
|
+
requiresOpenPr: z5.boolean().optional(),
|
|
6412
|
+
ownWorkflow: z5.boolean().optional(),
|
|
6413
|
+
principalOwed: z5.literal(true).optional(),
|
|
6414
|
+
validates: z5.enum(["body", "issue"]).optional()
|
|
5773
6415
|
}).superRefine((entry2, ctx) => {
|
|
5774
6416
|
if (!entry2.env)
|
|
5775
6417
|
return;
|
|
@@ -5779,25 +6421,26 @@ var CheckEntrySchema = z3.object({
|
|
|
5779
6421
|
const members = value.anyOf;
|
|
5780
6422
|
if (new Set(members).size !== members.length) {
|
|
5781
6423
|
ctx.addIssue({
|
|
5782
|
-
code:
|
|
6424
|
+
code: z5.ZodIssueCode.custom,
|
|
5783
6425
|
message: `env.${key}.anyOf has duplicate members`,
|
|
5784
6426
|
path: ["env", key, "anyOf"]
|
|
5785
6427
|
});
|
|
5786
6428
|
}
|
|
5787
6429
|
if (!members.includes(key)) {
|
|
5788
6430
|
ctx.addIssue({
|
|
5789
|
-
code:
|
|
6431
|
+
code: z5.ZodIssueCode.custom,
|
|
5790
6432
|
message: `env.${key}.anyOf must include "${key}" itself as a member`,
|
|
5791
6433
|
path: ["env", key, "anyOf"]
|
|
5792
6434
|
});
|
|
5793
6435
|
}
|
|
5794
6436
|
}
|
|
5795
6437
|
});
|
|
5796
|
-
var RoleEntrySchema =
|
|
5797
|
-
contract:
|
|
6438
|
+
var RoleEntrySchema = z5.object({
|
|
6439
|
+
contract: z5.string().refine((p) => p.includes("/"), {
|
|
5798
6440
|
message: 'must be a path (contain at least one "/"), not a bare filename'
|
|
5799
6441
|
})
|
|
5800
6442
|
});
|
|
6443
|
+
var MAX_REPORT_COMMAND_TIMEOUT_MS = 3600000;
|
|
5801
6444
|
var BRIEF_BUILTINS = [
|
|
5802
6445
|
"tier",
|
|
5803
6446
|
"testPlan",
|
|
@@ -5817,17 +6460,17 @@ var BRIEF_BUILTINS = [
|
|
|
5817
6460
|
"briefSections",
|
|
5818
6461
|
"milestoneShape"
|
|
5819
6462
|
];
|
|
5820
|
-
var BriefSectionSchema =
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
6463
|
+
var BriefSectionSchema = z5.union([
|
|
6464
|
+
z5.object({ builtin: z5.enum(BRIEF_BUILTINS) }),
|
|
6465
|
+
z5.object({ heading: z5.string().min(1), name: z5.string().optional() }),
|
|
6466
|
+
z5.object({ field: z5.string().min(1), name: z5.string().optional() }),
|
|
6467
|
+
z5.object({ phrase: z5.string().min(1), name: z5.string().optional() })
|
|
5825
6468
|
]);
|
|
5826
|
-
var BriefSchemaSchema =
|
|
5827
|
-
pr:
|
|
5828
|
-
issue:
|
|
5829
|
-
milestone:
|
|
5830
|
-
ack:
|
|
6469
|
+
var BriefSchemaSchema = z5.object({
|
|
6470
|
+
pr: z5.object({ sections: z5.array(BriefSectionSchema) }).optional(),
|
|
6471
|
+
issue: z5.object({ sections: z5.array(BriefSectionSchema) }).optional(),
|
|
6472
|
+
milestone: z5.object({ sections: z5.array(BriefSectionSchema) }).optional(),
|
|
6473
|
+
ack: z5.array(z5.enum(BRIEF_BUILTINS)).optional()
|
|
5831
6474
|
});
|
|
5832
6475
|
function isSafeRepoRelPath(p) {
|
|
5833
6476
|
if (p.length === 0)
|
|
@@ -5836,7 +6479,7 @@ function isSafeRepoRelPath(p) {
|
|
|
5836
6479
|
return false;
|
|
5837
6480
|
return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
|
|
5838
6481
|
}
|
|
5839
|
-
var SafeRepoRelPath =
|
|
6482
|
+
var SafeRepoRelPath = z5.string().refine(isSafeRepoRelPath, {
|
|
5840
6483
|
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
5841
6484
|
});
|
|
5842
6485
|
var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
|
|
@@ -5846,22 +6489,22 @@ function isCanonicalHookBlockPath(p) {
|
|
|
5846
6489
|
var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
|
|
5847
6490
|
message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
|
|
5848
6491
|
});
|
|
5849
|
-
var ManagedBlockRecordSchema =
|
|
6492
|
+
var ManagedBlockRecordSchema = z5.object({
|
|
5850
6493
|
path: ManagedHookBlockPath,
|
|
5851
|
-
marker:
|
|
5852
|
-
comment:
|
|
6494
|
+
marker: z5.string(),
|
|
6495
|
+
comment: z5.enum(["hash", "html"])
|
|
5853
6496
|
});
|
|
5854
|
-
var ManagedManifestSchema =
|
|
5855
|
-
version:
|
|
5856
|
-
files:
|
|
5857
|
-
blocks:
|
|
5858
|
-
labels:
|
|
5859
|
-
agents:
|
|
6497
|
+
var ManagedManifestSchema = z5.object({
|
|
6498
|
+
version: z5.number().int().positive(),
|
|
6499
|
+
files: z5.array(SafeRepoRelPath),
|
|
6500
|
+
blocks: z5.array(ManagedBlockRecordSchema),
|
|
6501
|
+
labels: z5.array(z5.string()),
|
|
6502
|
+
agents: z5.array(z5.enum(AGENT_VENDORS)).optional()
|
|
5860
6503
|
});
|
|
5861
|
-
var ProjectEntrySchema =
|
|
5862
|
-
name:
|
|
5863
|
-
description:
|
|
5864
|
-
path:
|
|
6504
|
+
var ProjectEntrySchema = z5.object({
|
|
6505
|
+
name: z5.string().min(1),
|
|
6506
|
+
description: z5.string().min(1).optional(),
|
|
6507
|
+
path: z5.string().min(1).optional()
|
|
5865
6508
|
});
|
|
5866
6509
|
function parseTokensCollectDeclaration(value) {
|
|
5867
6510
|
const m = value.trim().match(/^(\S+)\s+(\S+)$/);
|
|
@@ -5872,34 +6515,55 @@ function parseTokensCollectDeclaration(value) {
|
|
|
5872
6515
|
return null;
|
|
5873
6516
|
return { interpreter, script };
|
|
5874
6517
|
}
|
|
5875
|
-
var VinayaConfigSchema =
|
|
5876
|
-
rings:
|
|
5877
|
-
ring1_forgeWriteInterception:
|
|
5878
|
-
ring2_asyncAudits:
|
|
6518
|
+
var VinayaConfigSchema = z5.object({
|
|
6519
|
+
rings: z5.object({
|
|
6520
|
+
ring1_forgeWriteInterception: z5.boolean(),
|
|
6521
|
+
ring2_asyncAudits: z5.boolean()
|
|
5879
6522
|
}).optional(),
|
|
5880
|
-
checks:
|
|
5881
|
-
roles:
|
|
6523
|
+
checks: z5.record(z5.string(), CheckEntrySchema).optional(),
|
|
6524
|
+
roles: z5.record(z5.string(), RoleEntrySchema).optional(),
|
|
5882
6525
|
briefSchema: BriefSchemaSchema.optional(),
|
|
5883
6526
|
managed: ManagedManifestSchema.optional(),
|
|
5884
|
-
principals:
|
|
5885
|
-
releaseActor:
|
|
5886
|
-
ci:
|
|
5887
|
-
tokens:
|
|
5888
|
-
collect:
|
|
6527
|
+
principals: z5.array(z5.string()).min(1).optional(),
|
|
6528
|
+
releaseActor: z5.string().min(1).optional(),
|
|
6529
|
+
ci: z5.object({ setup: z5.string().min(1) }).optional(),
|
|
6530
|
+
tokens: z5.object({
|
|
6531
|
+
collect: z5.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
|
|
5889
6532
|
message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
|
|
5890
6533
|
})
|
|
5891
6534
|
}).optional(),
|
|
5892
|
-
blastRadius:
|
|
5893
|
-
proseGates:
|
|
5894
|
-
doctrineRoot:
|
|
5895
|
-
readerFacingPrefix:
|
|
5896
|
-
readerFacingSuffix:
|
|
5897
|
-
legacySlugDir:
|
|
6535
|
+
blastRadius: z5.object({ extraDomains: z5.array(z5.string()).optional() }).optional(),
|
|
6536
|
+
proseGates: z5.object({
|
|
6537
|
+
doctrineRoot: z5.string().min(1).optional(),
|
|
6538
|
+
readerFacingPrefix: z5.string().min(1).optional(),
|
|
6539
|
+
readerFacingSuffix: z5.string().min(1).optional(),
|
|
6540
|
+
legacySlugDir: z5.string().min(1).optional()
|
|
6541
|
+
}).optional(),
|
|
6542
|
+
projects: z5.array(ProjectEntrySchema).optional(),
|
|
6543
|
+
dispatch: z5.object({
|
|
6544
|
+
timeoutMs: z5.number().int().positive().optional(),
|
|
6545
|
+
killGraceMs: z5.number().int().positive().optional(),
|
|
6546
|
+
agent: z5.enum(["claude", "codex", "gemini"]).optional()
|
|
5898
6547
|
}).optional(),
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
6548
|
+
reviewPolicy: z5.object({
|
|
6549
|
+
codeReviewThreshold: z5.string().min(1).optional(),
|
|
6550
|
+
securityThreshold: z5.string().min(1).optional(),
|
|
6551
|
+
maxRounds: z5.number().optional()
|
|
6552
|
+
}).optional(),
|
|
6553
|
+
prePush: z5.object({
|
|
6554
|
+
alwaysRun: z5.array(z5.string()).optional()
|
|
6555
|
+
}).optional(),
|
|
6556
|
+
report: z5.object({
|
|
6557
|
+
commandTimeoutMs: z5.number().int().positive().optional()
|
|
6558
|
+
}).superRefine((report, ctx) => {
|
|
6559
|
+
const commandTimeoutMs = report.commandTimeoutMs;
|
|
6560
|
+
if (commandTimeoutMs !== undefined && commandTimeoutMs > MAX_REPORT_COMMAND_TIMEOUT_MS) {
|
|
6561
|
+
ctx.addIssue({
|
|
6562
|
+
code: z5.ZodIssueCode.custom,
|
|
6563
|
+
path: ["commandTimeoutMs"],
|
|
6564
|
+
message: "must be at most 3600000 (1 hour)"
|
|
6565
|
+
});
|
|
6566
|
+
}
|
|
5903
6567
|
}).optional()
|
|
5904
6568
|
});
|
|
5905
6569
|
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
@@ -5974,6 +6638,28 @@ function resolvePrincipalAllowlist(config) {
|
|
|
5974
6638
|
function resolveReleaseActor(config) {
|
|
5975
6639
|
return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
|
|
5976
6640
|
}
|
|
6641
|
+
function resolveReviewPolicy(config) {
|
|
6642
|
+
const raw = config?.reviewPolicy;
|
|
6643
|
+
if (!raw)
|
|
6644
|
+
return DEFAULT_REVIEW_POLICY;
|
|
6645
|
+
const codeReviewThreshold = raw.codeReviewThreshold ?? DEFAULT_REVIEW_POLICY.codeReviewThreshold;
|
|
6646
|
+
if (!isKnownSeverity(CODE_REVIEW_SEVERITY_ORDER, codeReviewThreshold)) {
|
|
6647
|
+
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.`);
|
|
6648
|
+
}
|
|
6649
|
+
const securityThreshold = raw.securityThreshold ?? DEFAULT_REVIEW_POLICY.securityThreshold;
|
|
6650
|
+
if (!isKnownSeverity(SECURITY_SEVERITY_ORDER, securityThreshold)) {
|
|
6651
|
+
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.`);
|
|
6652
|
+
}
|
|
6653
|
+
const maxRounds = raw.maxRounds ?? DEFAULT_REVIEW_POLICY.maxRounds;
|
|
6654
|
+
if (!Number.isInteger(maxRounds) || maxRounds < 1) {
|
|
6655
|
+
throw new Error(`vinaya.config.json: reviewPolicy.maxRounds "${maxRounds}" is not a positive integer — fix the config, this never falls back to a default.`);
|
|
6656
|
+
}
|
|
6657
|
+
return {
|
|
6658
|
+
codeReviewThreshold,
|
|
6659
|
+
securityThreshold,
|
|
6660
|
+
maxRounds
|
|
6661
|
+
};
|
|
6662
|
+
}
|
|
5977
6663
|
function trustAnchorRepo() {
|
|
5978
6664
|
const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
5979
6665
|
const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
|
|
@@ -6119,38 +6805,71 @@ function taskToEntry(entries, slug) {
|
|
|
6119
6805
|
m.set(`${slug}/${e.task.id}`, e);
|
|
6120
6806
|
return m;
|
|
6121
6807
|
}
|
|
6122
|
-
function recoveryPromptFor(
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
case "A1":
|
|
6808
|
+
function recoveryPromptFor(code) {
|
|
6809
|
+
switch (code) {
|
|
6810
|
+
case "d1-self-dependency":
|
|
6811
|
+
return "This is an INTERNAL parser-bug report, not a real coherence failure — a task cannot depend on itself, so there is nothing here to close or resolve. Do NOT work around it. Report it upstream, and re-run once the rationale text is corrected or the parser fix ships.";
|
|
6812
|
+
case "closed-without-merge":
|
|
6128
6813
|
return "The task's Issue is closed but its closing PR is not merged. Verify the PR actually merged (or reopen the Issue if it was closed in error), then re-run `vinaya check coherence`.";
|
|
6129
|
-
case "
|
|
6814
|
+
case "archived-without-provenance":
|
|
6815
|
+
return "The closing PR merged but carries no `### AEG provenance` comment. Ask the Archivist to post it, then re-run `vinaya check coherence`.";
|
|
6816
|
+
case "auto-close-misfire":
|
|
6130
6817
|
return "The closing PR merged but the Issue is still open (a GitHub auto-close misfire). Manually close the Issue, then re-run `vinaya check coherence`.";
|
|
6131
|
-
case "
|
|
6818
|
+
case "phantom-issue-ref":
|
|
6132
6819
|
return "The topology names an Issue number that doesn't resolve on the forge. Fix the Issue number in the topology, or ask the Planner to re-cut it, then re-run `vinaya check coherence`.";
|
|
6133
|
-
case "
|
|
6820
|
+
case "orphan-task":
|
|
6134
6821
|
return "An open Issue under this tranche's label is missing from the topology. Add its row to the tranche's task list, then re-run `vinaya check coherence`.";
|
|
6135
|
-
case "
|
|
6822
|
+
case "tbd-in-active-tranche":
|
|
6136
6823
|
return "A task in this active tranche has no Issue (#TBD). Ask the Planner to cut the Issue, then re-run `vinaya check coherence`.";
|
|
6137
|
-
case "
|
|
6824
|
+
case "dispatched-on-unmet-deps":
|
|
6138
6825
|
return "This task has an open PR but a declared dependency isn't closed. Close the dependency first (or verify it truly is), then re-run `vinaya check coherence`.";
|
|
6139
|
-
case "
|
|
6826
|
+
case "missing-rationale-field":
|
|
6140
6827
|
return "The Issue fails the rationale gate. Ask the Planner to complete the eight-field rationale on the Issue body, then re-run `vinaya check coherence`.";
|
|
6828
|
+
case "surface-excludes-bound-doc":
|
|
6829
|
+
return "The Issue's Surface `out:` list excludes a doc-owners-bound document its `in:` list otherwise covers. Fix the Surface split, or update the bound doc in the same PR, then re-run `vinaya check coherence`.";
|
|
6830
|
+
case "surface-overlap":
|
|
6831
|
+
return "This Issue overlaps another open task Issue sharing the same Milestone. Resolve the overlap (split scope or serialize the tasks), then re-run `vinaya check coherence`.";
|
|
6832
|
+
case "archive-recommended":
|
|
6833
|
+
return "This active tranche has no open task-Issues left (advisory). Archive it to `completed/`, or confirm it should stay open, then re-run `vinaya check coherence`.";
|
|
6834
|
+
case "premature-archive":
|
|
6835
|
+
return "This archived tranche still has an open task-Issue (advisory). Investigate whether the archive was premature, then re-run `vinaya check coherence`.";
|
|
6836
|
+
case "milestone-drift":
|
|
6837
|
+
return "This Issue's GitHub-native milestone doesn't match its `vinaya/tranche:` label (advisory, cosmetic). Re-attach the Issue to the correct Milestone, then re-run `vinaya check coherence`.";
|
|
6838
|
+
case "tranche-not-archived":
|
|
6839
|
+
return "Every task Issue in this tranche is closed but it was never archived. Ask the Tranche Archivist to run, then re-run `vinaya check coherence`.";
|
|
6840
|
+
case "doc-owners-dangling-pointer":
|
|
6841
|
+
return "A `.vinaya/doc-owners` entry points at a file that no longer exists in-repo. Fix or remove the dangling entry, then re-run `vinaya check coherence`.";
|
|
6842
|
+
case "doc-owners-duplicate-glob":
|
|
6843
|
+
return "A `.vinaya/doc-owners` glob is registered more than once. Remove the duplicate entry, then re-run `vinaya check coherence`.";
|
|
6844
|
+
case "forge-read-unavailable":
|
|
6845
|
+
return "A forge read failed while assembling coherence facts (severity:infra) — this is an outage, not a drift finding. Confirm `gh auth status` passes and the forge is reachable, then re-run `vinaya check coherence`.";
|
|
6141
6846
|
default:
|
|
6142
|
-
return
|
|
6847
|
+
return assertNeverCoherenceCode(code);
|
|
6143
6848
|
}
|
|
6144
6849
|
}
|
|
6850
|
+
function assertNeverCoherenceCode(x) {
|
|
6851
|
+
throw new Error(`Unhandled CoherenceFailureCode: ${JSON.stringify(x)}`);
|
|
6852
|
+
}
|
|
6145
6853
|
function emitFailure(result) {
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6854
|
+
if (result.failures.length === 0) {
|
|
6855
|
+
emitCheckError({
|
|
6856
|
+
schema: CHECK_SCHEMA_VERSION,
|
|
6857
|
+
check: CHECK_NAME,
|
|
6858
|
+
severity: "error",
|
|
6859
|
+
message: `${result.check}: ${result.note ?? "see check output"}`,
|
|
6860
|
+
agent_recovery_prompt: "Read the named coherence failure and resolve the underlying forge/topology drift it names, then re-run `vinaya check coherence`."
|
|
6861
|
+
});
|
|
6862
|
+
return;
|
|
6863
|
+
}
|
|
6864
|
+
for (const failure of result.failures) {
|
|
6865
|
+
emitCheckError({
|
|
6866
|
+
schema: CHECK_SCHEMA_VERSION,
|
|
6867
|
+
check: CHECK_NAME,
|
|
6868
|
+
severity: "error",
|
|
6869
|
+
message: `${result.check}: ${failure.reason}`,
|
|
6870
|
+
agent_recovery_prompt: recoveryPromptFor(failure.code)
|
|
6871
|
+
});
|
|
6872
|
+
}
|
|
6154
6873
|
}
|
|
6155
6874
|
async function main() {
|
|
6156
6875
|
const slug = currentTrancheSlug();
|