@attalabs/vinaya 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/aeg-root/enforcement.md +2 -2
- package/aeg-root/milestone-model.md +2 -0
- package/aeg-root/process.md +1 -1
- package/aeg-root/roles/planner.md +2 -0
- package/aeg-root/roles/principal.md +4 -2
- package/aeg-root/roles/reviewer.md +6 -2
- package/aeg-root/roles/security.md +6 -2
- package/dist/checks/bin/check-body-bare-digits.js +128 -22
- package/dist/checks/bin/check-branch-topology.js +148 -24
- package/dist/checks/bin/check-brief-shape.js +430 -32
- package/dist/checks/bin/check-changeset-coverage.js +148 -24
- package/dist/checks/bin/check-closes-n.js +148 -24
- package/dist/checks/bin/check-coherence.js +148 -24
- package/dist/checks/bin/check-dead-branch-push.js +110 -22
- package/dist/checks/bin/check-dispatch-readiness.js +148 -24
- package/dist/checks/bin/check-doc-coverage-push.js +148 -24
- package/dist/checks/bin/check-doc-coverage.js +148 -24
- package/dist/checks/bin/check-doctrine-no-procedures.js +110 -22
- package/dist/checks/bin/check-doctrine-portability.js +148 -24
- package/dist/checks/bin/check-evidence-fresh.js +650 -63
- package/dist/checks/bin/check-exec-bits.js +148 -24
- package/dist/checks/bin/check-first-push-dispatch.js +148 -24
- package/dist/checks/bin/check-issue-assignment.js +148 -24
- package/dist/checks/bin/check-main-branch-refusal.js +110 -22
- package/dist/checks/bin/check-no-disk-state.js +110 -22
- package/dist/checks/bin/check-pr-premise-reassert.js +110 -22
- package/dist/checks/bin/check-pr-report-density.js +110 -22
- package/dist/checks/bin/check-quoted-command.js +148 -24
- package/dist/checks/bin/check-reader-resolvable-prose.js +148 -24
- package/dist/checks/bin/check-registry-gates.js +110 -22
- package/dist/checks/bin/check-retired-vocabulary.js +148 -24
- package/dist/checks/bin/check-review-gate.js +169 -77
- package/dist/checks/bin/check-single-plan-pr.js +110 -22
- package/dist/checks/bin/check-surface-scope.js +148 -24
- package/dist/checks/bin/check-test-plan.js +110 -22
- package/dist/checks/bin/check-token-collection-wired.js +110 -22
- package/dist/checks/bin/check-token-report.js +110 -22
- package/dist/checks/bin/check-workspace-escape.js +148 -24
- package/dist/index.js +1584 -572
- package/package.json +1 -1
|
@@ -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,7 @@ 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;
|
|
1951
1957
|
function firstFiveLines(comment) {
|
|
1952
1958
|
return comment.split(`
|
|
1953
1959
|
`).slice(0, 5).join(`
|
|
@@ -1961,6 +1967,15 @@ function extractObjectivesVersion(comment) {
|
|
|
1961
1967
|
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1962
1968
|
return m ? m[1].toLowerCase() : null;
|
|
1963
1969
|
}
|
|
1970
|
+
function firstSevenLines(comment) {
|
|
1971
|
+
return comment.split(`
|
|
1972
|
+
`).slice(0, 7).join(`
|
|
1973
|
+
`);
|
|
1974
|
+
}
|
|
1975
|
+
function extractRulingOrdinal(comment) {
|
|
1976
|
+
const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
|
|
1977
|
+
return m ? Number.parseInt(m[1], 10) : null;
|
|
1978
|
+
}
|
|
1964
1979
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1965
1980
|
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1966
1981
|
if (candidates.length === 0) {
|
|
@@ -1968,6 +1983,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1968
1983
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1969
1984
|
headSha: null,
|
|
1970
1985
|
objectivesVersion: null,
|
|
1986
|
+
rulingOrdinal: null,
|
|
1971
1987
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1972
1988
|
};
|
|
1973
1989
|
}
|
|
@@ -1978,6 +1994,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1978
1994
|
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1979
1995
|
headSha: null,
|
|
1980
1996
|
objectivesVersion: null,
|
|
1997
|
+
rulingOrdinal: null,
|
|
1981
1998
|
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1982
1999
|
};
|
|
1983
2000
|
}
|
|
@@ -1985,6 +2002,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1985
2002
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1986
2003
|
headSha: extractHeadSha(latest),
|
|
1987
2004
|
objectivesVersion: extractObjectivesVersion(latest),
|
|
2005
|
+
rulingOrdinal: extractRulingOrdinal(latest),
|
|
1988
2006
|
danglingNote: null
|
|
1989
2007
|
};
|
|
1990
2008
|
}
|
|
@@ -2346,6 +2364,9 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
2346
2364
|
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2347
2365
|
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2348
2366
|
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2367
|
+
function maskedForHeadingSearch(body) {
|
|
2368
|
+
return maskDetailsBlocks(maskCode(body));
|
|
2369
|
+
}
|
|
2349
2370
|
function hasBacktickedPath(text) {
|
|
2350
2371
|
let i = 0;
|
|
2351
2372
|
while (i < text.length) {
|
|
@@ -2368,16 +2389,22 @@ function stripObjectiveBackticks(text) {
|
|
|
2368
2389
|
function wordCount(text) {
|
|
2369
2390
|
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2370
2391
|
}
|
|
2371
|
-
function
|
|
2372
|
-
const
|
|
2392
|
+
function objectivesSectionBounds(body) {
|
|
2393
|
+
const masked = maskedForHeadingSearch(body);
|
|
2394
|
+
const heading = HEADING_RE.exec(masked);
|
|
2373
2395
|
if (!heading)
|
|
2374
2396
|
return null;
|
|
2375
|
-
const
|
|
2397
|
+
const start = heading.index + heading[0].length;
|
|
2398
|
+
const afterHeading = masked.slice(start);
|
|
2376
2399
|
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2377
|
-
return next ?
|
|
2400
|
+
return { start, end: next ? start + next.index : body.length };
|
|
2401
|
+
}
|
|
2402
|
+
function objectivesSectionText(body) {
|
|
2403
|
+
const bounds = objectivesSectionBounds(body);
|
|
2404
|
+
return bounds === null ? null : body.slice(bounds.start, bounds.end);
|
|
2378
2405
|
}
|
|
2379
2406
|
function hasObjectivesHeading(body) {
|
|
2380
|
-
return HEADING_RE.test(body);
|
|
2407
|
+
return HEADING_RE.test(maskedForHeadingSearch(body));
|
|
2381
2408
|
}
|
|
2382
2409
|
function objectivesOf(body) {
|
|
2383
2410
|
const section = objectivesSectionText(body);
|
|
@@ -2437,6 +2464,15 @@ function isIssueNotFoundError(err) {
|
|
|
2437
2464
|
`);
|
|
2438
2465
|
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2439
2466
|
}
|
|
2467
|
+
function resolveObjectivesSource(prBody, issue, cutoverIssue) {
|
|
2468
|
+
if (issue !== null && issue < cutoverIssue)
|
|
2469
|
+
return { kind: "none" };
|
|
2470
|
+
if (issue !== null)
|
|
2471
|
+
return { kind: "issue", issue };
|
|
2472
|
+
if (hasObjectivesHeading(prBody))
|
|
2473
|
+
return { kind: "body" };
|
|
2474
|
+
return { kind: "none" };
|
|
2475
|
+
}
|
|
2440
2476
|
|
|
2441
2477
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2442
2478
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -2938,17 +2974,40 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2938
2974
|
];
|
|
2939
2975
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2940
2976
|
}
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
`
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2977
|
+
function contentAfterNLines(body, n) {
|
|
2978
|
+
let idx = -1;
|
|
2979
|
+
for (let i = 0;i < n; i++) {
|
|
2980
|
+
idx = body.indexOf(`
|
|
2981
|
+
`, idx + 1);
|
|
2982
|
+
if (idx === -1)
|
|
2983
|
+
return "";
|
|
2984
|
+
}
|
|
2985
|
+
return body.slice(idx + 1);
|
|
2986
|
+
}
|
|
2987
|
+
var BRIEF_MARKER_LINE_RE = /^<!-- aeg:brief:v(\d+) -->$/;
|
|
2988
|
+
function parseBriefMarkerVersion(firstLine) {
|
|
2989
|
+
const m = BRIEF_MARKER_LINE_RE.exec(firstLine.trim());
|
|
2990
|
+
if (!m)
|
|
2991
|
+
return null;
|
|
2992
|
+
const version = Number.parseInt(m[1], 10);
|
|
2993
|
+
return Number.isInteger(version) && version >= 1 ? version : null;
|
|
2994
|
+
}
|
|
2995
|
+
function frozenBriefContent(body, version) {
|
|
2996
|
+
return contentAfterNLines(body, version === 1 ? 2 : 3);
|
|
2997
|
+
}
|
|
2998
|
+
function resolveNewestFrozenBrief(comments, allowlist) {
|
|
2999
|
+
let best = null;
|
|
3000
|
+
for (const c of comments) {
|
|
3001
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3002
|
+
continue;
|
|
3003
|
+
const version = parseBriefMarkerVersion(c.body.split(`
|
|
3004
|
+
`)[0] ?? "");
|
|
3005
|
+
if (version === null)
|
|
3006
|
+
continue;
|
|
3007
|
+
if (best === null || version > best.version)
|
|
3008
|
+
best = { ...c, version };
|
|
3009
|
+
}
|
|
3010
|
+
return best === null ? null : { ...best, content: frozenBriefContent(best.body, best.version) };
|
|
2952
3011
|
}
|
|
2953
3012
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2954
3013
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
@@ -3675,6 +3734,11 @@ function isBoundToObjectives(extraction, currentVersion) {
|
|
|
3675
3734
|
return true;
|
|
3676
3735
|
return extraction.objectivesVersion === currentVersion;
|
|
3677
3736
|
}
|
|
3737
|
+
function isBoundToRulings(extraction, currentOrdinal) {
|
|
3738
|
+
if (extraction.rulingOrdinal === null)
|
|
3739
|
+
return currentOrdinal === 0;
|
|
3740
|
+
return extraction.rulingOrdinal === currentOrdinal;
|
|
3741
|
+
}
|
|
3678
3742
|
function checkReviewGate(input) {
|
|
3679
3743
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3680
3744
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3703,7 +3767,9 @@ function checkReviewGate(input) {
|
|
|
3703
3767
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3704
3768
|
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3705
3769
|
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3706
|
-
|
|
3770
|
+
const codeReviewRulingsBound = isBoundToRulings(codeReview, input.rulingOrdinal);
|
|
3771
|
+
const securityRulingsBound = isBoundToRulings(security, input.rulingOrdinal);
|
|
3772
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && codeReviewRulingsBound && securityClean && securityBound && securityObjectivesBound && securityRulingsBound && mechanicalChecksClean) {
|
|
3707
3773
|
return {
|
|
3708
3774
|
verdict: "pass",
|
|
3709
3775
|
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.`,
|
|
@@ -3717,6 +3783,8 @@ function checkReviewGate(input) {
|
|
|
3717
3783
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3718
3784
|
} else if (!codeReviewObjectivesBound) {
|
|
3719
3785
|
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3786
|
+
} else if (!codeReviewRulingsBound) {
|
|
3787
|
+
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`);
|
|
3720
3788
|
}
|
|
3721
3789
|
if (!securityClean) {
|
|
3722
3790
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
@@ -3724,6 +3792,8 @@ function checkReviewGate(input) {
|
|
|
3724
3792
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3725
3793
|
} else if (!securityObjectivesBound) {
|
|
3726
3794
|
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3795
|
+
} else if (!securityRulingsBound) {
|
|
3796
|
+
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`);
|
|
3727
3797
|
}
|
|
3728
3798
|
if (!mechanicalChecksClean) {
|
|
3729
3799
|
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 +3810,24 @@ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
|
|
|
3740
3810
|
function isChangesetsReleasePr(branch, author, expectedAuthor) {
|
|
3741
3811
|
return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
|
|
3742
3812
|
}
|
|
3813
|
+
// ../../packages/aeg-core/src/ruling-ordinal.ts
|
|
3814
|
+
var RULING_MARKER_ORDINAL = /^<!-- aeg:principal:ruling:\d+-(\d+) -->$/;
|
|
3815
|
+
function newestPrincipalRulingOrdinal(comments, allowlist) {
|
|
3816
|
+
let best = 0;
|
|
3817
|
+
for (const c of comments) {
|
|
3818
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3819
|
+
continue;
|
|
3820
|
+
const firstLine = (c.body.split(`
|
|
3821
|
+
`)[0] ?? "").trim();
|
|
3822
|
+
const m = RULING_MARKER_ORDINAL.exec(firstLine);
|
|
3823
|
+
if (!m)
|
|
3824
|
+
continue;
|
|
3825
|
+
const k = Number.parseInt(m[1], 10);
|
|
3826
|
+
if (k > best)
|
|
3827
|
+
best = k;
|
|
3828
|
+
}
|
|
3829
|
+
return best;
|
|
3830
|
+
}
|
|
3743
3831
|
// ../../packages/aeg-core/src/markdown-table.ts
|
|
3744
3832
|
function splitRow(line) {
|
|
3745
3833
|
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
@@ -5290,12 +5378,40 @@ function loadConfig() {
|
|
|
5290
5378
|
}
|
|
5291
5379
|
var TOKENS_COLLECT_TRUST_PATH = join(GLOBAL_VINAYA_HOME, "tokens-collect-trust.json");
|
|
5292
5380
|
|
|
5381
|
+
// src/lib/patch-id.ts
|
|
5382
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
5383
|
+
function patchIdAt(base, sha) {
|
|
5384
|
+
try {
|
|
5385
|
+
execFileSync3("git", ["fetch", "--quiet", "origin", sha], { stdio: ["ignore", "ignore", "ignore"] });
|
|
5386
|
+
} catch {}
|
|
5387
|
+
try {
|
|
5388
|
+
const diff = execFileSync3("git", ["diff", `origin/${base}...${sha}`], {
|
|
5389
|
+
encoding: "utf8",
|
|
5390
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
5391
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5392
|
+
});
|
|
5393
|
+
if (diff === "")
|
|
5394
|
+
return null;
|
|
5395
|
+
const out = execFileSync3("git", ["patch-id", "--stable"], {
|
|
5396
|
+
input: diff,
|
|
5397
|
+
encoding: "utf8",
|
|
5398
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5399
|
+
}).trim();
|
|
5400
|
+
const id = out.split(/\s+/)[0] ?? "";
|
|
5401
|
+
return id === "" ? null : id;
|
|
5402
|
+
} catch {
|
|
5403
|
+
return null;
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
|
|
5407
|
+
// src/lib/review-gate-check-name.ts
|
|
5408
|
+
var REVIEW_GATE_CHECK_RUN_NAME = "vinaya review gate";
|
|
5409
|
+
|
|
5293
5410
|
// src/checks/bin/check-review-gate.ts
|
|
5294
5411
|
var CHECK_NAME = "review-gate";
|
|
5295
|
-
var OWN_CHECK_RUN_NAME = "vinaya review gate";
|
|
5296
5412
|
function fetchPr(prNumber) {
|
|
5297
5413
|
try {
|
|
5298
|
-
const out =
|
|
5414
|
+
const out = execFileSync4("gh", ["pr", "view", String(prNumber), "--json", "number,comments,labels,headRefName,headRefOid,baseRefName,body"], {
|
|
5299
5415
|
encoding: "utf8",
|
|
5300
5416
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5301
5417
|
});
|
|
@@ -5305,27 +5421,28 @@ function fetchPr(prNumber) {
|
|
|
5305
5421
|
}
|
|
5306
5422
|
}
|
|
5307
5423
|
function fetchIssueBodyForObjectives(issueNumber) {
|
|
5308
|
-
return
|
|
5424
|
+
return execFileSync4("gh", ["issue", "view", String(issueNumber), "--json", "body", "--jq", ".body"], {
|
|
5309
5425
|
encoding: "utf8",
|
|
5310
5426
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5311
5427
|
});
|
|
5312
5428
|
}
|
|
5313
5429
|
function resolveObjectivesVersion(pr) {
|
|
5314
5430
|
const { issue } = extractIssue(pr.body);
|
|
5315
|
-
|
|
5431
|
+
const source = resolveObjectivesSource(pr.body, issue, OBJECTIVES_SINCE_ISSUE);
|
|
5432
|
+
if (source.kind === "none")
|
|
5316
5433
|
return null;
|
|
5317
|
-
if (
|
|
5434
|
+
if (source.kind === "issue") {
|
|
5318
5435
|
let issueBody;
|
|
5319
5436
|
try {
|
|
5320
|
-
issueBody = fetchIssueBodyForObjectives(issue);
|
|
5437
|
+
issueBody = fetchIssueBodyForObjectives(source.issue);
|
|
5321
5438
|
} catch (err) {
|
|
5322
5439
|
if (isIssueNotFoundError(err)) {
|
|
5323
5440
|
emitCheckError({
|
|
5324
5441
|
schema: CHECK_SCHEMA_VERSION,
|
|
5325
5442
|
check: CHECK_NAME,
|
|
5326
5443
|
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\`.`
|
|
5444
|
+
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.`,
|
|
5445
|
+
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
5446
|
});
|
|
5330
5447
|
process.exit(1);
|
|
5331
5448
|
}
|
|
@@ -5333,7 +5450,7 @@ function resolveObjectivesVersion(pr) {
|
|
|
5333
5450
|
schema: CHECK_SCHEMA_VERSION,
|
|
5334
5451
|
check: CHECK_NAME,
|
|
5335
5452
|
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}`,
|
|
5453
|
+
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
5454
|
agent_recovery_prompt: "Confirm `gh auth status` passes and the Issue number is correct, then re-run `vinaya check review-gate`."
|
|
5338
5455
|
});
|
|
5339
5456
|
process.exit(1);
|
|
@@ -5344,32 +5461,29 @@ function resolveObjectivesVersion(pr) {
|
|
|
5344
5461
|
schema: CHECK_SCHEMA_VERSION,
|
|
5345
5462
|
check: CHECK_NAME,
|
|
5346
5463
|
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\`.`
|
|
5464
|
+
message: `review-gate severity:infra — Issue #${source.issue}'s \`## Objectives\` section does not parse (${parsed.errors.join("; ")}) — cannot verify the objectives-version binding.`,
|
|
5465
|
+
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
5466
|
});
|
|
5350
5467
|
process.exit(1);
|
|
5351
5468
|
}
|
|
5352
5469
|
return objectivesVersion(parsed.objectives);
|
|
5353
5470
|
}
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
process.exit(1);
|
|
5365
|
-
}
|
|
5366
|
-
return objectivesVersion(own.objectives);
|
|
5471
|
+
const own = objectivesOf(pr.body);
|
|
5472
|
+
if (!own.ok) {
|
|
5473
|
+
emitCheckError({
|
|
5474
|
+
schema: CHECK_SCHEMA_VERSION,
|
|
5475
|
+
check: CHECK_NAME,
|
|
5476
|
+
severity: "error",
|
|
5477
|
+
message: `review-gate severity:infra — this PR body's own \`## Objectives\` section does not parse (${own.errors.join("; ")}) — cannot verify the objectives-version binding.`,
|
|
5478
|
+
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`."
|
|
5479
|
+
});
|
|
5480
|
+
process.exit(1);
|
|
5367
5481
|
}
|
|
5368
|
-
return
|
|
5482
|
+
return objectivesVersion(own.objectives);
|
|
5369
5483
|
}
|
|
5370
5484
|
function shaFromLsRemote(branch) {
|
|
5371
5485
|
try {
|
|
5372
|
-
const out =
|
|
5486
|
+
const out = execFileSync4("git", ["ls-remote", "origin", `refs/heads/${branch}`], {
|
|
5373
5487
|
encoding: "utf8",
|
|
5374
5488
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5375
5489
|
}).trim();
|
|
@@ -5381,7 +5495,7 @@ function shaFromLsRemote(branch) {
|
|
|
5381
5495
|
}
|
|
5382
5496
|
function shaFromGhApi(branch) {
|
|
5383
5497
|
try {
|
|
5384
|
-
const out =
|
|
5498
|
+
const out = execFileSync4("gh", ["api", `repos/{owner}/{repo}/git/ref/heads/${branch}`, "--jq", ".object.sha"], {
|
|
5385
5499
|
encoding: "utf8",
|
|
5386
5500
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5387
5501
|
}).trim();
|
|
@@ -5398,29 +5512,6 @@ function resolveTrueHeadSha(pr) {
|
|
|
5398
5512
|
}
|
|
5399
5513
|
return trueSha;
|
|
5400
5514
|
}
|
|
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
5515
|
function bucketFor(run2) {
|
|
5425
5516
|
if (run2.status !== "completed")
|
|
5426
5517
|
return "pending";
|
|
@@ -5438,7 +5529,7 @@ function bucketFor(run2) {
|
|
|
5438
5529
|
}
|
|
5439
5530
|
function fetchMechanicalChecks(headSha) {
|
|
5440
5531
|
try {
|
|
5441
|
-
const out =
|
|
5532
|
+
const out = execFileSync4("gh", [
|
|
5442
5533
|
"api",
|
|
5443
5534
|
`repos/{owner}/{repo}/commits/${headSha}/check-runs`,
|
|
5444
5535
|
"--paginate",
|
|
@@ -5453,14 +5544,14 @@ function fetchMechanicalChecks(headSha) {
|
|
|
5453
5544
|
if (!seen || run2.id > seen.id)
|
|
5454
5545
|
latestByName.set(run2.name, run2);
|
|
5455
5546
|
}
|
|
5456
|
-
return Array.from(latestByName.values()).filter((r) => r.name !==
|
|
5547
|
+
return Array.from(latestByName.values()).filter((r) => r.name !== REVIEW_GATE_CHECK_RUN_NAME).map((r) => ({ name: r.name, bucket: bucketFor(r) }));
|
|
5457
5548
|
} catch {
|
|
5458
5549
|
return null;
|
|
5459
5550
|
}
|
|
5460
5551
|
}
|
|
5461
5552
|
function fetchWaiverLabelActor(prNumber, label2) {
|
|
5462
5553
|
try {
|
|
5463
|
-
const out =
|
|
5554
|
+
const out = execFileSync4("gh", ["api", `repos/{owner}/{repo}/issues/${prNumber}/timeline`, "--paginate"], {
|
|
5464
5555
|
encoding: "utf8",
|
|
5465
5556
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5466
5557
|
});
|
|
@@ -5528,7 +5619,8 @@ function main() {
|
|
|
5528
5619
|
mechanicalChecks,
|
|
5529
5620
|
headSha,
|
|
5530
5621
|
patchIdOf: (sha) => patchIdAt(pr.baseRefName, sha),
|
|
5531
|
-
objectivesVersion: waived ? null : resolveObjectivesVersion(pr)
|
|
5622
|
+
objectivesVersion: waived ? null : resolveObjectivesVersion(pr),
|
|
5623
|
+
rulingOrdinal: newestPrincipalRulingOrdinal(pr.comments.map((c) => ({ body: c.body, author: c.author?.login ?? null })), principalAllowlist)
|
|
5532
5624
|
});
|
|
5533
5625
|
if (result.verdict === "fail") {
|
|
5534
5626
|
emitCheckError({
|