@cejel/cejel 0.6.3 → 0.6.5
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.
|
@@ -532,6 +532,8 @@ var WITAN_RUBRIC_VERSION_V21 = "witan-rubric-v21-prospective-2026-08-10";
|
|
|
532
532
|
var WITAN_RUBRIC_VERSION_V22 = "witan-rubric-v22-prospective-2026-08-10";
|
|
533
533
|
var WITAN_RUBRIC_VERSION_V23 = "witan-rubric-v23-prospective-2026-09-06";
|
|
534
534
|
var WITAN_RUBRIC_VERSION_V24 = "witan-rubric-v24-prospective-2026-09-15";
|
|
535
|
+
var WITAN_RUBRIC_VERSION_V25 = "witan-rubric-v25-prospective-2026-10-12";
|
|
536
|
+
var WITAN_RUBRIC_VERSION_V26 = "witan-rubric-v26-prospective-2026-10-14";
|
|
535
537
|
var WITAN_LAST_CALIBRATED_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V17;
|
|
536
538
|
var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
|
|
537
539
|
WITAN_RUBRIC_VERSION_V18,
|
|
@@ -540,7 +542,9 @@ var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
|
|
|
540
542
|
WITAN_RUBRIC_VERSION_V21,
|
|
541
543
|
WITAN_RUBRIC_VERSION_V22,
|
|
542
544
|
WITAN_RUBRIC_VERSION_V23,
|
|
543
|
-
WITAN_RUBRIC_VERSION_V24
|
|
545
|
+
WITAN_RUBRIC_VERSION_V24,
|
|
546
|
+
WITAN_RUBRIC_VERSION_V25,
|
|
547
|
+
WITAN_RUBRIC_VERSION_V26
|
|
544
548
|
]);
|
|
545
549
|
var WITAN_SELECTABLE_RUBRIC_VERSIONS = Object.freeze([
|
|
546
550
|
WITAN_LAST_CALIBRATED_RUBRIC_VERSION,
|
|
@@ -566,6 +570,8 @@ var WITAN_NO_MEASUREMENT_REASON = "No free-core rubric criterion produced a meas
|
|
|
566
570
|
var WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the tracked scan-eligible inventory matched no concrete test file, configured test runner, CI test command, or coverage configuration.";
|
|
567
571
|
var WITAN_DIRECTORY_WALK_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the bounded directory walk matched no concrete test file, configured test runner, CI test command, or coverage configuration.";
|
|
568
572
|
var WITAN_LEGACY_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the complete tracked inventory contains no concrete test file, configured test runner, CI test command, or coverage configuration.";
|
|
573
|
+
var WITAN_A5_RECONCILIATION_UNSETTLED_DISCLOSURE = "Whether this repository's claims are reconciled against its implementation cannot be settled from static evidence; Cejel makes no finding on it.";
|
|
574
|
+
var WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE = "Whether tenant isolation is enforced outside the database cannot be settled from static evidence; Cejel makes no finding on it.";
|
|
569
575
|
function isWitanNoMeasurementAbstention(report) {
|
|
570
576
|
return report.verdict === "insufficient_source" && report.insufficientSourceReason.startsWith(WITAN_NO_MEASUREMENT_REASON) && report.criteria.every(
|
|
571
577
|
({ status }) => status === "not_applicable" || status === "insufficient_data"
|
|
@@ -642,14 +648,21 @@ init_esm_shims();
|
|
|
642
648
|
init_esm_shims();
|
|
643
649
|
import { createHash } from "crypto";
|
|
644
650
|
var WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS = Object.freeze({
|
|
645
|
-
[WITAN_RUBRIC_VERSION_V17]: "sha256:
|
|
646
|
-
[WITAN_RUBRIC_VERSION_V18]: "sha256:
|
|
647
|
-
[WITAN_RUBRIC_VERSION_V19]: "sha256:
|
|
648
|
-
[WITAN_RUBRIC_VERSION_V20]: "sha256:
|
|
649
|
-
[WITAN_RUBRIC_VERSION_V21]: "sha256:
|
|
650
|
-
[WITAN_RUBRIC_VERSION_V22]: "sha256:
|
|
651
|
-
[WITAN_RUBRIC_VERSION_V23]: "sha256:
|
|
652
|
-
[WITAN_RUBRIC_VERSION_V24]: "sha256:
|
|
651
|
+
[WITAN_RUBRIC_VERSION_V17]: "sha256:5cd48f8c642300ae8736a09d4a24ce3a4f8885f6e099b5f27f99d950e0c944b4",
|
|
652
|
+
[WITAN_RUBRIC_VERSION_V18]: "sha256:4a5d17c84e265ec20cf54216d2e794dec1d299d859c69ac8ef1ab317fc479e2f",
|
|
653
|
+
[WITAN_RUBRIC_VERSION_V19]: "sha256:73dc0282a9ea2a481db8e86a9a5dc227a8b108356c358de029901f2dc38cef79",
|
|
654
|
+
[WITAN_RUBRIC_VERSION_V20]: "sha256:b5f4254a6056ff792d79a2d85892cdf05484eabded85ba13251a2c8fd5ac6336",
|
|
655
|
+
[WITAN_RUBRIC_VERSION_V21]: "sha256:f599d06354adb45af018639594cb6f44e7cb40445b84a0dcdd4438b8d7d069d0",
|
|
656
|
+
[WITAN_RUBRIC_VERSION_V22]: "sha256:34fb6ac5d1f6faf0912aa4d00924837eeee543614d7971247d005312b89928e1",
|
|
657
|
+
[WITAN_RUBRIC_VERSION_V23]: "sha256:ce24235861ce0f0dc4dafd22059a78d31f348e5c741e4056475867c29cc8b814",
|
|
658
|
+
[WITAN_RUBRIC_VERSION_V24]: "sha256:b221907d60d209cd3d57437892ae3dd873f4fa221ad8743954a30c8575d4e77c",
|
|
659
|
+
[WITAN_RUBRIC_VERSION_V25]: "sha256:0f2638a5088f95a610a0e12012442f1067af3e985b955f96ec71853d549693b9",
|
|
660
|
+
// v25's closure plus v26 item 6 (docs/calibration/v26/ruling-item-6.md and its amendment 1).
|
|
661
|
+
// The corpus holds no declared claim-reconciliation artifact, so the v25 A5 finding no longer
|
|
662
|
+
// fires on it and, by amendment 1, A5 is scored without reconciliation_artifact_depth. The A5
|
|
663
|
+
// component and the report components of the specimens whose code-trust score that moves differ
|
|
664
|
+
// from v25's. A2 does not move on this corpus.
|
|
665
|
+
[WITAN_RUBRIC_VERSION_V26]: "sha256:4530317d56fc73eb89287e344ad626e2b9a96908ef12eb9e9c13b839bd599dae"
|
|
653
666
|
});
|
|
654
667
|
function rubricBehaviourFingerprint(rubricVersion) {
|
|
655
668
|
return WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS[rubricVersion];
|
|
@@ -717,7 +730,7 @@ var WITAN_RUBRIC = [
|
|
|
717
730
|
|
|
718
731
|
// src/witan/scoring.ts
|
|
719
732
|
function usesV17DetectorClosure(rubricVersion) {
|
|
720
|
-
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_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
733
|
+
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_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
721
734
|
}
|
|
722
735
|
var MAX_SIGNAL_ADJUSTMENT = 0.8;
|
|
723
736
|
function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC, toolVersion) {
|
|
@@ -888,7 +901,7 @@ function statusForScore(score) {
|
|
|
888
901
|
}
|
|
889
902
|
function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubricVersion) {
|
|
890
903
|
if (adjustment === 0) return nativeStatus;
|
|
891
|
-
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_V24 && rubricVersion !== WITAN_RUBRIC_VERSION_V23) {
|
|
904
|
+
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_V25 && rubricVersion !== WITAN_RUBRIC_VERSION_V26 && rubricVersion !== WITAN_RUBRIC_VERSION_V24 && rubricVersion !== WITAN_RUBRIC_VERSION_V23) {
|
|
892
905
|
return statusForScore(roundScore(Math.max(0, nativeScore - adjustment)));
|
|
893
906
|
}
|
|
894
907
|
const statusFloor = {
|
|
@@ -945,7 +958,7 @@ function mergeSignalsByCriterion(signals) {
|
|
|
945
958
|
return merged;
|
|
946
959
|
}
|
|
947
960
|
function usesMetricScoring(rubricVersion) {
|
|
948
|
-
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_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
|
|
961
|
+
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_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
|
|
949
962
|
}
|
|
950
963
|
function unmeasuredStatus(rubricVersion) {
|
|
951
964
|
return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
|
|
@@ -1333,7 +1346,7 @@ function calibratedCriterionStateV11(criterionId, metrics) {
|
|
|
1333
1346
|
return void 0;
|
|
1334
1347
|
}
|
|
1335
1348
|
function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence, rubricVersion) {
|
|
1336
|
-
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_V24 && rubricVersion !== WITAN_RUBRIC_VERSION_V23 && findings.length > 0) {
|
|
1349
|
+
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_V25 && rubricVersion !== WITAN_RUBRIC_VERSION_V26 && rubricVersion !== WITAN_RUBRIC_VERSION_V24 && rubricVersion !== WITAN_RUBRIC_VERSION_V23 && findings.length > 0) {
|
|
1337
1350
|
return [...findings];
|
|
1338
1351
|
}
|
|
1339
1352
|
if (status !== "critical" && status !== "warning") return [...findings];
|
|
@@ -1479,7 +1492,7 @@ var CERTIFICATE_GLOSSARY = [
|
|
|
1479
1492
|
{
|
|
1480
1493
|
key: "production-readiness-basic-checks",
|
|
1481
1494
|
term: "Production-readiness basic checks",
|
|
1482
|
-
definition: "A count of six named checks: a build or type-check command, a CI workflow, deployment configuration, an environment template, a health or readiness signal, and an error boundary.",
|
|
1495
|
+
definition: "A count of six named checks: a build or type-check command, a CI workflow, deployment configuration, an environment template, a health or readiness signal, and an error boundary. In the calibrated rubric the health or readiness signal is credited only for a dedicated health or readiness file (such as health.ts or readiness.ts), a Dockerfile HEALTHCHECK, or Kubernetes liveness or readiness probes. A health route declared inline in an application file does not count there. The prospective witan-rubric-v25 and witan-rubric-v26 also credit such an inline route.",
|
|
1483
1496
|
metricNames: ["prod_readiness_primitives"]
|
|
1484
1497
|
},
|
|
1485
1498
|
{
|
|
@@ -1611,7 +1624,7 @@ var CERTIFICATE_GLOSSARY = [
|
|
|
1611
1624
|
{
|
|
1612
1625
|
key: "protected-path-review-gate",
|
|
1613
1626
|
term: "Protected-path review gate",
|
|
1614
|
-
definition: "A binary check for a CODEOWNERS file or
|
|
1627
|
+
definition: "A binary check for a CODEOWNERS file in a location GitHub reads (the repository root, .github/ or docs/), or a document stating a required-review or branch-protection policy. A changelog, an experiment or calibration record, a review note or a report does not count. This is the repository-visible proxy for human review of sensitive-path changes.",
|
|
1615
1628
|
metricNames: ["protected_path_review_gate"]
|
|
1616
1629
|
}
|
|
1617
1630
|
];
|
|
@@ -1710,7 +1723,7 @@ var BINARY_METRIC_COPY = {
|
|
|
1710
1723
|
},
|
|
1711
1724
|
protected_path_review_gate: {
|
|
1712
1725
|
positive: "present \u2014 CODEOWNERS or a documented required-review policy was detected",
|
|
1713
|
-
negative: "missing \u2014 no CODEOWNERS
|
|
1726
|
+
negative: "missing \u2014 no CODEOWNERS file in a location GitHub reads, and no documented required-review or branch-protection policy detected"
|
|
1714
1727
|
}
|
|
1715
1728
|
};
|
|
1716
1729
|
var CONDITIONAL_METRIC_COPY = {
|
|
@@ -1958,6 +1971,17 @@ function buildWithheldPathsGapSentence(report) {
|
|
|
1958
1971
|
const reportsOnWhatItRead = signals.length === 1 ? "that signal reports on what it read" : "these signals report on what they read";
|
|
1959
1972
|
return `${count} ${entryWord} withheld (${reasonLabel}); ${signalList} would have read ${pronoun}. Under this rubric ${reportsOnWhatItRead}; the prospective v23 rubric abstains instead.`;
|
|
1960
1973
|
}
|
|
1974
|
+
var SETTLING_EVIDENCE_DISCLOSURES = [
|
|
1975
|
+
{ criterionId: "A2", sentence: WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE },
|
|
1976
|
+
{ criterionId: "A5", sentence: WITAN_A5_RECONCILIATION_UNSETTLED_DISCLOSURE }
|
|
1977
|
+
];
|
|
1978
|
+
function settlingEvidenceDisclosures(report) {
|
|
1979
|
+
return SETTLING_EVIDENCE_DISCLOSURES.filter(
|
|
1980
|
+
({ criterionId, sentence }) => report.criteria.some(
|
|
1981
|
+
(criterion) => criterion.id === criterionId && criterion.status !== "not_applicable" && criterion.notes?.includes(sentence) === true
|
|
1982
|
+
)
|
|
1983
|
+
).map(({ sentence }) => sentence);
|
|
1984
|
+
}
|
|
1961
1985
|
function describeEstablishedDimensions(report, measured, applicable) {
|
|
1962
1986
|
const total = report.criteria.length;
|
|
1963
1987
|
const counts = formatMeasuredDimensionCounts(computeMeasuredCoverage(report));
|
|
@@ -2016,6 +2040,7 @@ function buildRelyingPartySummary(report) {
|
|
|
2016
2040
|
`${contentEntries(excludedByExtension, "was", "were")} not read because ${excludedByExtension === 1 ? "its file type is" : "their file types are"} outside what Cejel reads. This is a fixed limit of the tool, not something the repository needs to fix.`
|
|
2017
2041
|
);
|
|
2018
2042
|
}
|
|
2043
|
+
gaps.push(...settlingEvidenceDisclosures(report));
|
|
2019
2044
|
const withheldPathsSentence = buildWithheldPathsGapSentence(report);
|
|
2020
2045
|
if (withheldPathsSentence !== void 0) {
|
|
2021
2046
|
gaps.push(withheldPathsSentence);
|
|
@@ -2892,7 +2917,7 @@ import {
|
|
|
2892
2917
|
realpathSync,
|
|
2893
2918
|
statSync
|
|
2894
2919
|
} from "fs";
|
|
2895
|
-
import { basename, dirname, extname, join, relative, resolve, sep } from "path";
|
|
2920
|
+
import { basename, dirname, extname, join, posix, relative, resolve, sep } from "path";
|
|
2896
2921
|
|
|
2897
2922
|
// src/witan/json-safe.ts
|
|
2898
2923
|
init_esm_shims();
|
|
@@ -2902,7 +2927,7 @@ function stripBom(text) {
|
|
|
2902
2927
|
|
|
2903
2928
|
// src/witan/repo-signals.ts
|
|
2904
2929
|
function usesV17DetectorClosure2(rubricVersion) {
|
|
2905
|
-
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 || rubricVersion === WITAN_RUBRIC_VERSION_V24;
|
|
2930
|
+
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 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
2906
2931
|
}
|
|
2907
2932
|
var LEGACY_INVENTORY_ABSENCE_CONTEXT = {
|
|
2908
2933
|
rubricGeneration: "legacy",
|
|
@@ -2917,13 +2942,28 @@ var INVENTORY_SCAN_PATTERN_SETS = {
|
|
|
2917
2942
|
id: "cejel.core-a1.concrete-test-files.v1",
|
|
2918
2943
|
count: 16
|
|
2919
2944
|
},
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2945
|
+
// v1 (10 branches, every rubric but v23) and v2 (14, v23) were retired on 2026-10-14
|
|
2946
|
+
// (goal_cejel_v26_coverage_config_recognition_2026-10-14), when v23's command-flag recognition
|
|
2947
|
+
// and eleven documented configuration locations shipped under every rubric. Branches, counted
|
|
2948
|
+
// the way v2 counted runners (one runner-flag branch however many runners it knows):
|
|
2949
|
+
// configuration (19): nyc/c8/coverage/codecov .json|.yml|.yaml; .coveragerc; lcov.info;
|
|
2950
|
+
// .coveralls.yml; vitest.config coverage with disabled values; jest.config coverage keys;
|
|
2951
|
+
// pyproject [tool.coverage]; package.json nyc/c8/istanbul objects; .codecov.yml|.yaml;
|
|
2952
|
+
// .nycrc family; .c8rc family; setup.cfg/tox.ini [coverage:run|report]; package.json
|
|
2953
|
+
// "jest" coverage keys; vite.config test.coverage; .simplecov; phpunit <coverage>; JaCoCo
|
|
2954
|
+
// in pom.xml; JaCoCo in build.gradle(.kts); tarpaulin.toml.
|
|
2955
|
+
// commands (6): CI run steps; reachable package scripts; reachable Make/just recipes; tracked
|
|
2956
|
+
// shell wrappers; coverage-tool invocation; runner coverage flags.
|
|
2957
|
+
// v4 is those 25. v3 adds the four v17 heuristics v23 replaced rather than kept: a bare
|
|
2958
|
+
// `coverage:` key in vitest.config, a jest.config coverage key of any value, a package script
|
|
2959
|
+
// anywhere invoking nyc/c8/istanbul, and a script named `coverage`.
|
|
2960
|
+
a1CoverageConfigurationV3: {
|
|
2961
|
+
id: "cejel.core-a1.coverage-configuration.v3",
|
|
2962
|
+
count: 29
|
|
2923
2963
|
},
|
|
2924
|
-
|
|
2925
|
-
id: "cejel.core-a1.coverage-configuration.
|
|
2926
|
-
count:
|
|
2964
|
+
a1CoverageConfigurationV4: {
|
|
2965
|
+
id: "cejel.core-a1.coverage-configuration.v4",
|
|
2966
|
+
count: 25
|
|
2927
2967
|
},
|
|
2928
2968
|
a2CurrentSecret: {
|
|
2929
2969
|
id: "cejel.core-a2.current-secret-shape.v1",
|
|
@@ -2944,6 +2984,11 @@ var INVENTORY_SCAN_PATTERN_SETS = {
|
|
|
2944
2984
|
a5ClaimRealityArtifact: {
|
|
2945
2985
|
id: "cejel.core-a5.claim-reality-artifact.v1",
|
|
2946
2986
|
count: 3
|
|
2987
|
+
},
|
|
2988
|
+
// v26 item 6: the one path the repository itself declared, looked up in the inventory.
|
|
2989
|
+
a5DeclaredClaimArtifactPath: {
|
|
2990
|
+
id: "cejel.core-a5.declared-claim-artifact-path.v1",
|
|
2991
|
+
count: 1
|
|
2947
2992
|
}
|
|
2948
2993
|
};
|
|
2949
2994
|
function inventoryScanDerivation(inventoryCount, patternSet, matchCount) {
|
|
@@ -3149,15 +3194,17 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
3149
3194
|
rubricGeneration: usesV17Detectors ? "current" : "legacy",
|
|
3150
3195
|
inventoryProvenance: fileInventory.repoFileInventoryScope === "tracked-scan-eligible" ? "tracked-scan-eligible" : "directory-walk"
|
|
3151
3196
|
};
|
|
3152
|
-
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 || rubricVersion === WITAN_RUBRIC_VERSION_V24;
|
|
3153
|
-
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 || rubricVersion === WITAN_RUBRIC_VERSION_V24;
|
|
3154
|
-
const usesV20A3ExplicitGaps = rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24;
|
|
3155
|
-
const usesV21ExecutedEscalations = rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24;
|
|
3156
|
-
const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24;
|
|
3157
|
-
const
|
|
3197
|
+
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 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3198
|
+
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 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3199
|
+
const usesV20A3ExplicitGaps = rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3200
|
+
const usesV21ExecutedEscalations = rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3201
|
+
const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_RUBRIC_VERSION_V24 || rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3202
|
+
const usesV23StrictCoverageAndRecipeRunners = rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
3158
3203
|
const usesV23PemPrivateKeyGrammar = rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
3159
3204
|
const usesV23WithheldPathAbstention = rubricVersion === WITAN_RUBRIC_VERSION_V23;
|
|
3160
3205
|
const usesV24SecretContentContext = rubricVersion === WITAN_RUBRIC_VERSION_V24;
|
|
3206
|
+
const usesV25InlineHealthRoute = rubricVersion === WITAN_RUBRIC_VERSION_V25 || rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3207
|
+
const usesV26SettlingEvidence = rubricVersion === WITAN_RUBRIC_VERSION_V26;
|
|
3161
3208
|
const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
|
|
3162
3209
|
const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure2(rubricVersion) ? applyReadableSourceRepresentationGate(
|
|
3163
3210
|
options.repoPath,
|
|
@@ -3193,10 +3240,12 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
3193
3240
|
usesV20A3ExplicitGaps,
|
|
3194
3241
|
usesV21ExecutedEscalations,
|
|
3195
3242
|
usesV22PackageStartEntrypoint,
|
|
3196
|
-
|
|
3243
|
+
usesV23StrictCoverageAndRecipeRunners,
|
|
3197
3244
|
usesV23PemPrivateKeyGrammar,
|
|
3198
3245
|
usesV23WithheldPathAbstention,
|
|
3199
3246
|
usesV24SecretContentContext,
|
|
3247
|
+
usesV25InlineHealthRoute,
|
|
3248
|
+
usesV26SettlingEvidence,
|
|
3200
3249
|
reviewableSourceProof,
|
|
3201
3250
|
inventoryAbsenceContext,
|
|
3202
3251
|
scanLimitations
|
|
@@ -3811,7 +3860,7 @@ function buildNotApplicableSignal(criterionId, reason) {
|
|
|
3811
3860
|
notApplicable: true
|
|
3812
3861
|
};
|
|
3813
3862
|
}
|
|
3814
|
-
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint,
|
|
3863
|
+
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23StrictCoverageAndRecipeRunners, useV23PemPrivateKeyGrammar, useV23WithheldPathAbstention, useV24SecretContentContext, useV25InlineHealthRoute, useV26SettlingEvidence, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
|
|
3815
3864
|
const signals = [];
|
|
3816
3865
|
const mono = resolveMonorepoContext(repoPath, scanLimitations);
|
|
3817
3866
|
const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
|
|
@@ -3823,7 +3872,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
3823
3872
|
useV27Detectors,
|
|
3824
3873
|
useV33Detectors,
|
|
3825
3874
|
useV36Detectors,
|
|
3826
|
-
|
|
3875
|
+
useV23StrictCoverageAndRecipeRunners,
|
|
3827
3876
|
reviewableSourceProof,
|
|
3828
3877
|
inventoryAbsenceContext
|
|
3829
3878
|
)
|
|
@@ -3842,7 +3891,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
3842
3891
|
useV24SecretContentContext,
|
|
3843
3892
|
useV18NativeRls,
|
|
3844
3893
|
inventoryAbsenceContext,
|
|
3845
|
-
scanLimitations
|
|
3894
|
+
scanLimitations,
|
|
3895
|
+
useV26SettlingEvidence
|
|
3846
3896
|
)
|
|
3847
3897
|
);
|
|
3848
3898
|
const a3Signal = collectCriterion(
|
|
@@ -3854,7 +3904,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
3854
3904
|
useV20A3ExplicitGaps,
|
|
3855
3905
|
useV22PackageStartEntrypoint,
|
|
3856
3906
|
useV23WithheldPathAbstention,
|
|
3857
|
-
inventoryAbsenceContext
|
|
3907
|
+
inventoryAbsenceContext,
|
|
3908
|
+
useV25InlineHealthRoute
|
|
3858
3909
|
)
|
|
3859
3910
|
);
|
|
3860
3911
|
const a4Signal = collectCriterion(
|
|
@@ -3875,7 +3926,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
3875
3926
|
repoFiles,
|
|
3876
3927
|
useV27Detectors,
|
|
3877
3928
|
useV23WithheldPathAbstention,
|
|
3878
|
-
inventoryAbsenceContext
|
|
3929
|
+
inventoryAbsenceContext,
|
|
3930
|
+
useV26SettlingEvidence
|
|
3879
3931
|
)
|
|
3880
3932
|
);
|
|
3881
3933
|
const b1Signal = buildNotApplicableSignal(
|
|
@@ -3927,7 +3979,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
3927
3979
|
}
|
|
3928
3980
|
return signals;
|
|
3929
3981
|
}
|
|
3930
|
-
function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors,
|
|
3982
|
+
function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV23StrictCoverageAndRecipeRunners, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
3931
3983
|
const evidence = [];
|
|
3932
3984
|
const findings = [];
|
|
3933
3985
|
const allTestFiles = findConcreteTestFiles(
|
|
@@ -3938,10 +3990,10 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3938
3990
|
const testFiles = allTestFiles.slice(0, 8);
|
|
3939
3991
|
const sourceFiles = repoFiles.filter(isSourceFile);
|
|
3940
3992
|
const runnerFiles = repoFiles.filter(
|
|
3941
|
-
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && (isTestRunnerConfig(repoPath, file) ||
|
|
3993
|
+
(file) => (!useV27Detectors || isAuthoredProductionPath(file)) && (isTestRunnerConfig(repoPath, file) || useV23StrictCoverageAndRecipeRunners && isRecipeTestEntry(repoPath, file))
|
|
3942
3994
|
).slice(0, 6);
|
|
3943
3995
|
const baseConfiguredRunnerFiles = useV33Detectors ? findConfiguredTestRunnerFiles(repoPath, repoFiles) : runnerFiles;
|
|
3944
|
-
const configuredRunnerFiles =
|
|
3996
|
+
const configuredRunnerFiles = useV23StrictCoverageAndRecipeRunners ? [
|
|
3945
3997
|
.../* @__PURE__ */ new Set([
|
|
3946
3998
|
...baseConfiguredRunnerFiles,
|
|
3947
3999
|
...repoFiles.filter((file) => isRecipeTestEntry(repoPath, file))
|
|
@@ -3950,11 +4002,12 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3950
4002
|
const packageJson = findRootPackageJson(repoFiles);
|
|
3951
4003
|
const packageScripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
|
|
3952
4004
|
const { coverageAnalysis, coveragePercent } = withContentReadSignal("A1", "coverage_percent", () => {
|
|
3953
|
-
const
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
4005
|
+
const commandAnalysis = analyzeCoverageConfiguration(repoPath, repoFiles, useV27Detectors);
|
|
4006
|
+
const analysis = useV23StrictCoverageAndRecipeRunners ? commandAnalysis : unionWithLegacyCoverageRecognition(
|
|
4007
|
+
commandAnalysis,
|
|
4008
|
+
findCoverageConfigFiles(repoPath, repoFiles, useV27Detectors),
|
|
4009
|
+
packageScripts.has("coverage")
|
|
4010
|
+
);
|
|
3958
4011
|
return {
|
|
3959
4012
|
coverageAnalysis: analysis,
|
|
3960
4013
|
coveragePercent: readCoveragePercent(repoPath, analysis.coverageFiles.slice(0, 4))
|
|
@@ -4106,7 +4159,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
4106
4159
|
evidence: firstEvidence,
|
|
4107
4160
|
context: inventoryAbsenceContext,
|
|
4108
4161
|
inventoryCount: repoFiles.length,
|
|
4109
|
-
patternSet:
|
|
4162
|
+
patternSet: useV23StrictCoverageAndRecipeRunners ? INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV4 : INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV3,
|
|
4110
4163
|
matchCount: 0
|
|
4111
4164
|
})
|
|
4112
4165
|
);
|
|
@@ -4253,7 +4306,7 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inve
|
|
|
4253
4306
|
notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files. Verification-script signals (lint/typecheck/coverage/CI commands) are independent of test-file presence and are still credited above where detected.`
|
|
4254
4307
|
};
|
|
4255
4308
|
}
|
|
4256
|
-
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV23PemPrivateKeyGrammar, useV24SecretContentContext, useV18NativeRls, inventoryAbsenceContext, scanLimitations) {
|
|
4309
|
+
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV23PemPrivateKeyGrammar, useV24SecretContentContext, useV18NativeRls, inventoryAbsenceContext, scanLimitations, useV26SettlingEvidence = false) {
|
|
4257
4310
|
const evidence = [];
|
|
4258
4311
|
const findings = [];
|
|
4259
4312
|
const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
|
|
@@ -4539,7 +4592,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
4539
4592
|
evidence: historySecretScan.envPathEvidence
|
|
4540
4593
|
});
|
|
4541
4594
|
}
|
|
4542
|
-
const hasTenantWithoutRlsGap = rlsPolicyCount === 0 && (useV18NativeRls ? tenantWithoutRlsPremiseFiles.length > 0 : isMultiTenant);
|
|
4595
|
+
const hasTenantWithoutRlsGap = !useV26SettlingEvidence && rlsPolicyCount === 0 && (useV18NativeRls ? tenantWithoutRlsPremiseFiles.length > 0 : isMultiTenant);
|
|
4543
4596
|
if (hasTenantWithoutRlsGap) {
|
|
4544
4597
|
const gapEvidence = rlsMigration ?? tenantScopedFile ?? tenantWithoutRlsPremiseFiles[0];
|
|
4545
4598
|
if (gapEvidence) {
|
|
@@ -4565,6 +4618,28 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
4565
4618
|
);
|
|
4566
4619
|
}
|
|
4567
4620
|
}
|
|
4621
|
+
let tenantIsolationUnsettled = false;
|
|
4622
|
+
if (useV26SettlingEvidence) {
|
|
4623
|
+
const uncoveredTables = withContentReadSignals(
|
|
4624
|
+
"A2",
|
|
4625
|
+
["rls_policy_count", "tenant_scope_ratio"],
|
|
4626
|
+
() => findUncoveredTenantTables(repoPath, repoFiles)
|
|
4627
|
+
);
|
|
4628
|
+
for (const table of uncoveredTables) {
|
|
4629
|
+
findings.push({
|
|
4630
|
+
severity: "warning",
|
|
4631
|
+
summary: `Table \`${table.name}\` carries a tenant column and no row-level-security policy, while this repository enforces tenant isolation with row-level security elsewhere.`,
|
|
4632
|
+
evidence: evidenceForRelativeAtLine(
|
|
4633
|
+
repoPath,
|
|
4634
|
+
table.file,
|
|
4635
|
+
"artifact",
|
|
4636
|
+
"Tenant table without a row-level-security policy",
|
|
4637
|
+
table.line
|
|
4638
|
+
)
|
|
4639
|
+
});
|
|
4640
|
+
}
|
|
4641
|
+
tenantIsolationUnsettled = uncoveredTables.length === 0 && tenantWithoutRlsPremiseFiles.length > 0;
|
|
4642
|
+
}
|
|
4568
4643
|
evidence.push(...cryptoHygiene.evidence);
|
|
4569
4644
|
findings.push(...cryptoHygiene.findings);
|
|
4570
4645
|
if (evidence.length === 0 && findings.length === 0) return null;
|
|
@@ -4652,7 +4727,8 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
4652
4727
|
useV18NativeRls ? "History secret scanning covers all reachable git history for credential-pattern paths unless the explicit credential-blob safety valve is reported. Positive multi-tenancy credit is derived only from this repository\u2019s own CREATE POLICY USING/WITH CHECK clauses; no fixed tenant-token vocabulary can create isolation credit. A conservative schema premise is retained only to fail closed when a tenant-shaped schema has zero RLS policies." : "History secret scanning covers all reachable git history for credential-pattern paths unless the explicit credential-blob safety valve is reported.",
|
|
4653
4728
|
...abstainsSecretCleanliness ? [
|
|
4654
4729
|
"Cejel abstained on secret cleanliness: the current-tree scan matched a secret-shaped value it could not classify from content as either a credential or an example. The metric is withheld rather than scored; every other A2 signal is unaffected."
|
|
4655
|
-
] : []
|
|
4730
|
+
] : [],
|
|
4731
|
+
...tenantIsolationUnsettled ? [WITAN_A2_TENANT_ISOLATION_UNSETTLED_DISCLOSURE] : []
|
|
4656
4732
|
].join(" ")
|
|
4657
4733
|
};
|
|
4658
4734
|
}
|
|
@@ -4752,7 +4828,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = fal
|
|
|
4752
4828
|
}
|
|
4753
4829
|
return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
|
|
4754
4830
|
}
|
|
4755
|
-
function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, useV23WithheldPathAbstention = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
4831
|
+
function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, useV23WithheldPathAbstention = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, useV25InlineHealthRoute = false) {
|
|
4756
4832
|
const v20DirectHttpEntrypoint = useV20ExplicitGaps ? findV20DirectHttpServerEntrypointFile(repoPath, repoFiles) : null;
|
|
4757
4833
|
const v22PackageStartHttpEntrypoint = useV22PackageStartEntrypoint ? findV22PackageStartHttpServerEntrypointFile(repoPath, repoFiles) : null;
|
|
4758
4834
|
const v20RuntimeContainer = useV20ExplicitGaps ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, true) : null;
|
|
@@ -4790,8 +4866,16 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
4790
4866
|
)
|
|
4791
4867
|
);
|
|
4792
4868
|
const healthCheck = healthChecks[0];
|
|
4793
|
-
const healthReadinessRouteReads = (file) => isAuthoredProductionPath(file) && (isImplementationFile(file) || HEALTH_ROUTE_FUNCTIONS_DIR_PATTERN.test(file));
|
|
4869
|
+
const healthReadinessRouteReads = (file) => isAuthoredProductionPath(file) && (isImplementationFile(file) || HEALTH_ROUTE_FUNCTIONS_DIR_PATTERN.test(file) || HEALTH_ROUTE_ENTRYPOINT_HOME_PATTERN.test(file));
|
|
4794
4870
|
const observabilityDepthReads = (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file);
|
|
4871
|
+
const inlineHealthRouteFile = useV25InlineHealthRoute && healthChecks.length === 0 ? withContentReadSignal(
|
|
4872
|
+
"A3",
|
|
4873
|
+
"prod_readiness_primitives",
|
|
4874
|
+
() => repoFiles.find(
|
|
4875
|
+
(file) => healthReadinessRouteReads(file) && fileMatchesOutsideComments(repoPath, file, V20_HEALTH_OR_READINESS_ROUTE_PATTERN)
|
|
4876
|
+
)
|
|
4877
|
+
) : void 0;
|
|
4878
|
+
const hasHealthOrReadinessSignal = healthChecks.length > 0 || inlineHealthRouteFile !== void 0;
|
|
4795
4879
|
const { hasV20HealthOrReadinessRoute, hasV22PackageStartHealthOrReadinessRoute } = withContentReadSignal("A3", "health_readiness_route", () => ({
|
|
4796
4880
|
hasV20HealthOrReadinessRoute: useV20ExplicitGaps && repoFiles.some(
|
|
4797
4881
|
(file) => healthReadinessRouteReads(file) && fileContains(repoPath, file, V20_HEALTH_OR_READINESS_ROUTE_PATTERN)
|
|
@@ -4855,12 +4939,28 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
4855
4939
|
/rollback|roll back|migration safety|down migration|reversible|undo migration/i
|
|
4856
4940
|
)
|
|
4857
4941
|
);
|
|
4942
|
+
const rootOnlyScriptsNote = rootOnlyPackageScriptsDisclosure(repoFiles, packageJson);
|
|
4858
4943
|
if (packageJson && (scripts.has("build") || scripts.has("typecheck"))) {
|
|
4859
4944
|
evidence.push(
|
|
4860
4945
|
evidenceForRelative(repoPath, packageJson, "prod_check", "Build or typecheck script")
|
|
4861
4946
|
);
|
|
4862
4947
|
}
|
|
4863
|
-
if (workflow)
|
|
4948
|
+
if (workflow) {
|
|
4949
|
+
evidence.push(
|
|
4950
|
+
evidenceForRelative(
|
|
4951
|
+
repoPath,
|
|
4952
|
+
workflow,
|
|
4953
|
+
"ci_run",
|
|
4954
|
+
ciWorkflowCitationLabel(
|
|
4955
|
+
"CI workflow",
|
|
4956
|
+
"counted",
|
|
4957
|
+
workflows.length,
|
|
4958
|
+
1,
|
|
4959
|
+
unreadCiWorkflowReasons((file) => !useV27Detectors || isAuthoredProductionPath(file))
|
|
4960
|
+
)
|
|
4961
|
+
)
|
|
4962
|
+
);
|
|
4963
|
+
}
|
|
4864
4964
|
if (releaseDeployConfig)
|
|
4865
4965
|
evidence.push(
|
|
4866
4966
|
evidenceForRelative(
|
|
@@ -4889,6 +4989,24 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
4889
4989
|
evidenceForRelative(repoPath, healthCheck, "prod_check", "Health/readiness signal")
|
|
4890
4990
|
)
|
|
4891
4991
|
);
|
|
4992
|
+
} else if (inlineHealthRouteFile) {
|
|
4993
|
+
withContentReadSignal(
|
|
4994
|
+
"A3",
|
|
4995
|
+
"prod_readiness_primitives",
|
|
4996
|
+
() => evidence.push(
|
|
4997
|
+
evidenceForRelativeAtLine(
|
|
4998
|
+
repoPath,
|
|
4999
|
+
inlineHealthRouteFile,
|
|
5000
|
+
"prod_check",
|
|
5001
|
+
"Health/readiness route",
|
|
5002
|
+
findFirstMatchingLineOutsideComments(
|
|
5003
|
+
repoPath,
|
|
5004
|
+
inlineHealthRouteFile,
|
|
5005
|
+
V20_HEALTH_OR_READINESS_ROUTE_PATTERN
|
|
5006
|
+
)
|
|
5007
|
+
)
|
|
5008
|
+
)
|
|
5009
|
+
);
|
|
4892
5010
|
}
|
|
4893
5011
|
if (errorBoundary) {
|
|
4894
5012
|
evidence.push(
|
|
@@ -4928,8 +5046,13 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
4928
5046
|
}
|
|
4929
5047
|
if (evidence.length === 0) return null;
|
|
4930
5048
|
if (useV20ExplicitGaps) {
|
|
4931
|
-
const
|
|
5049
|
+
const workspaceManifests = packageJson ? findDeclaredWorkspaceManifests(repoPath, repoFiles, packageJson) : NO_DECLARED_WORKSPACE_MANIFESTS;
|
|
5050
|
+
const hasBuildOrTypecheck = scripts.has("build") || scripts.has("typecheck") || workspaceManifests.declared > workspaceManifests.read.length || workspaceManifests.read.some((file) => {
|
|
5051
|
+
const workspaceScripts = readPackageScripts(join(repoPath, file));
|
|
5052
|
+
return workspaceScripts.has("build") || workspaceScripts.has("typecheck");
|
|
5053
|
+
});
|
|
4932
5054
|
if (packageJson && !hasBuildOrTypecheck) {
|
|
5055
|
+
const workspaceRead = workspaceManifests.read.length;
|
|
4933
5056
|
findings.push({
|
|
4934
5057
|
severity: "info",
|
|
4935
5058
|
summary: "A deployable service package manifest declares neither a build nor a typecheck script.",
|
|
@@ -4937,7 +5060,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
4937
5060
|
repoPath,
|
|
4938
5061
|
packageJson,
|
|
4939
5062
|
"prod_check",
|
|
4940
|
-
"Deployable service manifest without a build or typecheck script"
|
|
5063
|
+
workspaceRead > 0 ? `Deployable service manifest without a build or typecheck script (${workspaceRead} declared workspace package.json file${workspaceRead === 1 ? "" : "s"} read)` : "Deployable service manifest without a build or typecheck script"
|
|
4941
5064
|
)
|
|
4942
5065
|
});
|
|
4943
5066
|
} else if (runtimeContainer) {
|
|
@@ -5016,7 +5139,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
5016
5139
|
workflows.length > 0,
|
|
5017
5140
|
deployConfigs.length > 0,
|
|
5018
5141
|
envTemplate,
|
|
5019
|
-
|
|
5142
|
+
hasHealthOrReadinessSignal,
|
|
5020
5143
|
errorBoundary !== void 0
|
|
5021
5144
|
].filter(Boolean).length,
|
|
5022
5145
|
6,
|
|
@@ -5033,9 +5156,10 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
5033
5156
|
{ label: "CI workflow", count: workflows.length > 0 ? 1 : 0 },
|
|
5034
5157
|
{ label: "deployment configuration", count: deployConfigs.length > 0 ? 1 : 0 },
|
|
5035
5158
|
{ label: "environment template", count: envTemplate ? 1 : 0 },
|
|
5036
|
-
{ label: "health or readiness signal", count:
|
|
5159
|
+
{ label: "health or readiness signal", count: hasHealthOrReadinessSignal ? 1 : 0 },
|
|
5037
5160
|
{ label: "error boundary", count: errorBoundary !== void 0 ? 1 : 0 }
|
|
5038
|
-
]
|
|
5161
|
+
],
|
|
5162
|
+
...rootOnlyScriptsNote ? { condition: rootOnlyScriptsNote } : {}
|
|
5039
5163
|
}
|
|
5040
5164
|
),
|
|
5041
5165
|
metric(
|
|
@@ -5278,7 +5402,7 @@ function hasDeclaredVersionConstraint(dependency) {
|
|
|
5278
5402
|
if (/[~^<>=!]/.test(trimmed)) return true;
|
|
5279
5403
|
return /^v?\d/.test(trimmed);
|
|
5280
5404
|
}
|
|
5281
|
-
function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, useV23WithheldPathAbstention, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
5405
|
+
function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, useV23WithheldPathAbstention, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, useV26SettlingEvidence = false) {
|
|
5282
5406
|
const claimImplementationReads = claimImplementationFileReads(useV27Detectors);
|
|
5283
5407
|
abstainSignalOnWithheldPaths(
|
|
5284
5408
|
"A5",
|
|
@@ -5372,10 +5496,40 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, use
|
|
|
5372
5496
|
)
|
|
5373
5497
|
);
|
|
5374
5498
|
}
|
|
5499
|
+
const claimArtifactDeclarations = useV26SettlingEvidence && reconciliationArtifacts.length === 0 ? withContentReadSignal(
|
|
5500
|
+
"A5",
|
|
5501
|
+
"reconciliation_artifact_depth",
|
|
5502
|
+
() => scanClaimArtifactDeclarations(repoPath, repoFiles)
|
|
5503
|
+
) : void 0;
|
|
5504
|
+
const declaredAbsentArtifact = claimArtifactDeclarations?.firstAbsent;
|
|
5505
|
+
const reconciliationUnsettled = useV26SettlingEvidence && reconciliationArtifacts.length === 0 && declaredAbsentArtifact === void 0;
|
|
5506
|
+
const omitReconciliationArtifactDepth = claimArtifactDeclarations !== void 0 && !claimArtifactDeclarations.declared;
|
|
5375
5507
|
return {
|
|
5376
5508
|
criterionId: "A5",
|
|
5377
5509
|
positiveEvidence: evidence,
|
|
5378
|
-
findings: reconciliationArtifacts.length > 0 ? [] : [
|
|
5510
|
+
findings: reconciliationArtifacts.length > 0 ? [] : useV26SettlingEvidence ? declaredAbsentArtifact === void 0 ? [] : [
|
|
5511
|
+
inventoryAbsenceFinding({
|
|
5512
|
+
// Same severity rule as the finding it replaces: documented negative space is
|
|
5513
|
+
// itself claim-reality discipline.
|
|
5514
|
+
severity: negativeSpaceDoc ? "info" : "warning",
|
|
5515
|
+
summaries: {
|
|
5516
|
+
legacy: `This repository declares a claim-reconciliation artifact at \`${declaredAbsentArtifact.path}\`, and that path is not present in the tracked scan-eligible inventory.`,
|
|
5517
|
+
currentTracked: `This repository declares a claim-reconciliation artifact at \`${declaredAbsentArtifact.path}\`, and that path is not present in the tracked scan-eligible inventory.`,
|
|
5518
|
+
currentDirectoryWalk: `This repository declares a claim-reconciliation artifact at \`${declaredAbsentArtifact.path}\`, and that path is not present in the bounded directory walk.`
|
|
5519
|
+
},
|
|
5520
|
+
evidence: evidenceForRelativeAtLine(
|
|
5521
|
+
repoPath,
|
|
5522
|
+
declaredAbsentArtifact.declaringFile,
|
|
5523
|
+
"claim_reconciliation",
|
|
5524
|
+
"Declares a claim-reconciliation artifact that is absent",
|
|
5525
|
+
declaredAbsentArtifact.line
|
|
5526
|
+
),
|
|
5527
|
+
context: inventoryAbsenceContext,
|
|
5528
|
+
inventoryCount: repoFiles.length,
|
|
5529
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a5DeclaredClaimArtifactPath,
|
|
5530
|
+
matchCount: 0
|
|
5531
|
+
})
|
|
5532
|
+
] : [
|
|
5379
5533
|
inventoryAbsenceFinding({
|
|
5380
5534
|
// Proxy-only A5: cap at warning (never critical) — a static proxy is lower-confidence
|
|
5381
5535
|
// than a curated registry or reconciliation artifact. Downgraded to info when the repo
|
|
@@ -5422,16 +5576,19 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, use
|
|
|
5422
5576
|
"Credits multiple claim-bearing documents without judging unverified prose as truth.",
|
|
5423
5577
|
"saturating_count"
|
|
5424
5578
|
),
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5579
|
+
...omitReconciliationArtifactDepth ? [] : [
|
|
5580
|
+
metric(
|
|
5581
|
+
"reconciliation_artifact_depth",
|
|
5582
|
+
"Reconciliation artifact depth",
|
|
5583
|
+
reconciliationArtifacts.length,
|
|
5584
|
+
3,
|
|
5585
|
+
0.15,
|
|
5586
|
+
"artifacts",
|
|
5587
|
+
"Requires a specific claim-reality artifact to reach full depth."
|
|
5588
|
+
)
|
|
5589
|
+
]
|
|
5590
|
+
],
|
|
5591
|
+
...reconciliationUnsettled ? { notes: WITAN_A5_RECONCILIATION_UNSETTLED_DISCLOSURE } : {}
|
|
5435
5592
|
};
|
|
5436
5593
|
}
|
|
5437
5594
|
function collectB2PrTraceEvidence(repoPath, repoFiles) {
|
|
@@ -5442,8 +5599,16 @@ function collectB2PrTraceEvidence(repoPath, repoFiles) {
|
|
|
5442
5599
|
const branchProtectionDoc = repoFiles.find(
|
|
5443
5600
|
(file) => /branch.*protection|review.*gate/i.test(file) || GITHUB_CODEOWNERS_PATH_PATTERN.test(file)
|
|
5444
5601
|
);
|
|
5602
|
+
const citedWorkflows = workflows.slice(0, 3);
|
|
5603
|
+
const workflowLabel = ciWorkflowCitationLabel(
|
|
5604
|
+
"Pull-request CI workflow",
|
|
5605
|
+
"counted",
|
|
5606
|
+
workflows.length,
|
|
5607
|
+
citedWorkflows.length,
|
|
5608
|
+
unreadCiWorkflowReasons(() => true)
|
|
5609
|
+
);
|
|
5445
5610
|
const evidence = [
|
|
5446
|
-
...
|
|
5611
|
+
...citedWorkflows.map((file) => evidenceForRelative(repoPath, file, "ci_run", workflowLabel)),
|
|
5447
5612
|
...prTemplate ? [evidenceForRelative(repoPath, prTemplate, "pull_request", "Pull request template")] : [],
|
|
5448
5613
|
...branchProtectionDoc ? [
|
|
5449
5614
|
evidenceForRelative(
|
|
@@ -5516,11 +5681,35 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
5516
5681
|
if (packageJson && scripts.has("lint")) {
|
|
5517
5682
|
evidence.push(evidenceForRelative(repoPath, packageJson, "ci_run", "Lint script"));
|
|
5518
5683
|
}
|
|
5684
|
+
const unreadWorkflows = unreadCiWorkflowReasons(() => true);
|
|
5685
|
+
const workflowsRead = workflows.length + rootWorkflows.length;
|
|
5686
|
+
const delegatedSteps = withContentReadSignal(
|
|
5687
|
+
"B3",
|
|
5688
|
+
"default_branch_ci_depth",
|
|
5689
|
+
() => sumDelegatedCiSteps(
|
|
5690
|
+
countDelegatedCiSteps(repoPath, workflows),
|
|
5691
|
+
mono ? countDelegatedCiSteps(mono.root, rootWorkflows) : NO_DELEGATED_CI_STEPS
|
|
5692
|
+
)
|
|
5693
|
+
);
|
|
5519
5694
|
if (workflow) {
|
|
5520
5695
|
withContentReadSignals(
|
|
5521
5696
|
"B3",
|
|
5522
5697
|
["ci_script_depth", "default_branch_ci_depth"],
|
|
5523
|
-
() => evidence.push(
|
|
5698
|
+
() => evidence.push(
|
|
5699
|
+
evidenceForRelative(
|
|
5700
|
+
repoPath,
|
|
5701
|
+
workflow,
|
|
5702
|
+
"ci_run",
|
|
5703
|
+
ciWorkflowCitationLabel(
|
|
5704
|
+
"CI workflow",
|
|
5705
|
+
"read",
|
|
5706
|
+
workflowsRead,
|
|
5707
|
+
1,
|
|
5708
|
+
unreadWorkflows,
|
|
5709
|
+
delegatedSteps
|
|
5710
|
+
)
|
|
5711
|
+
)
|
|
5712
|
+
)
|
|
5524
5713
|
);
|
|
5525
5714
|
} else if (rootWorkflows[0] && mono) {
|
|
5526
5715
|
const rootWorkflow = rootWorkflows[0];
|
|
@@ -5528,7 +5717,19 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
5528
5717
|
"B3",
|
|
5529
5718
|
["ci_script_depth", "default_branch_ci_depth"],
|
|
5530
5719
|
() => evidence.push(
|
|
5531
|
-
evidenceForRelative(
|
|
5720
|
+
evidenceForRelative(
|
|
5721
|
+
mono.root,
|
|
5722
|
+
rootWorkflow,
|
|
5723
|
+
"ci_run",
|
|
5724
|
+
ciWorkflowCitationLabel(
|
|
5725
|
+
"CI workflow (monorepo root)",
|
|
5726
|
+
"read",
|
|
5727
|
+
workflowsRead,
|
|
5728
|
+
1,
|
|
5729
|
+
unreadWorkflows,
|
|
5730
|
+
delegatedSteps
|
|
5731
|
+
)
|
|
5732
|
+
)
|
|
5532
5733
|
)
|
|
5533
5734
|
);
|
|
5534
5735
|
}
|
|
@@ -5540,9 +5741,16 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
5540
5741
|
CI_TYPECHECK_COMMAND_PATTERN,
|
|
5541
5742
|
CI_BUILD_COMMAND_PATTERN
|
|
5542
5743
|
];
|
|
5543
|
-
const
|
|
5544
|
-
|
|
5545
|
-
|
|
5744
|
+
const workspaceManifests = findDeclaredWorkspaceManifests(repoPath, repoFiles, packageJson);
|
|
5745
|
+
const scriptSources = [
|
|
5746
|
+
scripts,
|
|
5747
|
+
...workspaceManifests.read.map((file) => readPackageScripts(join(repoPath, file)))
|
|
5748
|
+
];
|
|
5749
|
+
const declaresScript = (name) => scriptSources.some(
|
|
5750
|
+
(source) => name === "test" ? hasCredibleTestScript(source) : source.has(name)
|
|
5751
|
+
);
|
|
5752
|
+
const rootOnlyScriptsNote = workspaceManifests.read.length > 0 ? workspacePackageScriptsDisclosure(repoFiles, packageJson, workspaceManifests) : rootOnlyPackageScriptsDisclosure(repoFiles, packageJson);
|
|
5753
|
+
const scriptDepth = ["test", "lint", "typecheck", "build"].filter(declaresScript).length;
|
|
5546
5754
|
const { repoCategoryHits, rootCategoryHits } = withContentReadSignal(
|
|
5547
5755
|
"B3",
|
|
5548
5756
|
"ci_script_depth",
|
|
@@ -5575,7 +5783,7 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
5575
5783
|
components: [
|
|
5576
5784
|
...["test", "lint", "typecheck", "build"].map((name) => ({
|
|
5577
5785
|
label: `package script: ${name === "typecheck" ? "type-check" : name}`,
|
|
5578
|
-
count: (name
|
|
5786
|
+
count: declaresScript(name) ? 1 : 0
|
|
5579
5787
|
})),
|
|
5580
5788
|
...CI_COMMAND_CATEGORIES.map((_pattern, index) => ({
|
|
5581
5789
|
label: `repository CI command: ${["test", "lint", "type-check", "build"][index]}`,
|
|
@@ -5585,7 +5793,8 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
5585
5793
|
label: `monorepo-root CI command: ${["test", "lint", "type-check", "build"][index]}`,
|
|
5586
5794
|
count: rootCategoryHits[index] ? 1 : 0
|
|
5587
5795
|
})) : []
|
|
5588
|
-
]
|
|
5796
|
+
],
|
|
5797
|
+
...rootOnlyScriptsNote ? { condition: rootOnlyScriptsNote } : {}
|
|
5589
5798
|
}
|
|
5590
5799
|
),
|
|
5591
5800
|
metric(
|
|
@@ -6018,7 +6227,7 @@ var HEAVY_TEST_DEPENDENCY_NAME_PATTERN = /^(?:(?:jest|mocha|ava|tape|jasmine|kar
|
|
|
6018
6227
|
var TIMING_SAFE_COMPARE_PATTERN = /\btiming[-_]?safe[-_]?(?:equal|compare)\b|crypto\.timingSafeEqual\s*\(/i;
|
|
6019
6228
|
var CANONICAL_SERIALIZE_PATTERN = /\bcanonicalize\s*\(|\bcanonicalJson\b|\bstableStringify\s*\(|json-stable-stringify|safe-stable-stringify|\bsortKeys\s*\(/i;
|
|
6020
6229
|
var SIGN_OR_HMAC_CALL_PATTERN = /\bcreateHmac\s*\(|\bcreateSign\s*\(|\.sign\s*\(/;
|
|
6021
|
-
var INSECURE_SECRET_COMPARE_LINE_PATTERN = /\b\w*(?:hmac|signature|\bmac
|
|
6230
|
+
var INSECURE_SECRET_COMPARE_LINE_PATTERN = /\b\w*(?:hmac|signature|\bmac)\w*\s*(===|!==)\s*(?!['"`]|undefined\b|null\b)([\w.]+(?:\s*[-+]\s*\d+)?)/i;
|
|
6022
6231
|
var DEFAULT_SECRET_SENTINEL_COMPARE_LINE_PATTERN = /\b(?:secret|token|password|apiKey)\w*\s*(?:===|!==|==|!=)\s*[\w.]*default(?:Secret|Token|Password|ApiKey)\b/i;
|
|
6023
6232
|
var NUMERIC_SHAPED_OPERAND_PATTERN = /^-?\d+$|\.(?:length|len|size|count)\b|(?:length|index|idx|offset|count|size|len)$/i;
|
|
6024
6233
|
function lineHasInsecureSecretCompare(line, useV39Detectors = false) {
|
|
@@ -6031,9 +6240,124 @@ function lineHasInsecureSecretCompare(line, useV39Detectors = false) {
|
|
|
6031
6240
|
function findInsecureSecretCompareLine(repoPath, file, useV39Detectors = false) {
|
|
6032
6241
|
const fullPath = join(repoPath, file);
|
|
6033
6242
|
if (!isRegularFile(fullPath)) return null;
|
|
6034
|
-
const
|
|
6243
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
6244
|
+
const lines = contents.split("\n");
|
|
6035
6245
|
const index = lines.findIndex((line) => lineHasInsecureSecretCompare(line, useV39Detectors));
|
|
6036
|
-
|
|
6246
|
+
const keyedDigestLine = findKeyedHmacDigestCompareLine(file, contents);
|
|
6247
|
+
if (keyedDigestLine === null) return index === -1 ? null : index + 1;
|
|
6248
|
+
return index === -1 ? keyedDigestLine : Math.min(index + 1, keyedDigestLine);
|
|
6249
|
+
}
|
|
6250
|
+
var KEYED_HMAC_GRAMMARS = [
|
|
6251
|
+
[
|
|
6252
|
+
/\.(?:ts|tsx|js|jsx)$/,
|
|
6253
|
+
{
|
|
6254
|
+
keyed: /\bcreateHmac\s*\(|\bsubtle\s*\.\s*sign\s*\(\s*(?:['"]HMAC['"]|\{\s*name\s*:\s*['"]HMAC['"])/,
|
|
6255
|
+
produces: /\.\s*digest\s*\(|\bsubtle\s*\.\s*sign\s*\(/,
|
|
6256
|
+
objectDigestCall: String.raw`\.\s*digest\s*\(`,
|
|
6257
|
+
equality: "===|!==",
|
|
6258
|
+
hashComments: false
|
|
6259
|
+
}
|
|
6260
|
+
],
|
|
6261
|
+
[
|
|
6262
|
+
/\.py$/,
|
|
6263
|
+
{
|
|
6264
|
+
keyed: /\bhmac\s*\.\s*(?:new|digest)\s*\(/,
|
|
6265
|
+
produces: /\.\s*(?:hex)?digest\s*\(/,
|
|
6266
|
+
objectDigestCall: String.raw`\.\s*(?:hex)?digest\s*\(`,
|
|
6267
|
+
equality: "==|!=",
|
|
6268
|
+
hashComments: true
|
|
6269
|
+
}
|
|
6270
|
+
],
|
|
6271
|
+
[
|
|
6272
|
+
/\.go$/,
|
|
6273
|
+
{
|
|
6274
|
+
keyed: /\bhmac\s*\.\s*New\s*\(/,
|
|
6275
|
+
produces: /\.\s*Sum\s*\(/,
|
|
6276
|
+
objectDigestCall: String.raw`\.\s*Sum\s*\(`,
|
|
6277
|
+
equality: "==|!=",
|
|
6278
|
+
hashComments: false
|
|
6279
|
+
}
|
|
6280
|
+
],
|
|
6281
|
+
[
|
|
6282
|
+
/\.rb$/,
|
|
6283
|
+
{
|
|
6284
|
+
keyed: /\bOpenSSL\s*::\s*HMAC\s*\.\s*(?:new|(?:hex|base64)?digest)\b/,
|
|
6285
|
+
produces: /\.\s*(?:hex|base64)?digest\b/,
|
|
6286
|
+
objectDigestCall: String.raw`\.\s*(?:hex|base64)?digest\b`,
|
|
6287
|
+
equality: "==|!=",
|
|
6288
|
+
hashComments: true
|
|
6289
|
+
}
|
|
6290
|
+
]
|
|
6291
|
+
];
|
|
6292
|
+
var VARIABLE_ASSIGNMENT_PATTERN = /(?<![\w$.])([A-Za-z_$][\w$]*)(?:\s*:\s*[A-Za-z_$][\w$.<>[\]]*)?\s*:?=(?![=>])/g;
|
|
6293
|
+
var ASSIGNED_EXPRESSION_MAX_CHARS = 512;
|
|
6294
|
+
function assignedExpressionEnd(code, start) {
|
|
6295
|
+
const limit = Math.min(code.length, start + ASSIGNED_EXPRESSION_MAX_CHARS);
|
|
6296
|
+
let depth = 0;
|
|
6297
|
+
for (let i = start; i < limit; i += 1) {
|
|
6298
|
+
const ch = code[i];
|
|
6299
|
+
if (ch === "(" || ch === "[" || ch === "{") depth += 1;
|
|
6300
|
+
else if (ch === ")" || ch === "]" || ch === "}") {
|
|
6301
|
+
if (depth === 0) return i;
|
|
6302
|
+
depth -= 1;
|
|
6303
|
+
} else if (depth === 0 && (ch === ";" || ch === ",")) return i;
|
|
6304
|
+
else if (depth === 0 && ch === "\n" && !/^\s*\./.test(code.slice(i + 1, i + 65))) return i;
|
|
6305
|
+
}
|
|
6306
|
+
return limit;
|
|
6307
|
+
}
|
|
6308
|
+
var escapeIdentifier = (identifier) => identifier.replace(/\$/g, "\\$");
|
|
6309
|
+
function findKeyedHmacDigestCompareLine(file, contents) {
|
|
6310
|
+
const grammar = KEYED_HMAC_GRAMMARS.find(([extension]) => extension.test(file))?.[1];
|
|
6311
|
+
if (!grammar || !/digest/i.test(contents) || !grammar.produces.test(contents)) return null;
|
|
6312
|
+
const { commentFree, code } = maskCommentsAndStrings(contents, grammar.hashComments);
|
|
6313
|
+
const startsInCode = (pattern, from, to) => {
|
|
6314
|
+
for (const match of commentFree.slice(from, to).matchAll(new RegExp(pattern.source, "g"))) {
|
|
6315
|
+
if (code[from + match.index] === commentFree[from + match.index]) return true;
|
|
6316
|
+
}
|
|
6317
|
+
return false;
|
|
6318
|
+
};
|
|
6319
|
+
const boundAt = /* @__PURE__ */ new Map();
|
|
6320
|
+
const hmacValues = [];
|
|
6321
|
+
const hmacObjects = [];
|
|
6322
|
+
for (const assignment of code.matchAll(VARIABLE_ASSIGNMENT_PATTERN)) {
|
|
6323
|
+
const name = assignment[1];
|
|
6324
|
+
const start = assignment.index + assignment[0].length;
|
|
6325
|
+
const end = assignedExpressionEnd(code, start);
|
|
6326
|
+
const keyed = startsInCode(grammar.keyed, start, end);
|
|
6327
|
+
let bound = false;
|
|
6328
|
+
if (keyed && startsInCode(grammar.produces, start, end)) {
|
|
6329
|
+
hmacValues.push(name);
|
|
6330
|
+
bound = true;
|
|
6331
|
+
} else if (keyed) {
|
|
6332
|
+
hmacObjects.push(name);
|
|
6333
|
+
} else {
|
|
6334
|
+
bound = hmacValues.some(
|
|
6335
|
+
(value) => startsInCode(new RegExp(`(?<![\\w$.])${escapeIdentifier(value)}(?![\\w$])`), start, end)
|
|
6336
|
+
) || hmacObjects.some(
|
|
6337
|
+
(object) => startsInCode(
|
|
6338
|
+
new RegExp(`(?<![\\w$.])${escapeIdentifier(object)}\\s*${grammar.objectDigestCall}`),
|
|
6339
|
+
start,
|
|
6340
|
+
end
|
|
6341
|
+
)
|
|
6342
|
+
);
|
|
6343
|
+
}
|
|
6344
|
+
if (bound && !boundAt.has(name)) boundAt.set(name, assignment.index);
|
|
6345
|
+
}
|
|
6346
|
+
if (boundAt.size === 0) return null;
|
|
6347
|
+
const comparison = new RegExp(
|
|
6348
|
+
`(?<![\\w$.])([A-Za-z_$][\\w$]*)\\s*(?:${grammar.equality})(?!=)\\s*(?!['"\`]|undefined\\b|null\\b|None\\b|nil\\b)([\\w.]+(?:\\s*[-+]\\s*\\d+)?)`,
|
|
6349
|
+
"g"
|
|
6350
|
+
);
|
|
6351
|
+
for (const match of commentFree.matchAll(comparison)) {
|
|
6352
|
+
const at = match.index;
|
|
6353
|
+
const name = match[1];
|
|
6354
|
+
if (code[at] !== commentFree[at] || !/digest/i.test(name)) continue;
|
|
6355
|
+
const assignedAt = boundAt.get(name);
|
|
6356
|
+
if (assignedAt === void 0 || assignedAt > at) continue;
|
|
6357
|
+
if (NUMERIC_SHAPED_OPERAND_PATTERN.test(match[2].trim())) continue;
|
|
6358
|
+
return contents.slice(0, at).split("\n").length;
|
|
6359
|
+
}
|
|
6360
|
+
return null;
|
|
6037
6361
|
}
|
|
6038
6362
|
function findFirstMatchingLine(repoPath, file, pattern) {
|
|
6039
6363
|
const fullPath = join(repoPath, file);
|
|
@@ -6412,6 +6736,166 @@ var JS_TEST_ASSERTION_PATTERN = /(expect\(|assert\.|should\.|toEqual\(|toBe\(|as
|
|
|
6412
6736
|
var CPP_TEST_ASSERTION_PATTERN = /\bEXPECT_|ASSERT_EQ\b|ASSERT_TRUE\b|ASSERT_FALSE\b|\bREQUIRE\s*\(|\bCHECK\s*\(|\bTEST\s*\(|\bTEST_F\s*\(|\bTEST_CASE\s*\(|\bSECTION\s*\(/;
|
|
6413
6737
|
var MULTI_ECOSYSTEM_TEST_ASSERTION_PATTERN = /\.assert[A-Z_]\w*\s*\(|\bassert[A-Z]\w*\s*\(|\bpytest\.raises\b|\bself\.fail\s*\(|\bt\.(?:Error|Errorf|Fatal|Fatalf|Fail|FailNow)\b|\brequire\.[A-Z]\w*\s*\(|\b(?:debug_)?assert(?:_eq|_ne)?!\s*\(|\bassert_\w+\b|\$this->assert\w+\s*\(/;
|
|
6414
6738
|
var SUITE_LEVEL_SKIP_PATTERN = /\b(?:describe|suite|context)\.skip\s*\(/;
|
|
6739
|
+
function hasLiveAvaAssertion(contents) {
|
|
6740
|
+
const uncommented = stripJavaScriptComments(contents);
|
|
6741
|
+
const imports = [...uncommented.matchAll(/\bimport\s+([A-Za-z_$][\w$]*)\s+from\s*['"]ava['"]|\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*require\s*\(\s*['"]ava['"]\s*\)/g)];
|
|
6742
|
+
if (!imports.length) return false;
|
|
6743
|
+
let quote = "";
|
|
6744
|
+
let escaped = false;
|
|
6745
|
+
const characters = uncommented.split("");
|
|
6746
|
+
for (let i = 0; i < characters.length; i += 1) {
|
|
6747
|
+
const ch = characters[i];
|
|
6748
|
+
if (quote) {
|
|
6749
|
+
characters[i] = ch === "\n" ? "\n" : " ";
|
|
6750
|
+
if (escaped) escaped = false;
|
|
6751
|
+
else if (ch === "\\") escaped = true;
|
|
6752
|
+
else if (ch === quote) quote = "";
|
|
6753
|
+
} else if (ch === "'" || ch === '"' || ch === "`") {
|
|
6754
|
+
quote = ch;
|
|
6755
|
+
characters[i] = " ";
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
const code = characters.join("");
|
|
6759
|
+
const imported = imports.find((candidate) => code[candidate.index] === uncommented[candidate.index]);
|
|
6760
|
+
const runner = imported?.[1] ?? imported?.[2];
|
|
6761
|
+
if (!runner) return false;
|
|
6762
|
+
const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6763
|
+
const calls = new RegExp(`\\b${escape(runner)}((?:\\.\\w+)*)\\s*\\(`, "g");
|
|
6764
|
+
const closeAt = (open, left, right) => {
|
|
6765
|
+
let depth = 1;
|
|
6766
|
+
for (let i = open + 1; i < code.length; i += 1) {
|
|
6767
|
+
if (code[i] === left) depth += 1;
|
|
6768
|
+
else if (code[i] === right && --depth === 0) return i;
|
|
6769
|
+
}
|
|
6770
|
+
return -1;
|
|
6771
|
+
};
|
|
6772
|
+
for (const call of code.matchAll(calls)) {
|
|
6773
|
+
const modifiers = call[1].split(".").filter(Boolean);
|
|
6774
|
+
if (modifiers.some((modifier) => !["serial", "only", "failing"].includes(modifier))) continue;
|
|
6775
|
+
const open = call.index + call[0].length - 1;
|
|
6776
|
+
const close = closeAt(open, "(", ")");
|
|
6777
|
+
if (close < 0) continue;
|
|
6778
|
+
const args = code.slice(open + 1, close);
|
|
6779
|
+
const callback = /(?:\basync\s+)?(?:\(\s*([A-Za-z_$][\w$]*)\s*\)|(?<![\w$])([A-Za-z_$][\w$]*))\s*=>|\bfunction(?:\s+\w+)?\s*\(\s*([A-Za-z_$][\w$]*)\s*\)/.exec(args);
|
|
6780
|
+
const parameter = callback?.[1] ?? callback?.[2] ?? callback?.[3];
|
|
6781
|
+
if (!callback || !parameter) continue;
|
|
6782
|
+
let start = open + 1 + callback.index + callback[0].length;
|
|
6783
|
+
while (/\s/.test(code[start] ?? "") && start < close) start += 1;
|
|
6784
|
+
const end = code[start] === "{" ? closeAt(start, "{", "}") : close;
|
|
6785
|
+
if (end < 0 || end > close) continue;
|
|
6786
|
+
const assertion = new RegExp(`\\b${escape(parameter)}\\.(?:assert|pass|fail|truthy|falsy|true|false|is|not|deepEqual|notDeepEqual|throws|throwsAsync|notThrows|notThrowsAsync|regex|notRegex|snapshot|like)\\s*\\(`);
|
|
6787
|
+
const body = code.slice(start, end);
|
|
6788
|
+
const active = body.split("");
|
|
6789
|
+
let maskedThrough = -1;
|
|
6790
|
+
for (const nested of body.matchAll(/=>\s*|\bfunction(?:\s+\w+)?\s*\([^)]*\)\s*/g)) {
|
|
6791
|
+
if (nested.index < maskedThrough) continue;
|
|
6792
|
+
const begin = nested.index + nested[0].length;
|
|
6793
|
+
let stop = begin;
|
|
6794
|
+
if (body[begin] === "{") {
|
|
6795
|
+
stop = closeAt(start + begin, "{", "}") - start + 1;
|
|
6796
|
+
} else {
|
|
6797
|
+
let depth = 0;
|
|
6798
|
+
while (stop < body.length) {
|
|
6799
|
+
const ch = body[stop];
|
|
6800
|
+
if (depth === 0 && /[;,)]/.test(ch)) break;
|
|
6801
|
+
if (ch === "(" || ch === "[") depth += 1;
|
|
6802
|
+
else if (ch === ")" || ch === "]") depth -= 1;
|
|
6803
|
+
stop += 1;
|
|
6804
|
+
}
|
|
6805
|
+
}
|
|
6806
|
+
if (stop <= begin) continue;
|
|
6807
|
+
for (let i = nested.index; i < Math.min(stop, active.length); i += 1) active[i] = " ";
|
|
6808
|
+
maskedThrough = stop;
|
|
6809
|
+
}
|
|
6810
|
+
if (assertion.test(active.join(""))) return true;
|
|
6811
|
+
}
|
|
6812
|
+
return false;
|
|
6813
|
+
}
|
|
6814
|
+
var DOTNET_ASSERTION_PATTERN = /\b(?:Classic|Collection|String)?Assert\.[A-Z]\w*\s*(?:<[^<>()]*>\s*)?\(/;
|
|
6815
|
+
var XCTEST_ASSERTION_PATTERN = /\bXCT(?:Assert\w*|Fail|Unwrap)\s*\(/;
|
|
6816
|
+
var SCALATEST_IMPORT_PATTERN = /\bimport\s+org\.scalatest\b/;
|
|
6817
|
+
var SCALATEST_ASSERTION_PATTERN = /\b(?:assert|assertResult)\s*\(|\b(?:assertThrows|intercept)\s*\[/;
|
|
6818
|
+
var SCALATEST_MATCHERS_IMPORT_PATTERN = /\bimport\s+org\.scalatest\.(?:matchers\b|(?:Should|Must)?Matchers\b|_|\*|\{[^}]*\b(?:Should|Must)?Matchers\b)/;
|
|
6819
|
+
var SCALATEST_MATCHER_PATTERN = /\b(?:should|must)(?:Be|Equal)\b|\b(?:should|must)\s+(?:not\s+)?(?:be|equal|have|contain|include|startWith|endWith|matchPattern)\b|\b(?:should|must)\s*===/;
|
|
6820
|
+
var KOTEST_MATCHERS_IMPORT_PATTERN = /\bimport\s+io\.kotest\.(?:matchers|assertions)\b/;
|
|
6821
|
+
var KOTEST_MATCHER_PATTERN = /\bshould[A-Z]\w*\b/;
|
|
6822
|
+
var TAPE_OR_TAP_BINDING_PATTERN = /\bfrom\s*['"]tape?['"]|\brequire\s*\(\s*['"]tape?['"]\s*\)/g;
|
|
6823
|
+
var TAPE_OR_TAP_ASSERTION_PATTERN = /\bt\.(?:ok|notOk|notok|true|false|assert|error|ifError|ifErr|iferror|equal|equals|isEqual|strictEqual|strictEquals|is|notEqual|notEquals|isNotEqual|doesNotEqual|isInequal|notStrictEqual|notStrictEquals|isNot|not|looseEqual|looseEquals|notLooseEqual|notLooseEquals|deepEqual|deepEquals|isEquivalent|same|notDeepEqual|notDeepEquals|notEquivalent|notDeeply|notSame|deepLooseEqual|notDeepLooseEqual|strictSame|strictNotSame|has|notHas|hasStrict|notHasStrict|match|notMatch|doesNotMatch|matchOnly|notMatchOnly|matchStrict|notMatchStrict|matchOnlyStrict|notMatchOnlyStrict|type|throws|doesNotThrow|rejects|resolves|resolveMatch|emits|pass|fail)\s*\(/;
|
|
6824
|
+
var DOTNET_IGNORED_CLASS_PATTERN = /\[[^[\]]*\b(?:NUnit\.Framework\.|Microsoft\.VisualStudio\.TestTools\.UnitTesting\.)?Ignore(?:Attribute)?\b[^[\]]*\](?:\s*\[[^[\]]*\])*\s*(?:(?:public|internal|private|protected|sealed|abstract|static|partial|file)\s+)*class\b/;
|
|
6825
|
+
var SCALATEST_IGNORED_CLASS_PATTERN = /@(?:org\.scalatest\.)?Ignore\b(?:\s*@[\w.]+(?:\([^)]*\))?)*\s*(?:(?:final|abstract|sealed|private|protected)\s+)*class\b/;
|
|
6826
|
+
var KOTEST_IMPORT_PATTERN = /\bimport\s+io\.kotest\b/;
|
|
6827
|
+
var KOTEST_IGNORED_CLASS_PATTERN = /@(?:io\.kotest\.core\.annotation\.)?Ignored\b(?:\s*@[\w.]+(?:\([^)]*\))?)*\s*(?:(?:public|internal|private|open|abstract)\s+)*class\b/;
|
|
6828
|
+
var SUITE_SKIP_NAME_SHAPED_TEST_FILE_PATTERN = /Tests?\.cs$|(?:Spec|Test|Suite)\.scala$/;
|
|
6829
|
+
function maskCommentsAndStrings(contents, hashComments = false) {
|
|
6830
|
+
const commentFree = contents.split("");
|
|
6831
|
+
const code = contents.split("");
|
|
6832
|
+
const blank = (target, from, to) => {
|
|
6833
|
+
for (let i2 = from; i2 < to; i2 += 1) if (target[i2] !== "\n") target[i2] = " ";
|
|
6834
|
+
};
|
|
6835
|
+
let i = 0;
|
|
6836
|
+
while (i < contents.length) {
|
|
6837
|
+
const ch = contents[i];
|
|
6838
|
+
const next = contents[i + 1];
|
|
6839
|
+
let end = i + 1;
|
|
6840
|
+
if (hashComments ? ch === "#" : ch === "/" && next === "/") {
|
|
6841
|
+
end = contents.indexOf("\n", i);
|
|
6842
|
+
if (end < 0) end = contents.length;
|
|
6843
|
+
blank(commentFree, i, end);
|
|
6844
|
+
blank(code, i, end);
|
|
6845
|
+
} else if (!hashComments && ch === "/" && next === "*") {
|
|
6846
|
+
let depth = 1;
|
|
6847
|
+
end = i + 2;
|
|
6848
|
+
while (end < contents.length && depth > 0) {
|
|
6849
|
+
if (contents.startsWith("/*", end)) {
|
|
6850
|
+
depth += 1;
|
|
6851
|
+
end += 2;
|
|
6852
|
+
} else if (contents.startsWith("*/", end)) {
|
|
6853
|
+
depth -= 1;
|
|
6854
|
+
end += 2;
|
|
6855
|
+
} else end += 1;
|
|
6856
|
+
}
|
|
6857
|
+
blank(commentFree, i, end);
|
|
6858
|
+
blank(code, i, end);
|
|
6859
|
+
} else if (contents.startsWith('"""', i) || hashComments && contents.startsWith("'''", i)) {
|
|
6860
|
+
const close = contents.indexOf(contents.slice(i, i + 3), i + 3);
|
|
6861
|
+
end = close < 0 ? contents.length : close + 3;
|
|
6862
|
+
blank(code, i, end);
|
|
6863
|
+
} else if (ch === '"' || ch === "'" || ch === "`") {
|
|
6864
|
+
const verbatim = ch === '"' && contents[i - 1] === "@";
|
|
6865
|
+
end = i + 1;
|
|
6866
|
+
while (end < contents.length) {
|
|
6867
|
+
const c = contents[end];
|
|
6868
|
+
if (verbatim && c === '"' && contents[end + 1] === '"') {
|
|
6869
|
+
end += 2;
|
|
6870
|
+
continue;
|
|
6871
|
+
}
|
|
6872
|
+
if (!verbatim && c === "\\") {
|
|
6873
|
+
end += 2;
|
|
6874
|
+
continue;
|
|
6875
|
+
}
|
|
6876
|
+
end += 1;
|
|
6877
|
+
if (c === ch) break;
|
|
6878
|
+
}
|
|
6879
|
+
blank(code, i, end);
|
|
6880
|
+
}
|
|
6881
|
+
i = Math.max(end, i + 1);
|
|
6882
|
+
}
|
|
6883
|
+
return { commentFree: commentFree.join(""), code: code.join("") };
|
|
6884
|
+
}
|
|
6885
|
+
function hasLiveFrameworkAssertion({ commentFree, code }) {
|
|
6886
|
+
const body = code.replace(/^[ \t]*import\b.*$/gm, (line) => " ".repeat(line.length));
|
|
6887
|
+
if (DOTNET_ASSERTION_PATTERN.test(body) || XCTEST_ASSERTION_PATTERN.test(body)) return true;
|
|
6888
|
+
if (SCALATEST_IMPORT_PATTERN.test(code) && SCALATEST_ASSERTION_PATTERN.test(body)) return true;
|
|
6889
|
+
if (SCALATEST_MATCHERS_IMPORT_PATTERN.test(code) && SCALATEST_MATCHER_PATTERN.test(body)) return true;
|
|
6890
|
+
if (KOTEST_MATCHERS_IMPORT_PATTERN.test(code) && KOTEST_MATCHER_PATTERN.test(body)) return true;
|
|
6891
|
+
const tapeBound = [...commentFree.matchAll(TAPE_OR_TAP_BINDING_PATTERN)].some(
|
|
6892
|
+
(binding) => code[binding.index] === commentFree[binding.index]
|
|
6893
|
+
);
|
|
6894
|
+
return tapeBound && TAPE_OR_TAP_ASSERTION_PATTERN.test(body);
|
|
6895
|
+
}
|
|
6896
|
+
function hasFrameworkSuiteLevelSkip({ code }) {
|
|
6897
|
+
return DOTNET_IGNORED_CLASS_PATTERN.test(code) || SCALATEST_IMPORT_PATTERN.test(code) && SCALATEST_IGNORED_CLASS_PATTERN.test(code) || KOTEST_IMPORT_PATTERN.test(code) && KOTEST_IGNORED_CLASS_PATTERN.test(code);
|
|
6898
|
+
}
|
|
6415
6899
|
function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
|
|
6416
6900
|
let nonHollowCount = 0;
|
|
6417
6901
|
let ratedCount = 0;
|
|
@@ -6419,11 +6903,14 @@ function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
|
|
|
6419
6903
|
const fullPath = join(repoPath, file);
|
|
6420
6904
|
if (!isRegularFile(fullPath)) continue;
|
|
6421
6905
|
const contents = readRepoText(fullPath, "utf8");
|
|
6422
|
-
|
|
6906
|
+
let maskedSource;
|
|
6907
|
+
const masked = () => maskedSource ??= maskCommentsAndStrings(contents);
|
|
6908
|
+
const frameworkSuiteSkipped = /\bIgnored?\b/.test(contents) && hasFrameworkSuiteLevelSkip(masked());
|
|
6909
|
+
const nonHollow = (JS_TEST_ASSERTION_PATTERN.test(contents) || CPP_TEST_ASSERTION_PATTERN.test(contents) || MULTI_ECOSYSTEM_TEST_ASSERTION_PATTERN.test(contents) || hasLiveAvaAssertion(contents) || hasLiveFrameworkAssertion(masked())) && !SUITE_LEVEL_SKIP_PATTERN.test(contents) && !frameworkSuiteSkipped;
|
|
6423
6910
|
if (nonHollow) {
|
|
6424
6911
|
nonHollowCount += 1;
|
|
6425
6912
|
ratedCount += 1;
|
|
6426
|
-
} else if (NAME_SHAPED_TEST_FILE_PATTERN.test(file) || useV17Detectors && NAME_SHAPED_TEST_FILE_PATTERN_V17.test(file)) {
|
|
6913
|
+
} else if (NAME_SHAPED_TEST_FILE_PATTERN.test(file) || useV17Detectors && NAME_SHAPED_TEST_FILE_PATTERN_V17.test(file) || frameworkSuiteSkipped && SUITE_SKIP_NAME_SHAPED_TEST_FILE_PATTERN.test(file)) {
|
|
6427
6914
|
ratedCount += 1;
|
|
6428
6915
|
}
|
|
6429
6916
|
}
|
|
@@ -6930,6 +7417,354 @@ function deriveRlsPolicyScopeInventory(repoPath, repoFiles) {
|
|
|
6930
7417
|
storageFiles
|
|
6931
7418
|
};
|
|
6932
7419
|
}
|
|
7420
|
+
var DATA_LAYER_PATH_PATTERN = /(^|\/)(?:migrations?|drizzle|prisma)\/|\.(?:sql|prisma)$/i;
|
|
7421
|
+
var RLS_STATEMENT_PHRASE_PATTERN = /\b(?:enable|disable)\s+row\s+level\s+security\b|\bcreate\s+(?:or\s+replace\s+)?policy\b/i;
|
|
7422
|
+
var SQL_TABLE_REMOVAL_PHRASE_PATTERN = /\bdrop\s+table\b|\brename\s+to\b/i;
|
|
7423
|
+
var SQL_CREATE_TABLE_HEAD = /^create\s+(?:(?:global|local)\s+)?(?:(?:temporary|temp|unlogged)\s+)?table\s+(?:if\s+not\s+exists\s+)?/i;
|
|
7424
|
+
var SQL_ALTER_TABLE_HEAD = /^alter\s+table\s+(?:if\s+exists\s+)?(?:only\s+)?/i;
|
|
7425
|
+
var SQL_DROP_TABLE_HEAD = /^drop\s+table\s+(?:if\s+exists\s+)?/i;
|
|
7426
|
+
var SQL_RENAME_TO_CLAUSE = /^\s+rename\s+to\s+/i;
|
|
7427
|
+
var SQL_CREATE_POLICY_HEAD = /^create\s+(?:or\s+replace\s+)?policy\s+/i;
|
|
7428
|
+
var SQL_RLS_TOGGLE_PATTERN = /\b(enable|disable)\s+row\s+level\s+security\b/gi;
|
|
7429
|
+
var SQL_PLAIN_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
7430
|
+
var SQL_TABLE_CONSTRAINT_PATTERN = /^(?:constraint|primary|foreign|unique|check|exclude|like)\b/i;
|
|
7431
|
+
var MAX_UNCOVERED_TENANT_TABLES = 3;
|
|
7432
|
+
function compareMigrationPaths(left, right) {
|
|
7433
|
+
const leftChunks = left.match(/\d+|\D+/g) ?? [];
|
|
7434
|
+
const rightChunks = right.match(/\d+|\D+/g) ?? [];
|
|
7435
|
+
for (let index = 0; index < Math.min(leftChunks.length, rightChunks.length); index += 1) {
|
|
7436
|
+
const a = leftChunks[index];
|
|
7437
|
+
const b = rightChunks[index];
|
|
7438
|
+
if (a === b) continue;
|
|
7439
|
+
if (/^\d/.test(a) && /^\d/.test(b)) {
|
|
7440
|
+
const x = a.replace(/^0+(?=\d)/, "");
|
|
7441
|
+
const y = b.replace(/^0+(?=\d)/, "");
|
|
7442
|
+
if (x.length !== y.length) return x.length - y.length;
|
|
7443
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
7444
|
+
return a.length - b.length;
|
|
7445
|
+
}
|
|
7446
|
+
return a < b ? -1 : 1;
|
|
7447
|
+
}
|
|
7448
|
+
return leftChunks.length - rightChunks.length;
|
|
7449
|
+
}
|
|
7450
|
+
function blankSqlNonCode(text) {
|
|
7451
|
+
const out = text.split("");
|
|
7452
|
+
let dynamic = false;
|
|
7453
|
+
const blank = (from, to) => {
|
|
7454
|
+
for (let index2 = from; index2 < to; index2 += 1) {
|
|
7455
|
+
if (out[index2] !== "\n" && out[index2] !== "\r") out[index2] = " ";
|
|
7456
|
+
}
|
|
7457
|
+
};
|
|
7458
|
+
const noteBody = (from, to) => {
|
|
7459
|
+
const body = text.slice(from, to);
|
|
7460
|
+
if (RLS_STATEMENT_PHRASE_PATTERN.test(body) || SQL_TABLE_REMOVAL_PHRASE_PATTERN.test(body)) {
|
|
7461
|
+
dynamic = true;
|
|
7462
|
+
}
|
|
7463
|
+
};
|
|
7464
|
+
let index = 0;
|
|
7465
|
+
while (index < text.length) {
|
|
7466
|
+
const character = text[index];
|
|
7467
|
+
const next = text[index + 1];
|
|
7468
|
+
if (character === "-" && next === "-") {
|
|
7469
|
+
const newline = text.indexOf("\n", index);
|
|
7470
|
+
const end = newline === -1 ? text.length : newline;
|
|
7471
|
+
blank(index, end);
|
|
7472
|
+
index = end;
|
|
7473
|
+
} else if (character === "/" && next === "*") {
|
|
7474
|
+
let depth = 1;
|
|
7475
|
+
let cursor = index + 2;
|
|
7476
|
+
while (cursor < text.length && depth > 0) {
|
|
7477
|
+
if (text[cursor] === "/" && text[cursor + 1] === "*") {
|
|
7478
|
+
depth += 1;
|
|
7479
|
+
cursor += 2;
|
|
7480
|
+
} else if (text[cursor] === "*" && text[cursor + 1] === "/") {
|
|
7481
|
+
depth -= 1;
|
|
7482
|
+
cursor += 2;
|
|
7483
|
+
} else {
|
|
7484
|
+
cursor += 1;
|
|
7485
|
+
}
|
|
7486
|
+
}
|
|
7487
|
+
blank(index, cursor);
|
|
7488
|
+
index = cursor;
|
|
7489
|
+
} else if (character === "'") {
|
|
7490
|
+
const escapes = index > 0 && /[Ee]/.test(text[index - 1] ?? "") && !/[A-Za-z0-9_$]/.test(text[index - 2] ?? "");
|
|
7491
|
+
let cursor = index + 1;
|
|
7492
|
+
while (cursor < text.length) {
|
|
7493
|
+
if (escapes && text[cursor] === "\\") {
|
|
7494
|
+
cursor += 2;
|
|
7495
|
+
} else if (text[cursor] === "'") {
|
|
7496
|
+
if (text[cursor + 1] !== "'") break;
|
|
7497
|
+
cursor += 2;
|
|
7498
|
+
} else {
|
|
7499
|
+
cursor += 1;
|
|
7500
|
+
}
|
|
7501
|
+
}
|
|
7502
|
+
noteBody(index + 1, cursor);
|
|
7503
|
+
blank(index + 1, cursor);
|
|
7504
|
+
index = cursor + 1;
|
|
7505
|
+
} else if (character === '"') {
|
|
7506
|
+
let cursor = index + 1;
|
|
7507
|
+
while (cursor < text.length) {
|
|
7508
|
+
if (text[cursor] === '"') {
|
|
7509
|
+
if (text[cursor + 1] !== '"') break;
|
|
7510
|
+
cursor += 2;
|
|
7511
|
+
} else {
|
|
7512
|
+
cursor += 1;
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
index = cursor + 1;
|
|
7516
|
+
} else if (character === "$" && !/[A-Za-z0-9_$]/.test(text[index - 1] ?? "")) {
|
|
7517
|
+
const tag = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.exec(text.slice(index, index + 66))?.[0];
|
|
7518
|
+
if (tag === void 0) {
|
|
7519
|
+
index += 1;
|
|
7520
|
+
continue;
|
|
7521
|
+
}
|
|
7522
|
+
const close = text.indexOf(tag, index + tag.length);
|
|
7523
|
+
const end = close === -1 ? text.length : close;
|
|
7524
|
+
noteBody(index + tag.length, end);
|
|
7525
|
+
blank(index + tag.length, end);
|
|
7526
|
+
index = close === -1 ? text.length : close + tag.length;
|
|
7527
|
+
} else {
|
|
7528
|
+
index += 1;
|
|
7529
|
+
}
|
|
7530
|
+
}
|
|
7531
|
+
return { code: out.join(""), dynamic };
|
|
7532
|
+
}
|
|
7533
|
+
function splitSqlStatements(code) {
|
|
7534
|
+
const statements = [];
|
|
7535
|
+
let start = 0;
|
|
7536
|
+
let quoted = false;
|
|
7537
|
+
for (let index = 0; index <= code.length; index += 1) {
|
|
7538
|
+
const character = code[index];
|
|
7539
|
+
if (character === '"') quoted = !quoted;
|
|
7540
|
+
if (index === code.length || character === ";" && !quoted) {
|
|
7541
|
+
const raw = code.slice(start, index);
|
|
7542
|
+
const lead = raw.length - raw.trimStart().length;
|
|
7543
|
+
if (raw.trim() !== "") statements.push({ text: raw.slice(lead), start: start + lead });
|
|
7544
|
+
start = index + 1;
|
|
7545
|
+
}
|
|
7546
|
+
}
|
|
7547
|
+
return statements;
|
|
7548
|
+
}
|
|
7549
|
+
function readSqlIdentifier(text, from) {
|
|
7550
|
+
let index = from;
|
|
7551
|
+
while (/\s/.test(text[index] ?? "")) index += 1;
|
|
7552
|
+
if (text[index] === '"') {
|
|
7553
|
+
let cursor = index + 1;
|
|
7554
|
+
while (cursor < text.length) {
|
|
7555
|
+
if (text[cursor] === '"') {
|
|
7556
|
+
if (text[cursor + 1] !== '"') break;
|
|
7557
|
+
cursor += 2;
|
|
7558
|
+
} else {
|
|
7559
|
+
cursor += 1;
|
|
7560
|
+
}
|
|
7561
|
+
}
|
|
7562
|
+
if (cursor >= text.length) return null;
|
|
7563
|
+
const inner = text.slice(index + 1, cursor).replace(/""/g, '"');
|
|
7564
|
+
return { value: SQL_PLAIN_IDENTIFIER_PATTERN.test(inner) ? inner : null, end: cursor + 1 };
|
|
7565
|
+
}
|
|
7566
|
+
const bare = /^[^\s(),;.]+/.exec(text.slice(index))?.[0];
|
|
7567
|
+
if (bare === void 0) return null;
|
|
7568
|
+
return { value: SQL_PLAIN_IDENTIFIER_PATTERN.test(bare) ? bare : null, end: index + bare.length };
|
|
7569
|
+
}
|
|
7570
|
+
function readSqlTableName(text, from) {
|
|
7571
|
+
const segments = [];
|
|
7572
|
+
let normalisable = true;
|
|
7573
|
+
let index = from;
|
|
7574
|
+
for (; ; ) {
|
|
7575
|
+
const identifier = readSqlIdentifier(text, index);
|
|
7576
|
+
if (identifier === null) return { segments: null, end: index };
|
|
7577
|
+
if (identifier.value === null) normalisable = false;
|
|
7578
|
+
else segments.push(identifier.value.toLowerCase());
|
|
7579
|
+
index = identifier.end;
|
|
7580
|
+
let cursor = index;
|
|
7581
|
+
while (/\s/.test(text[cursor] ?? "")) cursor += 1;
|
|
7582
|
+
if (text[cursor] !== ".") break;
|
|
7583
|
+
index = cursor + 1;
|
|
7584
|
+
}
|
|
7585
|
+
return {
|
|
7586
|
+
segments: normalisable && segments.length >= 1 && segments.length <= 2 ? segments : null,
|
|
7587
|
+
end: index
|
|
7588
|
+
};
|
|
7589
|
+
}
|
|
7590
|
+
function sqlColumnListHasTenantColumn(text, open) {
|
|
7591
|
+
let depth = 0;
|
|
7592
|
+
let quoted = false;
|
|
7593
|
+
let elementStart = open + 1;
|
|
7594
|
+
const elements = [];
|
|
7595
|
+
for (let index = open; index < text.length; index += 1) {
|
|
7596
|
+
const character = text[index];
|
|
7597
|
+
if (character === '"') quoted = !quoted;
|
|
7598
|
+
if (quoted) continue;
|
|
7599
|
+
if (character === "(") depth += 1;
|
|
7600
|
+
else if (character === ")") {
|
|
7601
|
+
depth -= 1;
|
|
7602
|
+
if (depth === 0) {
|
|
7603
|
+
elements.push(text.slice(elementStart, index));
|
|
7604
|
+
break;
|
|
7605
|
+
}
|
|
7606
|
+
} else if (character === "," && depth === 1) {
|
|
7607
|
+
elements.push(text.slice(elementStart, index));
|
|
7608
|
+
elementStart = index + 1;
|
|
7609
|
+
}
|
|
7610
|
+
}
|
|
7611
|
+
return elements.some((element) => {
|
|
7612
|
+
const trimmed = element.trim();
|
|
7613
|
+
if (trimmed === "" || SQL_TABLE_CONSTRAINT_PATTERN.test(trimmed)) return false;
|
|
7614
|
+
const column = readSqlIdentifier(trimmed, 0)?.value;
|
|
7615
|
+
return column != null && TENANT_SCOPE_PATTERN_V11.test(column);
|
|
7616
|
+
});
|
|
7617
|
+
}
|
|
7618
|
+
function lineAtOffset(text, offset) {
|
|
7619
|
+
let line = 1;
|
|
7620
|
+
for (let index = 0; index < offset && index < text.length; index += 1) {
|
|
7621
|
+
if (text[index] === "\n") line += 1;
|
|
7622
|
+
}
|
|
7623
|
+
return line;
|
|
7624
|
+
}
|
|
7625
|
+
function findUncoveredTenantTables(repoPath, repoFiles) {
|
|
7626
|
+
const events = [];
|
|
7627
|
+
const nameEvents = [];
|
|
7628
|
+
const tenantTables = [];
|
|
7629
|
+
let order = 0;
|
|
7630
|
+
const dataLayerFiles = repoFiles.filter((file) => DATA_LAYER_PATH_PATTERN.test(file)).sort(compareMigrationPaths);
|
|
7631
|
+
for (const file of dataLayerFiles) {
|
|
7632
|
+
const fullPath = join(repoPath, file);
|
|
7633
|
+
if (!isRegularFile(fullPath)) continue;
|
|
7634
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
7635
|
+
if (!/\.sql$/i.test(file)) {
|
|
7636
|
+
if (RLS_STATEMENT_PHRASE_PATTERN.test(contents)) return [];
|
|
7637
|
+
continue;
|
|
7638
|
+
}
|
|
7639
|
+
const { code, dynamic } = blankSqlNonCode(contents);
|
|
7640
|
+
if (dynamic) return [];
|
|
7641
|
+
const dir = posix.dirname(file);
|
|
7642
|
+
for (const statement of splitSqlStatements(code)) {
|
|
7643
|
+
order += 1;
|
|
7644
|
+
const createTable = SQL_CREATE_TABLE_HEAD.exec(statement.text);
|
|
7645
|
+
if (createTable) {
|
|
7646
|
+
const { segments, end } = readSqlTableName(statement.text, createTable[0].length);
|
|
7647
|
+
const open = statement.text.slice(end).search(/\S/);
|
|
7648
|
+
if (segments === null || open === -1 || statement.text[end + open] !== "(") continue;
|
|
7649
|
+
if (sqlColumnListHasTenantColumn(statement.text, end + open)) {
|
|
7650
|
+
tenantTables.push({
|
|
7651
|
+
key: segments[segments.length - 1],
|
|
7652
|
+
segments,
|
|
7653
|
+
file,
|
|
7654
|
+
line: lineAtOffset(contents, statement.start),
|
|
7655
|
+
order,
|
|
7656
|
+
dir
|
|
7657
|
+
});
|
|
7658
|
+
}
|
|
7659
|
+
continue;
|
|
7660
|
+
}
|
|
7661
|
+
const dropTable = SQL_DROP_TABLE_HEAD.exec(statement.text);
|
|
7662
|
+
if (dropTable) {
|
|
7663
|
+
let cursor = dropTable[0].length;
|
|
7664
|
+
for (; ; ) {
|
|
7665
|
+
const { segments, end } = readSqlTableName(statement.text, cursor);
|
|
7666
|
+
if (segments === null) return [];
|
|
7667
|
+
nameEvents.push({ key: segments[segments.length - 1], order, dir });
|
|
7668
|
+
const comma = /^\s*,/.exec(statement.text.slice(end));
|
|
7669
|
+
if (!comma) break;
|
|
7670
|
+
cursor = end + comma[0].length;
|
|
7671
|
+
}
|
|
7672
|
+
continue;
|
|
7673
|
+
}
|
|
7674
|
+
const alterTable = SQL_ALTER_TABLE_HEAD.exec(statement.text);
|
|
7675
|
+
if (alterTable) {
|
|
7676
|
+
const { segments: renamed, end: renamedEnd } = readSqlTableName(
|
|
7677
|
+
statement.text,
|
|
7678
|
+
alterTable[0].length
|
|
7679
|
+
);
|
|
7680
|
+
const renameTo = renamed === null ? null : SQL_RENAME_TO_CLAUSE.exec(statement.text.slice(renamedEnd));
|
|
7681
|
+
if (renamed !== null && renameTo) {
|
|
7682
|
+
const { segments: to } = readSqlTableName(statement.text, renamedEnd + renameTo[0].length);
|
|
7683
|
+
if (to === null) return [];
|
|
7684
|
+
nameEvents.push({
|
|
7685
|
+
key: renamed[renamed.length - 1],
|
|
7686
|
+
to: to[to.length - 1],
|
|
7687
|
+
order,
|
|
7688
|
+
dir
|
|
7689
|
+
});
|
|
7690
|
+
continue;
|
|
7691
|
+
}
|
|
7692
|
+
const toggles = [...statement.text.matchAll(SQL_RLS_TOGGLE_PATTERN)];
|
|
7693
|
+
if (toggles.length === 0) {
|
|
7694
|
+
if (SQL_TABLE_REMOVAL_PHRASE_PATTERN.test(statement.text)) return [];
|
|
7695
|
+
continue;
|
|
7696
|
+
}
|
|
7697
|
+
const { segments } = readSqlTableName(statement.text, alterTable[0].length);
|
|
7698
|
+
if (segments === null) return [];
|
|
7699
|
+
const key = segments[segments.length - 1];
|
|
7700
|
+
for (const toggle of toggles) {
|
|
7701
|
+
order += 1;
|
|
7702
|
+
events.push({ key, covers: toggle[1]?.toLowerCase() === "enable", order, dir });
|
|
7703
|
+
}
|
|
7704
|
+
continue;
|
|
7705
|
+
}
|
|
7706
|
+
const createPolicy = SQL_CREATE_POLICY_HEAD.exec(statement.text);
|
|
7707
|
+
if (createPolicy) {
|
|
7708
|
+
const policyName = readSqlIdentifier(statement.text, createPolicy[0].length);
|
|
7709
|
+
const on = policyName ? /^\s+on\s+/i.exec(statement.text.slice(policyName.end)) : null;
|
|
7710
|
+
if (!policyName || !on) return [];
|
|
7711
|
+
const { segments } = readSqlTableName(statement.text, policyName.end + on[0].length);
|
|
7712
|
+
if (segments === null) return [];
|
|
7713
|
+
events.push({ key: segments[segments.length - 1], covers: true, order, dir });
|
|
7714
|
+
continue;
|
|
7715
|
+
}
|
|
7716
|
+
if (RLS_STATEMENT_PHRASE_PATTERN.test(statement.text)) return [];
|
|
7717
|
+
}
|
|
7718
|
+
}
|
|
7719
|
+
if (!events.some((event) => event.covers)) return [];
|
|
7720
|
+
const followTable = (table) => {
|
|
7721
|
+
const spans = [];
|
|
7722
|
+
let key = table.key;
|
|
7723
|
+
let from = Number.NEGATIVE_INFINITY;
|
|
7724
|
+
for (; ; ) {
|
|
7725
|
+
const current = key;
|
|
7726
|
+
if (nameEvents.some((event) => event.key === current && event.dir !== table.dir)) return null;
|
|
7727
|
+
const next = nameEvents.find(
|
|
7728
|
+
(event) => event.key === current && event.dir === table.dir && event.order > Math.max(from, table.order)
|
|
7729
|
+
);
|
|
7730
|
+
if (next === void 0) {
|
|
7731
|
+
spans.push({ key, from, until: Number.POSITIVE_INFINITY });
|
|
7732
|
+
return spans;
|
|
7733
|
+
}
|
|
7734
|
+
if (next.to === void 0) return null;
|
|
7735
|
+
spans.push({ key, from, until: next.order });
|
|
7736
|
+
key = next.to;
|
|
7737
|
+
from = next.order;
|
|
7738
|
+
}
|
|
7739
|
+
};
|
|
7740
|
+
const isCovered = (table, spans) => {
|
|
7741
|
+
const forTable = events.filter(
|
|
7742
|
+
(event) => spans.some(
|
|
7743
|
+
(span) => span.key === event.key && (event.dir !== table.dir || event.order > span.from && event.order < span.until)
|
|
7744
|
+
)
|
|
7745
|
+
);
|
|
7746
|
+
const lastCover = forTable.filter((event) => event.covers).at(-1);
|
|
7747
|
+
const lastDisable = forTable.filter((event) => !event.covers).at(-1);
|
|
7748
|
+
if (lastCover === void 0) return false;
|
|
7749
|
+
if (lastDisable === void 0) return true;
|
|
7750
|
+
if (lastCover.dir !== lastDisable.dir) return true;
|
|
7751
|
+
return lastCover.order > lastDisable.order;
|
|
7752
|
+
};
|
|
7753
|
+
const uncovered = /* @__PURE__ */ new Map();
|
|
7754
|
+
for (const table of tenantTables) {
|
|
7755
|
+
const spans = followTable(table);
|
|
7756
|
+
const current = spans?.at(-1)?.key;
|
|
7757
|
+
if (spans === null || current === void 0 || uncovered.has(current)) continue;
|
|
7758
|
+
if (isCovered(table, spans)) continue;
|
|
7759
|
+
uncovered.set(current, {
|
|
7760
|
+
name: [...table.segments.slice(0, -1), current].join("."),
|
|
7761
|
+
file: table.file,
|
|
7762
|
+
line: table.line,
|
|
7763
|
+
order: table.order
|
|
7764
|
+
});
|
|
7765
|
+
}
|
|
7766
|
+
return [...uncovered.values()].sort((left, right) => left.order - right.order).slice(0, MAX_UNCOVERED_TENANT_TABLES).map(({ name, file, line }) => ({ name, file, line }));
|
|
7767
|
+
}
|
|
6933
7768
|
function findDeployableServicePremiseFiles(repoPath, repoFiles, strictOperationalBoundary = false) {
|
|
6934
7769
|
const explicitTargets = repoFiles.filter(
|
|
6935
7770
|
(file) => isAuthoredProductionPath(file) && isExplicitDeployTarget(file) && (!strictOperationalBoundary || isV47OperationalApplicationPath(file))
|
|
@@ -7019,10 +7854,190 @@ function isAuthenticatedClaimRealityArtifact(repoPath, file) {
|
|
|
7019
7854
|
}
|
|
7020
7855
|
return false;
|
|
7021
7856
|
}
|
|
7857
|
+
var DECLARED_CLAIM_ARTIFACT_BASENAME_PATTERN = /^claims?[-_.]?(?:reality[-_.]?)?(?:reconciliation|register|ledger)\.(?:md|mdx|json|ya?ml)$/i;
|
|
7858
|
+
var DECLARED_CLAIM_ARTIFACT_PATH_PATTERN = /^[A-Za-z0-9._/-]{1,160}$/;
|
|
7859
|
+
var MARKDOWN_FENCE_PATTERN = /^\s{0,3}(`{3,}|~{3,})/;
|
|
7860
|
+
var MARKDOWN_INLINE_LINK_PATTERN = /(!?)\[[^\]]*\]\(\s*<?([^\s)>]+)>?[^)]*\)/g;
|
|
7861
|
+
var MARKDOWN_REFERENCE_DEFINITION_PATTERN = /^\s{0,3}\[[^\]]+\]:\s*<?([^\s>]+)>?/;
|
|
7862
|
+
var MARKDOWN_CODE_SPAN_PATTERN = /`([^`]+)`/g;
|
|
7863
|
+
function isClaimArtifactDeclaringDocument(file) {
|
|
7864
|
+
return isClaimSourceFile(file) || /^CONTRIBUTING\.md$/i.test(file);
|
|
7865
|
+
}
|
|
7866
|
+
function isClaimArtifactDeclaringWorkflow(file) {
|
|
7867
|
+
return /^\.github\/workflows\/[^/]+\.ya?ml$/.test(file);
|
|
7868
|
+
}
|
|
7869
|
+
function normaliseDeclaredClaimArtifactPath(candidate, baseDir) {
|
|
7870
|
+
if (!DECLARED_CLAIM_ARTIFACT_PATH_PATTERN.test(candidate)) return null;
|
|
7871
|
+
const joined = candidate.startsWith("/") ? posix.normalize(candidate.slice(1)) : posix.normalize(posix.join(baseDir, candidate));
|
|
7872
|
+
const path = joined.replace(/\/+$/, "");
|
|
7873
|
+
if (path === "" || path === "." || path === ".." || path.startsWith("../") || path.startsWith("/")) {
|
|
7874
|
+
return null;
|
|
7875
|
+
}
|
|
7876
|
+
return DECLARED_CLAIM_ARTIFACT_BASENAME_PATTERN.test(posix.basename(path)) ? path : null;
|
|
7877
|
+
}
|
|
7878
|
+
function markdownLinkTarget(target) {
|
|
7879
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("//") || target.startsWith("#")) {
|
|
7880
|
+
return null;
|
|
7881
|
+
}
|
|
7882
|
+
const withoutSuffix = target.replace(/[?#].*$/, "");
|
|
7883
|
+
try {
|
|
7884
|
+
return decodeURIComponent(withoutSuffix);
|
|
7885
|
+
} catch {
|
|
7886
|
+
return null;
|
|
7887
|
+
}
|
|
7888
|
+
}
|
|
7889
|
+
function markdownClaimArtifactDeclarations(file, contents) {
|
|
7890
|
+
const baseDir = posix.dirname(file);
|
|
7891
|
+
const declarations = [];
|
|
7892
|
+
let fence = null;
|
|
7893
|
+
let inHtmlComment = false;
|
|
7894
|
+
contents.split(/\r?\n/).forEach((rawLine, index) => {
|
|
7895
|
+
const marker = MARKDOWN_FENCE_PATTERN.exec(rawLine)?.[1];
|
|
7896
|
+
if (fence !== null) {
|
|
7897
|
+
if (marker !== void 0 && marker[0] === fence[0] && marker.length >= fence.length) fence = null;
|
|
7898
|
+
return;
|
|
7899
|
+
}
|
|
7900
|
+
if (marker !== void 0) {
|
|
7901
|
+
fence = marker;
|
|
7902
|
+
return;
|
|
7903
|
+
}
|
|
7904
|
+
let line = rawLine;
|
|
7905
|
+
if (inHtmlComment) {
|
|
7906
|
+
const close = line.indexOf("-->");
|
|
7907
|
+
if (close === -1) return;
|
|
7908
|
+
line = line.slice(close + 3);
|
|
7909
|
+
inHtmlComment = false;
|
|
7910
|
+
}
|
|
7911
|
+
line = line.replace(/<!--[\s\S]*?-->/g, " ");
|
|
7912
|
+
const open = line.indexOf("<!--");
|
|
7913
|
+
if (open !== -1) {
|
|
7914
|
+
line = line.slice(0, open);
|
|
7915
|
+
inHtmlComment = true;
|
|
7916
|
+
}
|
|
7917
|
+
const lineNumber = index + 1;
|
|
7918
|
+
for (const match of line.matchAll(MARKDOWN_CODE_SPAN_PATTERN)) {
|
|
7919
|
+
const span = (match[1] ?? "").trim().replace(/^\.\//, "");
|
|
7920
|
+
const candidates = [
|
|
7921
|
+
normaliseDeclaredClaimArtifactPath(span, "."),
|
|
7922
|
+
normaliseDeclaredClaimArtifactPath(span, baseDir)
|
|
7923
|
+
].filter((path) => path !== null);
|
|
7924
|
+
if (candidates.length > 0) declarations.push({ candidates: [...new Set(candidates)], line: lineNumber });
|
|
7925
|
+
}
|
|
7926
|
+
const prose = line.replace(MARKDOWN_CODE_SPAN_PATTERN, " ");
|
|
7927
|
+
const targets = [];
|
|
7928
|
+
for (const match of prose.matchAll(MARKDOWN_INLINE_LINK_PATTERN)) {
|
|
7929
|
+
if (match[1] === "!") continue;
|
|
7930
|
+
targets.push(match[2] ?? "");
|
|
7931
|
+
}
|
|
7932
|
+
const reference = MARKDOWN_REFERENCE_DEFINITION_PATTERN.exec(prose);
|
|
7933
|
+
if (reference?.[1]) targets.push(reference[1]);
|
|
7934
|
+
for (const target of targets) {
|
|
7935
|
+
const decoded = markdownLinkTarget(target);
|
|
7936
|
+
const path = decoded === null ? null : normaliseDeclaredClaimArtifactPath(decoded, baseDir);
|
|
7937
|
+
if (path !== null) declarations.push({ candidates: [path], line: lineNumber });
|
|
7938
|
+
}
|
|
7939
|
+
});
|
|
7940
|
+
return declarations;
|
|
7941
|
+
}
|
|
7942
|
+
var WORKFLOW_WRITE_OPERATOR_PATTERN = />|(?:^|[\s|;&(])(?:tee|-o|--out[\w-]*)(?=[\s=]|$)/;
|
|
7943
|
+
var WORKFLOW_ARTIFACT_ACTION_PATTERN = /^\s*(?:-\s+)?uses\s*:\s*["']?actions\/(?:upload|download)-artifact(?:[/@]|["']?\s*$)/i;
|
|
7944
|
+
function workflowClaimArtifactDeclarations(contents) {
|
|
7945
|
+
if (/^\s*(?:-\s+)?working-directory\s*:/m.test(contents)) return [];
|
|
7946
|
+
const declarations = [];
|
|
7947
|
+
let stepsIndent = null;
|
|
7948
|
+
let stepItemIndent = null;
|
|
7949
|
+
let step = null;
|
|
7950
|
+
let writeContinues = false;
|
|
7951
|
+
const closeStep = () => {
|
|
7952
|
+
if (step !== null && !step.movesArtifact) declarations.push(...step.declarations);
|
|
7953
|
+
step = null;
|
|
7954
|
+
writeContinues = false;
|
|
7955
|
+
};
|
|
7956
|
+
contents.split(/\r?\n/).forEach((rawLine, index) => {
|
|
7957
|
+
const line = rawLine.replace(/(^|\s)#.*$/, "");
|
|
7958
|
+
if (line.trim() === "") return;
|
|
7959
|
+
const indent = line.length - line.trimStart().length;
|
|
7960
|
+
if (stepsIndent !== null && (indent < stepsIndent || indent === stepsIndent && !line.trimStart().startsWith("- "))) {
|
|
7961
|
+
closeStep();
|
|
7962
|
+
stepsIndent = null;
|
|
7963
|
+
}
|
|
7964
|
+
const stepsKey = /^(\s*)steps\s*:\s*$/.exec(line);
|
|
7965
|
+
if (stepsKey) {
|
|
7966
|
+
closeStep();
|
|
7967
|
+
stepsIndent = stepsKey[1]?.length ?? 0;
|
|
7968
|
+
stepItemIndent = null;
|
|
7969
|
+
return;
|
|
7970
|
+
}
|
|
7971
|
+
if (stepsIndent === null) return;
|
|
7972
|
+
if (line.trimStart().startsWith("- ") && (stepItemIndent === null || indent <= stepItemIndent)) {
|
|
7973
|
+
closeStep();
|
|
7974
|
+
stepItemIndent = indent;
|
|
7975
|
+
}
|
|
7976
|
+
step ??= { declarations: [], movesArtifact: false };
|
|
7977
|
+
if (WORKFLOW_ARTIFACT_ACTION_PATTERN.test(line)) step.movesArtifact = true;
|
|
7978
|
+
const writeAt = writeContinues ? 0 : line.search(WORKFLOW_WRITE_OPERATOR_PATTERN);
|
|
7979
|
+
writeContinues = writeAt !== -1 && /\\\s*$/.test(line);
|
|
7980
|
+
const read = writeAt === -1 ? line : line.slice(0, writeAt);
|
|
7981
|
+
for (const token of read.split(/[\s'"`=,;:()[\]{}<>|&]+/)) {
|
|
7982
|
+
const path = normaliseDeclaredClaimArtifactPath(token.replace(/^\.\//, ""), ".");
|
|
7983
|
+
if (path !== null) step.declarations.push({ candidates: [path], line: index + 1 });
|
|
7984
|
+
}
|
|
7985
|
+
});
|
|
7986
|
+
closeStep();
|
|
7987
|
+
return declarations;
|
|
7988
|
+
}
|
|
7989
|
+
function scanClaimArtifactDeclarations(repoPath, repoFiles) {
|
|
7990
|
+
const declaringFiles = repoFiles.filter((file) => isClaimArtifactDeclaringDocument(file) || isClaimArtifactDeclaringWorkflow(file)).sort();
|
|
7991
|
+
let declared = false;
|
|
7992
|
+
if (declaringFiles.length === 0) return { declared };
|
|
7993
|
+
const known = [...repoFiles, ...withheldRepoPaths().map(({ path }) => path)].map(
|
|
7994
|
+
(path) => path.toLowerCase()
|
|
7995
|
+
);
|
|
7996
|
+
const isPresent = (path) => {
|
|
7997
|
+
const lower = path.toLowerCase();
|
|
7998
|
+
return known.some((file) => file === lower || file.startsWith(`${lower}/`));
|
|
7999
|
+
};
|
|
8000
|
+
for (const file of declaringFiles) {
|
|
8001
|
+
const fullPath = join(repoPath, file);
|
|
8002
|
+
if (!isRegularFile(fullPath)) continue;
|
|
8003
|
+
const contents = readRepoText(fullPath, "utf8");
|
|
8004
|
+
const declarations = isClaimArtifactDeclaringWorkflow(file) ? workflowClaimArtifactDeclarations(contents) : markdownClaimArtifactDeclarations(file, contents);
|
|
8005
|
+
for (const declaration of declarations) {
|
|
8006
|
+
declared = true;
|
|
8007
|
+
if (declaration.candidates.some(isPresent)) continue;
|
|
8008
|
+
const [path] = declaration.candidates;
|
|
8009
|
+
if (path !== void 0) {
|
|
8010
|
+
return { declared, firstAbsent: { path, declaringFile: file, line: declaration.line } };
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
}
|
|
8014
|
+
return { declared };
|
|
8015
|
+
}
|
|
7022
8016
|
function isCoverageConfig(repoPath, file) {
|
|
7023
8017
|
if (/(^|\/)(nyc|c8|coverage|codecov)\.(json|yml|yaml)$/.test(file) || /(^|\/)\.coveragerc$/.test(file) || /(^|\/)lcov\.info$/.test(file) || /(^|\/)\.coveralls\.yml$/.test(file)) {
|
|
7024
8018
|
return true;
|
|
7025
8019
|
}
|
|
8020
|
+
if (/(^|\/)\.codecov\.ya?ml$/.test(file) || /(^|\/)\.nycrc(?:\.json|\.ya?ml)?$/.test(file) || /(^|\/)\.c8rc(?:\.json)?$/.test(file) || /(^|\/)\.?tarpaulin\.toml$/.test(file)) {
|
|
8021
|
+
return true;
|
|
8022
|
+
}
|
|
8023
|
+
if (/(^|\/)(?:setup\.cfg|tox\.ini)$/.test(file)) {
|
|
8024
|
+
return fileContains(repoPath, file, /^\s*\[coverage:(?:run|report)\]\s*$/m);
|
|
8025
|
+
}
|
|
8026
|
+
if (/(^|\/)vite\.config\.[cm]?[jt]s$/.test(file)) {
|
|
8027
|
+
return viteConfigCollectsCoverage(repoPath, file);
|
|
8028
|
+
}
|
|
8029
|
+
if (/(^|\/)\.simplecov$/.test(file)) {
|
|
8030
|
+
return fileContains(repoPath, file, /^(?!\s*#).*\bSimpleCov\b/m);
|
|
8031
|
+
}
|
|
8032
|
+
if (/(^|\/)phpunit\.xml(?:\.dist)?$/.test(file)) {
|
|
8033
|
+
return /<coverage(?=[\s>/])/.test(stripXmlComments(readRepoText(join(repoPath, file), "utf8")));
|
|
8034
|
+
}
|
|
8035
|
+
if (/(^|\/)pom\.xml$/.test(file)) {
|
|
8036
|
+
return pomAppliesJacoco(readRepoText(join(repoPath, file), "utf8"));
|
|
8037
|
+
}
|
|
8038
|
+
if (/(^|\/)build\.gradle(?:\.kts)?$/.test(file)) {
|
|
8039
|
+
return gradleAppliesJacoco(readRepoText(join(repoPath, file), "utf8"));
|
|
8040
|
+
}
|
|
7026
8041
|
if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
7027
8042
|
return fileContains(repoPath, file, /\bcoverage\s*:/i);
|
|
7028
8043
|
}
|
|
@@ -7054,19 +8069,76 @@ function packageJsonHasCoverageTooling(repoPath, file) {
|
|
|
7054
8069
|
}
|
|
7055
8070
|
}
|
|
7056
8071
|
}
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
8072
|
+
return packageJsonHasStaticCoverageTooling(repoPath, file);
|
|
8073
|
+
}
|
|
8074
|
+
function vitestCoveragePropertyCollects(contents) {
|
|
8075
|
+
const property = /(?:^|[{,])\s*["']?coverage["']?\s*:\s*([^,\r\n}]*)/im.exec(contents)?.[1]?.trim();
|
|
8076
|
+
if (!property || /^(?:false\b|null\b|undefined\b)/i.test(property)) return false;
|
|
8077
|
+
if (/^\{/.test(property) && /\benabled\s*:\s*false\b/i.test(contents)) return false;
|
|
8078
|
+
return true;
|
|
8079
|
+
}
|
|
8080
|
+
function viteConfigCollectsCoverage(repoPath, file) {
|
|
8081
|
+
const directory = dirname(file);
|
|
8082
|
+
for (const extension of ["ts", "mts", "cts", "js", "mjs", "cjs"]) {
|
|
8083
|
+
const sibling = join(repoPath, directory, `vitest.config.${extension}`);
|
|
8084
|
+
if (!isRegularFile(sibling)) continue;
|
|
8085
|
+
const vitestConfig = stripJavaScriptComments(readRepoText(sibling, "utf8"));
|
|
8086
|
+
if (!/["']\.\/vite\.config(?:\.[cm]?[jt]s)?["']/.test(vitestConfig)) return false;
|
|
8087
|
+
}
|
|
8088
|
+
const contents = stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"));
|
|
8089
|
+
const testProperty = /(?:^|[{,])\s*["']?test["']?\s*:\s*\{/m.exec(contents);
|
|
8090
|
+
if (!testProperty) return false;
|
|
8091
|
+
const block = balancedBraceBlock(contents, testProperty.index + testProperty[0].length - 1);
|
|
8092
|
+
return vitestCoveragePropertyCollects(block);
|
|
8093
|
+
}
|
|
8094
|
+
function balancedBraceBlock(contents, open) {
|
|
8095
|
+
let depth = 0;
|
|
8096
|
+
let quote = null;
|
|
8097
|
+
for (let index = open; index < contents.length; index += 1) {
|
|
8098
|
+
const character = contents[index];
|
|
8099
|
+
if (quote) {
|
|
8100
|
+
if (character === "\\") index += 1;
|
|
8101
|
+
else if (character === quote) quote = null;
|
|
8102
|
+
continue;
|
|
8103
|
+
}
|
|
8104
|
+
if (character === '"' || character === "'" || character === "`") quote = character;
|
|
8105
|
+
else if (character === "{") depth += 1;
|
|
8106
|
+
else if (character === "}") {
|
|
8107
|
+
depth -= 1;
|
|
8108
|
+
if (depth === 0) return contents.slice(open, index + 1);
|
|
8109
|
+
}
|
|
8110
|
+
}
|
|
8111
|
+
return contents.slice(open);
|
|
8112
|
+
}
|
|
8113
|
+
function stripXmlComments(contents) {
|
|
8114
|
+
return contents.replace(/<!--[\s\S]*?-->/g, "");
|
|
8115
|
+
}
|
|
8116
|
+
function pomAppliesJacoco(contents) {
|
|
8117
|
+
const pom = stripXmlComments(contents).replace(/<pluginManagement>[\s\S]*?<\/pluginManagement>/g, "");
|
|
8118
|
+
if (/<jacoco\.skip>\s*true\s*<\/jacoco\.skip>/i.test(pom)) return false;
|
|
8119
|
+
for (const plugin of pom.matchAll(/<plugin>([\s\S]*?)<\/plugin>/g)) {
|
|
8120
|
+
const body = plugin[1] ?? "";
|
|
8121
|
+
if (!/<artifactId>\s*jacoco-maven-plugin\s*<\/artifactId>/.test(body)) continue;
|
|
8122
|
+
if (/<skip>\s*true\s*<\/skip>/i.test(body)) continue;
|
|
8123
|
+
return true;
|
|
8124
|
+
}
|
|
8125
|
+
return false;
|
|
8126
|
+
}
|
|
8127
|
+
function gradleAppliesJacoco(contents) {
|
|
8128
|
+
const script = stripJavaScriptComments(contents);
|
|
8129
|
+
if (/\bapply\s*\(?\s*plugin\s*[:=]\s*["']jacoco["']/.test(script)) return true;
|
|
8130
|
+
for (const block of script.matchAll(/\bplugins\s*\{([^{}]*)\}/g)) {
|
|
8131
|
+
if (/(?:^|[\s;])(?:id\s*\(?\s*["']jacoco["']\s*\)?|jacoco)[ \t]*(?:$|[\s;])/m.test(block[1] ?? "")) {
|
|
8132
|
+
return true;
|
|
8133
|
+
}
|
|
7060
8134
|
}
|
|
7061
8135
|
return false;
|
|
7062
8136
|
}
|
|
7063
8137
|
function isV23CoverageConfig(repoPath, file) {
|
|
7064
8138
|
if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
if (/^\{/.test(property) && /\benabled\s*:\s*false\b/i.test(contents)) return false;
|
|
7069
|
-
return true;
|
|
8139
|
+
return vitestCoveragePropertyCollects(
|
|
8140
|
+
stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"))
|
|
8141
|
+
);
|
|
7070
8142
|
}
|
|
7071
8143
|
if (/(^|\/)jest\.config\.[cm]?[jt]s$/.test(file)) {
|
|
7072
8144
|
const contents = stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"));
|
|
@@ -7126,6 +8198,10 @@ function packageJsonHasStaticCoverageTooling(repoPath, file) {
|
|
|
7126
8198
|
if (!isRegularFile(fullPath)) return false;
|
|
7127
8199
|
const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
|
|
7128
8200
|
if (!parsed) return false;
|
|
8201
|
+
const jest = parsed.jest;
|
|
8202
|
+
if (isRecord(jest) && (jest.collectCoverage === true || isRecord(jest.coverageThreshold) || typeof jest.coverageProvider === "string")) {
|
|
8203
|
+
return true;
|
|
8204
|
+
}
|
|
7129
8205
|
return ["nyc", "c8", "istanbul"].some((key) => {
|
|
7130
8206
|
const config = parsed[key];
|
|
7131
8207
|
return typeof config === "object" && config !== null && !Array.isArray(config);
|
|
@@ -7207,6 +8283,19 @@ function analyzeCoverageConfiguration(repoPath, repoFiles, authoredOnly = false)
|
|
|
7207
8283
|
detectorDecisions
|
|
7208
8284
|
};
|
|
7209
8285
|
}
|
|
8286
|
+
function unionWithLegacyCoverageRecognition(analysis, legacyFiles, hasLegacyCoverageScript) {
|
|
8287
|
+
const legacy = new Set(legacyFiles);
|
|
8288
|
+
const coverageFiles = [
|
|
8289
|
+
...legacyFiles,
|
|
8290
|
+
...analysis.coverageFiles.filter((file) => !legacy.has(file))
|
|
8291
|
+
];
|
|
8292
|
+
return {
|
|
8293
|
+
coverageFiles,
|
|
8294
|
+
unresolvedFiles: analysis.unresolvedFiles.filter((file) => !legacy.has(file)),
|
|
8295
|
+
hasCoverageCommand: hasLegacyCoverageScript || analysis.hasCoverageCommand,
|
|
8296
|
+
detectorDecisions: analysis.detectorDecisions
|
|
8297
|
+
};
|
|
8298
|
+
}
|
|
7210
8299
|
function stripJavaScriptComments(contents) {
|
|
7211
8300
|
let output = "";
|
|
7212
8301
|
let quote = null;
|
|
@@ -7577,14 +8666,89 @@ function coverageFlagCommandState(words) {
|
|
|
7577
8666
|
if (executable === "vitest" || executable === "bnt") {
|
|
7578
8667
|
return flagState(args, ["--coverage"], ["--no-coverage"]);
|
|
7579
8668
|
}
|
|
8669
|
+
if (isPythonModuleRunner(executable, args)) {
|
|
8670
|
+
return flagState(args.slice(2), ["--cov"], ["--no-cov"]);
|
|
8671
|
+
}
|
|
8672
|
+
if (executable === "go") {
|
|
8673
|
+
if (args[0]?.toLowerCase() !== "test") return "none";
|
|
8674
|
+
const end = args.findIndex((arg, index) => index > 0 && /^--?args$/i.test(arg));
|
|
8675
|
+
return goTestCoverageState(end < 0 ? args.slice(1) : args.slice(1, end));
|
|
8676
|
+
}
|
|
8677
|
+
if (executable === "dotnet") {
|
|
8678
|
+
if (args[0]?.toLowerCase() !== "test") return "none";
|
|
8679
|
+
const end = args.indexOf("--", 1);
|
|
8680
|
+
return dotnetTestCoverageState(end < 0 ? args.slice(1) : args.slice(1, end));
|
|
8681
|
+
}
|
|
8682
|
+
if (executable === "cargo") {
|
|
8683
|
+
const subcommand = args[0]?.startsWith("+") ? args.slice(1) : args;
|
|
8684
|
+
if (subcommand[0]?.toLowerCase() !== "llvm-cov") return "none";
|
|
8685
|
+
if (["report", "clean", "show-env", "run"].includes(subcommand[1]?.toLowerCase() ?? "")) {
|
|
8686
|
+
return "none";
|
|
8687
|
+
}
|
|
8688
|
+
const end = subcommand.indexOf("--");
|
|
8689
|
+
const owned = end < 0 ? subcommand.slice(1) : subcommand.slice(1, end);
|
|
8690
|
+
return owned.some((arg) => arg.toLowerCase() === "--no-run") ? "none" : "collects";
|
|
8691
|
+
}
|
|
7580
8692
|
return "none";
|
|
7581
8693
|
}
|
|
8694
|
+
function isPythonModuleRunner(executable, args) {
|
|
8695
|
+
return /^(?:python[0-9.]*|py)$/.test(executable) && args[0] === "-m" && (args[1] === "pytest" || args[1] === "py.test");
|
|
8696
|
+
}
|
|
8697
|
+
function goTestCoverageState(args) {
|
|
8698
|
+
let enabled = false;
|
|
8699
|
+
let disabled = false;
|
|
8700
|
+
let unresolved = false;
|
|
8701
|
+
for (const raw of args) {
|
|
8702
|
+
const [name, assigned] = raw.toLowerCase().replace(/^--/u, "-").split("=", 2);
|
|
8703
|
+
if (name === "-covermode" || name === "-coverpkg" || name === "-coverprofile") {
|
|
8704
|
+
enabled = true;
|
|
8705
|
+
} else if (name === "-cover") {
|
|
8706
|
+
if (assigned === void 0 || /^(?:true|t|1)$/u.test(assigned)) enabled = true;
|
|
8707
|
+
else if (/^(?:false|f|0)$/u.test(assigned)) disabled = true;
|
|
8708
|
+
else unresolved = true;
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8711
|
+
if (unresolved || enabled && disabled) return "unresolved";
|
|
8712
|
+
return enabled ? "collects" : "none";
|
|
8713
|
+
}
|
|
8714
|
+
function dotnetTestCoverageState(args) {
|
|
8715
|
+
let enabled = false;
|
|
8716
|
+
let disabled = false;
|
|
8717
|
+
let unresolved = false;
|
|
8718
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
8719
|
+
const lower = (args[index] ?? "").toLowerCase();
|
|
8720
|
+
const collect = /^--collect(?:[:=]([\s\S]*))?$/u.exec(lower);
|
|
8721
|
+
if (collect) {
|
|
8722
|
+
const value = (collect[1] ?? args[index += 1]?.toLowerCase() ?? "").trim();
|
|
8723
|
+
if (/\$|\{|\}/u.test(value)) unresolved = true;
|
|
8724
|
+
else if (value === "xplat code coverage" || value === "code coverage") enabled = true;
|
|
8725
|
+
continue;
|
|
8726
|
+
}
|
|
8727
|
+
const property = /^(?:[/-]p|--property):([\s\S]*)$/u.exec(lower);
|
|
8728
|
+
if (!property) continue;
|
|
8729
|
+
for (const assignment of (property[1] ?? "").split(/[;,]/u)) {
|
|
8730
|
+
const [name, value] = assignment.split("=", 2).map((part) => part.trim());
|
|
8731
|
+
if (name !== "collectcoverage" || value === void 0) continue;
|
|
8732
|
+
if (value === "true") enabled = true;
|
|
8733
|
+
else if (value === "false") disabled = true;
|
|
8734
|
+
else unresolved = true;
|
|
8735
|
+
}
|
|
8736
|
+
}
|
|
8737
|
+
if (unresolved || enabled && disabled) return "unresolved";
|
|
8738
|
+
return enabled ? "collects" : "none";
|
|
8739
|
+
}
|
|
7582
8740
|
function isRecognizedCoverageFlagRunner(words) {
|
|
7583
8741
|
const command = unwrapPackageManagerExecutable(words);
|
|
7584
8742
|
const executable = basename(command[0] ?? "").replace(/\.cmd$/i, "").toLowerCase();
|
|
7585
8743
|
if (["bnt", "vitest", "jest", "pytest", "py.test"].includes(executable)) return true;
|
|
7586
8744
|
if (executable === "node") return command.slice(1).includes("--test");
|
|
7587
|
-
|
|
8745
|
+
if (isPythonModuleRunner(executable, command.slice(1))) return true;
|
|
8746
|
+
const subcommand = command[1]?.toLowerCase();
|
|
8747
|
+
if (executable === "go" || executable === "dotnet") return subcommand === "test";
|
|
8748
|
+
if (executable === "cargo") {
|
|
8749
|
+
return (subcommand?.startsWith("+") ? command[2] : command[1])?.toLowerCase() === "llvm-cov";
|
|
8750
|
+
}
|
|
8751
|
+
return (executable === "bun" || executable === "deno") && subcommand === "test";
|
|
7588
8752
|
}
|
|
7589
8753
|
function flagState(args, enablingNames, disablingNames) {
|
|
7590
8754
|
let enabled = false;
|
|
@@ -8057,8 +9221,9 @@ var RACK_ENTRYPOINT_FILE_PATTERN = /(^|\/)(?:config\.ru|(?:main|server|app|start
|
|
|
8057
9221
|
var SERVER_ENTRYPOINT_PATTERN = /\bapp\.listen\s*\(|\bserver\.listen\s*\(\s*(?:PORT|port|\d+)|http\.ListenAndServe\s*\(|http\.ListenAndServeTLS\s*\(|axum::Server(?:::|\.)|actix_web::HttpServer(?:::|\.)|uvicorn\.run\s*\(/;
|
|
8058
9222
|
var V20_DIRECT_HTTP_SERVER_PATTERN = /\b(?:http|https)\.createServer\s*\([\s\S]{0,1500}?\)\.listen\s*\(\s*(?:PORT|port|\d+)/;
|
|
8059
9223
|
var HEALTH_ROUTE_FUNCTIONS_DIR_PATTERN = /(^|\/)functions\/.*\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|cpp|cc|cxx|c|h|hpp|kt|swift|php)$/;
|
|
9224
|
+
var HEALTH_ROUTE_ENTRYPOINT_HOME_PATTERN = /^(?:(?:server|index|app|main)\.(?:ts|tsx|js|jsx|mjs|cjs)|(?:server|backend|api)\/.*\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|rb|php))$/;
|
|
8060
9225
|
var V20_HEALTH_OR_READINESS_ROUTE_PATTERN = /["'`](?:[\w${}./-]{0,60}\/[_-]?(?:(?:health|ready|live)z?|readiness|liveness|up)(?=["'`/])|(?:healthz|readiness|liveness)(?=["'`]))|@Get\s*\(\s*(["'`])health\1\s*\)/i;
|
|
8061
|
-
var EXPRESS_ERROR_MIDDLEWARE_PATTERN = /\(\s*(?:_?err|_?error)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*(?:req|request)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*(?:res|response)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*_?next(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*\)\s*(?:=>|\{)/i;
|
|
9226
|
+
var EXPRESS_ERROR_MIDDLEWARE_PATTERN = /\(\s*(?:_?err|_?error)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*_?(?:req|request)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*_?(?:res|response)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*_?next(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,?\s*\)\s*(?:=>|\{)/i;
|
|
8062
9227
|
var EXPRESS_MIDDLEWARE_REACHABLE_PATTERN = /\.use\s*\(|\bmodule\.exports\b|\bexport\s+(?:default\b|function\b|const\b|class\b)/;
|
|
8063
9228
|
var ERROR_BOUNDARY_FILENAME_PATTERN = /(^|\/)(?:error-boundary|error)\.(?:tsx|jsx|ts|js)$/;
|
|
8064
9229
|
var RACK_SERVER_ENTRYPOINT_PATTERN = /Rack::(?:Server|Handler(?:::\w+)?)\.(?:start|run)\s*\(/;
|
|
@@ -8243,6 +9408,231 @@ function isV24IgnoredScanFile(file) {
|
|
|
8243
9408
|
function findRootPackageJson(repoFiles) {
|
|
8244
9409
|
return repoFiles.includes("package.json") ? "package.json" : null;
|
|
8245
9410
|
}
|
|
9411
|
+
var WORKSPACE_PATTERN_LIMIT = 64;
|
|
9412
|
+
var WORKSPACE_PACKAGE_MANIFEST_LIMIT = 200;
|
|
9413
|
+
var NO_DECLARED_WORKSPACE_MANIFESTS = { read: [], declared: 0 };
|
|
9414
|
+
function findDeclaredWorkspaceManifests(repoPath, repoFiles, rootPackageJson) {
|
|
9415
|
+
const patterns = [];
|
|
9416
|
+
if (rootPackageJson) {
|
|
9417
|
+
const declared = readPackageJsonWorkspacePatterns(join(repoPath, rootPackageJson));
|
|
9418
|
+
if (declared === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
|
|
9419
|
+
patterns.push(...declared);
|
|
9420
|
+
}
|
|
9421
|
+
if (repoFiles.includes("pnpm-workspace.yaml")) {
|
|
9422
|
+
const declared = readPnpmWorkspacePatterns(join(repoPath, "pnpm-workspace.yaml"));
|
|
9423
|
+
if (declared === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
|
|
9424
|
+
patterns.push(...declared);
|
|
9425
|
+
}
|
|
9426
|
+
if (patterns.length === 0 || patterns.length > WORKSPACE_PATTERN_LIMIT) {
|
|
9427
|
+
return NO_DECLARED_WORKSPACE_MANIFESTS;
|
|
9428
|
+
}
|
|
9429
|
+
const include = [];
|
|
9430
|
+
const exclude = [];
|
|
9431
|
+
for (const pattern of patterns) {
|
|
9432
|
+
const negated = pattern.startsWith("!");
|
|
9433
|
+
const regex = workspaceGlobToRegExp(negated ? pattern.slice(1) : pattern);
|
|
9434
|
+
if (negated) {
|
|
9435
|
+
if (regex === null) return NO_DECLARED_WORKSPACE_MANIFESTS;
|
|
9436
|
+
exclude.push(regex);
|
|
9437
|
+
} else if (regex !== null) {
|
|
9438
|
+
include.push(regex);
|
|
9439
|
+
}
|
|
9440
|
+
}
|
|
9441
|
+
const matched = repoFiles.filter((file) => {
|
|
9442
|
+
if (file === rootPackageJson || !file.endsWith("/package.json")) return false;
|
|
9443
|
+
if (!isAuthoredProductionPath(file)) return false;
|
|
9444
|
+
const dir = file.slice(0, -"/package.json".length);
|
|
9445
|
+
return include.some((regex) => regex.test(dir)) && !exclude.some((regex) => regex.test(dir));
|
|
9446
|
+
}).sort();
|
|
9447
|
+
return { read: matched.slice(0, WORKSPACE_PACKAGE_MANIFEST_LIMIT), declared: matched.length };
|
|
9448
|
+
}
|
|
9449
|
+
function readPackageJsonWorkspacePatterns(packageJsonPath) {
|
|
9450
|
+
if (!isRegularFile(packageJsonPath)) return [];
|
|
9451
|
+
const parsed = parseJsonObject(readRepoText(packageJsonPath, "utf8"));
|
|
9452
|
+
const workspaces = parsed?.workspaces;
|
|
9453
|
+
if (workspaces === void 0) return [];
|
|
9454
|
+
const list = isRecord(workspaces) ? workspaces.packages : workspaces;
|
|
9455
|
+
if (list === void 0 && isRecord(workspaces)) return [];
|
|
9456
|
+
if (!Array.isArray(list) || !list.every((entry) => typeof entry === "string")) return null;
|
|
9457
|
+
return list;
|
|
9458
|
+
}
|
|
9459
|
+
function readPnpmWorkspacePatterns(yamlPath) {
|
|
9460
|
+
if (!isRegularFile(yamlPath)) return [];
|
|
9461
|
+
const patterns = [];
|
|
9462
|
+
let inPackages = false;
|
|
9463
|
+
for (const line of readRepoText(yamlPath, "utf8").split(/\r?\n/)) {
|
|
9464
|
+
if (!inPackages) {
|
|
9465
|
+
const key = /^packages\s*:(.*)$/.exec(line);
|
|
9466
|
+
if (!key) continue;
|
|
9467
|
+
const rest = stripYamlLineComment(key[1] ?? "").trim();
|
|
9468
|
+
if (rest === "") {
|
|
9469
|
+
inPackages = true;
|
|
9470
|
+
continue;
|
|
9471
|
+
}
|
|
9472
|
+
const flow = /^\[(.*)\]$/.exec(rest);
|
|
9473
|
+
if (!flow) return null;
|
|
9474
|
+
if ((flow[1] ?? "").trim() === "") return [];
|
|
9475
|
+
for (const item2 of (flow[1] ?? "").split(",")) {
|
|
9476
|
+
const scalar = yamlScalar(item2.trim());
|
|
9477
|
+
if (scalar === null) return null;
|
|
9478
|
+
patterns.push(scalar);
|
|
9479
|
+
}
|
|
9480
|
+
return patterns;
|
|
9481
|
+
}
|
|
9482
|
+
if (/^\s*(?:#.*)?$/.test(line)) continue;
|
|
9483
|
+
const item = /^\s*-\s+(.*)$/.exec(line);
|
|
9484
|
+
if (item) {
|
|
9485
|
+
const scalar = yamlScalar((item[1] ?? "").trim());
|
|
9486
|
+
if (scalar === null) return null;
|
|
9487
|
+
patterns.push(scalar);
|
|
9488
|
+
continue;
|
|
9489
|
+
}
|
|
9490
|
+
if (/^\S/.test(line)) break;
|
|
9491
|
+
return null;
|
|
9492
|
+
}
|
|
9493
|
+
return patterns;
|
|
9494
|
+
}
|
|
9495
|
+
function stripYamlLineComment(text) {
|
|
9496
|
+
return /^\s*#/.test(text) ? "" : text.replace(/\s+#.*$/, "");
|
|
9497
|
+
}
|
|
9498
|
+
function yamlScalar(raw) {
|
|
9499
|
+
const single = /^'((?:[^']|'')*)'\s*(?:#.*)?$/.exec(raw);
|
|
9500
|
+
if (single) return (single[1] ?? "").replace(/''/g, "'");
|
|
9501
|
+
const double = /^"([^"\\]*)"\s*(?:#.*)?$/.exec(raw);
|
|
9502
|
+
if (double) return double[1] ?? "";
|
|
9503
|
+
const plain = stripYamlLineComment(raw).trim();
|
|
9504
|
+
return plain === "" || /^["'[{&*|>]/.test(plain) ? null : plain;
|
|
9505
|
+
}
|
|
9506
|
+
function workspaceGlobToRegExp(pattern) {
|
|
9507
|
+
const normalised = pattern.trim().replace(/^\.\//, "").replace(/\/+$/, "");
|
|
9508
|
+
if (normalised === "" || normalised.startsWith("/") || /(^|\/)\.\.?(\/|$)/.test(normalised) || /[[\]{}()!+@\\]/.test(normalised)) {
|
|
9509
|
+
return null;
|
|
9510
|
+
}
|
|
9511
|
+
const segments = normalised.split("/").filter((segment, index, all) => !(segment === "**" && all[index - 1] === "**"));
|
|
9512
|
+
const anySegment = "(?!\\.)[^/]+";
|
|
9513
|
+
if (segments.length === 1 && segments[0] === "**") return new RegExp(`^${anySegment}(?:/${anySegment})*$`);
|
|
9514
|
+
let source = "";
|
|
9515
|
+
segments.forEach((segment, index) => {
|
|
9516
|
+
if (segment === "**") {
|
|
9517
|
+
source += index === 0 ? `(?:${anySegment}/)*` : `(?:/${anySegment})*`;
|
|
9518
|
+
return;
|
|
9519
|
+
}
|
|
9520
|
+
if (index > 0 && !(index === 1 && segments[0] === "**")) source += "/";
|
|
9521
|
+
const body = segment.split("").map(
|
|
9522
|
+
(char) => char === "*" ? "[^/]*" : char === "?" ? "[^/]" : char.replace(/[.^$|]/g, "\\$&")
|
|
9523
|
+
).join("");
|
|
9524
|
+
source += /^[*?]/.test(segment) ? `(?!\\.)${body}` : body;
|
|
9525
|
+
});
|
|
9526
|
+
return new RegExp(`^${source}$`);
|
|
9527
|
+
}
|
|
9528
|
+
function ciWorkflowCitationLabel(baseLabel, verb, considered, cited, unread, delegated = NO_DELEGATED_CI_STEPS) {
|
|
9529
|
+
const unreadTotal = [...unread.values()].reduce((sum, count) => sum + count, 0);
|
|
9530
|
+
const { crossRepositoryReusableWorkflows: crossRepository, compositeActions } = delegated;
|
|
9531
|
+
const delegatedTotal = crossRepository + compositeActions;
|
|
9532
|
+
if (considered <= cited && unreadTotal === 0 && delegatedTotal === 0) return baseLabel;
|
|
9533
|
+
const parts = [
|
|
9534
|
+
`${considered} workflow file${considered === 1 ? "" : "s"} ${verb}`,
|
|
9535
|
+
`${cited} cited`
|
|
9536
|
+
];
|
|
9537
|
+
if (unreadTotal > 0) {
|
|
9538
|
+
const reasons = [...unread.keys()].sort().join(", ");
|
|
9539
|
+
parts.push(`${unreadTotal} not read (${reasons})`);
|
|
9540
|
+
}
|
|
9541
|
+
if (delegatedTotal === 0) return `${baseLabel} (${parts.join("; ")})`;
|
|
9542
|
+
const delegatedCount = `${delegatedTotal} delegated step${delegatedTotal === 1 ? "" : "s"} not read`;
|
|
9543
|
+
const kinds = [
|
|
9544
|
+
...crossRepository > 0 ? [
|
|
9545
|
+
`${crossRepository} reusable workflow${crossRepository === 1 ? " in another repository" : "s in other repositories"}`
|
|
9546
|
+
] : [],
|
|
9547
|
+
...compositeActions > 0 ? [`${compositeActions} local action${compositeActions === 1 ? "" : "s"}`] : []
|
|
9548
|
+
];
|
|
9549
|
+
const full = `${baseLabel} (${[...parts, `${delegatedCount} (${kinds.join(", ")})`].join("; ")})`;
|
|
9550
|
+
return full.length <= EVIDENCE_LABEL_MAX_LENGTH ? full : `${baseLabel} (${[...parts, delegatedCount].join("; ")})`;
|
|
9551
|
+
}
|
|
9552
|
+
var EVIDENCE_LABEL_MAX_LENGTH = 180;
|
|
9553
|
+
var NO_DELEGATED_CI_STEPS = {
|
|
9554
|
+
crossRepositoryReusableWorkflows: 0,
|
|
9555
|
+
compositeActions: 0
|
|
9556
|
+
};
|
|
9557
|
+
function sumDelegatedCiSteps(left, right) {
|
|
9558
|
+
return {
|
|
9559
|
+
crossRepositoryReusableWorkflows: left.crossRepositoryReusableWorkflows + right.crossRepositoryReusableWorkflows,
|
|
9560
|
+
compositeActions: left.compositeActions + right.compositeActions
|
|
9561
|
+
};
|
|
9562
|
+
}
|
|
9563
|
+
var CROSS_REPOSITORY_REUSABLE_WORKFLOW_PATTERN = /^[^./\s][^/\s]*\/[^/\s]+\/\.github\/workflows\/[^@\s]+\.ya?ml@\S+$/u;
|
|
9564
|
+
function countDelegatedCiSteps(root, workflows) {
|
|
9565
|
+
let crossRepositoryReusableWorkflows = 0;
|
|
9566
|
+
let compositeActions = 0;
|
|
9567
|
+
for (const file of workflows) {
|
|
9568
|
+
if (!/^\.github\/workflows\//.test(file)) continue;
|
|
9569
|
+
const fullPath = join(root, file);
|
|
9570
|
+
if (!isRegularFile(fullPath)) continue;
|
|
9571
|
+
for (const uses of extractWorkflowUsesValues(readRepoText(fullPath, "utf8"))) {
|
|
9572
|
+
if (CROSS_REPOSITORY_REUSABLE_WORKFLOW_PATTERN.test(uses)) {
|
|
9573
|
+
crossRepositoryReusableWorkflows += 1;
|
|
9574
|
+
continue;
|
|
9575
|
+
}
|
|
9576
|
+
if (!uses.startsWith("./") || /^\.\/\.github\/workflows\//.test(uses)) continue;
|
|
9577
|
+
const directory = uses.slice(2).replace(/\/+$/u, "");
|
|
9578
|
+
if (directory.length === 0 || directory.split("/").includes("..")) continue;
|
|
9579
|
+
if (isRegularFile(join(root, directory, "action.yml")) || isRegularFile(join(root, directory, "action.yaml"))) {
|
|
9580
|
+
compositeActions += 1;
|
|
9581
|
+
}
|
|
9582
|
+
}
|
|
9583
|
+
}
|
|
9584
|
+
return { crossRepositoryReusableWorkflows, compositeActions };
|
|
9585
|
+
}
|
|
9586
|
+
function extractWorkflowUsesValues(contents) {
|
|
9587
|
+
const values = [];
|
|
9588
|
+
let blockScalarIndentation = null;
|
|
9589
|
+
for (const line of contents.split(/\r?\n/u)) {
|
|
9590
|
+
const indentation = /^\s*/u.exec(line)?.[0].length ?? 0;
|
|
9591
|
+
if (blockScalarIndentation !== null) {
|
|
9592
|
+
if (line.trim().length === 0 || indentation > blockScalarIndentation) continue;
|
|
9593
|
+
blockScalarIndentation = null;
|
|
9594
|
+
}
|
|
9595
|
+
const match = /^\s*(?:-\s*)?uses\s*:\s*(.*)$/u.exec(line);
|
|
9596
|
+
if (match) {
|
|
9597
|
+
const value = (match[1] ?? "").replace(/\s+#.*$/u, "").trim();
|
|
9598
|
+
const quoted = /^(?:"(.*)"|'(.*)')$/u.exec(value);
|
|
9599
|
+
values.push(quoted ? quoted[1] ?? quoted[2] ?? "" : value);
|
|
9600
|
+
continue;
|
|
9601
|
+
}
|
|
9602
|
+
if (/:\s*[|>][-+0-9]*\s*(?:#.*)?$/u.test(line)) blockScalarIndentation = indentation;
|
|
9603
|
+
}
|
|
9604
|
+
return values;
|
|
9605
|
+
}
|
|
9606
|
+
function unreadCiWorkflowReasons(includes) {
|
|
9607
|
+
const unread = /* @__PURE__ */ new Map();
|
|
9608
|
+
for (const { path, reasons } of withheldRepoPaths()) {
|
|
9609
|
+
if (!isCiWorkflow(path) || !includes(path)) continue;
|
|
9610
|
+
const reason = [...reasons].sort()[0];
|
|
9611
|
+
if (reason !== void 0) unread.set(reason, (unread.get(reason) ?? 0) + 1);
|
|
9612
|
+
}
|
|
9613
|
+
return unread;
|
|
9614
|
+
}
|
|
9615
|
+
function rootOnlyPackageScriptsDisclosure(repoFiles, rootPackageJson) {
|
|
9616
|
+
const others = repoFiles.filter(
|
|
9617
|
+
(file) => file !== rootPackageJson && /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
|
|
9618
|
+
).length;
|
|
9619
|
+
if (others === 0) return null;
|
|
9620
|
+
const present = `${others}${rootPackageJson ? " other" : ""} package.json file${others === 1 ? "" : "s"} present`;
|
|
9621
|
+
return `${rootPackageJson ? "" : "no root package.json found; "}package scripts for this metric are read from the root package.json only; ${present}`;
|
|
9622
|
+
}
|
|
9623
|
+
function workspacePackageScriptsDisclosure(repoFiles, rootPackageJson, workspace) {
|
|
9624
|
+
const readCount = workspace.read.length;
|
|
9625
|
+
const workspaceRead = workspace.declared > readCount ? `the first ${readCount} of ${workspace.declared} declared workspace package.json files` : `${readCount} declared workspace package.json file${readCount === 1 ? "" : "s"}`;
|
|
9626
|
+
const read = new Set(workspace.read);
|
|
9627
|
+
const others = repoFiles.filter(
|
|
9628
|
+
(file) => file !== rootPackageJson && !read.has(file) && /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
|
|
9629
|
+
).length;
|
|
9630
|
+
return [
|
|
9631
|
+
...rootPackageJson ? [] : ["no root package.json found"],
|
|
9632
|
+
`package scripts for this metric are read from ${rootPackageJson ? `the root package.json and ${workspaceRead}` : workspaceRead}`,
|
|
9633
|
+
...others > 0 ? [`${others} other package.json file${others === 1 ? "" : "s"} present`] : []
|
|
9634
|
+
].join("; ");
|
|
9635
|
+
}
|
|
8246
9636
|
function isRegularFile(path) {
|
|
8247
9637
|
try {
|
|
8248
9638
|
return lstatSync(path).isFile();
|