@attalabs/vinaya 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/aeg-root/enforcement.md +5 -5
- package/aeg-root/milestone-model.md +2 -0
- package/aeg-root/process.md +1 -1
- package/aeg-root/roles/developer.md +23 -17
- package/aeg-root/roles/planner.md +4 -0
- package/aeg-root/roles/principal.md +8 -2
- package/aeg-root/roles/reviewer.md +9 -5
- package/aeg-root/roles/security.md +9 -5
- package/dist/checks/bin/check-body-bare-digits.js +1038 -257
- package/dist/checks/bin/check-branch-topology.js +963 -199
- package/dist/checks/bin/check-brief-shape.js +1276 -203
- package/dist/checks/bin/check-changeset-coverage.js +1677 -281
- package/dist/checks/bin/check-closes-n.js +967 -203
- package/dist/checks/bin/check-coherence.js +1127 -284
- package/dist/checks/bin/check-dead-branch-push.js +910 -193
- package/dist/checks/bin/check-dispatch-readiness.js +1189 -295
- package/dist/checks/bin/check-doc-coverage-push.js +1673 -277
- package/dist/checks/bin/check-doc-coverage.js +1675 -279
- package/dist/checks/bin/check-doctrine-no-procedures.js +1020 -257
- package/dist/checks/bin/check-doctrine-portability.js +1672 -276
- package/dist/checks/bin/check-evidence-fresh.js +2526 -322
- package/dist/checks/bin/check-exec-bits.js +1670 -274
- package/dist/checks/bin/check-first-push-dispatch.js +1075 -265
- package/dist/checks/bin/check-issue-assignment.js +965 -201
- 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 +910 -193
- package/dist/checks/bin/check-no-disk-state.js +910 -193
- package/dist/checks/bin/check-pr-premise-reassert.js +1020 -257
- package/dist/checks/bin/check-pr-report-density.js +910 -193
- package/dist/checks/bin/check-quoted-command.js +1651 -274
- package/dist/checks/bin/check-reader-resolvable-prose.js +1655 -278
- package/dist/checks/bin/check-registry-gates.js +997 -193
- package/dist/checks/bin/check-retired-vocabulary.js +1649 -272
- package/dist/checks/bin/check-review-gate.js +1163 -316
- package/dist/checks/bin/check-single-plan-pr.js +910 -193
- package/dist/checks/bin/check-surface-scope.js +973 -201
- package/dist/checks/bin/check-test-plan.js +939 -206
- package/dist/checks/bin/check-token-collection-wired.js +910 -193
- package/dist/checks/bin/check-token-report.js +910 -193
- package/dist/checks/bin/check-workspace-escape.js +1668 -272
- package/dist/index.js +10602 -5352
- 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
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// src/checks/bin/check-review-gate.ts
|
|
6
|
-
import { execFileSync as
|
|
6
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
7
7
|
// ../../packages/aeg-core/src/gate-audience.ts
|
|
8
8
|
var GATE_AUDIENCE = {
|
|
9
9
|
"check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
|
|
@@ -303,7 +303,7 @@ function issueListByLabelArgs(owner, repo, label) {
|
|
|
303
303
|
"--state",
|
|
304
304
|
"all",
|
|
305
305
|
"--json",
|
|
306
|
-
"number,title,body,state,labels,milestone",
|
|
306
|
+
"number,title,body,state,labels,milestone,stateReason",
|
|
307
307
|
"--limit",
|
|
308
308
|
"200"
|
|
309
309
|
];
|
|
@@ -649,14 +649,19 @@ function resolveTaskIssueRef(title, labels) {
|
|
|
649
649
|
var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
|
|
650
650
|
var NEXT_HEADING = /^#{1,6}\s+\S/m;
|
|
651
651
|
var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
652
|
-
function
|
|
653
|
-
const text = stripCode(description, { inlineSpans: "keep" });
|
|
652
|
+
function intentsSection(text) {
|
|
654
653
|
const start = text.match(INTENTS_HEADING);
|
|
655
654
|
if (!start || start.index === undefined)
|
|
656
|
-
return
|
|
655
|
+
return null;
|
|
657
656
|
const rest = text.slice(start.index + start[0].length);
|
|
658
657
|
const next = rest.match(NEXT_HEADING);
|
|
659
|
-
|
|
658
|
+
return rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
|
|
659
|
+
}
|
|
660
|
+
function intentGoalForSlug(description, slug) {
|
|
661
|
+
const text = stripCode(description, { inlineSpans: "keep" });
|
|
662
|
+
const section = intentsSection(text);
|
|
663
|
+
if (section === null)
|
|
664
|
+
return "";
|
|
660
665
|
for (const line of section.split(`
|
|
661
666
|
`)) {
|
|
662
667
|
const trimmed = line.trim();
|
|
@@ -1948,6 +1953,9 @@ function parseInlineFieldList(section) {
|
|
|
1948
1953
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1949
1954
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1950
1955
|
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1956
|
+
var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
|
|
1957
|
+
var BRIEF_HASH_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Brief hash:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1958
|
+
var POLICY_DIGEST_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Policy digest:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1951
1959
|
function firstFiveLines(comment) {
|
|
1952
1960
|
return comment.split(`
|
|
1953
1961
|
`).slice(0, 5).join(`
|
|
@@ -1961,6 +1969,35 @@ function extractObjectivesVersion(comment) {
|
|
|
1961
1969
|
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1962
1970
|
return m ? m[1].toLowerCase() : null;
|
|
1963
1971
|
}
|
|
1972
|
+
function firstSevenLines(comment) {
|
|
1973
|
+
return comment.split(`
|
|
1974
|
+
`).slice(0, 7).join(`
|
|
1975
|
+
`);
|
|
1976
|
+
}
|
|
1977
|
+
function extractRulingOrdinal(comment) {
|
|
1978
|
+
const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
|
|
1979
|
+
return m ? Number.parseInt(m[1], 10) : null;
|
|
1980
|
+
}
|
|
1981
|
+
function firstElevenLines(comment) {
|
|
1982
|
+
return comment.split(`
|
|
1983
|
+
`).slice(0, 11).join(`
|
|
1984
|
+
`);
|
|
1985
|
+
}
|
|
1986
|
+
function extractBriefHash(comment) {
|
|
1987
|
+
const m = firstElevenLines(comment).match(BRIEF_HASH_PATTERN);
|
|
1988
|
+
return m ? m[1].toLowerCase() : null;
|
|
1989
|
+
}
|
|
1990
|
+
function extractPolicyDigest(comment) {
|
|
1991
|
+
const m = firstElevenLines(comment).match(POLICY_DIGEST_PATTERN);
|
|
1992
|
+
return m ? m[1].toLowerCase() : null;
|
|
1993
|
+
}
|
|
1994
|
+
var FINDING_SEVERITY_LINE = /^\d+\.\s+\[([A-Z][A-Z]*)\]\s+(.+?)\s+—/gm;
|
|
1995
|
+
function extractFindingSeverities(comment) {
|
|
1996
|
+
return [...comment.matchAll(FINDING_SEVERITY_LINE)].map((m) => ({
|
|
1997
|
+
severity: m[1],
|
|
1998
|
+
location: m[2]
|
|
1999
|
+
}));
|
|
2000
|
+
}
|
|
1964
2001
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1965
2002
|
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1966
2003
|
if (candidates.length === 0) {
|
|
@@ -1968,6 +2005,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1968
2005
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1969
2006
|
headSha: null,
|
|
1970
2007
|
objectivesVersion: null,
|
|
2008
|
+
rulingOrdinal: null,
|
|
2009
|
+
briefHash: null,
|
|
2010
|
+
policyDigest: null,
|
|
2011
|
+
findingSeverities: [],
|
|
1971
2012
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1972
2013
|
};
|
|
1973
2014
|
}
|
|
@@ -1978,6 +2019,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1978
2019
|
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1979
2020
|
headSha: null,
|
|
1980
2021
|
objectivesVersion: null,
|
|
2022
|
+
rulingOrdinal: null,
|
|
2023
|
+
briefHash: null,
|
|
2024
|
+
policyDigest: null,
|
|
2025
|
+
findingSeverities: [],
|
|
1981
2026
|
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1982
2027
|
};
|
|
1983
2028
|
}
|
|
@@ -1985,6 +2030,10 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1985
2030
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1986
2031
|
headSha: extractHeadSha(latest),
|
|
1987
2032
|
objectivesVersion: extractObjectivesVersion(latest),
|
|
2033
|
+
rulingOrdinal: extractRulingOrdinal(latest),
|
|
2034
|
+
briefHash: extractBriefHash(latest),
|
|
2035
|
+
policyDigest: extractPolicyDigest(latest),
|
|
2036
|
+
findingSeverities: extractFindingSeverities(latest),
|
|
1988
2037
|
danglingNote: null
|
|
1989
2038
|
};
|
|
1990
2039
|
}
|
|
@@ -2320,6 +2369,53 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
|
|
|
2320
2369
|
}
|
|
2321
2370
|
return out;
|
|
2322
2371
|
}
|
|
2372
|
+
// ../../packages/aeg-core/src/review-policy.ts
|
|
2373
|
+
var CODE_REVIEW_SEVERITY_ORDER = ["BLOCKER", "MAJOR", "MINOR"];
|
|
2374
|
+
var SECURITY_SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
|
|
2375
|
+
var DEFAULT_MAX_ROUNDS = 3;
|
|
2376
|
+
var DEFAULT_REVIEW_POLICY = {
|
|
2377
|
+
codeReviewThreshold: "BLOCKER",
|
|
2378
|
+
securityThreshold: "HIGH",
|
|
2379
|
+
maxRounds: DEFAULT_MAX_ROUNDS
|
|
2380
|
+
};
|
|
2381
|
+
var FILE_SHAPED_LOCATION = /\.[a-zA-Z0-9]{1,10}(:\d+)?\s*$/;
|
|
2382
|
+
var PROSE_LOCATION_PATTERNS = [/\bpr\s*body\b/i, /\bcomment\b/i];
|
|
2383
|
+
var ROLE_FILE_LOCATION = /(^|\/)aeg-root\/roles\//i;
|
|
2384
|
+
function isProseLocation(location) {
|
|
2385
|
+
if (ROLE_FILE_LOCATION.test(location))
|
|
2386
|
+
return true;
|
|
2387
|
+
if (FILE_SHAPED_LOCATION.test(location))
|
|
2388
|
+
return false;
|
|
2389
|
+
return PROSE_LOCATION_PATTERNS.some((pattern) => pattern.test(location));
|
|
2390
|
+
}
|
|
2391
|
+
var PROSE_CAP_SEVERITY = "MINOR";
|
|
2392
|
+
function blockingSeverities(scale, threshold) {
|
|
2393
|
+
const idx = scale.indexOf(threshold);
|
|
2394
|
+
if (idx === -1) {
|
|
2395
|
+
throw new Error(`blockingSeverities: threshold "${threshold}" is not one of ${scale.join(" > ")}`);
|
|
2396
|
+
}
|
|
2397
|
+
return scale.slice(0, idx + 1);
|
|
2398
|
+
}
|
|
2399
|
+
function evaluateReviewFindings(findings, scale, threshold) {
|
|
2400
|
+
const blocking = new Set(blockingSeverities(scale, threshold));
|
|
2401
|
+
const blockingFindings = findings.filter((f) => {
|
|
2402
|
+
if (!scale.includes(f.severity)) {
|
|
2403
|
+
throw new Error(`evaluateReviewFindings: severity "${f.severity}" is not one of ${scale.join(" > ")}`);
|
|
2404
|
+
}
|
|
2405
|
+
const effectiveSeverity = f.location !== undefined && isProseLocation(f.location) ? PROSE_CAP_SEVERITY : f.severity;
|
|
2406
|
+
return blocking.has(effectiveSeverity);
|
|
2407
|
+
});
|
|
2408
|
+
return { outcome: blockingFindings.length > 0 ? "blocked" : "clean", blockingFindings };
|
|
2409
|
+
}
|
|
2410
|
+
function evaluateCodeReview(findings, policy) {
|
|
2411
|
+
return evaluateReviewFindings(findings, CODE_REVIEW_SEVERITY_ORDER, policy.codeReviewThreshold);
|
|
2412
|
+
}
|
|
2413
|
+
function evaluateSecurityReview(findings, policy) {
|
|
2414
|
+
return evaluateReviewFindings(findings, SECURITY_SEVERITY_ORDER, policy.securityThreshold);
|
|
2415
|
+
}
|
|
2416
|
+
function isKnownSeverity(scale, value) {
|
|
2417
|
+
return scale.includes(value);
|
|
2418
|
+
}
|
|
2323
2419
|
// ../../packages/aeg-core/src/pr-tier.ts
|
|
2324
2420
|
var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
|
|
2325
2421
|
function readTierFromPrBody(prBody) {
|
|
@@ -2346,6 +2442,9 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
2346
2442
|
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2347
2443
|
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2348
2444
|
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2445
|
+
function maskedForHeadingSearch(body) {
|
|
2446
|
+
return maskDetailsBlocks(maskCode(body));
|
|
2447
|
+
}
|
|
2349
2448
|
function hasBacktickedPath(text) {
|
|
2350
2449
|
let i = 0;
|
|
2351
2450
|
while (i < text.length) {
|
|
@@ -2368,16 +2467,22 @@ function stripObjectiveBackticks(text) {
|
|
|
2368
2467
|
function wordCount(text) {
|
|
2369
2468
|
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2370
2469
|
}
|
|
2371
|
-
function
|
|
2372
|
-
const
|
|
2470
|
+
function objectivesSectionBounds(body) {
|
|
2471
|
+
const masked = maskedForHeadingSearch(body);
|
|
2472
|
+
const heading = HEADING_RE.exec(masked);
|
|
2373
2473
|
if (!heading)
|
|
2374
2474
|
return null;
|
|
2375
|
-
const
|
|
2475
|
+
const start = heading.index + heading[0].length;
|
|
2476
|
+
const afterHeading = masked.slice(start);
|
|
2376
2477
|
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2377
|
-
return next ?
|
|
2478
|
+
return { start, end: next ? start + next.index : body.length };
|
|
2479
|
+
}
|
|
2480
|
+
function objectivesSectionText(body) {
|
|
2481
|
+
const bounds = objectivesSectionBounds(body);
|
|
2482
|
+
return bounds === null ? null : body.slice(bounds.start, bounds.end);
|
|
2378
2483
|
}
|
|
2379
2484
|
function hasObjectivesHeading(body) {
|
|
2380
|
-
return HEADING_RE.test(body);
|
|
2485
|
+
return HEADING_RE.test(maskedForHeadingSearch(body));
|
|
2381
2486
|
}
|
|
2382
2487
|
function objectivesOf(body) {
|
|
2383
2488
|
const section = objectivesSectionText(body);
|
|
@@ -2437,6 +2542,15 @@ function isIssueNotFoundError(err) {
|
|
|
2437
2542
|
`);
|
|
2438
2543
|
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2439
2544
|
}
|
|
2545
|
+
function resolveObjectivesSource(prBody, issue, cutoverIssue) {
|
|
2546
|
+
if (issue !== null && issue < cutoverIssue)
|
|
2547
|
+
return { kind: "none" };
|
|
2548
|
+
if (issue !== null)
|
|
2549
|
+
return { kind: "issue", issue };
|
|
2550
|
+
if (hasObjectivesHeading(prBody))
|
|
2551
|
+
return { kind: "body" };
|
|
2552
|
+
return { kind: "none" };
|
|
2553
|
+
}
|
|
2440
2554
|
|
|
2441
2555
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2442
2556
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -2672,7 +2786,7 @@ function checkForField(prBody) {
|
|
|
2672
2786
|
]
|
|
2673
2787
|
};
|
|
2674
2788
|
}
|
|
2675
|
-
function
|
|
2789
|
+
function checkClosesNPresence(prBody) {
|
|
2676
2790
|
const closesPattern = /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s{0,8}:?\s{0,8}#\d+/i;
|
|
2677
2791
|
if (closesPattern.test(stripCode(prBody))) {
|
|
2678
2792
|
return { status: "pass", errors: [] };
|
|
@@ -2704,10 +2818,22 @@ var COMMIT_TYPES = [
|
|
|
2704
2818
|
"Test"
|
|
2705
2819
|
];
|
|
2706
2820
|
var COMMIT_TYPE_STYLE = new RegExp(`^(${COMMIT_TYPES.join("|")})(\\([a-z0-9-]+\\))?: \\S`);
|
|
2821
|
+
function checkForgeTitle(title) {
|
|
2822
|
+
const taskStyle = /^\[[a-z0-9._-]+\] \S+ — \S/;
|
|
2823
|
+
if (COMMIT_TYPE_STYLE.test(title) || taskStyle.test(title))
|
|
2824
|
+
return { status: "pass", errors: [] };
|
|
2825
|
+
return {
|
|
2826
|
+
status: "fail",
|
|
2827
|
+
errors: [
|
|
2828
|
+
`brief-validation title: "${title}" matches neither title grammar — expected \`Type: description\` / \`Type(scope): description\` (commitlint types + Plan) or \`[tranche] id — description\` (task form).`
|
|
2829
|
+
]
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2707
2832
|
var BRIEF_SHAPE_MARKERS = [checkSurfaceMap, checkDocUpdateList, checkStopConditions, checkAutonomyClause];
|
|
2708
2833
|
var TASK_BRANCH_PATTERN = /^task\/[^/]+\/[^/]+$/;
|
|
2834
|
+
var TASK_ISSUE_BRANCH_PATTERN = /^task\/issue-\d+$/;
|
|
2709
2835
|
function isTaskBranch(branch) {
|
|
2710
|
-
return TASK_BRANCH_PATTERN.test(branch);
|
|
2836
|
+
return TASK_BRANCH_PATTERN.test(branch) || TASK_ISSUE_BRANCH_PATTERN.test(branch);
|
|
2711
2837
|
}
|
|
2712
2838
|
function isBriefShaped(prBody) {
|
|
2713
2839
|
const stripped = stripCode(prBody);
|
|
@@ -2829,8 +2955,9 @@ function packagesNamedIn(text) {
|
|
|
2829
2955
|
}
|
|
2830
2956
|
function hasTestPathForConsumer(text, consumerDir) {
|
|
2831
2957
|
const escaped = consumerDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2832
|
-
const
|
|
2833
|
-
|
|
2958
|
+
const filePathRe = new RegExp(`${escaped}\\/[\\w./-]*\\.test\\.[A-Za-z0-9]+`);
|
|
2959
|
+
const testDirRe = new RegExp(`${escaped}\\/(?:[\\w-]+\\/)*(?:tests|specs)(?:\\/|\\b)`);
|
|
2960
|
+
return filePathRe.test(text) || testDirRe.test(text);
|
|
2834
2961
|
}
|
|
2835
2962
|
function checkConsumerTests(prBody, consumersOf) {
|
|
2836
2963
|
const section4 = extractNumberedSection(prBody, 4);
|
|
@@ -2934,21 +3061,44 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2934
3061
|
checkConsumerTests(prBody, consumersOf),
|
|
2935
3062
|
checkDefeatCases(prBody),
|
|
2936
3063
|
...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
|
|
2937
|
-
...requireClosesN ? [
|
|
3064
|
+
...requireClosesN ? [checkClosesNPresence(prBody)] : []
|
|
2938
3065
|
];
|
|
2939
3066
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2940
3067
|
}
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
`
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
3068
|
+
function contentAfterNLines(body, n) {
|
|
3069
|
+
let idx = -1;
|
|
3070
|
+
for (let i = 0;i < n; i++) {
|
|
3071
|
+
idx = body.indexOf(`
|
|
3072
|
+
`, idx + 1);
|
|
3073
|
+
if (idx === -1)
|
|
3074
|
+
return "";
|
|
3075
|
+
}
|
|
3076
|
+
return body.slice(idx + 1);
|
|
3077
|
+
}
|
|
3078
|
+
var BRIEF_MARKER_LINE_RE = /^<!-- aeg:brief:v(\d+) -->$/;
|
|
3079
|
+
function parseBriefMarkerVersion(firstLine) {
|
|
3080
|
+
const m = BRIEF_MARKER_LINE_RE.exec(firstLine.trim());
|
|
3081
|
+
if (!m)
|
|
3082
|
+
return null;
|
|
3083
|
+
const version = Number.parseInt(m[1], 10);
|
|
3084
|
+
return Number.isInteger(version) && version >= 1 ? version : null;
|
|
3085
|
+
}
|
|
3086
|
+
function frozenBriefContent(body, version) {
|
|
3087
|
+
return contentAfterNLines(body, version === 1 ? 2 : 3);
|
|
3088
|
+
}
|
|
3089
|
+
function resolveNewestFrozenBrief(comments, allowlist) {
|
|
3090
|
+
let best = null;
|
|
3091
|
+
for (const c of comments) {
|
|
3092
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3093
|
+
continue;
|
|
3094
|
+
const version = parseBriefMarkerVersion(c.body.split(`
|
|
3095
|
+
`)[0] ?? "");
|
|
3096
|
+
if (version === null)
|
|
3097
|
+
continue;
|
|
3098
|
+
if (best === null || version > best.version)
|
|
3099
|
+
best = { ...c, version };
|
|
3100
|
+
}
|
|
3101
|
+
return best === null ? null : { ...best, content: frozenBriefContent(best.body, best.version) };
|
|
2952
3102
|
}
|
|
2953
3103
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2954
3104
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
@@ -3201,6 +3351,28 @@ function topLevelSectionText(body, headingName) {
|
|
|
3201
3351
|
const next = /^##[ \t]/m.exec(afterHeading);
|
|
3202
3352
|
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
3203
3353
|
}
|
|
3354
|
+
function partHasBacktickedPath(text) {
|
|
3355
|
+
let i = 0;
|
|
3356
|
+
while (i < text.length) {
|
|
3357
|
+
const start = text.indexOf("`", i);
|
|
3358
|
+
if (start === -1)
|
|
3359
|
+
return false;
|
|
3360
|
+
const end = text.indexOf("`", start + 1);
|
|
3361
|
+
if (end === -1)
|
|
3362
|
+
return false;
|
|
3363
|
+
if (text.slice(start + 1, end).includes("/"))
|
|
3364
|
+
return true;
|
|
3365
|
+
i = end + 1;
|
|
3366
|
+
}
|
|
3367
|
+
return false;
|
|
3368
|
+
}
|
|
3369
|
+
var PART_MIN_WORDS_OUTSIDE_BACKTICKS = 3;
|
|
3370
|
+
function stripPartBackticks(text) {
|
|
3371
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
3372
|
+
}
|
|
3373
|
+
function partWordCount(text) {
|
|
3374
|
+
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
3375
|
+
}
|
|
3204
3376
|
function looksLikeFilePath(entry2) {
|
|
3205
3377
|
const stripped = entry2.replace(/\/\*\*?$/, "");
|
|
3206
3378
|
const lastSegment = stripped.split("/").pop() ?? stripped;
|
|
@@ -3241,6 +3413,18 @@ function globCoversPath(glob, path) {
|
|
|
3241
3413
|
const p = path.replace(/\/+$/, "");
|
|
3242
3414
|
return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
|
|
3243
3415
|
}
|
|
3416
|
+
function checkSurfaceGlobsResolve(body, resolvesToFile) {
|
|
3417
|
+
const surface = parseIssueSurface(body);
|
|
3418
|
+
if (!surface.ok)
|
|
3419
|
+
return { status: "pass", errors: [] };
|
|
3420
|
+
const errors = [];
|
|
3421
|
+
for (const glob of surface.value.in) {
|
|
3422
|
+
if (!resolvesToFile(glob)) {
|
|
3423
|
+
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.`);
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3427
|
+
}
|
|
3244
3428
|
function checkSurfaceScope(changedFiles, outGlobs) {
|
|
3245
3429
|
if (outGlobs.length === 0)
|
|
3246
3430
|
return { ok: true };
|
|
@@ -3252,9 +3436,77 @@ function checkSurfaceScope(changedFiles, outGlobs) {
|
|
|
3252
3436
|
}
|
|
3253
3437
|
return violations.length > 0 ? { ok: false, violations } : { ok: true };
|
|
3254
3438
|
}
|
|
3439
|
+
var ISSUE_PART_LINE_RE = /^Part\s+(\d+)\s*\(([^)]*)\)\s*[-—–]\s*(.*)$/i;
|
|
3440
|
+
function parseIssueParts(body) {
|
|
3441
|
+
const section = topLevelSectionText(body, "Parts");
|
|
3442
|
+
if (section === null)
|
|
3443
|
+
return { ok: false, errors: ["no `## Parts` heading found in the body."] };
|
|
3444
|
+
const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
3445
|
+
const parts = [];
|
|
3446
|
+
const errors = [];
|
|
3447
|
+
for (const line of lines) {
|
|
3448
|
+
const m = ISSUE_PART_LINE_RE.exec(line);
|
|
3449
|
+
if (!m) {
|
|
3450
|
+
errors.push(`"${line}" is not a well-formed Parts line — expected \`Part <n> (<refs>) — <outcome>\`.`);
|
|
3451
|
+
continue;
|
|
3452
|
+
}
|
|
3453
|
+
const n = Number.parseInt(m[1], 10);
|
|
3454
|
+
const refs = m[2];
|
|
3455
|
+
const text = m[3].trim();
|
|
3456
|
+
if (text.length === 0) {
|
|
3457
|
+
errors.push(`Part ${n} has no outcome text after the dash — every Part states one observable outcome.`);
|
|
3458
|
+
continue;
|
|
3459
|
+
}
|
|
3460
|
+
if (partHasBacktickedPath(text) && partWordCount(stripPartBackticks(text)) < PART_MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
3461
|
+
errors.push(`Part ${n} is little more than a file path — a Part names an outcome and symbols, never a bare path.`);
|
|
3462
|
+
continue;
|
|
3463
|
+
}
|
|
3464
|
+
const objectiveIds = [...refs.matchAll(/O(\d+)/g)].map((r) => Number.parseInt(r[1], 10));
|
|
3465
|
+
parts.push({ n, objectiveIds, text });
|
|
3466
|
+
}
|
|
3467
|
+
if (parts.length === 0) {
|
|
3468
|
+
errors.push("the `## Parts` section has no well-formed `Part <n> (<refs>) — <outcome>` lines.");
|
|
3469
|
+
}
|
|
3470
|
+
if (errors.length > 0)
|
|
3471
|
+
return { ok: false, errors };
|
|
3472
|
+
return { ok: true, value: parts };
|
|
3473
|
+
}
|
|
3474
|
+
function checkPartsCiteDefinedObjectives(body) {
|
|
3475
|
+
const parts = parseIssueParts(body);
|
|
3476
|
+
const objectives = objectivesOf(body);
|
|
3477
|
+
if (!parts.ok || !objectives.ok)
|
|
3478
|
+
return { status: "pass", errors: [] };
|
|
3479
|
+
const definedIds = new Set(objectives.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
|
|
3480
|
+
const errors = [];
|
|
3481
|
+
for (const part of parts.value) {
|
|
3482
|
+
for (const objectiveId of part.objectiveIds) {
|
|
3483
|
+
if (!definedIds.has(objectiveId)) {
|
|
3484
|
+
errors.push(`issue-validation Parts: Part ${part.n} cites O${objectiveId}, which the Issue's own \`## Objectives\` section does not define.`);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3489
|
+
}
|
|
3255
3490
|
function isTaskIssueLabelSet(labels) {
|
|
3256
3491
|
return hasLabel("tranche", labels);
|
|
3257
3492
|
}
|
|
3493
|
+
function checkTrancheLabelPresence(_body, _labels) {
|
|
3494
|
+
return { status: "pass", errors: [] };
|
|
3495
|
+
}
|
|
3496
|
+
function checkMilestoneAttach(labels, currentMilestoneTitle, resolvedMilestoneTitle) {
|
|
3497
|
+
if (!isTaskIssueLabelSet(labels))
|
|
3498
|
+
return { status: "pass", errors: [] };
|
|
3499
|
+
if (resolvedMilestoneTitle === null)
|
|
3500
|
+
return { status: "pass", errors: [] };
|
|
3501
|
+
if (currentMilestoneTitle === resolvedMilestoneTitle)
|
|
3502
|
+
return { status: "pass", errors: [] };
|
|
3503
|
+
return {
|
|
3504
|
+
status: "fail",
|
|
3505
|
+
errors: [
|
|
3506
|
+
`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}".`
|
|
3507
|
+
]
|
|
3508
|
+
};
|
|
3509
|
+
}
|
|
3258
3510
|
var TYPE_LABEL_IDS = LABELS.filter((l) => l.category === "type").map((l) => l.id);
|
|
3259
3511
|
function isControlCodePoint(codePoint) {
|
|
3260
3512
|
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
@@ -3319,6 +3571,19 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
|
|
|
3319
3571
|
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;
|
|
3320
3572
|
var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
|
|
3321
3573
|
|
|
3574
|
+
// ../../packages/aeg-core/src/task-branch-identity.ts
|
|
3575
|
+
var ISSUE_BRANCH_PATTERN = /^task\/issue-(\d+)$/;
|
|
3576
|
+
var TRANCHE_BRANCH_PATTERN = /^task\/([^/]+)\/([^/]+)$/;
|
|
3577
|
+
function parseTaskBranchIdentity(branch) {
|
|
3578
|
+
const issueMatch = ISSUE_BRANCH_PATTERN.exec(branch);
|
|
3579
|
+
if (issueMatch)
|
|
3580
|
+
return { kind: "issue", issueNumber: Number(issueMatch[1]) };
|
|
3581
|
+
const trancheMatch = TRANCHE_BRANCH_PATTERN.exec(branch);
|
|
3582
|
+
if (trancheMatch)
|
|
3583
|
+
return { kind: "tranche", tranche: trancheMatch[1], taskId: trancheMatch[2] };
|
|
3584
|
+
return null;
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3322
3587
|
// ../../packages/aeg-core/src/coherence-checks.ts
|
|
3323
3588
|
var COHERENCE_ENFORCED_FROM = "2026-07-01";
|
|
3324
3589
|
function isGrandfathered(isoDate) {
|
|
@@ -3339,6 +3604,7 @@ function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
|
|
|
3339
3604
|
if (handClosed)
|
|
3340
3605
|
continue;
|
|
3341
3606
|
failures.push({
|
|
3607
|
+
code: "closed-without-merge",
|
|
3342
3608
|
issue: e.task.issue,
|
|
3343
3609
|
tranche: e.trancheSlug,
|
|
3344
3610
|
task: e.task.id,
|
|
@@ -3363,6 +3629,7 @@ function checkA3(entries) {
|
|
|
3363
3629
|
continue;
|
|
3364
3630
|
if (e.facts.prState === "merged" && e.facts.issueState !== "closed") {
|
|
3365
3631
|
failures.push({
|
|
3632
|
+
code: "auto-close-misfire",
|
|
3366
3633
|
issue: e.task.issue,
|
|
3367
3634
|
tranche: e.trancheSlug,
|
|
3368
3635
|
task: e.task.id,
|
|
@@ -3387,6 +3654,7 @@ function checkT1(entries) {
|
|
|
3387
3654
|
continue;
|
|
3388
3655
|
if (e.facts === undefined) {
|
|
3389
3656
|
failures.push({
|
|
3657
|
+
code: "phantom-issue-ref",
|
|
3390
3658
|
issue: e.task.issue,
|
|
3391
3659
|
tranche: e.trancheSlug,
|
|
3392
3660
|
task: e.task.id,
|
|
@@ -3405,6 +3673,7 @@ function checkT2(openIssuesBySlug, topologyIssuesBySlug, ciTrancheSlug) {
|
|
|
3405
3673
|
for (const num of openNums) {
|
|
3406
3674
|
if (!topologySet.has(num)) {
|
|
3407
3675
|
failures.push({
|
|
3676
|
+
code: "orphan-task",
|
|
3408
3677
|
issue: num,
|
|
3409
3678
|
tranche: slug,
|
|
3410
3679
|
reason: `Issue #${num} is open and labeled ${trancheLabel(slug)} but does not appear in the topology file`
|
|
@@ -3439,6 +3708,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
|
|
|
3439
3708
|
continue;
|
|
3440
3709
|
if (forgeUnavailableSlugs?.has(e.trancheSlug)) {
|
|
3441
3710
|
failures.push({
|
|
3711
|
+
code: "tbd-in-active-tranche",
|
|
3442
3712
|
issue: null,
|
|
3443
3713
|
tranche: e.trancheSlug,
|
|
3444
3714
|
task: e.task.id,
|
|
@@ -3448,6 +3718,7 @@ function checkT3(entries, ciTrancheSlug, enrichedEntries, forgeUnavailableSlugs)
|
|
|
3448
3718
|
continue;
|
|
3449
3719
|
}
|
|
3450
3720
|
failures.push({
|
|
3721
|
+
code: "tbd-in-active-tranche",
|
|
3451
3722
|
issue: null,
|
|
3452
3723
|
tranche: e.trancheSlug,
|
|
3453
3724
|
task: e.task.id,
|
|
@@ -3480,6 +3751,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
3480
3751
|
const sameIssue = depEntry.task.issue !== null && e.task.issue !== null && depEntry.task.issue === e.task.issue;
|
|
3481
3752
|
if (sameTask || sameIssue) {
|
|
3482
3753
|
failures.push({
|
|
3754
|
+
code: "d1-self-dependency",
|
|
3483
3755
|
issue: e.task.issue,
|
|
3484
3756
|
tranche: e.trancheSlug,
|
|
3485
3757
|
task: e.task.id,
|
|
@@ -3491,6 +3763,7 @@ function checkD1(entries, issueToEntry, taskToEntry) {
|
|
|
3491
3763
|
const depClosed = depFacts?.issueState === "closed";
|
|
3492
3764
|
if (!depClosed) {
|
|
3493
3765
|
failures.push({
|
|
3766
|
+
code: "dispatched-on-unmet-deps",
|
|
3494
3767
|
issue: e.task.issue,
|
|
3495
3768
|
tranche: e.trancheSlug,
|
|
3496
3769
|
task: e.task.id,
|
|
@@ -3516,11 +3789,13 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
|
|
|
3516
3789
|
const errors = [
|
|
3517
3790
|
...checkIssueRationale(issue.body).errors,
|
|
3518
3791
|
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
|
|
3519
|
-
...checkIssueObjectives(issue.body, issue.number).errors
|
|
3792
|
+
...checkIssueObjectives(issue.body, issue.number).errors,
|
|
3793
|
+
...checkPartsCiteDefinedObjectives(issue.body).errors
|
|
3520
3794
|
];
|
|
3521
3795
|
if (errors.length === 0)
|
|
3522
3796
|
continue;
|
|
3523
3797
|
failures.push({
|
|
3798
|
+
code: "missing-rationale-field",
|
|
3524
3799
|
issue: issue.number,
|
|
3525
3800
|
tranche: slug,
|
|
3526
3801
|
reason: `Issue #${issue.number} fails the rationale gate: ${errors.join(" | ")}`,
|
|
@@ -3549,6 +3824,7 @@ function checkL1(files, entriesBySlug) {
|
|
|
3549
3824
|
const allClosed = withFacts.every((e) => e.facts?.issueState === "closed");
|
|
3550
3825
|
if (allClosed) {
|
|
3551
3826
|
failures.push({
|
|
3827
|
+
code: "archive-recommended",
|
|
3552
3828
|
tranche: f.slug,
|
|
3553
3829
|
reason: "Active tranche has no open task-Issues — consider archiving to completed/"
|
|
3554
3830
|
});
|
|
@@ -3580,27 +3856,38 @@ function extractClosesReferences(prBody) {
|
|
|
3580
3856
|
}
|
|
3581
3857
|
return referenced;
|
|
3582
3858
|
}
|
|
3583
|
-
function
|
|
3859
|
+
function checkClosesNTopology(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
3584
3860
|
const referenced = extractClosesReferences(prBody);
|
|
3585
3861
|
if (taskIssueRefs) {
|
|
3586
3862
|
for (const n of referenced) {
|
|
3587
|
-
const
|
|
3588
|
-
if (!
|
|
3863
|
+
const ref2 = taskIssueRefs.get(n);
|
|
3864
|
+
if (!ref2)
|
|
3589
3865
|
continue;
|
|
3590
|
-
const expectedBranch = `task/${
|
|
3866
|
+
const expectedBranch = `task/${ref2.trancheSlug}/${ref2.taskId}`;
|
|
3591
3867
|
if (branch !== expectedBranch) {
|
|
3592
3868
|
return {
|
|
3593
3869
|
ok: false,
|
|
3594
|
-
message: `closes-n-reverse: branch "${branch}" closes #${n} (task ${
|
|
3870
|
+
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.`
|
|
3595
3871
|
};
|
|
3596
3872
|
}
|
|
3597
3873
|
}
|
|
3598
3874
|
}
|
|
3599
|
-
const
|
|
3600
|
-
if (!
|
|
3875
|
+
const ref = parseTaskBranchIdentity(branch);
|
|
3876
|
+
if (!ref)
|
|
3601
3877
|
return { ok: true };
|
|
3602
|
-
|
|
3603
|
-
|
|
3878
|
+
if (ref.kind === "issue") {
|
|
3879
|
+
const expectedIssue2 = ref.issueNumber;
|
|
3880
|
+
if (!referenced.has(expectedIssue2)) {
|
|
3881
|
+
return {
|
|
3882
|
+
ok: false,
|
|
3883
|
+
expectedIssue: expectedIssue2,
|
|
3884
|
+
message: `closes-n: PR body does not contain \`Closes #${expectedIssue2}\` (required for branch "${branch}"). Add it to the PR body Summary section.`
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
return { ok: true, expectedIssue: expectedIssue2 };
|
|
3888
|
+
}
|
|
3889
|
+
const trancheSlug = ref.tranche;
|
|
3890
|
+
const taskId = ref.taskId;
|
|
3604
3891
|
const trancheFile = trancheFiles.find((f) => f.slug === trancheSlug);
|
|
3605
3892
|
if (!trancheFile) {
|
|
3606
3893
|
return {
|
|
@@ -3652,29 +3939,67 @@ function extractIssue(body) {
|
|
|
3652
3939
|
const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
|
|
3653
3940
|
return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
|
|
3654
3941
|
}
|
|
3655
|
-
// ../../packages/aeg-core/src/review-
|
|
3656
|
-
|
|
3657
|
-
|
|
3942
|
+
// ../../packages/aeg-core/src/review-input-manifest.ts
|
|
3943
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3944
|
+
function briefHash(brief) {
|
|
3945
|
+
return createHash4("sha256").update(`${brief}
|
|
3946
|
+
`).digest("hex");
|
|
3947
|
+
}
|
|
3948
|
+
function policyDigest(policy) {
|
|
3949
|
+
return createHash4("sha256").update(JSON.stringify({ codeReviewThreshold: policy.codeReviewThreshold, securityThreshold: policy.securityThreshold })).digest("hex");
|
|
3950
|
+
}
|
|
3951
|
+
function isBoundToHead(echoed, headSha) {
|
|
3952
|
+
if (!echoed.headSha)
|
|
3658
3953
|
return false;
|
|
3659
|
-
return headSha.toLowerCase().startsWith(
|
|
3954
|
+
return headSha.toLowerCase().startsWith(echoed.headSha.toLowerCase());
|
|
3660
3955
|
}
|
|
3661
|
-
function isBoundByPatchIdentity(
|
|
3662
|
-
if (patchIdOf === undefined || !
|
|
3956
|
+
function isBoundByPatchIdentity(echoed, headSha, patchIdOf) {
|
|
3957
|
+
if (patchIdOf === undefined || !echoed.headSha)
|
|
3663
3958
|
return false;
|
|
3664
|
-
const judged = patchIdOf(
|
|
3959
|
+
const judged = patchIdOf(echoed.headSha);
|
|
3665
3960
|
const current = patchIdOf(headSha);
|
|
3666
3961
|
if (judged === null || current === null)
|
|
3667
3962
|
return false;
|
|
3668
3963
|
return judged === current;
|
|
3669
3964
|
}
|
|
3670
|
-
function isBoundToPatch(
|
|
3671
|
-
return isBoundToHead(
|
|
3965
|
+
function isBoundToPatch(echoed, headSha, patchIdOf) {
|
|
3966
|
+
return isBoundToHead(echoed, headSha) || isBoundByPatchIdentity(echoed, headSha, patchIdOf);
|
|
3672
3967
|
}
|
|
3673
|
-
function isBoundToObjectives(
|
|
3968
|
+
function isBoundToObjectives(echoed, currentVersion) {
|
|
3674
3969
|
if (currentVersion === null)
|
|
3675
3970
|
return true;
|
|
3676
|
-
return
|
|
3971
|
+
return echoed.objectivesVersion === currentVersion;
|
|
3972
|
+
}
|
|
3973
|
+
function isBoundToRulings(echoed, currentOrdinal) {
|
|
3974
|
+
if (echoed.rulingOrdinal === null)
|
|
3975
|
+
return currentOrdinal === 0;
|
|
3976
|
+
return echoed.rulingOrdinal === currentOrdinal;
|
|
3977
|
+
}
|
|
3978
|
+
function isBoundToBriefHash(echoed, currentHash) {
|
|
3979
|
+
if (currentHash === null)
|
|
3980
|
+
return true;
|
|
3981
|
+
return echoed.briefHash === currentHash;
|
|
3982
|
+
}
|
|
3983
|
+
function isBoundToPolicy(echoed, currentDigest) {
|
|
3984
|
+
return echoed.policyDigest === currentDigest;
|
|
3677
3985
|
}
|
|
3986
|
+
function compareManifest(echoed, current, patchIdOf) {
|
|
3987
|
+
const head = isBoundToPatch(echoed, current.headSha, patchIdOf);
|
|
3988
|
+
const briefHashBound = isBoundToBriefHash(echoed, current.briefHash);
|
|
3989
|
+
const objectivesVersion2 = isBoundToObjectives(echoed, current.objectivesVersion);
|
|
3990
|
+
const rulingOrdinal = isBoundToRulings(echoed, current.rulingOrdinal);
|
|
3991
|
+
const policyDigestBound = isBoundToPolicy(echoed, current.policyDigest);
|
|
3992
|
+
return {
|
|
3993
|
+
bound: head && briefHashBound && objectivesVersion2 && rulingOrdinal && policyDigestBound,
|
|
3994
|
+
head,
|
|
3995
|
+
briefHash: briefHashBound,
|
|
3996
|
+
objectivesVersion: objectivesVersion2,
|
|
3997
|
+
rulingOrdinal,
|
|
3998
|
+
policyDigest: policyDigestBound
|
|
3999
|
+
};
|
|
4000
|
+
}
|
|
4001
|
+
|
|
4002
|
+
// ../../packages/aeg-core/src/review-gate.ts
|
|
3678
4003
|
function checkReviewGate(input) {
|
|
3679
4004
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3680
4005
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3697,13 +4022,55 @@ function checkReviewGate(input) {
|
|
|
3697
4022
|
const verifiedBodies = verified.map((c) => c.body);
|
|
3698
4023
|
const codeReview = extractCodeReviewVerdict(verifiedBodies);
|
|
3699
4024
|
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
3700
|
-
const
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
4025
|
+
const policy = input.policy ?? DEFAULT_REVIEW_POLICY;
|
|
4026
|
+
let codeReviewPolicyEvaluation;
|
|
4027
|
+
let securityPolicyEvaluation;
|
|
4028
|
+
try {
|
|
4029
|
+
codeReviewPolicyEvaluation = evaluateCodeReview(codeReview.findingSeverities, policy);
|
|
4030
|
+
securityPolicyEvaluation = evaluateSecurityReview(security.findingSeverities, policy);
|
|
4031
|
+
} catch (err) {
|
|
4032
|
+
return {
|
|
4033
|
+
verdict: "fail",
|
|
4034
|
+
reason: `a verdict comment carries a finding severity this repository's policy does not recognize: ${err instanceof Error ? err.message : String(err)}`,
|
|
4035
|
+
waived: false
|
|
4036
|
+
};
|
|
4037
|
+
}
|
|
4038
|
+
const codeReviewTextClean = codeReview.value === "APPROVE";
|
|
4039
|
+
const securityTextClean = security.value === "PASS";
|
|
4040
|
+
const codeReviewPolicyClean = codeReviewPolicyEvaluation.outcome === "clean";
|
|
4041
|
+
const securityPolicyClean = securityPolicyEvaluation.outcome === "clean";
|
|
4042
|
+
const codeReviewClean = codeReviewTextClean && codeReviewPolicyClean;
|
|
4043
|
+
const securityClean = securityTextClean && securityPolicyClean;
|
|
4044
|
+
const currentManifest = {
|
|
4045
|
+
headSha: input.headSha,
|
|
4046
|
+
briefHash: input.briefHash ?? null,
|
|
4047
|
+
objectivesVersion: input.objectivesVersion,
|
|
4048
|
+
rulingOrdinal: input.rulingOrdinal,
|
|
4049
|
+
policyDigest: policyDigest(policy)
|
|
4050
|
+
};
|
|
4051
|
+
const codeReviewEchoed = {
|
|
4052
|
+
headSha: codeReview.headSha,
|
|
4053
|
+
briefHash: codeReview.briefHash,
|
|
4054
|
+
objectivesVersion: codeReview.objectivesVersion,
|
|
4055
|
+
rulingOrdinal: codeReview.rulingOrdinal,
|
|
4056
|
+
policyDigest: codeReview.policyDigest
|
|
4057
|
+
};
|
|
4058
|
+
const securityEchoed = {
|
|
4059
|
+
headSha: security.headSha,
|
|
4060
|
+
briefHash: security.briefHash,
|
|
4061
|
+
objectivesVersion: security.objectivesVersion,
|
|
4062
|
+
rulingOrdinal: security.rulingOrdinal,
|
|
4063
|
+
policyDigest: security.policyDigest
|
|
4064
|
+
};
|
|
4065
|
+
const codeReviewBinding = compareManifest(codeReviewEchoed, currentManifest, input.patchIdOf);
|
|
4066
|
+
const securityBinding = compareManifest(securityEchoed, currentManifest, input.patchIdOf);
|
|
4067
|
+
const codeReviewBound = codeReviewBinding.head;
|
|
4068
|
+
const securityBound = securityBinding.head;
|
|
4069
|
+
const codeReviewObjectivesBound = codeReviewBinding.objectivesVersion;
|
|
4070
|
+
const securityObjectivesBound = securityBinding.objectivesVersion;
|
|
4071
|
+
const codeReviewRulingsBound = codeReviewBinding.rulingOrdinal;
|
|
4072
|
+
const securityRulingsBound = securityBinding.rulingOrdinal;
|
|
4073
|
+
if (codeReviewClean && codeReviewBinding.bound && securityClean && securityBinding.bound && mechanicalChecksClean) {
|
|
3707
4074
|
return {
|
|
3708
4075
|
verdict: "pass",
|
|
3709
4076
|
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.`,
|
|
@@ -3711,19 +4078,35 @@ function checkReviewGate(input) {
|
|
|
3711
4078
|
};
|
|
3712
4079
|
}
|
|
3713
4080
|
const problems = [];
|
|
3714
|
-
if (!
|
|
4081
|
+
if (!codeReviewTextClean) {
|
|
3715
4082
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
4083
|
+
} else if (!codeReviewPolicyClean) {
|
|
4084
|
+
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`);
|
|
3716
4085
|
} else if (!codeReviewBound) {
|
|
3717
4086
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3718
4087
|
} else if (!codeReviewObjectivesBound) {
|
|
3719
4088
|
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3720
|
-
}
|
|
3721
|
-
|
|
4089
|
+
} else if (!codeReviewRulingsBound) {
|
|
4090
|
+
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`);
|
|
4091
|
+
} else if (!codeReviewBinding.briefHash) {
|
|
4092
|
+
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"}`);
|
|
4093
|
+
} else if (!codeReviewBinding.policyDigest) {
|
|
4094
|
+
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}`);
|
|
4095
|
+
}
|
|
4096
|
+
if (!securityTextClean) {
|
|
3722
4097
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
4098
|
+
} else if (!securityPolicyClean) {
|
|
4099
|
+
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`);
|
|
3723
4100
|
} else if (!securityBound) {
|
|
3724
4101
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3725
4102
|
} else if (!securityObjectivesBound) {
|
|
3726
4103
|
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
4104
|
+
} else if (!securityRulingsBound) {
|
|
4105
|
+
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`);
|
|
4106
|
+
} else if (!securityBinding.briefHash) {
|
|
4107
|
+
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"}`);
|
|
4108
|
+
} else if (!securityBinding.policyDigest) {
|
|
4109
|
+
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}`);
|
|
3727
4110
|
}
|
|
3728
4111
|
if (!mechanicalChecksClean) {
|
|
3729
4112
|
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(", ")}`);
|
|
@@ -3740,6 +4123,24 @@ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
|
|
|
3740
4123
|
function isChangesetsReleasePr(branch, author, expectedAuthor) {
|
|
3741
4124
|
return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
|
|
3742
4125
|
}
|
|
4126
|
+
// ../../packages/aeg-core/src/ruling-ordinal.ts
|
|
4127
|
+
var RULING_MARKER_ORDINAL = /^<!-- aeg:principal:ruling:\d+-(\d+) -->$/;
|
|
4128
|
+
function newestPrincipalRulingOrdinal(comments, allowlist) {
|
|
4129
|
+
let best = 0;
|
|
4130
|
+
for (const c of comments) {
|
|
4131
|
+
if (!isPrincipal(c.author, allowlist))
|
|
4132
|
+
continue;
|
|
4133
|
+
const firstLine = (c.body.split(`
|
|
4134
|
+
`)[0] ?? "").trim();
|
|
4135
|
+
const m = RULING_MARKER_ORDINAL.exec(firstLine);
|
|
4136
|
+
if (!m)
|
|
4137
|
+
continue;
|
|
4138
|
+
const k = Number.parseInt(m[1], 10);
|
|
4139
|
+
if (k > best)
|
|
4140
|
+
best = k;
|
|
4141
|
+
}
|
|
4142
|
+
return best;
|
|
4143
|
+
}
|
|
3743
4144
|
// ../../packages/aeg-core/src/markdown-table.ts
|
|
3744
4145
|
function splitRow(line) {
|
|
3745
4146
|
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
@@ -4397,42 +4798,45 @@ function checkDispatchReadiness(input) {
|
|
|
4397
4798
|
const { trancheSlug, task } = input;
|
|
4398
4799
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
4399
4800
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
4400
|
-
const
|
|
4801
|
+
const blockerDetails = [];
|
|
4802
|
+
const push = (blockerClass, message) => {
|
|
4803
|
+
blockerDetails.push({ class: blockerClass, message });
|
|
4804
|
+
};
|
|
4401
4805
|
if (task.issue === null) {
|
|
4402
|
-
|
|
4806
|
+
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.`);
|
|
4403
4807
|
} else if (input.issue === null) {
|
|
4404
|
-
|
|
4808
|
+
push("issue-existence", `dispatch-gate issue-existence: ${taskLabel} names Issue #${task.issue}, but it does not resolve to a real GitHub Issue (phantom reference).`);
|
|
4405
4809
|
}
|
|
4406
4810
|
if (input.issue !== null && !input.issueRationalePass) {
|
|
4407
|
-
|
|
4811
|
+
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.`);
|
|
4408
4812
|
}
|
|
4409
4813
|
for (const dep of input.dependsOn) {
|
|
4410
4814
|
if (isSelfDependency(dep, task, input.issue)) {
|
|
4411
4815
|
const issueStr = input.issue !== null ? `#${input.issue.number}` : "?";
|
|
4412
|
-
|
|
4816
|
+
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.`);
|
|
4413
4817
|
continue;
|
|
4414
4818
|
}
|
|
4415
4819
|
if (dep.resolved === false) {
|
|
4416
|
-
|
|
4820
|
+
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.`);
|
|
4417
4821
|
continue;
|
|
4418
4822
|
}
|
|
4419
4823
|
if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
|
|
4420
4824
|
const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
|
|
4421
|
-
|
|
4825
|
+
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.`);
|
|
4422
4826
|
}
|
|
4423
4827
|
}
|
|
4424
4828
|
for (const c of input.conflictsWith) {
|
|
4425
4829
|
if (c.openOrInFlight) {
|
|
4426
4830
|
const issueStr = c.issue !== null ? ` (#${c.issue})` : "";
|
|
4427
|
-
|
|
4831
|
+
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.`);
|
|
4428
4832
|
}
|
|
4429
4833
|
}
|
|
4430
4834
|
for (const proj of input.priorTrancheArchival) {
|
|
4431
4835
|
if (proj.priorTrancheSlug !== null && !proj.archived) {
|
|
4432
|
-
|
|
4836
|
+
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.`);
|
|
4433
4837
|
}
|
|
4434
4838
|
}
|
|
4435
|
-
return { ready:
|
|
4839
|
+
return { ready: blockerDetails.length === 0, blockers: blockerDetails.map((b) => b.message), blockerDetails };
|
|
4436
4840
|
}
|
|
4437
4841
|
// ../../packages/aeg-core/src/single-plan-pr.ts
|
|
4438
4842
|
function trancheSlugFromTopologyPath(path) {
|
|
@@ -4534,6 +4938,91 @@ function checkBranchTopology(input) {
|
|
|
4534
4938
|
reason: `Branch \`${branch}\` matches topology row \`${taskId}\` in ${topoPath}.`
|
|
4535
4939
|
};
|
|
4536
4940
|
}
|
|
4941
|
+
// ../../packages/aeg-core/src/task-tools.ts
|
|
4942
|
+
import { z } from "zod";
|
|
4943
|
+
var TASK_TOOL_ERROR_KINDS = [
|
|
4944
|
+
"validation",
|
|
4945
|
+
"authority",
|
|
4946
|
+
"precondition",
|
|
4947
|
+
"capability",
|
|
4948
|
+
"infrastructure",
|
|
4949
|
+
"cancellation",
|
|
4950
|
+
"timeout",
|
|
4951
|
+
"uncertain_effect"
|
|
4952
|
+
];
|
|
4953
|
+
var TaskToolErrorSchema = z.object({
|
|
4954
|
+
kind: z.enum(TASK_TOOL_ERROR_KINDS),
|
|
4955
|
+
message: z.string().min(1),
|
|
4956
|
+
detail: z.string().optional()
|
|
4957
|
+
});
|
|
4958
|
+
var TaskToolRefSchema = z.union([
|
|
4959
|
+
z.object({ tranche: z.string().min(1), id: z.string().min(1) }),
|
|
4960
|
+
z.object({ issue: z.number().int().positive() })
|
|
4961
|
+
]);
|
|
4962
|
+
var MAX_PAGE_LIMIT = 100;
|
|
4963
|
+
var PageRequestSchema = z.object({
|
|
4964
|
+
cursor: z.string().optional(),
|
|
4965
|
+
limit: z.number().int().positive().max(MAX_PAGE_LIMIT).optional()
|
|
4966
|
+
});
|
|
4967
|
+
var FreshnessSchema = z.enum(["fresh", "stale", "unknown"]);
|
|
4968
|
+
var ObservedSchema = z.object({
|
|
4969
|
+
observedAt: z.string(),
|
|
4970
|
+
freshness: FreshnessSchema
|
|
4971
|
+
});
|
|
4972
|
+
var TaskStatusInputSchema = z.object({
|
|
4973
|
+
task: TaskToolRefSchema.optional()
|
|
4974
|
+
}).merge(PageRequestSchema);
|
|
4975
|
+
var TaskStatusItemSchema = z.object({
|
|
4976
|
+
task: TaskToolRefSchema,
|
|
4977
|
+
issue: z.number().int().positive(),
|
|
4978
|
+
pr: z.number().int().positive().nullable(),
|
|
4979
|
+
state: z.string()
|
|
4980
|
+
}).merge(ObservedSchema);
|
|
4981
|
+
var TaskStatusResultSchema = z.object({
|
|
4982
|
+
items: z.array(TaskStatusItemSchema),
|
|
4983
|
+
nextCursor: z.string().nullable()
|
|
4984
|
+
});
|
|
4985
|
+
var RequestedAuthoritySchema = z.enum(["planner", "principal", "operator", "self"]);
|
|
4986
|
+
var EscalationInputsSchema = z.object({
|
|
4987
|
+
task: z.number().int().positive(),
|
|
4988
|
+
round: z.number().int().nonnegative(),
|
|
4989
|
+
head: z.string(),
|
|
4990
|
+
branch: z.string(),
|
|
4991
|
+
prNumber: z.number().int().positive()
|
|
4992
|
+
});
|
|
4993
|
+
var EscalationEvidenceSchema = z.object({
|
|
4994
|
+
round: z.number().int().nonnegative(),
|
|
4995
|
+
reviewer: z.string().nullable(),
|
|
4996
|
+
security: z.string().nullable()
|
|
4997
|
+
});
|
|
4998
|
+
var TaskEscalationReadInputSchema = z.object({
|
|
4999
|
+
task: TaskToolRefSchema
|
|
5000
|
+
}).merge(PageRequestSchema);
|
|
5001
|
+
var TaskEscalationPacketSchema = z.object({
|
|
5002
|
+
reason: z.string(),
|
|
5003
|
+
detail: z.string().nullable(),
|
|
5004
|
+
inputs: EscalationInputsSchema.nullable(),
|
|
5005
|
+
evidence: EscalationEvidenceSchema.nullable(),
|
|
5006
|
+
attemptedRecovery: z.string(),
|
|
5007
|
+
requestedAuthority: RequestedAuthoritySchema,
|
|
5008
|
+
permittedNextActions: z.array(z.string())
|
|
5009
|
+
}).merge(ObservedSchema);
|
|
5010
|
+
var TaskEscalationReadResultSchema = z.object({
|
|
5011
|
+
items: z.array(TaskEscalationPacketSchema),
|
|
5012
|
+
nextCursor: z.string().nullable()
|
|
5013
|
+
}).merge(ObservedSchema);
|
|
5014
|
+
var TaskStartInputSchema = z.object({
|
|
5015
|
+
tranche: z.string().min(1),
|
|
5016
|
+
id: z.string().min(1)
|
|
5017
|
+
});
|
|
5018
|
+
var TaskResumeInputSchema = z.object({
|
|
5019
|
+
task: TaskToolRefSchema
|
|
5020
|
+
});
|
|
5021
|
+
var TaskCancelInputSchema = z.object({
|
|
5022
|
+
task: TaskToolRefSchema,
|
|
5023
|
+
reason: z.string().min(1)
|
|
5024
|
+
});
|
|
5025
|
+
var NoResultSchema = z.never();
|
|
4537
5026
|
// ../../packages/aeg-core/src/first-push-dispatch-gate.ts
|
|
4538
5027
|
function parseTaskBranch(branch) {
|
|
4539
5028
|
const m = /^task\/([^/]+)\/([^/]+)$/.exec(branch);
|
|
@@ -4617,7 +5106,6 @@ function decideIssueAssignment(input) {
|
|
|
4617
5106
|
};
|
|
4618
5107
|
}
|
|
4619
5108
|
// ../../packages/aeg-core/src/test-plan-gate.ts
|
|
4620
|
-
var TASK_BRANCH_PATTERN2 = /^task\/[^/]+\/[^/]+$/;
|
|
4621
5109
|
function evaluateTestPlanGate(body, branch) {
|
|
4622
5110
|
if (!body) {
|
|
4623
5111
|
return {
|
|
@@ -4630,7 +5118,7 @@ function evaluateTestPlanGate(body, branch) {
|
|
|
4630
5118
|
}
|
|
4631
5119
|
const located = locateTestPlanSection(body);
|
|
4632
5120
|
if (!located.found) {
|
|
4633
|
-
if (
|
|
5121
|
+
if (parseTaskBranchIdentity(branch) !== null) {
|
|
4634
5122
|
return {
|
|
4635
5123
|
verdict: "fail",
|
|
4636
5124
|
messages: [
|
|
@@ -4758,7 +5246,7 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4758
5246
|
return findings;
|
|
4759
5247
|
}
|
|
4760
5248
|
// ../../packages/aeg-core/src/log/schema.ts
|
|
4761
|
-
import { z } from "zod";
|
|
5249
|
+
import { z as z2 } from "zod";
|
|
4762
5250
|
var ROLE_VALUES = [
|
|
4763
5251
|
"planner",
|
|
4764
5252
|
"developer",
|
|
@@ -4768,136 +5256,172 @@ var ROLE_VALUES = [
|
|
|
4768
5256
|
"archivist",
|
|
4769
5257
|
"architect"
|
|
4770
5258
|
];
|
|
4771
|
-
var RoleSchema =
|
|
5259
|
+
var RoleSchema = z2.enum(ROLE_VALUES);
|
|
4772
5260
|
var HOST_VALUES = ["hook", "ci", "cli", "loop"];
|
|
4773
|
-
var HostSchema =
|
|
5261
|
+
var HostSchema = z2.enum(HOST_VALUES);
|
|
4774
5262
|
var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
4775
|
-
var
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
doctrine: z.string(),
|
|
5263
|
+
var headerMetaCore = {
|
|
5264
|
+
ts: z2.string(),
|
|
5265
|
+
run_id: z2.string().regex(RUN_ID_PATTERN),
|
|
5266
|
+
seq: z2.number().int().nonnegative(),
|
|
5267
|
+
repo: z2.string().nullable(),
|
|
5268
|
+
vinaya: z2.string(),
|
|
5269
|
+
doctrine: z2.string(),
|
|
4783
5270
|
host: HostSchema,
|
|
4784
|
-
machine:
|
|
5271
|
+
machine: z2.string()
|
|
5272
|
+
};
|
|
5273
|
+
var HeaderMetaV1Schema = z2.object({
|
|
5274
|
+
schema: z2.literal(1),
|
|
5275
|
+
...headerMetaCore
|
|
5276
|
+
}).strict();
|
|
5277
|
+
var LineageSchema = z2.object({
|
|
5278
|
+
run: z2.string().nullable(),
|
|
5279
|
+
attempt: z2.number().int().nullable(),
|
|
5280
|
+
parent: z2.string().nullable()
|
|
4785
5281
|
}).strict();
|
|
4786
|
-
var
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
round: z.number().int().optional(),
|
|
4792
|
-
objectives_version: z.string().optional()
|
|
5282
|
+
var InputVersionsSchema = z2.object({
|
|
5283
|
+
objectives_version: z2.string().nullable(),
|
|
5284
|
+
brief_hash: z2.string().nullable(),
|
|
5285
|
+
ruling_ordinal: z2.number().int().nullable(),
|
|
5286
|
+
policy_digest: z2.string().nullable()
|
|
4793
5287
|
}).strict();
|
|
4794
|
-
var
|
|
5288
|
+
var ProvenanceSchema = z2.enum(["parent_attributed", "env_correlated", "self_reported", "unavailable"]);
|
|
5289
|
+
var HeaderMetaV2Schema = z2.object({
|
|
5290
|
+
schema: z2.literal(2),
|
|
5291
|
+
...headerMetaCore,
|
|
5292
|
+
event_id: z2.string().min(1),
|
|
5293
|
+
process_id: z2.string().min(1),
|
|
5294
|
+
actor_id: z2.string().nullable(),
|
|
5295
|
+
lineage: LineageSchema,
|
|
5296
|
+
input_versions: InputVersionsSchema,
|
|
5297
|
+
provenance: ProvenanceSchema
|
|
5298
|
+
}).strict();
|
|
5299
|
+
var HeaderMetaSchema = z2.discriminatedUnion("schema", [HeaderMetaV1Schema, HeaderMetaV2Schema]);
|
|
5300
|
+
var SubjectSchema = z2.object({
|
|
5301
|
+
issue: z2.number().int().nullable(),
|
|
5302
|
+
pr: z2.number().int().optional(),
|
|
5303
|
+
sha: z2.string().optional(),
|
|
5304
|
+
role: z2.union([RoleSchema, z2.literal("unattributed")]),
|
|
5305
|
+
round: z2.number().int().optional(),
|
|
5306
|
+
objectives_version: z2.string().optional()
|
|
5307
|
+
}).strict();
|
|
5308
|
+
var HeaderSchema = z2.object({
|
|
4795
5309
|
meta: HeaderMetaSchema,
|
|
4796
5310
|
subject: SubjectSchema
|
|
4797
5311
|
}).strict();
|
|
4798
5312
|
var envelopeTail = {
|
|
4799
|
-
duration_ms:
|
|
4800
|
-
payload:
|
|
5313
|
+
duration_ms: z2.number().nonnegative().optional(),
|
|
5314
|
+
payload: z2.object({}).strict()
|
|
4801
5315
|
};
|
|
4802
|
-
var
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
5316
|
+
var ReviewFindingSchema = z2.object({
|
|
5317
|
+
id: z2.string(),
|
|
5318
|
+
severity: z2.string(),
|
|
5319
|
+
state: z2.string().optional(),
|
|
5320
|
+
severity_scale: z2.string().optional(),
|
|
5321
|
+
policy_treatment: z2.enum(["blocking", "non_blocking", "unavailable"]).optional(),
|
|
5322
|
+
confidence: z2.number().min(0).max(1).optional(),
|
|
5323
|
+
confidence_scale: z2.string().optional(),
|
|
5324
|
+
confidence_source: z2.string().optional()
|
|
5325
|
+
}).strict();
|
|
5326
|
+
var DispatchOutcomeSchema = z2.discriminatedUnion("type", [
|
|
5327
|
+
z2.object({ type: z2.literal("pr_opened"), pr: z2.number().int(), head: z2.string() }).strict(),
|
|
5328
|
+
z2.object({
|
|
5329
|
+
type: z2.literal("round_pushed"),
|
|
5330
|
+
pr: z2.number().int(),
|
|
5331
|
+
head: z2.string(),
|
|
5332
|
+
comment_id: z2.number().int()
|
|
4809
5333
|
}).strict(),
|
|
4810
|
-
|
|
4811
|
-
type:
|
|
4812
|
-
verdict:
|
|
4813
|
-
head:
|
|
4814
|
-
comment_id:
|
|
4815
|
-
objectives:
|
|
4816
|
-
findings:
|
|
5334
|
+
z2.object({
|
|
5335
|
+
type: z2.literal("verdict"),
|
|
5336
|
+
verdict: z2.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
|
|
5337
|
+
head: z2.string(),
|
|
5338
|
+
comment_id: z2.number().int(),
|
|
5339
|
+
objectives: z2.array(z2.object({ id: z2.string(), met: z2.boolean() }).strict()),
|
|
5340
|
+
findings: z2.array(ReviewFindingSchema)
|
|
4817
5341
|
}).strict(),
|
|
4818
|
-
|
|
4819
|
-
type:
|
|
4820
|
-
class:
|
|
4821
|
-
comment_id:
|
|
5342
|
+
z2.object({
|
|
5343
|
+
type: z2.literal("escalation"),
|
|
5344
|
+
class: z2.enum(["authority", "strategy", "product"]),
|
|
5345
|
+
comment_id: z2.number().int()
|
|
4822
5346
|
}).strict(),
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
5347
|
+
z2.object({ type: z2.literal("brief"), comment_id: z2.number().int(), hash: z2.string() }).strict(),
|
|
5348
|
+
z2.object({ type: z2.literal("plan"), issues: z2.array(z2.number().int()) }).strict(),
|
|
5349
|
+
z2.object({ type: z2.literal("archive"), provenance_comment_id: z2.number().int() }).strict()
|
|
4826
5350
|
]);
|
|
4827
5351
|
var dispatchShared = {
|
|
4828
5352
|
meta: HeaderMetaSchema,
|
|
4829
5353
|
subject: SubjectSchema,
|
|
4830
|
-
kind:
|
|
5354
|
+
kind: z2.literal("dispatch"),
|
|
4831
5355
|
...envelopeTail,
|
|
4832
5356
|
target_role: RoleSchema,
|
|
4833
|
-
model:
|
|
4834
|
-
round:
|
|
4835
|
-
effect_id:
|
|
5357
|
+
model: z2.string(),
|
|
5358
|
+
round: z2.number().int().optional(),
|
|
5359
|
+
effect_id: z2.string()
|
|
4836
5360
|
};
|
|
4837
|
-
var dispatchUsageField =
|
|
4838
|
-
var DispatchEventSchema =
|
|
4839
|
-
|
|
4840
|
-
|
|
5361
|
+
var dispatchUsageField = z2.object({ input: z2.number().nonnegative(), output: z2.number().nonnegative() }).strict().nullable();
|
|
5362
|
+
var DispatchEventSchema = z2.discriminatedUnion("event", [
|
|
5363
|
+
z2.object({ ...dispatchShared, event: z2.literal("dispatched"), prompt_hash: z2.string() }).strict(),
|
|
5364
|
+
z2.object({
|
|
4841
5365
|
...dispatchShared,
|
|
4842
|
-
event:
|
|
5366
|
+
event: z2.literal("outcome_received"),
|
|
4843
5367
|
outcome: DispatchOutcomeSchema,
|
|
4844
5368
|
usage: dispatchUsageField
|
|
4845
5369
|
}).strict(),
|
|
4846
|
-
|
|
5370
|
+
z2.object({
|
|
4847
5371
|
...dispatchShared,
|
|
4848
|
-
event:
|
|
4849
|
-
reason:
|
|
5372
|
+
event: z2.literal("dispatch_failed"),
|
|
5373
|
+
reason: z2.enum(["timeout", "crash", "refused", "unattributed_write"]),
|
|
4850
5374
|
usage: dispatchUsageField
|
|
4851
5375
|
}).strict()
|
|
4852
5376
|
]);
|
|
4853
5377
|
var loopShared = {
|
|
4854
5378
|
meta: HeaderMetaSchema,
|
|
4855
5379
|
subject: SubjectSchema,
|
|
4856
|
-
kind:
|
|
5380
|
+
kind: z2.literal("dev_review_loop"),
|
|
4857
5381
|
...envelopeTail,
|
|
4858
|
-
loop_id:
|
|
5382
|
+
loop_id: z2.string()
|
|
4859
5383
|
};
|
|
4860
|
-
var DevReviewLoopEventSchema =
|
|
4861
|
-
|
|
5384
|
+
var DevReviewLoopEventSchema = z2.discriminatedUnion("event", [
|
|
5385
|
+
z2.object({
|
|
4862
5386
|
...loopShared,
|
|
4863
|
-
event:
|
|
4864
|
-
task:
|
|
4865
|
-
policy:
|
|
4866
|
-
max_rounds:
|
|
4867
|
-
reviewers:
|
|
4868
|
-
models:
|
|
5387
|
+
event: z2.literal("loop_started"),
|
|
5388
|
+
task: z2.number().int(),
|
|
5389
|
+
policy: z2.object({
|
|
5390
|
+
max_rounds: z2.number().int().nonnegative(),
|
|
5391
|
+
reviewers: z2.array(RoleSchema),
|
|
5392
|
+
models: z2.record(RoleSchema, z2.string())
|
|
4869
5393
|
}).strict()
|
|
4870
5394
|
}).strict(),
|
|
4871
|
-
|
|
4872
|
-
|
|
5395
|
+
z2.object({ ...loopShared, event: z2.literal("round_started"), round: z2.number().int(), base_head: z2.string() }).strict(),
|
|
5396
|
+
z2.object({
|
|
4873
5397
|
...loopShared,
|
|
4874
|
-
event:
|
|
4875
|
-
round:
|
|
4876
|
-
head:
|
|
4877
|
-
green:
|
|
5398
|
+
event: z2.literal("gate_result_read"),
|
|
5399
|
+
round: z2.number().int(),
|
|
5400
|
+
head: z2.string(),
|
|
5401
|
+
green: z2.boolean()
|
|
4878
5402
|
}).strict(),
|
|
4879
|
-
|
|
5403
|
+
z2.object({
|
|
4880
5404
|
...loopShared,
|
|
4881
|
-
event:
|
|
4882
|
-
round:
|
|
4883
|
-
head:
|
|
4884
|
-
all_approve:
|
|
4885
|
-
blockers:
|
|
5405
|
+
event: z2.literal("verdicts_read"),
|
|
5406
|
+
round: z2.number().int(),
|
|
5407
|
+
head: z2.string(),
|
|
5408
|
+
all_approve: z2.boolean(),
|
|
5409
|
+
blockers: z2.number().int().nonnegative()
|
|
4886
5410
|
}).strict(),
|
|
4887
|
-
|
|
5411
|
+
z2.object({
|
|
4888
5412
|
...loopShared,
|
|
4889
|
-
event:
|
|
4890
|
-
round:
|
|
4891
|
-
open:
|
|
4892
|
-
resolved:
|
|
4893
|
-
new:
|
|
4894
|
-
recurring:
|
|
5413
|
+
event: z2.literal("findings_compared"),
|
|
5414
|
+
round: z2.number().int(),
|
|
5415
|
+
open: z2.array(z2.string()),
|
|
5416
|
+
resolved: z2.array(z2.string()),
|
|
5417
|
+
new: z2.array(z2.string()),
|
|
5418
|
+
recurring: z2.array(z2.string())
|
|
4895
5419
|
}).strict(),
|
|
4896
|
-
|
|
5420
|
+
z2.object({
|
|
4897
5421
|
...loopShared,
|
|
4898
|
-
event:
|
|
4899
|
-
round:
|
|
4900
|
-
condition:
|
|
5422
|
+
event: z2.literal("stop_condition_met"),
|
|
5423
|
+
round: z2.number().int(),
|
|
5424
|
+
condition: z2.enum([
|
|
4901
5425
|
"green",
|
|
4902
5426
|
"max_rounds",
|
|
4903
5427
|
"no_progress",
|
|
@@ -4907,42 +5431,49 @@ var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
|
|
|
4907
5431
|
"reappearance"
|
|
4908
5432
|
])
|
|
4909
5433
|
}).strict(),
|
|
4910
|
-
|
|
5434
|
+
z2.object({
|
|
4911
5435
|
...loopShared,
|
|
4912
|
-
event:
|
|
4913
|
-
round:
|
|
4914
|
-
reason:
|
|
5436
|
+
event: z2.literal("paused"),
|
|
5437
|
+
round: z2.number().int(),
|
|
5438
|
+
reason: z2.enum(["escalation", "principal_item", "refreeze_needed"])
|
|
4915
5439
|
}).strict(),
|
|
4916
|
-
|
|
5440
|
+
z2.object({
|
|
4917
5441
|
...loopShared,
|
|
4918
|
-
event:
|
|
4919
|
-
round:
|
|
4920
|
-
by:
|
|
5442
|
+
event: z2.literal("resumed"),
|
|
5443
|
+
round: z2.number().int(),
|
|
5444
|
+
by: z2.literal("principal")
|
|
4921
5445
|
}).strict(),
|
|
4922
|
-
|
|
5446
|
+
z2.object({
|
|
4923
5447
|
...loopShared,
|
|
4924
|
-
event:
|
|
4925
|
-
round:
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
files_changed: z.number().int().nonnegative(),
|
|
4929
|
-
insertions: z.number().int().nonnegative(),
|
|
4930
|
-
deletions: z.number().int().nonnegative(),
|
|
4931
|
-
wall_ms: z.number().nonnegative(),
|
|
4932
|
-
outcome: z.enum(["green", "changes_requested", "escalated"])
|
|
5448
|
+
event: z2.literal("unpushed_work_resume"),
|
|
5449
|
+
round: z2.number().int(),
|
|
5450
|
+
branch: z2.string(),
|
|
5451
|
+
detail: z2.string()
|
|
4933
5452
|
}).strict(),
|
|
4934
|
-
|
|
5453
|
+
z2.object({
|
|
4935
5454
|
...loopShared,
|
|
4936
|
-
event:
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
5455
|
+
event: z2.literal("round_ended"),
|
|
5456
|
+
round: z2.number().int(),
|
|
5457
|
+
base_head: z2.string(),
|
|
5458
|
+
head: z2.string(),
|
|
5459
|
+
files_changed: z2.number().int().nonnegative(),
|
|
5460
|
+
insertions: z2.number().int().nonnegative(),
|
|
5461
|
+
deletions: z2.number().int().nonnegative(),
|
|
5462
|
+
wall_ms: z2.number().nonnegative(),
|
|
5463
|
+
outcome: z2.enum(["green", "changes_requested", "escalated"])
|
|
5464
|
+
}).strict(),
|
|
5465
|
+
z2.object({
|
|
5466
|
+
...loopShared,
|
|
5467
|
+
event: z2.literal("journal_finalized"),
|
|
5468
|
+
rounds: z2.number().int().nonnegative(),
|
|
5469
|
+
total_wall_ms: z2.number().nonnegative(),
|
|
5470
|
+
time_to_green_ms: z2.number().nonnegative().nullable(),
|
|
5471
|
+
files_changed_total: z2.number().int().nonnegative(),
|
|
5472
|
+
final_head: z2.string(),
|
|
5473
|
+
result: z2.enum(["merged_ready", "stopped"])
|
|
4943
5474
|
}).strict()
|
|
4944
5475
|
]);
|
|
4945
|
-
var ForgeOpSchema =
|
|
5476
|
+
var ForgeOpSchema = z2.enum([
|
|
4946
5477
|
"pr.create",
|
|
4947
5478
|
"pr.comment",
|
|
4948
5479
|
"pr.body.replace",
|
|
@@ -4956,24 +5487,210 @@ var ForgeOpSchema = z.enum([
|
|
|
4956
5487
|
"label.add",
|
|
4957
5488
|
"label.remove"
|
|
4958
5489
|
]);
|
|
4959
|
-
var ForgeWriteTargetSchema =
|
|
4960
|
-
issue:
|
|
4961
|
-
pr:
|
|
5490
|
+
var ForgeWriteTargetSchema = z2.object({
|
|
5491
|
+
issue: z2.number().int().optional(),
|
|
5492
|
+
pr: z2.number().int().optional()
|
|
4962
5493
|
}).strict();
|
|
4963
5494
|
var forgeWriteShared = {
|
|
4964
5495
|
meta: HeaderMetaSchema,
|
|
4965
5496
|
subject: SubjectSchema,
|
|
4966
|
-
kind:
|
|
5497
|
+
kind: z2.literal("forge_write"),
|
|
4967
5498
|
...envelopeTail,
|
|
4968
5499
|
op: ForgeOpSchema,
|
|
4969
5500
|
target: ForgeWriteTargetSchema
|
|
4970
5501
|
};
|
|
4971
|
-
var ForgeWriteEventSchema =
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
5502
|
+
var ForgeWriteEventSchema = z2.discriminatedUnion("event", [
|
|
5503
|
+
z2.object({ ...forgeWriteShared, event: z2.literal("validated") }).strict(),
|
|
5504
|
+
z2.object({ ...forgeWriteShared, event: z2.literal("refused"), reason: z2.string() }).strict(),
|
|
5505
|
+
z2.object({ ...forgeWriteShared, event: z2.literal("written"), comment_ids: z2.array(z2.string()) }).strict()
|
|
5506
|
+
]);
|
|
5507
|
+
var GateOutcomeSchema = z2.enum([
|
|
5508
|
+
"pass",
|
|
5509
|
+
"fail",
|
|
5510
|
+
"wait",
|
|
5511
|
+
"skip",
|
|
5512
|
+
"invalid_input",
|
|
5513
|
+
"unavailable_dependency",
|
|
5514
|
+
"timeout",
|
|
5515
|
+
"cancelled"
|
|
5516
|
+
]);
|
|
5517
|
+
var gateShared = {
|
|
5518
|
+
meta: HeaderMetaSchema,
|
|
5519
|
+
subject: SubjectSchema,
|
|
5520
|
+
kind: z2.literal("gate"),
|
|
5521
|
+
...envelopeTail,
|
|
5522
|
+
check: z2.string(),
|
|
5523
|
+
check_version: z2.string().nullable(),
|
|
5524
|
+
policy_version: z2.string().nullable(),
|
|
5525
|
+
input_fingerprint: z2.string().nullable()
|
|
5526
|
+
};
|
|
5527
|
+
var GateEventSchema = z2.discriminatedUnion("event", [
|
|
5528
|
+
z2.object({
|
|
5529
|
+
...gateShared,
|
|
5530
|
+
event: z2.literal("checked"),
|
|
5531
|
+
outcome: GateOutcomeSchema,
|
|
5532
|
+
reason: z2.string().optional()
|
|
5533
|
+
}).strict()
|
|
5534
|
+
]);
|
|
5535
|
+
var OperationResultSchema = z2.enum(["ok", "error", "refused", "timeout", "cancelled", "unavailable"]);
|
|
5536
|
+
var operationShared = {
|
|
5537
|
+
meta: HeaderMetaSchema,
|
|
5538
|
+
subject: SubjectSchema,
|
|
5539
|
+
kind: z2.literal("operation"),
|
|
5540
|
+
...envelopeTail,
|
|
5541
|
+
operation: z2.string(),
|
|
5542
|
+
target: z2.string().nullable()
|
|
5543
|
+
};
|
|
5544
|
+
var OperationEventSchema = z2.discriminatedUnion("event", [
|
|
5545
|
+
z2.object({
|
|
5546
|
+
...operationShared,
|
|
5547
|
+
event: z2.literal("completed"),
|
|
5548
|
+
result: OperationResultSchema,
|
|
5549
|
+
error_class: z2.string().nullable()
|
|
5550
|
+
}).strict()
|
|
5551
|
+
]);
|
|
5552
|
+
var UsageUnitsSchema = z2.object({
|
|
5553
|
+
input: z2.number().nonnegative().nullable(),
|
|
5554
|
+
output: z2.number().nonnegative().nullable(),
|
|
5555
|
+
cache: z2.number().nonnegative().nullable()
|
|
5556
|
+
}).strict();
|
|
5557
|
+
var usageShared = {
|
|
5558
|
+
meta: HeaderMetaSchema,
|
|
5559
|
+
subject: SubjectSchema,
|
|
5560
|
+
kind: z2.literal("usage"),
|
|
5561
|
+
...envelopeTail,
|
|
5562
|
+
model: z2.string().nullable(),
|
|
5563
|
+
source: z2.string(),
|
|
5564
|
+
semantics: z2.enum(["cumulative", "delta"])
|
|
5565
|
+
};
|
|
5566
|
+
var UsageEventSchema = z2.discriminatedUnion("event", [
|
|
5567
|
+
z2.object({
|
|
5568
|
+
...usageShared,
|
|
5569
|
+
event: z2.literal("observed"),
|
|
5570
|
+
units: UsageUnitsSchema,
|
|
5571
|
+
unknown_reason: z2.string().nullable()
|
|
5572
|
+
}).strict()
|
|
5573
|
+
]);
|
|
5574
|
+
var RoleAttemptOutcomeSchema = z2.enum([
|
|
5575
|
+
"completed",
|
|
5576
|
+
"incomplete",
|
|
5577
|
+
"infrastructure_failed",
|
|
5578
|
+
"cancelled",
|
|
5579
|
+
"timed_out",
|
|
5580
|
+
"capability_refused"
|
|
5581
|
+
]);
|
|
5582
|
+
var roleAttemptShared = {
|
|
5583
|
+
meta: HeaderMetaSchema,
|
|
5584
|
+
subject: SubjectSchema,
|
|
5585
|
+
kind: z2.literal("role_attempt"),
|
|
5586
|
+
...envelopeTail,
|
|
5587
|
+
actor: z2.string().nullable(),
|
|
5588
|
+
attempt: z2.number().int().nullable()
|
|
5589
|
+
};
|
|
5590
|
+
var RoleAttemptEventSchema = z2.discriminatedUnion("event", [
|
|
5591
|
+
z2.object({
|
|
5592
|
+
...roleAttemptShared,
|
|
5593
|
+
event: z2.literal("attempted"),
|
|
5594
|
+
outcome: RoleAttemptOutcomeSchema,
|
|
5595
|
+
usage: dispatchUsageField
|
|
5596
|
+
}).strict()
|
|
5597
|
+
]);
|
|
5598
|
+
var handoffShared = {
|
|
5599
|
+
meta: HeaderMetaSchema,
|
|
5600
|
+
subject: SubjectSchema,
|
|
5601
|
+
kind: z2.literal("handoff"),
|
|
5602
|
+
...envelopeTail,
|
|
5603
|
+
class: z2.enum(["authority", "strategy", "product"]),
|
|
5604
|
+
reason: z2.string()
|
|
5605
|
+
};
|
|
5606
|
+
var HandoffEventSchema = z2.discriminatedUnion("event", [
|
|
5607
|
+
z2.object({
|
|
5608
|
+
...handoffShared,
|
|
5609
|
+
event: z2.literal("raised"),
|
|
5610
|
+
requested_decision: z2.string().nullable()
|
|
5611
|
+
}).strict(),
|
|
5612
|
+
z2.object({
|
|
5613
|
+
...handoffShared,
|
|
5614
|
+
event: z2.literal("resolved"),
|
|
5615
|
+
resolution: z2.string().nullable(),
|
|
5616
|
+
resolved_by: z2.string().nullable()
|
|
5617
|
+
}).strict()
|
|
5618
|
+
]);
|
|
5619
|
+
var EffectTargetSchema = z2.object({
|
|
5620
|
+
kind: z2.string(),
|
|
5621
|
+
ref: z2.string()
|
|
5622
|
+
}).strict();
|
|
5623
|
+
var effectShared = {
|
|
5624
|
+
meta: HeaderMetaSchema,
|
|
5625
|
+
subject: SubjectSchema,
|
|
5626
|
+
kind: z2.literal("effect"),
|
|
5627
|
+
...envelopeTail,
|
|
5628
|
+
effect_id: z2.string(),
|
|
5629
|
+
target: EffectTargetSchema
|
|
5630
|
+
};
|
|
5631
|
+
var EffectEventSchema = z2.discriminatedUnion("event", [
|
|
5632
|
+
z2.object({ ...effectShared, event: z2.literal("attempted") }).strict(),
|
|
5633
|
+
z2.object({ ...effectShared, event: z2.literal("observed"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict(),
|
|
5634
|
+
z2.object({ ...effectShared, event: z2.literal("verified"), outcome: z2.enum(["success", "failure", "uncertain"]) }).strict()
|
|
4975
5635
|
]);
|
|
4976
|
-
var LogEventSchema =
|
|
5636
|
+
var LogEventSchema = z2.union([
|
|
5637
|
+
DispatchEventSchema,
|
|
5638
|
+
DevReviewLoopEventSchema,
|
|
5639
|
+
ForgeWriteEventSchema,
|
|
5640
|
+
GateEventSchema,
|
|
5641
|
+
OperationEventSchema,
|
|
5642
|
+
UsageEventSchema,
|
|
5643
|
+
RoleAttemptEventSchema,
|
|
5644
|
+
HandoffEventSchema,
|
|
5645
|
+
EffectEventSchema
|
|
5646
|
+
]);
|
|
5647
|
+
// ../../packages/aeg-core/src/dev-review-loop/journal-reconstruction.ts
|
|
5648
|
+
var STOPPED_CONDITIONS = new Set(["confidence", "reappearance", "no_progress", "max_rounds"]);
|
|
5649
|
+
// ../../packages/aeg-core/src/control-store/records.ts
|
|
5650
|
+
import { z as z3 } from "zod";
|
|
5651
|
+
var isoTimestamp = z3.string().min(1);
|
|
5652
|
+
var taskId = z3.number().int().positive();
|
|
5653
|
+
var epochNumber = z3.number().int().nonnegative();
|
|
5654
|
+
var RunRecordSchema = z3.object({
|
|
5655
|
+
version: z3.literal(1),
|
|
5656
|
+
kind: z3.literal("run"),
|
|
5657
|
+
task: taskId,
|
|
5658
|
+
runId: z3.string().min(1),
|
|
5659
|
+
pid: z3.number().int().positive(),
|
|
5660
|
+
host: z3.string().min(1),
|
|
5661
|
+
startedAt: isoTimestamp
|
|
5662
|
+
}).strict();
|
|
5663
|
+
var InputRecordSchema = z3.object({
|
|
5664
|
+
version: z3.literal(1),
|
|
5665
|
+
kind: z3.literal("input"),
|
|
5666
|
+
task: taskId,
|
|
5667
|
+
runId: z3.string().min(1),
|
|
5668
|
+
source: z3.union([z3.literal("fresh"), z3.literal("resume")]),
|
|
5669
|
+
pr: z3.number().int().positive().nullable(),
|
|
5670
|
+
round: z3.number().int().nonnegative(),
|
|
5671
|
+
recordedAt: isoTimestamp
|
|
5672
|
+
}).strict();
|
|
5673
|
+
var OwnershipRecordSchema = z3.object({
|
|
5674
|
+
version: z3.literal(1),
|
|
5675
|
+
kind: z3.literal("ownership"),
|
|
5676
|
+
task: taskId,
|
|
5677
|
+
epoch: epochNumber,
|
|
5678
|
+
ownerId: z3.string().min(1),
|
|
5679
|
+
pid: z3.number().int().positive(),
|
|
5680
|
+
host: z3.string().min(1),
|
|
5681
|
+
acquiredAt: isoTimestamp
|
|
5682
|
+
}).strict();
|
|
5683
|
+
var TransitionRecordSchema = z3.object({
|
|
5684
|
+
version: z3.literal(1),
|
|
5685
|
+
kind: z3.literal("transition"),
|
|
5686
|
+
task: taskId,
|
|
5687
|
+
epoch: epochNumber,
|
|
5688
|
+
seq: z3.number().int().nonnegative(),
|
|
5689
|
+
from: z3.string().min(1),
|
|
5690
|
+
to: z3.string().min(1),
|
|
5691
|
+
detail: z3.string().optional(),
|
|
5692
|
+
at: isoTimestamp
|
|
5693
|
+
}).strict();
|
|
4977
5694
|
// src/checks/contract.ts
|
|
4978
5695
|
var CHECK_SCHEMA_VERSION = 1;
|
|
4979
5696
|
function emitCheckError(error) {
|
|
@@ -4986,27 +5703,29 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
4986
5703
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
|
|
4987
5704
|
import { homedir } from "node:os";
|
|
4988
5705
|
import { dirname, join, resolve } from "node:path";
|
|
4989
|
-
import { z as
|
|
5706
|
+
import { z as z4 } from "zod";
|
|
4990
5707
|
|
|
4991
5708
|
// src/lib/agent-vendors.ts
|
|
4992
5709
|
var AGENT_VENDORS = ["skills", "claude", "gemini"];
|
|
4993
5710
|
|
|
4994
5711
|
// src/lib/config.ts
|
|
4995
|
-
var EnvEntrySchema =
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5712
|
+
var EnvEntrySchema = z4.union([
|
|
5713
|
+
z4.literal(true),
|
|
5714
|
+
z4.object({ optional: z4.literal(true) }),
|
|
5715
|
+
z4.object({ anyOf: z4.array(z4.string()).min(2) }),
|
|
5716
|
+
z4.string()
|
|
5000
5717
|
]);
|
|
5001
|
-
var CheckEntrySchema =
|
|
5002
|
-
run:
|
|
5003
|
-
scope:
|
|
5004
|
-
include:
|
|
5005
|
-
args:
|
|
5006
|
-
timeoutMs:
|
|
5007
|
-
env:
|
|
5008
|
-
requiresOpenPr:
|
|
5009
|
-
ownWorkflow:
|
|
5718
|
+
var CheckEntrySchema = z4.object({
|
|
5719
|
+
run: z4.string(),
|
|
5720
|
+
scope: z4.enum(["diff", "full"]),
|
|
5721
|
+
include: z4.array(z4.string()).optional(),
|
|
5722
|
+
args: z4.array(z4.string()).optional(),
|
|
5723
|
+
timeoutMs: z4.number().optional(),
|
|
5724
|
+
env: z4.record(z4.string(), EnvEntrySchema).optional(),
|
|
5725
|
+
requiresOpenPr: z4.boolean().optional(),
|
|
5726
|
+
ownWorkflow: z4.boolean().optional(),
|
|
5727
|
+
principalOwed: z4.literal(true).optional(),
|
|
5728
|
+
validates: z4.enum(["body", "issue"]).optional()
|
|
5010
5729
|
}).superRefine((entry2, ctx) => {
|
|
5011
5730
|
if (!entry2.env)
|
|
5012
5731
|
return;
|
|
@@ -5016,25 +5735,26 @@ var CheckEntrySchema = z2.object({
|
|
|
5016
5735
|
const members = value.anyOf;
|
|
5017
5736
|
if (new Set(members).size !== members.length) {
|
|
5018
5737
|
ctx.addIssue({
|
|
5019
|
-
code:
|
|
5738
|
+
code: z4.ZodIssueCode.custom,
|
|
5020
5739
|
message: `env.${key}.anyOf has duplicate members`,
|
|
5021
5740
|
path: ["env", key, "anyOf"]
|
|
5022
5741
|
});
|
|
5023
5742
|
}
|
|
5024
5743
|
if (!members.includes(key)) {
|
|
5025
5744
|
ctx.addIssue({
|
|
5026
|
-
code:
|
|
5745
|
+
code: z4.ZodIssueCode.custom,
|
|
5027
5746
|
message: `env.${key}.anyOf must include "${key}" itself as a member`,
|
|
5028
5747
|
path: ["env", key, "anyOf"]
|
|
5029
5748
|
});
|
|
5030
5749
|
}
|
|
5031
5750
|
}
|
|
5032
5751
|
});
|
|
5033
|
-
var RoleEntrySchema =
|
|
5034
|
-
contract:
|
|
5752
|
+
var RoleEntrySchema = z4.object({
|
|
5753
|
+
contract: z4.string().refine((p) => p.includes("/"), {
|
|
5035
5754
|
message: 'must be a path (contain at least one "/"), not a bare filename'
|
|
5036
5755
|
})
|
|
5037
5756
|
});
|
|
5757
|
+
var MAX_REPORT_COMMAND_TIMEOUT_MS = 3600000;
|
|
5038
5758
|
var BRIEF_BUILTINS = [
|
|
5039
5759
|
"tier",
|
|
5040
5760
|
"testPlan",
|
|
@@ -5054,17 +5774,17 @@ var BRIEF_BUILTINS = [
|
|
|
5054
5774
|
"briefSections",
|
|
5055
5775
|
"milestoneShape"
|
|
5056
5776
|
];
|
|
5057
|
-
var BriefSectionSchema =
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5777
|
+
var BriefSectionSchema = z4.union([
|
|
5778
|
+
z4.object({ builtin: z4.enum(BRIEF_BUILTINS) }),
|
|
5779
|
+
z4.object({ heading: z4.string().min(1), name: z4.string().optional() }),
|
|
5780
|
+
z4.object({ field: z4.string().min(1), name: z4.string().optional() }),
|
|
5781
|
+
z4.object({ phrase: z4.string().min(1), name: z4.string().optional() })
|
|
5062
5782
|
]);
|
|
5063
|
-
var BriefSchemaSchema =
|
|
5064
|
-
pr:
|
|
5065
|
-
issue:
|
|
5066
|
-
milestone:
|
|
5067
|
-
ack:
|
|
5783
|
+
var BriefSchemaSchema = z4.object({
|
|
5784
|
+
pr: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
|
|
5785
|
+
issue: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
|
|
5786
|
+
milestone: z4.object({ sections: z4.array(BriefSectionSchema) }).optional(),
|
|
5787
|
+
ack: z4.array(z4.enum(BRIEF_BUILTINS)).optional()
|
|
5068
5788
|
});
|
|
5069
5789
|
function isSafeRepoRelPath(p) {
|
|
5070
5790
|
if (p.length === 0)
|
|
@@ -5073,7 +5793,7 @@ function isSafeRepoRelPath(p) {
|
|
|
5073
5793
|
return false;
|
|
5074
5794
|
return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
|
|
5075
5795
|
}
|
|
5076
|
-
var SafeRepoRelPath =
|
|
5796
|
+
var SafeRepoRelPath = z4.string().refine(isSafeRepoRelPath, {
|
|
5077
5797
|
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
5078
5798
|
});
|
|
5079
5799
|
var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
|
|
@@ -5083,22 +5803,22 @@ function isCanonicalHookBlockPath(p) {
|
|
|
5083
5803
|
var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
|
|
5084
5804
|
message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
|
|
5085
5805
|
});
|
|
5086
|
-
var ManagedBlockRecordSchema =
|
|
5806
|
+
var ManagedBlockRecordSchema = z4.object({
|
|
5087
5807
|
path: ManagedHookBlockPath,
|
|
5088
|
-
marker:
|
|
5089
|
-
comment:
|
|
5808
|
+
marker: z4.string(),
|
|
5809
|
+
comment: z4.enum(["hash", "html"])
|
|
5090
5810
|
});
|
|
5091
|
-
var ManagedManifestSchema =
|
|
5092
|
-
version:
|
|
5093
|
-
files:
|
|
5094
|
-
blocks:
|
|
5095
|
-
labels:
|
|
5096
|
-
agents:
|
|
5811
|
+
var ManagedManifestSchema = z4.object({
|
|
5812
|
+
version: z4.number().int().positive(),
|
|
5813
|
+
files: z4.array(SafeRepoRelPath),
|
|
5814
|
+
blocks: z4.array(ManagedBlockRecordSchema),
|
|
5815
|
+
labels: z4.array(z4.string()),
|
|
5816
|
+
agents: z4.array(z4.enum(AGENT_VENDORS)).optional()
|
|
5097
5817
|
});
|
|
5098
|
-
var ProjectEntrySchema =
|
|
5099
|
-
name:
|
|
5100
|
-
description:
|
|
5101
|
-
path:
|
|
5818
|
+
var ProjectEntrySchema = z4.object({
|
|
5819
|
+
name: z4.string().min(1),
|
|
5820
|
+
description: z4.string().min(1).optional(),
|
|
5821
|
+
path: z4.string().min(1).optional()
|
|
5102
5822
|
});
|
|
5103
5823
|
function parseTokensCollectDeclaration(value) {
|
|
5104
5824
|
const m = value.trim().match(/^(\S+)\s+(\S+)$/);
|
|
@@ -5109,34 +5829,55 @@ function parseTokensCollectDeclaration(value) {
|
|
|
5109
5829
|
return null;
|
|
5110
5830
|
return { interpreter, script };
|
|
5111
5831
|
}
|
|
5112
|
-
var VinayaConfigSchema =
|
|
5113
|
-
rings:
|
|
5114
|
-
ring1_forgeWriteInterception:
|
|
5115
|
-
ring2_asyncAudits:
|
|
5832
|
+
var VinayaConfigSchema = z4.object({
|
|
5833
|
+
rings: z4.object({
|
|
5834
|
+
ring1_forgeWriteInterception: z4.boolean(),
|
|
5835
|
+
ring2_asyncAudits: z4.boolean()
|
|
5116
5836
|
}).optional(),
|
|
5117
|
-
checks:
|
|
5118
|
-
roles:
|
|
5837
|
+
checks: z4.record(z4.string(), CheckEntrySchema).optional(),
|
|
5838
|
+
roles: z4.record(z4.string(), RoleEntrySchema).optional(),
|
|
5119
5839
|
briefSchema: BriefSchemaSchema.optional(),
|
|
5120
5840
|
managed: ManagedManifestSchema.optional(),
|
|
5121
|
-
principals:
|
|
5122
|
-
releaseActor:
|
|
5123
|
-
ci:
|
|
5124
|
-
tokens:
|
|
5125
|
-
collect:
|
|
5841
|
+
principals: z4.array(z4.string()).min(1).optional(),
|
|
5842
|
+
releaseActor: z4.string().min(1).optional(),
|
|
5843
|
+
ci: z4.object({ setup: z4.string().min(1) }).optional(),
|
|
5844
|
+
tokens: z4.object({
|
|
5845
|
+
collect: z4.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
|
|
5126
5846
|
message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
|
|
5127
5847
|
})
|
|
5128
5848
|
}).optional(),
|
|
5129
|
-
blastRadius:
|
|
5130
|
-
proseGates:
|
|
5131
|
-
doctrineRoot:
|
|
5132
|
-
readerFacingPrefix:
|
|
5133
|
-
readerFacingSuffix:
|
|
5134
|
-
legacySlugDir:
|
|
5849
|
+
blastRadius: z4.object({ extraDomains: z4.array(z4.string()).optional() }).optional(),
|
|
5850
|
+
proseGates: z4.object({
|
|
5851
|
+
doctrineRoot: z4.string().min(1).optional(),
|
|
5852
|
+
readerFacingPrefix: z4.string().min(1).optional(),
|
|
5853
|
+
readerFacingSuffix: z4.string().min(1).optional(),
|
|
5854
|
+
legacySlugDir: z4.string().min(1).optional()
|
|
5855
|
+
}).optional(),
|
|
5856
|
+
projects: z4.array(ProjectEntrySchema).optional(),
|
|
5857
|
+
dispatch: z4.object({
|
|
5858
|
+
timeoutMs: z4.number().int().positive().optional(),
|
|
5859
|
+
killGraceMs: z4.number().int().positive().optional(),
|
|
5860
|
+
agent: z4.enum(["claude", "codex", "gemini"]).optional()
|
|
5861
|
+
}).optional(),
|
|
5862
|
+
reviewPolicy: z4.object({
|
|
5863
|
+
codeReviewThreshold: z4.string().min(1).optional(),
|
|
5864
|
+
securityThreshold: z4.string().min(1).optional(),
|
|
5865
|
+
maxRounds: z4.number().optional()
|
|
5135
5866
|
}).optional(),
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5867
|
+
prePush: z4.object({
|
|
5868
|
+
alwaysRun: z4.array(z4.string()).optional()
|
|
5869
|
+
}).optional(),
|
|
5870
|
+
report: z4.object({
|
|
5871
|
+
commandTimeoutMs: z4.number().int().positive().optional()
|
|
5872
|
+
}).superRefine((report, ctx) => {
|
|
5873
|
+
const commandTimeoutMs = report.commandTimeoutMs;
|
|
5874
|
+
if (commandTimeoutMs !== undefined && commandTimeoutMs > MAX_REPORT_COMMAND_TIMEOUT_MS) {
|
|
5875
|
+
ctx.addIssue({
|
|
5876
|
+
code: z4.ZodIssueCode.custom,
|
|
5877
|
+
path: ["commandTimeoutMs"],
|
|
5878
|
+
message: "must be at most 3600000 (1 hour)"
|
|
5879
|
+
});
|
|
5880
|
+
}
|
|
5140
5881
|
}).optional()
|
|
5141
5882
|
});
|
|
5142
5883
|
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
@@ -5211,6 +5952,28 @@ function resolvePrincipalAllowlist(config) {
|
|
|
5211
5952
|
function resolveReleaseActor(config) {
|
|
5212
5953
|
return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
|
|
5213
5954
|
}
|
|
5955
|
+
function resolveReviewPolicy(config) {
|
|
5956
|
+
const raw = config?.reviewPolicy;
|
|
5957
|
+
if (!raw)
|
|
5958
|
+
return DEFAULT_REVIEW_POLICY;
|
|
5959
|
+
const codeReviewThreshold = raw.codeReviewThreshold ?? DEFAULT_REVIEW_POLICY.codeReviewThreshold;
|
|
5960
|
+
if (!isKnownSeverity(CODE_REVIEW_SEVERITY_ORDER, codeReviewThreshold)) {
|
|
5961
|
+
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.`);
|
|
5962
|
+
}
|
|
5963
|
+
const securityThreshold = raw.securityThreshold ?? DEFAULT_REVIEW_POLICY.securityThreshold;
|
|
5964
|
+
if (!isKnownSeverity(SECURITY_SEVERITY_ORDER, securityThreshold)) {
|
|
5965
|
+
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.`);
|
|
5966
|
+
}
|
|
5967
|
+
const maxRounds = raw.maxRounds ?? DEFAULT_REVIEW_POLICY.maxRounds;
|
|
5968
|
+
if (!Number.isInteger(maxRounds) || maxRounds < 1) {
|
|
5969
|
+
throw new Error(`vinaya.config.json: reviewPolicy.maxRounds "${maxRounds}" is not a positive integer — fix the config, this never falls back to a default.`);
|
|
5970
|
+
}
|
|
5971
|
+
return {
|
|
5972
|
+
codeReviewThreshold,
|
|
5973
|
+
securityThreshold,
|
|
5974
|
+
maxRounds
|
|
5975
|
+
};
|
|
5976
|
+
}
|
|
5214
5977
|
function trustAnchorRepo() {
|
|
5215
5978
|
const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
5216
5979
|
const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
|
|
@@ -5290,12 +6053,40 @@ function loadConfig() {
|
|
|
5290
6053
|
}
|
|
5291
6054
|
var TOKENS_COLLECT_TRUST_PATH = join(GLOBAL_VINAYA_HOME, "tokens-collect-trust.json");
|
|
5292
6055
|
|
|
6056
|
+
// src/lib/patch-id.ts
|
|
6057
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
6058
|
+
function patchIdAt(base, sha) {
|
|
6059
|
+
try {
|
|
6060
|
+
execFileSync3("git", ["fetch", "--quiet", "origin", sha], { stdio: ["ignore", "ignore", "ignore"] });
|
|
6061
|
+
} catch {}
|
|
6062
|
+
try {
|
|
6063
|
+
const diff = execFileSync3("git", ["diff", `origin/${base}...${sha}`], {
|
|
6064
|
+
encoding: "utf8",
|
|
6065
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
6066
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6067
|
+
});
|
|
6068
|
+
if (diff === "")
|
|
6069
|
+
return null;
|
|
6070
|
+
const out = execFileSync3("git", ["patch-id", "--stable"], {
|
|
6071
|
+
input: diff,
|
|
6072
|
+
encoding: "utf8",
|
|
6073
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6074
|
+
}).trim();
|
|
6075
|
+
const id = out.split(/\s+/)[0] ?? "";
|
|
6076
|
+
return id === "" ? null : id;
|
|
6077
|
+
} catch {
|
|
6078
|
+
return null;
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
|
|
6082
|
+
// src/lib/review-gate-check-name.ts
|
|
6083
|
+
var REVIEW_GATE_CHECK_RUN_NAME = "vinaya review gate";
|
|
6084
|
+
|
|
5293
6085
|
// src/checks/bin/check-review-gate.ts
|
|
5294
6086
|
var CHECK_NAME = "review-gate";
|
|
5295
|
-
var OWN_CHECK_RUN_NAME = "vinaya review gate";
|
|
5296
6087
|
function fetchPr(prNumber) {
|
|
5297
6088
|
try {
|
|
5298
|
-
const out =
|
|
6089
|
+
const out = execFileSync4("gh", ["pr", "view", String(prNumber), "--json", "number,comments,labels,headRefName,headRefOid,baseRefName,body"], {
|
|
5299
6090
|
encoding: "utf8",
|
|
5300
6091
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5301
6092
|
});
|
|
@@ -5305,27 +6096,66 @@ function fetchPr(prNumber) {
|
|
|
5305
6096
|
}
|
|
5306
6097
|
}
|
|
5307
6098
|
function fetchIssueBodyForObjectives(issueNumber) {
|
|
5308
|
-
return
|
|
6099
|
+
return execFileSync4("gh", ["issue", "view", String(issueNumber), "--json", "body", "--jq", ".body"], {
|
|
5309
6100
|
encoding: "utf8",
|
|
5310
6101
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5311
6102
|
});
|
|
5312
6103
|
}
|
|
6104
|
+
function fetchIssueCommentsForBrief(issueNumber) {
|
|
6105
|
+
const out = execFileSync4("gh", ["issue", "view", String(issueNumber), "--json", "comments"], {
|
|
6106
|
+
encoding: "utf8",
|
|
6107
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6108
|
+
});
|
|
6109
|
+
const parsed = JSON.parse(out);
|
|
6110
|
+
return parsed.comments.map((c) => ({ body: c.body, author: c.author?.login ?? null }));
|
|
6111
|
+
}
|
|
6112
|
+
function resolveBriefHash(pr, principalAllowlist) {
|
|
6113
|
+
const { issue } = extractIssue(pr.body);
|
|
6114
|
+
if (issue === null)
|
|
6115
|
+
return null;
|
|
6116
|
+
let comments;
|
|
6117
|
+
try {
|
|
6118
|
+
comments = fetchIssueCommentsForBrief(issue);
|
|
6119
|
+
} catch (err) {
|
|
6120
|
+
if (isIssueNotFoundError(err)) {
|
|
6121
|
+
emitCheckError({
|
|
6122
|
+
schema: CHECK_SCHEMA_VERSION,
|
|
6123
|
+
check: CHECK_NAME,
|
|
6124
|
+
severity: "error",
|
|
6125
|
+
message: `review-gate severity:infra — Issue #${issue} does not resolve via \`gh issue view\` — cannot verify the brief-hash binding for a PR whose linked Issue no longer exists.`,
|
|
6126
|
+
agent_recovery_prompt: `Restore Issue #${issue}, fix \`Closes #N\` to name a real Issue, or have a principal apply the \`vinaya/waiver:review\` label, then re-run \`vinaya check review-gate\`.`
|
|
6127
|
+
});
|
|
6128
|
+
process.exit(1);
|
|
6129
|
+
}
|
|
6130
|
+
emitCheckError({
|
|
6131
|
+
schema: CHECK_SCHEMA_VERSION,
|
|
6132
|
+
check: CHECK_NAME,
|
|
6133
|
+
severity: "error",
|
|
6134
|
+
message: `review-gate severity:infra — could not fetch Issue #${issue}'s comments via \`gh issue view\` to resolve its frozen-brief hash: ${err.message}`,
|
|
6135
|
+
agent_recovery_prompt: "Confirm `gh auth status` passes and the Issue number is correct, then re-run `vinaya check review-gate`."
|
|
6136
|
+
});
|
|
6137
|
+
process.exit(1);
|
|
6138
|
+
}
|
|
6139
|
+
const frozen = resolveNewestFrozenBrief(comments, principalAllowlist);
|
|
6140
|
+
return frozen ? briefHash(frozen.content) : null;
|
|
6141
|
+
}
|
|
5313
6142
|
function resolveObjectivesVersion(pr) {
|
|
5314
6143
|
const { issue } = extractIssue(pr.body);
|
|
5315
|
-
|
|
6144
|
+
const source = resolveObjectivesSource(pr.body, issue, OBJECTIVES_SINCE_ISSUE);
|
|
6145
|
+
if (source.kind === "none")
|
|
5316
6146
|
return null;
|
|
5317
|
-
if (
|
|
6147
|
+
if (source.kind === "issue") {
|
|
5318
6148
|
let issueBody;
|
|
5319
6149
|
try {
|
|
5320
|
-
issueBody = fetchIssueBodyForObjectives(issue);
|
|
6150
|
+
issueBody = fetchIssueBodyForObjectives(source.issue);
|
|
5321
6151
|
} catch (err) {
|
|
5322
6152
|
if (isIssueNotFoundError(err)) {
|
|
5323
6153
|
emitCheckError({
|
|
5324
6154
|
schema: CHECK_SCHEMA_VERSION,
|
|
5325
6155
|
check: CHECK_NAME,
|
|
5326
6156
|
severity: "error",
|
|
5327
|
-
message: `review-gate severity:infra — Issue #${issue} does not resolve via \`gh issue view\` — cannot verify the objectives-version binding for a PR whose linked Issue is at/above the objectives cutover.`,
|
|
5328
|
-
agent_recovery_prompt: `Restore Issue #${issue}, fix \`Closes #N\` to name a real Issue, or have a principal apply the \`vinaya/waiver:review\` label, then re-run \`vinaya check review-gate\`.`
|
|
6157
|
+
message: `review-gate severity:infra — Issue #${source.issue} does not resolve via \`gh issue view\` — cannot verify the objectives-version binding for a PR whose linked Issue is at/above the objectives cutover.`,
|
|
6158
|
+
agent_recovery_prompt: `Restore Issue #${source.issue}, fix \`Closes #N\` to name a real Issue, or have a principal apply the \`vinaya/waiver:review\` label, then re-run \`vinaya check review-gate\`.`
|
|
5329
6159
|
});
|
|
5330
6160
|
process.exit(1);
|
|
5331
6161
|
}
|
|
@@ -5333,7 +6163,7 @@ function resolveObjectivesVersion(pr) {
|
|
|
5333
6163
|
schema: CHECK_SCHEMA_VERSION,
|
|
5334
6164
|
check: CHECK_NAME,
|
|
5335
6165
|
severity: "error",
|
|
5336
|
-
message: `review-gate severity:infra — could not fetch Issue #${issue}'s body via \`gh issue view\` to resolve its objectives version: ${err.message}`,
|
|
6166
|
+
message: `review-gate severity:infra — could not fetch Issue #${source.issue}'s body via \`gh issue view\` to resolve its objectives version: ${err.message}`,
|
|
5337
6167
|
agent_recovery_prompt: "Confirm `gh auth status` passes and the Issue number is correct, then re-run `vinaya check review-gate`."
|
|
5338
6168
|
});
|
|
5339
6169
|
process.exit(1);
|
|
@@ -5344,32 +6174,38 @@ function resolveObjectivesVersion(pr) {
|
|
|
5344
6174
|
schema: CHECK_SCHEMA_VERSION,
|
|
5345
6175
|
check: CHECK_NAME,
|
|
5346
6176
|
severity: "error",
|
|
5347
|
-
message: `review-gate severity:infra — Issue #${issue}'s \`## Objectives\` section does not parse (${parsed.errors.join("; ")}) — cannot verify the objectives-version binding.`,
|
|
5348
|
-
agent_recovery_prompt: `Fix Issue #${issue}'s \`## Objectives\` section, or have a principal apply the \`vinaya/waiver:review\` label, then re-run \`vinaya check review-gate\`.`
|
|
6177
|
+
message: `review-gate severity:infra — Issue #${source.issue}'s \`## Objectives\` section does not parse (${parsed.errors.join("; ")}) — cannot verify the objectives-version binding.`,
|
|
6178
|
+
agent_recovery_prompt: `Fix Issue #${source.issue}'s \`## Objectives\` section, or have a principal apply the \`vinaya/waiver:review\` label, then re-run \`vinaya check review-gate\`.`
|
|
5349
6179
|
});
|
|
5350
6180
|
process.exit(1);
|
|
5351
6181
|
}
|
|
5352
6182
|
return objectivesVersion(parsed.objectives);
|
|
5353
6183
|
}
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
process.exit(1);
|
|
5365
|
-
}
|
|
5366
|
-
return objectivesVersion(own.objectives);
|
|
6184
|
+
const own = objectivesOf(pr.body);
|
|
6185
|
+
if (!own.ok) {
|
|
6186
|
+
emitCheckError({
|
|
6187
|
+
schema: CHECK_SCHEMA_VERSION,
|
|
6188
|
+
check: CHECK_NAME,
|
|
6189
|
+
severity: "error",
|
|
6190
|
+
message: `review-gate severity:infra — this PR body's own \`## Objectives\` section does not parse (${own.errors.join("; ")}) — cannot verify the objectives-version binding.`,
|
|
6191
|
+
agent_recovery_prompt: "Fix the PR body's `## Objectives` section, or have a principal apply the `vinaya/waiver:review` label, then re-run `vinaya check review-gate`."
|
|
6192
|
+
});
|
|
6193
|
+
process.exit(1);
|
|
5367
6194
|
}
|
|
5368
|
-
return
|
|
6195
|
+
return objectivesVersion(own.objectives);
|
|
6196
|
+
}
|
|
6197
|
+
function uncheckedPrincipalReason(body, branch) {
|
|
6198
|
+
const result = evaluateTestPlanGate(body, branch);
|
|
6199
|
+
if (result.verdict !== "fail")
|
|
6200
|
+
return null;
|
|
6201
|
+
const uncheckedLines = result.messages.filter((m) => /^\s*[-*]\s+\[\s\]/.test(m)).map((m) => m.trim());
|
|
6202
|
+
if (uncheckedLines.length === 0)
|
|
6203
|
+
return null;
|
|
6204
|
+
return `unticked [principal] Test Plan item(s) — ${uncheckedLines.join("; ")}`;
|
|
5369
6205
|
}
|
|
5370
6206
|
function shaFromLsRemote(branch) {
|
|
5371
6207
|
try {
|
|
5372
|
-
const out =
|
|
6208
|
+
const out = execFileSync4("git", ["ls-remote", "origin", `refs/heads/${branch}`], {
|
|
5373
6209
|
encoding: "utf8",
|
|
5374
6210
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5375
6211
|
}).trim();
|
|
@@ -5381,7 +6217,7 @@ function shaFromLsRemote(branch) {
|
|
|
5381
6217
|
}
|
|
5382
6218
|
function shaFromGhApi(branch) {
|
|
5383
6219
|
try {
|
|
5384
|
-
const out =
|
|
6220
|
+
const out = execFileSync4("gh", ["api", `repos/{owner}/{repo}/git/ref/heads/${branch}`, "--jq", ".object.sha"], {
|
|
5385
6221
|
encoding: "utf8",
|
|
5386
6222
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5387
6223
|
}).trim();
|
|
@@ -5398,29 +6234,6 @@ function resolveTrueHeadSha(pr) {
|
|
|
5398
6234
|
}
|
|
5399
6235
|
return trueSha;
|
|
5400
6236
|
}
|
|
5401
|
-
function patchIdAt(base, sha) {
|
|
5402
|
-
try {
|
|
5403
|
-
execFileSync3("git", ["fetch", "--quiet", "origin", sha], { stdio: ["ignore", "ignore", "ignore"] });
|
|
5404
|
-
} catch {}
|
|
5405
|
-
try {
|
|
5406
|
-
const diff = execFileSync3("git", ["diff", `origin/${base}...${sha}`], {
|
|
5407
|
-
encoding: "utf8",
|
|
5408
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
5409
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
5410
|
-
});
|
|
5411
|
-
if (diff === "")
|
|
5412
|
-
return null;
|
|
5413
|
-
const out = execFileSync3("git", ["patch-id", "--stable"], {
|
|
5414
|
-
input: diff,
|
|
5415
|
-
encoding: "utf8",
|
|
5416
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5417
|
-
}).trim();
|
|
5418
|
-
const id = out.split(/\s+/)[0] ?? "";
|
|
5419
|
-
return id === "" ? null : id;
|
|
5420
|
-
} catch {
|
|
5421
|
-
return null;
|
|
5422
|
-
}
|
|
5423
|
-
}
|
|
5424
6237
|
function bucketFor(run2) {
|
|
5425
6238
|
if (run2.status !== "completed")
|
|
5426
6239
|
return "pending";
|
|
@@ -5438,7 +6251,7 @@ function bucketFor(run2) {
|
|
|
5438
6251
|
}
|
|
5439
6252
|
function fetchMechanicalChecks(headSha) {
|
|
5440
6253
|
try {
|
|
5441
|
-
const out =
|
|
6254
|
+
const out = execFileSync4("gh", [
|
|
5442
6255
|
"api",
|
|
5443
6256
|
`repos/{owner}/{repo}/commits/${headSha}/check-runs`,
|
|
5444
6257
|
"--paginate",
|
|
@@ -5453,14 +6266,14 @@ function fetchMechanicalChecks(headSha) {
|
|
|
5453
6266
|
if (!seen || run2.id > seen.id)
|
|
5454
6267
|
latestByName.set(run2.name, run2);
|
|
5455
6268
|
}
|
|
5456
|
-
return Array.from(latestByName.values()).filter((r) => r.name !==
|
|
6269
|
+
return Array.from(latestByName.values()).filter((r) => r.name !== REVIEW_GATE_CHECK_RUN_NAME).map((r) => ({ name: r.name, bucket: bucketFor(r) }));
|
|
5457
6270
|
} catch {
|
|
5458
6271
|
return null;
|
|
5459
6272
|
}
|
|
5460
6273
|
}
|
|
5461
6274
|
function fetchWaiverLabelActor(prNumber, label2) {
|
|
5462
6275
|
try {
|
|
5463
|
-
const out =
|
|
6276
|
+
const out = execFileSync4("gh", ["api", `repos/{owner}/{repo}/issues/${prNumber}/timeline`, "--paginate"], {
|
|
5464
6277
|
encoding: "utf8",
|
|
5465
6278
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5466
6279
|
});
|
|
@@ -5513,7 +6326,21 @@ function main() {
|
|
|
5513
6326
|
});
|
|
5514
6327
|
process.exit(1);
|
|
5515
6328
|
}
|
|
5516
|
-
const
|
|
6329
|
+
const trustAnchorConfig = loadTrustAnchorConfig();
|
|
6330
|
+
const principalAllowlist = resolvePrincipalAllowlist(trustAnchorConfig);
|
|
6331
|
+
let reviewPolicy;
|
|
6332
|
+
try {
|
|
6333
|
+
reviewPolicy = resolveReviewPolicy(trustAnchorConfig);
|
|
6334
|
+
} catch (err) {
|
|
6335
|
+
emitCheckError({
|
|
6336
|
+
schema: CHECK_SCHEMA_VERSION,
|
|
6337
|
+
check: CHECK_NAME,
|
|
6338
|
+
severity: "error",
|
|
6339
|
+
message: `review-gate severity:infra — ${err instanceof Error ? err.message : String(err)}`,
|
|
6340
|
+
agent_recovery_prompt: "Fix `reviewPolicy` in the default branch's `vinaya.config.json` to a known severity on each role's own scale, then re-run `vinaya check review-gate`."
|
|
6341
|
+
});
|
|
6342
|
+
process.exit(1);
|
|
6343
|
+
}
|
|
5517
6344
|
const waived = isWaiverLabelActorVerified({
|
|
5518
6345
|
label: WAIVER_LABEL_REVIEW,
|
|
5519
6346
|
labels,
|
|
@@ -5528,8 +6355,12 @@ function main() {
|
|
|
5528
6355
|
mechanicalChecks,
|
|
5529
6356
|
headSha,
|
|
5530
6357
|
patchIdOf: (sha) => patchIdAt(pr.baseRefName, sha),
|
|
5531
|
-
objectivesVersion: waived ? null : resolveObjectivesVersion(pr)
|
|
6358
|
+
objectivesVersion: waived ? null : resolveObjectivesVersion(pr),
|
|
6359
|
+
rulingOrdinal: newestPrincipalRulingOrdinal(pr.comments.map((c) => ({ body: c.body, author: c.author?.login ?? null })), principalAllowlist),
|
|
6360
|
+
policy: reviewPolicy,
|
|
6361
|
+
briefHash: waived ? null : resolveBriefHash(pr, principalAllowlist)
|
|
5532
6362
|
});
|
|
6363
|
+
let failed = false;
|
|
5533
6364
|
if (result.verdict === "fail") {
|
|
5534
6365
|
emitCheckError({
|
|
5535
6366
|
schema: CHECK_SCHEMA_VERSION,
|
|
@@ -5538,8 +6369,24 @@ function main() {
|
|
|
5538
6369
|
message: `review-gate (PR #${prNumber}): ${result.reason}`,
|
|
5539
6370
|
agent_recovery_prompt: "Wait for a code-reviewer APPROVE and a security-review PASS on this PR (or ask a principal to apply the `vinaya/waiver:review` label), then re-run `vinaya check review-gate`."
|
|
5540
6371
|
});
|
|
5541
|
-
|
|
6372
|
+
failed = true;
|
|
6373
|
+
}
|
|
6374
|
+
const principalReason = uncheckedPrincipalReason(pr.body, pr.headRefName);
|
|
6375
|
+
if (principalReason !== null) {
|
|
6376
|
+
emitCheckError({
|
|
6377
|
+
schema: CHECK_SCHEMA_VERSION,
|
|
6378
|
+
check: CHECK_NAME,
|
|
6379
|
+
severity: "error",
|
|
6380
|
+
message: `review-gate (PR #${prNumber}): ${principalReason}`,
|
|
6381
|
+
agent_recovery_prompt: "Nothing for the Developer to fix here — wait for the Principal to verify in a real signed-in browser and tick each `[principal]` Test Plan box, then re-run `vinaya check review-gate`."
|
|
6382
|
+
});
|
|
6383
|
+
failed = true;
|
|
5542
6384
|
}
|
|
5543
|
-
process.exit(0);
|
|
6385
|
+
process.exit(failed ? 1 : 0);
|
|
5544
6386
|
}
|
|
5545
|
-
main
|
|
6387
|
+
if (__require.main == __require.module) {
|
|
6388
|
+
main();
|
|
6389
|
+
}
|
|
6390
|
+
export {
|
|
6391
|
+
uncheckedPrincipalReason
|
|
6392
|
+
};
|