@cejel/cejel 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,10 @@ certificate + badge over all of them. See "Aggregate your scanners" below.
18
18
 
19
19
  > Dogfooded in production. Cejel is run continuously on Barg Labs' own multi-product
20
20
  > monorepo — the ten-product studio it was built inside — which it currently scores
21
- > 3.5/4.0 ("Verified"). We score ourselves before asking you to score yourself.
21
+ > 3.3/4.0 on its rubric-native certificate. That transparency snapshot opts into prospective
22
+ > v18; the public CLI default remains calibrated v17. On the comparative board, our code sits
23
+ > outside the ranked population and receives no verdict band. We score ourselves before
24
+ > asking you to score yourself.
22
25
 
23
26
  ## Install
24
27
 
@@ -46,6 +49,29 @@ chmod +x cejel
46
49
  ./cejel .
47
50
  ```
48
51
 
52
+ **Windows x86_64 (PowerShell).**
53
+
54
+ ```powershell
55
+ $asset = "cejel-Windows-x86_64.exe"
56
+ Invoke-WebRequest "https://github.com/BargLabs/cejel/releases/latest/download/$asset" -OutFile $asset
57
+ Invoke-WebRequest "https://github.com/BargLabs/cejel/releases/latest/download/SHA256SUMS" -OutFile SHA256SUMS
58
+ $expected = ((Select-String -Path SHA256SUMS -Pattern " $([regex]::Escape($asset))$").Line -split "\s+")[0]
59
+ $actual = (Get-FileHash -Algorithm SHA256 $asset).Hash.ToLowerInvariant()
60
+ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
61
+ throw "SHA-256 verification failed for $asset"
62
+ }
63
+ .\cejel-Windows-x86_64.exe .
64
+ ```
65
+
66
+ > **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
67
+ > 0.2.2 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
68
+ > signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
69
+ > it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
70
+ > GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM and an
71
+ > own-platform verification receipt covering `--version`, `--help`, a real scan,
72
+ > source/binary parity, and a network-denied scan. If your policy requires Authenticode,
73
+ > use the npm package or the OCI image until a human-approved signing path is available.
74
+
49
75
  **Don't take the offline claim on trust — check it.** Turn your network off, then run the
50
76
  binary. It will score your repository and write you a certificate anyway. That is the whole
51
77
  product, and you can falsify it in ten seconds:
@@ -58,7 +84,17 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
58
84
  **npm.**
59
85
 
60
86
  ```bash
61
- npx @cejel/cejel .
87
+ npx @cejel/cejel@latest .
88
+ ```
89
+
90
+ > **Distribution note:** Cejel `0.2.2` is the coordinated release version for npm,
91
+ > standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry.
92
+
93
+ `npx` can reuse a stale cached package. Force the current npm release with the `@latest`
94
+ specifier above, and check the version that will run before comparing certificates:
95
+
96
+ ```bash
97
+ npx @cejel/cejel@latest --version
62
98
  ```
63
99
 
64
100
  If a root `package.json` still carries a template name, override only the certificate display
@@ -86,29 +122,30 @@ pnpm install && pnpm build
86
122
  node dist/index.js .
87
123
  ```
88
124
 
89
- Released binaries: `cejel-Darwin-arm64`, `cejel-Darwin-x86_64`, `cejel-Linux-aarch64`, and
90
- `cejel-Linux-x86_64`. The release also carries `SHA256SUMS`; each binary is executed against
91
- the source build and with networking denied before attachment. Releases from v0.1.6 also
92
- carry a Sigstore bundle containing GitHub's signed build-provenance attestation for all four
93
- binaries. Verify a downloaded binary with:
125
+ Released binaries: `cejel-Darwin-arm64`, `cejel-Darwin-x86_64`, `cejel-Linux-aarch64`,
126
+ `cejel-Linux-x86_64`, and `cejel-Windows-x86_64.exe`. The release also carries
127
+ `SHA256SUMS`, a per-binary SPDX SBOM, and an own-platform verification receipt. Each binary
128
+ is executed against the source build and with networking denied before attachment. Releases
129
+ from v0.1.6 also carry a Sigstore bundle containing GitHub's signed build-provenance
130
+ attestation for the release set. Verify a downloaded binary with:
94
131
 
95
132
  ```bash
96
- gh attestation verify ./cejel-Darwin-arm64 -R BargLabs/cejel
133
+ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
97
134
  ```
98
135
 
99
136
  This is cryptographically signed provenance. It is distinct from Apple Developer ID or
100
137
  Microsoft Authenticode code-signing.
101
138
 
102
- **Docker / OCI.** The same MCP server is published as a non-root, multi-platform image:
139
+ **Docker / OCI.** The current container release is `0.2.2`:
103
140
 
104
141
  ```bash
105
- docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.2.0
142
+ docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.2.2
106
143
  ```
107
144
 
108
145
  The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
109
146
 
110
147
  ```bash
111
- docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.2.0 .
148
+ docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.2.2 .
112
149
  ```
113
150
 
114
151
  The OCI image carries an SBOM, maximum-mode build provenance, and a signed registry
@@ -356,6 +393,46 @@ The server exposes one tool and two resources:
356
393
  Like the CLI, scoring over MCP is fully offline: no network calls, no telemetry, no signup,
357
394
  and the server writes no files.
358
395
 
396
+ ## Install on OpenClaw
397
+
398
+ OpenClaw stores outbound MCP servers under `mcp.servers`. Add Cejel with the npm package's
399
+ shipped `cejel-mcp` bin:
400
+
401
+ ```json
402
+ {
403
+ "mcp": {
404
+ "servers": {
405
+ "cejel": {
406
+ "command": "npx",
407
+ "args": ["-y", "--package=@cejel/cejel", "cejel-mcp"]
408
+ }
409
+ }
410
+ }
411
+ }
412
+ ```
413
+
414
+ The equivalent OpenClaw command is:
415
+
416
+ ```bash
417
+ openclaw mcp add cejel --command npx --arg -y --arg --package=@cejel/cejel --arg cejel-mcp
418
+ openclaw mcp doctor cejel --probe
419
+ ```
420
+
421
+ The OCI image is an alternative when Docker is the preferred execution boundary. Replace
422
+ the host path with the repository OpenClaw should allow Cejel to read:
423
+
424
+ ```bash
425
+ openclaw mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.2.2"]}'
426
+ openclaw mcp doctor cejel-oci --probe
427
+ ```
428
+
429
+ Cejel is active in the Official MCP Registry as `io.github.BargLabs/cejel`. The explicit
430
+ OpenClaw configuration above works independently of catalog indexing.
431
+
432
+ Cejel scans code you point it at. This free adoption surface does **not** watch, intercept,
433
+ or govern an agent's runtime actions. Runtime-action governance is a separate boundary for
434
+ the future paid Agent Pack.
435
+
359
436
  ## What "offline" means here
360
437
 
361
438
  Scoring a repo — `cejel scan .` itself, and the Action's scoring step — makes zero network
@@ -4368,7 +4368,7 @@ var WitanAttestationStatementSchema = external_exports.object({
4368
4368
  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.";
4369
4369
  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.";
4370
4370
  function isWitanNoMeasurementAbstention(report) {
4371
- return report.verdict === "insufficient_source" && report.insufficientSourceReason === WITAN_NO_MEASUREMENT_REASON && report.criteria.every(
4371
+ return report.verdict === "insufficient_source" && report.insufficientSourceReason.startsWith(WITAN_NO_MEASUREMENT_REASON) && report.criteria.every(
4372
4372
  ({ status }) => status === "not_applicable" || status === "insufficient_data"
4373
4373
  );
4374
4374
  }
@@ -4544,7 +4544,7 @@ function formatMetricValue(value) {
4544
4544
  }
4545
4545
 
4546
4546
  // src/witan/html.ts
4547
- function renderWitanHtmlReport(report) {
4547
+ function renderWitanHtmlReport(report, options = {}) {
4548
4548
  const codeCriteria = criteriaByCategory(report, "code_trust");
4549
4549
  const processCriteria = criteriaByCategory(report, "process_trust");
4550
4550
  const verifiedEvidence = report.criteria.flatMap(
@@ -4555,6 +4555,7 @@ function renderWitanHtmlReport(report) {
4555
4555
  const externalSourceSummaries = summarizeExternalSources(report.consumedSignals ?? []);
4556
4556
  const externalFindings = collectExternalFindings(report.consumedSignals ?? []);
4557
4557
  const coverage = computeMeasuredCoverage(report);
4558
+ const gitHistoryUnavailable = !report.repo.headSha;
4558
4559
  return `<!doctype html>
4559
4560
  <html lang="en">
4560
4561
  <head>
@@ -4577,6 +4578,7 @@ function renderWitanHtmlReport(report) {
4577
4578
  <dl class="meta">
4578
4579
  <div><dt>Date</dt><dd>${escapeHtml(formatDate(report.generatedAt))}</dd></div>
4579
4580
  <div><dt>Run</dt><dd>${escapeHtml(renderRepo(report))}</dd></div>
4581
+ <div><dt>CLI</dt><dd>${escapeHtml(options.cliVersion ? `Cejel ${options.cliVersion}` : "Not recorded")}</dd></div>
4580
4582
  <div><dt>Rubric</dt><dd>${escapeHtml(report.rubricVersion)}</dd></div>
4581
4583
  ${contributingSources.length > 0 ? `<div><dt>Sources</dt><dd>Incorporates findings from: ${escapeHtml(contributingSources.join(", "))}<ul class="source-counts">${externalSourceSummaries.map((s) => `<li>${escapeHtml(formatExternalSourceLine(s))}</li>`).join("")}</ul></dd></div>` : ""}
4582
4584
  </dl>
@@ -4597,8 +4599,8 @@ function renderWitanHtmlReport(report) {
4597
4599
  </header>
4598
4600
 
4599
4601
  <section class="trust-grid" aria-label="Rubric criteria">
4600
- ${renderCriterionColumn("Code trust", codeCriteria)}
4601
- ${renderCriterionColumn("Process trust", processCriteria)}
4602
+ ${renderCriterionColumn("Code trust", codeCriteria, gitHistoryUnavailable)}
4603
+ ${renderCriterionColumn("Process trust", processCriteria, gitHistoryUnavailable)}
4602
4604
  </section>
4603
4605
 
4604
4606
  <section class="evidence-grid" aria-label="Evidence and gaps">
@@ -4641,7 +4643,7 @@ function renderExternalFindingItems(findings) {
4641
4643
  function criteriaByCategory(report, category) {
4642
4644
  return report.criteria.filter((criterion) => criterion.category === category);
4643
4645
  }
4644
- function renderCriterionColumn(title, criteria) {
4646
+ function renderCriterionColumn(title, criteria, gitHistoryUnavailable) {
4645
4647
  const applicable = criteria.filter((criterion) => criterion.status !== "not_applicable");
4646
4648
  const notApplicable = criteria.filter((criterion) => criterion.status === "not_applicable");
4647
4649
  const notApplicableGroup = notApplicable.length > 0 ? `
@@ -4649,7 +4651,7 @@ function renderCriterionColumn(title, criteria) {
4649
4651
  return `<section class="criteria-column">
4650
4652
  <h2>${escapeHtml(title)}</h2>
4651
4653
  <div class="criteria-list">
4652
- ${applicable.map(renderCriterionCard).join("")}
4654
+ ${applicable.map((criterion) => renderCriterionCard(criterion, gitHistoryUnavailable)).join("")}
4653
4655
  </div>${notApplicableGroup}
4654
4656
  </section>`;
4655
4657
  }
@@ -4665,12 +4667,14 @@ function renderNotApplicableItem(criterion) {
4665
4667
  const reason = criterion.notes ?? "Not applicable to this repository.";
4666
4668
  return `<li><span class="na-id">${escapeHtml(criterion.id)}</span> ${escapeHtml(criterion.title)} \u2014 <span class="na-reason">${escapeHtml(reason)}</span></li>`;
4667
4669
  }
4668
- function renderCriterionCard(criterion) {
4670
+ function renderCriterionCard(criterion, gitHistoryUnavailable) {
4669
4671
  const evidence = [
4670
4672
  ...criterion.evidence.map(renderEvidencePointer),
4671
4673
  ...criterion.findings.map((finding) => renderFindingEvidence(criterion, finding))
4672
4674
  ];
4673
4675
  const metrics = criterion.metrics.map(renderMetric);
4676
+ const statusReconciliation = renderStatusReconciliation(criterion);
4677
+ const historyWarning = gitHistoryUnavailable && criterion.metrics.some((metric2) => metric2.name === "pr_merge_ratio") ? `<p class="scan-warning"><strong>Scan limitation:</strong> Git history was unavailable (for example, this may be a source tarball), so the recent PR merge ratio is not a measured repository outcome and its displayed zero may undercount ${escapeHtml(criterion.id)}. Re-scan a full Git clone for this signal.</p>` : "";
4674
4678
  return `<article class="criterion">
4675
4679
  <div class="criterion-top">
4676
4680
  <div>
@@ -4680,6 +4684,8 @@ function renderCriterionCard(criterion) {
4680
4684
  ${renderStatusChip(criterion.status)}
4681
4685
  </div>
4682
4686
  <div class="criterion-score">${criterion.status === "not_applicable" ? "N/A" : criterion.status === "insufficient_data" ? "No data" : formatScore(criterion.score)}</div>
4687
+ ${statusReconciliation}
4688
+ ${historyWarning}
4683
4689
  <ul class="criterion-metrics">
4684
4690
  ${metrics.length > 0 ? metrics.map((item) => `<li>${item}</li>`).join("") : "<li>No measured depth metrics supplied.</li>"}
4685
4691
  </ul>
@@ -4688,6 +4694,20 @@ function renderCriterionCard(criterion) {
4688
4694
  </ul>
4689
4695
  </article>`;
4690
4696
  }
4697
+ function renderStatusReconciliation(criterion) {
4698
+ if (criterion.status === "not_applicable" || criterion.status === "insufficient_data" || criterion.status === "unverified") {
4699
+ return "";
4700
+ }
4701
+ const numericBand = scoreBandForPresentation(criterion.score);
4702
+ if (numericBand === criterion.status) return "";
4703
+ return `<p class="status-explanation"><strong>Why the labels differ:</strong> the ${escapeHtml(criterion.status)} dimension band is calibrated from criterion evidence thresholds and findings, independently of the ${formatScore(criterion.score)}/4.0 weighted score (which falls in the ${numericBand} numeric band).</p>`;
4704
+ }
4705
+ function scoreBandForPresentation(score) {
4706
+ if (score >= 3.5) return "verified";
4707
+ if (score >= 2.5) return "info";
4708
+ if (score >= 1.5) return "warning";
4709
+ return "critical";
4710
+ }
4691
4711
  function renderMetric(metric2) {
4692
4712
  const unit = metric2.unit ? ` ${escapeHtml(metric2.unit)}` : "";
4693
4713
  if (metric2.kind === "saturating_count" && metric2.max !== void 0 && metric2.value > metric2.max) {
@@ -4860,6 +4880,12 @@ h2 { font-family: var(--serif); font-size: 30px; font-weight: 400; letter-spacin
4860
4880
  .criterion-id { color: var(--periwinkle); font-family: var(--mono); font-size: 12px; margin-bottom: 4px; }
4861
4881
  h3 { font-size: 15px; line-height: 1.35; font-weight: 600; margin-bottom: 0; }
4862
4882
  .criterion-score { margin-top: 12px; color: var(--text); font-family: var(--mono); font-size: 24px; }
4883
+ .status-explanation, .scan-warning {
4884
+ margin: 10px 0 0; border-left: 3px solid var(--periwinkle); padding: 8px 10px;
4885
+ background: var(--periwinkle-weak); color: var(--muted); font-size: 12px; line-height: 1.5;
4886
+ }
4887
+ .scan-warning { border-left-color: var(--warn); background: var(--warn-weak); }
4888
+ .status-explanation strong, .scan-warning strong { color: var(--text); }
4863
4889
  .criterion-metrics { display: grid; gap: 7px; margin-top: 10px; padding: 0; list-style: none; font-size: 12px; }
4864
4890
  .criterion-metrics li { display: flex; justify-content: space-between; gap: 12px; border-bottom: 1px solid rgba(238, 244, 247, .08); padding-bottom: 6px; }
4865
4891
  .criterion-metrics strong { color: var(--text); font-weight: 650; }
@@ -5086,8 +5112,12 @@ var WITAN_RUBRIC_VERSION_V14 = "witan-rubric-v14-2026-07-23";
5086
5112
  var WITAN_RUBRIC_VERSION_V15 = "witan-rubric-v15-2026-07-24";
5087
5113
  var WITAN_RUBRIC_VERSION_V16 = "witan-rubric-v16-2026-07-24";
5088
5114
  var WITAN_RUBRIC_VERSION_V17 = "witan-rubric-v17-2026-07-24";
5115
+ var WITAN_RUBRIC_VERSION_V18 = "witan-rubric-v18-prospective-2026-07-25";
5089
5116
 
5090
5117
  // src/witan/repo-signals.ts
5118
+ function usesV17DetectorClosure(rubricVersion) {
5119
+ return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18;
5120
+ }
5091
5121
  function buildWitanInputFromRepo(options) {
5092
5122
  assertRepoPathExists(options.repoPath);
5093
5123
  const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -5096,26 +5126,27 @@ function buildWitanInputFromRepo(options) {
5096
5126
  const repoFiles = listRepoFiles(options.repoPath);
5097
5127
  const inventoryFiles = listRepoInventory(options.repoPath, repoFiles);
5098
5128
  const ignoredTargetReason = repoFiles.length === 0 ? explainIgnoredScanTarget(options.repoPath) : void 0;
5099
- const usesV17Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V17;
5129
+ const usesV17Detectors = usesV17DetectorClosure(rubricVersion);
5130
+ const usesV18NativeRls = rubricVersion === WITAN_RUBRIC_VERSION_V18;
5100
5131
  const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
5101
- const readableArchetype = 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 ? applyReadableSourceRepresentationGate(
5132
+ 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(
5102
5133
  options.repoPath,
5103
5134
  inventoryFiles,
5104
5135
  structuralArchetype,
5105
5136
  headSha,
5106
5137
  rubricVersion
5107
5138
  ) : structuralArchetype;
5108
- const gatedArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17 ? applySemanticSourceRepresentationGate(
5139
+ const gatedArchetype = rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure(rubricVersion) ? applySemanticSourceRepresentationGate(
5109
5140
  options.repoPath,
5110
5141
  inventoryFiles,
5111
5142
  readableArchetype,
5112
5143
  rubricVersion
5113
5144
  ) : readableArchetype;
5114
- const usesV8Detectors = 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;
5115
- const usesV33Detectors = 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;
5116
- const usesV36Detectors = 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;
5117
- const usesV39Detectors = 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;
5118
- const usesV47Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17;
5145
+ const usesV8Detectors = 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 || usesV17DetectorClosure(rubricVersion);
5146
+ const usesV33Detectors = 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 || usesV17DetectorClosure(rubricVersion);
5147
+ const usesV36Detectors = 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 || usesV17DetectorClosure(rubricVersion);
5148
+ const usesV39Detectors = 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 || usesV17DetectorClosure(rubricVersion);
5149
+ const usesV47Detectors = rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure(rubricVersion);
5119
5150
  const reviewableSourceProof = usesV17Detectors ? buildReviewableSourceProof(options.repoPath, inventoryFiles, headSha, rubricVersion) : void 0;
5120
5151
  const coreSignals = collectRepoSignals(
5121
5152
  options.repoPath,
@@ -5126,6 +5157,7 @@ function buildWitanInputFromRepo(options) {
5126
5157
  usesV36Detectors,
5127
5158
  usesV39Detectors,
5128
5159
  usesV47Detectors,
5160
+ usesV18NativeRls,
5129
5161
  reviewableSourceProof
5130
5162
  );
5131
5163
  const archetype = usesV17Detectors && reviewableSourceProof?.passes === true && isV17RescuableAbstention(gatedArchetype.abstentionKind) && coreSignals.some(isMeasuredCriterionSignal) ? {
@@ -5233,7 +5265,7 @@ var SOURCE_EXTENSION_PATTERN_V10 = new RegExp(
5233
5265
  "i"
5234
5266
  );
5235
5267
  function isRecognizedSourcePath(path, rubricVersion = WITAN_RUBRIC_VERSION) {
5236
- return (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 ? SOURCE_EXTENSION_PATTERN_V10 : SOURCE_EXTENSION_PATTERN).test(path);
5268
+ return (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 || usesV17DetectorClosure(rubricVersion) ? SOURCE_EXTENSION_PATTERN_V10 : SOURCE_EXTENSION_PATTERN).test(path);
5237
5269
  }
5238
5270
  var BUNDLED_BINARY_EXTENSION_PATTERN = /\.(vsix|zip|tgz|whl|exe|dmg|pkg|msi|jar|war|apk|ipa|dll|dylib|ttf|otf|woff2?|eot|eps)$/i;
5239
5271
  var DOCS_OR_MEDIA_EXTENSION_PATTERN = /\.(md|mdx|rst|png|jpe?g|gif|svg|webp|ico|pdf)$/i;
@@ -5249,6 +5281,7 @@ var SOURCE_DOMINANCE_RATIO_THRESHOLD_V8 = 0.2;
5249
5281
  var SOURCE_DOMINANCE_RATIO_THRESHOLD_V9 = 0.8;
5250
5282
  var SOURCE_DOMINANCE_RATIO_THRESHOLD_V10 = 0.5;
5251
5283
  var SOURCE_DOMINANCE_RATIO_THRESHOLD_V11 = 0.2;
5284
+ var V17_REVIEWABLE_SOURCE_RATABLE_SHARE_THRESHOLD = 0.2;
5252
5285
  var GENERATED_SOURCE_MINIMUM = 8;
5253
5286
  var GENERATED_SOURCE_RATIO_THRESHOLD = 0.8;
5254
5287
  var GENERATED_AUTHORED_MAXIMUM = 3;
@@ -5292,7 +5325,7 @@ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION)
5292
5325
  insufficientSourceReason: `${catalogSourceFiles.length} of ${sourceFileCount} source-shaped file(s) are under independent solution, exercise, challenge, or algorithm catalog paths; only ${sourceFileCount - catalogSourceFiles.length} source-shaped file(s) remain outside that catalog. Cejel abstains because the repository does not expose one cohesive product implementation to score. ${INGEST_POINTER}`
5293
5326
  };
5294
5327
  }
5295
- if (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) {
5328
+ if (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 || usesV17DetectorClosure(rubricVersion)) {
5296
5329
  const authoredSourceFiles = sourceFiles.filter(isAuthoredProductionPath);
5297
5330
  const documentationFiles = repoFiles.filter(
5298
5331
  (file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file)
@@ -5310,7 +5343,7 @@ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION)
5310
5343
  (file) => ARTIFACT_DOMINANCE_EXTENSION_PATTERN.test(file)
5311
5344
  );
5312
5345
  const materialFiles = authoredSourceFiles.length + artifactFiles.length;
5313
- if (artifactFiles.length >= 10 && (authoredSourceFiles.length <= 10 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || rubricVersion === WITAN_RUBRIC_VERSION_V17) && materialFiles > 0 && authoredSourceFiles.length / materialFiles <= 0.1) {
5346
+ if (artifactFiles.length >= 10 && (authoredSourceFiles.length <= 10 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure(rubricVersion)) && materialFiles > 0 && authoredSourceFiles.length / materialFiles <= 0.1) {
5314
5347
  return {
5315
5348
  archetype: "binary_only",
5316
5349
  sourceFileCount,
@@ -5337,7 +5370,7 @@ function classifyRepoArchetype(repoFiles, rubricVersion = WITAN_RUBRIC_VERSION)
5337
5370
  const docsDistributionFiles = repoFiles.filter(
5338
5371
  (file) => DOCS_OR_MEDIA_EXTENSION_PATTERN.test(file) || DOC_DISTRIBUTION_COMPANION_PATTERN.test(file)
5339
5372
  );
5340
- if (docsFiles.length > 0 && (docsDistributionFiles.length === totalFileCount || (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) && unrecognisedSourceFiles.length === 0)) {
5373
+ if (docsFiles.length > 0 && (docsDistributionFiles.length === totalFileCount || (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 || usesV17DetectorClosure(rubricVersion)) && unrecognisedSourceFiles.length === 0)) {
5341
5374
  return {
5342
5375
  archetype: "docs_only",
5343
5376
  sourceFileCount,
@@ -5456,6 +5489,23 @@ function isRepresentativeSourceText(contents) {
5456
5489
  function representativeSourceRank(rankKey, file) {
5457
5490
  return createHash2("sha256").update(`${rankKey}:${file}`).digest("hex");
5458
5491
  }
5492
+ function explainNoMeasurementSourceCoverage(repoPath, rubricVersion) {
5493
+ if (!usesV17DetectorClosure(rubricVersion)) return void 0;
5494
+ const repoFiles = listRepoFiles(repoPath);
5495
+ const inventoryFiles = listRepoInventory(repoPath, repoFiles);
5496
+ const proof = buildReviewableSourceProof(
5497
+ repoPath,
5498
+ inventoryFiles,
5499
+ readGitHead(repoPath),
5500
+ rubricVersion
5501
+ );
5502
+ if (proof.sourceShapedFileCount === 0) return void 0;
5503
+ const ratableShare = proof.eligibleSourceFileCount / proof.sourceShapedFileCount;
5504
+ if (ratableShare >= V17_REVIEWABLE_SOURCE_RATABLE_SHARE_THRESHOLD) return void 0;
5505
+ const ratablePct = (ratableShare * 100).toFixed(1);
5506
+ const thresholdPct = (V17_REVIEWABLE_SOURCE_RATABLE_SHARE_THRESHOLD * 100).toFixed(0);
5507
+ return `${WITAN_NO_MEASUREMENT_REASON} Source coverage: ${proof.eligibleSourceFileCount} of ${proof.sourceShapedFileCount} source-shaped files (${ratablePct}%) are criterion-ratable, below the ${thresholdPct}% reviewable-source threshold (${inventoryFiles.length} tracked files in total).`;
5508
+ }
5459
5509
  function buildReviewableSourceProof(repoPath, repoFiles, headSha, rubricVersion) {
5460
5510
  const sourceShapedFiles = repoFiles.filter(
5461
5511
  (path) => isRecognizedSourcePath(path, rubricVersion) || isUnrecognisedSourcePath(path, rubricVersion) || /\.ipynb$/i.test(path)
@@ -5480,7 +5530,7 @@ function buildReviewableSourceProof(repoPath, repoFiles, headSha, rubricVersion)
5480
5530
  sampledFileCount: sample.length,
5481
5531
  readableSampledFileCount: readablePaths.length,
5482
5532
  ...readablePaths[0] ? { firstReadablePath: readablePaths[0] } : {},
5483
- passes: eligibleSourceFiles.length > 0 && ratableShare >= 0.2 && readablePaths.length > 0 && (readableShare >= 0.75 || readablePaths.length >= 8)
5533
+ passes: eligibleSourceFiles.length > 0 && ratableShare >= V17_REVIEWABLE_SOURCE_RATABLE_SHARE_THRESHOLD && readablePaths.length > 0 && (readableShare >= 0.75 || readablePaths.length >= 8)
5484
5534
  };
5485
5535
  }
5486
5536
  function applyReadableSourceRepresentationGate(repoPath, repoFiles, classification, headSha, rubricVersion) {
@@ -5622,7 +5672,7 @@ function readRepresentativeSourceText(repoPath, file) {
5622
5672
  }
5623
5673
  }
5624
5674
  function sourceDominanceThreshold(rubricVersion) {
5625
- if (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) {
5675
+ if (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 || usesV17DetectorClosure(rubricVersion)) {
5626
5676
  return SOURCE_DOMINANCE_RATIO_THRESHOLD_V11;
5627
5677
  }
5628
5678
  if (rubricVersion === WITAN_RUBRIC_VERSION_V10) return SOURCE_DOMINANCE_RATIO_THRESHOLD_V10;
@@ -5636,7 +5686,7 @@ function isUnrecognisedSourcePath(file, rubricVersion = WITAN_RUBRIC_VERSION) {
5636
5686
  const base = basename(file);
5637
5687
  if (!/\.([a-zA-Z0-9_]+)$/.test(base)) return false;
5638
5688
  if (KNOWN_NON_SOURCE_EXTENSION_PATTERN.test(base)) return false;
5639
- if ((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) && V13_NON_SOURCE_EXTENSION_PATTERN.test(base)) {
5689
+ if ((rubricVersion === WITAN_RUBRIC_VERSION_V13 || rubricVersion === WITAN_RUBRIC_VERSION_V14 || rubricVersion === WITAN_RUBRIC_VERSION_V15 || rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure(rubricVersion)) && V13_NON_SOURCE_EXTENSION_PATTERN.test(base)) {
5640
5690
  return false;
5641
5691
  }
5642
5692
  if (DOCS_OR_MEDIA_EXTENSION_PATTERN.test(base)) return false;
@@ -5722,7 +5772,7 @@ function buildNotApplicableSignal(criterionId, reason) {
5722
5772
  notApplicable: true
5723
5773
  };
5724
5774
  }
5725
- function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, reviewableSourceProof) {
5775
+ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, reviewableSourceProof) {
5726
5776
  const signals = [];
5727
5777
  const mono = resolveMonorepoContext(repoPath);
5728
5778
  const a1Signal = collectA1TestIntegrityEvidence(
@@ -5740,7 +5790,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
5740
5790
  useV33Detectors,
5741
5791
  useV36Detectors,
5742
5792
  useV39Detectors,
5743
- useV47Detectors
5793
+ useV47Detectors,
5794
+ useV18NativeRls
5744
5795
  );
5745
5796
  const a3Signal = collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors);
5746
5797
  const a4Signal = collectA4DependencyEvidence(
@@ -6032,7 +6083,7 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
6032
6083
  notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files.`
6033
6084
  };
6034
6085
  }
6035
- function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors) {
6086
+ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls) {
6036
6087
  const evidence = [];
6037
6088
  const findings = [];
6038
6089
  const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
@@ -6128,19 +6179,21 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
6128
6179
  evidence.push(evidenceForRelative(repoPath, dbLayerImportFile, "artifact", "DB client import"));
6129
6180
  }
6130
6181
  const v47RlsPolicyFiles = useV47Detectors ? findRlsPolicyFiles(repoPath, repoFiles) : [];
6131
- const v47TenantStorageFiles = useV47Detectors ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
6182
+ const nativeRlsInventory = useV18NativeRls ? deriveRlsPolicyScopeInventory(repoPath, repoFiles) : null;
6183
+ const tenantWithoutRlsPremiseFiles = useV18NativeRls ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
6184
+ const v47TenantStorageFiles = useV47Detectors && !useV18NativeRls ? findTenantStoragePremiseFiles(repoPath, repoFiles) : [];
6132
6185
  const rlsMigration = useV47Detectors ? v47RlsPolicyFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, RLS_PATTERN));
6133
6186
  const tenantScopePattern = useV39Detectors ? TENANT_SCOPE_PATTERN_V11 : useV36Detectors ? TENANT_SCOPE_PATTERN_V10 : TENANT_SCOPE_PATTERN;
6134
- const tenantScopedFile = useV47Detectors ? v47TenantStorageFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, tenantScopePattern));
6135
- const rlsPolicyCount = useV47Detectors ? v47RlsPolicyFiles.length : countPatternMatches(
6187
+ const tenantScopedFile = useV18NativeRls ? nativeRlsInventory?.storageFiles[0] : useV47Detectors ? v47TenantStorageFiles[0] : migrationFiles.find((file) => fileContains(repoPath, file, tenantScopePattern));
6188
+ const rlsPolicyCount = useV18NativeRls ? nativeRlsInventory?.policyCount ?? 0 : useV47Detectors ? v47RlsPolicyFiles.length : countPatternMatches(
6136
6189
  repoPath,
6137
6190
  migrationFiles,
6138
6191
  /create policy|enable row level security|force row level security/gi
6139
6192
  );
6140
- const tenantScopedMigrationFileCount = useV47Detectors ? v47TenantStorageFiles.length : countFilesContaining(repoPath, migrationFiles, tenantScopePattern);
6193
+ const tenantScopedMigrationFileCount = useV18NativeRls ? nativeRlsInventory?.storageFiles.length ?? 0 : useV47Detectors ? v47TenantStorageFiles.length : countFilesContaining(repoPath, migrationFiles, tenantScopePattern);
6141
6194
  const gitignoreEnvFile = gitignore && fileContains(repoPath, gitignore, /^\.env(\*|\b)|\.env\./m) ? gitignore : null;
6142
6195
  const envHandlingDepth = (envExamples.length > 0 ? 1 : 0) + (gitignoreEnvFile ? 1 : 0) + (countFilesContaining(repoPath, repoFiles.filter(isImplementationFile), ENV_READ_PATTERN) > 0 ? 1 : 0);
6143
- const isMultiTenant = tenantScopedMigrationFileCount > 0;
6196
+ const isMultiTenant = useV18NativeRls ? (nativeRlsInventory?.policyCount ?? 0) > 0 && (nativeRlsInventory?.scopeIdentifiers.length ?? 0) > 0 : tenantScopedMigrationFileCount > 0;
6144
6197
  if (gitignoreEnvFile) {
6145
6198
  evidence.push(
6146
6199
  evidenceForRelative(repoPath, gitignoreEnvFile, "secret_scan", ".env files are gitignored")
@@ -6156,7 +6209,12 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
6156
6209
  }
6157
6210
  if (tenantScopedFile) {
6158
6211
  evidence.push(
6159
- evidenceForRelative(repoPath, tenantScopedFile, "artifact", "Tenant scoping signal")
6212
+ evidenceForRelative(
6213
+ repoPath,
6214
+ tenantScopedFile,
6215
+ "artifact",
6216
+ useV18NativeRls ? `Repository-native RLS scope (${nativeRlsInventory?.scopeIdentifiers.join(", ")})` : "Tenant scoping signal"
6217
+ )
6160
6218
  );
6161
6219
  }
6162
6220
  if (committedSecret) {
@@ -6214,8 +6272,9 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
6214
6272
  evidence: historySecretScan.envPathEvidence
6215
6273
  });
6216
6274
  }
6217
- if (isMultiTenant && rlsPolicyCount === 0) {
6218
- const gapEvidence = rlsMigration ?? tenantScopedFile;
6275
+ const hasTenantWithoutRlsGap = rlsPolicyCount === 0 && (useV18NativeRls ? tenantWithoutRlsPremiseFiles.length > 0 : isMultiTenant);
6276
+ if (hasTenantWithoutRlsGap) {
6277
+ const gapEvidence = rlsMigration ?? tenantScopedFile ?? tenantWithoutRlsPremiseFiles[0];
6219
6278
  if (gapEvidence) {
6220
6279
  findings.push({
6221
6280
  severity: "warning",
@@ -6258,20 +6317,20 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
6258
6317
  "rls_policy_count",
6259
6318
  "RLS policy count",
6260
6319
  rlsPolicyCount,
6261
- Math.max(tenantScopedMigrationFileCount * 3, 3),
6320
+ useV18NativeRls ? Math.max(tenantScopedMigrationFileCount, 1) : Math.max(tenantScopedMigrationFileCount * 3, 3),
6262
6321
  0.4,
6263
6322
  "policies",
6264
- "Counts static row-level-security enables, forced-RLS statements, and policy definitions.",
6323
+ useV18NativeRls ? "Counts CREATE POLICY definitions with repository-native USING/WITH CHECK clauses against the repository-native scoped data-layer surface." : "Counts static row-level-security enables, forced-RLS statements, and policy definitions.",
6265
6324
  "saturating_count"
6266
6325
  ),
6267
6326
  metric(
6268
6327
  "tenant_scope_ratio",
6269
6328
  "Tenant-scoped schema ratio",
6270
- countFilesContaining(repoPath, migrationFiles, tenantScopePattern),
6329
+ useV18NativeRls ? tenantScopedMigrationFileCount : countFilesContaining(repoPath, migrationFiles, tenantScopePattern),
6271
6330
  Math.max(migrationFiles.length, 1),
6272
6331
  0.25,
6273
6332
  "ratio",
6274
- "Measures tenant/studio/workspace scoping evidence across data-layer files."
6333
+ useV18NativeRls ? `Measures repository-native RLS scope identifiers (${nativeRlsInventory?.scopeIdentifiers.join(", ")}) across data-layer files.` : "Measures tenant/studio/workspace scoping evidence across data-layer files."
6275
6334
  )
6276
6335
  ] : [];
6277
6336
  const cryptoMetrics = cryptoHygiene.hasSurface ? [
@@ -6290,7 +6349,7 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
6290
6349
  positiveEvidence: evidence,
6291
6350
  findings,
6292
6351
  metrics: [...baseMetrics, ...isolationMetrics, ...cryptoMetrics],
6293
- notes: "History secret scanning covers all reachable git history for credential-pattern paths unless the explicit credential-blob safety valve is reported."
6352
+ notes: useV18NativeRls ? "History secret scanning covers all reachable git history for credential-pattern paths unless the explicit credential-blob safety valve is reported. Positive multi-tenancy credit is derived only from this repository\u2019s own CREATE POLICY USING/WITH CHECK clauses; no fixed tenant-token vocabulary can create isolation credit. A conservative schema premise is retained only to fail closed when a tenant-shaped schema has zero RLS policies." : "History secret scanning covers all reachable git history for credential-pattern paths unless the explicit credential-blob safety valve is reported."
6294
6353
  };
6295
6354
  }
6296
6355
  function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = false) {
@@ -7186,7 +7245,7 @@ var TEST_DIRECTORY_SOURCE_PATTERN_V17 = new RegExp(
7186
7245
  "i"
7187
7246
  );
7188
7247
  function isTestFile(file, rubricVersion = WITAN_RUBRIC_VERSION) {
7189
- return rubricVersion === WITAN_RUBRIC_VERSION_V17 && (TEST_DIRECTORY_SOURCE_PATTERN_V17.test(file) || /\.tftest\.hcl$/i.test(file) || /\.bats$/i.test(file) || /(^|\/)test[^/]*\.m$/i.test(file) || /_test\.m$/i.test(file)) || TEST_DIRECTORY_SOURCE_PATTERN.test(file) || // AVA's default discovery includes test.js and test-*.js at the scanned package root.
7248
+ return usesV17DetectorClosure(rubricVersion) && (TEST_DIRECTORY_SOURCE_PATTERN_V17.test(file) || /\.tftest\.hcl$/i.test(file) || /\.bats$/i.test(file) || /(^|\/)test[^/]*\.m$/i.test(file) || /_test\.m$/i.test(file)) || TEST_DIRECTORY_SOURCE_PATTERN.test(file) || // AVA's default discovery includes test.js and test-*.js at the scanned package root.
7190
7249
  // Keep this root-anchored: nested files need an existing test-directory/suffix convention.
7191
7250
  /^test(?:-[^/]+)?\.[cm]?[jt]sx?$/.test(file) || /\.(test|spec)\.[cm]?[jt]sx?$/.test(file) || /(^|\/)test_.*\.py$/.test(file) || /_test\.go$/.test(file) || /(^|\/)test_.*\.(cpp|cc|cxx)$/.test(file) || /_(test|tests)\.(cpp|cc|cxx)$/.test(file) || /Tests?\.(java|kt)$/.test(file) || /(_test|_spec)\.(rs|rb|php|swift|kt)$/.test(file);
7192
7251
  }
@@ -7857,6 +7916,138 @@ function findRlsPolicyFiles(repoPath, repoFiles) {
7857
7916
  )
7858
7917
  );
7859
7918
  }
7919
+ var RLS_CLAUSE_SQL_WORDS = /* @__PURE__ */ new Set([
7920
+ "all",
7921
+ "and",
7922
+ "any",
7923
+ "as",
7924
+ "asc",
7925
+ "between",
7926
+ "by",
7927
+ "case",
7928
+ "cast",
7929
+ "current",
7930
+ "desc",
7931
+ "distinct",
7932
+ "else",
7933
+ "end",
7934
+ "exists",
7935
+ "false",
7936
+ "from",
7937
+ "in",
7938
+ "is",
7939
+ "join",
7940
+ "like",
7941
+ "limit",
7942
+ "not",
7943
+ "null",
7944
+ "on",
7945
+ "or",
7946
+ "order",
7947
+ "select",
7948
+ "then",
7949
+ "text",
7950
+ "true",
7951
+ "uuid",
7952
+ "varchar",
7953
+ "when",
7954
+ "where"
7955
+ ]);
7956
+ function extractBalancedSqlClause(statement, keyword) {
7957
+ const clauses = [];
7958
+ const keywordPattern = keyword === "using" ? /\busing\s*\(/gi : /\bwith\s+check\s*\(/gi;
7959
+ for (const match of statement.matchAll(keywordPattern)) {
7960
+ const openingParen = (match.index ?? 0) + match[0].lastIndexOf("(");
7961
+ let depth = 0;
7962
+ let quote = null;
7963
+ for (let index = openingParen; index < statement.length; index += 1) {
7964
+ const character = statement[index];
7965
+ if (quote) {
7966
+ if (character === quote && statement[index - 1] !== "\\") quote = null;
7967
+ continue;
7968
+ }
7969
+ if (character === "'" || character === '"') {
7970
+ quote = character;
7971
+ } else if (character === "(") {
7972
+ depth += 1;
7973
+ } else if (character === ")") {
7974
+ depth -= 1;
7975
+ if (depth === 0) {
7976
+ clauses.push(statement.slice(openingParen + 1, index));
7977
+ break;
7978
+ }
7979
+ }
7980
+ }
7981
+ }
7982
+ return clauses;
7983
+ }
7984
+ function extractRlsScopeIdentifiers(clause) {
7985
+ const scrubbed = clause.replace(/'(?:''|[^'])*'/g, " ").replace(/"(?:""|[^"])*"/g, " ");
7986
+ const identifiers = /* @__PURE__ */ new Set();
7987
+ const identifierPattern = /\b[A-Za-z_][A-Za-z0-9_$]*(?:\.[A-Za-z_][A-Za-z0-9_$]*)*\b/g;
7988
+ for (const match of scrubbed.matchAll(identifierPattern)) {
7989
+ const token = match[0];
7990
+ const segments = token.split(".").map((segment) => segment.toLowerCase());
7991
+ const lastSegment = segments.at(-1);
7992
+ if (!lastSegment || lastSegment.length < 2 || RLS_CLAUSE_SQL_WORDS.has(lastSegment)) continue;
7993
+ if (["app", "auth", "jwt", "request", "session"].includes(segments[0] ?? "")) continue;
7994
+ const remainder = scrubbed.slice((match.index ?? 0) + token.length);
7995
+ if (/^\s*\(/.test(remainder)) continue;
7996
+ if (["auth", "current_setting", "jwt", "pg_catalog", "public", "uid"].includes(lastSegment)) {
7997
+ continue;
7998
+ }
7999
+ identifiers.add(lastSegment);
8000
+ }
8001
+ return [...identifiers].sort();
8002
+ }
8003
+ function deriveRlsPolicyScopeInventory(repoPath, repoFiles) {
8004
+ const candidatePolicyFiles = findRlsPolicyFiles(repoPath, repoFiles);
8005
+ const policyFiles = /* @__PURE__ */ new Set();
8006
+ const scopeIdentifiers = /* @__PURE__ */ new Set();
8007
+ let policyCount = 0;
8008
+ for (const file of candidatePolicyFiles) {
8009
+ let content;
8010
+ try {
8011
+ content = readFileSync(join(repoPath, file), "utf8");
8012
+ } catch {
8013
+ continue;
8014
+ }
8015
+ for (const match of content.matchAll(/\bcreate\s+policy\b[\s\S]*?(?:;|$)/gi)) {
8016
+ const statement = match[0];
8017
+ const clauses = [
8018
+ ...extractBalancedSqlClause(statement, "using"),
8019
+ ...extractBalancedSqlClause(statement, "with check")
8020
+ ];
8021
+ if (clauses.length === 0) continue;
8022
+ policyCount += 1;
8023
+ policyFiles.add(file);
8024
+ for (const clause of clauses) {
8025
+ for (const identifier of extractRlsScopeIdentifiers(clause)) {
8026
+ scopeIdentifiers.add(identifier);
8027
+ }
8028
+ }
8029
+ }
8030
+ }
8031
+ const identifiers = [...scopeIdentifiers].sort();
8032
+ const dataLayerFiles = repoFiles.filter(
8033
+ (file) => /(^|\/)(?:migrations?|drizzle|prisma)\/|\.(?:sql|prisma)$/i.test(file)
8034
+ );
8035
+ const storageFiles = identifiers.length === 0 ? [] : dataLayerFiles.filter(
8036
+ (file) => identifiers.some(
8037
+ (identifier) => fileContains(
8038
+ repoPath,
8039
+ file,
8040
+ new RegExp(`\\b${identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i")
8041
+ )
8042
+ )
8043
+ );
8044
+ return {
8045
+ policyFiles: [...policyFiles].sort(),
8046
+ policyCount,
8047
+ scopeIdentifiers: identifiers,
8048
+ storageFiles
8049
+ };
8050
+ }
7860
8051
  function findDeployableServicePremiseFiles(repoPath, repoFiles, strictOperationalBoundary = false) {
7861
8052
  const explicitTargets = repoFiles.filter(
7862
8053
  (file) => isAuthoredProductionPath(file) && isExplicitDeployTarget(file) && (!strictOperationalBoundary || isV47OperationalApplicationPath(file))
@@ -9605,6 +9796,9 @@ var WITAN_RUBRIC = [
9605
9796
  ];
9606
9797
 
9607
9798
  // src/witan/scoring.ts
9799
+ function usesV17DetectorClosure2(rubricVersion) {
9800
+ return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18;
9801
+ }
9608
9802
  var MAX_SIGNAL_ADJUSTMENT = 0.8;
9609
9803
  function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
9610
9804
  const parsedInput = WitanReportInputSchema.parse(input);
@@ -9688,7 +9882,7 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC) {
9688
9882
  const isFreeCoreRubric = rubric.length === WITAN_RUBRIC.length && rubric.every(
9689
9883
  ({ id, category }, index) => id === WITAN_RUBRIC[index]?.id && category === WITAN_RUBRIC[index]?.category
9690
9884
  );
9691
- const noMeasurementAbstention = (parsedInput.rubricVersion === WITAN_RUBRIC_VERSION_V16 || parsedInput.rubricVersion === WITAN_RUBRIC_VERSION_V17) && isFreeCoreRubric && !hasMeasuredCriterion;
9885
+ const noMeasurementAbstention = (parsedInput.rubricVersion === WITAN_RUBRIC_VERSION_V16 || usesV17DetectorClosure2(parsedInput.rubricVersion)) && isFreeCoreRubric && !hasMeasuredCriterion;
9692
9886
  const insufficientSourceReason = parsedInput.insufficientSourceReason ?? (noMeasurementAbstention ? WITAN_NO_MEASUREMENT_REASON : void 0);
9693
9887
  const abstained = insufficientSourceReason != null;
9694
9888
  return WitanReportSchema.parse({
@@ -9756,7 +9950,7 @@ function statusForScore(score) {
9756
9950
  }
9757
9951
  function statusAfterInputAdjustment(nativeStatus, nativeScore, adjustment, rubricVersion) {
9758
9952
  if (adjustment === 0) return nativeStatus;
9759
- 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) {
9953
+ 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) {
9760
9954
  return statusForScore(roundScore(Math.max(0, nativeScore - adjustment)));
9761
9955
  }
9762
9956
  const statusFloor = {
@@ -9800,7 +9994,7 @@ function mergeSignalsByCriterion(signals) {
9800
9994
  return merged;
9801
9995
  }
9802
9996
  function usesMetricScoring(rubricVersion) {
9803
- 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_TRADING_RUBRIC_VERSION_V0;
9997
+ 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_TRADING_RUBRIC_VERSION_V0;
9804
9998
  }
9805
9999
  function unmeasuredStatus(rubricVersion) {
9806
10000
  return rubricVersion === WITAN_TRADING_RUBRIC_VERSION_V0 ? "unverified" : "insufficient_data";
@@ -9931,7 +10125,7 @@ function scoreMetrics(metrics) {
9931
10125
  function capScoreForFindings(score, findings, rubricVersion, criterionId) {
9932
10126
  if (findings.some((finding) => finding.severity === "critical")) {
9933
10127
  const criticalFindings = findings.filter(({ severity }) => severity === "critical");
9934
- const authenticatedA1Absence = rubricVersion === WITAN_RUBRIC_VERSION_V17 && criterionId === "A1" && criticalFindings.length === 1 && criticalFindings[0]?.summary === WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY;
10128
+ const authenticatedA1Absence = usesV17DetectorClosure2(rubricVersion) && criterionId === "A1" && criticalFindings.length === 1 && criticalFindings[0]?.summary === WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY;
9935
10129
  return authenticatedA1Absence ? Math.min(score, 1.4) : 1.4;
9936
10130
  }
9937
10131
  if (findings.some((finding) => finding.severity === "warning")) return Math.min(score, 2.4);
@@ -9940,8 +10134,8 @@ function capScoreForFindings(score, findings, rubricVersion, criterionId) {
9940
10134
  }
9941
10135
  function statusForScoreAndFindings(criterionId, score, findings, metrics, rubricVersion) {
9942
10136
  if (findings.some((finding) => finding.severity === "critical")) return "critical";
9943
- 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) {
9944
- const calibrated = 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 ? calibratedCriterionStateV11(criterionId, metrics) : rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 ? calibratedCriterionStateV9(criterionId, metrics) : calibratedCriterionStateV8(criterionId, metrics);
10137
+ 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 || usesV17DetectorClosure2(rubricVersion)) {
10138
+ const calibrated = 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 || usesV17DetectorClosure2(rubricVersion) ? calibratedCriterionStateV11(criterionId, metrics) : rubricVersion === WITAN_RUBRIC_VERSION_V9 || rubricVersion === WITAN_RUBRIC_VERSION_V10 ? calibratedCriterionStateV9(criterionId, metrics) : calibratedCriterionStateV8(criterionId, metrics);
9945
10139
  if (calibrated && calibrated !== "verified") return calibrated;
9946
10140
  if (calibrated === "verified" && !findings.some((finding) => finding.severity === "warning")) {
9947
10141
  return "verified";
@@ -10167,7 +10361,7 @@ function calibratedCriterionStateV11(criterionId, metrics) {
10167
10361
  return void 0;
10168
10362
  }
10169
10363
  function ensureFindingsExplainStatus(criterionId, score, status, findings, evidence, rubricVersion) {
10170
- 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 && findings.length > 0) {
10364
+ 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 && findings.length > 0) {
10171
10365
  return [...findings];
10172
10366
  }
10173
10367
  if (status !== "critical" && status !== "warning") return [...findings];
@@ -10212,7 +10406,18 @@ function scoreRepoWithPublicCejel(options) {
10212
10406
  ...options.rubricVersion ? { rubricVersion: options.rubricVersion } : {}
10213
10407
  });
10214
10408
  const inputSignals = resolvePublicIngestSignals(options);
10215
- return createWitanReport(input, inputSignals.length > 0 ? inputSignals : void 0);
10409
+ const report = createWitanReport(input, inputSignals.length > 0 ? inputSignals : void 0);
10410
+ if (report.verdict !== "insufficient_source" || !isWitanNoMeasurementAbstention(report)) {
10411
+ return report;
10412
+ }
10413
+ const contextualReason = explainNoMeasurementSourceCoverage(
10414
+ options.repoPath,
10415
+ report.rubricVersion
10416
+ );
10417
+ return contextualReason ? {
10418
+ ...report,
10419
+ insufficientSourceReason: contextualReason
10420
+ } : report;
10216
10421
  }
10217
10422
  function resolvePublicIngestSignals(options) {
10218
10423
  const seen = /* @__PURE__ */ new Set();
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  runCejelScan,
10
10
  serializeWitanReport,
11
11
  verifyWitanAttestationBinding
12
- } from "./chunk-PYI3OIBW.js";
12
+ } from "./chunk-SNDRU52Z.js";
13
13
 
14
14
  // src/index.ts
15
15
  import { createHash } from "crypto";
@@ -224,7 +224,11 @@ async function runWitanFreeCli(args) {
224
224
  JSON.stringify(attestation, null, 2),
225
225
  "utf8"
226
226
  );
227
- writeFileSync(join(options.outDir, "certificate.html"), renderWitanHtmlReport(report), "utf8");
227
+ writeFileSync(
228
+ join(options.outDir, "certificate.html"),
229
+ renderWitanHtmlReport(report, { cliVersion: cliVersion() }),
230
+ "utf8"
231
+ );
228
232
  writeFileSync(
229
233
  join(options.outDir, "badge.json"),
230
234
  JSON.stringify(renderWitanBadgeEndpoint(report), null, 2),
package/dist/mcp/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  renderWitanBadgeSvg,
11
11
  renderWitanHtmlReport,
12
12
  runCejelScan
13
- } from "../chunk-PYI3OIBW.js";
13
+ } from "../chunk-SNDRU52Z.js";
14
14
 
15
15
  // node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
16
16
  var require_code = __commonJS({
@@ -17097,7 +17097,11 @@ function createCejelMcpServer(identity) {
17097
17097
  if (!lastScan) throw new Error("No scan has run yet \u2014 call the scan tool first.");
17098
17098
  return {
17099
17099
  contents: [
17100
- { uri: uri.href, mimeType: "text/html", text: renderWitanHtmlReport(lastScan.report) }
17100
+ {
17101
+ uri: uri.href,
17102
+ mimeType: "text/html",
17103
+ text: renderWitanHtmlReport(lastScan.report, { cliVersion: identity.version })
17104
+ }
17101
17105
  ]
17102
17106
  };
17103
17107
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cejel/cejel",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "mcpName": "io.github.BargLabs/cejel",
5
5
  "private": false,
6
6
  "publishConfig": {