@codyswann/lisa 4.35.2 → 4.35.3
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/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +2 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +25 -2
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +5 -1
- package/plugins/lisa/.codex-plugin/hooks.json +4 -0
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/block-host-name-leak.agy.sh +61 -0
- package/plugins/lisa/hooks/block-host-name-leak.mjs +412 -0
- package/plugins/lisa/hooks/block-host-name-leak.sh +61 -0
- package/plugins/lisa-agy/hooks/block-host-name-leak.agy.sh +61 -0
- package/plugins/lisa-agy/hooks/block-host-name-leak.mjs +412 -0
- package/plugins/lisa-agy/hooks/block-host-name-leak.sh +61 -0
- package/plugins/lisa-agy/hooks.json +13 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +5 -1
- package/plugins/lisa-copilot/hooks/block-host-name-leak.mjs +412 -0
- package/plugins/lisa-copilot/hooks/block-host-name-leak.sh +61 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/block-host-name-leak.mjs +412 -0
- package/plugins/lisa-cursor/hooks/block-host-name-leak.sh +61 -0
- package/plugins/lisa-cursor/hooks/hooks.json +4 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/.claude-plugin/plugin.json +4 -0
- package/plugins/src/base/hooks/block-host-name-leak.agy.sh +61 -0
- package/plugins/src/base/hooks/block-host-name-leak.mjs +412 -0
- package/plugins/src/base/hooks/block-host-name-leak.sh +61 -0
- package/scripts/downstream-name-digest.mjs +128 -0
- package/scripts/generate-agy-plugin-artifacts.mjs +12 -0
- package/scripts/reconcile-release-tags.mjs +331 -0
- package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +132 -39
|
@@ -1246,30 +1246,47 @@ export const REVIEW_GATE_STATES = Object.freeze({
|
|
|
1246
1246
|
* WHICH condition produced the state, as stable tokens.
|
|
1247
1247
|
*
|
|
1248
1248
|
* The state is the gate's SEVERITY and stays three-valued; this is what the
|
|
1249
|
-
* gate OBSERVED, and it is
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
* | condition
|
|
1253
|
-
*
|
|
1254
|
-
* | `absent`
|
|
1255
|
-
* | `objected`
|
|
1256
|
-
* | `pending`
|
|
1257
|
-
* | `unrecognised`
|
|
1258
|
-
*
|
|
1259
|
-
*
|
|
1260
|
-
*
|
|
1261
|
-
*
|
|
1262
|
-
* #3716 and #3600 each described a
|
|
1263
|
-
* they are
|
|
1249
|
+
* gate OBSERVED, and it is seven-valued because `unsatisfied` is reached from
|
|
1250
|
+
* five structurally different situations:
|
|
1251
|
+
*
|
|
1252
|
+
* | condition | what actually happened | operator's next move |
|
|
1253
|
+
* |-----------------|----------------------------------|-----------------------|
|
|
1254
|
+
* | `absent` | nobody reviewed | investigate the silence |
|
|
1255
|
+
* | `objected` | a review ran and objected | READ THE OBJECTION |
|
|
1256
|
+
* | `pending` | the reviewer is late | wait, then re-run |
|
|
1257
|
+
* | `unrecognised` | the reviewer said something new | classify the phrase |
|
|
1258
|
+
* | `undetermined` | THE GATE STOPPED WAITING | re-run; read nothing into it |
|
|
1259
|
+
*
|
|
1260
|
+
* All of them published one word, so an operator handed `unsatisfied` had to
|
|
1261
|
+
* guess between situations whose correct responses have nothing in common. That
|
|
1262
|
+
* collapse is why CodySwannGT/lisa#3706, #3716 and #3600 each described a
|
|
1263
|
+
* different defect and each was partly right: they are faces of one
|
|
1264
|
+
* impoverished vocabulary.
|
|
1264
1265
|
*
|
|
1265
1266
|
* The second row is the sharpest. This file states that a review which
|
|
1266
1267
|
* "RAN AND OBJECTED is the case this gate exists to let through to a human" —
|
|
1267
1268
|
* and then reported it identically to nobody-reviewed. Blocking is correct
|
|
1268
1269
|
* there, so no exit code was ever wrong; what was missing is any way to tell
|
|
1269
|
-
* the operator which of
|
|
1270
|
+
* the operator which of them they were looking at.
|
|
1271
|
+
*
|
|
1272
|
+
* THE LAST ROW IS NOT AN OBSERVATION AND MUST NEVER READ LIKE ONE (#3716).
|
|
1273
|
+
* `absent` and `pending` are things the gate SAW; `undetermined` is the gate
|
|
1274
|
+
* saying it ran out of time before it could see anything. Reported as `absent`
|
|
1275
|
+
* — "did not report on this pull request at all... investigate the silence" —
|
|
1276
|
+
* it sends an operator to audit the change instead of the wait, which is
|
|
1277
|
+
* exactly what a false red costs. MEASURED: a settle window expired NINE
|
|
1278
|
+
* SECONDS before the review it was waiting for arrived.
|
|
1279
|
+
*
|
|
1280
|
+
* And it is not a rare corner. {@link checksSettled} returns false whenever a
|
|
1281
|
+
* declared check is missing or still pending, so the wait CANNOT end early on
|
|
1282
|
+
* either shape: with one declared check, every `absent` and every `pending`
|
|
1283
|
+
* this gate can currently produce is a wait that expired. The two tokens are
|
|
1284
|
+
* kept because they are the honest labels for a settled reading, and a future
|
|
1285
|
+
* declaration with several checks can settle one while another is still in
|
|
1286
|
+
* flight — but today `undetermined` is what fires, and that is the point.
|
|
1270
1287
|
*
|
|
1271
1288
|
* SEVERITY IS DELIBERATELY UNCHANGED HERE. See `reviewGateState` for why
|
|
1272
|
-
* `pending` keeps blocking.
|
|
1289
|
+
* `pending` keeps blocking, and why `undetermined` blocks with it.
|
|
1273
1290
|
*/
|
|
1274
1291
|
export const REVIEW_GATE_CONDITIONS = Object.freeze({
|
|
1275
1292
|
satisfied: "satisfied",
|
|
@@ -1278,6 +1295,7 @@ export const REVIEW_GATE_CONDITIONS = Object.freeze({
|
|
|
1278
1295
|
objected: "objected",
|
|
1279
1296
|
pending: "pending",
|
|
1280
1297
|
unrecognised: "unrecognised",
|
|
1298
|
+
undetermined: "undetermined",
|
|
1281
1299
|
});
|
|
1282
1300
|
|
|
1283
1301
|
/**
|
|
@@ -1317,17 +1335,35 @@ function normalizeDescription(description) {
|
|
|
1317
1335
|
* would trade a false red for a silent merge, and this file has a whole
|
|
1318
1336
|
* vocabulary of evidence that the silent direction is the expensive one.
|
|
1319
1337
|
*
|
|
1320
|
-
*
|
|
1338
|
+
* WAITING IS NOT OBSERVING (#3716). `reading.waitExpired` is the settle loop
|
|
1339
|
+
* reporting that it hit its deadline rather than reaching a terminal read. When
|
|
1340
|
+
* it is set, the two shapes that mean "nothing conclusive yet" — no status at
|
|
1341
|
+
* all, and a status still in flight — are reported as `undetermined` instead of
|
|
1342
|
+
* as `absent` and `pending`. The distinction between those two shapes is
|
|
1343
|
+
* preserved inside the sentence, because "never started" and "started and ran
|
|
1344
|
+
* long" are different things to go and look at; what changes is that neither is
|
|
1345
|
+
* asserted as a fact about the reviewer when the gate simply stopped waiting.
|
|
1346
|
+
*
|
|
1347
|
+
* @param {{present: boolean, state?: string, description?: string, waitExpired?: boolean}} reading - One check, and whether the settle wait expired
|
|
1321
1348
|
* @param {{waive?: readonly string[], satisfy?: readonly string[]}} [vocabulary] - Per-check extensions
|
|
1322
1349
|
* @returns {{state: string, condition: string, why: string}} Severity, the condition observed, and a one-line reason
|
|
1323
1350
|
*/
|
|
1324
1351
|
export function reviewGateState(reading, vocabulary = {}) {
|
|
1352
|
+
// Shared by both inconclusive shapes so they can never drift apart, and so
|
|
1353
|
+
// the sentence always says what to do (re-run) before it says what was seen.
|
|
1354
|
+
const undetermined = seen => ({
|
|
1355
|
+
state: REVIEW_GATE_STATES.unsatisfied,
|
|
1356
|
+
condition: REVIEW_GATE_CONDITIONS.undetermined,
|
|
1357
|
+
why: `was ${seen} when the settle window EXPIRED. The gate stopped waiting; it did not observe that nobody reviewed. This is NOT a finding about the code and nothing about the change should be investigated on the strength of it — RE-RUN THIS JOB once the reviewer has settled. Blocking is deliberate: an expired wait has not established that a review happened, and passing on it would let a pull request merge unreviewed whenever a reviewer ran slow. Do NOT re-request the review: a re-request OVERWRITES the existing commit status rather than adding to it, so under throttle it destroys a real review one-way and replaces a substantive objection with a rate-limit string.`,
|
|
1358
|
+
});
|
|
1325
1359
|
if (!reading.present) {
|
|
1326
|
-
return
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1360
|
+
return reading.waitExpired === true
|
|
1361
|
+
? undetermined("still unreported")
|
|
1362
|
+
: {
|
|
1363
|
+
state: REVIEW_GATE_STATES.unsatisfied,
|
|
1364
|
+
condition: REVIEW_GATE_CONDITIONS.absent,
|
|
1365
|
+
why: "did not report on this pull request at all. ABSENT is not the same as waived: nothing said it could not review, so nothing accounts for the silence. A guard that read absence as permission would pass forever the first time it looked at the wrong commit.",
|
|
1366
|
+
};
|
|
1331
1367
|
}
|
|
1332
1368
|
const state = String(reading.state ?? "").toUpperCase();
|
|
1333
1369
|
const text = normalizeDescription(reading.description);
|
|
@@ -1339,11 +1375,13 @@ export function reviewGateState(reading, vocabulary = {}) {
|
|
|
1339
1375
|
};
|
|
1340
1376
|
}
|
|
1341
1377
|
if (state !== "SUCCESS") {
|
|
1342
|
-
return
|
|
1343
|
-
state:
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1378
|
+
return reading.waitExpired === true
|
|
1379
|
+
? undetermined(`still ${state === "" ? "unreported" : state}`)
|
|
1380
|
+
: {
|
|
1381
|
+
state: REVIEW_GATE_STATES.unsatisfied,
|
|
1382
|
+
condition: REVIEW_GATE_CONDITIONS.pending,
|
|
1383
|
+
why: `is still ${state === "" ? "unreported" : state} after the settle window. An unsettled check has not said anything yet, and the gate does not guess on its behalf. This is LATENESS, not a verdict about the code — wait for the reviewer to settle, then RE-RUN THIS JOB. Do NOT re-request the review: a re-request OVERWRITES the existing commit status rather than adding to it, so under throttle it destroys a real review one-way and replaces a substantive objection with a rate-limit string.`,
|
|
1384
|
+
};
|
|
1347
1385
|
}
|
|
1348
1386
|
const satisfies = [...REVIEW_SATISFACTIONS, ...(vocabulary.satisfy ?? [])];
|
|
1349
1387
|
if (satisfies.some(phrase => text === normalizeDescription(phrase))) {
|
|
@@ -1378,14 +1416,19 @@ export function reviewGateState(reading, vocabulary = {}) {
|
|
|
1378
1416
|
*
|
|
1379
1417
|
* @param {object} declaration - The per-repo declaration
|
|
1380
1418
|
* @param {ReadonlyArray<{name: string, state: string, description?: string}>} checks - The checks
|
|
1381
|
-
* @param {{headSha?: string}} [options] - `headSha` is cited in every finding
|
|
1382
|
-
* @returns {{violations: object[], states: Record<string, string>, descriptions: Record<string, string>, checked: number}} Findings, per-check state, the description each verdict was read from, and how many were examined
|
|
1419
|
+
* @param {{headSha?: string, waitExpired?: boolean}} [options] - `headSha` is cited in every finding; `waitExpired` says the settle loop hit its deadline
|
|
1420
|
+
* @returns {{violations: object[], states: Record<string, string>, conditions: Record<string, string>, descriptions: Record<string, string>, checked: number}} Findings, per-check state and condition, the description each verdict was read from, and how many were examined
|
|
1383
1421
|
*/
|
|
1384
1422
|
export function evaluateReviewGate(declaration, checks, options = {}) {
|
|
1385
1423
|
const declared = declaration.evidence_bearing_checks ?? {};
|
|
1386
1424
|
const at = citeHeadSha(options.headSha);
|
|
1387
1425
|
const violations = [];
|
|
1388
1426
|
const states = {};
|
|
1427
|
+
// Carried alongside the states for the same reason the descriptions are: the
|
|
1428
|
+
// RENDERED verdict has to say WHICH condition it is publishing, and `states`
|
|
1429
|
+
// is three-valued severity that cannot tell an expired wait from a review
|
|
1430
|
+
// that ran and objected (#3716).
|
|
1431
|
+
const conditions = {};
|
|
1389
1432
|
// Carried out with the states because the RENDERED verdict quotes it. A title
|
|
1390
1433
|
// that said only "WAIVED" would tell an operator that something was waived
|
|
1391
1434
|
// and not which vendor sentence did the waiving, which is the fact that says
|
|
@@ -1399,15 +1442,17 @@ export function evaluateReviewGate(declaration, checks, options = {}) {
|
|
|
1399
1442
|
const found = checks.find(check => check.name === name);
|
|
1400
1443
|
const verdict = reviewGateState(
|
|
1401
1444
|
found === undefined
|
|
1402
|
-
? { present: false }
|
|
1445
|
+
? { present: false, waitExpired: options.waitExpired === true }
|
|
1403
1446
|
: {
|
|
1404
1447
|
present: true,
|
|
1405
1448
|
state: found.state,
|
|
1406
1449
|
description: found.description,
|
|
1450
|
+
waitExpired: options.waitExpired === true,
|
|
1407
1451
|
},
|
|
1408
1452
|
vocabulary
|
|
1409
1453
|
);
|
|
1410
1454
|
states[name] = verdict.state;
|
|
1455
|
+
conditions[name] = verdict.condition;
|
|
1411
1456
|
// Assigned ONLY when the check reported. An absent check with a `""`
|
|
1412
1457
|
// description entry renders as `CodeRabbit reported ""` — a sentence that
|
|
1413
1458
|
// says it reported. MEASURED on this fix's own first commit, where the
|
|
@@ -1432,7 +1477,7 @@ export function evaluateReviewGate(declaration, checks, options = {}) {
|
|
|
1432
1477
|
});
|
|
1433
1478
|
}
|
|
1434
1479
|
|
|
1435
|
-
return { violations, states, descriptions, checked };
|
|
1480
|
+
return { violations, states, conditions, descriptions, checked };
|
|
1436
1481
|
}
|
|
1437
1482
|
|
|
1438
1483
|
/**
|
|
@@ -1512,13 +1557,22 @@ function waiveRateSuffix(rate) {
|
|
|
1512
1557
|
* that matters — a waiver and a satisfaction never render the same — is
|
|
1513
1558
|
* testable without a GitHub API.
|
|
1514
1559
|
*
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1560
|
+
* THE TITLE IS THE ONLY LAYER A MERGE DECISION READS (#3716). Before this, an
|
|
1561
|
+
* expired settle wait published `UNREVIEWED — ... posted NO review status at
|
|
1562
|
+
* all`: a sentence asserting an observation the gate never made. The timeout
|
|
1563
|
+
* was known — the settle loop returned it — and it reached only a parenthetical
|
|
1564
|
+
* in the job summary, and only on the branch where there were NO violations. So
|
|
1565
|
+
* the one path where it mattered, a red check blocking a merge, was the one
|
|
1566
|
+
* path that never said it. The conditions are carried here for that reason.
|
|
1567
|
+
*
|
|
1568
|
+
* @param {{states?: Record<string, string>, conditions?: Record<string, string>, descriptions?: Record<string, string>, refusal?: {kind: string}|null, waiveRate?: {waived: number, sampled: number}}} reading -
|
|
1569
|
+
* The gate's per-check states and conditions, the descriptions they were read
|
|
1570
|
+
* from, any refusal, and an optional sampled waive rate
|
|
1518
1571
|
* @returns {{verdict: string, conclusion: string, title: string}} What to publish
|
|
1519
1572
|
*/
|
|
1520
1573
|
export function reviewGateVerdict(reading = {}) {
|
|
1521
1574
|
const states = reading.states ?? {};
|
|
1575
|
+
const conditions = reading.conditions ?? {};
|
|
1522
1576
|
const descriptions = reading.descriptions ?? {};
|
|
1523
1577
|
const names = Object.keys(states);
|
|
1524
1578
|
const suffix = waiveRateSuffix(reading.waiveRate);
|
|
@@ -1540,23 +1594,45 @@ export function reviewGateVerdict(reading = {}) {
|
|
|
1540
1594
|
* check that posted nothing is the one phrasing this whole file exists to
|
|
1541
1595
|
* refuse: it makes absence look like a quiet answer.
|
|
1542
1596
|
*
|
|
1597
|
+
* An `undetermined` check is quoted as a WAIT, never as a report. Saying it
|
|
1598
|
+
* "posted NO review status at all" is true of the bytes and false as an
|
|
1599
|
+
* account of what happened, and it is the sentence that sends an operator to
|
|
1600
|
+
* audit the change rather than re-run the job.
|
|
1601
|
+
*
|
|
1543
1602
|
* @param {string} name - The declared check name
|
|
1544
|
-
* @returns {string} `<name> reported "<description>"`, or
|
|
1603
|
+
* @returns {string} `<name> reported "<description>"`, or what the wait did
|
|
1545
1604
|
*/
|
|
1546
|
-
const quote = name =>
|
|
1547
|
-
|
|
1605
|
+
const quote = name => {
|
|
1606
|
+
if (conditions[name] === REVIEW_GATE_CONDITIONS.undetermined) {
|
|
1607
|
+
return `${name} had not settled when the gate's wait EXPIRED — not observed to be unreviewed`;
|
|
1608
|
+
}
|
|
1609
|
+
return Object.hasOwn(descriptions, name)
|
|
1548
1610
|
? `${name} reported ${JSON.stringify(descriptions[name])}`
|
|
1549
1611
|
: `${name} posted NO review status at all`;
|
|
1612
|
+
};
|
|
1550
1613
|
|
|
1551
1614
|
const unsatisfied = names.filter(
|
|
1552
1615
|
name => states[name] === REVIEW_GATE_STATES.unsatisfied
|
|
1553
1616
|
);
|
|
1554
1617
|
if (unsatisfied.length > 0) {
|
|
1618
|
+
// FAIL-CLOSED, LABELLED. The conclusion is `failure` either way: an expired
|
|
1619
|
+
// wait has not established that a review happened, and the whole family of
|
|
1620
|
+
// defects this gate is made of is a control reporting a conclusion it did
|
|
1621
|
+
// not reach — so resolving a timeout to `success` would be a new instance
|
|
1622
|
+
// of exactly that. What changes is the WORD, because "we could not tell"
|
|
1623
|
+
// and "nobody reviewed" are different things to hand a human. Only when
|
|
1624
|
+
// EVERY unsatisfied check is undetermined, so a real objection alongside a
|
|
1625
|
+
// slow reviewer still leads with UNREVIEWED.
|
|
1626
|
+
const allUndetermined = unsatisfied.every(
|
|
1627
|
+
name => conditions[name] === REVIEW_GATE_CONDITIONS.undetermined
|
|
1628
|
+
);
|
|
1555
1629
|
return {
|
|
1556
1630
|
verdict: REVIEW_GATE_STATES.unsatisfied,
|
|
1557
1631
|
conclusion: REVIEW_VERDICT_CONCLUSIONS.unsatisfied,
|
|
1558
1632
|
title: fitTitle(
|
|
1559
|
-
|
|
1633
|
+
allUndetermined
|
|
1634
|
+
? `UNDETERMINED — the gate stopped waiting before review evidence settled, and is NOT reporting that nobody reviewed: ${unsatisfied.map(quote).join("; ")}. RE-RUN this job; do not investigate the change on the strength of this${suffix}`
|
|
1635
|
+
: `UNREVIEWED — review evidence unsatisfied: ${unsatisfied.map(quote).join("; ")}${suffix}`
|
|
1560
1636
|
),
|
|
1561
1637
|
};
|
|
1562
1638
|
}
|
|
@@ -2444,6 +2520,11 @@ export function inspectVacuity(argv, declaration, options = {}) {
|
|
|
2444
2520
|
// repositories that have not opted in yet.
|
|
2445
2521
|
const gate = evaluateReviewGate(declaration, read.checks, {
|
|
2446
2522
|
headSha: read.headSha,
|
|
2523
|
+
// The settle loop exits on one of two things: everything declared reached a
|
|
2524
|
+
// terminal read, or the deadline passed. So `settled === false` IS "the
|
|
2525
|
+
// wait expired", and it is the only place that fact exists — every reader
|
|
2526
|
+
// downstream sees a check row that looks identical either way (#3716).
|
|
2527
|
+
waitExpired: read.settled === false,
|
|
2447
2528
|
});
|
|
2448
2529
|
// Sampled ONLY when this pull request is itself waived. The rate answers
|
|
2449
2530
|
// "is this the exception or the rule?", a question that only arises once a
|
|
@@ -2466,10 +2547,12 @@ export function inspectVacuity(argv, declaration, options = {}) {
|
|
|
2466
2547
|
checked: evaluated.checked,
|
|
2467
2548
|
violations: [...evaluated.violations, ...gate.violations],
|
|
2468
2549
|
gateStates: gate.states,
|
|
2550
|
+
gateConditions: gate.conditions,
|
|
2469
2551
|
gateDescriptions: gate.descriptions,
|
|
2470
2552
|
waiveRate,
|
|
2471
2553
|
verdict: reviewGateVerdict({
|
|
2472
2554
|
states: gate.states,
|
|
2555
|
+
conditions: gate.conditions,
|
|
2473
2556
|
descriptions: gate.descriptions,
|
|
2474
2557
|
waiveRate,
|
|
2475
2558
|
}),
|
|
@@ -2712,6 +2795,16 @@ function main(argv) {
|
|
|
2712
2795
|
`::${blocks(violation) ? "error" : "warning"} title=${violation.kind}::${violation.message.split("\n")[0]}\n`
|
|
2713
2796
|
);
|
|
2714
2797
|
}
|
|
2798
|
+
// The clean branch above has always disclosed an expired wait. This branch
|
|
2799
|
+
// — a RED run, the only one where the disclosure changes what an operator
|
|
2800
|
+
// does next — did not, so the fact was published exactly where it was not
|
|
2801
|
+
// needed and withheld where it was (#3716).
|
|
2802
|
+
if (result.vacuity?.settled === false) {
|
|
2803
|
+
lines.push(
|
|
2804
|
+
"",
|
|
2805
|
+
"⏳ The settle wait EXPIRED before every declared review check reached a terminal state, so the readings above are where the wait stopped rather than where the reviewer finished. RE-RUN this job before treating any of it as a finding about the change."
|
|
2806
|
+
);
|
|
2807
|
+
}
|
|
2715
2808
|
if (warnOnly) {
|
|
2716
2809
|
lines.push(
|
|
2717
2810
|
"",
|