@cejel/cejel 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,19 +5,24 @@ import {
5
5
  describeGitFailure,
6
6
  execGit,
7
7
  isExpectedGitAbsence
8
- } from "./chunk-EPK7UTIK.js";
8
+ } from "./chunk-OIGCYULH.js";
9
9
  import {
10
10
  external_exports
11
- } from "./chunk-KGFGX3TP.js";
11
+ } from "./chunk-UYJBSQCV.js";
12
12
  import {
13
+ MAX_REPOSITORY_CONTENT_BYTES,
14
+ contentReadSignalKey,
13
15
  init_esm_shims,
14
16
  readRepoText,
15
17
  readRepoTextPrefix,
16
18
  recordContentSkip,
19
+ recordContentSkipForCriteria,
17
20
  recordFilesystemSkip,
18
21
  trackContentReads,
19
- withContentReadCriterion
20
- } from "./chunk-R4DPQLS2.js";
22
+ withContentReadCriterion,
23
+ withContentReadSignal,
24
+ withContentReadSignals
25
+ } from "./chunk-ZNVILRHK.js";
21
26
 
22
27
  // src/witan/schemas.ts
23
28
  init_esm_shims();
@@ -117,10 +122,26 @@ var WitanEvidencePointerSchema = external_exports.object({
117
122
  }).strict().refine((value) => Boolean(value.url ?? value.path ?? value.contentHash), {
118
123
  message: "Witan evidence must include a path, url, or content hash."
119
124
  });
125
+ var WitanInventoryScanDerivationSchema = external_exports.object({
126
+ kind: external_exports.literal("inventory-scan"),
127
+ inventoryScope: external_exports.enum(["tracked-scan-eligible", "tracked-complete"]),
128
+ inventoryCount: external_exports.number().int().nonnegative(),
129
+ patternSetId: external_exports.string().min(1).max(180),
130
+ patternCount: external_exports.number().int().positive(),
131
+ matchCount: external_exports.number().int().nonnegative()
132
+ }).strict();
133
+ var WitanFindingDerivationSchema = external_exports.discriminatedUnion("kind", [
134
+ WitanInventoryScanDerivationSchema
135
+ ]);
120
136
  var WitanFindingSchema = external_exports.object({
121
137
  severity: WitanFindingSeveritySchema,
122
138
  summary: external_exports.string().min(1).max(500),
123
- evidence: WitanEvidencePointerSchema
139
+ evidence: WitanEvidencePointerSchema,
140
+ // Absence findings emitted by the native repository detector must name the exact
141
+ // inventory and versioned pattern set whose zero result supports the proposition.
142
+ // This remains optional at the shared schema boundary because positive findings and
143
+ // semantic derivations are separate contracts; native absence emitters require it.
144
+ derivation: WitanFindingDerivationSchema.optional()
124
145
  }).strict();
125
146
  var WitanCriterionMetricSchema = external_exports.object({
126
147
  name: external_exports.string().min(1).max(80),
@@ -321,6 +342,7 @@ var WitanReportSchema = external_exports.preprocess((value) => {
321
342
  }, StrictWitanReportSchema);
322
343
  var WITAN_ATTESTATION_STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
323
344
  var WITAN_ATTESTATION_PREDICATE_TYPE = "https://cejel.dev/attestations/scan/v1";
345
+ var WITAN_REPORT_FORMAT_VERSION = "1.0";
324
346
  var WitanAttestationOutcomeSchema = external_exports.discriminatedUnion("status", [
325
347
  external_exports.object({
326
348
  status: external_exports.literal("scored"),
@@ -350,7 +372,16 @@ var WitanAttestationStatementSchema = external_exports.object({
350
372
  version: external_exports.string().min(1).max(80)
351
373
  }).strict(),
352
374
  generatedAt: external_exports.string().datetime({ offset: true }),
375
+ // Optional on read for attestations emitted before Evidence Seam v1. New attestations
376
+ // always carry it, keeping report.json byte-stable while giving paired consumers an
377
+ // explicit report contract version.
378
+ reportFormatVersion: external_exports.string().regex(/^1\.\d+$/).optional(),
353
379
  rubricVersion: external_exports.string().min(1).max(120),
380
+ // Only present when the scan ran under the GitHub Action (GITHUB_RUN_ATTEMPT observed in
381
+ // the runner environment). Absent, never fabricated or defaulted to "1", for a local CLI
382
+ // or any other CI. Not part of report.json: it is per-invocation, not per-repository-
383
+ // state, and report.json's reproducibility guarantee excludes exactly that kind of field.
384
+ githubRunAttempt: external_exports.string().regex(/^[1-9][0-9]*$/).optional(),
354
385
  repository: external_exports.object({
355
386
  productSlug: external_exports.string().regex(/^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/),
356
387
  url: external_exports.string().url().optional(),
@@ -392,13 +423,15 @@ var WITAN_RUBRIC_VERSION_V19 = "witan-rubric-v19-prospective-2026-08-09";
392
423
  var WITAN_RUBRIC_VERSION_V20 = "witan-rubric-v20-prospective-2026-08-10";
393
424
  var WITAN_RUBRIC_VERSION_V21 = "witan-rubric-v21-prospective-2026-08-10";
394
425
  var WITAN_RUBRIC_VERSION_V22 = "witan-rubric-v22-prospective-2026-08-10";
426
+ var WITAN_RUBRIC_VERSION_V23 = "witan-rubric-v23-prospective-2026-09-06";
395
427
  var WITAN_LAST_CALIBRATED_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V17;
396
428
  var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
397
429
  WITAN_RUBRIC_VERSION_V18,
398
430
  WITAN_RUBRIC_VERSION_V19,
399
431
  WITAN_RUBRIC_VERSION_V20,
400
432
  WITAN_RUBRIC_VERSION_V21,
401
- WITAN_RUBRIC_VERSION_V22
433
+ WITAN_RUBRIC_VERSION_V22,
434
+ WITAN_RUBRIC_VERSION_V23
402
435
  ]);
403
436
  var WITAN_SELECTABLE_RUBRIC_VERSIONS = Object.freeze([
404
437
  WITAN_LAST_CALIBRATED_RUBRIC_VERSION,
@@ -421,7 +454,9 @@ init_esm_shims();
421
454
  // src/witan/abstention.ts
422
455
  init_esm_shims();
423
456
  var WITAN_NO_MEASUREMENT_REASON = "No free-core rubric criterion produced a measurable signal. Cejel abstains rather than publish a numeric zero for an entirely unmeasured repository.";
424
- var WITAN_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.";
457
+ 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.";
458
+ 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.";
459
+ 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.";
425
460
  function isWitanNoMeasurementAbstention(report) {
426
461
  return report.verdict === "insufficient_source" && report.insufficientSourceReason.startsWith(WITAN_NO_MEASUREMENT_REASON) && report.criteria.every(
427
462
  ({ status }) => status === "not_applicable" || status === "insufficient_data"
@@ -785,6 +820,32 @@ function glossaryEntryForMetric(metric2) {
785
820
  function formatCertificateMetricLabel(metric2) {
786
821
  return CERTIFICATE_METRIC_REGISTRY[metric2.name]?.displayLabel ?? metric2.label;
787
822
  }
823
+ function formatCertificateMetricAppliedWeightPercent(criterion, metric2) {
824
+ const metrics = criterion.metrics;
825
+ const index = metrics.indexOf(metric2);
826
+ if (index === -1) {
827
+ throw new Error(
828
+ "formatCertificateMetricAppliedWeightPercent: metric does not belong to criterion.metrics"
829
+ );
830
+ }
831
+ const totalWeight = metrics.reduce((sum, m) => sum + m.weight, 0);
832
+ if (totalWeight <= 0) return 0;
833
+ const shares = metrics.map((m, i) => {
834
+ const raw = m.weight / totalWeight * 100;
835
+ const floor = Math.floor(raw);
836
+ return { i, floor, fraction: raw - floor };
837
+ });
838
+ const flooredTotal = shares.reduce((sum, s) => sum + s.floor, 0);
839
+ const byLargestRemainder = [...shares].sort((a, b) => b.fraction - a.fraction);
840
+ const result = new Map(shares.map((s) => [s.i, s.floor]));
841
+ let remainder = Math.round(100 - flooredTotal);
842
+ for (const s of byLargestRemainder) {
843
+ if (remainder <= 0) break;
844
+ result.set(s.i, (result.get(s.i) ?? s.floor) + 1);
845
+ remainder -= 1;
846
+ }
847
+ return result.get(index) ?? 0;
848
+ }
788
849
  function glossaryEntriesForReport(report) {
789
850
  const entries = new Map(CERTIFICATE_GLOSSARY.map((entry) => [entry.key, entry]));
790
851
  for (const metric2 of report.criteria.flatMap((criterion) => criterion.metrics)) {
@@ -900,6 +961,74 @@ function formatCertificateMetricValue(criterion, metric2) {
900
961
  `${formatMetricNumber(metric2.value)}/${formatMetricNumber(metric2.max)}${unit}`
901
962
  );
902
963
  }
964
+ var NEXT_STEP_FINDING_SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning"]);
965
+ var NEXT_STEP_FINDING_SEVERITY_RANK = {
966
+ critical: 0,
967
+ warning: 1,
968
+ info: 2
969
+ };
970
+ var MAX_NEXT_STEP_FINDINGS = 3;
971
+ function buildNextSteps(report, unmeasured, notApplicable, a1, coverageMetric) {
972
+ const steps = [];
973
+ if (unmeasured.length > 0) {
974
+ const ids = unmeasured.map((criterion) => criterion.id).join(", ");
975
+ steps.push(
976
+ `Obtain measurable evidence for ${ids} \u2014 ${unmeasured.length === 1 ? "this scored dimension has" : "these scored dimensions have"} no recorded result, and could change the verdict.`
977
+ );
978
+ }
979
+ const criterionFindings = report.criteria.flatMap(
980
+ (criterion) => criterion.findings.filter((finding) => NEXT_STEP_FINDING_SEVERITIES.has(finding.severity)).map((finding) => ({ criterionId: criterion.id, finding }))
981
+ );
982
+ const sortedFindings = [...criterionFindings].sort(
983
+ (a, b) => NEXT_STEP_FINDING_SEVERITY_RANK[a.finding.severity] - NEXT_STEP_FINDING_SEVERITY_RANK[b.finding.severity]
984
+ );
985
+ for (const { criterionId, finding } of sortedFindings.slice(0, MAX_NEXT_STEP_FINDINGS)) {
986
+ steps.push(
987
+ `Evidence is currently absent for the ${finding.severity} finding on ${criterionId}: obtain independent verification before relying on this criterion.`
988
+ );
989
+ }
990
+ if (sortedFindings.length > MAX_NEXT_STEP_FINDINGS) {
991
+ steps.push(
992
+ `${sortedFindings.length - MAX_NEXT_STEP_FINDINGS} more critical/warning finding(s) need the same independent verification; see the findings listed above for the complete set.`
993
+ );
994
+ }
995
+ if (a1 && coverageMetric && isCoverageNotMeasured(a1, coverageMetric)) {
996
+ steps.push(
997
+ "Obtain a coverage report or configured threshold for A1's static coverage metric; none was found, so it was not measured."
998
+ );
999
+ }
1000
+ if (!report.repo.headSha && report.criteria.some(
1001
+ (criterion) => criterion.metrics.some((metric2) => metric2.name === "pr_merge_ratio")
1002
+ )) {
1003
+ steps.push(
1004
+ "Restore Git history access so the recent-commit PR-reference proxy has commit subjects to inspect; its scoring zero here is a conservative default, not a detected outcome."
1005
+ );
1006
+ }
1007
+ if (notApplicable.length > 0) {
1008
+ const ids = notApplicable.map((criterion) => criterion.id).join(", ");
1009
+ steps.push(
1010
+ `Confirm ${ids} ${notApplicable.length === 1 ? "is" : "are"} correctly marked not applicable to this repository; ${notApplicable.length === 1 ? "it was" : "they were"} not examined.`
1011
+ );
1012
+ }
1013
+ const scanLimitationCount = report.scanLimitations?.length ?? 0;
1014
+ if (scanLimitationCount > 0) {
1015
+ steps.push(
1016
+ `Resolve the ${scanLimitationCount} scan limitation${scanLimitationCount === 1 ? "" : "s"} qualifying the readable evidence, then reproduce the scan.`
1017
+ );
1018
+ }
1019
+ const skippedCount = report.contentReadSummary?.skipped ?? 0;
1020
+ if (skippedCount > 0) {
1021
+ steps.push(
1022
+ `Make the ${skippedCount} skipped content entr${skippedCount === 1 ? "y" : "ies"} readable (see the itemized reasons in the certificate), then reproduce the scan.`
1023
+ );
1024
+ }
1025
+ if (steps.length === 0) {
1026
+ steps.push(
1027
+ "No unresolved evidence gaps were identified across the categories Cejel tracks; reproduce the scan at the reported revision when one is available before relying on this certificate."
1028
+ );
1029
+ }
1030
+ return steps;
1031
+ }
903
1032
  function buildRelyingPartySummary(report) {
904
1033
  const applicable = report.criteria.filter((criterion) => criterion.status !== "not_applicable");
905
1034
  const measured = applicable.filter((criterion) => criterion.status !== "insufficient_data");
@@ -947,7 +1076,7 @@ function buildRelyingPartySummary(report) {
947
1076
  examined: `Cejel examined the repository evidence recorded for ${report.productDisplayName}${revision} under ${report.rubricVersion}.`,
948
1077
  established: `The report established measured results for ${measured.length} of ${applicable.length} applicable rubric dimensions and recorded ${findings} evidence-backed finding${findings === 1 ? "" : "s"}.`,
949
1078
  notEstablished: gaps.join(" "),
950
- next: "Review the cited evidence and open or unverified items, reproduce the scan at the reported revision when one is available, and obtain missing measurements or resolve limitations before deciding whether to accept the code."
1079
+ next: buildNextSteps(report, unmeasured, notApplicable, a1, coverageMetric).join(" ")
951
1080
  };
952
1081
  }
953
1082
  function formatMetricNumber(value) {
@@ -1027,6 +1156,7 @@ function formatMetricMember(criterion, metric2) {
1027
1156
  }
1028
1157
 
1029
1158
  // src/witan/html.ts
1159
+ var CEJEL_CERTIFICATE_FORMAT_VERSION = "1.0";
1030
1160
  function renderWitanHtmlReport(report, options = {}) {
1031
1161
  const codeCriteria = criteriaByCategory(report, "code_trust");
1032
1162
  const processCriteria = criteriaByCategory(report, "process_trust");
@@ -1045,6 +1175,7 @@ function renderWitanHtmlReport(report, options = {}) {
1045
1175
  <head>
1046
1176
  <meta charset="utf-8">
1047
1177
  <meta name="viewport" content="width=device-width, initial-scale=1">
1178
+ <meta name="cejel-certificate-format" content="${CEJEL_CERTIFICATE_FORMAT_VERSION}">
1048
1179
  <title>Cejel Trust Certificate - ${escapeHtml(report.productDisplayName)}</title>
1049
1180
  <style>${CERTIFICATE_CSS}</style>
1050
1181
  </head>
@@ -1064,6 +1195,7 @@ function renderWitanHtmlReport(report, options = {}) {
1064
1195
  ${options.generatedAt ? `<div><dt>Date</dt><dd>${escapeHtml(formatDate(options.generatedAt))}</dd></div>` : ""}
1065
1196
  <div><dt>Run</dt><dd>${escapeHtml(renderRepo(report))}</dd></div>
1066
1197
  <div><dt>CLI</dt><dd>${escapeHtml(options.cliVersion ? `Cejel ${options.cliVersion}` : "Not recorded")}</dd></div>
1198
+ ${options.runAttempt ? `<div><dt>Run attempt</dt><dd>${escapeHtml(options.runAttempt)}</dd></div>` : ""}
1067
1199
  <div><dt>Rubric</dt><dd>${escapeHtml(report.rubricVersion)}</dd></div>
1068
1200
  ${// Gated on the rubric actually being prospective — a calibrated (v17) certificate's
1069
1201
  // HTML stays byte-identical to before --rubric-pin existed.
@@ -1086,6 +1218,8 @@ function renderWitanHtmlReport(report, options = {}) {
1086
1218
  </div>
1087
1219
  </header>
1088
1220
 
1221
+ ${renderFindingsFirstSummary(report)}
1222
+
1089
1223
  ${renderRelyingPartySummary(relyingPartySummary)}
1090
1224
 
1091
1225
  ${(report.scanLimitations?.length ?? 0) > 0 ? `<section class="scan-limitations-section evidence-section" aria-label="Scan limitations">
@@ -1127,6 +1261,26 @@ function renderWitanHtmlReport(report, options = {}) {
1127
1261
  </html>
1128
1262
  `;
1129
1263
  }
1264
+ var FINDINGS_FIRST_SEVERITY_ORDER = {
1265
+ critical: 0,
1266
+ warning: 1,
1267
+ info: 2
1268
+ };
1269
+ function collectFindingsFirstEntries(report) {
1270
+ return report.criteria.flatMap(
1271
+ (criterion) => criterion.findings.filter((finding) => finding.severity === "critical" || finding.severity === "warning").map((finding) => ({ criterion, finding }))
1272
+ ).sort(
1273
+ (a, b) => FINDINGS_FIRST_SEVERITY_ORDER[a.finding.severity] - FINDINGS_FIRST_SEVERITY_ORDER[b.finding.severity]
1274
+ );
1275
+ }
1276
+ function renderFindingsFirstSummary(report) {
1277
+ const entries = collectFindingsFirstEntries(report);
1278
+ const body = entries.length > 0 ? `<ul>${entries.map(({ criterion, finding }) => `<li>${renderFindingEvidence(criterion, finding)}</li>`).join("")}</ul>` : '<p class="findings-first-empty">No critical or warning findings were identified across any criterion in this scan.</p>';
1279
+ return `<section class="findings-first-summary" aria-labelledby="findings-first-heading">
1280
+ <h2 id="findings-first-heading">Critical and warning findings</h2>
1281
+ ${body}
1282
+ </section>`;
1283
+ }
1130
1284
  function renderRelyingPartySummary(summary) {
1131
1285
  return `<section class="relying-party-summary" aria-labelledby="relying-party-heading">
1132
1286
  <h2 id="relying-party-heading">How to read this certificate</h2>
@@ -1140,10 +1294,12 @@ function renderRelyingPartySummary(summary) {
1140
1294
  }
1141
1295
  function renderGlossary(entries) {
1142
1296
  return `<section class="glossary" aria-labelledby="glossary-heading">
1143
- <h2 id="glossary-heading">Plain-language glossary</h2>
1144
- <dl>${entries.map(
1297
+ <details>
1298
+ <summary><h2 id="glossary-heading">Plain-language glossary</h2></summary>
1299
+ <dl>${entries.map(
1145
1300
  (entry) => `<div><dt id="glossary-${escapeAttribute(entry.key)}">${escapeHtml(entry.term)}</dt><dd>${escapeHtml(entry.definition)}</dd></div>`
1146
1301
  ).join("")}</dl>
1302
+ </details>
1147
1303
  </section>`;
1148
1304
  }
1149
1305
  function renderContentReadSummary(report) {
@@ -1218,12 +1374,12 @@ function renderCriterionColumn(title, criteria, gitHistoryUnavailable) {
1218
1374
  </section>`;
1219
1375
  }
1220
1376
  function renderNotApplicableGroup(criteria) {
1221
- return `<div class="na-group">
1222
- <h3 class="na-heading">Not applicable to this repository</h3>
1377
+ return `<details class="na-group">
1378
+ <summary><h3 class="na-heading">Not applicable to this repository</h3></summary>
1223
1379
  <ul class="na-list">
1224
1380
  ${criteria.map(renderNotApplicableItem).join("")}
1225
1381
  </ul>
1226
- </div>`;
1382
+ </details>`;
1227
1383
  }
1228
1384
  function renderNotApplicableItem(criterion) {
1229
1385
  const reason = criterion.notes ?? "Not applicable to this repository.";
@@ -1283,15 +1439,21 @@ function scoreBandForPresentation(score) {
1283
1439
  if (score >= 1.5) return "warning";
1284
1440
  return "critical";
1285
1441
  }
1442
+ function renderMetricWeightBadge(criterion, metric2) {
1443
+ const appliedSharePercent = formatCertificateMetricAppliedWeightPercent(criterion, metric2);
1444
+ return `<span class="metric-weight">weight ${appliedSharePercent}% of ${escapeHtml(criterion.id)}</span>`;
1445
+ }
1286
1446
  function renderMetric(criterion, metric2) {
1287
1447
  const entry = glossaryEntryForMetric(metric2);
1288
1448
  const tooltipId = `tooltip-${criterion.id}-${entry.key}`;
1289
1449
  const formatted = formatCertificateMetricValue(criterion, metric2);
1450
+ const weightBadge = renderMetricWeightBadge(criterion, metric2);
1290
1451
  const cappedEntry = formatted.includes("capped") ? CERTIFICATE_GLOSSARY.find((candidate) => candidate.key === "capped") : void 0;
1291
1452
  const label = `<span class="term-help" tabindex="0" aria-describedby="${escapeAttribute(tooltipId)}"><strong>${escapeHtml(formatCertificateMetricLabel(metric2))}</strong><span class="term-tooltip" id="${escapeAttribute(tooltipId)}" role="tooltip">${escapeHtml(entry.definition)}</span></span>`;
1292
- if (!cappedEntry) return `${label}<span class="metric-value">${escapeHtml(formatted)}</span>`;
1453
+ if (!cappedEntry)
1454
+ return `${label}<span class="metric-value">${escapeHtml(formatted)} ${weightBadge}</span>`;
1293
1455
  const cappedTooltipId = `${tooltipId}-capped`;
1294
- return `${label}<span class="term-help metric-value" tabindex="0" aria-describedby="${escapeAttribute(cappedTooltipId)}">${escapeHtml(formatted)}<span class="term-tooltip" id="${escapeAttribute(cappedTooltipId)}" role="tooltip">${escapeHtml(cappedEntry.definition)}</span></span>`;
1456
+ return `${label}<span class="term-help metric-value" tabindex="0" aria-describedby="${escapeAttribute(cappedTooltipId)}">${escapeHtml(formatted)} ${weightBadge}<span class="term-tooltip" id="${escapeAttribute(cappedTooltipId)}" role="tooltip">${escapeHtml(cappedEntry.definition)}</span></span>`;
1295
1457
  }
1296
1458
  function renderCategoryScore(score, coverage, category) {
1297
1459
  const categoryCoverage = coverage.byCategory.find((entry) => entry.category === category);
@@ -1439,6 +1601,9 @@ dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
1439
1601
  .subscores { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; color: var(--muted); font-family: var(--mono); font-size: 12px; }
1440
1602
  .coverage-note { margin-top: 8px; color: var(--faint); font-family: var(--mono); font-size: 11px; }
1441
1603
  .source-counts { margin: 8px 0 0; padding-left: 18px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
1604
+ .findings-first-summary { margin-top: 20px; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface); padding: 22px; }
1605
+ .findings-first-summary ul { margin: 0; padding-left: 18px; display: grid; gap: 10px; }
1606
+ .findings-first-empty { margin: 0; color: var(--muted); }
1442
1607
  .relying-party-summary, .glossary { margin-top: 28px; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface); padding: 22px; }
1443
1608
  .relying-party-summary dl { display: grid; gap: 14px; margin: 0; }
1444
1609
  .relying-party-summary dl div { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 18px; }
@@ -1446,6 +1611,14 @@ dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
1446
1611
  .glossary dl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 22px; margin: 0; }
1447
1612
  .glossary dl div { border-top: 1px solid var(--line); padding-top: 10px; }
1448
1613
  .glossary dd { margin-top: 4px; font-size: 13px; }
1614
+ /* Track A3: native <details>/<summary> disclosure \u2014 the summary is the clickable control, a
1615
+ heading nests inside it as its accessible label (still exposed as a heading), and the heading's
1616
+ own vertical margin is neutralized by making it inline, so the marker sits flush against it. */
1617
+ summary { cursor: pointer; }
1618
+ summary h2, summary h3 { display: inline; }
1619
+ summary::marker { color: var(--periwinkle); }
1620
+ summary:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--periwinkle); border-radius: 3px; }
1621
+ .glossary details[open] dl, .na-group[open] .na-list { margin-top: 16px; }
1449
1622
  .trust-grid, .evidence-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 28px; }
1450
1623
  .scan-limitations-section { margin-top: 28px; border-color: rgba(231, 191, 114, .44); }
1451
1624
  .scan-limitations-section .scan-warning { list-style: none; margin-left: -18px; }
@@ -1477,6 +1650,7 @@ h3 { font-size: 15px; line-height: 1.35; font-weight: 600; margin-bottom: 0; }
1477
1650
  .criterion-metrics li { display: flex; justify-content: space-between; gap: 12px; border-bottom: 1px solid rgba(238, 244, 247, .08); padding-bottom: 6px; }
1478
1651
  .criterion-metrics strong { color: var(--text); font-weight: 650; }
1479
1652
  .criterion-metrics span { color: var(--muted); }
1653
+ .criterion-metrics .metric-weight { font-size: 10px; color: var(--faint); }
1480
1654
  .criterion-metrics > li > .metric-value {
1481
1655
  min-width: 0; white-space: normal; overflow-wrap: anywhere; text-align: right;
1482
1656
  }
@@ -1639,6 +1813,8 @@ function createWitanAttestation(report, options) {
1639
1813
  predicate: {
1640
1814
  tool: { name: "cejel", version: options.toolVersion },
1641
1815
  generatedAt: options.generatedAt,
1816
+ reportFormatVersion: WITAN_REPORT_FORMAT_VERSION,
1817
+ ...options.githubRunAttempt ? { githubRunAttempt: options.githubRunAttempt } : {},
1642
1818
  rubricVersion: report.rubricVersion,
1643
1819
  repository: {
1644
1820
  productSlug: report.productSlug,
@@ -1724,6 +1900,23 @@ init_esm_shims();
1724
1900
  // src/witan/markdown.ts
1725
1901
  init_esm_shims();
1726
1902
 
1903
+ // src/presentation-safety.ts
1904
+ init_esm_shims();
1905
+ var PRESENTATION_CONTROL_PATTERN = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
1906
+ function sanitizePresentationLine(value, options) {
1907
+ const withoutControls = value.replace(PRESENTATION_CONTROL_PATTERN, "").trim();
1908
+ const safeValue = withoutControls.length > 0 ? withoutControls : options.fallback;
1909
+ if (safeValue.length <= options.maxLength) return safeValue;
1910
+ const ellipsis = ".".repeat(Math.min(3, options.maxLength));
1911
+ const contentLimit = Math.max(options.maxLength - ellipsis.length, 0);
1912
+ let prefix = "";
1913
+ for (const character of safeValue) {
1914
+ if (prefix.length + character.length > contentLimit) break;
1915
+ prefix += character;
1916
+ }
1917
+ return `${prefix}${ellipsis}`;
1918
+ }
1919
+
1727
1920
  // src/witan/repo-signals.ts
1728
1921
  init_esm_shims();
1729
1922
  import { createHash as createHash2 } from "crypto";
@@ -1743,27 +1936,148 @@ function stripBom(text) {
1743
1936
 
1744
1937
  // src/witan/repo-signals.ts
1745
1938
  function usesV17DetectorClosure(rubricVersion) {
1746
- return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
1939
+ return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
1940
+ }
1941
+ var LEGACY_INVENTORY_ABSENCE_CONTEXT = {
1942
+ rubricGeneration: "legacy",
1943
+ inventoryProvenance: "directory-walk"
1944
+ };
1945
+ var INVENTORY_SCAN_PATTERN_SETS = {
1946
+ a1AuthenticatedAbsence: {
1947
+ id: "cejel.core-a1.test-integrity-surface.v1",
1948
+ count: 4
1949
+ },
1950
+ a1ConcreteTests: {
1951
+ id: "cejel.core-a1.concrete-test-files.v1",
1952
+ count: 16
1953
+ },
1954
+ a1CoverageConfigurationV1: {
1955
+ id: "cejel.core-a1.coverage-configuration.v1",
1956
+ count: 10
1957
+ },
1958
+ a1CoverageConfigurationV2: {
1959
+ id: "cejel.core-a1.coverage-configuration.v2",
1960
+ count: 14
1961
+ },
1962
+ a2CurrentSecret: {
1963
+ id: "cejel.core-a2.current-secret-shape.v1",
1964
+ count: 3
1965
+ },
1966
+ a2RlsPolicy: {
1967
+ id: "cejel.core-a2.rls-policy.v1",
1968
+ count: 3
1969
+ },
1970
+ a3CiOrReleaseDeploy: {
1971
+ id: "cejel.core-a3.ci-or-release-deploy.v1",
1972
+ count: 12
1973
+ },
1974
+ a4Lockfile: {
1975
+ id: "cejel.core-a4.lockfile.v1",
1976
+ count: 11
1977
+ },
1978
+ a5ClaimRealityArtifact: {
1979
+ id: "cejel.core-a5.claim-reality-artifact.v1",
1980
+ count: 3
1981
+ }
1982
+ };
1983
+ function inventoryScanDerivation(inventoryCount, patternSet, matchCount) {
1984
+ return {
1985
+ kind: "inventory-scan",
1986
+ inventoryScope: "tracked-scan-eligible",
1987
+ inventoryCount,
1988
+ patternSetId: patternSet.id,
1989
+ patternCount: patternSet.count,
1990
+ matchCount
1991
+ };
1992
+ }
1993
+ function inventoryAbsenceFinding(options) {
1994
+ const { context, summaries } = options;
1995
+ const summary = context.rubricGeneration === "legacy" ? summaries.legacy : context.inventoryProvenance === "tracked-scan-eligible" ? summaries.currentTracked : summaries.currentDirectoryWalk;
1996
+ const emitsDerivation = context.rubricGeneration === "current" && context.inventoryProvenance === "tracked-scan-eligible";
1997
+ return {
1998
+ severity: options.severity,
1999
+ summary,
2000
+ evidence: options.evidence,
2001
+ ...emitsDerivation ? {
2002
+ derivation: inventoryScanDerivation(
2003
+ options.inventoryCount,
2004
+ options.patternSet,
2005
+ options.matchCount
2006
+ )
2007
+ } : {}
2008
+ };
2009
+ }
2010
+ function wholesaleCriterionAbstention(signal) {
2011
+ return {
2012
+ criterionId: signal.criterionId,
2013
+ positiveEvidence: [],
2014
+ findings: [],
2015
+ metrics: [],
2016
+ insufficientData: true,
2017
+ notes: "Cejel abstained on this criterion because one or more relevant repository files could not be read. Resolve the content-read limitations and re-scan."
2018
+ };
1747
2019
  }
1748
2020
  function buildWitanInputFromRepo(options) {
1749
2021
  const tracked = trackContentReads(options.repoPath, () => buildWitanInputFromRepoUntracked(options));
1750
2022
  const affectedCriteria = tracked.affectedCriteria;
2023
+ const rubricVersion = options.rubricVersion ?? WITAN_RUBRIC_VERSION;
2024
+ if (rubricVersion !== WITAN_RUBRIC_VERSION_V23) {
2025
+ const signals2 = (tracked.value.signals ?? []).map(
2026
+ (signal) => affectedCriteria.has(signal.criterionId) ? wholesaleCriterionAbstention(signal) : signal
2027
+ );
2028
+ const affectedCount = affectedCriteria.size;
2029
+ const scanLimitations2 = [...tracked.value.scanLimitations ?? []];
2030
+ if (affectedCount > 0 && scanLimitations2.length < 16) {
2031
+ scanLimitations2.push(
2032
+ `${affectedCount} ${affectedCount === 1 ? "criterion" : "criteria"} abstained because relevant repository content could not be read. Counts and errno classes are recorded in contentReadSummary; file paths are intentionally omitted.`
2033
+ );
2034
+ }
2035
+ return {
2036
+ ...tracked.value,
2037
+ signals: signals2,
2038
+ ...scanLimitations2.length > 0 ? { scanLimitations: scanLimitations2 } : {},
2039
+ contentReadSummary: tracked.summary
2040
+ };
2041
+ }
2042
+ const wholesaleAbstainedCriteria = [];
2043
+ const partiallyAbstainedSignals = [];
1751
2044
  const signals = (tracked.value.signals ?? []).map((signal) => {
1752
2045
  if (!affectedCriteria.has(signal.criterionId)) return signal;
2046
+ if (tracked.unattributedCriteria.has(signal.criterionId)) {
2047
+ wholesaleAbstainedCriteria.push(signal.criterionId);
2048
+ return wholesaleCriterionAbstention(signal);
2049
+ }
2050
+ const prefix = contentReadSignalKey(signal.criterionId, "");
2051
+ const affectedNames = [...tracked.affectedSignals].filter((key) => key.startsWith(prefix)).map((key) => key.slice(prefix.length));
2052
+ if (affectedNames.length === 0) {
2053
+ wholesaleAbstainedCriteria.push(signal.criterionId);
2054
+ return wholesaleCriterionAbstention(signal);
2055
+ }
2056
+ const affectedNameSet = new Set(affectedNames);
2057
+ const metrics = (signal.metrics ?? []).filter((metric2) => !affectedNameSet.has(metric2.name));
2058
+ const anythingSurvives = metrics.length > 0 || (signal.positiveEvidence?.length ?? 0) > 0 || (signal.findings?.length ?? 0) > 0;
2059
+ if (!anythingSurvives) {
2060
+ wholesaleAbstainedCriteria.push(signal.criterionId);
2061
+ return wholesaleCriterionAbstention(signal);
2062
+ }
2063
+ for (const name of affectedNames) {
2064
+ partiallyAbstainedSignals.push(`${signal.criterionId}.${name}`);
2065
+ }
1753
2066
  return {
1754
- criterionId: signal.criterionId,
1755
- positiveEvidence: [],
1756
- findings: [],
1757
- metrics: [],
1758
- insufficientData: true,
1759
- notes: "Cejel abstained on this criterion because one or more relevant repository files could not be read. Resolve the content-read limitations and re-scan."
2067
+ ...signal,
2068
+ metrics,
2069
+ notes: `Cejel abstained on ${[...affectedNames].sort().join(", ")} in this criterion because that content could not be read; other signals in this criterion were computed from readable files and are unaffected.`
1760
2070
  };
1761
2071
  });
1762
- const affectedCount = affectedCriteria.size;
1763
2072
  const scanLimitations = [...tracked.value.scanLimitations ?? []];
1764
- if (affectedCount > 0 && scanLimitations.length < 16) {
2073
+ if (wholesaleAbstainedCriteria.length > 0 && scanLimitations.length < 16) {
1765
2074
  scanLimitations.push(
1766
- `${affectedCount} ${affectedCount === 1 ? "criterion" : "criteria"} abstained because relevant repository content could not be read. Counts and errno classes are recorded in contentReadSummary; file paths are intentionally omitted.`
2075
+ `${wholesaleAbstainedCriteria.length} ${wholesaleAbstainedCriteria.length === 1 ? "criterion" : "criteria"} abstained because relevant repository content could not be read and the affected signal could not be isolated. Counts and errno classes are recorded in contentReadSummary; file paths are intentionally omitted.`
2076
+ );
2077
+ }
2078
+ if (partiallyAbstainedSignals.length > 0 && scanLimitations.length < 16) {
2079
+ scanLimitations.push(
2080
+ `${partiallyAbstainedSignals.length} ${partiallyAbstainedSignals.length === 1 ? "signal" : "signals"} abstained because its own repository content could not be read, while other signals in the same criterion were unaffected: ${[...partiallyAbstainedSignals].sort().join(", ")}.`
1767
2081
  );
1768
2082
  }
1769
2083
  return {
@@ -1783,11 +2097,16 @@ function buildWitanInputFromRepoUntracked(options) {
1783
2097
  const inventoryFiles = fileInventory.inventoryFiles;
1784
2098
  const ignoredTargetReason = repoFiles.length === 0 ? explainIgnoredScanTarget(options.repoPath) : void 0;
1785
2099
  const usesV17Detectors = usesV17DetectorClosure(rubricVersion);
1786
- 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;
1787
- const usesV19CommitYear = rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
1788
- const usesV20A3ExplicitGaps = rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
1789
- const usesV21ExecutedEscalations = rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
1790
- const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22;
2100
+ const inventoryAbsenceContext = {
2101
+ rubricGeneration: usesV17Detectors ? "current" : "legacy",
2102
+ inventoryProvenance: fileInventory.repoFileInventoryScope === "tracked-scan-eligible" ? "tracked-scan-eligible" : "directory-walk"
2103
+ };
2104
+ const usesV18NativeRls = rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
2105
+ const usesV19CommitYear = rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
2106
+ const usesV20A3ExplicitGaps = rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
2107
+ const usesV21ExecutedEscalations = rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
2108
+ const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
2109
+ const usesV23CommandCoverage = rubricVersion === WITAN_RUBRIC_VERSION_V23;
1791
2110
  const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
1792
2111
  const readableArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure(rubricVersion) ? applyReadableSourceRepresentationGate(
1793
2112
  options.repoPath,
@@ -1823,7 +2142,9 @@ function buildWitanInputFromRepoUntracked(options) {
1823
2142
  usesV20A3ExplicitGaps,
1824
2143
  usesV21ExecutedEscalations,
1825
2144
  usesV22PackageStartEntrypoint,
2145
+ usesV23CommandCoverage,
1826
2146
  reviewableSourceProof,
2147
+ inventoryAbsenceContext,
1827
2148
  scanLimitations
1828
2149
  );
1829
2150
  const archetype = usesV17Detectors && reviewableSourceProof?.passes === true && isV17RescuableAbstention(gatedArchetype.abstentionKind) && coreSignals.some(isMeasuredCriterionSignal) ? {
@@ -1833,7 +2154,10 @@ function buildWitanInputFromRepoUntracked(options) {
1833
2154
  } : gatedArchetype;
1834
2155
  return {
1835
2156
  productSlug: options.productSlug,
1836
- productDisplayName: options.productDisplayName,
2157
+ productDisplayName: sanitizePresentationLine(options.productDisplayName, {
2158
+ fallback: "Repository",
2159
+ maxLength: 120
2160
+ }),
1837
2161
  repo: {
1838
2162
  ...headSha ? { headSha } : {}
1839
2163
  },
@@ -2430,7 +2754,7 @@ function buildNotApplicableSignal(criterionId, reason) {
2430
2754
  notApplicable: true
2431
2755
  };
2432
2756
  }
2433
- function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, reviewableSourceProof, scanLimitations = /* @__PURE__ */ new Set()) {
2757
+ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23CommandCoverage, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
2434
2758
  const signals = [];
2435
2759
  const mono = resolveMonorepoContext(repoPath, scanLimitations);
2436
2760
  const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
@@ -2442,7 +2766,9 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
2442
2766
  useV27Detectors,
2443
2767
  useV33Detectors,
2444
2768
  useV36Detectors,
2445
- reviewableSourceProof
2769
+ useV23CommandCoverage,
2770
+ reviewableSourceProof,
2771
+ inventoryAbsenceContext
2446
2772
  )
2447
2773
  );
2448
2774
  const a2Signal = collectCriterion(
@@ -2456,6 +2782,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
2456
2782
  useV39Detectors,
2457
2783
  useV47Detectors,
2458
2784
  useV18NativeRls,
2785
+ inventoryAbsenceContext,
2459
2786
  scanLimitations
2460
2787
  )
2461
2788
  );
@@ -2466,7 +2793,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
2466
2793
  repoFiles,
2467
2794
  useV27Detectors,
2468
2795
  useV20A3ExplicitGaps,
2469
- useV22PackageStartEntrypoint
2796
+ useV22PackageStartEntrypoint,
2797
+ inventoryAbsenceContext
2470
2798
  )
2471
2799
  );
2472
2800
  const a4Signal = collectCriterion(
@@ -2476,12 +2804,18 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
2476
2804
  repoFiles,
2477
2805
  mono,
2478
2806
  useV27Detectors,
2479
- useV47Detectors
2807
+ useV47Detectors,
2808
+ inventoryAbsenceContext
2480
2809
  )
2481
2810
  );
2482
2811
  const a5Signal = collectCriterion(
2483
2812
  "A5",
2484
- () => collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors)
2813
+ () => collectA5ClaimRealityEvidence(
2814
+ repoPath,
2815
+ repoFiles,
2816
+ useV27Detectors,
2817
+ inventoryAbsenceContext
2818
+ )
2485
2819
  );
2486
2820
  const b1Signal = buildNotApplicableSignal(
2487
2821
  "B1",
@@ -2532,7 +2866,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
2532
2866
  }
2533
2867
  return signals;
2534
2868
  }
2535
- function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, reviewableSourceProof) {
2869
+ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV23CommandCoverage, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
2536
2870
  const evidence = [];
2537
2871
  const findings = [];
2538
2872
  const allTestFiles = findConcreteTestFiles(
@@ -2543,17 +2877,33 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
2543
2877
  const testFiles = allTestFiles.slice(0, 8);
2544
2878
  const sourceFiles = repoFiles.filter(isSourceFile);
2545
2879
  const runnerFiles = repoFiles.filter(
2546
- (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isTestRunnerConfig(repoPath, file)
2880
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && (isTestRunnerConfig(repoPath, file) || useV23CommandCoverage && isRecipeTestEntry(repoPath, file))
2547
2881
  ).slice(0, 6);
2548
- const configuredRunnerFiles = useV33Detectors ? findConfiguredTestRunnerFiles(repoPath, repoFiles) : runnerFiles;
2549
- const allCoverageFiles = findCoverageConfigFiles(repoPath, repoFiles, useV27Detectors);
2550
- const coverageFiles = allCoverageFiles.slice(0, 4);
2882
+ const baseConfiguredRunnerFiles = useV33Detectors ? findConfiguredTestRunnerFiles(repoPath, repoFiles) : runnerFiles;
2883
+ const configuredRunnerFiles = useV23CommandCoverage ? [
2884
+ .../* @__PURE__ */ new Set([
2885
+ ...baseConfiguredRunnerFiles,
2886
+ ...repoFiles.filter((file) => isRecipeTestEntry(repoPath, file))
2887
+ ])
2888
+ ].sort() : baseConfiguredRunnerFiles;
2551
2889
  const packageJson = findRootPackageJson(repoFiles);
2552
2890
  const packageScripts = packageJson ? readPackageScripts(join(repoPath, packageJson)) : /* @__PURE__ */ new Map();
2891
+ const { coverageAnalysis, coveragePercent } = withContentReadSignal("A1", "coverage_percent", () => {
2892
+ const analysis = useV23CommandCoverage ? analyzeCoverageConfiguration(repoPath, repoFiles, useV27Detectors) : {
2893
+ coverageFiles: findCoverageConfigFiles(repoPath, repoFiles, useV27Detectors),
2894
+ unresolvedFiles: [],
2895
+ hasCoverageCommand: packageScripts.has("coverage")
2896
+ };
2897
+ return {
2898
+ coverageAnalysis: analysis,
2899
+ coveragePercent: readCoveragePercent(repoPath, analysis.coverageFiles.slice(0, 4))
2900
+ };
2901
+ });
2902
+ const allCoverageFiles = coverageAnalysis.coverageFiles;
2903
+ const coverageFiles = allCoverageFiles.slice(0, 4);
2553
2904
  const packageJsonFiles = useV27Detectors ? repoFiles.filter(
2554
2905
  (file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)
2555
2906
  ) : packageJson ? [packageJson] : [];
2556
- const coveragePercent = readCoveragePercent(repoPath, coverageFiles);
2557
2907
  const ciWorkflows = repoFiles.filter(
2558
2908
  (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isCiWorkflow(file)
2559
2909
  );
@@ -2568,7 +2918,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
2568
2918
  );
2569
2919
  const verificationScriptCount = [
2570
2920
  packageScripts.has("test") || Boolean(ciTestWorkflow),
2571
- packageScripts.has("coverage"),
2921
+ coverageAnalysis.hasCoverageCommand,
2572
2922
  packageScripts.has("lint") || ciHasLintCommand,
2573
2923
  packageScripts.has("typecheck") || ciHasTypecheckCommand
2574
2924
  ].filter(Boolean).length;
@@ -2588,9 +2938,11 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
2588
2938
  if (packageJson && [...packageScripts.values()].some((script) => TEST_RUNNER_PATTERN.test(script))) {
2589
2939
  evidence.push(evidenceForRelative(repoPath, packageJson, "test_run", "Configured test runner"));
2590
2940
  }
2591
- for (const file of coverageFiles) {
2592
- evidence.push(evidenceForRelative(repoPath, file, "coverage", "Coverage configuration"));
2593
- }
2941
+ withContentReadSignal("A1", "coverage_percent", () => {
2942
+ for (const file of coverageFiles) {
2943
+ evidence.push(evidenceForRelative(repoPath, file, "coverage", "Coverage configuration"));
2944
+ }
2945
+ });
2594
2946
  if (ciTestWorkflow) {
2595
2947
  evidence.push(
2596
2948
  evidenceForRelative(repoPath, ciTestWorkflow, "test_run", "CI workflow runs the test suite")
@@ -2629,7 +2981,12 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
2629
2981
  }
2630
2982
  }
2631
2983
  if (evidence.length === 0 && findings.length === 0 && configuredRunnerFiles.length === 0) {
2632
- return reviewableSourceProof?.passes === true && reviewableSourceProof.firstReadablePath ? buildA1AuthenticatedAbsenceSignal(repoPath, reviewableSourceProof) : null;
2984
+ return reviewableSourceProof?.passes === true && reviewableSourceProof.firstReadablePath ? buildA1AuthenticatedAbsenceSignal(
2985
+ repoPath,
2986
+ reviewableSourceProof,
2987
+ repoFiles.length,
2988
+ inventoryAbsenceContext
2989
+ ) : null;
2633
2990
  }
2634
2991
  if (testFiles.length === 0) {
2635
2992
  const fallback = configuredRunnerFiles[0] ? evidenceForRelative(
@@ -2639,13 +2996,23 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
2639
2996
  "Configured test runner"
2640
2997
  ) : useV33Detectors ? void 0 : evidence[0] ?? findings[0]?.evidence;
2641
2998
  if (fallback) {
2642
- findings.push({
2643
- severity: "warning",
2644
- summary: "A test runner is configured, but no concrete test files were detected.",
2645
- evidence: fallback
2646
- });
2999
+ findings.push(
3000
+ inventoryAbsenceFinding({
3001
+ severity: "warning",
3002
+ summaries: {
3003
+ legacy: "A test runner is configured, but no concrete test files were detected.",
3004
+ currentTracked: "A test runner is configured, but the tracked scan-eligible inventory matched no concrete test file.",
3005
+ currentDirectoryWalk: "A test runner is configured, but the bounded directory walk matched no concrete test file."
3006
+ },
3007
+ evidence: fallback,
3008
+ context: inventoryAbsenceContext,
3009
+ inventoryCount: repoFiles.length,
3010
+ patternSet: INVENTORY_SCAN_PATTERN_SETS.a1ConcreteTests,
3011
+ matchCount: 0
3012
+ })
3013
+ );
2647
3014
  }
2648
- } else if (coverageFiles.length === 0 && (!useV33Detectors || configuredRunnerFiles.length > 0)) {
3015
+ } else if (coverageFiles.length === 0 && coverageAnalysis.unresolvedFiles.length === 0 && (!useV33Detectors || configuredRunnerFiles.length > 0)) {
2649
3016
  if (isLeanTestToolchain) {
2650
3017
  const leanEvidenceFile = packageJson ?? testFiles[0];
2651
3018
  if (leanEvidenceFile) {
@@ -2661,17 +3028,27 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
2661
3028
  } else {
2662
3029
  const firstEvidence = evidence[0];
2663
3030
  if (!firstEvidence) return null;
2664
- findings.push({
2665
- severity: "info",
2666
- summary: "Test suite files are present, but no coverage configuration was detected.",
2667
- evidence: firstEvidence
2668
- });
3031
+ findings.push(
3032
+ inventoryAbsenceFinding({
3033
+ severity: "info",
3034
+ summaries: {
3035
+ legacy: "Test suite files are present, but no coverage configuration was detected.",
3036
+ currentTracked: "Test suite files are present, but the tracked scan-eligible inventory matched no coverage configuration.",
3037
+ currentDirectoryWalk: "Test suite files are present, but the bounded directory walk matched no coverage configuration."
3038
+ },
3039
+ evidence: firstEvidence,
3040
+ context: inventoryAbsenceContext,
3041
+ inventoryCount: repoFiles.length,
3042
+ patternSet: useV23CommandCoverage ? INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV2 : INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfigurationV1,
3043
+ matchCount: 0
3044
+ })
3045
+ );
2669
3046
  }
2670
3047
  }
2671
- const nonHollowShare = measureNonHollowTestShare(
2672
- repoPath,
2673
- allTestFiles,
2674
- reviewableSourceProof !== void 0
3048
+ const nonHollowShare = withContentReadSignal(
3049
+ "A1",
3050
+ "non_hollow_test_share",
3051
+ () => measureNonHollowTestShare(repoPath, allTestFiles, reviewableSourceProof !== void 0)
2675
3052
  );
2676
3053
  return {
2677
3054
  criterionId: "A1",
@@ -2712,7 +3089,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
2712
3089
  label: "test command",
2713
3090
  count: packageScripts.has("test") || Boolean(ciTestWorkflow) ? 1 : 0
2714
3091
  },
2715
- { label: "coverage command", count: packageScripts.has("coverage") ? 1 : 0 },
3092
+ { label: "coverage command", count: coverageAnalysis.hasCoverageCommand ? 1 : 0 },
2716
3093
  {
2717
3094
  label: "lint command",
2718
3095
  count: packageScripts.has("lint") || ciHasLintCommand ? 1 : 0
@@ -2735,10 +3112,10 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
2735
3112
  "Penalizes skipped or placeholder-only test files; test-directory support scaffolding (helpers/fixtures with no test in them) is excluded from the denominator."
2736
3113
  )
2737
3114
  ],
2738
- notes: "A1 is detected from real test files, test runner configuration, and optional coverage configuration."
3115
+ notes: coverageFiles.length === 0 && coverageAnalysis.unresolvedFiles.length > 0 ? `A1 is detected from real test files, test runner configuration, and optional coverage configuration. Coverage command reachability or flag ownership was unresolved in ${coverageAnalysis.unresolvedFiles.slice(0, 4).join(", ")}; the no-coverage absence claim was withheld.` : "A1 is detected from real test files, test runner configuration, and optional coverage configuration."
2739
3116
  };
2740
3117
  }
2741
- function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
3118
+ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inventoryAbsenceContext) {
2742
3119
  const sourcePath = proof.firstReadablePath;
2743
3120
  if (!sourcePath) {
2744
3121
  throw new Error("Cejel invariant: a passing reviewable-source proof must have a readable path");
@@ -2753,11 +3130,19 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
2753
3130
  criterionId: "A1",
2754
3131
  positiveEvidence: [anchor],
2755
3132
  findings: [
2756
- {
3133
+ inventoryAbsenceFinding({
2757
3134
  severity: "critical",
2758
- summary: WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY,
2759
- evidence: anchor
2760
- }
3135
+ summaries: {
3136
+ legacy: WITAN_LEGACY_AUTHENTICATED_A1_ABSENCE_SUMMARY,
3137
+ currentTracked: WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY,
3138
+ currentDirectoryWalk: WITAN_DIRECTORY_WALK_AUTHENTICATED_A1_ABSENCE_SUMMARY
3139
+ },
3140
+ evidence: anchor,
3141
+ context: inventoryAbsenceContext,
3142
+ inventoryCount,
3143
+ patternSet: INVENTORY_SCAN_PATTERN_SETS.a1AuthenticatedAbsence,
3144
+ matchCount: 0
3145
+ })
2761
3146
  ],
2762
3147
  metrics: [
2763
3148
  metric(
@@ -2811,7 +3196,7 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
2811
3196
  notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files.`
2812
3197
  };
2813
3198
  }
2814
- function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, scanLimitations) {
3199
+ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, inventoryAbsenceContext, scanLimitations) {
2815
3200
  const evidence = [];
2816
3201
  const findings = [];
2817
3202
  const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
@@ -2819,40 +3204,47 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
2819
3204
  const migrationFiles = repoFiles.filter(
2820
3205
  (file) => /(^|\/)(migrations?|drizzle|prisma)\//.test(file)
2821
3206
  );
2822
- let committedSecret;
2823
- for (const path of repoFiles) {
2824
- const authoredProductionPath = useV47Detectors ? isV47AuthoredProductionPath(path) : useV39Detectors ? isV39AuthoredProductionPath(path) : isAuthoredProductionPath(path);
2825
- if (isIgnoredScanFile(path, useV47Detectors) || useV33Detectors && !authoredProductionPath) {
2826
- continue;
2827
- }
2828
- const match = findCommittedSecretInFile(
2829
- repoPath,
2830
- path,
2831
- useV36Detectors,
2832
- useV39Detectors,
2833
- useV47Detectors
2834
- );
2835
- if (!match) continue;
2836
- committedSecret = { path, match };
2837
- break;
2838
- }
2839
- const currentSecretFingerprintsByPath = /* @__PURE__ */ new Map();
2840
- if (useV27Detectors) {
2841
- for (const path of repoFiles.filter((candidate) => isCredentialHistoryPath(candidate))) {
2842
- currentSecretFingerprintsByPath.set(
2843
- path,
2844
- new Set(
2845
- findSecretFingerprintsInFile(
2846
- repoPath,
3207
+ const { committedSecret, currentSecretFingerprintsByPath } = withContentReadSignal(
3208
+ "A2",
3209
+ "secret_cleanliness",
3210
+ () => {
3211
+ let match_;
3212
+ for (const path of repoFiles) {
3213
+ const authoredProductionPath = useV47Detectors ? isV47AuthoredProductionPath(path) : useV39Detectors ? isV39AuthoredProductionPath(path) : isAuthoredProductionPath(path);
3214
+ if (isIgnoredScanFile(path, useV47Detectors) || useV33Detectors && !authoredProductionPath) {
3215
+ continue;
3216
+ }
3217
+ const found = findCommittedSecretInFile(
3218
+ repoPath,
3219
+ path,
3220
+ useV36Detectors,
3221
+ useV39Detectors,
3222
+ useV47Detectors
3223
+ );
3224
+ if (!found) continue;
3225
+ match_ = { path, match: found };
3226
+ break;
3227
+ }
3228
+ const fingerprintsByPath = /* @__PURE__ */ new Map();
3229
+ if (useV27Detectors) {
3230
+ for (const path of repoFiles.filter((candidate) => isCredentialHistoryPath(candidate))) {
3231
+ fingerprintsByPath.set(
2847
3232
  path,
2848
- useV36Detectors,
2849
- useV39Detectors,
2850
- useV47Detectors
2851
- )
2852
- )
2853
- );
3233
+ new Set(
3234
+ findSecretFingerprintsInFile(
3235
+ repoPath,
3236
+ path,
3237
+ useV36Detectors,
3238
+ useV39Detectors,
3239
+ useV47Detectors
3240
+ )
3241
+ )
3242
+ );
3243
+ }
3244
+ }
3245
+ return { committedSecret: match_, currentSecretFingerprintsByPath: fingerprintsByPath };
2854
3246
  }
2855
- }
3247
+ );
2856
3248
  const historySecretScan = committedSecret && !useV27Detectors ? null : collectHistorySecretEvidence(
2857
3249
  repoPath,
2858
3250
  useV27Detectors,
@@ -2864,10 +3256,14 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
2864
3256
  useV47Detectors
2865
3257
  );
2866
3258
  const hasConfirmedSecretFinding = committedSecret != null || historySecretScan?.evidence != null;
2867
- const cryptoHygiene = collectCryptoHygieneEvidence(
2868
- repoPath,
2869
- repoFiles.filter(isImplementationFile).slice(0, 60),
2870
- useV39Detectors
3259
+ const cryptoHygiene = withContentReadSignal(
3260
+ "A2",
3261
+ "crypto_comparison_hygiene",
3262
+ () => collectCryptoHygieneEvidence(
3263
+ repoPath,
3264
+ repoFiles.filter(isImplementationFile).slice(0, 60),
3265
+ useV39Detectors
3266
+ )
2871
3267
  );
2872
3268
  const implFiles = repoFiles.filter(isImplementationFile);
2873
3269
  const gitignoreHasEnvRule = gitignore != null && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m);
@@ -2907,42 +3303,77 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
2907
3303
  } else if (dbLayerImportFile != null) {
2908
3304
  evidence.push(evidenceForRelative(repoPath, dbLayerImportFile, "artifact", "DB client import"));
2909
3305
  }
2910
- const v47RlsPolicyFiles = useV47Detectors ? findRlsPolicyFiles(repoPath, repoFiles) : [];
2911
- const nativeRlsInventory = useV18NativeRls ? deriveRlsPolicyScopeInventory(repoPath, repoFiles) : null;
2912
- const tenantWithoutRlsPremiseFiles = useV18NativeRls ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
2913
- const v47TenantStorageFiles = useV47Detectors && !useV18NativeRls ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
2914
- const rlsMigration = useV47Detectors ? v47RlsPolicyFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, RLS_PATTERN));
2915
3306
  const tenantScopePattern = useV39Detectors ? TENANT_SCOPE_PATTERN_V11 : useV36Detectors ? TENANT_SCOPE_PATTERN_V10 : TENANT_SCOPE_PATTERN;
2916
- const tenantScopedFile = useV18NativeRls ? nativeRlsInventory?.storageFiles[0] : useV47Detectors ? v47TenantStorageFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, tenantScopePattern));
2917
- const rlsPolicyCount = useV18NativeRls ? nativeRlsInventory?.policyCount ?? 0 : useV47Detectors ? v47RlsPolicyFiles.length : countPatternMatches(
2918
- repoPath,
2919
- migrationFiles,
2920
- /create policy|enable row level security|force row level security/gi
3307
+ const {
3308
+ rlsMigration,
3309
+ tenantScopedFile,
3310
+ rlsPolicyCount,
3311
+ tenantScopedMigrationFileCount,
3312
+ nativeRlsInventory,
3313
+ tenantWithoutRlsPremiseFiles
3314
+ } = withContentReadSignals("A2", ["rls_policy_count", "tenant_scope_ratio"], () => {
3315
+ const v47RlsPolicyFiles = useV47Detectors ? findRlsPolicyFiles(repoPath, repoFiles) : [];
3316
+ const rlsInventory = useV18NativeRls ? deriveRlsPolicyScopeInventory(repoPath, repoFiles) : null;
3317
+ const tenantWithoutRlsFiles = useV18NativeRls ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
3318
+ const v47TenantStorageFiles = useV47Detectors && !useV18NativeRls ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
3319
+ return {
3320
+ rlsMigration: useV47Detectors ? v47RlsPolicyFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, RLS_PATTERN)),
3321
+ tenantScopedFile: useV18NativeRls ? rlsInventory?.storageFiles[0] : useV47Detectors ? v47TenantStorageFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, tenantScopePattern)),
3322
+ rlsPolicyCount: useV18NativeRls ? rlsInventory?.policyCount ?? 0 : useV47Detectors ? v47RlsPolicyFiles.length : countPatternMatches(
3323
+ repoPath,
3324
+ migrationFiles,
3325
+ /create policy|enable row level security|force row level security/gi
3326
+ ),
3327
+ tenantScopedMigrationFileCount: useV18NativeRls ? rlsInventory?.storageFiles.length ?? 0 : useV47Detectors ? v47TenantStorageFiles.length : countFilesContaining(repoPath, migrationFiles, tenantScopePattern),
3328
+ nativeRlsInventory: rlsInventory,
3329
+ tenantWithoutRlsPremiseFiles: tenantWithoutRlsFiles
3330
+ };
3331
+ });
3332
+ const { gitignoreEnvFile, hasEnvReadsInImplementation, envHandlingDepth } = withContentReadSignal(
3333
+ "A2",
3334
+ "env_handling_depth",
3335
+ () => {
3336
+ const envFile = gitignore && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m) ? gitignore : null;
3337
+ const hasEnvReads = countFilesContaining(repoPath, repoFiles.filter(isImplementationFile), ENV_READ_PATTERN) > 0;
3338
+ const depth = (envExamples.length > 0 ? 1 : 0) + (envFile ? 1 : 0) + (hasEnvReads ? 1 : 0);
3339
+ return { gitignoreEnvFile: envFile, hasEnvReadsInImplementation: hasEnvReads, envHandlingDepth: depth };
3340
+ }
2921
3341
  );
2922
- const tenantScopedMigrationFileCount = useV18NativeRls ? nativeRlsInventory?.storageFiles.length ?? 0 : useV47Detectors ? v47TenantStorageFiles.length : countFilesContaining(repoPath, migrationFiles, tenantScopePattern);
2923
- const gitignoreEnvFile = gitignore && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m) ? gitignore : null;
2924
- const envHandlingDepth = (envExamples.length > 0 ? 1 : 0) + (gitignoreEnvFile ? 1 : 0) + (countFilesContaining(repoPath, repoFiles.filter(isImplementationFile), ENV_READ_PATTERN) > 0 ? 1 : 0);
2925
3342
  const isMultiTenant = useV18NativeRls ? (nativeRlsInventory?.policyCount ?? 0) > 0 && (nativeRlsInventory?.scopeIdentifiers.length ?? 0) > 0 : tenantScopedMigrationFileCount > 0;
2926
3343
  if (gitignoreEnvFile) {
2927
- evidence.push(
2928
- evidenceForRelative(repoPath, gitignoreEnvFile, "secret_scan", ".env files are gitignored")
3344
+ withContentReadSignal(
3345
+ "A2",
3346
+ "env_handling_depth",
3347
+ () => evidence.push(
3348
+ evidenceForRelative(repoPath, gitignoreEnvFile, "secret_scan", ".env files are gitignored")
3349
+ )
2929
3350
  );
2930
3351
  }
2931
- for (const file of envExamples.slice(0, 3)) {
2932
- evidence.push(evidenceForRelative(repoPath, file, "secret_scan", "Environment template"));
2933
- }
3352
+ withContentReadSignal("A2", "env_handling_depth", () => {
3353
+ for (const file of envExamples.slice(0, 3)) {
3354
+ evidence.push(evidenceForRelative(repoPath, file, "secret_scan", "Environment template"));
3355
+ }
3356
+ });
2934
3357
  if (rlsMigration) {
2935
- evidence.push(
2936
- evidenceForRelative(repoPath, rlsMigration, "artifact", "RLS or tenant migration")
3358
+ withContentReadSignals(
3359
+ "A2",
3360
+ ["rls_policy_count", "tenant_scope_ratio"],
3361
+ () => evidence.push(
3362
+ evidenceForRelative(repoPath, rlsMigration, "artifact", "RLS or tenant migration")
3363
+ )
2937
3364
  );
2938
3365
  }
2939
3366
  if (tenantScopedFile) {
2940
- evidence.push(
2941
- evidenceForRelative(
2942
- repoPath,
2943
- tenantScopedFile,
2944
- "artifact",
2945
- useV18NativeRls ? `Repository-native RLS scope (${nativeRlsInventory?.scopeIdentifiers.join(", ")})` : "Tenant scoping signal"
3367
+ withContentReadSignals(
3368
+ "A2",
3369
+ ["rls_policy_count", "tenant_scope_ratio"],
3370
+ () => evidence.push(
3371
+ evidenceForRelative(
3372
+ repoPath,
3373
+ tenantScopedFile,
3374
+ "artifact",
3375
+ useV18NativeRls ? `Repository-native RLS scope (${nativeRlsInventory?.scopeIdentifiers.join(", ")})` : "Tenant scoping signal"
3376
+ )
2946
3377
  )
2947
3378
  );
2948
3379
  }
@@ -2964,16 +3395,26 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
2964
3395
  )
2965
3396
  });
2966
3397
  } else if (committedEnvFilePath) {
2967
- findings.push({
2968
- severity: useV33Detectors ? "info" : "warning",
2969
- summary: "A non-template .env file is committed in the current repository tree; no secret-shaped value was detected.",
2970
- evidence: evidenceForRelative(
2971
- repoPath,
2972
- committedEnvFilePath,
2973
- "secret_scan",
2974
- "Committed .env file (no confirmed secret value found)"
2975
- )
2976
- });
3398
+ findings.push(
3399
+ inventoryAbsenceFinding({
3400
+ severity: useV33Detectors ? "info" : "warning",
3401
+ summaries: {
3402
+ legacy: "A non-template .env file is committed in the current repository tree; no secret-shaped value was detected.",
3403
+ currentTracked: "A tracked non-template .env file is present, while the bounded scan over the tracked scan-eligible inventory matched no secret-shaped value.",
3404
+ currentDirectoryWalk: "A non-template .env file is present in the bounded directory walk, which matched no secret-shaped value."
3405
+ },
3406
+ evidence: evidenceForRelative(
3407
+ repoPath,
3408
+ committedEnvFilePath,
3409
+ "secret_scan",
3410
+ "Committed .env file (no confirmed secret value found)"
3411
+ ),
3412
+ context: inventoryAbsenceContext,
3413
+ inventoryCount: repoFiles.length,
3414
+ patternSet: INVENTORY_SCAN_PATTERN_SETS.a2CurrentSecret,
3415
+ matchCount: 0
3416
+ })
3417
+ );
2977
3418
  }
2978
3419
  if (historySecretScan?.evidence) {
2979
3420
  const historyPath = historySecretScan.evidence.path ?? "";
@@ -3005,16 +3446,26 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
3005
3446
  if (hasTenantWithoutRlsGap) {
3006
3447
  const gapEvidence = rlsMigration ?? tenantScopedFile ?? tenantWithoutRlsPremiseFiles[0];
3007
3448
  if (gapEvidence) {
3008
- findings.push({
3009
- severity: "warning",
3010
- summary: "Tenant-scoped schema detected but no row-level-security policies found \u2014 isolation gap.",
3011
- evidence: evidenceForRelative(
3012
- repoPath,
3013
- gapEvidence,
3014
- "artifact",
3015
- "Tenant schema without RLS policies"
3016
- )
3017
- });
3449
+ findings.push(
3450
+ inventoryAbsenceFinding({
3451
+ severity: "warning",
3452
+ summaries: {
3453
+ legacy: "Tenant-scoped schema detected but no row-level-security policies found \u2014 isolation gap.",
3454
+ currentTracked: "A tenant-scoped schema signal is present, while the tracked scan-eligible inventory matched no row-level-security policy.",
3455
+ currentDirectoryWalk: "A tenant-scoped schema signal is present, while the bounded directory walk matched no row-level-security policy."
3456
+ },
3457
+ evidence: evidenceForRelative(
3458
+ repoPath,
3459
+ gapEvidence,
3460
+ "artifact",
3461
+ "Tenant schema without RLS policies"
3462
+ ),
3463
+ context: inventoryAbsenceContext,
3464
+ inventoryCount: repoFiles.length,
3465
+ patternSet: INVENTORY_SCAN_PATTERN_SETS.a2RlsPolicy,
3466
+ matchCount: 0
3467
+ })
3468
+ );
3018
3469
  }
3019
3470
  }
3020
3471
  evidence.push(...cryptoHygiene.evidence);
@@ -3046,11 +3497,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
3046
3497
  { label: ".gitignore environment-file rule", count: gitignoreEnvFile ? 1 : 0 },
3047
3498
  {
3048
3499
  label: "environment reads in implementation code",
3049
- count: countFilesContaining(
3050
- repoPath,
3051
- repoFiles.filter(isImplementationFile),
3052
- ENV_READ_PATTERN
3053
- ) > 0 ? 1 : 0
3500
+ count: hasEnvReadsInImplementation ? 1 : 0
3054
3501
  }
3055
3502
  ]
3056
3503
  }
@@ -3199,7 +3646,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = fal
3199
3646
  }
3200
3647
  return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
3201
3648
  }
3202
- function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false) {
3649
+ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
3203
3650
  const v20DirectHttpEntrypoint = useV20ExplicitGaps ? findV20DirectHttpServerEntrypointFile(repoPath, repoFiles) : null;
3204
3651
  const v22PackageStartHttpEntrypoint = useV22PackageStartEntrypoint ? findV22PackageStartHttpServerEntrypointFile(repoPath, repoFiles) : null;
3205
3652
  const v20RuntimeContainer = useV20ExplicitGaps ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, true) : null;
@@ -3229,37 +3676,51 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3229
3676
  const envTemplate = repoFiles.find(
3230
3677
  (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isEnvTemplatePath(file)
3231
3678
  );
3232
- const healthChecks = repoFiles.filter(
3233
- (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isHealthCheckSignalFile(repoPath, file)
3679
+ const healthChecks = withContentReadSignal(
3680
+ "A3",
3681
+ "prod_readiness_primitives",
3682
+ () => repoFiles.filter(
3683
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isHealthCheckSignalFile(repoPath, file)
3684
+ )
3234
3685
  );
3235
3686
  const healthCheck = healthChecks[0];
3236
- const hasV20HealthOrReadinessRoute = useV20ExplicitGaps && repoFiles.some(
3237
- (file) => isAuthoredProductionPath(file) && isImplementationFile(file) && fileContains(repoPath, file, V20_HEALTH_OR_READINESS_ROUTE_PATTERN)
3238
- );
3239
- const hasV22PackageStartHealthOrReadinessRoute = v22PackageStartHttpEntrypoint !== null && fileContains(
3240
- repoPath,
3241
- v22PackageStartHttpEntrypoint,
3242
- V20_HEALTH_OR_READINESS_ROUTE_PATTERN
3243
- );
3687
+ const { hasV20HealthOrReadinessRoute, hasV22PackageStartHealthOrReadinessRoute } = withContentReadSignal("A3", "health_readiness_route", () => ({
3688
+ hasV20HealthOrReadinessRoute: useV20ExplicitGaps && repoFiles.some(
3689
+ (file) => isAuthoredProductionPath(file) && isImplementationFile(file) && fileContains(repoPath, file, V20_HEALTH_OR_READINESS_ROUTE_PATTERN)
3690
+ ),
3691
+ hasV22PackageStartHealthOrReadinessRoute: v22PackageStartHttpEntrypoint !== null && fileContains(
3692
+ repoPath,
3693
+ v22PackageStartHttpEntrypoint,
3694
+ V20_HEALTH_OR_READINESS_ROUTE_PATTERN
3695
+ )
3696
+ }));
3244
3697
  const serverEntrypoint = v22PackageStartHttpEntrypoint ?? findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) ?? v20DirectHttpEntrypoint ?? null;
3245
3698
  const runtimeContainer = useV27Detectors ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, useV20ExplicitGaps) : null;
3246
3699
  const errorBoundaries = repoFiles.filter(
3247
3700
  (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
3248
3701
  );
3249
3702
  const errorBoundary = errorBoundaries[0];
3250
- const observabilityCount = countFilesContaining(
3251
- repoPath,
3252
- repoFiles.filter(
3253
- (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file)
3254
- ),
3255
- /sentry|otel|opentelemetry|datadog|prometheus|metrics|logger|logtail/i
3703
+ const observabilityCount = withContentReadSignal(
3704
+ "A3",
3705
+ "observability_depth",
3706
+ () => countFilesContaining(
3707
+ repoPath,
3708
+ repoFiles.filter(
3709
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file)
3710
+ ),
3711
+ /sentry|otel|opentelemetry|datadog|prometheus|metrics|logger|logtail/i
3712
+ )
3256
3713
  );
3257
- const rollbackSafetyCount = countFilesContaining(
3258
- repoPath,
3259
- repoFiles.filter(
3260
- (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /(^|\/)(docs|migrations?|drizzle|prisma|scripts)\//.test(file)
3261
- ),
3262
- /rollback|roll back|migration safety|down migration|reversible|undo migration/i
3714
+ const rollbackSafetyCount = withContentReadSignal(
3715
+ "A3",
3716
+ "rollback_safety_depth",
3717
+ () => countFilesContaining(
3718
+ repoPath,
3719
+ repoFiles.filter(
3720
+ (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /(^|\/)(docs|migrations?|drizzle|prisma|scripts)\//.test(file)
3721
+ ),
3722
+ /rollback|roll back|migration safety|down migration|reversible|undo migration/i
3723
+ )
3263
3724
  );
3264
3725
  if (packageJson && (scripts.has("build") || scripts.has("typecheck"))) {
3265
3726
  evidence.push(
@@ -3287,10 +3748,15 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3287
3748
  );
3288
3749
  if (envTemplate)
3289
3750
  evidence.push(evidenceForRelative(repoPath, envTemplate, "prod_check", "Environment template"));
3290
- if (healthCheck)
3291
- evidence.push(
3292
- evidenceForRelative(repoPath, healthCheck, "prod_check", "Health/readiness signal")
3751
+ if (healthCheck) {
3752
+ withContentReadSignal(
3753
+ "A3",
3754
+ "prod_readiness_primitives",
3755
+ () => evidence.push(
3756
+ evidenceForRelative(repoPath, healthCheck, "prod_check", "Health/readiness signal")
3757
+ )
3293
3758
  );
3759
+ }
3294
3760
  if (errorBoundary)
3295
3761
  evidence.push(evidenceForRelative(repoPath, errorBoundary, "prod_check", "Error boundary"));
3296
3762
  if (evidence.length === 0) {
@@ -3329,7 +3795,12 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3329
3795
  )
3330
3796
  });
3331
3797
  } else if (runtimeContainer) {
3332
- if (!fileContains(repoPath, runtimeContainer, /^\s*HEALTHCHECK\s+(?!NONE\b)/im)) {
3798
+ const hasActiveHealthcheck = withContentReadSignal(
3799
+ "A3",
3800
+ "dockerfile_healthcheck",
3801
+ () => fileContains(repoPath, runtimeContainer, /^\s*HEALTHCHECK\s+(?!NONE\b)/im)
3802
+ );
3803
+ if (!hasActiveHealthcheck) {
3333
3804
  findings.push({
3334
3805
  severity: "info",
3335
3806
  summary: "A runtime Dockerfile declares no active HEALTHCHECK instruction.",
@@ -3341,10 +3812,14 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3341
3812
  )
3342
3813
  });
3343
3814
  }
3344
- } else if (serverEntrypoint && fileContains(
3345
- repoPath,
3346
- serverEntrypoint,
3347
- /\b(?:request|req)\.(?:url|path|pathname|method)\b/i
3815
+ } else if (serverEntrypoint && withContentReadSignal(
3816
+ "A3",
3817
+ "health_readiness_route",
3818
+ () => fileContains(
3819
+ repoPath,
3820
+ serverEntrypoint,
3821
+ /\b(?:request|req)\.(?:url|path|pathname|method)\b/i
3822
+ )
3348
3823
  ) && healthChecks.length === 0 && !hasV20HealthOrReadinessRoute && !hasV22PackageStartHealthOrReadinessRoute) {
3349
3824
  findings.push({
3350
3825
  severity: "info",
@@ -3361,11 +3836,21 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3361
3836
  if (!workflow && releaseDeployConfigs.length === 0) {
3362
3837
  const firstEvidence = evidence[0];
3363
3838
  if (!firstEvidence) return null;
3364
- findings.push({
3365
- severity: "info",
3366
- summary: "A deployable service surface exists, but no CI or release-deployment automation was detected.",
3367
- evidence: firstEvidence
3368
- });
3839
+ findings.push(
3840
+ inventoryAbsenceFinding({
3841
+ severity: "info",
3842
+ summaries: {
3843
+ legacy: "A deployable service surface exists, but no CI or release-deployment automation was detected.",
3844
+ currentTracked: "A deployable service signal is present, while the tracked scan-eligible inventory matched no CI workflow or release-deployment automation.",
3845
+ currentDirectoryWalk: "A deployable service signal is present, while the bounded directory walk matched no CI workflow or release-deployment automation."
3846
+ },
3847
+ evidence: firstEvidence,
3848
+ context: inventoryAbsenceContext,
3849
+ inventoryCount: repoFiles.length,
3850
+ patternSet: INVENTORY_SCAN_PATTERN_SETS.a3CiOrReleaseDeploy,
3851
+ matchCount: 0
3852
+ })
3853
+ );
3369
3854
  }
3370
3855
  return {
3371
3856
  criterionId: "A3",
@@ -3435,27 +3920,46 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3435
3920
  ]
3436
3921
  };
3437
3922
  }
3438
- function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors, useV47Detectors) {
3923
+ function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors, useV47Detectors, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
3439
3924
  const evidence = [];
3440
3925
  const findings = [];
3441
3926
  const manifests = repoFiles.filter(isDependencyManifest);
3442
3927
  const lockfiles = repoFiles.filter(isLockfile);
3443
- const manifest = pickPrimaryDependencyManifest(repoPath, manifests);
3444
3928
  const lockfile = lockfiles[0];
3445
3929
  const rootLockfile = mono ? mono.sharedFiles.find(isLockfile) : void 0;
3446
3930
  const rootUpdateConfig = mono ? mono.sharedFiles.find(isDependencyUpdateConfig) : void 0;
3447
3931
  const hasLockfile = Boolean(lockfile) || Boolean(rootLockfile);
3448
3932
  const updateConfig = repoFiles.find(isDependencyUpdateConfig);
3449
3933
  const hasUpdateConfig = Boolean(updateConfig) || Boolean(rootUpdateConfig);
3450
- const auditConfig = manifests.some((file) => packageJsonHasAuditScript(repoPath, file));
3451
- const dependencySpecs = readDependencySpecs(repoPath, manifests);
3934
+ const auditConfig = withContentReadSignal(
3935
+ "A4",
3936
+ "dependency_automation_ratio",
3937
+ () => manifests.some((file) => packageJsonHasAuditScript(repoPath, file))
3938
+ );
3939
+ const { manifest, dependencySpecs } = withContentReadSignals(
3940
+ "A4",
3941
+ ["pinned_dependency_ratio", "declared_version_range_ratio", "dependency_count_sanity"],
3942
+ () => ({
3943
+ // Cite the manifest that actually carries dependencies for evidence/findings, not
3944
+ // whichever sorts first (e.g. a near-empty requirements-windows.txt that precedes the
3945
+ // real requirements.txt), so the evidence pointer reflects the file the A4 score is
3946
+ // derived from. The score itself is unaffected: it aggregates specs across ALL manifests.
3947
+ manifest: pickPrimaryDependencyManifest(repoPath, manifests),
3948
+ dependencySpecs: readDependencySpecs(repoPath, manifests)
3949
+ })
3950
+ );
3452
3951
  const pinnedDependencyCount = dependencySpecs.filter(
3453
3952
  (dependency) => isPinnedDependencyVersion(dependency.version)
3454
3953
  ).length;
3455
- if (manifest)
3456
- evidence.push(
3457
- evidenceForRelative(repoPath, manifest, "dependency_report", "Dependency manifest")
3954
+ if (manifest) {
3955
+ withContentReadSignals(
3956
+ "A4",
3957
+ ["pinned_dependency_ratio", "declared_version_range_ratio", "dependency_count_sanity"],
3958
+ () => evidence.push(
3959
+ evidenceForRelative(repoPath, manifest, "dependency_report", "Dependency manifest")
3960
+ )
3458
3961
  );
3962
+ }
3459
3963
  if (lockfile)
3460
3964
  evidence.push(
3461
3965
  evidenceForRelative(repoPath, lockfile, "dependency_report", "Dependency lockfile")
@@ -3488,13 +3992,36 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors,
3488
3992
  const deployableServiceFiles = useV47Detectors ? findDeployableServicePremiseFiles(repoPath, repoFiles, true) : [];
3489
3993
  const isAppOrService = (useV47Detectors ? deployableServiceFiles.length > 0 : isDeployableService(repoPath, repoFiles, useV27Detectors)) || packagedApplicationFiles.length > 0;
3490
3994
  if (isAppOrService && !hasLockfile && manifest) {
3491
- findings.push({
3492
- severity: "critical",
3493
- summary: "Dependency manifest is present without a detected lockfile \u2014 non-reproducible installs.",
3494
- evidence: evidenceForRelative(repoPath, manifest, "dependency_report", "Dependency manifest")
3495
- });
3995
+ withContentReadSignals(
3996
+ "A4",
3997
+ ["pinned_dependency_ratio", "declared_version_range_ratio", "dependency_count_sanity"],
3998
+ () => findings.push(
3999
+ inventoryAbsenceFinding({
4000
+ severity: "critical",
4001
+ summaries: {
4002
+ legacy: "Dependency manifest is present without a detected lockfile \u2014 non-reproducible installs.",
4003
+ currentTracked: "Dependency manifest is present, but the tracked scan-eligible inventory matched no recognized lockfile.",
4004
+ currentDirectoryWalk: "Dependency manifest is present, but the bounded directory walk matched no recognized lockfile."
4005
+ },
4006
+ evidence: evidenceForRelative(
4007
+ repoPath,
4008
+ manifest,
4009
+ "dependency_report",
4010
+ "Dependency manifest"
4011
+ ),
4012
+ context: inventoryAbsenceContext,
4013
+ inventoryCount: repoFiles.length + (mono?.sharedFiles.length ?? 0),
4014
+ patternSet: INVENTORY_SCAN_PATTERN_SETS.a4Lockfile,
4015
+ matchCount: 0
4016
+ })
4017
+ )
4018
+ );
3496
4019
  }
3497
- if (manifest && hasSuspiciousDependencies(repoPath, manifest, useV47Detectors)) {
4020
+ if (manifest && withContentReadSignal(
4021
+ "A4",
4022
+ "suspicious_dependencies",
4023
+ () => hasSuspiciousDependencies(repoPath, manifest, useV47Detectors)
4024
+ )) {
3498
4025
  findings.push({
3499
4026
  severity: "critical",
3500
4027
  summary: "Dependency manifest contains obviously suspicious placeholder dependency names.",
@@ -3600,7 +4127,7 @@ function hasDeclaredVersionConstraint(dependency) {
3600
4127
  if (/[~^<>=!]/.test(trimmed)) return true;
3601
4128
  return /^v?\d/.test(trimmed);
3602
4129
  }
3603
- function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors) {
4130
+ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
3604
4131
  const reconciliationArtifacts = useV27Detectors ? findClaimRealityReconciliationArtifacts(repoPath, repoFiles) : [];
3605
4132
  const claimSourceFiles = useV27Detectors ? findClaimSourceFiles(repoFiles) : repoFiles.filter(
3606
4133
  (file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
@@ -3648,16 +4175,24 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors) {
3648
4175
  ...allDocFiles,
3649
4176
  ...repoFiles.filter((file) => /(^|\/)(SECURITY|THREAT[_-]?MODEL)\.md$/i.test(file))
3650
4177
  ]);
3651
- const negativeSpaceDoc = [...negativeSpaceCandidates].find(
3652
- (file) => fileContains(repoPath, file, NEGATIVE_SPACE_SECTION_PATTERN) || fileContains(repoPath, file, NEGATIVE_SPACE_PHRASE_PATTERN)
4178
+ const negativeSpaceDoc = withContentReadSignal(
4179
+ "A5",
4180
+ "negative_space_documentation",
4181
+ () => [...negativeSpaceCandidates].find(
4182
+ (file) => fileContains(repoPath, file, NEGATIVE_SPACE_SECTION_PATTERN) || fileContains(repoPath, file, NEGATIVE_SPACE_PHRASE_PATTERN)
4183
+ )
3653
4184
  );
3654
4185
  if (negativeSpaceDoc) {
3655
- evidence.push(
3656
- evidenceForRelative(
3657
- repoPath,
3658
- negativeSpaceDoc,
3659
- "claim_reconciliation",
3660
- 'Documented limitations / threat model / "not covered" section'
4186
+ withContentReadSignal(
4187
+ "A5",
4188
+ "negative_space_documentation",
4189
+ () => evidence.push(
4190
+ evidenceForRelative(
4191
+ repoPath,
4192
+ negativeSpaceDoc,
4193
+ "claim_reconciliation",
4194
+ 'Documented limitations / threat model / "not covered" section'
4195
+ )
3661
4196
  )
3662
4197
  );
3663
4198
  }
@@ -3665,15 +4200,23 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors) {
3665
4200
  criterionId: "A5",
3666
4201
  positiveEvidence: evidence,
3667
4202
  findings: reconciliationArtifacts.length > 0 ? [] : [
3668
- {
4203
+ inventoryAbsenceFinding({
3669
4204
  // Proxy-only A5: cap at warning (never critical) — a static proxy is lower-confidence
3670
4205
  // than a curated registry or reconciliation artifact. Downgraded to info when the repo
3671
4206
  // documents honest negative space: that scoping IS a form of claim-reality discipline,
3672
4207
  // not an additional gap on top of the missing dedicated artifact.
3673
4208
  severity: negativeSpaceDoc ? "info" : "warning",
3674
- summary: negativeSpaceDoc ? "Claim source and implementation files are present; no dedicated claim-reality report artifact was supplied, but the repo explicitly documents what it does NOT cover/protect against \u2014 honest scoping, not overclaiming." : "Claim source and implementation files are present, but no dedicated claim-reality report artifact was supplied.",
3675
- evidence: firstEvidence
3676
- }
4209
+ summaries: {
4210
+ legacy: negativeSpaceDoc ? "Claim source and implementation files are present; no dedicated claim-reality report artifact was supplied, but the repo explicitly documents what it does NOT cover/protect against \u2014 honest scoping, not overclaiming." : "Claim source and implementation files are present, but no dedicated claim-reality report artifact was supplied.",
4211
+ currentTracked: negativeSpaceDoc ? "Claim-source and implementation-file signals are present, while the tracked scan-eligible inventory matched no dedicated claim-reality report artifact; the repo separately documents what it does NOT cover or protect against." : "Claim-source and implementation-file signals are present, while the tracked scan-eligible inventory matched no dedicated claim-reality report artifact.",
4212
+ currentDirectoryWalk: negativeSpaceDoc ? "Claim-source and implementation-file signals are present, while the bounded directory walk matched no dedicated claim-reality report artifact; the repo separately documents what it does NOT cover or protect against." : "Claim-source and implementation-file signals are present, while the bounded directory walk matched no dedicated claim-reality report artifact."
4213
+ },
4214
+ evidence: firstEvidence,
4215
+ context: inventoryAbsenceContext,
4216
+ inventoryCount: repoFiles.length,
4217
+ patternSet: INVENTORY_SCAN_PATTERN_SETS.a5ClaimRealityArtifact,
4218
+ matchCount: 0
4219
+ })
3677
4220
  ],
3678
4221
  // Recalibrated (goal_cejel_launch_hardening_combined_2026-07-06, Phase 3 H1):
3679
4222
  // reconciliation_artifact_depth is usually 0 on the generic-proxy path, so its prior 0.3
@@ -3786,18 +4329,33 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
3786
4329
  const workflows = repoFiles.filter(isCiWorkflow);
3787
4330
  const workflow = workflows[0];
3788
4331
  const rootWorkflows = mono ? mono.sharedFiles.filter(isCiWorkflow) : [];
3789
- const defaultBranchCiCount = workflows.filter((file) => workflowTargetsDefaultBranch(repoPath, file)).length + (mono ? rootWorkflows.filter((file) => workflowTargetsDefaultBranch(mono.root, file)).length : 0);
4332
+ const defaultBranchCiCount = withContentReadSignal(
4333
+ "B3",
4334
+ "default_branch_ci_depth",
4335
+ () => workflows.filter((file) => workflowTargetsDefaultBranch(repoPath, file)).length + (mono ? rootWorkflows.filter((file) => workflowTargetsDefaultBranch(mono.root, file)).length : 0)
4336
+ );
3790
4337
  if (packageJson && scripts.has("test")) {
3791
4338
  evidence.push(evidenceForRelative(repoPath, packageJson, "test_run", "Test script"));
3792
4339
  }
3793
4340
  if (packageJson && scripts.has("lint")) {
3794
4341
  evidence.push(evidenceForRelative(repoPath, packageJson, "ci_run", "Lint script"));
3795
4342
  }
3796
- if (workflow) evidence.push(evidenceForRelative(repoPath, workflow, "ci_run", "CI workflow"));
3797
- else if (rootWorkflows[0] && mono)
3798
- evidence.push(
3799
- evidenceForRelative(mono.root, rootWorkflows[0], "ci_run", "CI workflow (monorepo root)")
4343
+ if (workflow) {
4344
+ withContentReadSignals(
4345
+ "B3",
4346
+ ["ci_script_depth", "default_branch_ci_depth"],
4347
+ () => evidence.push(evidenceForRelative(repoPath, workflow, "ci_run", "CI workflow"))
3800
4348
  );
4349
+ } else if (rootWorkflows[0] && mono) {
4350
+ const rootWorkflow = rootWorkflows[0];
4351
+ withContentReadSignals(
4352
+ "B3",
4353
+ ["ci_script_depth", "default_branch_ci_depth"],
4354
+ () => evidence.push(
4355
+ evidenceForRelative(mono.root, rootWorkflow, "ci_run", "CI workflow (monorepo root)")
4356
+ )
4357
+ );
4358
+ }
3801
4359
  if (evidence.length === 0) return null;
3802
4360
  const CI_BUILD_COMMAND_PATTERN = /\b(cargo\s+build|cmake|ninja|make\s+(build|all))\b/i;
3803
4361
  const CI_COMMAND_CATEGORIES = [
@@ -3807,10 +4365,19 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
3807
4365
  CI_BUILD_COMMAND_PATTERN
3808
4366
  ];
3809
4367
  const scriptDepth = ["test", "lint", "typecheck", "build"].filter((s) => scripts.has(s)).length;
3810
- const countCiCommandCategories = (root, files) => CI_COMMAND_CATEGORIES.filter(
3811
- (pattern) => files.some((file) => fileContains(root, file, pattern))
3812
- ).length;
3813
- const ciCommandDepth = countCiCommandCategories(repoPath, workflows) + (mono ? countCiCommandCategories(mono.root, rootWorkflows) : 0);
4368
+ const { repoCategoryHits, rootCategoryHits } = withContentReadSignal(
4369
+ "B3",
4370
+ "ci_script_depth",
4371
+ () => ({
4372
+ repoCategoryHits: CI_COMMAND_CATEGORIES.map(
4373
+ (pattern) => workflows.some((file) => fileContains(repoPath, file, pattern))
4374
+ ),
4375
+ rootCategoryHits: mono ? CI_COMMAND_CATEGORIES.map(
4376
+ (pattern) => rootWorkflows.some((file) => fileContains(mono.root, file, pattern))
4377
+ ) : []
4378
+ })
4379
+ );
4380
+ const ciCommandDepth = repoCategoryHits.filter(Boolean).length + rootCategoryHits.filter(Boolean).length;
3814
4381
  const combinedDepth = scriptDepth + ciCommandDepth;
3815
4382
  return {
3816
4383
  criterionId: "B3",
@@ -3832,13 +4399,13 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
3832
4399
  label: `package script: ${name === "typecheck" ? "type-check" : name}`,
3833
4400
  count: scripts.has(name) ? 1 : 0
3834
4401
  })),
3835
- ...CI_COMMAND_CATEGORIES.map((pattern, index) => ({
4402
+ ...CI_COMMAND_CATEGORIES.map((_pattern, index) => ({
3836
4403
  label: `repository CI command: ${["test", "lint", "type-check", "build"][index]}`,
3837
- count: workflows.some((file) => fileContains(repoPath, file, pattern)) ? 1 : 0
4404
+ count: repoCategoryHits[index] ? 1 : 0
3838
4405
  })),
3839
- ...mono ? CI_COMMAND_CATEGORIES.map((pattern, index) => ({
4406
+ ...mono ? CI_COMMAND_CATEGORIES.map((_pattern, index) => ({
3840
4407
  label: `monorepo-root CI command: ${["test", "lint", "type-check", "build"][index]}`,
3841
- count: rootWorkflows.some((file) => fileContains(mono.root, file, pattern)) ? 1 : 0
4408
+ count: rootCategoryHits[index] ? 1 : 0
3842
4409
  })) : []
3843
4410
  ]
3844
4411
  }
@@ -3883,9 +4450,11 @@ function readAuditFreshnessCommitterYear(repoPath, scanLimitations) {
3883
4450
  function collectB4AuditEvidence(repoPath, repoFiles, generatedAt, useV19CommitYear = false, scanLimitations = /* @__PURE__ */ new Set()) {
3884
4451
  const evidence = [];
3885
4452
  const auditFiles = repoFiles.filter(isAuditFile);
3886
- for (const file of auditFiles.slice(0, 5)) {
3887
- evidence.push(evidenceForRelative(repoPath, file, "audit_log", "Audit or changelog artifact"));
3888
- }
4453
+ withContentReadSignal("B4", "audit_freshness_depth", () => {
4454
+ for (const file of auditFiles.slice(0, 5)) {
4455
+ evidence.push(evidenceForRelative(repoPath, file, "audit_log", "Audit or changelog artifact"));
4456
+ }
4457
+ });
3889
4458
  if (evidence.length === 0) {
3890
4459
  return buildNotApplicableSignal(
3891
4460
  "B4",
@@ -3930,7 +4499,11 @@ function collectB4AuditEvidence(repoPath, repoFiles, generatedAt, useV19CommitYe
3930
4499
  metric(
3931
4500
  "audit_freshness_depth",
3932
4501
  "Audit freshness depth",
3933
- countFilesContaining(repoPath, auditFiles, freshnessPattern),
4502
+ withContentReadSignal(
4503
+ "B4",
4504
+ "audit_freshness_depth",
4505
+ () => countFilesContaining(repoPath, auditFiles, freshnessPattern)
4506
+ ),
3934
4507
  Math.max(auditFiles.length, 1),
3935
4508
  0.2,
3936
4509
  "ratio",
@@ -3943,93 +4516,128 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
3943
4516
  const evidence = [];
3944
4517
  const findings = [];
3945
4518
  const docFiles = repoFiles.filter((file) => /\.(md|mdx)$/i.test(file));
3946
- const humanGateDoc = docFiles.find(
3947
- (file) => fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN)
4519
+ const humanGateDoc = withContentReadSignal(
4520
+ "B6",
4521
+ "human_gate_documented",
4522
+ () => docFiles.find((file) => fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN))
3948
4523
  );
3949
4524
  const implFiles = repoFiles.filter(isImplementationFile);
3950
- const gatedPrivilegeCheckFile = implFiles.find(
3951
- (file) => fileContains(repoPath, file, GATED_PRIVILEGE_CHECK_PATTERN) && fileContains(repoPath, file, SET_ROLE_PATTERN)
4525
+ const gatedPrivilegeCheckFile = withContentReadSignal(
4526
+ "B6",
4527
+ "fail_closed_privilege_check",
4528
+ () => implFiles.find(
4529
+ (file) => fileContains(repoPath, file, GATED_PRIVILEGE_CHECK_PATTERN) && fileContains(repoPath, file, SET_ROLE_PATTERN)
4530
+ )
3952
4531
  );
3953
- const killSwitchFile = implFiles.find(
3954
- (file) => fileContains(repoPath, file, KILL_SWITCH_NAME_PATTERN) && fileContains(repoPath, file, KILL_SWITCH_FAIL_CLOSED_PATTERN)
4532
+ const killSwitchFile = withContentReadSignal(
4533
+ "B6",
4534
+ "kill_switch_fail_safe_present",
4535
+ () => implFiles.find(
4536
+ (file) => fileContains(repoPath, file, KILL_SWITCH_NAME_PATTERN) && fileContains(repoPath, file, KILL_SWITCH_FAIL_CLOSED_PATTERN)
4537
+ )
3955
4538
  );
3956
4539
  const codeownersFile = repoFiles.find((file) => /(^|\/)CODEOWNERS$/.test(file));
3957
- const reviewGateDoc = docFiles.find(
3958
- (file) => fileContains(repoPath, file, REQUIRED_REVIEW_PATTERN)
4540
+ const reviewGateDoc = withContentReadSignal(
4541
+ "B6",
4542
+ "protected_path_review_gate",
4543
+ () => docFiles.find((file) => fileContains(repoPath, file, REQUIRED_REVIEW_PATTERN))
3959
4544
  );
3960
4545
  const protectedPathReviewGate = codeownersFile ?? reviewGateDoc;
3961
- const executableFiles = implFiles.filter(
3962
- (file) => fileContains(repoPath, file, SQL_EXEC_PATTERN)
3963
- );
3964
- const historicalUngatedEscalationFiles = executableFiles.filter((file) => {
3965
- if (useV39Detectors && !fileHasExecutedPrivilegeEscalation(repoPath, file)) return false;
3966
- const hasEscalation = fileContains(repoPath, file, ROLE_MEMBERSHIP_GRANT_PATTERN) || fileContains(repoPath, file, SUPERUSER_ESCALATION_PATTERN);
3967
- if (!hasEscalation) return false;
3968
- return !fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN);
4546
+ const { executableFiles, v21ExecutedEscalationFileSet, productionUngatedEscalationFiles } = withContentReadSignal("B6", "privilege_escalation_cleanliness", () => {
4547
+ const executable = implFiles.filter((file) => fileContains(repoPath, file, SQL_EXEC_PATTERN));
4548
+ const historicalUngated = executable.filter((file) => {
4549
+ if (useV39Detectors && !fileHasExecutedPrivilegeEscalation(repoPath, file)) return false;
4550
+ const hasEscalation = fileContains(repoPath, file, ROLE_MEMBERSHIP_GRANT_PATTERN) || fileContains(repoPath, file, SUPERUSER_ESCALATION_PATTERN);
4551
+ if (!hasEscalation) return false;
4552
+ return !fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN);
4553
+ });
4554
+ const v21Executed = useV21ExecutedEscalations ? repoFiles.filter(
4555
+ (file) => isV39AuthoredProductionPath(file) && (fileHasV21RawSqlEscalation(repoPath, file) || isImplementationFile(file) && fileHasV21DriverEscalation(repoPath, file))
4556
+ ) : [];
4557
+ const v21ExecutedSet = new Set(v21Executed);
4558
+ const ungated = [.../* @__PURE__ */ new Set([...historicalUngated, ...v21Executed])].filter(
4559
+ (file) => !fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN)
4560
+ );
4561
+ const productionUngated = ungated.filter(
4562
+ (file) => useV39Detectors ? isV39AuthoredProductionPath(file) : !isTestOrFixturePath(file)
4563
+ );
4564
+ return {
4565
+ executableFiles: executable,
4566
+ v21ExecutedEscalationFileSet: v21ExecutedSet,
4567
+ productionUngatedEscalationFiles: productionUngated
4568
+ };
3969
4569
  });
3970
- const v21ExecutedEscalationFiles = useV21ExecutedEscalations ? repoFiles.filter(
3971
- (file) => isV39AuthoredProductionPath(file) && (fileHasV21RawSqlEscalation(repoPath, file) || isImplementationFile(file) && fileHasV21DriverEscalation(repoPath, file))
3972
- ) : [];
3973
- const v21ExecutedEscalationFileSet = new Set(v21ExecutedEscalationFiles);
3974
- const ungatedEscalationFiles = [
3975
- .../* @__PURE__ */ new Set([...historicalUngatedEscalationFiles, ...v21ExecutedEscalationFiles])
3976
- ].filter((file) => !fileContains(repoPath, file, HUMAN_GATE_MARKER_PATTERN));
3977
- const productionUngatedEscalationFiles = ungatedEscalationFiles.filter(
3978
- (file) => useV39Detectors ? isV39AuthoredProductionPath(file) : !isTestOrFixturePath(file)
3979
- );
3980
4570
  if (humanGateDoc) {
3981
- evidence.push(
3982
- evidenceForRelative(
3983
- repoPath,
3984
- humanGateDoc,
3985
- "artifact",
3986
- "Documents privileged operations as human-executed/gated"
4571
+ withContentReadSignal(
4572
+ "B6",
4573
+ "human_gate_documented",
4574
+ () => evidence.push(
4575
+ evidenceForRelative(
4576
+ repoPath,
4577
+ humanGateDoc,
4578
+ "artifact",
4579
+ "Documents privileged operations as human-executed/gated"
4580
+ )
3987
4581
  )
3988
4582
  );
3989
4583
  }
3990
4584
  if (gatedPrivilegeCheckFile) {
3991
- evidence.push(
3992
- evidenceForRelative(
3993
- repoPath,
3994
- gatedPrivilegeCheckFile,
3995
- "artifact",
3996
- "Fail-closed privilege-membership check before role elevation"
4585
+ withContentReadSignal(
4586
+ "B6",
4587
+ "fail_closed_privilege_check",
4588
+ () => evidence.push(
4589
+ evidenceForRelative(
4590
+ repoPath,
4591
+ gatedPrivilegeCheckFile,
4592
+ "artifact",
4593
+ "Fail-closed privilege-membership check before role elevation"
4594
+ )
3997
4595
  )
3998
4596
  );
3999
4597
  }
4000
4598
  if (killSwitchFile) {
4001
- evidence.push(
4002
- evidenceForRelative(
4003
- repoPath,
4004
- killSwitchFile,
4005
- "artifact",
4006
- "Un-overridable kill-switch / fail-safe governance toggle"
4599
+ withContentReadSignal(
4600
+ "B6",
4601
+ "kill_switch_fail_safe_present",
4602
+ () => evidence.push(
4603
+ evidenceForRelative(
4604
+ repoPath,
4605
+ killSwitchFile,
4606
+ "artifact",
4607
+ "Un-overridable kill-switch / fail-safe governance toggle"
4608
+ )
4007
4609
  )
4008
4610
  );
4009
4611
  }
4010
4612
  if (protectedPathReviewGate) {
4011
- evidence.push(
4012
- evidenceForRelative(
4013
- repoPath,
4014
- protectedPathReviewGate,
4015
- "artifact",
4016
- "CODEOWNERS/required-review gate on protected paths"
4613
+ withContentReadSignal(
4614
+ "B6",
4615
+ "protected_path_review_gate",
4616
+ () => evidence.push(
4617
+ evidenceForRelative(
4618
+ repoPath,
4619
+ protectedPathReviewGate,
4620
+ "artifact",
4621
+ "CODEOWNERS/required-review gate on protected paths"
4622
+ )
4017
4623
  )
4018
4624
  );
4019
4625
  }
4020
- for (const file of productionUngatedEscalationFiles.slice(0, 5)) {
4021
- const isV21ExecutedShape = v21ExecutedEscalationFileSet.has(file);
4022
- findings.push({
4023
- severity: "critical",
4024
- summary: isV21ExecutedShape ? "An authored SQL artifact contains, or a direct database-driver call executes, an administrative role grant, SUPERUSER escalation, or schema-wide table privilege grant with no documented human gate." : "Role-membership GRANT or SUPERUSER escalation executes in code with no documented human gate.",
4025
- evidence: evidenceForRelative(
4026
- repoPath,
4027
- file,
4028
- "artifact",
4029
- isV21ExecutedShape ? "Ungated authored or directly executed administrative SQL statement" : "Ungated privilege-escalation statement"
4030
- )
4031
- });
4032
- }
4626
+ withContentReadSignal("B6", "privilege_escalation_cleanliness", () => {
4627
+ for (const file of productionUngatedEscalationFiles.slice(0, 5)) {
4628
+ const isV21ExecutedShape = v21ExecutedEscalationFileSet.has(file);
4629
+ findings.push({
4630
+ severity: "critical",
4631
+ summary: isV21ExecutedShape ? "This file contains an authored administrative SQL statement \u2014 role-membership grant, SUPERUSER escalation, or schema-wide table privilege grant \u2014 or executes one through a direct database-driver call, and does not itself contain text matching the human-gate marker pattern." : "This file executes a role-membership GRANT or SUPERUSER escalation and does not itself contain text matching the human-gate marker pattern.",
4632
+ evidence: evidenceForRelative(
4633
+ repoPath,
4634
+ file,
4635
+ "artifact",
4636
+ isV21ExecutedShape ? "Ungated authored or directly executed administrative SQL statement" : "Ungated privilege-escalation statement"
4637
+ )
4638
+ });
4639
+ }
4640
+ });
4033
4641
  if (evidence.length === 0 && findings.length === 0) {
4034
4642
  return buildNotApplicableSignal(
4035
4643
  "B6",
@@ -4051,7 +4659,7 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
4051
4659
  }
4052
4660
  )
4053
4661
  ] : [];
4054
- const hasPrivilegedOpsSurface = humanGateDoc != null || gatedPrivilegeCheckFile != null || executableFiles.length > 0 || v21ExecutedEscalationFiles.length > 0;
4662
+ const hasPrivilegedOpsSurface = humanGateDoc != null || gatedPrivilegeCheckFile != null || executableFiles.length > 0 || v21ExecutedEscalationFileSet.size > 0;
4055
4663
  return {
4056
4664
  criterionId: "B6",
4057
4665
  positiveEvidence: evidence,
@@ -4348,10 +4956,20 @@ function parseGitTrackedFiles(repoPath, output, includeHardExcluded) {
4348
4956
  continue;
4349
4957
  }
4350
4958
  try {
4351
- if (!lstatSync(fullPath).isFile()) {
4959
+ const stat = lstatSync(fullPath);
4960
+ if (!stat.isFile()) {
4352
4961
  recordContentSkip(fullPath, "non_regular_file", true);
4353
4962
  continue;
4354
4963
  }
4964
+ if (stat.size > MAX_REPOSITORY_CONTENT_BYTES) {
4965
+ const affectedCriteria = affectedCriteriaForUnavailablePath(file);
4966
+ if (affectedCriteria.length > 0) {
4967
+ recordContentSkipForCriteria(fullPath, "too_large", affectedCriteria, true);
4968
+ } else {
4969
+ recordContentSkip(fullPath, "too_large", true);
4970
+ }
4971
+ continue;
4972
+ }
4355
4973
  } catch (error) {
4356
4974
  recordFilesystemSkip(fullPath, error, false, true);
4357
4975
  continue;
@@ -4363,6 +4981,39 @@ function parseGitTrackedFiles(repoPath, output, includeHardExcluded) {
4363
4981
  }
4364
4982
  return files.sort();
4365
4983
  }
4984
+ function affectedCriteriaForUnavailablePath(file) {
4985
+ const affected = /* @__PURE__ */ new Set();
4986
+ if (isDependencyManifest(file) || isLockfile(file)) affected.add("A4");
4987
+ if (isCiWorkflow(file)) {
4988
+ affected.add("A1");
4989
+ affected.add("A3");
4990
+ affected.add("B2");
4991
+ affected.add("B3");
4992
+ }
4993
+ if (isDeployConfig(file)) {
4994
+ affected.add("A3");
4995
+ affected.add("B2");
4996
+ }
4997
+ if (isTestFile(file)) {
4998
+ affected.add("A1");
4999
+ affected.add("A2");
5000
+ }
5001
+ if (isImplementationFile(file)) {
5002
+ affected.add("A1");
5003
+ affected.add("A2");
5004
+ affected.add("A3");
5005
+ affected.add("B1");
5006
+ affected.add("B6");
5007
+ }
5008
+ if (/\.(?:md|mdx|rst|txt)$/i.test(file)) {
5009
+ affected.add("A5");
5010
+ affected.add("B4");
5011
+ affected.add("B5");
5012
+ affected.add("B6");
5013
+ }
5014
+ if (isAuditFile(file)) affected.add("B5");
5015
+ return [...affected];
5016
+ }
4366
5017
  function directoryInventory(repoPath) {
4367
5018
  const files = [];
4368
5019
  const rootDevice = statSync(repoPath).dev;
@@ -4375,6 +5026,7 @@ function collectRepoFileInventory(repoPath) {
4375
5026
  return {
4376
5027
  repoFiles: parseGitTrackedFiles(repoPath, tracked.stdout, false),
4377
5028
  inventoryFiles: parseGitTrackedFiles(repoPath, tracked.stdout, true),
5029
+ repoFileInventoryScope: "tracked-scan-eligible",
4378
5030
  scanLimitations: []
4379
5031
  };
4380
5032
  }
@@ -4482,8 +5134,13 @@ function visitRepoDir(repoPath, dirPath, files, rootDevice) {
4482
5134
  continue;
4483
5135
  }
4484
5136
  const repoRelativePath = relative(repoPath, fullPath);
4485
- if (size > 512e3) {
4486
- recordContentSkip(fullPath, "too_large");
5137
+ if (size > MAX_REPOSITORY_CONTENT_BYTES) {
5138
+ const affectedCriteria = affectedCriteriaForUnavailablePath(repoRelativePath);
5139
+ if (affectedCriteria.length > 0) {
5140
+ recordContentSkipForCriteria(fullPath, "too_large", affectedCriteria);
5141
+ } else {
5142
+ recordContentSkip(fullPath, "too_large");
5143
+ }
4487
5144
  continue;
4488
5145
  }
4489
5146
  if (!isPotentialContentPath(repoRelativePath)) {
@@ -4925,6 +5582,9 @@ function isRecord(value) {
4925
5582
  function isTestRunnerConfig(repoPath, file) {
4926
5583
  return /(^|\/)(vitest|jest|pytest|playwright|cypress|mocha)\.config\.[cm]?[jt]s$/.test(file) || /(^|\/)(pytest\.ini|tox\.ini)$/.test(file) || /(^|\/)phpunit\.xml(\.dist)?$/.test(file) || /(^|\/)pyproject\.toml$/.test(file) && fileContains(repoPath, file, /^\[tool\.pytest(?:\.|\])/m) || /(^|\/)CMakeLists\.txt$/.test(file) && fileContains(repoPath, file, /\b(?:enable_testing|add_test|ctest_add_tests)\s*\(/i) || /(^|\/)Makefile$/.test(file) && fileContains(repoPath, file, /^\s*(?:test|tests|check)\s*:/m);
4927
5584
  }
5585
+ function isRecipeTestEntry(repoPath, file) {
5586
+ return isRecipeFile(file) && fileContains(repoPath, file, /^\s*(?:test|tests|check)\s*:/m);
5587
+ }
4928
5588
  function findCoverageConfigFiles(repoPath, repoFiles, authoredOnly = false) {
4929
5589
  return repoFiles.filter(
4930
5590
  (file) => (!authoredOnly || isAuthoredProductionPath(file)) && isCoverageConfig(repoPath, file)
@@ -5218,6 +5878,580 @@ function packageJsonHasCoverageTooling(repoPath, file) {
5218
5878
  }
5219
5879
  return false;
5220
5880
  }
5881
+ function isV23CoverageConfig(repoPath, file) {
5882
+ if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
5883
+ const contents = stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"));
5884
+ const property = /(?:^|[{,])\s*["']?coverage["']?\s*:\s*([^,\r\n}]*)/im.exec(contents)?.[1]?.trim();
5885
+ if (!property || /^(?:false\b|null\b|undefined\b)/i.test(property)) return false;
5886
+ if (/^\{/.test(property) && /\benabled\s*:\s*false\b/i.test(contents)) return false;
5887
+ return true;
5888
+ }
5889
+ if (/(^|\/)jest\.config\.[cm]?[jt]s$/.test(file)) {
5890
+ const contents = stripJavaScriptComments(readRepoText(join(repoPath, file), "utf8"));
5891
+ return /(?:^|[{,])\s*["']?collectCoverage["']?\s*:\s*true\b/m.test(contents) || /(?:^|[{,])\s*["']?(?:coverageThreshold|coverageProvider|coverageDirectory)["']?\s*:/m.test(
5892
+ contents
5893
+ );
5894
+ }
5895
+ if (/(^|\/)package\.json$/.test(file)) {
5896
+ return packageJsonHasStaticCoverageTooling(repoPath, file);
5897
+ }
5898
+ return isCoverageConfig(repoPath, file);
5899
+ }
5900
+ function coverageConfigurationDecision(repoPath, file) {
5901
+ const detectorState = isV23CoverageConfig(repoPath, file) ? "collects" : "none";
5902
+ if (/(^|\/)vitest\.config\.[cm]?[jt]s$/.test(file)) {
5903
+ const rawContents = readRepoText(join(repoPath, file), "utf8");
5904
+ const contents = stripJavaScriptComments(rawContents);
5905
+ const match = /(?:^|[{,])\s*["']?coverage["']?\s*:\s*([^,\r\n}]*)/im.exec(contents);
5906
+ if (!match) return null;
5907
+ return {
5908
+ path: file,
5909
+ source: "runner-configuration",
5910
+ locator: "coverage",
5911
+ evidence: rawContents.slice(match.index, match.index + match[0].length).trim(),
5912
+ detectorRecognized: true,
5913
+ detectorState
5914
+ };
5915
+ }
5916
+ if (/(^|\/)jest\.config\.[cm]?[jt]s$/.test(file)) {
5917
+ const rawContents = readRepoText(join(repoPath, file), "utf8");
5918
+ const contents = stripJavaScriptComments(rawContents);
5919
+ const match = /(?:^|[{,])\s*["']?(collectCoverage|coverageThreshold|coverageProvider|coverageDirectory)["']?\s*:\s*([^,\r\n}]*)/m.exec(
5920
+ contents
5921
+ );
5922
+ if (!match) return null;
5923
+ return {
5924
+ path: file,
5925
+ source: "runner-configuration",
5926
+ locator: match[1] ?? "coverage",
5927
+ evidence: rawContents.slice(match.index, match.index + match[0].length).trim(),
5928
+ detectorRecognized: true,
5929
+ detectorState
5930
+ };
5931
+ }
5932
+ if (detectorState === "none") return null;
5933
+ return {
5934
+ path: file,
5935
+ source: "runner-configuration",
5936
+ locator: "coverage",
5937
+ evidence: file,
5938
+ detectorRecognized: true,
5939
+ detectorState
5940
+ };
5941
+ }
5942
+ function packageJsonHasStaticCoverageTooling(repoPath, file) {
5943
+ const fullPath = join(repoPath, file);
5944
+ if (!isRegularFile(fullPath)) return false;
5945
+ const parsed = parseJsonObject(readRepoText(fullPath, "utf8"));
5946
+ if (!parsed) return false;
5947
+ return ["nyc", "c8", "istanbul"].some((key) => {
5948
+ const config = parsed[key];
5949
+ return typeof config === "object" && config !== null && !Array.isArray(config);
5950
+ });
5951
+ }
5952
+ function analyzeCoverageConfiguration(repoPath, repoFiles, authoredOnly = false) {
5953
+ const eligibleFiles = repoFiles.filter(
5954
+ (file) => !authoredOnly || isAuthoredProductionPath(file)
5955
+ );
5956
+ const eligibleSet = new Set(eligibleFiles);
5957
+ const coverageFiles = /* @__PURE__ */ new Set();
5958
+ const unresolvedFiles = /* @__PURE__ */ new Set();
5959
+ const ciCommandsByFile = /* @__PURE__ */ new Map();
5960
+ const detectorDecisions = [];
5961
+ let hasCoverageCommand = false;
5962
+ for (const file of eligibleFiles.filter(isCiWorkflow)) {
5963
+ const commands = extractCiRunCommands(readRepoText(join(repoPath, file), "utf8"));
5964
+ ciCommandsByFile.set(file, commands);
5965
+ const commandDecisions = commands.map(
5966
+ (command, index) => coverageDetectorDecision(repoPath, eligibleSet, {
5967
+ path: file,
5968
+ source: "ci-run",
5969
+ locator: String(index),
5970
+ command
5971
+ })
5972
+ );
5973
+ detectorDecisions.push(...commandDecisions);
5974
+ const state = combineCoverageCommandStates(
5975
+ commandDecisions.map((decision) => decision.detectorState)
5976
+ );
5977
+ if (state === "collects") {
5978
+ coverageFiles.add(file);
5979
+ hasCoverageCommand = true;
5980
+ }
5981
+ if (state === "unresolved") unresolvedFiles.add(file);
5982
+ }
5983
+ const ciCommands = [...ciCommandsByFile.values()].flat();
5984
+ for (const file of eligibleFiles) {
5985
+ if (isCiWorkflow(file) || isRecipeFile(file)) continue;
5986
+ if (/(^|\/)package\.json$/.test(file)) {
5987
+ if (packageJsonHasStaticCoverageTooling(repoPath, file)) coverageFiles.add(file);
5988
+ const packageState = packageScriptCoverageState(
5989
+ repoPath,
5990
+ eligibleSet,
5991
+ file,
5992
+ file === "package.json" ? ciCommands : [],
5993
+ detectorDecisions
5994
+ );
5995
+ if (packageState === "collects") {
5996
+ coverageFiles.add(file);
5997
+ hasCoverageCommand = true;
5998
+ }
5999
+ if (packageState === "unresolved") unresolvedFiles.add(file);
6000
+ continue;
6001
+ }
6002
+ const configurationDecision = coverageConfigurationDecision(repoPath, file);
6003
+ if (configurationDecision) detectorDecisions.push(configurationDecision);
6004
+ if (configurationDecision?.detectorState === "collects") coverageFiles.add(file);
6005
+ }
6006
+ for (const file of eligibleFiles.filter(isRecipeFile)) {
6007
+ const state = recipeCoverageState(
6008
+ repoPath,
6009
+ eligibleSet,
6010
+ file,
6011
+ ciCommands,
6012
+ detectorDecisions
6013
+ );
6014
+ if (state === "collects") {
6015
+ coverageFiles.add(file);
6016
+ hasCoverageCommand = true;
6017
+ }
6018
+ if (state === "unresolved") unresolvedFiles.add(file);
6019
+ }
6020
+ for (const file of coverageFiles) unresolvedFiles.delete(file);
6021
+ return {
6022
+ coverageFiles: [...coverageFiles].sort(),
6023
+ unresolvedFiles: [...unresolvedFiles].sort(),
6024
+ hasCoverageCommand,
6025
+ detectorDecisions
6026
+ };
6027
+ }
6028
+ function stripJavaScriptComments(contents) {
6029
+ let output = "";
6030
+ let quote = null;
6031
+ let escaped = false;
6032
+ let lineComment = false;
6033
+ let blockComment = false;
6034
+ for (let index = 0; index < contents.length; index += 1) {
6035
+ const character = contents[index] ?? "";
6036
+ const next = contents[index + 1] ?? "";
6037
+ if (lineComment) {
6038
+ if (character === "\n") {
6039
+ lineComment = false;
6040
+ output += "\n";
6041
+ } else {
6042
+ output += " ";
6043
+ }
6044
+ continue;
6045
+ }
6046
+ if (blockComment) {
6047
+ if (character === "*" && next === "/") {
6048
+ output += " ";
6049
+ index += 1;
6050
+ blockComment = false;
6051
+ } else {
6052
+ output += character === "\n" ? "\n" : " ";
6053
+ }
6054
+ continue;
6055
+ }
6056
+ if (escaped) {
6057
+ output += character;
6058
+ escaped = false;
6059
+ continue;
6060
+ }
6061
+ if (quote) {
6062
+ output += character;
6063
+ if (character === "\\") escaped = true;
6064
+ else if (character === quote) quote = null;
6065
+ continue;
6066
+ }
6067
+ if (character === "'" || character === '"' || character === "`") {
6068
+ quote = character;
6069
+ output += character;
6070
+ continue;
6071
+ }
6072
+ if (character === "/" && next === "/") {
6073
+ output += " ";
6074
+ index += 1;
6075
+ lineComment = true;
6076
+ continue;
6077
+ }
6078
+ if (character === "/" && next === "*") {
6079
+ output += " ";
6080
+ index += 1;
6081
+ blockComment = true;
6082
+ continue;
6083
+ }
6084
+ output += character;
6085
+ }
6086
+ return output;
6087
+ }
6088
+ function packageScriptCoverageState(repoPath, repoFiles, packageJson, ciCommands, detectorDecisions) {
6089
+ const scripts = readPackageScripts(join(repoPath, packageJson));
6090
+ if (scripts.size === 0) return "none";
6091
+ const roots = /* @__PURE__ */ new Set();
6092
+ let unresolved = false;
6093
+ if (scripts.has("test")) roots.add("test");
6094
+ for (const command of ciCommands) {
6095
+ const references = packageScriptReferences(command, scripts);
6096
+ for (const name of references.names) roots.add(name);
6097
+ if (references.dynamic && [...scripts.values()].some(
6098
+ (candidate) => commandInvokesCoverageTool(candidate) || containsPotentialCoverageFlag(candidate)
6099
+ )) {
6100
+ unresolved = true;
6101
+ }
6102
+ }
6103
+ const queue = [...roots];
6104
+ const visited = /* @__PURE__ */ new Set();
6105
+ while (queue.length > 0) {
6106
+ const name = queue.shift();
6107
+ if (!name || visited.has(name)) continue;
6108
+ const command = scripts.get(name);
6109
+ if (command === void 0) continue;
6110
+ visited.add(name);
6111
+ const decision = coverageDetectorDecision(repoPath, repoFiles, {
6112
+ path: packageJson,
6113
+ source: "package-script",
6114
+ locator: name,
6115
+ command
6116
+ });
6117
+ detectorDecisions.push(decision);
6118
+ const state = decision.detectorState;
6119
+ if (state === "collects") return "collects";
6120
+ if (state === "unresolved") unresolved = true;
6121
+ const references = packageScriptReferences(command, scripts);
6122
+ for (const referenced of references.names) {
6123
+ if (!visited.has(referenced)) queue.push(referenced);
6124
+ }
6125
+ for (const lifecycle of [`pre${name}`, `post${name}`]) {
6126
+ if (scripts.has(lifecycle) && !visited.has(lifecycle)) queue.push(lifecycle);
6127
+ }
6128
+ if (references.dynamic && [...scripts.values()].some(
6129
+ (candidate) => commandInvokesCoverageTool(candidate) || containsPotentialCoverageFlag(candidate)
6130
+ )) {
6131
+ unresolved = true;
6132
+ }
6133
+ }
6134
+ return unresolved ? "unresolved" : "none";
6135
+ }
6136
+ function packageScriptReferences(command, scripts) {
6137
+ const names = /* @__PURE__ */ new Set();
6138
+ let dynamic = false;
6139
+ for (const segment of splitShellCommandSegments(command)) {
6140
+ for (const match of segment.matchAll(
6141
+ /\bpnpm\s+run\s+\/((?:\\.|[^/])+)\/([dgimsuvy]*)/giu
6142
+ )) {
6143
+ const source = match[1];
6144
+ if (!source) continue;
6145
+ try {
6146
+ const flags = (match[2] ?? "").replace(/[dgy]/g, "");
6147
+ const selector = new RegExp(source, flags);
6148
+ for (const name of scripts.keys()) {
6149
+ if (selector.test(name)) names.add(name);
6150
+ selector.lastIndex = 0;
6151
+ }
6152
+ } catch {
6153
+ dynamic = true;
6154
+ }
6155
+ }
6156
+ for (const match of segment.matchAll(
6157
+ /\b(?:npm|pnpm|yarn)\s+(?:run\s+)?([A-Za-z0-9][A-Za-z0-9:._-]*)/giu
6158
+ )) {
6159
+ const name = match[1];
6160
+ if (name && scripts.has(name)) names.add(name);
6161
+ }
6162
+ if (/\b(?:npm|pnpm|yarn)\s+run\s+(?:\$|["']\$|`)/u.test(segment)) dynamic = true;
6163
+ }
6164
+ return { names: [...names], dynamic };
6165
+ }
6166
+ function extractCiRunCommands(contents) {
6167
+ const lines = contents.split(/\r?\n/);
6168
+ const commands = [];
6169
+ for (let index = 0; index < lines.length; index += 1) {
6170
+ const line = lines[index] ?? "";
6171
+ const match = /^(\s*)(?:-\s*)?run\s*:\s*(.*)$/u.exec(line);
6172
+ if (!match) continue;
6173
+ const indentation = match[1]?.length ?? 0;
6174
+ const value = match[2]?.trim() ?? "";
6175
+ if (value === "|" || value === ">" || value.startsWith("|-") || value.startsWith(">-")) {
6176
+ const block = [];
6177
+ for (index += 1; index < lines.length; index += 1) {
6178
+ const nested = lines[index] ?? "";
6179
+ if (nested.trim().length === 0) {
6180
+ block.push("");
6181
+ continue;
6182
+ }
6183
+ const nestedIndentation = /^\s*/u.exec(nested)?.[0].length ?? 0;
6184
+ if (nestedIndentation <= indentation) {
6185
+ index -= 1;
6186
+ break;
6187
+ }
6188
+ block.push(nested.slice(Math.min(nested.length, indentation + 2)));
6189
+ }
6190
+ commands.push(block.join(value.startsWith(">") ? " " : "\n"));
6191
+ continue;
6192
+ }
6193
+ if (value.length === 0) continue;
6194
+ const quoted = /^(?:"([\s\S]*)"|'([\s\S]*)')$/u.exec(value);
6195
+ commands.push(quoted ? quoted[1] ?? quoted[2] ?? "" : value);
6196
+ }
6197
+ return commands;
6198
+ }
6199
+ function isRecipeFile(file) {
6200
+ return /(^|\/)(?:Makefile|makefile|GNUmakefile|[Jj]ustfile)$/.test(file);
6201
+ }
6202
+ function recipeCoverageState(repoPath, repoFiles, file, ciCommands, detectorDecisions) {
6203
+ const targets = readRecipeTargets(readRepoText(join(repoPath, file), "utf8"));
6204
+ const roots = new Set(["test", "tests", "check"].filter((target) => targets.has(target)));
6205
+ const runner = /[Jj]ustfile$/.test(file) ? "just" : "make";
6206
+ for (const command of ciCommands) {
6207
+ for (const match of command.matchAll(new RegExp(`\\b${runner}\\s+([A-Za-z0-9._-]+)`, "gu"))) {
6208
+ const target = match[1];
6209
+ if (target && targets.has(target)) roots.add(target);
6210
+ }
6211
+ }
6212
+ const queue = [...roots];
6213
+ const visited = /* @__PURE__ */ new Set();
6214
+ let unresolved = false;
6215
+ while (queue.length > 0) {
6216
+ const name = queue.shift();
6217
+ if (!name || visited.has(name)) continue;
6218
+ const target = targets.get(name);
6219
+ if (!target) continue;
6220
+ visited.add(name);
6221
+ const commandDecisions = target.commands.map(
6222
+ (command, index) => coverageDetectorDecision(repoPath, repoFiles, {
6223
+ path: file,
6224
+ source: "recipe-command",
6225
+ locator: `${name}:${index}`,
6226
+ command
6227
+ })
6228
+ );
6229
+ detectorDecisions.push(...commandDecisions);
6230
+ const state = combineCoverageCommandStates(
6231
+ commandDecisions.map((decision) => decision.detectorState)
6232
+ );
6233
+ if (state === "collects") return "collects";
6234
+ if (state === "unresolved") unresolved = true;
6235
+ for (const dependency of target.dependencies) {
6236
+ if (targets.has(dependency) && !visited.has(dependency)) queue.push(dependency);
6237
+ }
6238
+ for (const command of target.commands) {
6239
+ const pattern = new RegExp(`(?:\\$\\(MAKE\\)|\\b${runner})\\s+([A-Za-z0-9._-]+)`, "gu");
6240
+ for (const match of command.matchAll(pattern)) {
6241
+ const referenced = match[1];
6242
+ if (referenced && targets.has(referenced) && !visited.has(referenced)) queue.push(referenced);
6243
+ }
6244
+ }
6245
+ }
6246
+ return unresolved ? "unresolved" : "none";
6247
+ }
6248
+ function readRecipeTargets(contents) {
6249
+ const targets = /* @__PURE__ */ new Map();
6250
+ let active = null;
6251
+ for (const line of contents.split(/\r?\n/)) {
6252
+ const header = /^([A-Za-z0-9][A-Za-z0-9._-]*)\s*:\s*([^#]*)/u.exec(line);
6253
+ if (header) {
6254
+ const name = header[1];
6255
+ if (!name) continue;
6256
+ active = {
6257
+ dependencies: (header[2] ?? "").trim().split(/\s+/u).filter(Boolean),
6258
+ commands: []
6259
+ };
6260
+ targets.set(name, active);
6261
+ continue;
6262
+ }
6263
+ if (active && /^\s+/u.test(line) && line.trim().length > 0) {
6264
+ active.commands.push(line.trim().replace(/^[@+-]+/u, ""));
6265
+ continue;
6266
+ }
6267
+ if (line.trim().length > 0 && !line.trimStart().startsWith("#")) active = null;
6268
+ }
6269
+ return targets;
6270
+ }
6271
+ function coverageCommandEvaluation(repoPath, repoFiles, command, depth = 0) {
6272
+ if (depth > 4) {
6273
+ const recognized = containsPotentialCoverageFlag(command);
6274
+ return { recognized, state: recognized ? "unresolved" : "none" };
6275
+ }
6276
+ const normalized = normalizeCoverageCommandPrefixes(command);
6277
+ const segments = normalized.splitShellOperators ? splitShellCommandSegments(normalized.segment) : [normalized.segment];
6278
+ const evaluations = segments.map((rawSegment) => {
6279
+ const candidate = normalizeCoverageCommandPrefixes(rawSegment);
6280
+ if (commandInvokesCoverageTool(candidate.segment)) {
6281
+ return { recognized: true, state: "collects" };
6282
+ }
6283
+ const words = shellWords(candidate.segment);
6284
+ if (words.length === 0) return { recognized: false, state: "none" };
6285
+ const direct = coverageFlagCommandState(words);
6286
+ if (direct !== "none") return { recognized: true, state: direct };
6287
+ const hasCoverageSyntax = containsPotentialCoverageFlag(candidate.segment);
6288
+ if (isRecognizedCoverageFlagRunner(words)) {
6289
+ return { recognized: hasCoverageSyntax, state: "none" };
6290
+ }
6291
+ const wrapper = reachableShellWrapper(words, repoFiles);
6292
+ if (wrapper) {
6293
+ return coverageCommandEvaluation(
6294
+ repoPath,
6295
+ repoFiles,
6296
+ readRepoText(join(repoPath, wrapper), "utf8"),
6297
+ depth + 1
6298
+ );
6299
+ }
6300
+ const executable = commandExecutable(words);
6301
+ const potentialFlagState = flagState(
6302
+ words.slice(1),
6303
+ ["--experimental-test-coverage", "--collectcoverage", "--collect-coverage", "--coverage", "--cov"],
6304
+ [
6305
+ "--no-experimental-test-coverage",
6306
+ "--no-collectcoverage",
6307
+ "--no-collect-coverage",
6308
+ "--no-coverage",
6309
+ "--no-cov"
6310
+ ]
6311
+ );
6312
+ if (hasCoverageSyntax && potentialFlagState !== "none" && executable && /(?:test|spec|runner)/i.test(executable) && !/(?:docs?|map|report|upload|codecov|coveralls)/i.test(executable)) {
6313
+ return { recognized: true, state: "unresolved" };
6314
+ }
6315
+ return { recognized: false, state: "none" };
6316
+ });
6317
+ return {
6318
+ recognized: evaluations.some((evaluation) => evaluation.recognized),
6319
+ state: combineCoverageCommandStates(
6320
+ evaluations.map((evaluation) => evaluation.state)
6321
+ )
6322
+ };
6323
+ }
6324
+ function coverageDetectorDecision(repoPath, repoFiles, input) {
6325
+ const evaluation = coverageCommandEvaluation(repoPath, repoFiles, input.command);
6326
+ return {
6327
+ path: input.path,
6328
+ source: input.source,
6329
+ locator: input.locator,
6330
+ evidence: input.command,
6331
+ detectorRecognized: evaluation.recognized,
6332
+ detectorState: evaluation.state
6333
+ };
6334
+ }
6335
+ function combineCoverageCommandStates(states) {
6336
+ if (states.includes("collects")) return "collects";
6337
+ return states.includes("unresolved") ? "unresolved" : "none";
6338
+ }
6339
+ function shellWords(segment) {
6340
+ const words = [];
6341
+ let offset = 0;
6342
+ while (offset < segment.length) {
6343
+ const scanned = scanShellWord(segment, offset);
6344
+ if (!scanned) break;
6345
+ words.push(scanned.word);
6346
+ if (scanned.end <= offset) break;
6347
+ offset = scanned.end;
6348
+ }
6349
+ return words;
6350
+ }
6351
+ function commandExecutable(words) {
6352
+ const normalized = unwrapPackageManagerExecutable(words);
6353
+ const executable = normalized[0];
6354
+ return executable ? basename(executable).replace(/\.cmd$/i, "").toLowerCase() : void 0;
6355
+ }
6356
+ function unwrapPackageManagerExecutable(words) {
6357
+ if (words.length === 0) return [];
6358
+ const first = basename(words[0] ?? "").toLowerCase();
6359
+ if (first === "pnpm" || first === "yarn") {
6360
+ const second = words[1]?.toLowerCase();
6361
+ return words.slice(second === "exec" || second === "dlx" ? 2 : 1);
6362
+ }
6363
+ if (first === "npx") return words.slice(1).filter((word) => !/^--(?:yes|no-install)$/u.test(word));
6364
+ if (first === "npm" && words[1]?.toLowerCase() === "exec") {
6365
+ let offset = 2;
6366
+ while (/^--(?:yes|no-install)$/u.test(words[offset] ?? "")) offset += 1;
6367
+ if (words[offset] === "--") offset += 1;
6368
+ return words.slice(offset);
6369
+ }
6370
+ return [...words];
6371
+ }
6372
+ function coverageFlagCommandState(words) {
6373
+ const command = unwrapPackageManagerExecutable(words);
6374
+ const executable = basename(command[0] ?? "").replace(/\.cmd$/i, "").toLowerCase();
6375
+ const args = command.slice(1);
6376
+ if (!executable) return "none";
6377
+ if (executable === "node") {
6378
+ if (!args.includes("--test")) return "none";
6379
+ return flagState(args, ["--experimental-test-coverage"], ["--no-experimental-test-coverage"]);
6380
+ }
6381
+ if (executable === "pytest" || executable === "py.test") {
6382
+ return flagState(args, ["--cov"], ["--no-cov"]);
6383
+ }
6384
+ if (executable === "bun" || executable === "deno") {
6385
+ if (args[0]?.toLowerCase() !== "test") return "none";
6386
+ return flagState(args.slice(1), ["--coverage"], ["--no-coverage"]);
6387
+ }
6388
+ if (executable === "jest") {
6389
+ return flagState(
6390
+ args,
6391
+ ["--coverage", "--collectcoverage", "--collect-coverage"],
6392
+ ["--no-coverage", "--no-collectcoverage", "--no-collect-coverage"]
6393
+ );
6394
+ }
6395
+ if (executable === "vitest" || executable === "bnt") {
6396
+ return flagState(args, ["--coverage"], ["--no-coverage"]);
6397
+ }
6398
+ return "none";
6399
+ }
6400
+ function isRecognizedCoverageFlagRunner(words) {
6401
+ const command = unwrapPackageManagerExecutable(words);
6402
+ const executable = basename(command[0] ?? "").replace(/\.cmd$/i, "").toLowerCase();
6403
+ if (["bnt", "vitest", "jest", "pytest", "py.test"].includes(executable)) return true;
6404
+ if (executable === "node") return command.slice(1).includes("--test");
6405
+ return (executable === "bun" || executable === "deno") && command[1]?.toLowerCase() === "test";
6406
+ }
6407
+ function flagState(args, enablingNames, disablingNames) {
6408
+ let enabled = false;
6409
+ let disabled = false;
6410
+ let unresolved = false;
6411
+ const enabling = new Set(enablingNames.map((name) => name.toLowerCase()));
6412
+ const disabling = new Set(disablingNames.map((name) => name.toLowerCase()));
6413
+ for (let index = 0; index < args.length; index += 1) {
6414
+ const raw = args[index] ?? "";
6415
+ const lower = raw.toLowerCase();
6416
+ if (disabling.has(lower)) {
6417
+ disabled = true;
6418
+ continue;
6419
+ }
6420
+ const [name, assigned] = lower.split("=", 2);
6421
+ if (!name || !enabling.has(name)) continue;
6422
+ if (assigned !== void 0) {
6423
+ if (assigned === "false" || assigned === "0" || assigned === "off") disabled = true;
6424
+ else if (/\$|\{|\}/u.test(assigned)) unresolved = true;
6425
+ else enabled = true;
6426
+ continue;
6427
+ }
6428
+ const next = args[index + 1]?.toLowerCase();
6429
+ if (next === "false" || next === "0" || next === "off") {
6430
+ disabled = true;
6431
+ index += 1;
6432
+ } else if (next && /\$|\{|\}/u.test(next)) {
6433
+ unresolved = true;
6434
+ index += 1;
6435
+ } else {
6436
+ enabled = true;
6437
+ }
6438
+ }
6439
+ if (unresolved || enabled && disabled) return "unresolved";
6440
+ return enabled && !disabled ? "collects" : "none";
6441
+ }
6442
+ function containsPotentialCoverageFlag(command) {
6443
+ return /(?:^|\s)--(?:experimental-test-coverage|collect-?coverage|no-coverage|coverage|no-cov|cov)(?:[=\s]|$)/iu.test(
6444
+ command
6445
+ );
6446
+ }
6447
+ function reachableShellWrapper(words, repoFiles) {
6448
+ const executable = basename(words[0] ?? "").toLowerCase();
6449
+ const candidate = /^(?:ba|da|k|z)?sh$/u.test(executable) ? words[1] : words[0];
6450
+ if (!candidate || !/(?:^|\/)\.?[^\s]*\.sh$/i.test(candidate)) return void 0;
6451
+ const normalized = candidate.replace(/^\.\//u, "");
6452
+ if (normalized.startsWith("/") || normalized.split("/").includes("..")) return void 0;
6453
+ return repoFiles.has(normalized) ? normalized : void 0;
6454
+ }
5221
6455
  function commandInvokesCoverageTool(command) {
5222
6456
  return normalizedCommandInvokesCoverageTool(normalizeCoverageCommandPrefixes(command), 0);
5223
6457
  }
@@ -6475,12 +7709,11 @@ function hasEnvPathInGitHistory(repoPath, scanLimitations) {
6475
7709
 
6476
7710
  // src/witan/public-scan.ts
6477
7711
  init_esm_shims();
6478
- import { resolve as resolve2 } from "path";
6479
7712
 
6480
7713
  // src/witan/ingest.ts
6481
7714
  init_esm_shims();
6482
- import { readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
6483
- import { join as join2 } from "path";
7715
+ import { lstatSync as lstatSync3, opendirSync, readFileSync as readFileSync4, realpathSync as realpathSync3 } from "fs";
7716
+ import { join as join2, resolve as resolve3 } from "path";
6484
7717
 
6485
7718
  // src/witan/finding-limits.ts
6486
7719
  init_esm_shims();
@@ -6504,49 +7737,118 @@ function clampFinding(finding) {
6504
7737
  // src/witan/generic-adapter.ts
6505
7738
  init_esm_shims();
6506
7739
  import { readFileSync } from "fs";
6507
- var VALID_DIMENSIONS = /* @__PURE__ */ new Set([
6508
- "A1",
6509
- "A2",
6510
- "A3",
6511
- "A4",
6512
- "A5",
6513
- "B1",
6514
- "B2",
6515
- "B3",
6516
- "B4",
6517
- "B5",
6518
- "B6"
6519
- ]);
6520
- var VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning", "info"]);
7740
+
7741
+ // src/witan/ingest-files.ts
7742
+ init_esm_shims();
7743
+ import { lstatSync as lstatSync2, realpathSync as realpathSync2 } from "fs";
7744
+ import { dirname as dirname2, isAbsolute, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
7745
+ var MAX_INGEST_DOCUMENT_BYTES = 8 * 1024 * 1024;
7746
+ function isContainedPath(candidate, directory) {
7747
+ const relativePath = relative2(directory, candidate);
7748
+ return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep2}`) && !isAbsolute(relativePath);
7749
+ }
7750
+ function isMissingIngestPath(error) {
7751
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
7752
+ }
7753
+ function resolveIngestFilePath(filePath, inputDirectory = dirname2(filePath)) {
7754
+ const lexicalDirectory = resolve2(inputDirectory);
7755
+ const lexicalCandidate = resolve2(filePath);
7756
+ const directoryStat = lstatSync2(lexicalDirectory);
7757
+ if (directoryStat.isSymbolicLink()) {
7758
+ throw new Error(`Cejel: refusing symlinked ingest directory: ${inputDirectory}`);
7759
+ }
7760
+ if (!directoryStat.isDirectory()) {
7761
+ throw new Error(`Cejel: refusing non-directory ingest boundary: ${inputDirectory}`);
7762
+ }
7763
+ const candidateStat = lstatSync2(lexicalCandidate);
7764
+ if (candidateStat.isSymbolicLink()) {
7765
+ throw new Error(`Cejel: refusing symlinked ingest file: ${filePath}`);
7766
+ }
7767
+ if (!candidateStat.isFile()) {
7768
+ throw new Error(`Cejel: refusing non-regular ingest file: ${filePath}`);
7769
+ }
7770
+ if (candidateStat.size > MAX_INGEST_DOCUMENT_BYTES) {
7771
+ throw new Error(
7772
+ `Cejel: ingest file exceeds the ${MAX_INGEST_DOCUMENT_BYTES.toLocaleString("en-US")} byte budget: ${filePath}`
7773
+ );
7774
+ }
7775
+ const resolvedDirectory = realpathSync2(lexicalDirectory);
7776
+ const resolvedCandidate = realpathSync2(lexicalCandidate);
7777
+ if (!isContainedPath(resolvedCandidate, resolvedDirectory)) {
7778
+ throw new Error(`Cejel: refusing ingest file outside its resolved input directory: ${filePath}`);
7779
+ }
7780
+ return resolvedCandidate;
7781
+ }
7782
+ function isResolvedIngestPathContained(candidate, directory) {
7783
+ return isContainedPath(candidate, directory);
7784
+ }
7785
+
7786
+ // src/witan/generic-adapter.ts
7787
+ var CEJEL_GENERIC_INGEST_CONTRACT_VERSION = "1.0";
7788
+ var CEJEL_GENERIC_INGEST_CONTRACT_MAJOR = 1;
7789
+ var GenericFindingV1Schema = external_exports.object({
7790
+ ruleId: external_exports.string().min(1),
7791
+ severity: external_exports.enum(["critical", "warning", "info"]),
7792
+ message: external_exports.string().min(1),
7793
+ location: external_exports.string().min(1).optional()
7794
+ }).passthrough();
7795
+ var GenericSignalV1Schema = external_exports.object({
7796
+ dimension: external_exports.enum(["A1", "A2", "A3", "A4", "A5", "B1", "B2", "B3", "B4", "B5", "B6"]),
7797
+ weight: external_exports.number().min(0).max(1).default(0.5),
7798
+ findings: external_exports.array(GenericFindingV1Schema).min(1)
7799
+ }).passthrough();
7800
+ var GenericSignalDocumentV1Schema = external_exports.object({
7801
+ version: external_exports.string().regex(/^1\.\d+$/),
7802
+ tool: external_exports.string().trim().min(1),
7803
+ signals: external_exports.array(GenericSignalV1Schema)
7804
+ }).passthrough();
6521
7805
  function isGenericSignalDocument(raw) {
6522
7806
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
6523
7807
  const doc = raw;
6524
7808
  return typeof doc.tool === "string" && doc.tool.length > 0 && Array.isArray(doc.signals);
6525
7809
  }
7810
+ function assertSupportedContractVersion(version) {
7811
+ if (typeof version !== "string") {
7812
+ throw new Error(
7813
+ `Cejel: generic ingest contract version is required; expected "version": "${CEJEL_GENERIC_INGEST_CONTRACT_VERSION}". Refusing to guess an unversioned contract.`
7814
+ );
7815
+ }
7816
+ const match = /^(\d+)\.(\d+)$/.exec(version);
7817
+ if (!match) {
7818
+ throw new Error(
7819
+ `Cejel: invalid generic ingest contract version "${version}"; expected <major>.<minor> (current ${CEJEL_GENERIC_INGEST_CONTRACT_VERSION}).`
7820
+ );
7821
+ }
7822
+ const major = Number(match[1]);
7823
+ if (major !== CEJEL_GENERIC_INGEST_CONTRACT_MAJOR) {
7824
+ throw new Error(
7825
+ `Cejel: unsupported generic ingest contract major version ${major} (declared "${version}"); this Cejel build supports major ${CEJEL_GENERIC_INGEST_CONTRACT_MAJOR}. Refusing to guess.`
7826
+ );
7827
+ }
7828
+ }
7829
+ function formatValidationIssues(error) {
7830
+ return error.issues.map((issue) => `${issue.path.length > 0 ? issue.path.join(".") : "<document>"}: ${issue.message}`).join("; ");
7831
+ }
6526
7832
  function parseGenericJson(raw) {
6527
7833
  if (!isGenericSignalDocument(raw)) return [];
6528
- const source = raw.tool ?? "unknown";
6529
- const signals = [];
6530
- for (const signal of raw.signals ?? []) {
6531
- const dimension = signal.dimension;
6532
- if (!dimension || !VALID_DIMENSIONS.has(dimension)) continue;
6533
- const weight = typeof signal.weight === "number" && Number.isFinite(signal.weight) ? Math.min(Math.max(signal.weight, 0), 1) : 0.5;
6534
- const findings = [];
6535
- for (const finding of signal.findings ?? []) {
6536
- const severity = finding.severity;
6537
- if (!severity || !VALID_SEVERITIES.has(severity)) continue;
6538
- const ruleId = finding.ruleId ?? "unknown";
6539
- findings.push({
6540
- ruleId,
6541
- severity,
6542
- message: finding.message ?? ruleId,
6543
- ...finding.location ? { location: finding.location } : {}
6544
- });
6545
- }
6546
- if (findings.length === 0) continue;
6547
- signals.push({ source, dimension, weight, findings });
7834
+ assertSupportedContractVersion(raw.version);
7835
+ const parsed = GenericSignalDocumentV1Schema.safeParse(raw);
7836
+ if (!parsed.success) {
7837
+ throw new Error(
7838
+ `Cejel: invalid generic ingest contract v${raw.version}: ${formatValidationIssues(parsed.error)}`
7839
+ );
6548
7840
  }
6549
- return signals;
7841
+ return parsed.data.signals.map((signal) => ({
7842
+ source: parsed.data.tool,
7843
+ dimension: signal.dimension,
7844
+ weight: signal.weight,
7845
+ findings: signal.findings.map((finding) => ({
7846
+ ruleId: finding.ruleId,
7847
+ severity: finding.severity,
7848
+ message: finding.message,
7849
+ ...finding.location ? { location: finding.location } : {}
7850
+ }))
7851
+ }));
6550
7852
  }
6551
7853
 
6552
7854
  // src/witan/sarif-adapter.ts
@@ -6808,30 +8110,35 @@ function rawToolName(raw) {
6808
8110
  }
6809
8111
  var MAX_INGEST_SOURCE_LENGTH = 120;
6810
8112
  function sanitizeIngestSource(source) {
6811
- const withoutControls = source.replace(/[\p{Cc}\p{Cf}]/gu, "").trim();
6812
- const safeSource = withoutControls.length > 0 ? withoutControls : "unknown";
6813
- const codePoints = Array.from(safeSource);
6814
- if (codePoints.length <= MAX_INGEST_SOURCE_LENGTH) return safeSource;
6815
- return `${codePoints.slice(0, MAX_INGEST_SOURCE_LENGTH - 3).join("")}...`;
8113
+ return sanitizePresentationLine(source, {
8114
+ fallback: "unknown",
8115
+ maxLength: MAX_INGEST_SOURCE_LENGTH
8116
+ });
6816
8117
  }
6817
8118
  function parseIngestFile(filePath, options = {}) {
8119
+ const resolvedFilePath = resolveIngestFilePath(filePath);
6818
8120
  let raw;
6819
8121
  try {
6820
- raw = JSON.parse(stripBom(readFileSync4(filePath, "utf8")));
8122
+ raw = JSON.parse(stripBom(readFileSync4(resolvedFilePath, "utf8")));
6821
8123
  } catch (error) {
6822
8124
  const message = error instanceof Error ? error.message : String(error);
6823
8125
  throw new Error(`Cejel: could not parse ingest file as JSON: ${filePath} (${message})`);
6824
8126
  }
8127
+ const rawCount = countRawEntries(raw);
8128
+ if (options.maxFindingCandidates !== void 0 && rawCount > options.maxFindingCandidates) {
8129
+ throw new Error(
8130
+ `Cejel: ${rawCount.toLocaleString("en-US")} ingest finding candidates exceed the ${options.maxFindingCandidates.toLocaleString("en-US")} remaining retained ingest finding budget; refusing before adapter materialization.`
8131
+ );
8132
+ }
6825
8133
  let signals;
6826
8134
  if (looksLikeSarif(raw)) signals = parseSarifJson(raw, options.extraSarifDimensionRules);
6827
8135
  else if (looksLikeScorecard(raw)) signals = parseScorecardJson(raw);
6828
8136
  else if (isGenericSignalDocument(raw)) signals = parseGenericJson(raw);
6829
8137
  if (!signals) {
6830
8138
  throw new Error(
6831
- `Cejel: unrecognized ingest file format: ${filePath} \u2014 expected SARIF (a "runs" array), OpenSSF Scorecard JSON (a "checks" array), or the generic Cejel external-signal shape (a "tool" string + "signals" array). See the cejel README "Aggregate your scanners".`
8139
+ `Cejel: unrecognized ingest file format: ${filePath} \u2014 expected SARIF (a "runs" array), OpenSSF Scorecard JSON (a "checks" array), or the versioned generic Cejel external-signal shape (a "version" string + "tool" string + "signals" array). See the cejel README "Aggregate your scanners".`
6832
8140
  );
6833
8141
  }
6834
- const rawCount = countRawEntries(raw);
6835
8142
  const mappedCount = signals.reduce((sum, signal) => sum + signal.findings.length, 0);
6836
8143
  if (rawCount > 0 && mappedCount === 0) {
6837
8144
  process.stderr.write(
@@ -6846,30 +8153,83 @@ function parseIngestFile(filePath, options = {}) {
6846
8153
  findings: signal.findings.map(clampFinding)
6847
8154
  }));
6848
8155
  }
6849
- function expandIngestPattern(pattern) {
8156
+ function expandIngestPattern(pattern, maxMatches = Number.POSITIVE_INFINITY) {
6850
8157
  if (!pattern.includes("*")) return [pattern];
6851
8158
  const lastSlash = pattern.lastIndexOf("/");
6852
8159
  const dir = lastSlash === -1 ? "." : pattern.slice(0, lastSlash);
6853
8160
  const filePattern = lastSlash === -1 ? pattern : pattern.slice(lastSlash + 1);
6854
8161
  const regexSource = filePattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
6855
8162
  const regex = new RegExp(`^${regexSource}$`);
6856
- let entries;
8163
+ const entries = [];
6857
8164
  try {
6858
- entries = readdirSync2(dir);
6859
- } catch {
8165
+ const directoryStat = lstatSync3(resolve3(dir));
8166
+ if (directoryStat.isSymbolicLink()) {
8167
+ throw new Error(`Cejel: refusing symlinked ingest directory: ${dir}`);
8168
+ }
8169
+ if (!directoryStat.isDirectory()) return [];
8170
+ const directory = opendirSync(dir);
8171
+ try {
8172
+ for (; ; ) {
8173
+ const entry = directory.readSync();
8174
+ if (!entry) break;
8175
+ if (!regex.test(entry.name)) continue;
8176
+ entries.push(entry.name);
8177
+ if (entries.length >= maxMatches) break;
8178
+ }
8179
+ } finally {
8180
+ directory.closeSync();
8181
+ }
8182
+ } catch (error) {
8183
+ if (!isMissingIngestPath(error)) throw error;
6860
8184
  return [];
6861
8185
  }
6862
- return entries.filter((name) => regex.test(name)).sort().map((name) => join2(dir, name));
8186
+ return entries.sort().map((name) => {
8187
+ const candidate = join2(dir, name);
8188
+ resolveIngestFilePath(candidate, dir);
8189
+ return candidate;
8190
+ });
6863
8191
  }
6864
- function discoverIngestInputs(repoPath) {
8192
+ function discoverIngestInputs(repoPath, maxMatches = Number.POSITIVE_INFINITY) {
8193
+ const resolvedRoot = realpathSync3(repoPath);
8194
+ const cejelDir = join2(repoPath, ".cejel");
6865
8195
  const inputsDir = join2(repoPath, ".cejel", "inputs");
6866
- let entries;
8196
+ const entries = [];
6867
8197
  try {
6868
- entries = readdirSync2(inputsDir);
6869
- } catch {
8198
+ const cejelStat = lstatSync3(cejelDir);
8199
+ if (cejelStat.isSymbolicLink()) {
8200
+ throw new Error(`Cejel: refusing symlinked ingest path component: ${cejelDir}`);
8201
+ }
8202
+ if (!cejelStat.isDirectory()) {
8203
+ throw new Error(`Cejel: refusing non-directory ingest path component: ${cejelDir}`);
8204
+ }
8205
+ const inputsStat = lstatSync3(inputsDir);
8206
+ if (inputsStat.isSymbolicLink()) {
8207
+ throw new Error(`Cejel: refusing symlinked ingest path component: ${inputsDir}`);
8208
+ }
8209
+ if (!inputsStat.isDirectory()) {
8210
+ throw new Error(`Cejel: refusing non-directory ingest path component: ${inputsDir}`);
8211
+ }
8212
+ const resolvedInputs = realpathSync3(inputsDir);
8213
+ if (!isResolvedIngestPathContained(resolvedInputs, resolvedRoot)) {
8214
+ throw new Error(`Cejel: refusing auto-discovery directory outside the repository root.`);
8215
+ }
8216
+ const directory = opendirSync(inputsDir);
8217
+ try {
8218
+ for (; ; ) {
8219
+ const entry = directory.readSync();
8220
+ if (!entry) break;
8221
+ if (!/\.(sarif|json)$/i.test(entry.name)) continue;
8222
+ entries.push(entry.name);
8223
+ if (entries.length >= maxMatches) break;
8224
+ }
8225
+ } finally {
8226
+ directory.closeSync();
8227
+ }
8228
+ } catch (error) {
8229
+ if (!isMissingIngestPath(error)) throw error;
6870
8230
  return [];
6871
8231
  }
6872
- return entries.filter((name) => /\.(sarif|json)$/i.test(name)).sort().map((name) => join2(inputsDir, name));
8232
+ return entries.sort().map((name) => resolveIngestFilePath(join2(inputsDir, name), inputsDir));
6873
8233
  }
6874
8234
 
6875
8235
  // src/witan/scoring.ts
@@ -6937,7 +8297,7 @@ var WITAN_RUBRIC = [
6937
8297
 
6938
8298
  // src/witan/scoring.ts
6939
8299
  function usesV17DetectorClosure2(rubricVersion) {
6940
- return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
8300
+ return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
6941
8301
  }
6942
8302
  var MAX_SIGNAL_ADJUSTMENT = 0.8;
6943
8303
  function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
@@ -7096,7 +8456,7 @@ function statusForScore(score) {
7096
8456
  }
7097
8457
  function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubricVersion) {
7098
8458
  if (adjustment === 0) return nativeStatus;
7099
- if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22) {
8459
+ if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22 && rubricVersion !== WITAN_RUBRIC_VERSION_V23) {
7100
8460
  return statusForScore(roundScore(Math.max(0, nativeScore - adjustment)));
7101
8461
  }
7102
8462
  const statusFloor = {
@@ -7141,7 +8501,7 @@ function mergeSignalsByCriterion(signals) {
7141
8501
  return merged;
7142
8502
  }
7143
8503
  function usesMetricScoring(rubricVersion) {
7144
- return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_RUBRIC_VERSION_V7 || rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
8504
+ return rubricVersion === WITAN_RUBRIC_VERSION_V1 || rubricVersion === WITAN_RUBRIC_VERSION_V2 || rubricVersion === WITAN_RUBRIC_VERSION_V3 || rubricVersion === WITAN_RUBRIC_VERSION_V4 || rubricVersion === WITAN_RUBRIC_VERSION_V5 || rubricVersion === WITAN_RUBRIC_VERSION_V6 || rubricVersion === WITAN_RUBRIC_VERSION_V7 || rubricVersion === WITAN_RUBRIC_VERSION_V8 || rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 || rubricVersion === WITAN_RUBRIC_VERSION_V11 || rubricVersion === WITAN_RUBRIC_VERSION_V12 || rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23 || rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0;
7145
8505
  }
7146
8506
  function unmeasuredStatus(rubricVersion) {
7147
8507
  return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
@@ -7281,7 +8641,11 @@ function scoreMetrics(metrics) {
7281
8641
  function capScoreForFindings(score, findings, rubricVersion, criterionId) {
7282
8642
  if (findings.some((finding) => finding.severity === "critical")) {
7283
8643
  const criticalFindings = findings.filter(({ severity }) => severity === "critical");
7284
- const authenticatedA1Absence = usesV17DetectorClosure2(rubricVersion) && criterionId === "A1" && criticalFindings.length === 1 && criticalFindings[0]?.summary === WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY;
8644
+ const authenticatedA1Absence = usesV17DetectorClosure2(rubricVersion) && criterionId === "A1" && criticalFindings.length === 1 && [
8645
+ WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY,
8646
+ WITAN_DIRECTORY_WALK_AUTHENTICATED_A1_ABSENCE_SUMMARY,
8647
+ WITAN_LEGACY_AUTHENTICATED_A1_ABSENCE_SUMMARY
8648
+ ].includes(criticalFindings[0]?.summary ?? "");
7285
8649
  return authenticatedA1Absence ? Math.min(score, 1.4) : 1.4;
7286
8650
  }
7287
8651
  if (findings.some((finding) => finding.severity === "warning")) return Math.min(score, 2.4);
@@ -7517,7 +8881,7 @@ function calibratedCriterionStateV11(criterionId, metrics) {
7517
8881
  return void 0;
7518
8882
  }
7519
8883
  function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence, rubricVersion) {
7520
- if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22 && findings.length > 0) {
8884
+ if (rubricVersion !== WITAN_RUBRIC_VERSION_V8 && rubricVersion !== WITAN_RUBRIC_VERSION_V9 && rubricVersion !== WITAN_RUBRIC_VERSION_V10 && rubricVersion !== WITAN_RUBRIC_VERSION_V11 && rubricVersion !== WITAN_RUBRIC_VERSION_V12 && rubricVersion !== WITAN_RUBRIC_VERSION_V13 && rubricVersion !== WITAN_RUBRIC_VERSION_V14 && rubricVersion !== WITAN_RUBRIC_VERSION_V15 && rubricVersion !== WITAN_RUBRIC_VERSION_V16 && rubricVersion !== WITAN_RUBRIC_VERSION_V17 && rubricVersion !== WITAN_RUBRIC_VERSION_V18 && rubricVersion !== WITAN_RUBRIC_VERSION_V19 && rubricVersion !== WITAN_RUBRIC_VERSION_V20 && rubricVersion !== WITAN_RUBRIC_VERSION_V21 && rubricVersion !== WITAN_RUBRIC_VERSION_V22 && rubricVersion !== WITAN_RUBRIC_VERSION_V23 && findings.length > 0) {
7521
8885
  return [...findings];
7522
8886
  }
7523
8887
  if (status !== "critical" && status !== "warning") return [...findings];
@@ -7553,6 +8917,8 @@ function roundScore(score) {
7553
8917
  }
7554
8918
 
7555
8919
  // src/witan/public-scan.ts
8920
+ var MAX_INGEST_DOCUMENTS = 128;
8921
+ var MAX_RETAINED_INGEST_FINDINGS = 1e4;
7556
8922
  function scoreRepoWithPublicCejel(options) {
7557
8923
  if (options.rubricVersion !== void 0) {
7558
8924
  assertSelectableRubricVersion(options.rubricVersion);
@@ -7580,36 +8946,58 @@ function scoreRepoWithPublicCejel(options) {
7580
8946
  }
7581
8947
  function resolvePublicIngestSignals(options) {
7582
8948
  const seen = /* @__PURE__ */ new Set();
7583
- const explicitFiles = [];
8949
+ const documents = [];
8950
+ const addDocument = (file, provenance) => {
8951
+ const resolved = resolveIngestFilePath(file);
8952
+ if (seen.has(resolved)) return;
8953
+ seen.add(resolved);
8954
+ documents.push({ file: resolved, provenance });
8955
+ if (documents.length > MAX_INGEST_DOCUMENTS) {
8956
+ throw new Error(
8957
+ `Cejel: ${documents.length} unique ingest documents exceed the ${MAX_INGEST_DOCUMENTS} ingest document budget; refusing to produce a partial certificate.`
8958
+ );
8959
+ }
8960
+ };
7584
8961
  for (const pattern of options.ingestPatterns ?? []) {
7585
- const matches = expandIngestPattern(pattern);
8962
+ const matches = expandIngestPattern(pattern, MAX_INGEST_DOCUMENTS + 1);
7586
8963
  if (matches.length === 0 && options.warnOnEmptyIngestMatch) {
7587
8964
  process.stderr.write(`Cejel: --ingest pattern matched no files: ${pattern}
7588
8965
  `);
7589
8966
  }
7590
8967
  for (const match of matches) {
7591
- const resolved = resolve2(match);
7592
- if (seen.has(resolved)) continue;
7593
- seen.add(resolved);
7594
- explicitFiles.push(match);
8968
+ addDocument(match, "operator_supplied");
7595
8969
  }
7596
8970
  }
7597
- const signals = explicitFiles.flatMap(
7598
- (file) => parseIngestFile(file, { provenance: "operator_supplied" })
7599
- );
7600
- if (!options.autoDiscoverIngest) return signals;
7601
- for (const discovered of discoverIngestInputs(options.repoPath)) {
7602
- const resolved = resolve2(discovered);
7603
- if (seen.has(resolved)) continue;
7604
- seen.add(resolved);
7605
- signals.push(...parseIngestFile(discovered, { provenance: "auto_discovered" }));
8971
+ if (options.autoDiscoverIngest) {
8972
+ for (const discovered of discoverIngestInputs(
8973
+ options.repoPath,
8974
+ MAX_INGEST_DOCUMENTS + 1
8975
+ )) {
8976
+ addDocument(discovered, "auto_discovered");
8977
+ }
8978
+ }
8979
+ const signals = [];
8980
+ let retainedFindings = 0;
8981
+ for (const document of documents) {
8982
+ const parsed = parseIngestFile(document.file, {
8983
+ maxFindingCandidates: MAX_RETAINED_INGEST_FINDINGS - retainedFindings,
8984
+ provenance: document.provenance
8985
+ });
8986
+ const documentFindings = parsed.reduce((count, signal) => count + signal.findings.length, 0);
8987
+ retainedFindings += documentFindings;
8988
+ if (retainedFindings > MAX_RETAINED_INGEST_FINDINGS) {
8989
+ throw new Error(
8990
+ `Cejel: ${retainedFindings.toLocaleString("en-US")} retained findings exceed the ${MAX_RETAINED_INGEST_FINDINGS.toLocaleString("en-US")} retained ingest finding budget; refusing to truncate evidence.`
8991
+ );
8992
+ }
8993
+ signals.push(...parsed);
7606
8994
  }
7607
8995
  return signals;
7608
8996
  }
7609
8997
 
7610
8998
  // src/product-identity.ts
7611
8999
  init_esm_shims();
7612
- import { existsSync, readFileSync as readFileSync5 } from "fs";
9000
+ import { existsSync, lstatSync as lstatSync4, readFileSync as readFileSync5 } from "fs";
7613
9001
  import { basename as basename2, join as join3 } from "path";
7614
9002
  var SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/;
7615
9003
  var FALLBACK_SLUG = "repo";
@@ -7617,7 +9005,10 @@ var FALLBACK_DISPLAY_NAME = "Repository";
7617
9005
  function deriveProductIdentity(repoPath, productName) {
7618
9006
  const packageJsonPath = join3(repoPath, "package.json");
7619
9007
  const raw = productName ?? readPackageName(packageJsonPath) ?? basename2(repoPath);
7620
- const displayName = raw.trim().length > 0 ? raw.trim() : FALLBACK_DISPLAY_NAME;
9008
+ const displayName = sanitizePresentationLine(raw, {
9009
+ fallback: FALLBACK_DISPLAY_NAME,
9010
+ maxLength: 120
9011
+ });
7621
9012
  return {
7622
9013
  productSlug: slugify(raw),
7623
9014
  productDisplayName: displayName
@@ -7626,6 +9017,8 @@ function deriveProductIdentity(repoPath, productName) {
7626
9017
  function readPackageName(packageJsonPath) {
7627
9018
  if (!existsSync(packageJsonPath)) return void 0;
7628
9019
  try {
9020
+ const stat = lstatSync4(packageJsonPath);
9021
+ if (!stat.isFile() || stat.size > MAX_REPOSITORY_CONTENT_BYTES) return void 0;
7629
9022
  const parsed = JSON.parse(readFileSync5(packageJsonPath, "utf8"));
7630
9023
  if (typeof parsed === "object" && parsed !== null && "name" in parsed) {
7631
9024
  const name = parsed.name;
@@ -7808,6 +9201,7 @@ export {
7808
9201
  CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE,
7809
9202
  CERTIFICATE_GLOSSARY,
7810
9203
  formatCertificateMetricLabel,
9204
+ formatCertificateMetricAppliedWeightPercent,
7811
9205
  formatCertificateMetricValue,
7812
9206
  buildRelyingPartySummary,
7813
9207
  WITAN_RUBRIC_VERSION_V22,