@cejel/cejel 0.4.6 → 0.4.7
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 +35 -11
- package/dist/calibration/llm-detector.js +5 -5
- package/dist/{chunk-QRWLHTGR.js → chunk-EP7YYWEP.js} +2 -2
- package/dist/{chunk-BZXBLDCJ.js → chunk-OIGCYULH.js} +1 -1
- package/dist/{chunk-VEJLHO5F.js → chunk-QTLLASOO.js} +1237 -264
- package/dist/{chunk-Y6NO5HAI.js → chunk-UYJBSQCV.js} +1 -1
- package/dist/{chunk-2IBG5OBF.js → chunk-XHAETB7N.js} +1 -1
- package/dist/{chunk-H55KECVT.js → chunk-ZNVILRHK.js} +44 -4
- package/dist/index.js +39 -9
- package/dist/mcp/index.js +4 -4
- package/dist/packs/d-series/index.js +4 -4
- package/dist/packs/decision-contracts/index.js +3 -3
- package/package.json +1 -1
|
@@ -5,12 +5,13 @@ import {
|
|
|
5
5
|
describeGitFailure,
|
|
6
6
|
execGit,
|
|
7
7
|
isExpectedGitAbsence
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-OIGCYULH.js";
|
|
9
9
|
import {
|
|
10
10
|
external_exports
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-UYJBSQCV.js";
|
|
12
12
|
import {
|
|
13
13
|
MAX_REPOSITORY_CONTENT_BYTES,
|
|
14
|
+
contentReadSignalKey,
|
|
14
15
|
init_esm_shims,
|
|
15
16
|
readRepoText,
|
|
16
17
|
readRepoTextPrefix,
|
|
@@ -18,8 +19,10 @@ import {
|
|
|
18
19
|
recordContentSkipForCriteria,
|
|
19
20
|
recordFilesystemSkip,
|
|
20
21
|
trackContentReads,
|
|
21
|
-
withContentReadCriterion
|
|
22
|
-
|
|
22
|
+
withContentReadCriterion,
|
|
23
|
+
withContentReadSignal,
|
|
24
|
+
withContentReadSignals
|
|
25
|
+
} from "./chunk-ZNVILRHK.js";
|
|
23
26
|
|
|
24
27
|
// src/witan/schemas.ts
|
|
25
28
|
init_esm_shims();
|
|
@@ -374,6 +377,11 @@ var WitanAttestationStatementSchema = external_exports.object({
|
|
|
374
377
|
// explicit report contract version.
|
|
375
378
|
reportFormatVersion: external_exports.string().regex(/^1\.\d+$/).optional(),
|
|
376
379
|
rubricVersion: external_exports.string().min(1).max(120),
|
|
380
|
+
// Only present when the scan ran under the GitHub Action (GITHUB_RUN_ATTEMPT observed in
|
|
381
|
+
// the runner environment). Absent, never fabricated or defaulted to "1", for a local CLI
|
|
382
|
+
// or any other CI. Not part of report.json: it is per-invocation, not per-repository-
|
|
383
|
+
// state, and report.json's reproducibility guarantee excludes exactly that kind of field.
|
|
384
|
+
githubRunAttempt: external_exports.string().regex(/^[1-9][0-9]*$/).optional(),
|
|
377
385
|
repository: external_exports.object({
|
|
378
386
|
productSlug: external_exports.string().regex(/^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/),
|
|
379
387
|
url: external_exports.string().url().optional(),
|
|
@@ -415,13 +423,15 @@ var WITAN_RUBRIC_VERSION_V19 = "witan-rubric-v19-prospective-2026-08-09";
|
|
|
415
423
|
var WITAN_RUBRIC_VERSION_V20 = "witan-rubric-v20-prospective-2026-08-10";
|
|
416
424
|
var WITAN_RUBRIC_VERSION_V21 = "witan-rubric-v21-prospective-2026-08-10";
|
|
417
425
|
var WITAN_RUBRIC_VERSION_V22 = "witan-rubric-v22-prospective-2026-08-10";
|
|
426
|
+
var WITAN_RUBRIC_VERSION_V23 = "witan-rubric-v23-prospective-2026-09-06";
|
|
418
427
|
var WITAN_LAST_CALIBRATED_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V17;
|
|
419
428
|
var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
|
|
420
429
|
WITAN_RUBRIC_VERSION_V18,
|
|
421
430
|
WITAN_RUBRIC_VERSION_V19,
|
|
422
431
|
WITAN_RUBRIC_VERSION_V20,
|
|
423
432
|
WITAN_RUBRIC_VERSION_V21,
|
|
424
|
-
WITAN_RUBRIC_VERSION_V22
|
|
433
|
+
WITAN_RUBRIC_VERSION_V22,
|
|
434
|
+
WITAN_RUBRIC_VERSION_V23
|
|
425
435
|
]);
|
|
426
436
|
var WITAN_SELECTABLE_RUBRIC_VERSIONS = Object.freeze([
|
|
427
437
|
WITAN_LAST_CALIBRATED_RUBRIC_VERSION,
|
|
@@ -810,6 +820,32 @@ function glossaryEntryForMetric(metric2) {
|
|
|
810
820
|
function formatCertificateMetricLabel(metric2) {
|
|
811
821
|
return CERTIFICATE_METRIC_REGISTRY[metric2.name]?.displayLabel ?? metric2.label;
|
|
812
822
|
}
|
|
823
|
+
function formatCertificateMetricAppliedWeightPercent(criterion, metric2) {
|
|
824
|
+
const metrics = criterion.metrics;
|
|
825
|
+
const index = metrics.indexOf(metric2);
|
|
826
|
+
if (index === -1) {
|
|
827
|
+
throw new Error(
|
|
828
|
+
"formatCertificateMetricAppliedWeightPercent: metric does not belong to criterion.metrics"
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
const totalWeight = metrics.reduce((sum, m) => sum + m.weight, 0);
|
|
832
|
+
if (totalWeight <= 0) return 0;
|
|
833
|
+
const shares = metrics.map((m, i) => {
|
|
834
|
+
const raw = m.weight / totalWeight * 100;
|
|
835
|
+
const floor = Math.floor(raw);
|
|
836
|
+
return { i, floor, fraction: raw - floor };
|
|
837
|
+
});
|
|
838
|
+
const flooredTotal = shares.reduce((sum, s) => sum + s.floor, 0);
|
|
839
|
+
const byLargestRemainder = [...shares].sort((a, b) => b.fraction - a.fraction);
|
|
840
|
+
const result = new Map(shares.map((s) => [s.i, s.floor]));
|
|
841
|
+
let remainder = Math.round(100 - flooredTotal);
|
|
842
|
+
for (const s of byLargestRemainder) {
|
|
843
|
+
if (remainder <= 0) break;
|
|
844
|
+
result.set(s.i, (result.get(s.i) ?? s.floor) + 1);
|
|
845
|
+
remainder -= 1;
|
|
846
|
+
}
|
|
847
|
+
return result.get(index) ?? 0;
|
|
848
|
+
}
|
|
813
849
|
function glossaryEntriesForReport(report) {
|
|
814
850
|
const entries = new Map(CERTIFICATE_GLOSSARY.map((entry) => [entry.key, entry]));
|
|
815
851
|
for (const metric2 of report.criteria.flatMap((criterion) => criterion.metrics)) {
|
|
@@ -925,6 +961,74 @@ function formatCertificateMetricValue(criterion, metric2) {
|
|
|
925
961
|
`${formatMetricNumber(metric2.value)}/${formatMetricNumber(metric2.max)}${unit}`
|
|
926
962
|
);
|
|
927
963
|
}
|
|
964
|
+
var NEXT_STEP_FINDING_SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning"]);
|
|
965
|
+
var NEXT_STEP_FINDING_SEVERITY_RANK = {
|
|
966
|
+
critical: 0,
|
|
967
|
+
warning: 1,
|
|
968
|
+
info: 2
|
|
969
|
+
};
|
|
970
|
+
var MAX_NEXT_STEP_FINDINGS = 3;
|
|
971
|
+
function buildNextSteps(report, unmeasured, notApplicable, a1, coverageMetric) {
|
|
972
|
+
const steps = [];
|
|
973
|
+
if (unmeasured.length > 0) {
|
|
974
|
+
const ids = unmeasured.map((criterion) => criterion.id).join(", ");
|
|
975
|
+
steps.push(
|
|
976
|
+
`Obtain measurable evidence for ${ids} \u2014 ${unmeasured.length === 1 ? "this scored dimension has" : "these scored dimensions have"} no recorded result, and could change the verdict.`
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
const criterionFindings = report.criteria.flatMap(
|
|
980
|
+
(criterion) => criterion.findings.filter((finding) => NEXT_STEP_FINDING_SEVERITIES.has(finding.severity)).map((finding) => ({ criterionId: criterion.id, finding }))
|
|
981
|
+
);
|
|
982
|
+
const sortedFindings = [...criterionFindings].sort(
|
|
983
|
+
(a, b) => NEXT_STEP_FINDING_SEVERITY_RANK[a.finding.severity] - NEXT_STEP_FINDING_SEVERITY_RANK[b.finding.severity]
|
|
984
|
+
);
|
|
985
|
+
for (const { criterionId, finding } of sortedFindings.slice(0, MAX_NEXT_STEP_FINDINGS)) {
|
|
986
|
+
steps.push(
|
|
987
|
+
`Evidence is currently absent for the ${finding.severity} finding on ${criterionId}: obtain independent verification before relying on this criterion.`
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
if (sortedFindings.length > MAX_NEXT_STEP_FINDINGS) {
|
|
991
|
+
steps.push(
|
|
992
|
+
`${sortedFindings.length - MAX_NEXT_STEP_FINDINGS} more critical/warning finding(s) need the same independent verification; see the findings listed above for the complete set.`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
if (a1 && coverageMetric && isCoverageNotMeasured(a1, coverageMetric)) {
|
|
996
|
+
steps.push(
|
|
997
|
+
"Obtain a coverage report or configured threshold for A1's static coverage metric; none was found, so it was not measured."
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
if (!report.repo.headSha && report.criteria.some(
|
|
1001
|
+
(criterion) => criterion.metrics.some((metric2) => metric2.name === "pr_merge_ratio")
|
|
1002
|
+
)) {
|
|
1003
|
+
steps.push(
|
|
1004
|
+
"Restore Git history access so the recent-commit PR-reference proxy has commit subjects to inspect; its scoring zero here is a conservative default, not a detected outcome."
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
if (notApplicable.length > 0) {
|
|
1008
|
+
const ids = notApplicable.map((criterion) => criterion.id).join(", ");
|
|
1009
|
+
steps.push(
|
|
1010
|
+
`Confirm ${ids} ${notApplicable.length === 1 ? "is" : "are"} correctly marked not applicable to this repository; ${notApplicable.length === 1 ? "it was" : "they were"} not examined.`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
const scanLimitationCount = report.scanLimitations?.length ?? 0;
|
|
1014
|
+
if (scanLimitationCount > 0) {
|
|
1015
|
+
steps.push(
|
|
1016
|
+
`Resolve the ${scanLimitationCount} scan limitation${scanLimitationCount === 1 ? "" : "s"} qualifying the readable evidence, then reproduce the scan.`
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
const skippedCount = report.contentReadSummary?.skipped ?? 0;
|
|
1020
|
+
if (skippedCount > 0) {
|
|
1021
|
+
steps.push(
|
|
1022
|
+
`Make the ${skippedCount} skipped content entr${skippedCount === 1 ? "y" : "ies"} readable (see the itemized reasons in the certificate), then reproduce the scan.`
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
if (steps.length === 0) {
|
|
1026
|
+
steps.push(
|
|
1027
|
+
"No unresolved evidence gaps were identified across the categories Cejel tracks; reproduce the scan at the reported revision when one is available before relying on this certificate."
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
return steps;
|
|
1031
|
+
}
|
|
928
1032
|
function buildRelyingPartySummary(report) {
|
|
929
1033
|
const applicable = report.criteria.filter((criterion) => criterion.status !== "not_applicable");
|
|
930
1034
|
const measured = applicable.filter((criterion) => criterion.status !== "insufficient_data");
|
|
@@ -972,7 +1076,7 @@ function buildRelyingPartySummary(report) {
|
|
|
972
1076
|
examined: `Cejel examined the repository evidence recorded for ${report.productDisplayName}${revision} under ${report.rubricVersion}.`,
|
|
973
1077
|
established: `The report established measured results for ${measured.length} of ${applicable.length} applicable rubric dimensions and recorded ${findings} evidence-backed finding${findings === 1 ? "" : "s"}.`,
|
|
974
1078
|
notEstablished: gaps.join(" "),
|
|
975
|
-
next:
|
|
1079
|
+
next: buildNextSteps(report, unmeasured, notApplicable, a1, coverageMetric).join(" ")
|
|
976
1080
|
};
|
|
977
1081
|
}
|
|
978
1082
|
function formatMetricNumber(value) {
|
|
@@ -1091,6 +1195,7 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
1091
1195
|
${options.generatedAt ? `<div><dt>Date</dt><dd>${escapeHtml(formatDate(options.generatedAt))}</dd></div>` : ""}
|
|
1092
1196
|
<div><dt>Run</dt><dd>${escapeHtml(renderRepo(report))}</dd></div>
|
|
1093
1197
|
<div><dt>CLI</dt><dd>${escapeHtml(options.cliVersion ? `Cejel ${options.cliVersion}` : "Not recorded")}</dd></div>
|
|
1198
|
+
${options.runAttempt ? `<div><dt>Run attempt</dt><dd>${escapeHtml(options.runAttempt)}</dd></div>` : ""}
|
|
1094
1199
|
<div><dt>Rubric</dt><dd>${escapeHtml(report.rubricVersion)}</dd></div>
|
|
1095
1200
|
${// Gated on the rubric actually being prospective — a calibrated (v17) certificate's
|
|
1096
1201
|
// HTML stays byte-identical to before --rubric-pin existed.
|
|
@@ -1113,6 +1218,8 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
1113
1218
|
</div>
|
|
1114
1219
|
</header>
|
|
1115
1220
|
|
|
1221
|
+
${renderFindingsFirstSummary(report)}
|
|
1222
|
+
|
|
1116
1223
|
${renderRelyingPartySummary(relyingPartySummary)}
|
|
1117
1224
|
|
|
1118
1225
|
${(report.scanLimitations?.length ?? 0) > 0 ? `<section class="scan-limitations-section evidence-section" aria-label="Scan limitations">
|
|
@@ -1154,6 +1261,26 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
1154
1261
|
</html>
|
|
1155
1262
|
`;
|
|
1156
1263
|
}
|
|
1264
|
+
var FINDINGS_FIRST_SEVERITY_ORDER = {
|
|
1265
|
+
critical: 0,
|
|
1266
|
+
warning: 1,
|
|
1267
|
+
info: 2
|
|
1268
|
+
};
|
|
1269
|
+
function collectFindingsFirstEntries(report) {
|
|
1270
|
+
return report.criteria.flatMap(
|
|
1271
|
+
(criterion) => criterion.findings.filter((finding) => finding.severity === "critical" || finding.severity === "warning").map((finding) => ({ criterion, finding }))
|
|
1272
|
+
).sort(
|
|
1273
|
+
(a, b) => FINDINGS_FIRST_SEVERITY_ORDER[a.finding.severity] - FINDINGS_FIRST_SEVERITY_ORDER[b.finding.severity]
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
function renderFindingsFirstSummary(report) {
|
|
1277
|
+
const entries = collectFindingsFirstEntries(report);
|
|
1278
|
+
const body = entries.length > 0 ? `<ul>${entries.map(({ criterion, finding }) => `<li>${renderFindingEvidence(criterion, finding)}</li>`).join("")}</ul>` : '<p class="findings-first-empty">No critical or warning findings were identified across any criterion in this scan.</p>';
|
|
1279
|
+
return `<section class="findings-first-summary" aria-labelledby="findings-first-heading">
|
|
1280
|
+
<h2 id="findings-first-heading">Critical and warning findings</h2>
|
|
1281
|
+
${body}
|
|
1282
|
+
</section>`;
|
|
1283
|
+
}
|
|
1157
1284
|
function renderRelyingPartySummary(summary) {
|
|
1158
1285
|
return `<section class="relying-party-summary" aria-labelledby="relying-party-heading">
|
|
1159
1286
|
<h2 id="relying-party-heading">How to read this certificate</h2>
|
|
@@ -1167,10 +1294,12 @@ function renderRelyingPartySummary(summary) {
|
|
|
1167
1294
|
}
|
|
1168
1295
|
function renderGlossary(entries) {
|
|
1169
1296
|
return `<section class="glossary" aria-labelledby="glossary-heading">
|
|
1170
|
-
<
|
|
1171
|
-
|
|
1297
|
+
<details>
|
|
1298
|
+
<summary><h2 id="glossary-heading">Plain-language glossary</h2></summary>
|
|
1299
|
+
<dl>${entries.map(
|
|
1172
1300
|
(entry) => `<div><dt id="glossary-${escapeAttribute(entry.key)}">${escapeHtml(entry.term)}</dt><dd>${escapeHtml(entry.definition)}</dd></div>`
|
|
1173
1301
|
).join("")}</dl>
|
|
1302
|
+
</details>
|
|
1174
1303
|
</section>`;
|
|
1175
1304
|
}
|
|
1176
1305
|
function renderContentReadSummary(report) {
|
|
@@ -1245,12 +1374,12 @@ function renderCriterionColumn(title, criteria, gitHistoryUnavailable) {
|
|
|
1245
1374
|
</section>`;
|
|
1246
1375
|
}
|
|
1247
1376
|
function renderNotApplicableGroup(criteria) {
|
|
1248
|
-
return `<
|
|
1249
|
-
<h3 class="na-heading">Not applicable to this repository</h3>
|
|
1377
|
+
return `<details class="na-group">
|
|
1378
|
+
<summary><h3 class="na-heading">Not applicable to this repository</h3></summary>
|
|
1250
1379
|
<ul class="na-list">
|
|
1251
1380
|
${criteria.map(renderNotApplicableItem).join("")}
|
|
1252
1381
|
</ul>
|
|
1253
|
-
</
|
|
1382
|
+
</details>`;
|
|
1254
1383
|
}
|
|
1255
1384
|
function renderNotApplicableItem(criterion) {
|
|
1256
1385
|
const reason = criterion.notes ?? "Not applicable to this repository.";
|
|
@@ -1310,15 +1439,21 @@ function scoreBandForPresentation(score) {
|
|
|
1310
1439
|
if (score >= 1.5) return "warning";
|
|
1311
1440
|
return "critical";
|
|
1312
1441
|
}
|
|
1442
|
+
function renderMetricWeightBadge(criterion, metric2) {
|
|
1443
|
+
const appliedSharePercent = formatCertificateMetricAppliedWeightPercent(criterion, metric2);
|
|
1444
|
+
return `<span class="metric-weight">weight ${appliedSharePercent}% of ${escapeHtml(criterion.id)}</span>`;
|
|
1445
|
+
}
|
|
1313
1446
|
function renderMetric(criterion, metric2) {
|
|
1314
1447
|
const entry = glossaryEntryForMetric(metric2);
|
|
1315
1448
|
const tooltipId = `tooltip-${criterion.id}-${entry.key}`;
|
|
1316
1449
|
const formatted = formatCertificateMetricValue(criterion, metric2);
|
|
1450
|
+
const weightBadge = renderMetricWeightBadge(criterion, metric2);
|
|
1317
1451
|
const cappedEntry = formatted.includes("capped") ? CERTIFICATE_GLOSSARY.find((candidate) => candidate.key === "capped") : void 0;
|
|
1318
1452
|
const label = `<span class="term-help" tabindex="0" aria-describedby="${escapeAttribute(tooltipId)}"><strong>${escapeHtml(formatCertificateMetricLabel(metric2))}</strong><span class="term-tooltip" id="${escapeAttribute(tooltipId)}" role="tooltip">${escapeHtml(entry.definition)}</span></span>`;
|
|
1319
|
-
if (!cappedEntry)
|
|
1453
|
+
if (!cappedEntry)
|
|
1454
|
+
return `${label}<span class="metric-value">${escapeHtml(formatted)} ${weightBadge}</span>`;
|
|
1320
1455
|
const cappedTooltipId = `${tooltipId}-capped`;
|
|
1321
|
-
return `${label}<span class="term-help metric-value" tabindex="0" aria-describedby="${escapeAttribute(cappedTooltipId)}">${escapeHtml(formatted)}<span class="term-tooltip" id="${escapeAttribute(cappedTooltipId)}" role="tooltip">${escapeHtml(cappedEntry.definition)}</span></span>`;
|
|
1456
|
+
return `${label}<span class="term-help metric-value" tabindex="0" aria-describedby="${escapeAttribute(cappedTooltipId)}">${escapeHtml(formatted)} ${weightBadge}<span class="term-tooltip" id="${escapeAttribute(cappedTooltipId)}" role="tooltip">${escapeHtml(cappedEntry.definition)}</span></span>`;
|
|
1322
1457
|
}
|
|
1323
1458
|
function renderCategoryScore(score, coverage, category) {
|
|
1324
1459
|
const categoryCoverage = coverage.byCategory.find((entry) => entry.category === category);
|
|
@@ -1466,6 +1601,9 @@ dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
|
|
|
1466
1601
|
.subscores { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; color: var(--muted); font-family: var(--mono); font-size: 12px; }
|
|
1467
1602
|
.coverage-note { margin-top: 8px; color: var(--faint); font-family: var(--mono); font-size: 11px; }
|
|
1468
1603
|
.source-counts { margin: 8px 0 0; padding-left: 18px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
|
1604
|
+
.findings-first-summary { margin-top: 20px; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface); padding: 22px; }
|
|
1605
|
+
.findings-first-summary ul { margin: 0; padding-left: 18px; display: grid; gap: 10px; }
|
|
1606
|
+
.findings-first-empty { margin: 0; color: var(--muted); }
|
|
1469
1607
|
.relying-party-summary, .glossary { margin-top: 28px; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface); padding: 22px; }
|
|
1470
1608
|
.relying-party-summary dl { display: grid; gap: 14px; margin: 0; }
|
|
1471
1609
|
.relying-party-summary dl div { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 18px; }
|
|
@@ -1473,6 +1611,14 @@ dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
|
|
|
1473
1611
|
.glossary dl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 22px; margin: 0; }
|
|
1474
1612
|
.glossary dl div { border-top: 1px solid var(--line); padding-top: 10px; }
|
|
1475
1613
|
.glossary dd { margin-top: 4px; font-size: 13px; }
|
|
1614
|
+
/* Track A3: native <details>/<summary> disclosure \u2014 the summary is the clickable control, a
|
|
1615
|
+
heading nests inside it as its accessible label (still exposed as a heading), and the heading's
|
|
1616
|
+
own vertical margin is neutralized by making it inline, so the marker sits flush against it. */
|
|
1617
|
+
summary { cursor: pointer; }
|
|
1618
|
+
summary h2, summary h3 { display: inline; }
|
|
1619
|
+
summary::marker { color: var(--periwinkle); }
|
|
1620
|
+
summary:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--periwinkle); border-radius: 3px; }
|
|
1621
|
+
.glossary details[open] dl, .na-group[open] .na-list { margin-top: 16px; }
|
|
1476
1622
|
.trust-grid, .evidence-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 28px; }
|
|
1477
1623
|
.scan-limitations-section { margin-top: 28px; border-color: rgba(231, 191, 114, .44); }
|
|
1478
1624
|
.scan-limitations-section .scan-warning { list-style: none; margin-left: -18px; }
|
|
@@ -1504,6 +1650,7 @@ h3 { font-size: 15px; line-height: 1.35; font-weight: 600; margin-bottom: 0; }
|
|
|
1504
1650
|
.criterion-metrics li { display: flex; justify-content: space-between; gap: 12px; border-bottom: 1px solid rgba(238, 244, 247, .08); padding-bottom: 6px; }
|
|
1505
1651
|
.criterion-metrics strong { color: var(--text); font-weight: 650; }
|
|
1506
1652
|
.criterion-metrics span { color: var(--muted); }
|
|
1653
|
+
.criterion-metrics .metric-weight { font-size: 10px; color: var(--faint); }
|
|
1507
1654
|
.criterion-metrics > li > .metric-value {
|
|
1508
1655
|
min-width: 0; white-space: normal; overflow-wrap: anywhere; text-align: right;
|
|
1509
1656
|
}
|
|
@@ -1667,6 +1814,7 @@ function createWitanAttestation(report, options) {
|
|
|
1667
1814
|
tool: { name: "cejel", version: options.toolVersion },
|
|
1668
1815
|
generatedAt: options.generatedAt,
|
|
1669
1816
|
reportFormatVersion: WITAN_REPORT_FORMAT_VERSION,
|
|
1817
|
+
...options.githubRunAttempt ? { githubRunAttempt: options.githubRunAttempt } : {},
|
|
1670
1818
|
rubricVersion: report.rubricVersion,
|
|
1671
1819
|
repository: {
|
|
1672
1820
|
productSlug: report.productSlug,
|
|
@@ -1788,7 +1936,7 @@ function stripBom(text) {
|
|
|
1788
1936
|
|
|
1789
1937
|
// src/witan/repo-signals.ts
|
|
1790
1938
|
function usesV17DetectorClosure(rubricVersion) {
|
|
1791
|
-
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
1939
|
+
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
1792
1940
|
}
|
|
1793
1941
|
var LEGACY_INVENTORY_ABSENCE_CONTEXT = {
|
|
1794
1942
|
rubricGeneration: "legacy",
|
|
@@ -1803,10 +1951,14 @@ var INVENTORY_SCAN_PATTERN_SETS = {
|
|
|
1803
1951
|
id: "cejel.core-a1.concrete-test-files.v1",
|
|
1804
1952
|
count: 16
|
|
1805
1953
|
},
|
|
1806
|
-
|
|
1954
|
+
a1CoverageConfigurationV1: {
|
|
1807
1955
|
id: "cejel.core-a1.coverage-configuration.v1",
|
|
1808
1956
|
count: 10
|
|
1809
1957
|
},
|
|
1958
|
+
a1CoverageConfigurationV2: {
|
|
1959
|
+
id: "cejel.core-a1.coverage-configuration.v2",
|
|
1960
|
+
count: 14
|
|
1961
|
+
},
|
|
1810
1962
|
a2CurrentSecret: {
|
|
1811
1963
|
id: "cejel.core-a2.current-secret-shape.v1",
|
|
1812
1964
|
count: 3
|
|
@@ -1855,25 +2007,77 @@ function inventoryAbsenceFinding(options) {
|
|
|
1855
2007
|
} : {}
|
|
1856
2008
|
};
|
|
1857
2009
|
}
|
|
2010
|
+
function wholesaleCriterionAbstention(signal) {
|
|
2011
|
+
return {
|
|
2012
|
+
criterionId: signal.criterionId,
|
|
2013
|
+
positiveEvidence: [],
|
|
2014
|
+
findings: [],
|
|
2015
|
+
metrics: [],
|
|
2016
|
+
insufficientData: true,
|
|
2017
|
+
notes: "Cejel abstained on this criterion because one or more relevant repository files could not be read. Resolve the content-read limitations and re-scan."
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
1858
2020
|
function buildWitanInputFromRepo(options) {
|
|
1859
2021
|
const tracked = trackContentReads(options.repoPath, () => buildWitanInputFromRepoUntracked(options));
|
|
1860
2022
|
const affectedCriteria = tracked.affectedCriteria;
|
|
2023
|
+
const rubricVersion = options.rubricVersion ?? WITAN_RUBRIC_VERSION;
|
|
2024
|
+
if (rubricVersion !== WITAN_RUBRIC_VERSION_V23) {
|
|
2025
|
+
const signals2 = (tracked.value.signals ?? []).map(
|
|
2026
|
+
(signal) => affectedCriteria.has(signal.criterionId) ? wholesaleCriterionAbstention(signal) : signal
|
|
2027
|
+
);
|
|
2028
|
+
const affectedCount = affectedCriteria.size;
|
|
2029
|
+
const scanLimitations2 = [...tracked.value.scanLimitations ?? []];
|
|
2030
|
+
if (affectedCount > 0 && scanLimitations2.length < 16) {
|
|
2031
|
+
scanLimitations2.push(
|
|
2032
|
+
`${affectedCount} ${affectedCount === 1 ? "criterion" : "criteria"} abstained because relevant repository content could not be read. Counts and errno classes are recorded in contentReadSummary; file paths are intentionally omitted.`
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
return {
|
|
2036
|
+
...tracked.value,
|
|
2037
|
+
signals: signals2,
|
|
2038
|
+
...scanLimitations2.length > 0 ? { scanLimitations: scanLimitations2 } : {},
|
|
2039
|
+
contentReadSummary: tracked.summary
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
const wholesaleAbstainedCriteria = [];
|
|
2043
|
+
const partiallyAbstainedSignals = [];
|
|
1861
2044
|
const signals = (tracked.value.signals ?? []).map((signal) => {
|
|
1862
2045
|
if (!affectedCriteria.has(signal.criterionId)) return signal;
|
|
2046
|
+
if (tracked.unattributedCriteria.has(signal.criterionId)) {
|
|
2047
|
+
wholesaleAbstainedCriteria.push(signal.criterionId);
|
|
2048
|
+
return wholesaleCriterionAbstention(signal);
|
|
2049
|
+
}
|
|
2050
|
+
const prefix = contentReadSignalKey(signal.criterionId, "");
|
|
2051
|
+
const affectedNames = [...tracked.affectedSignals].filter((key) => key.startsWith(prefix)).map((key) => key.slice(prefix.length));
|
|
2052
|
+
if (affectedNames.length === 0) {
|
|
2053
|
+
wholesaleAbstainedCriteria.push(signal.criterionId);
|
|
2054
|
+
return wholesaleCriterionAbstention(signal);
|
|
2055
|
+
}
|
|
2056
|
+
const affectedNameSet = new Set(affectedNames);
|
|
2057
|
+
const metrics = (signal.metrics ?? []).filter((metric2) => !affectedNameSet.has(metric2.name));
|
|
2058
|
+
const anythingSurvives = metrics.length > 0 || (signal.positiveEvidence?.length ?? 0) > 0 || (signal.findings?.length ?? 0) > 0;
|
|
2059
|
+
if (!anythingSurvives) {
|
|
2060
|
+
wholesaleAbstainedCriteria.push(signal.criterionId);
|
|
2061
|
+
return wholesaleCriterionAbstention(signal);
|
|
2062
|
+
}
|
|
2063
|
+
for (const name of affectedNames) {
|
|
2064
|
+
partiallyAbstainedSignals.push(`${signal.criterionId}.${name}`);
|
|
2065
|
+
}
|
|
1863
2066
|
return {
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
metrics: [],
|
|
1868
|
-
insufficientData: true,
|
|
1869
|
-
notes: "Cejel abstained on this criterion because one or more relevant repository files could not be read. Resolve the content-read limitations and re-scan."
|
|
2067
|
+
...signal,
|
|
2068
|
+
metrics,
|
|
2069
|
+
notes: `Cejel abstained on ${[...affectedNames].sort().join(", ")} in this criterion because that content could not be read; other signals in this criterion were computed from readable files and are unaffected.`
|
|
1870
2070
|
};
|
|
1871
2071
|
});
|
|
1872
|
-
const affectedCount = affectedCriteria.size;
|
|
1873
2072
|
const scanLimitations = [...tracked.value.scanLimitations ?? []];
|
|
1874
|
-
if (
|
|
2073
|
+
if (wholesaleAbstainedCriteria.length > 0 && scanLimitations.length < 16) {
|
|
1875
2074
|
scanLimitations.push(
|
|
1876
|
-
`${
|
|
2075
|
+
`${wholesaleAbstainedCriteria.length} ${wholesaleAbstainedCriteria.length === 1 ? "criterion" : "criteria"} abstained because relevant repository content could not be read and the affected signal could not be isolated. Counts and errno classes are recorded in contentReadSummary; file paths are intentionally omitted.`
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
if (partiallyAbstainedSignals.length > 0 && scanLimitations.length < 16) {
|
|
2079
|
+
scanLimitations.push(
|
|
2080
|
+
`${partiallyAbstainedSignals.length} ${partiallyAbstainedSignals.length === 1 ? "signal" : "signals"} abstained because its own repository content could not be read, while other signals in the same criterion were unaffected: ${[...partiallyAbstainedSignals].sort().join(", ")}.`
|
|
1877
2081
|
);
|
|
1878
2082
|
}
|
|
1879
2083
|
return {
|
|
@@ -1897,11 +2101,12 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
1897
2101
|
rubricGeneration: usesV17Detectors ? "current" : "legacy",
|
|
1898
2102
|
inventoryProvenance: fileInventory.repoFileInventoryScope === "tracked-scan-eligible" ? "tracked-scan-eligible" : "directory-walk"
|
|
1899
2103
|
};
|
|
1900
|
-
const usesV18NativeRls = rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
1901
|
-
const usesV19CommitYear = rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
1902
|
-
const usesV20A3ExplicitGaps = rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
1903
|
-
const usesV21ExecutedEscalations = rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
1904
|
-
const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
2104
|
+
const usesV18NativeRls = rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
2105
|
+
const usesV19CommitYear = rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
2106
|
+
const usesV20A3ExplicitGaps = rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
2107
|
+
const usesV21ExecutedEscalations = rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
2108
|
+
const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
2109
|
+
const usesV23CommandCoverage = rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
1905
2110
|
const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
|
|
1906
2111
|
const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure(rubricVersion) ? applyReadableSourceRepresentationGate(
|
|
1907
2112
|
options.repoPath,
|
|
@@ -1937,6 +2142,7 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
1937
2142
|
usesV20A3ExplicitGaps,
|
|
1938
2143
|
usesV21ExecutedEscalations,
|
|
1939
2144
|
usesV22PackageStartEntrypoint,
|
|
2145
|
+
usesV23CommandCoverage,
|
|
1940
2146
|
reviewableSourceProof,
|
|
1941
2147
|
inventoryAbsenceContext,
|
|
1942
2148
|
scanLimitations
|
|
@@ -2548,7 +2754,7 @@ function buildNotApplicableSignal(criterionId, reason) {
|
|
|
2548
2754
|
notApplicable: true
|
|
2549
2755
|
};
|
|
2550
2756
|
}
|
|
2551
|
-
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
|
|
2757
|
+
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23CommandCoverage, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
|
|
2552
2758
|
const signals = [];
|
|
2553
2759
|
const mono = resolveMonorepoContext(repoPath, scanLimitations);
|
|
2554
2760
|
const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
|
|
@@ -2560,6 +2766,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
2560
2766
|
useV27Detectors,
|
|
2561
2767
|
useV33Detectors,
|
|
2562
2768
|
useV36Detectors,
|
|
2769
|
+
useV23CommandCoverage,
|
|
2563
2770
|
reviewableSourceProof,
|
|
2564
2771
|
inventoryAbsenceContext
|
|
2565
2772
|
)
|
|
@@ -2659,7 +2866,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
2659
2866
|
}
|
|
2660
2867
|
return signals;
|
|
2661
2868
|
}
|
|
2662
|
-
function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
2869
|
+
function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV23CommandCoverage, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
2663
2870
|
const evidence = [];
|
|
2664
2871
|
const findings = [];
|
|
2665
2872
|
const allTestFiles = findConcreteTestFiles(
|
|
@@ -2670,17 +2877,33 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2670
2877
|
const testFiles = allTestFiles.slice(0, 8);
|
|
2671
2878
|
const sourceFiles = repoFiles.filter(isSourceFile);
|
|
2672
2879
|
const runnerFiles = repoFiles.filter(
|
|
2673
|
-
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isTestRunnerConfig(repoPath, file)
|
|
2880
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && (isTestRunnerConfig(repoPath, file) || useV23CommandCoverage && isRecipeTestEntry(repoPath, file))
|
|
2674
2881
|
).slice(0, 6);
|
|
2675
|
-
const
|
|
2676
|
-
const
|
|
2677
|
-
|
|
2882
|
+
const baseConfiguredRunnerFiles = useV33Detectors ? findConfiguredTestRunnerFiles(repoPath, repoFiles) : runnerFiles;
|
|
2883
|
+
const configuredRunnerFiles = useV23CommandCoverage ? [
|
|
2884
|
+
.../* @__PURE__ */ new Set([
|
|
2885
|
+
...baseConfiguredRunnerFiles,
|
|
2886
|
+
...repoFiles.filter((file) => isRecipeTestEntry(repoPath, file))
|
|
2887
|
+
])
|
|
2888
|
+
].sort() : baseConfiguredRunnerFiles;
|
|
2678
2889
|
const packageJson = findRootPackageJson(repoFiles);
|
|
2679
2890
|
const packageScripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
|
|
2891
|
+
const { coverageAnalysis, coveragePercent } = withContentReadSignal("A1", "coverage_percent", () => {
|
|
2892
|
+
const analysis = useV23CommandCoverage ? analyzeCoverageConfiguration(repoPath, repoFiles, useV27Detectors) : {
|
|
2893
|
+
coverageFiles: findCoverageConfigFiles(repoPath, repoFiles, useV27Detectors),
|
|
2894
|
+
unresolvedFiles: [],
|
|
2895
|
+
hasCoverageCommand: packageScripts.has("coverage")
|
|
2896
|
+
};
|
|
2897
|
+
return {
|
|
2898
|
+
coverageAnalysis: analysis,
|
|
2899
|
+
coveragePercent: readCoveragePercent(repoPath, analysis.coverageFiles.slice(0, 4))
|
|
2900
|
+
};
|
|
2901
|
+
});
|
|
2902
|
+
const allCoverageFiles = coverageAnalysis.coverageFiles;
|
|
2903
|
+
const coverageFiles = allCoverageFiles.slice(0, 4);
|
|
2680
2904
|
const packageJsonFiles = useV27Detectors ? repoFiles.filter(
|
|
2681
2905
|
(file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
|
|
2682
2906
|
) : packageJson ? [packageJson] : [];
|
|
2683
|
-
const coveragePercent = readCoveragePercent(repoPath, coverageFiles);
|
|
2684
2907
|
const ciWorkflows = repoFiles.filter(
|
|
2685
2908
|
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isCiWorkflow(file)
|
|
2686
2909
|
);
|
|
@@ -2695,7 +2918,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2695
2918
|
);
|
|
2696
2919
|
const verificationScriptCount = [
|
|
2697
2920
|
packageScripts.has("test") || Boolean(ciTestWorkflow),
|
|
2698
|
-
|
|
2921
|
+
coverageAnalysis.hasCoverageCommand,
|
|
2699
2922
|
packageScripts.has("lint") || ciHasLintCommand,
|
|
2700
2923
|
packageScripts.has("typecheck") || ciHasTypecheckCommand
|
|
2701
2924
|
].filter(Boolean).length;
|
|
@@ -2715,9 +2938,11 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2715
2938
|
if (packageJson && [...packageScripts.values()].some((script) => TEST_RUNNER_PATTERN.test(script))) {
|
|
2716
2939
|
evidence.push(evidenceForRelative(repoPath, packageJson, "test_run", "Configured test runner"));
|
|
2717
2940
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2941
|
+
withContentReadSignal("A1", "coverage_percent", () => {
|
|
2942
|
+
for (const file of coverageFiles) {
|
|
2943
|
+
evidence.push(evidenceForRelative(repoPath, file, "coverage", "Coverage configuration"));
|
|
2944
|
+
}
|
|
2945
|
+
});
|
|
2721
2946
|
if (ciTestWorkflow) {
|
|
2722
2947
|
evidence.push(
|
|
2723
2948
|
evidenceForRelative(repoPath, ciTestWorkflow, "test_run", "CI workflow runs the test suite")
|
|
@@ -2787,7 +3012,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2787
3012
|
})
|
|
2788
3013
|
);
|
|
2789
3014
|
}
|
|
2790
|
-
} else if (coverageFiles.length === 0 && (!useV33Detectors || configuredRunnerFiles.length > 0)) {
|
|
3015
|
+
} else if (coverageFiles.length === 0 && coverageAnalysis.unresolvedFiles.length === 0 && (!useV33Detectors || configuredRunnerFiles.length > 0)) {
|
|
2791
3016
|
if (isLeanTestToolchain) {
|
|
2792
3017
|
const leanEvidenceFile = packageJson ?? testFiles[0];
|
|
2793
3018
|
if (leanEvidenceFile) {
|
|
@@ -2814,16 +3039,16 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2814
3039
|
evidence: firstEvidence,
|
|
2815
3040
|
context: inventoryAbsenceContext,
|
|
2816
3041
|
inventoryCount: repoFiles.length,
|
|
2817
|
-
patternSet: INVENTORY_SCAN_PATTERN_SETS.
|
|
3042
|
+
patternSet: useV23CommandCoverage ? INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV2 : INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV1,
|
|
2818
3043
|
matchCount: 0
|
|
2819
3044
|
})
|
|
2820
3045
|
);
|
|
2821
3046
|
}
|
|
2822
3047
|
}
|
|
2823
|
-
const nonHollowShare =
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
reviewableSourceProof !== void 0
|
|
3048
|
+
const nonHollowShare = withContentReadSignal(
|
|
3049
|
+
"A1",
|
|
3050
|
+
"non_hollow_test_share",
|
|
3051
|
+
() => measureNonHollowTestShare(repoPath, allTestFiles, reviewableSourceProof !== void 0)
|
|
2827
3052
|
);
|
|
2828
3053
|
return {
|
|
2829
3054
|
criterionId: "A1",
|
|
@@ -2864,7 +3089,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2864
3089
|
label: "test command",
|
|
2865
3090
|
count: packageScripts.has("test") || Boolean(ciTestWorkflow) ? 1 : 0
|
|
2866
3091
|
},
|
|
2867
|
-
{ label: "coverage command", count:
|
|
3092
|
+
{ label: "coverage command", count: coverageAnalysis.hasCoverageCommand ? 1 : 0 },
|
|
2868
3093
|
{
|
|
2869
3094
|
label: "lint command",
|
|
2870
3095
|
count: packageScripts.has("lint") || ciHasLintCommand ? 1 : 0
|
|
@@ -2887,7 +3112,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2887
3112
|
"Penalizes skipped or placeholder-only test files; test-directory support scaffolding (helpers/fixtures with no test in them) is excluded from the denominator."
|
|
2888
3113
|
)
|
|
2889
3114
|
],
|
|
2890
|
-
notes: "A1 is detected from real test files, test runner configuration, and optional coverage configuration."
|
|
3115
|
+
notes: coverageFiles.length === 0 && coverageAnalysis.unresolvedFiles.length > 0 ? `A1 is detected from real test files, test runner configuration, and optional coverage configuration. Coverage command reachability or flag ownership was unresolved in ${coverageAnalysis.unresolvedFiles.slice(0, 4).join(", ")}; the no-coverage absence claim was withheld.` : "A1 is detected from real test files, test runner configuration, and optional coverage configuration."
|
|
2891
3116
|
};
|
|
2892
3117
|
}
|
|
2893
3118
|
function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inventoryAbsenceContext) {
|
|
@@ -2979,40 +3204,47 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2979
3204
|
const migrationFiles = repoFiles.filter(
|
|
2980
3205
|
(file) => /(^|\/)(migrations?|drizzle|prisma)\//.test(file)
|
|
2981
3206
|
);
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3207
|
+
const { committedSecret, currentSecretFingerprintsByPath } = withContentReadSignal(
|
|
3208
|
+
"A2",
|
|
3209
|
+
"secret_cleanliness",
|
|
3210
|
+
() => {
|
|
3211
|
+
let match_;
|
|
3212
|
+
for (const path of repoFiles) {
|
|
3213
|
+
const authoredProductionPath = useV47Detectors ? isV47AuthoredProductionPath(path) : useV39Detectors ? isV39AuthoredProductionPath(path) : isAuthoredProductionPath(path);
|
|
3214
|
+
if (isIgnoredScanFile(path, useV47Detectors) || useV33Detectors && !authoredProductionPath) {
|
|
3215
|
+
continue;
|
|
3216
|
+
}
|
|
3217
|
+
const found = findCommittedSecretInFile(
|
|
3218
|
+
repoPath,
|
|
3219
|
+
path,
|
|
3220
|
+
useV36Detectors,
|
|
3221
|
+
useV39Detectors,
|
|
3222
|
+
useV47Detectors
|
|
3223
|
+
);
|
|
3224
|
+
if (!found) continue;
|
|
3225
|
+
match_ = { path, match: found };
|
|
3226
|
+
break;
|
|
3227
|
+
}
|
|
3228
|
+
const fingerprintsByPath = /* @__PURE__ */ new Map();
|
|
3229
|
+
if (useV27Detectors) {
|
|
3230
|
+
for (const path of repoFiles.filter((candidate) => isCredentialHistoryPath(candidate))) {
|
|
3231
|
+
fingerprintsByPath.set(
|
|
3007
3232
|
path,
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3233
|
+
new Set(
|
|
3234
|
+
findSecretFingerprintsInFile(
|
|
3235
|
+
repoPath,
|
|
3236
|
+
path,
|
|
3237
|
+
useV36Detectors,
|
|
3238
|
+
useV39Detectors,
|
|
3239
|
+
useV47Detectors
|
|
3240
|
+
)
|
|
3241
|
+
)
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
return { committedSecret: match_, currentSecretFingerprintsByPath: fingerprintsByPath };
|
|
3014
3246
|
}
|
|
3015
|
-
|
|
3247
|
+
);
|
|
3016
3248
|
const historySecretScan = committedSecret && !useV27Detectors ? null : collectHistorySecretEvidence(
|
|
3017
3249
|
repoPath,
|
|
3018
3250
|
useV27Detectors,
|
|
@@ -3024,10 +3256,14 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
3024
3256
|
useV47Detectors
|
|
3025
3257
|
);
|
|
3026
3258
|
const hasConfirmedSecretFinding = committedSecret != null || historySecretScan?.evidence != null;
|
|
3027
|
-
const cryptoHygiene =
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3259
|
+
const cryptoHygiene = withContentReadSignal(
|
|
3260
|
+
"A2",
|
|
3261
|
+
"crypto_comparison_hygiene",
|
|
3262
|
+
() => collectCryptoHygieneEvidence(
|
|
3263
|
+
repoPath,
|
|
3264
|
+
repoFiles.filter(isImplementationFile).slice(0, 60),
|
|
3265
|
+
useV39Detectors
|
|
3266
|
+
)
|
|
3031
3267
|
);
|
|
3032
3268
|
const implFiles = repoFiles.filter(isImplementationFile);
|
|
3033
3269
|
const gitignoreHasEnvRule = gitignore != null && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m);
|
|
@@ -3067,42 +3303,77 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
3067
3303
|
} else if (dbLayerImportFile != null) {
|
|
3068
3304
|
evidence.push(evidenceForRelative(repoPath, dbLayerImportFile, "artifact", "DB client import"));
|
|
3069
3305
|
}
|
|
3070
|
-
const v47RlsPolicyFiles = useV47Detectors ? findRlsPolicyFiles(repoPath, repoFiles) : [];
|
|
3071
|
-
const nativeRlsInventory = useV18NativeRls ? deriveRlsPolicyScopeInventory(repoPath, repoFiles) : null;
|
|
3072
|
-
const tenantWithoutRlsPremiseFiles = useV18NativeRls ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
|
|
3073
|
-
const v47TenantStorageFiles = useV47Detectors && !useV18NativeRls ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
|
|
3074
|
-
const rlsMigration = useV47Detectors ? v47RlsPolicyFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, RLS_PATTERN));
|
|
3075
3306
|
const tenantScopePattern = useV39Detectors ? TENANT_SCOPE_PATTERN_V11 : useV36Detectors ? TENANT_SCOPE_PATTERN_V10 : TENANT_SCOPE_PATTERN;
|
|
3076
|
-
const
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3307
|
+
const {
|
|
3308
|
+
rlsMigration,
|
|
3309
|
+
tenantScopedFile,
|
|
3310
|
+
rlsPolicyCount,
|
|
3311
|
+
tenantScopedMigrationFileCount,
|
|
3312
|
+
nativeRlsInventory,
|
|
3313
|
+
tenantWithoutRlsPremiseFiles
|
|
3314
|
+
} = withContentReadSignals("A2", ["rls_policy_count", "tenant_scope_ratio"], () => {
|
|
3315
|
+
const v47RlsPolicyFiles = useV47Detectors ? findRlsPolicyFiles(repoPath, repoFiles) : [];
|
|
3316
|
+
const rlsInventory = useV18NativeRls ? deriveRlsPolicyScopeInventory(repoPath, repoFiles) : null;
|
|
3317
|
+
const tenantWithoutRlsFiles = useV18NativeRls ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
|
|
3318
|
+
const v47TenantStorageFiles = useV47Detectors && !useV18NativeRls ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
|
|
3319
|
+
return {
|
|
3320
|
+
rlsMigration: useV47Detectors ? v47RlsPolicyFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, RLS_PATTERN)),
|
|
3321
|
+
tenantScopedFile: useV18NativeRls ? rlsInventory?.storageFiles[0] : useV47Detectors ? v47TenantStorageFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, tenantScopePattern)),
|
|
3322
|
+
rlsPolicyCount: useV18NativeRls ? rlsInventory?.policyCount ?? 0 : useV47Detectors ? v47RlsPolicyFiles.length : countPatternMatches(
|
|
3323
|
+
repoPath,
|
|
3324
|
+
migrationFiles,
|
|
3325
|
+
/create policy|enable row level security|force row level security/gi
|
|
3326
|
+
),
|
|
3327
|
+
tenantScopedMigrationFileCount: useV18NativeRls ? rlsInventory?.storageFiles.length ?? 0 : useV47Detectors ? v47TenantStorageFiles.length : countFilesContaining(repoPath, migrationFiles, tenantScopePattern),
|
|
3328
|
+
nativeRlsInventory: rlsInventory,
|
|
3329
|
+
tenantWithoutRlsPremiseFiles: tenantWithoutRlsFiles
|
|
3330
|
+
};
|
|
3331
|
+
});
|
|
3332
|
+
const { gitignoreEnvFile, hasEnvReadsInImplementation, envHandlingDepth } = withContentReadSignal(
|
|
3333
|
+
"A2",
|
|
3334
|
+
"env_handling_depth",
|
|
3335
|
+
() => {
|
|
3336
|
+
const envFile = gitignore && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m) ? gitignore : null;
|
|
3337
|
+
const hasEnvReads = countFilesContaining(repoPath, repoFiles.filter(isImplementationFile), ENV_READ_PATTERN) > 0;
|
|
3338
|
+
const depth = (envExamples.length > 0 ? 1 : 0) + (envFile ? 1 : 0) + (hasEnvReads ? 1 : 0);
|
|
3339
|
+
return { gitignoreEnvFile: envFile, hasEnvReadsInImplementation: hasEnvReads, envHandlingDepth: depth };
|
|
3340
|
+
}
|
|
3081
3341
|
);
|
|
3082
|
-
const tenantScopedMigrationFileCount = useV18NativeRls ? nativeRlsInventory?.storageFiles.length ?? 0 : useV47Detectors ? v47TenantStorageFiles.length : countFilesContaining(repoPath, migrationFiles, tenantScopePattern);
|
|
3083
|
-
const gitignoreEnvFile = gitignore && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m) ? gitignore : null;
|
|
3084
|
-
const envHandlingDepth = (envExamples.length > 0 ? 1 : 0) + (gitignoreEnvFile ? 1 : 0) + (countFilesContaining(repoPath, repoFiles.filter(isImplementationFile), ENV_READ_PATTERN) > 0 ? 1 : 0);
|
|
3085
3342
|
const isMultiTenant = useV18NativeRls ? (nativeRlsInventory?.policyCount ?? 0) > 0 && (nativeRlsInventory?.scopeIdentifiers.length ?? 0) > 0 : tenantScopedMigrationFileCount > 0;
|
|
3086
3343
|
if (gitignoreEnvFile) {
|
|
3087
|
-
|
|
3088
|
-
|
|
3344
|
+
withContentReadSignal(
|
|
3345
|
+
"A2",
|
|
3346
|
+
"env_handling_depth",
|
|
3347
|
+
() => evidence.push(
|
|
3348
|
+
evidenceForRelative(repoPath, gitignoreEnvFile, "secret_scan", ".env files are gitignored")
|
|
3349
|
+
)
|
|
3089
3350
|
);
|
|
3090
3351
|
}
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3352
|
+
withContentReadSignal("A2", "env_handling_depth", () => {
|
|
3353
|
+
for (const file of envExamples.slice(0, 3)) {
|
|
3354
|
+
evidence.push(evidenceForRelative(repoPath, file, "secret_scan", "Environment template"));
|
|
3355
|
+
}
|
|
3356
|
+
});
|
|
3094
3357
|
if (rlsMigration) {
|
|
3095
|
-
|
|
3096
|
-
|
|
3358
|
+
withContentReadSignals(
|
|
3359
|
+
"A2",
|
|
3360
|
+
["rls_policy_count", "tenant_scope_ratio"],
|
|
3361
|
+
() => evidence.push(
|
|
3362
|
+
evidenceForRelative(repoPath, rlsMigration, "artifact", "RLS or tenant migration")
|
|
3363
|
+
)
|
|
3097
3364
|
);
|
|
3098
3365
|
}
|
|
3099
3366
|
if (tenantScopedFile) {
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3367
|
+
withContentReadSignals(
|
|
3368
|
+
"A2",
|
|
3369
|
+
["rls_policy_count", "tenant_scope_ratio"],
|
|
3370
|
+
() => evidence.push(
|
|
3371
|
+
evidenceForRelative(
|
|
3372
|
+
repoPath,
|
|
3373
|
+
tenantScopedFile,
|
|
3374
|
+
"artifact",
|
|
3375
|
+
useV18NativeRls ? `Repository-native RLS scope (${nativeRlsInventory?.scopeIdentifiers.join(", ")})` : "Tenant scoping signal"
|
|
3376
|
+
)
|
|
3106
3377
|
)
|
|
3107
3378
|
);
|
|
3108
3379
|
}
|
|
@@ -3226,11 +3497,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
3226
3497
|
{ label: ".gitignore environment-file rule", count: gitignoreEnvFile ? 1 : 0 },
|
|
3227
3498
|
{
|
|
3228
3499
|
label: "environment reads in implementation code",
|
|
3229
|
-
count:
|
|
3230
|
-
repoPath,
|
|
3231
|
-
repoFiles.filter(isImplementationFile),
|
|
3232
|
-
ENV_READ_PATTERN
|
|
3233
|
-
) > 0 ? 1 : 0
|
|
3500
|
+
count: hasEnvReadsInImplementation ? 1 : 0
|
|
3234
3501
|
}
|
|
3235
3502
|
]
|
|
3236
3503
|
}
|
|
@@ -3409,37 +3676,51 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3409
3676
|
const envTemplate = repoFiles.find(
|
|
3410
3677
|
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isEnvTemplatePath(file)
|
|
3411
3678
|
);
|
|
3412
|
-
const healthChecks =
|
|
3413
|
-
|
|
3679
|
+
const healthChecks = withContentReadSignal(
|
|
3680
|
+
"A3",
|
|
3681
|
+
"prod_readiness_primitives",
|
|
3682
|
+
() => repoFiles.filter(
|
|
3683
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isHealthCheckSignalFile(repoPath, file)
|
|
3684
|
+
)
|
|
3414
3685
|
);
|
|
3415
3686
|
const healthCheck = healthChecks[0];
|
|
3416
|
-
const hasV20HealthOrReadinessRoute =
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3687
|
+
const { hasV20HealthOrReadinessRoute, hasV22PackageStartHealthOrReadinessRoute } = withContentReadSignal("A3", "health_readiness_route", () => ({
|
|
3688
|
+
hasV20HealthOrReadinessRoute: useV20ExplicitGaps && repoFiles.some(
|
|
3689
|
+
(file) => isAuthoredProductionPath(file) && isImplementationFile(file) && fileContains(repoPath, file, V20_HEALTH_OR_READINESS_ROUTE_PATTERN)
|
|
3690
|
+
),
|
|
3691
|
+
hasV22PackageStartHealthOrReadinessRoute: v22PackageStartHttpEntrypoint !== null && fileContains(
|
|
3692
|
+
repoPath,
|
|
3693
|
+
v22PackageStartHttpEntrypoint,
|
|
3694
|
+
V20_HEALTH_OR_READINESS_ROUTE_PATTERN
|
|
3695
|
+
)
|
|
3696
|
+
}));
|
|
3424
3697
|
const serverEntrypoint = v22PackageStartHttpEntrypoint ?? findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) ?? v20DirectHttpEntrypoint ?? null;
|
|
3425
3698
|
const runtimeContainer = useV27Detectors ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, useV20ExplicitGaps) : null;
|
|
3426
3699
|
const errorBoundaries = repoFiles.filter(
|
|
3427
3700
|
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
|
|
3428
3701
|
);
|
|
3429
3702
|
const errorBoundary = errorBoundaries[0];
|
|
3430
|
-
const observabilityCount =
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3703
|
+
const observabilityCount = withContentReadSignal(
|
|
3704
|
+
"A3",
|
|
3705
|
+
"observability_depth",
|
|
3706
|
+
() => countFilesContaining(
|
|
3707
|
+
repoPath,
|
|
3708
|
+
repoFiles.filter(
|
|
3709
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file)
|
|
3710
|
+
),
|
|
3711
|
+
/sentry|otel|opentelemetry|datadog|prometheus|metrics|logger|logtail/i
|
|
3712
|
+
)
|
|
3436
3713
|
);
|
|
3437
|
-
const rollbackSafetyCount =
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3714
|
+
const rollbackSafetyCount = withContentReadSignal(
|
|
3715
|
+
"A3",
|
|
3716
|
+
"rollback_safety_depth",
|
|
3717
|
+
() => countFilesContaining(
|
|
3718
|
+
repoPath,
|
|
3719
|
+
repoFiles.filter(
|
|
3720
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /(^|\/)(docs|migrations?|drizzle|prisma|scripts)\//.test(file)
|
|
3721
|
+
),
|
|
3722
|
+
/rollback|roll back|migration safety|down migration|reversible|undo migration/i
|
|
3723
|
+
)
|
|
3443
3724
|
);
|
|
3444
3725
|
if (packageJson && (scripts.has("build") || scripts.has("typecheck"))) {
|
|
3445
3726
|
evidence.push(
|
|
@@ -3467,10 +3748,15 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3467
3748
|
);
|
|
3468
3749
|
if (envTemplate)
|
|
3469
3750
|
evidence.push(evidenceForRelative(repoPath, envTemplate, "prod_check", "Environment template"));
|
|
3470
|
-
if (healthCheck)
|
|
3471
|
-
|
|
3472
|
-
|
|
3751
|
+
if (healthCheck) {
|
|
3752
|
+
withContentReadSignal(
|
|
3753
|
+
"A3",
|
|
3754
|
+
"prod_readiness_primitives",
|
|
3755
|
+
() => evidence.push(
|
|
3756
|
+
evidenceForRelative(repoPath, healthCheck, "prod_check", "Health/readiness signal")
|
|
3757
|
+
)
|
|
3473
3758
|
);
|
|
3759
|
+
}
|
|
3474
3760
|
if (errorBoundary)
|
|
3475
3761
|
evidence.push(evidenceForRelative(repoPath, errorBoundary, "prod_check", "Error boundary"));
|
|
3476
3762
|
if (evidence.length === 0) {
|
|
@@ -3509,7 +3795,12 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3509
3795
|
)
|
|
3510
3796
|
});
|
|
3511
3797
|
} else if (runtimeContainer) {
|
|
3512
|
-
|
|
3798
|
+
const hasActiveHealthcheck = withContentReadSignal(
|
|
3799
|
+
"A3",
|
|
3800
|
+
"dockerfile_healthcheck",
|
|
3801
|
+
() => fileContains(repoPath, runtimeContainer, /^\s*HEALTHCHECK\s+(?!NONE\b)/im)
|
|
3802
|
+
);
|
|
3803
|
+
if (!hasActiveHealthcheck) {
|
|
3513
3804
|
findings.push({
|
|
3514
3805
|
severity: "info",
|
|
3515
3806
|
summary: "A runtime Dockerfile declares no active HEALTHCHECK instruction.",
|
|
@@ -3521,10 +3812,14 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3521
3812
|
)
|
|
3522
3813
|
});
|
|
3523
3814
|
}
|
|
3524
|
-
} else if (serverEntrypoint &&
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3815
|
+
} else if (serverEntrypoint && withContentReadSignal(
|
|
3816
|
+
"A3",
|
|
3817
|
+
"health_readiness_route",
|
|
3818
|
+
() => fileContains(
|
|
3819
|
+
repoPath,
|
|
3820
|
+
serverEntrypoint,
|
|
3821
|
+
/\b(?:request|req)\.(?:url|path|pathname|method)\b/i
|
|
3822
|
+
)
|
|
3528
3823
|
) && healthChecks.length === 0 && !hasV20HealthOrReadinessRoute && !hasV22PackageStartHealthOrReadinessRoute) {
|
|
3529
3824
|
findings.push({
|
|
3530
3825
|
severity: "info",
|
|
@@ -3630,22 +3925,41 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors,
|
|
|
3630
3925
|
const findings = [];
|
|
3631
3926
|
const manifests = repoFiles.filter(isDependencyManifest);
|
|
3632
3927
|
const lockfiles = repoFiles.filter(isLockfile);
|
|
3633
|
-
const manifest = pickPrimaryDependencyManifest(repoPath, manifests);
|
|
3634
3928
|
const lockfile = lockfiles[0];
|
|
3635
3929
|
const rootLockfile = mono ? mono.sharedFiles.find(isLockfile) : void 0;
|
|
3636
3930
|
const rootUpdateConfig = mono ? mono.sharedFiles.find(isDependencyUpdateConfig) : void 0;
|
|
3637
3931
|
const hasLockfile = Boolean(lockfile) || Boolean(rootLockfile);
|
|
3638
3932
|
const updateConfig = repoFiles.find(isDependencyUpdateConfig);
|
|
3639
3933
|
const hasUpdateConfig = Boolean(updateConfig) || Boolean(rootUpdateConfig);
|
|
3640
|
-
const auditConfig =
|
|
3641
|
-
|
|
3934
|
+
const auditConfig = withContentReadSignal(
|
|
3935
|
+
"A4",
|
|
3936
|
+
"dependency_automation_ratio",
|
|
3937
|
+
() => manifests.some((file) => packageJsonHasAuditScript(repoPath, file))
|
|
3938
|
+
);
|
|
3939
|
+
const { manifest, dependencySpecs } = withContentReadSignals(
|
|
3940
|
+
"A4",
|
|
3941
|
+
["pinned_dependency_ratio", "declared_version_range_ratio", "dependency_count_sanity"],
|
|
3942
|
+
() => ({
|
|
3943
|
+
// Cite the manifest that actually carries dependencies for evidence/findings, not
|
|
3944
|
+
// whichever sorts first (e.g. a near-empty requirements-windows.txt that precedes the
|
|
3945
|
+
// real requirements.txt), so the evidence pointer reflects the file the A4 score is
|
|
3946
|
+
// derived from. The score itself is unaffected: it aggregates specs across ALL manifests.
|
|
3947
|
+
manifest: pickPrimaryDependencyManifest(repoPath, manifests),
|
|
3948
|
+
dependencySpecs: readDependencySpecs(repoPath, manifests)
|
|
3949
|
+
})
|
|
3950
|
+
);
|
|
3642
3951
|
const pinnedDependencyCount = dependencySpecs.filter(
|
|
3643
3952
|
(dependency) => isPinnedDependencyVersion(dependency.version)
|
|
3644
3953
|
).length;
|
|
3645
|
-
if (manifest)
|
|
3646
|
-
|
|
3647
|
-
|
|
3954
|
+
if (manifest) {
|
|
3955
|
+
withContentReadSignals(
|
|
3956
|
+
"A4",
|
|
3957
|
+
["pinned_dependency_ratio", "declared_version_range_ratio", "dependency_count_sanity"],
|
|
3958
|
+
() => evidence.push(
|
|
3959
|
+
evidenceForRelative(repoPath, manifest, "dependency_report", "Dependency manifest")
|
|
3960
|
+
)
|
|
3648
3961
|
);
|
|
3962
|
+
}
|
|
3649
3963
|
if (lockfile)
|
|
3650
3964
|
evidence.push(
|
|
3651
3965
|
evidenceForRelative(repoPath, lockfile, "dependency_report", "Dependency lockfile")
|
|
@@ -3678,28 +3992,36 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors,
|
|
|
3678
3992
|
const deployableServiceFiles = useV47Detectors ? findDeployableServicePremiseFiles(repoPath, repoFiles, true) : [];
|
|
3679
3993
|
const isAppOrService = (useV47Detectors ? deployableServiceFiles.length > 0 : isDeployableService(repoPath, repoFiles, useV27Detectors)) || packagedApplicationFiles.length > 0;
|
|
3680
3994
|
if (isAppOrService && !hasLockfile && manifest) {
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3995
|
+
withContentReadSignals(
|
|
3996
|
+
"A4",
|
|
3997
|
+
["pinned_dependency_ratio", "declared_version_range_ratio", "dependency_count_sanity"],
|
|
3998
|
+
() => findings.push(
|
|
3999
|
+
inventoryAbsenceFinding({
|
|
4000
|
+
severity: "critical",
|
|
4001
|
+
summaries: {
|
|
4002
|
+
legacy: "Dependency manifest is present without a detected lockfile \u2014 non-reproducible installs.",
|
|
4003
|
+
currentTracked: "Dependency manifest is present, but the tracked scan-eligible inventory matched no recognized lockfile.",
|
|
4004
|
+
currentDirectoryWalk: "Dependency manifest is present, but the bounded directory walk matched no recognized lockfile."
|
|
4005
|
+
},
|
|
4006
|
+
evidence: evidenceForRelative(
|
|
4007
|
+
repoPath,
|
|
4008
|
+
manifest,
|
|
4009
|
+
"dependency_report",
|
|
4010
|
+
"Dependency manifest"
|
|
4011
|
+
),
|
|
4012
|
+
context: inventoryAbsenceContext,
|
|
4013
|
+
inventoryCount: repoFiles.length + (mono?.sharedFiles.length ?? 0),
|
|
4014
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a4Lockfile,
|
|
4015
|
+
matchCount: 0
|
|
4016
|
+
})
|
|
4017
|
+
)
|
|
3700
4018
|
);
|
|
3701
4019
|
}
|
|
3702
|
-
if (manifest &&
|
|
4020
|
+
if (manifest && withContentReadSignal(
|
|
4021
|
+
"A4",
|
|
4022
|
+
"suspicious_dependencies",
|
|
4023
|
+
() => hasSuspiciousDependencies(repoPath, manifest, useV47Detectors)
|
|
4024
|
+
)) {
|
|
3703
4025
|
findings.push({
|
|
3704
4026
|
severity: "critical",
|
|
3705
4027
|
summary: "Dependency manifest contains obviously suspicious placeholder dependency names.",
|
|
@@ -3853,16 +4175,24 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, inv
|
|
|
3853
4175
|
...allDocFiles,
|
|
3854
4176
|
...repoFiles.filter((file) => /(^|\/)(SECURITY|THREAT[_-]?MODEL)\.md$/i.test(file))
|
|
3855
4177
|
]);
|
|
3856
|
-
const negativeSpaceDoc =
|
|
3857
|
-
|
|
4178
|
+
const negativeSpaceDoc = withContentReadSignal(
|
|
4179
|
+
"A5",
|
|
4180
|
+
"negative_space_documentation",
|
|
4181
|
+
() => [...negativeSpaceCandidates].find(
|
|
4182
|
+
(file) => fileContains(repoPath, file, NEGATIVE_SPACE_SECTION_PATTERN) || fileContains(repoPath, file, NEGATIVE_SPACE_PHRASE_PATTERN)
|
|
4183
|
+
)
|
|
3858
4184
|
);
|
|
3859
4185
|
if (negativeSpaceDoc) {
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
4186
|
+
withContentReadSignal(
|
|
4187
|
+
"A5",
|
|
4188
|
+
"negative_space_documentation",
|
|
4189
|
+
() => evidence.push(
|
|
4190
|
+
evidenceForRelative(
|
|
4191
|
+
repoPath,
|
|
4192
|
+
negativeSpaceDoc,
|
|
4193
|
+
"claim_reconciliation",
|
|
4194
|
+
'Documented limitations / threat model / "not covered" section'
|
|
4195
|
+
)
|
|
3866
4196
|
)
|
|
3867
4197
|
);
|
|
3868
4198
|
}
|
|
@@ -3999,18 +4329,33 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
3999
4329
|
const workflows = repoFiles.filter(isCiWorkflow);
|
|
4000
4330
|
const workflow = workflows[0];
|
|
4001
4331
|
const rootWorkflows = mono ? mono.sharedFiles.filter(isCiWorkflow) : [];
|
|
4002
|
-
const defaultBranchCiCount =
|
|
4332
|
+
const defaultBranchCiCount = withContentReadSignal(
|
|
4333
|
+
"B3",
|
|
4334
|
+
"default_branch_ci_depth",
|
|
4335
|
+
() => workflows.filter((file) => workflowTargetsDefaultBranch(repoPath, file)).length + (mono ? rootWorkflows.filter((file) => workflowTargetsDefaultBranch(mono.root, file)).length : 0)
|
|
4336
|
+
);
|
|
4003
4337
|
if (packageJson && scripts.has("test")) {
|
|
4004
4338
|
evidence.push(evidenceForRelative(repoPath, packageJson, "test_run", "Test script"));
|
|
4005
4339
|
}
|
|
4006
4340
|
if (packageJson && scripts.has("lint")) {
|
|
4007
4341
|
evidence.push(evidenceForRelative(repoPath, packageJson, "ci_run", "Lint script"));
|
|
4008
4342
|
}
|
|
4009
|
-
if (workflow)
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4343
|
+
if (workflow) {
|
|
4344
|
+
withContentReadSignals(
|
|
4345
|
+
"B3",
|
|
4346
|
+
["ci_script_depth", "default_branch_ci_depth"],
|
|
4347
|
+
() => evidence.push(evidenceForRelative(repoPath, workflow, "ci_run", "CI workflow"))
|
|
4348
|
+
);
|
|
4349
|
+
} else if (rootWorkflows[0] && mono) {
|
|
4350
|
+
const rootWorkflow = rootWorkflows[0];
|
|
4351
|
+
withContentReadSignals(
|
|
4352
|
+
"B3",
|
|
4353
|
+
["ci_script_depth", "default_branch_ci_depth"],
|
|
4354
|
+
() => evidence.push(
|
|
4355
|
+
evidenceForRelative(mono.root, rootWorkflow, "ci_run", "CI workflow (monorepo root)")
|
|
4356
|
+
)
|
|
4013
4357
|
);
|
|
4358
|
+
}
|
|
4014
4359
|
if (evidence.length === 0) return null;
|
|
4015
4360
|
const CI_BUILD_COMMAND_PATTERN = /\b(cargo\s+build|cmake|ninja|make\s+(build|all))\b/i;
|
|
4016
4361
|
const CI_COMMAND_CATEGORIES = [
|
|
@@ -4020,10 +4365,19 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
4020
4365
|
CI_BUILD_COMMAND_PATTERN
|
|
4021
4366
|
];
|
|
4022
4367
|
const scriptDepth = ["test", "lint", "typecheck", "build"].filter((s) => scripts.has(s)).length;
|
|
4023
|
-
const
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4368
|
+
const { repoCategoryHits, rootCategoryHits } = withContentReadSignal(
|
|
4369
|
+
"B3",
|
|
4370
|
+
"ci_script_depth",
|
|
4371
|
+
() => ({
|
|
4372
|
+
repoCategoryHits: CI_COMMAND_CATEGORIES.map(
|
|
4373
|
+
(pattern) => workflows.some((file) => fileContains(repoPath, file, pattern))
|
|
4374
|
+
),
|
|
4375
|
+
rootCategoryHits: mono ? CI_COMMAND_CATEGORIES.map(
|
|
4376
|
+
(pattern) => rootWorkflows.some((file) => fileContains(mono.root, file, pattern))
|
|
4377
|
+
) : []
|
|
4378
|
+
})
|
|
4379
|
+
);
|
|
4380
|
+
const ciCommandDepth = repoCategoryHits.filter(Boolean).length + rootCategoryHits.filter(Boolean).length;
|
|
4027
4381
|
const combinedDepth = scriptDepth + ciCommandDepth;
|
|
4028
4382
|
return {
|
|
4029
4383
|
criterionId: "B3",
|
|
@@ -4045,13 +4399,13 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
4045
4399
|
label: `package script: ${name === "typecheck" ? "type-check" : name}`,
|
|
4046
4400
|
count: scripts.has(name) ? 1 : 0
|
|
4047
4401
|
})),
|
|
4048
|
-
...CI_COMMAND_CATEGORIES.map((
|
|
4402
|
+
...CI_COMMAND_CATEGORIES.map((_pattern, index) => ({
|
|
4049
4403
|
label: `repository CI command: ${["test", "lint", "type-check", "build"][index]}`,
|
|
4050
|
-
count:
|
|
4404
|
+
count: repoCategoryHits[index] ? 1 : 0
|
|
4051
4405
|
})),
|
|
4052
|
-
...mono ? CI_COMMAND_CATEGORIES.map((
|
|
4406
|
+
...mono ? CI_COMMAND_CATEGORIES.map((_pattern, index) => ({
|
|
4053
4407
|
label: `monorepo-root CI command: ${["test", "lint", "type-check", "build"][index]}`,
|
|
4054
|
-
count:
|
|
4408
|
+
count: rootCategoryHits[index] ? 1 : 0
|
|
4055
4409
|
})) : []
|
|
4056
4410
|
]
|
|
4057
4411
|
}
|
|
@@ -4096,9 +4450,11 @@ function readAuditFreshnessCommitterYear(repoPath, scanLimitations) {
|
|
|
4096
4450
|
function collectB4AuditEvidence(repoPath, repoFiles, generatedAt, useV19CommitYear = false, scanLimitations = /* @__PURE__ */ new Set()) {
|
|
4097
4451
|
const evidence = [];
|
|
4098
4452
|
const auditFiles = repoFiles.filter(isAuditFile);
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4453
|
+
withContentReadSignal("B4", "audit_freshness_depth", () => {
|
|
4454
|
+
for (const file of auditFiles.slice(0, 5)) {
|
|
4455
|
+
evidence.push(evidenceForRelative(repoPath, file, "audit_log", "Audit or changelog artifact"));
|
|
4456
|
+
}
|
|
4457
|
+
});
|
|
4102
4458
|
if (evidence.length === 0) {
|
|
4103
4459
|
return buildNotApplicableSignal(
|
|
4104
4460
|
"B4",
|
|
@@ -4143,7 +4499,11 @@ function collectB4AuditEvidence(repoPath, repoFiles, generatedAt, useV19CommitYe
|
|
|
4143
4499
|
metric(
|
|
4144
4500
|
"audit_freshness_depth",
|
|
4145
4501
|
"Audit freshness depth",
|
|
4146
|
-
|
|
4502
|
+
withContentReadSignal(
|
|
4503
|
+
"B4",
|
|
4504
|
+
"audit_freshness_depth",
|
|
4505
|
+
() => countFilesContaining(repoPath, auditFiles, freshnessPattern)
|
|
4506
|
+
),
|
|
4147
4507
|
Math.max(auditFiles.length, 1),
|
|
4148
4508
|
0.2,
|
|
4149
4509
|
"ratio",
|
|
@@ -4156,93 +4516,128 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
|
|
|
4156
4516
|
const evidence = [];
|
|
4157
4517
|
const findings = [];
|
|
4158
4518
|
const docFiles = repoFiles.filter((file) => /\.(md|mdx)$/i.test(file));
|
|
4159
|
-
const humanGateDoc =
|
|
4160
|
-
|
|
4519
|
+
const humanGateDoc = withContentReadSignal(
|
|
4520
|
+
"B6",
|
|
4521
|
+
"human_gate_documented",
|
|
4522
|
+
() => docFiles.find((file) => fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN))
|
|
4161
4523
|
);
|
|
4162
4524
|
const implFiles = repoFiles.filter(isImplementationFile);
|
|
4163
|
-
const gatedPrivilegeCheckFile =
|
|
4164
|
-
|
|
4525
|
+
const gatedPrivilegeCheckFile = withContentReadSignal(
|
|
4526
|
+
"B6",
|
|
4527
|
+
"fail_closed_privilege_check",
|
|
4528
|
+
() => implFiles.find(
|
|
4529
|
+
(file) => fileContains(repoPath, file, GATED_PRIVILEGE_CHECK_PATTERN) && fileContains(repoPath, file, SET_ROLE_PATTERN)
|
|
4530
|
+
)
|
|
4165
4531
|
);
|
|
4166
|
-
const killSwitchFile =
|
|
4167
|
-
|
|
4532
|
+
const killSwitchFile = withContentReadSignal(
|
|
4533
|
+
"B6",
|
|
4534
|
+
"kill_switch_fail_safe_present",
|
|
4535
|
+
() => implFiles.find(
|
|
4536
|
+
(file) => fileContains(repoPath, file, KILL_SWITCH_NAME_PATTERN) && fileContains(repoPath, file, KILL_SWITCH_FAIL_CLOSED_PATTERN)
|
|
4537
|
+
)
|
|
4168
4538
|
);
|
|
4169
4539
|
const codeownersFile = repoFiles.find((file) => /(^|\/)CODEOWNERS$/.test(file));
|
|
4170
|
-
const reviewGateDoc =
|
|
4171
|
-
|
|
4540
|
+
const reviewGateDoc = withContentReadSignal(
|
|
4541
|
+
"B6",
|
|
4542
|
+
"protected_path_review_gate",
|
|
4543
|
+
() => docFiles.find((file) => fileContains(repoPath, file, REQUIRED_REVIEW_PATTERN))
|
|
4172
4544
|
);
|
|
4173
4545
|
const protectedPathReviewGate = codeownersFile ?? reviewGateDoc;
|
|
4174
|
-
const executableFiles =
|
|
4175
|
-
(file) => fileContains(repoPath, file, SQL_EXEC_PATTERN)
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4546
|
+
const { executableFiles, v21ExecutedEscalationFileSet, productionUngatedEscalationFiles } = withContentReadSignal("B6", "privilege_escalation_cleanliness", () => {
|
|
4547
|
+
const executable = implFiles.filter((file) => fileContains(repoPath, file, SQL_EXEC_PATTERN));
|
|
4548
|
+
const historicalUngated = executable.filter((file) => {
|
|
4549
|
+
if (useV39Detectors && !fileHasExecutedPrivilegeEscalation(repoPath, file)) return false;
|
|
4550
|
+
const hasEscalation = fileContains(repoPath, file, ROLE_MEMBERSHIP_GRANT_PATTERN) || fileContains(repoPath, file, SUPERUSER_ESCALATION_PATTERN);
|
|
4551
|
+
if (!hasEscalation) return false;
|
|
4552
|
+
return !fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN);
|
|
4553
|
+
});
|
|
4554
|
+
const v21Executed = useV21ExecutedEscalations ? repoFiles.filter(
|
|
4555
|
+
(file) => isV39AuthoredProductionPath(file) && (fileHasV21RawSqlEscalation(repoPath, file) || isImplementationFile(file) && fileHasV21DriverEscalation(repoPath, file))
|
|
4556
|
+
) : [];
|
|
4557
|
+
const v21ExecutedSet = new Set(v21Executed);
|
|
4558
|
+
const ungated = [.../* @__PURE__ */ new Set([...historicalUngated, ...v21Executed])].filter(
|
|
4559
|
+
(file) => !fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN)
|
|
4560
|
+
);
|
|
4561
|
+
const productionUngated = ungated.filter(
|
|
4562
|
+
(file) => useV39Detectors ? isV39AuthoredProductionPath(file) : !isTestOrFixturePath(file)
|
|
4563
|
+
);
|
|
4564
|
+
return {
|
|
4565
|
+
executableFiles: executable,
|
|
4566
|
+
v21ExecutedEscalationFileSet: v21ExecutedSet,
|
|
4567
|
+
productionUngatedEscalationFiles: productionUngated
|
|
4568
|
+
};
|
|
4182
4569
|
});
|
|
4183
|
-
const v21ExecutedEscalationFiles = useV21ExecutedEscalations ? repoFiles.filter(
|
|
4184
|
-
(file) => isV39AuthoredProductionPath(file) && (fileHasV21RawSqlEscalation(repoPath, file) || isImplementationFile(file) && fileHasV21DriverEscalation(repoPath, file))
|
|
4185
|
-
) : [];
|
|
4186
|
-
const v21ExecutedEscalationFileSet = new Set(v21ExecutedEscalationFiles);
|
|
4187
|
-
const ungatedEscalationFiles = [
|
|
4188
|
-
.../* @__PURE__ */ new Set([...historicalUngatedEscalationFiles, ...v21ExecutedEscalationFiles])
|
|
4189
|
-
].filter((file) => !fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN));
|
|
4190
|
-
const productionUngatedEscalationFiles = ungatedEscalationFiles.filter(
|
|
4191
|
-
(file) => useV39Detectors ? isV39AuthoredProductionPath(file) : !isTestOrFixturePath(file)
|
|
4192
|
-
);
|
|
4193
4570
|
if (humanGateDoc) {
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4571
|
+
withContentReadSignal(
|
|
4572
|
+
"B6",
|
|
4573
|
+
"human_gate_documented",
|
|
4574
|
+
() => evidence.push(
|
|
4575
|
+
evidenceForRelative(
|
|
4576
|
+
repoPath,
|
|
4577
|
+
humanGateDoc,
|
|
4578
|
+
"artifact",
|
|
4579
|
+
"Documents privileged operations as human-executed/gated"
|
|
4580
|
+
)
|
|
4200
4581
|
)
|
|
4201
4582
|
);
|
|
4202
4583
|
}
|
|
4203
4584
|
if (gatedPrivilegeCheckFile) {
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4585
|
+
withContentReadSignal(
|
|
4586
|
+
"B6",
|
|
4587
|
+
"fail_closed_privilege_check",
|
|
4588
|
+
() => evidence.push(
|
|
4589
|
+
evidenceForRelative(
|
|
4590
|
+
repoPath,
|
|
4591
|
+
gatedPrivilegeCheckFile,
|
|
4592
|
+
"artifact",
|
|
4593
|
+
"Fail-closed privilege-membership check before role elevation"
|
|
4594
|
+
)
|
|
4210
4595
|
)
|
|
4211
4596
|
);
|
|
4212
4597
|
}
|
|
4213
4598
|
if (killSwitchFile) {
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4599
|
+
withContentReadSignal(
|
|
4600
|
+
"B6",
|
|
4601
|
+
"kill_switch_fail_safe_present",
|
|
4602
|
+
() => evidence.push(
|
|
4603
|
+
evidenceForRelative(
|
|
4604
|
+
repoPath,
|
|
4605
|
+
killSwitchFile,
|
|
4606
|
+
"artifact",
|
|
4607
|
+
"Un-overridable kill-switch / fail-safe governance toggle"
|
|
4608
|
+
)
|
|
4220
4609
|
)
|
|
4221
4610
|
);
|
|
4222
4611
|
}
|
|
4223
4612
|
if (protectedPathReviewGate) {
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4613
|
+
withContentReadSignal(
|
|
4614
|
+
"B6",
|
|
4615
|
+
"protected_path_review_gate",
|
|
4616
|
+
() => evidence.push(
|
|
4617
|
+
evidenceForRelative(
|
|
4618
|
+
repoPath,
|
|
4619
|
+
protectedPathReviewGate,
|
|
4620
|
+
"artifact",
|
|
4621
|
+
"CODEOWNERS/required-review gate on protected paths"
|
|
4622
|
+
)
|
|
4230
4623
|
)
|
|
4231
4624
|
);
|
|
4232
4625
|
}
|
|
4233
|
-
|
|
4234
|
-
const
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4626
|
+
withContentReadSignal("B6", "privilege_escalation_cleanliness", () => {
|
|
4627
|
+
for (const file of productionUngatedEscalationFiles.slice(0, 5)) {
|
|
4628
|
+
const isV21ExecutedShape = v21ExecutedEscalationFileSet.has(file);
|
|
4629
|
+
findings.push({
|
|
4630
|
+
severity: "critical",
|
|
4631
|
+
summary: isV21ExecutedShape ? "This file contains an authored administrative SQL statement \u2014 role-membership grant, SUPERUSER escalation, or schema-wide table privilege grant \u2014 or executes one through a direct database-driver call, and does not itself contain text matching the human-gate marker pattern." : "This file executes a role-membership GRANT or SUPERUSER escalation and does not itself contain text matching the human-gate marker pattern.",
|
|
4632
|
+
evidence: evidenceForRelative(
|
|
4633
|
+
repoPath,
|
|
4634
|
+
file,
|
|
4635
|
+
"artifact",
|
|
4636
|
+
isV21ExecutedShape ? "Ungated authored or directly executed administrative SQL statement" : "Ungated privilege-escalation statement"
|
|
4637
|
+
)
|
|
4638
|
+
});
|
|
4639
|
+
}
|
|
4640
|
+
});
|
|
4246
4641
|
if (evidence.length === 0 && findings.length === 0) {
|
|
4247
4642
|
return buildNotApplicableSignal(
|
|
4248
4643
|
"B6",
|
|
@@ -4264,7 +4659,7 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
|
|
|
4264
4659
|
}
|
|
4265
4660
|
)
|
|
4266
4661
|
] : [];
|
|
4267
|
-
const hasPrivilegedOpsSurface = humanGateDoc != null || gatedPrivilegeCheckFile != null || executableFiles.length > 0 ||
|
|
4662
|
+
const hasPrivilegedOpsSurface = humanGateDoc != null || gatedPrivilegeCheckFile != null || executableFiles.length > 0 || v21ExecutedEscalationFileSet.size > 0;
|
|
4268
4663
|
return {
|
|
4269
4664
|
criterionId: "B6",
|
|
4270
4665
|
positiveEvidence: evidence,
|
|
@@ -5187,6 +5582,9 @@ function isRecord(value) {
|
|
|
5187
5582
|
function isTestRunnerConfig(repoPath, file) {
|
|
5188
5583
|
return /(^|\/)(vitest|jest|pytest|playwright|cypress|mocha)\.config\.[cm]?[jt]s$/.test(file) || /(^|\/)(pytest\.ini|tox\.ini)$/.test(file) || /(^|\/)phpunit\.xml(\.dist)?$/.test(file) || /(^|\/)pyproject\.toml$/.test(file) && fileContains(repoPath, file, /^\[tool\.pytest(?:\.|\])/m) || /(^|\/)CMakeLists\.txt$/.test(file) && fileContains(repoPath, file, /\b(?:enable_testing|add_test|ctest_add_tests)\s*\(/i) || /(^|\/)Makefile$/.test(file) && fileContains(repoPath, file, /^\s*(?:test|tests|check)\s*:/m);
|
|
5189
5584
|
}
|
|
5585
|
+
function isRecipeTestEntry(repoPath, file) {
|
|
5586
|
+
return isRecipeFile(file) && fileContains(repoPath, file, /^\s*(?:test|tests|check)\s*:/m);
|
|
5587
|
+
}
|
|
5190
5588
|
function findCoverageConfigFiles(repoPath, repoFiles, authoredOnly = false) {
|
|
5191
5589
|
return repoFiles.filter(
|
|
5192
5590
|
(file) => (!authoredOnly || isAuthoredProductionPath(file)) && isCoverageConfig(repoPath, file)
|
|
@@ -5480,6 +5878,580 @@ function packageJsonHasCoverageTooling(repoPath, file) {
|
|
|
5480
5878
|
}
|
|
5481
5879
|
return false;
|
|
5482
5880
|
}
|
|
5881
|
+
function isV23CoverageConfig(repoPath, file) {
|
|
5882
|
+
if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
5883
|
+
const contents = stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"));
|
|
5884
|
+
const property = /(?:^|[{,])\s*["']?coverage["']?\s*:\s*([^,\r\n}]*)/im.exec(contents)?.[1]?.trim();
|
|
5885
|
+
if (!property || /^(?:false\b|null\b|undefined\b)/i.test(property)) return false;
|
|
5886
|
+
if (/^\{/.test(property) && /\benabled\s*:\s*false\b/i.test(contents)) return false;
|
|
5887
|
+
return true;
|
|
5888
|
+
}
|
|
5889
|
+
if (/(^|\/)jest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
5890
|
+
const contents = stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"));
|
|
5891
|
+
return /(?:^|[{,])\s*["']?collectCoverage["']?\s*:\s*true\b/m.test(contents) || /(?:^|[{,])\s*["']?(?:coverageThreshold|coverageProvider|coverageDirectory)["']?\s*:/m.test(
|
|
5892
|
+
contents
|
|
5893
|
+
);
|
|
5894
|
+
}
|
|
5895
|
+
if (/(^|\/)package\.json$/.test(file)) {
|
|
5896
|
+
return packageJsonHasStaticCoverageTooling(repoPath, file);
|
|
5897
|
+
}
|
|
5898
|
+
return isCoverageConfig(repoPath, file);
|
|
5899
|
+
}
|
|
5900
|
+
function coverageConfigurationDecision(repoPath, file) {
|
|
5901
|
+
const detectorState = isV23CoverageConfig(repoPath, file) ? "collects" : "none";
|
|
5902
|
+
if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
5903
|
+
const rawContents = readRepoText(join(repoPath, file), "utf8");
|
|
5904
|
+
const contents = stripJavaScriptComments(rawContents);
|
|
5905
|
+
const match = /(?:^|[{,])\s*["']?coverage["']?\s*:\s*([^,\r\n}]*)/im.exec(contents);
|
|
5906
|
+
if (!match) return null;
|
|
5907
|
+
return {
|
|
5908
|
+
path: file,
|
|
5909
|
+
source: "runner-configuration",
|
|
5910
|
+
locator: "coverage",
|
|
5911
|
+
evidence: rawContents.slice(match.index, match.index + match[0].length).trim(),
|
|
5912
|
+
detectorRecognized: true,
|
|
5913
|
+
detectorState
|
|
5914
|
+
};
|
|
5915
|
+
}
|
|
5916
|
+
if (/(^|\/)jest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
5917
|
+
const rawContents = readRepoText(join(repoPath, file), "utf8");
|
|
5918
|
+
const contents = stripJavaScriptComments(rawContents);
|
|
5919
|
+
const match = /(?:^|[{,])\s*["']?(collectCoverage|coverageThreshold|coverageProvider|coverageDirectory)["']?\s*:\s*([^,\r\n}]*)/m.exec(
|
|
5920
|
+
contents
|
|
5921
|
+
);
|
|
5922
|
+
if (!match) return null;
|
|
5923
|
+
return {
|
|
5924
|
+
path: file,
|
|
5925
|
+
source: "runner-configuration",
|
|
5926
|
+
locator: match[1] ?? "coverage",
|
|
5927
|
+
evidence: rawContents.slice(match.index, match.index + match[0].length).trim(),
|
|
5928
|
+
detectorRecognized: true,
|
|
5929
|
+
detectorState
|
|
5930
|
+
};
|
|
5931
|
+
}
|
|
5932
|
+
if (detectorState === "none") return null;
|
|
5933
|
+
return {
|
|
5934
|
+
path: file,
|
|
5935
|
+
source: "runner-configuration",
|
|
5936
|
+
locator: "coverage",
|
|
5937
|
+
evidence: file,
|
|
5938
|
+
detectorRecognized: true,
|
|
5939
|
+
detectorState
|
|
5940
|
+
};
|
|
5941
|
+
}
|
|
5942
|
+
function packageJsonHasStaticCoverageTooling(repoPath, file) {
|
|
5943
|
+
const fullPath = join(repoPath, file);
|
|
5944
|
+
if (!isRegularFile(fullPath)) return false;
|
|
5945
|
+
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
5946
|
+
if (!parsed) return false;
|
|
5947
|
+
return ["nyc", "c8", "istanbul"].some((key) => {
|
|
5948
|
+
const config = parsed[key];
|
|
5949
|
+
return typeof config === "object" && config !== null && !Array.isArray(config);
|
|
5950
|
+
});
|
|
5951
|
+
}
|
|
5952
|
+
function analyzeCoverageConfiguration(repoPath, repoFiles, authoredOnly = false) {
|
|
5953
|
+
const eligibleFiles = repoFiles.filter(
|
|
5954
|
+
(file) => !authoredOnly || isAuthoredProductionPath(file)
|
|
5955
|
+
);
|
|
5956
|
+
const eligibleSet = new Set(eligibleFiles);
|
|
5957
|
+
const coverageFiles = /* @__PURE__ */ new Set();
|
|
5958
|
+
const unresolvedFiles = /* @__PURE__ */ new Set();
|
|
5959
|
+
const ciCommandsByFile = /* @__PURE__ */ new Map();
|
|
5960
|
+
const detectorDecisions = [];
|
|
5961
|
+
let hasCoverageCommand = false;
|
|
5962
|
+
for (const file of eligibleFiles.filter(isCiWorkflow)) {
|
|
5963
|
+
const commands = extractCiRunCommands(readRepoText(join(repoPath, file), "utf8"));
|
|
5964
|
+
ciCommandsByFile.set(file, commands);
|
|
5965
|
+
const commandDecisions = commands.map(
|
|
5966
|
+
(command, index) => coverageDetectorDecision(repoPath, eligibleSet, {
|
|
5967
|
+
path: file,
|
|
5968
|
+
source: "ci-run",
|
|
5969
|
+
locator: String(index),
|
|
5970
|
+
command
|
|
5971
|
+
})
|
|
5972
|
+
);
|
|
5973
|
+
detectorDecisions.push(...commandDecisions);
|
|
5974
|
+
const state = combineCoverageCommandStates(
|
|
5975
|
+
commandDecisions.map((decision) => decision.detectorState)
|
|
5976
|
+
);
|
|
5977
|
+
if (state === "collects") {
|
|
5978
|
+
coverageFiles.add(file);
|
|
5979
|
+
hasCoverageCommand = true;
|
|
5980
|
+
}
|
|
5981
|
+
if (state === "unresolved") unresolvedFiles.add(file);
|
|
5982
|
+
}
|
|
5983
|
+
const ciCommands = [...ciCommandsByFile.values()].flat();
|
|
5984
|
+
for (const file of eligibleFiles) {
|
|
5985
|
+
if (isCiWorkflow(file) || isRecipeFile(file)) continue;
|
|
5986
|
+
if (/(^|\/)package\.json$/.test(file)) {
|
|
5987
|
+
if (packageJsonHasStaticCoverageTooling(repoPath, file)) coverageFiles.add(file);
|
|
5988
|
+
const packageState = packageScriptCoverageState(
|
|
5989
|
+
repoPath,
|
|
5990
|
+
eligibleSet,
|
|
5991
|
+
file,
|
|
5992
|
+
file === "package.json" ? ciCommands : [],
|
|
5993
|
+
detectorDecisions
|
|
5994
|
+
);
|
|
5995
|
+
if (packageState === "collects") {
|
|
5996
|
+
coverageFiles.add(file);
|
|
5997
|
+
hasCoverageCommand = true;
|
|
5998
|
+
}
|
|
5999
|
+
if (packageState === "unresolved") unresolvedFiles.add(file);
|
|
6000
|
+
continue;
|
|
6001
|
+
}
|
|
6002
|
+
const configurationDecision = coverageConfigurationDecision(repoPath, file);
|
|
6003
|
+
if (configurationDecision) detectorDecisions.push(configurationDecision);
|
|
6004
|
+
if (configurationDecision?.detectorState === "collects") coverageFiles.add(file);
|
|
6005
|
+
}
|
|
6006
|
+
for (const file of eligibleFiles.filter(isRecipeFile)) {
|
|
6007
|
+
const state = recipeCoverageState(
|
|
6008
|
+
repoPath,
|
|
6009
|
+
eligibleSet,
|
|
6010
|
+
file,
|
|
6011
|
+
ciCommands,
|
|
6012
|
+
detectorDecisions
|
|
6013
|
+
);
|
|
6014
|
+
if (state === "collects") {
|
|
6015
|
+
coverageFiles.add(file);
|
|
6016
|
+
hasCoverageCommand = true;
|
|
6017
|
+
}
|
|
6018
|
+
if (state === "unresolved") unresolvedFiles.add(file);
|
|
6019
|
+
}
|
|
6020
|
+
for (const file of coverageFiles) unresolvedFiles.delete(file);
|
|
6021
|
+
return {
|
|
6022
|
+
coverageFiles: [...coverageFiles].sort(),
|
|
6023
|
+
unresolvedFiles: [...unresolvedFiles].sort(),
|
|
6024
|
+
hasCoverageCommand,
|
|
6025
|
+
detectorDecisions
|
|
6026
|
+
};
|
|
6027
|
+
}
|
|
6028
|
+
function stripJavaScriptComments(contents) {
|
|
6029
|
+
let output = "";
|
|
6030
|
+
let quote = null;
|
|
6031
|
+
let escaped = false;
|
|
6032
|
+
let lineComment = false;
|
|
6033
|
+
let blockComment = false;
|
|
6034
|
+
for (let index = 0; index < contents.length; index += 1) {
|
|
6035
|
+
const character = contents[index] ?? "";
|
|
6036
|
+
const next = contents[index + 1] ?? "";
|
|
6037
|
+
if (lineComment) {
|
|
6038
|
+
if (character === "\n") {
|
|
6039
|
+
lineComment = false;
|
|
6040
|
+
output += "\n";
|
|
6041
|
+
} else {
|
|
6042
|
+
output += " ";
|
|
6043
|
+
}
|
|
6044
|
+
continue;
|
|
6045
|
+
}
|
|
6046
|
+
if (blockComment) {
|
|
6047
|
+
if (character === "*" && next === "/") {
|
|
6048
|
+
output += " ";
|
|
6049
|
+
index += 1;
|
|
6050
|
+
blockComment = false;
|
|
6051
|
+
} else {
|
|
6052
|
+
output += character === "\n" ? "\n" : " ";
|
|
6053
|
+
}
|
|
6054
|
+
continue;
|
|
6055
|
+
}
|
|
6056
|
+
if (escaped) {
|
|
6057
|
+
output += character;
|
|
6058
|
+
escaped = false;
|
|
6059
|
+
continue;
|
|
6060
|
+
}
|
|
6061
|
+
if (quote) {
|
|
6062
|
+
output += character;
|
|
6063
|
+
if (character === "\\") escaped = true;
|
|
6064
|
+
else if (character === quote) quote = null;
|
|
6065
|
+
continue;
|
|
6066
|
+
}
|
|
6067
|
+
if (character === "'" || character === '"' || character === "`") {
|
|
6068
|
+
quote = character;
|
|
6069
|
+
output += character;
|
|
6070
|
+
continue;
|
|
6071
|
+
}
|
|
6072
|
+
if (character === "/" && next === "/") {
|
|
6073
|
+
output += " ";
|
|
6074
|
+
index += 1;
|
|
6075
|
+
lineComment = true;
|
|
6076
|
+
continue;
|
|
6077
|
+
}
|
|
6078
|
+
if (character === "/" && next === "*") {
|
|
6079
|
+
output += " ";
|
|
6080
|
+
index += 1;
|
|
6081
|
+
blockComment = true;
|
|
6082
|
+
continue;
|
|
6083
|
+
}
|
|
6084
|
+
output += character;
|
|
6085
|
+
}
|
|
6086
|
+
return output;
|
|
6087
|
+
}
|
|
6088
|
+
function packageScriptCoverageState(repoPath, repoFiles, packageJson, ciCommands, detectorDecisions) {
|
|
6089
|
+
const scripts = readPackageScripts(join(repoPath, packageJson));
|
|
6090
|
+
if (scripts.size === 0) return "none";
|
|
6091
|
+
const roots = /* @__PURE__ */ new Set();
|
|
6092
|
+
let unresolved = false;
|
|
6093
|
+
if (scripts.has("test")) roots.add("test");
|
|
6094
|
+
for (const command of ciCommands) {
|
|
6095
|
+
const references = packageScriptReferences(command, scripts);
|
|
6096
|
+
for (const name of references.names) roots.add(name);
|
|
6097
|
+
if (references.dynamic && [...scripts.values()].some(
|
|
6098
|
+
(candidate) => commandInvokesCoverageTool(candidate) || containsPotentialCoverageFlag(candidate)
|
|
6099
|
+
)) {
|
|
6100
|
+
unresolved = true;
|
|
6101
|
+
}
|
|
6102
|
+
}
|
|
6103
|
+
const queue = [...roots];
|
|
6104
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6105
|
+
while (queue.length > 0) {
|
|
6106
|
+
const name = queue.shift();
|
|
6107
|
+
if (!name || visited.has(name)) continue;
|
|
6108
|
+
const command = scripts.get(name);
|
|
6109
|
+
if (command === void 0) continue;
|
|
6110
|
+
visited.add(name);
|
|
6111
|
+
const decision = coverageDetectorDecision(repoPath, repoFiles, {
|
|
6112
|
+
path: packageJson,
|
|
6113
|
+
source: "package-script",
|
|
6114
|
+
locator: name,
|
|
6115
|
+
command
|
|
6116
|
+
});
|
|
6117
|
+
detectorDecisions.push(decision);
|
|
6118
|
+
const state = decision.detectorState;
|
|
6119
|
+
if (state === "collects") return "collects";
|
|
6120
|
+
if (state === "unresolved") unresolved = true;
|
|
6121
|
+
const references = packageScriptReferences(command, scripts);
|
|
6122
|
+
for (const referenced of references.names) {
|
|
6123
|
+
if (!visited.has(referenced)) queue.push(referenced);
|
|
6124
|
+
}
|
|
6125
|
+
for (const lifecycle of [`pre${name}`, `post${name}`]) {
|
|
6126
|
+
if (scripts.has(lifecycle) && !visited.has(lifecycle)) queue.push(lifecycle);
|
|
6127
|
+
}
|
|
6128
|
+
if (references.dynamic && [...scripts.values()].some(
|
|
6129
|
+
(candidate) => commandInvokesCoverageTool(candidate) || containsPotentialCoverageFlag(candidate)
|
|
6130
|
+
)) {
|
|
6131
|
+
unresolved = true;
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
6134
|
+
return unresolved ? "unresolved" : "none";
|
|
6135
|
+
}
|
|
6136
|
+
function packageScriptReferences(command, scripts) {
|
|
6137
|
+
const names = /* @__PURE__ */ new Set();
|
|
6138
|
+
let dynamic = false;
|
|
6139
|
+
for (const segment of splitShellCommandSegments(command)) {
|
|
6140
|
+
for (const match of segment.matchAll(
|
|
6141
|
+
/\bpnpm\s+run\s+\/((?:\\.|[^/])+)\/([dgimsuvy]*)/giu
|
|
6142
|
+
)) {
|
|
6143
|
+
const source = match[1];
|
|
6144
|
+
if (!source) continue;
|
|
6145
|
+
try {
|
|
6146
|
+
const flags = (match[2] ?? "").replace(/[dgy]/g, "");
|
|
6147
|
+
const selector = new RegExp(source, flags);
|
|
6148
|
+
for (const name of scripts.keys()) {
|
|
6149
|
+
if (selector.test(name)) names.add(name);
|
|
6150
|
+
selector.lastIndex = 0;
|
|
6151
|
+
}
|
|
6152
|
+
} catch {
|
|
6153
|
+
dynamic = true;
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
for (const match of segment.matchAll(
|
|
6157
|
+
/\b(?:npm|pnpm|yarn)\s+(?:run\s+)?([A-Za-z0-9][A-Za-z0-9:._-]*)/giu
|
|
6158
|
+
)) {
|
|
6159
|
+
const name = match[1];
|
|
6160
|
+
if (name && scripts.has(name)) names.add(name);
|
|
6161
|
+
}
|
|
6162
|
+
if (/\b(?:npm|pnpm|yarn)\s+run\s+(?:\$|["']\$|`)/u.test(segment)) dynamic = true;
|
|
6163
|
+
}
|
|
6164
|
+
return { names: [...names], dynamic };
|
|
6165
|
+
}
|
|
6166
|
+
function extractCiRunCommands(contents) {
|
|
6167
|
+
const lines = contents.split(/\r?\n/);
|
|
6168
|
+
const commands = [];
|
|
6169
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
6170
|
+
const line = lines[index] ?? "";
|
|
6171
|
+
const match = /^(\s*)(?:-\s*)?run\s*:\s*(.*)$/u.exec(line);
|
|
6172
|
+
if (!match) continue;
|
|
6173
|
+
const indentation = match[1]?.length ?? 0;
|
|
6174
|
+
const value = match[2]?.trim() ?? "";
|
|
6175
|
+
if (value === "|" || value === ">" || value.startsWith("|-") || value.startsWith(">-")) {
|
|
6176
|
+
const block = [];
|
|
6177
|
+
for (index += 1; index < lines.length; index += 1) {
|
|
6178
|
+
const nested = lines[index] ?? "";
|
|
6179
|
+
if (nested.trim().length === 0) {
|
|
6180
|
+
block.push("");
|
|
6181
|
+
continue;
|
|
6182
|
+
}
|
|
6183
|
+
const nestedIndentation = /^\s*/u.exec(nested)?.[0].length ?? 0;
|
|
6184
|
+
if (nestedIndentation <= indentation) {
|
|
6185
|
+
index -= 1;
|
|
6186
|
+
break;
|
|
6187
|
+
}
|
|
6188
|
+
block.push(nested.slice(Math.min(nested.length, indentation + 2)));
|
|
6189
|
+
}
|
|
6190
|
+
commands.push(block.join(value.startsWith(">") ? " " : "\n"));
|
|
6191
|
+
continue;
|
|
6192
|
+
}
|
|
6193
|
+
if (value.length === 0) continue;
|
|
6194
|
+
const quoted = /^(?:"([\s\S]*)"|'([\s\S]*)')$/u.exec(value);
|
|
6195
|
+
commands.push(quoted ? quoted[1] ?? quoted[2] ?? "" : value);
|
|
6196
|
+
}
|
|
6197
|
+
return commands;
|
|
6198
|
+
}
|
|
6199
|
+
function isRecipeFile(file) {
|
|
6200
|
+
return /(^|\/)(?:Makefile|makefile|GNUmakefile|[Jj]ustfile)$/.test(file);
|
|
6201
|
+
}
|
|
6202
|
+
function recipeCoverageState(repoPath, repoFiles, file, ciCommands, detectorDecisions) {
|
|
6203
|
+
const targets = readRecipeTargets(readRepoText(join(repoPath, file), "utf8"));
|
|
6204
|
+
const roots = new Set(["test", "tests", "check"].filter((target) => targets.has(target)));
|
|
6205
|
+
const runner = /[Jj]ustfile$/.test(file) ? "just" : "make";
|
|
6206
|
+
for (const command of ciCommands) {
|
|
6207
|
+
for (const match of command.matchAll(new RegExp(`\\b${runner}\\s+([A-Za-z0-9._-]+)`, "gu"))) {
|
|
6208
|
+
const target = match[1];
|
|
6209
|
+
if (target && targets.has(target)) roots.add(target);
|
|
6210
|
+
}
|
|
6211
|
+
}
|
|
6212
|
+
const queue = [...roots];
|
|
6213
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6214
|
+
let unresolved = false;
|
|
6215
|
+
while (queue.length > 0) {
|
|
6216
|
+
const name = queue.shift();
|
|
6217
|
+
if (!name || visited.has(name)) continue;
|
|
6218
|
+
const target = targets.get(name);
|
|
6219
|
+
if (!target) continue;
|
|
6220
|
+
visited.add(name);
|
|
6221
|
+
const commandDecisions = target.commands.map(
|
|
6222
|
+
(command, index) => coverageDetectorDecision(repoPath, repoFiles, {
|
|
6223
|
+
path: file,
|
|
6224
|
+
source: "recipe-command",
|
|
6225
|
+
locator: `${name}:${index}`,
|
|
6226
|
+
command
|
|
6227
|
+
})
|
|
6228
|
+
);
|
|
6229
|
+
detectorDecisions.push(...commandDecisions);
|
|
6230
|
+
const state = combineCoverageCommandStates(
|
|
6231
|
+
commandDecisions.map((decision) => decision.detectorState)
|
|
6232
|
+
);
|
|
6233
|
+
if (state === "collects") return "collects";
|
|
6234
|
+
if (state === "unresolved") unresolved = true;
|
|
6235
|
+
for (const dependency of target.dependencies) {
|
|
6236
|
+
if (targets.has(dependency) && !visited.has(dependency)) queue.push(dependency);
|
|
6237
|
+
}
|
|
6238
|
+
for (const command of target.commands) {
|
|
6239
|
+
const pattern = new RegExp(`(?:\\$\\(MAKE\\)|\\b${runner})\\s+([A-Za-z0-9._-]+)`, "gu");
|
|
6240
|
+
for (const match of command.matchAll(pattern)) {
|
|
6241
|
+
const referenced = match[1];
|
|
6242
|
+
if (referenced && targets.has(referenced) && !visited.has(referenced)) queue.push(referenced);
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
return unresolved ? "unresolved" : "none";
|
|
6247
|
+
}
|
|
6248
|
+
function readRecipeTargets(contents) {
|
|
6249
|
+
const targets = /* @__PURE__ */ new Map();
|
|
6250
|
+
let active = null;
|
|
6251
|
+
for (const line of contents.split(/\r?\n/)) {
|
|
6252
|
+
const header = /^([A-Za-z0-9][A-Za-z0-9._-]*)\s*:\s*([^#]*)/u.exec(line);
|
|
6253
|
+
if (header) {
|
|
6254
|
+
const name = header[1];
|
|
6255
|
+
if (!name) continue;
|
|
6256
|
+
active = {
|
|
6257
|
+
dependencies: (header[2] ?? "").trim().split(/\s+/u).filter(Boolean),
|
|
6258
|
+
commands: []
|
|
6259
|
+
};
|
|
6260
|
+
targets.set(name, active);
|
|
6261
|
+
continue;
|
|
6262
|
+
}
|
|
6263
|
+
if (active && /^\s+/u.test(line) && line.trim().length > 0) {
|
|
6264
|
+
active.commands.push(line.trim().replace(/^[@+-]+/u, ""));
|
|
6265
|
+
continue;
|
|
6266
|
+
}
|
|
6267
|
+
if (line.trim().length > 0 && !line.trimStart().startsWith("#")) active = null;
|
|
6268
|
+
}
|
|
6269
|
+
return targets;
|
|
6270
|
+
}
|
|
6271
|
+
function coverageCommandEvaluation(repoPath, repoFiles, command, depth = 0) {
|
|
6272
|
+
if (depth > 4) {
|
|
6273
|
+
const recognized = containsPotentialCoverageFlag(command);
|
|
6274
|
+
return { recognized, state: recognized ? "unresolved" : "none" };
|
|
6275
|
+
}
|
|
6276
|
+
const normalized = normalizeCoverageCommandPrefixes(command);
|
|
6277
|
+
const segments = normalized.splitShellOperators ? splitShellCommandSegments(normalized.segment) : [normalized.segment];
|
|
6278
|
+
const evaluations = segments.map((rawSegment) => {
|
|
6279
|
+
const candidate = normalizeCoverageCommandPrefixes(rawSegment);
|
|
6280
|
+
if (commandInvokesCoverageTool(candidate.segment)) {
|
|
6281
|
+
return { recognized: true, state: "collects" };
|
|
6282
|
+
}
|
|
6283
|
+
const words = shellWords(candidate.segment);
|
|
6284
|
+
if (words.length === 0) return { recognized: false, state: "none" };
|
|
6285
|
+
const direct = coverageFlagCommandState(words);
|
|
6286
|
+
if (direct !== "none") return { recognized: true, state: direct };
|
|
6287
|
+
const hasCoverageSyntax = containsPotentialCoverageFlag(candidate.segment);
|
|
6288
|
+
if (isRecognizedCoverageFlagRunner(words)) {
|
|
6289
|
+
return { recognized: hasCoverageSyntax, state: "none" };
|
|
6290
|
+
}
|
|
6291
|
+
const wrapper = reachableShellWrapper(words, repoFiles);
|
|
6292
|
+
if (wrapper) {
|
|
6293
|
+
return coverageCommandEvaluation(
|
|
6294
|
+
repoPath,
|
|
6295
|
+
repoFiles,
|
|
6296
|
+
readRepoText(join(repoPath, wrapper), "utf8"),
|
|
6297
|
+
depth + 1
|
|
6298
|
+
);
|
|
6299
|
+
}
|
|
6300
|
+
const executable = commandExecutable(words);
|
|
6301
|
+
const potentialFlagState = flagState(
|
|
6302
|
+
words.slice(1),
|
|
6303
|
+
["--experimental-test-coverage", "--collectcoverage", "--collect-coverage", "--coverage", "--cov"],
|
|
6304
|
+
[
|
|
6305
|
+
"--no-experimental-test-coverage",
|
|
6306
|
+
"--no-collectcoverage",
|
|
6307
|
+
"--no-collect-coverage",
|
|
6308
|
+
"--no-coverage",
|
|
6309
|
+
"--no-cov"
|
|
6310
|
+
]
|
|
6311
|
+
);
|
|
6312
|
+
if (hasCoverageSyntax && potentialFlagState !== "none" && executable && /(?:test|spec|runner)/i.test(executable) && !/(?:docs?|map|report|upload|codecov|coveralls)/i.test(executable)) {
|
|
6313
|
+
return { recognized: true, state: "unresolved" };
|
|
6314
|
+
}
|
|
6315
|
+
return { recognized: false, state: "none" };
|
|
6316
|
+
});
|
|
6317
|
+
return {
|
|
6318
|
+
recognized: evaluations.some((evaluation) => evaluation.recognized),
|
|
6319
|
+
state: combineCoverageCommandStates(
|
|
6320
|
+
evaluations.map((evaluation) => evaluation.state)
|
|
6321
|
+
)
|
|
6322
|
+
};
|
|
6323
|
+
}
|
|
6324
|
+
function coverageDetectorDecision(repoPath, repoFiles, input) {
|
|
6325
|
+
const evaluation = coverageCommandEvaluation(repoPath, repoFiles, input.command);
|
|
6326
|
+
return {
|
|
6327
|
+
path: input.path,
|
|
6328
|
+
source: input.source,
|
|
6329
|
+
locator: input.locator,
|
|
6330
|
+
evidence: input.command,
|
|
6331
|
+
detectorRecognized: evaluation.recognized,
|
|
6332
|
+
detectorState: evaluation.state
|
|
6333
|
+
};
|
|
6334
|
+
}
|
|
6335
|
+
function combineCoverageCommandStates(states) {
|
|
6336
|
+
if (states.includes("collects")) return "collects";
|
|
6337
|
+
return states.includes("unresolved") ? "unresolved" : "none";
|
|
6338
|
+
}
|
|
6339
|
+
function shellWords(segment) {
|
|
6340
|
+
const words = [];
|
|
6341
|
+
let offset = 0;
|
|
6342
|
+
while (offset < segment.length) {
|
|
6343
|
+
const scanned = scanShellWord(segment, offset);
|
|
6344
|
+
if (!scanned) break;
|
|
6345
|
+
words.push(scanned.word);
|
|
6346
|
+
if (scanned.end <= offset) break;
|
|
6347
|
+
offset = scanned.end;
|
|
6348
|
+
}
|
|
6349
|
+
return words;
|
|
6350
|
+
}
|
|
6351
|
+
function commandExecutable(words) {
|
|
6352
|
+
const normalized = unwrapPackageManagerExecutable(words);
|
|
6353
|
+
const executable = normalized[0];
|
|
6354
|
+
return executable ? basename(executable).replace(/\.cmd$/i, "").toLowerCase() : void 0;
|
|
6355
|
+
}
|
|
6356
|
+
function unwrapPackageManagerExecutable(words) {
|
|
6357
|
+
if (words.length === 0) return [];
|
|
6358
|
+
const first = basename(words[0] ?? "").toLowerCase();
|
|
6359
|
+
if (first === "pnpm" || first === "yarn") {
|
|
6360
|
+
const second = words[1]?.toLowerCase();
|
|
6361
|
+
return words.slice(second === "exec" || second === "dlx" ? 2 : 1);
|
|
6362
|
+
}
|
|
6363
|
+
if (first === "npx") return words.slice(1).filter((word) => !/^--(?:yes|no-install)$/u.test(word));
|
|
6364
|
+
if (first === "npm" && words[1]?.toLowerCase() === "exec") {
|
|
6365
|
+
let offset = 2;
|
|
6366
|
+
while (/^--(?:yes|no-install)$/u.test(words[offset] ?? "")) offset += 1;
|
|
6367
|
+
if (words[offset] === "--") offset += 1;
|
|
6368
|
+
return words.slice(offset);
|
|
6369
|
+
}
|
|
6370
|
+
return [...words];
|
|
6371
|
+
}
|
|
6372
|
+
function coverageFlagCommandState(words) {
|
|
6373
|
+
const command = unwrapPackageManagerExecutable(words);
|
|
6374
|
+
const executable = basename(command[0] ?? "").replace(/\.cmd$/i, "").toLowerCase();
|
|
6375
|
+
const args = command.slice(1);
|
|
6376
|
+
if (!executable) return "none";
|
|
6377
|
+
if (executable === "node") {
|
|
6378
|
+
if (!args.includes("--test")) return "none";
|
|
6379
|
+
return flagState(args, ["--experimental-test-coverage"], ["--no-experimental-test-coverage"]);
|
|
6380
|
+
}
|
|
6381
|
+
if (executable === "pytest" || executable === "py.test") {
|
|
6382
|
+
return flagState(args, ["--cov"], ["--no-cov"]);
|
|
6383
|
+
}
|
|
6384
|
+
if (executable === "bun" || executable === "deno") {
|
|
6385
|
+
if (args[0]?.toLowerCase() !== "test") return "none";
|
|
6386
|
+
return flagState(args.slice(1), ["--coverage"], ["--no-coverage"]);
|
|
6387
|
+
}
|
|
6388
|
+
if (executable === "jest") {
|
|
6389
|
+
return flagState(
|
|
6390
|
+
args,
|
|
6391
|
+
["--coverage", "--collectcoverage", "--collect-coverage"],
|
|
6392
|
+
["--no-coverage", "--no-collectcoverage", "--no-collect-coverage"]
|
|
6393
|
+
);
|
|
6394
|
+
}
|
|
6395
|
+
if (executable === "vitest" || executable === "bnt") {
|
|
6396
|
+
return flagState(args, ["--coverage"], ["--no-coverage"]);
|
|
6397
|
+
}
|
|
6398
|
+
return "none";
|
|
6399
|
+
}
|
|
6400
|
+
function isRecognizedCoverageFlagRunner(words) {
|
|
6401
|
+
const command = unwrapPackageManagerExecutable(words);
|
|
6402
|
+
const executable = basename(command[0] ?? "").replace(/\.cmd$/i, "").toLowerCase();
|
|
6403
|
+
if (["bnt", "vitest", "jest", "pytest", "py.test"].includes(executable)) return true;
|
|
6404
|
+
if (executable === "node") return command.slice(1).includes("--test");
|
|
6405
|
+
return (executable === "bun" || executable === "deno") && command[1]?.toLowerCase() === "test";
|
|
6406
|
+
}
|
|
6407
|
+
function flagState(args, enablingNames, disablingNames) {
|
|
6408
|
+
let enabled = false;
|
|
6409
|
+
let disabled = false;
|
|
6410
|
+
let unresolved = false;
|
|
6411
|
+
const enabling = new Set(enablingNames.map((name) => name.toLowerCase()));
|
|
6412
|
+
const disabling = new Set(disablingNames.map((name) => name.toLowerCase()));
|
|
6413
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
6414
|
+
const raw = args[index] ?? "";
|
|
6415
|
+
const lower = raw.toLowerCase();
|
|
6416
|
+
if (disabling.has(lower)) {
|
|
6417
|
+
disabled = true;
|
|
6418
|
+
continue;
|
|
6419
|
+
}
|
|
6420
|
+
const [name, assigned] = lower.split("=", 2);
|
|
6421
|
+
if (!name || !enabling.has(name)) continue;
|
|
6422
|
+
if (assigned !== void 0) {
|
|
6423
|
+
if (assigned === "false" || assigned === "0" || assigned === "off") disabled = true;
|
|
6424
|
+
else if (/\$|\{|\}/u.test(assigned)) unresolved = true;
|
|
6425
|
+
else enabled = true;
|
|
6426
|
+
continue;
|
|
6427
|
+
}
|
|
6428
|
+
const next = args[index + 1]?.toLowerCase();
|
|
6429
|
+
if (next === "false" || next === "0" || next === "off") {
|
|
6430
|
+
disabled = true;
|
|
6431
|
+
index += 1;
|
|
6432
|
+
} else if (next && /\$|\{|\}/u.test(next)) {
|
|
6433
|
+
unresolved = true;
|
|
6434
|
+
index += 1;
|
|
6435
|
+
} else {
|
|
6436
|
+
enabled = true;
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
if (unresolved || enabled && disabled) return "unresolved";
|
|
6440
|
+
return enabled && !disabled ? "collects" : "none";
|
|
6441
|
+
}
|
|
6442
|
+
function containsPotentialCoverageFlag(command) {
|
|
6443
|
+
return /(?:^|\s)--(?:experimental-test-coverage|collect-?coverage|no-coverage|coverage|no-cov|cov)(?:[=\s]|$)/iu.test(
|
|
6444
|
+
command
|
|
6445
|
+
);
|
|
6446
|
+
}
|
|
6447
|
+
function reachableShellWrapper(words, repoFiles) {
|
|
6448
|
+
const executable = basename(words[0] ?? "").toLowerCase();
|
|
6449
|
+
const candidate = /^(?:ba|da|k|z)?sh$/u.test(executable) ? words[1] : words[0];
|
|
6450
|
+
if (!candidate || !/(?:^|\/)\.?[^\s]*\.sh$/i.test(candidate)) return void 0;
|
|
6451
|
+
const normalized = candidate.replace(/^\.\//u, "");
|
|
6452
|
+
if (normalized.startsWith("/") || normalized.split("/").includes("..")) return void 0;
|
|
6453
|
+
return repoFiles.has(normalized) ? normalized : void 0;
|
|
6454
|
+
}
|
|
5483
6455
|
function commandInvokesCoverageTool(command) {
|
|
5484
6456
|
return normalizedCommandInvokesCoverageTool(normalizeCoverageCommandPrefixes(command), 0);
|
|
5485
6457
|
}
|
|
@@ -7325,7 +8297,7 @@ var WITAN_RUBRIC = [
|
|
|
7325
8297
|
|
|
7326
8298
|
// src/witan/scoring.ts
|
|
7327
8299
|
function usesV17DetectorClosure2(rubricVersion) {
|
|
7328
|
-
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
8300
|
+
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
7329
8301
|
}
|
|
7330
8302
|
var MAX_SIGNAL_ADJUSTMENT = 0.8;
|
|
7331
8303
|
function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
|
|
@@ -7484,7 +8456,7 @@ function statusForScore(score) {
|
|
|
7484
8456
|
}
|
|
7485
8457
|
function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubricVersion) {
|
|
7486
8458
|
if (adjustment === 0) return nativeStatus;
|
|
7487
|
-
if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22) {
|
|
8459
|
+
if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22 && rubricVersion !== WITAN_RUBRIC_VERSION_V23) {
|
|
7488
8460
|
return statusForScore(roundScore(Math.max(0, nativeScore - adjustment)));
|
|
7489
8461
|
}
|
|
7490
8462
|
const statusFloor = {
|
|
@@ -7529,7 +8501,7 @@ function mergeSignalsByCriterion(signals) {
|
|
|
7529
8501
|
return merged;
|
|
7530
8502
|
}
|
|
7531
8503
|
function usesMetricScoring(rubricVersion) {
|
|
7532
|
-
return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_RUBRIC_VERSION_V7 || rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
|
|
8504
|
+
return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_RUBRIC_VERSION_V7 || rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
|
|
7533
8505
|
}
|
|
7534
8506
|
function unmeasuredStatus(rubricVersion) {
|
|
7535
8507
|
return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
|
|
@@ -7909,7 +8881,7 @@ function calibratedCriterionStateV11(criterionId, metrics) {
|
|
|
7909
8881
|
return void 0;
|
|
7910
8882
|
}
|
|
7911
8883
|
function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence, rubricVersion) {
|
|
7912
|
-
if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22 && findings.length > 0) {
|
|
8884
|
+
if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22 && rubricVersion !== WITAN_RUBRIC_VERSION_V23 && findings.length > 0) {
|
|
7913
8885
|
return [...findings];
|
|
7914
8886
|
}
|
|
7915
8887
|
if (status !== "critical" && status !== "warning") return [...findings];
|
|
@@ -8229,6 +9201,7 @@ export {
|
|
|
8229
9201
|
CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE,
|
|
8230
9202
|
CERTIFICATE_GLOSSARY,
|
|
8231
9203
|
formatCertificateMetricLabel,
|
|
9204
|
+
formatCertificateMetricAppliedWeightPercent,
|
|
8232
9205
|
formatCertificateMetricValue,
|
|
8233
9206
|
buildRelyingPartySummary,
|
|
8234
9207
|
WITAN_RUBRIC_VERSION_V22,
|