@cejel/cejel 0.4.8 → 0.4.10

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
@@ -20,12 +20,8 @@ portable, offline trust certificate that aggregates them. Pipe in SARIF-compatib
20
20
  (MunaTrust, Snyk, Semgrep, CodeQL, Codex) plus OpenSSF Scorecard, and get one shareable
21
21
  certificate + badge over all of them. See "Aggregate your scanners" below.
22
22
 
23
- > Dogfooded in production. Cejel is run continuously on Barg Labs' own multi-product
24
- > monorepo — the ten-product studio it was built inside — which it currently scores
25
- > 3.3/4.0 on its rubric-native certificate. That transparency snapshot opts into prospective
26
- > v18; the public CLI default remains calibrated v17. On the comparative board, our code sits
27
- > outside the ranked population and receives no verdict band. We score ourselves before
28
- > asking you to score yourself.
23
+ > Cejel is used on Barg Labs’ own code. The [public board](https://cejel.dev/leaderboard/)
24
+ > labels publisher-owned snapshots separately from the ranked public repositories.
29
25
 
30
26
  ### Rubric calibration policy
31
27
 
@@ -40,7 +36,10 @@ untouched holdout, every preregistered gate to pass, and a separately recorded p
40
36
 
41
37
  ### Measured recall (bounded)
42
38
 
43
- > **Measured recall (bounded).** Cejel publishes no general detection-recall figure. On a frozen,
39
+ > **Measured recall (bounded).** Cejel publishes one cohort-level in-scope detection-recall
40
+ > figure, for the prospective v22 rubric, in `docs/calibration/v22-detection-recall/`; no general
41
+ > figure exists, and the three quantities called recall are set side by side on
42
+ > cejel.dev/methodology. On a frozen,
44
43
  > preregistered, in-scope held-out fixture set of 30 named defects, the released public default
45
44
  > (v17, tree `a857f0b3`) cited **16/30**, two-sided 95% Wilson interval **[36.1%, 69.8%]**; the
46
45
  > prospective v22 detector (commit `8a289ea`, tree `10960a03`) cited **24/30**, two-sided 95%
@@ -93,7 +92,7 @@ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
93
92
  ```
94
93
 
95
94
  > **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
96
- > 0.4.8 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
95
+ > 0.4.10 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
97
96
  > signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
98
97
  > it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
99
98
  > GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM and an
@@ -116,7 +115,7 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
116
115
  npx @cejel/cejel@latest .
117
116
  ```
118
117
 
119
- > **Distribution note:** Cejel `0.4.8` is the coordinated release version for npm,
118
+ > **Distribution note:** Cejel `0.4.10` is the coordinated release version for npm,
120
119
  > standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry.
121
120
 
122
121
  `npx` can reuse a stale cached package. Force the current npm release with the `@latest`
@@ -178,16 +177,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
178
177
  This is cryptographically signed provenance. It is distinct from Apple Developer ID or
179
178
  Microsoft Authenticode code-signing.
180
179
 
181
- **Docker / OCI.** The current container release is `0.4.8`:
180
+ **Docker / OCI.** The current container release is `0.4.10`:
182
181
 
183
182
  ```bash
184
- docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.4.8
183
+ docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.4.10
185
184
  ```
186
185
 
187
186
  The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
188
187
 
189
188
  ```bash
190
- docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.4.8 .
189
+ docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.4.10 .
191
190
  ```
192
191
 
193
192
  The OCI image carries an SBOM, maximum-mode build provenance, and a signed registry
@@ -195,17 +194,15 @@ attestation.
195
194
 
196
195
  ## Leaderboard
197
196
 
198
- This repo ships the [Cejel OSS trust leaderboard](./leaderboard/leaderboard.md): elite OSS
199
- projects, the public Cejel repository itself, and one explicitly labeled transparency
200
- snapshot from the private studio monorepo where Cejel was built, with a per-repository
201
- evidence report for every row under [`leaderboard/reports/`](./leaderboard/reports/). The
202
- board is also hosted at [cejel.dev](https://cejel.dev). Every score is produced by the same
203
- sealed public scorer used by `npx @cejel/cejel@latest .`; no private domain collector contributes.
204
- For each public-repository row, check out the immutable source commit printed in its report
205
- and run the public scorer to reproduce the score, verdict, measured coverage, and evidence.
206
- The private Alfred snapshot discloses its limitation instead: its source commit and withheld
207
- locations are not publicly available, so that row is not independently reproducible and is
208
- not presented as a public-repository self-score.
197
+ The [Cejel OSS trust leaderboard](https://cejel.dev/leaderboard/) is hosted on cejel.dev,
198
+ which is the single current board. This repository no longer distributes a second copy of
199
+ its scores or certificates. The board records its producing package version, rubric,
200
+ run date, pinned public revisions, and reproduction instructions for that particular run.
201
+ The private Alfred transparency snapshot is not independently reproducible.
202
+
203
+ The site's History section preserves the **2026-08-18 withdrawal**: the earlier
204
+ sealed-public-scorer reproducibility claim was false. The stale prospective-rubric copy
205
+ previously linked here has been removed; it must not be treated as current evidence.
209
206
 
210
207
  ### Redaction policy
211
208
 
@@ -519,7 +516,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
519
516
  the host path with the repository OpenClaw should allow Cejel to read:
520
517
 
521
518
  ```bash
522
- npx -y openclaw@latest mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.4.8"]}'
519
+ npx -y openclaw@latest mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.4.10"]}'
523
520
  npx -y openclaw@latest mcp doctor cejel-oci --probe
524
521
  ```
525
522
 
@@ -546,6 +543,47 @@ part of the scoring guarantee. If the tracked-file inventory fails unexpectedly,
546
543
  HTML, and markdown certificates declare that the scanner used its bounded directory fallback
547
544
  instead of silently presenting the two inventories as equivalent.
548
545
 
546
+ ## What a certificate's scope means
547
+
548
+ A certificate scores the repository tree at one pinned revision, and only that tree. That
549
+ boundary is deliberate: a certificate that reached outside the pinned tree could not be
550
+ reproduced by whoever receives it, and reproducibility is the entire product. Scoring only the
551
+ pinned tree is correct — the gap was that nothing on the certificate itself told a reader which
552
+ kind of statement they were holding. A repository whose regression suite lives in a separate
553
+ repository, checked out and run by its own CI, is correctly reported as having zero test files
554
+ against its source files. That statement is true about the tree and false about the system, and a
555
+ reader with no other context cannot tell the difference.
556
+
557
+ Every certificate now carries a standing scope line, in the "How to read this certificate"
558
+ section on every surface (HTML, Markdown, terminal), regardless of verdict: the certificate is a
559
+ statement about the repository tree at the pinned revision, not about the system that tree
560
+ belongs to, and evidence outside that tree is neither seen nor claimed to be absent. This is
561
+ additive, not a hedge — a finding that says evidence was not found in this tree stays exactly as
562
+ stated.
563
+
564
+ Four specific situations can make the tree's own conclusion misleading about the system it
565
+ belongs to:
566
+
567
+ - **A test suite kept in a separate repository.** Not detectable from inside the pinned tree —
568
+ there is no reliable signal that a sibling repository exists. Covered by the standing scope
569
+ line above, not a conditional disclosure.
570
+ - **CI configuration that changes into a different working directory before running its checks.**
571
+ Also not detectable without executing the CI job itself, which the offline scanner does not do.
572
+ Covered by the standing scope line above.
573
+ - **A large implementation file that exceeds the read limit.** This one is detectable, and
574
+ disclosed conditionally when it happens: when a file above the size ceiling is the reason a
575
+ specific signal has nothing to measure, the certificate's scan-limitations section names it —
576
+ "declined a large implementation file that exceeded the repository content size limit" — rather
577
+ than folding it into the generic coverage-limit sentence used for an extension exclusion or a
578
+ non-regular file. Today this fires for two A3 signals (health/readiness route detection and
579
+ observability-marker counting); it extends to more signals as they adopt the same per-signal
580
+ attribution.
581
+ - **A recognized-but-nonfunctional package script**, such as npm's auto-generated
582
+ `"test": "echo \"Error: no test specified\" && exit 1"` placeholder being credited the same as a
583
+ real test runner. This was a detector defect, not a scope-disclosure gap — Cejel now checks the
584
+ `test` script's content against known test-runner invocations rather than crediting it by key
585
+ presence alone, so no separate disclosure line is needed for it.
586
+
549
587
  ## Help validate Cejel
550
588
 
551
589
  Cejel does not collect telemetry. External validation is therefore opt-in and inspectable:
@@ -680,7 +718,7 @@ published in public.
680
718
 
681
719
  **No rubric change re-scores you silently.** Every change to scoring behavior requires a
682
720
  `WITAN_RUBRIC_VERSION` bump and a corpus-wide before/after delta published in
683
- [`docs/leaderboard/RUBRIC_CHANGELOG.md`](../../docs/leaderboard/RUBRIC_CHANGELOG.md) — score,
721
+ [`leaderboard/RUBRIC_CHANGELOG.md`](./leaderboard/RUBRIC_CHANGELOG.md) — score,
684
722
  verdict, and rank for every repository, "no repository moved" stated explicitly when that is
685
723
  the result. A build that changes scoring without both fails; see that file's `v2` entry for
686
724
  the home-field fix above as the first rubric change recorded this way.
@@ -5,20 +5,20 @@ import {
5
5
  deriveProductIdentity,
6
6
  runCejelScan,
7
7
  serializeWitanReport
8
- } from "../chunk-6R3VJGJ2.js";
8
+ } from "../chunk-TUUNJP27.js";
9
9
  import {
10
10
  listCejelLlmPackFiles
11
- } from "../chunk-ERNYMFH4.js";
12
- import "../chunk-KNQBI3IX.js";
11
+ } from "../chunk-IN2LPGGU.js";
12
+ import "../chunk-IHT7B6L7.js";
13
13
  import {
14
14
  external_exports
15
- } from "../chunk-J6R5OHN7.js";
15
+ } from "../chunk-GKYTLWQK.js";
16
16
  import {
17
17
  init_esm_shims,
18
18
  readRepoText,
19
19
  recordContentSkip,
20
20
  recordFilesystemSkip
21
- } from "../chunk-V3B2V4WA.js";
21
+ } from "../chunk-PTZU6G4E.js";
22
22
 
23
23
  // src/calibration/llm-detector.ts
24
24
  init_esm_shims();
@@ -4,7 +4,7 @@ const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  __export,
6
6
  init_esm_shims
7
- } from "./chunk-V3B2V4WA.js";
7
+ } from "./chunk-PTZU6G4E.js";
8
8
 
9
9
  // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
10
10
  var external_exports = {};
@@ -3,7 +3,7 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  init_esm_shims
6
- } from "./chunk-V3B2V4WA.js";
6
+ } from "./chunk-PTZU6G4E.js";
7
7
 
8
8
  // src/witan/git-exec.ts
9
9
  init_esm_shims();
@@ -5,10 +5,10 @@ import {
5
5
  describeGitFailure,
6
6
  execGit,
7
7
  isExpectedGitAbsence
8
- } from "./chunk-KNQBI3IX.js";
8
+ } from "./chunk-IHT7B6L7.js";
9
9
  import {
10
10
  init_esm_shims
11
- } from "./chunk-V3B2V4WA.js";
11
+ } from "./chunk-PTZU6G4E.js";
12
12
 
13
13
  // src/packs/llm/files.ts
14
14
  init_esm_shims();
@@ -7,7 +7,7 @@ import {
7
7
  __filename,
8
8
  __require,
9
9
  init_esm_shims
10
- } from "./chunk-V3B2V4WA.js";
10
+ } from "./chunk-PTZU6G4E.js";
11
11
 
12
12
  // node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/typescript.js
13
13
  var require_typescript = __commonJS({
@@ -96,22 +96,24 @@ function errnoClass(error) {
96
96
  const code = error.code;
97
97
  return typeof code === "string" && /^E[A-Z0-9_]+$/.test(code) ? code : null;
98
98
  }
99
+ function attributeSkip(session, criterion, signals, reason) {
100
+ session.affectedCriteria.add(criterion);
101
+ if (signals && signals.length > 0) {
102
+ for (const signal of signals) {
103
+ const key = contentReadSignalKey(criterion, signal);
104
+ session.affectedSignals.add(key);
105
+ addSkipReason(session.signalSkipReasons, key, reason);
106
+ }
107
+ } else {
108
+ session.unattributedCriteria.add(criterion);
109
+ addSkipReason(session.criterionSkipReasons, criterion, reason);
110
+ }
111
+ }
99
112
  function recordSkip(path2, reason, errno, affectsCurrentCriterion = false, deduplicate = false) {
100
113
  const session = activeSession;
101
114
  if (!session) return;
102
115
  if (affectsCurrentCriterion && session.criterion) {
103
- session.affectedCriteria.add(session.criterion);
104
- const signals = session.signals;
105
- if (signals && signals.length > 0) {
106
- for (const signal of signals) {
107
- const key = contentReadSignalKey(session.criterion, signal);
108
- session.affectedSignals.add(key);
109
- addSkipReason(session.signalSkipReasons, key, reason);
110
- }
111
- } else {
112
- session.unattributedCriteria.add(session.criterion);
113
- addSkipReason(session.criterionSkipReasons, session.criterion, reason);
114
- }
116
+ attributeSkip(session, session.criterion, session.signals, reason);
115
117
  }
116
118
  if (deduplicate) {
117
119
  const key = `${resolve(path2)}\0${reason}\0${errno ?? ""}`;
@@ -168,7 +170,8 @@ function trackContentReads(_repoPath, collect) {
168
170
  affectedSignals: /* @__PURE__ */ new Set(),
169
171
  unattributedCriteria: /* @__PURE__ */ new Set(),
170
172
  criterionSkipReasons: /* @__PURE__ */ new Map(),
171
- signalSkipReasons: /* @__PURE__ */ new Map()
173
+ signalSkipReasons: /* @__PURE__ */ new Map(),
174
+ withheldPaths: /* @__PURE__ */ new Map()
172
175
  };
173
176
  activeSession = session;
174
177
  try {
@@ -217,6 +220,22 @@ function withContentReadSignals(criterionId, signalIds, collect) {
217
220
  function recordContentSkip(path2, reason, deduplicate = false) {
218
221
  recordSkip(path2, reason, void 0, false, deduplicate);
219
222
  }
223
+ function registerWithheldRepoPath(relativePath, reason) {
224
+ const session = activeSession;
225
+ if (!session) return;
226
+ addSkipReason(session.withheldPaths, relativePath, reason);
227
+ }
228
+ function abstainSignalOnWithheldPaths(criterionId, signalId, wouldHaveRead) {
229
+ const session = activeSession;
230
+ if (!session) return false;
231
+ let abstained = false;
232
+ for (const [path2, reasons] of session.withheldPaths) {
233
+ if (!wouldHaveRead(path2)) continue;
234
+ abstained = true;
235
+ for (const reason of reasons) attributeSkip(session, criterionId, [signalId], reason);
236
+ }
237
+ return abstained;
238
+ }
220
239
  function recordFilesystemSkip(path2, error, deniedContext = false, deduplicate = true) {
221
240
  const errno = errnoClass(error);
222
241
  if (!errno) throw error;
@@ -295,6 +314,8 @@ export {
295
314
  withContentReadSignal,
296
315
  withContentReadSignals,
297
316
  recordContentSkip,
317
+ registerWithheldRepoPath,
318
+ abstainSignalOnWithheldPaths,
298
319
  recordFilesystemSkip,
299
320
  readRepoText,
300
321
  readRepoTextPrefix
@@ -5,12 +5,13 @@ import {
5
5
  describeGitFailure,
6
6
  execGit,
7
7
  isExpectedGitAbsence
8
- } from "./chunk-KNQBI3IX.js";
8
+ } from "./chunk-IHT7B6L7.js";
9
9
  import {
10
10
  external_exports
11
- } from "./chunk-J6R5OHN7.js";
11
+ } from "./chunk-GKYTLWQK.js";
12
12
  import {
13
13
  MAX_REPOSITORY_CONTENT_BYTES,
14
+ abstainSignalOnWithheldPaths,
14
15
  classifyAbstentionReason,
15
16
  contentReadSignalKey,
16
17
  init_esm_shims,
@@ -18,11 +19,12 @@ import {
18
19
  readRepoTextPrefix,
19
20
  recordContentSkip,
20
21
  recordFilesystemSkip,
22
+ registerWithheldRepoPath,
21
23
  trackContentReads,
22
24
  withContentReadCriterion,
23
25
  withContentReadSignal,
24
26
  withContentReadSignals
25
- } from "./chunk-V3B2V4WA.js";
27
+ } from "./chunk-PTZU6G4E.js";
26
28
 
27
29
  // src/witan/schemas.ts
28
30
  init_esm_shims();
@@ -313,6 +315,16 @@ var WitanReportCommonSchema = external_exports.object({
313
315
  productDisplayName: external_exports.string().min(1).max(120),
314
316
  repo: WitanRepoRefSchema,
315
317
  rubricVersion: external_exports.string().min(1).max(120),
318
+ // The BEHAVIOURAL identity of the rubric named above, beside its NAME. `rubricVersion` is an
319
+ // identifier the tool's author controls and can leave unchanged across a scoring change (this
320
+ // happened under witan-rubric-v17-2026-07-24 in 0.4.9 — see leaderboard/RUBRIC_CHANGELOG.md).
321
+ // This field is the sha256 of the scoring-relevant output of a fixed committed fixture corpus
322
+ // under that rubric, so two certificates naming the same rubric can be checked for behavioural
323
+ // agreement rather than assumed into it. See src/witan/rubric-fingerprint.ts for exactly what
324
+ // the digest covers, what it excludes, and what it does not claim. Optional and absent —
325
+ // never fabricated — for any rubric this build has no measurement for, and absent on every
326
+ // report emitted before report format 1.2; those artifacts' attestations remain valid.
327
+ rubricBehaviourFingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
316
328
  // Rubric-driven: length matches whatever rubric produced this report, not a fixed enum.
317
329
  criteria: external_exports.array(WitanCriterionScoreSchema).min(1),
318
330
  consumedSignals: external_exports.array(WitanConsumedSignalSummarySchema).optional(),
@@ -366,7 +378,7 @@ var WitanReportSchema = external_exports.preprocess((value) => {
366
378
  }, StrictWitanReportSchema);
367
379
  var WITAN_ATTESTATION_STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
368
380
  var WITAN_ATTESTATION_PREDICATE_TYPE = "https://cejel.dev/attestations/scan/v1";
369
- var WITAN_REPORT_FORMAT_VERSION = "1.1";
381
+ var WITAN_REPORT_FORMAT_VERSION = "1.2";
370
382
  var WitanAttestationOutcomeSchema = external_exports.discriminatedUnion("status", [
371
383
  external_exports.object({
372
384
  status: external_exports.literal("scored"),
@@ -584,6 +596,7 @@ init_esm_shims();
584
596
  // src/witan/certificate-presentation.ts
585
597
  init_esm_shims();
586
598
  var CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE = "Product name and slug are caller context and are excluded from certificate byte-comparison claims.";
599
+ var CERTIFICATE_SCOPE_NOTICE = "This certificate is a statement about the repository tree at the revision pinned above, not about the system that tree is part of. Evidence outside that tree \u2014 for example, a test suite kept in a separate repository, or CI configuration that changes into a different working directory before running its checks \u2014 is neither seen by this scan nor claimed to be absent.";
587
600
  var CERTIFICATE_METRIC_REGISTRY = {
588
601
  test_to_source_ratio: { displayLabel: "Test-to-source file ratio" },
589
602
  coverage_percent: { displayLabel: "Static coverage percentage" },
@@ -1097,6 +1110,7 @@ function buildRelyingPartySummary(report) {
1097
1110
  "Anything absent from the measured evidence was not established: no measurement and no finding are not clearance."
1098
1111
  );
1099
1112
  return {
1113
+ scope: CERTIFICATE_SCOPE_NOTICE,
1100
1114
  examined: `Cejel examined the repository evidence recorded for ${report.productDisplayName}${revision} under ${report.rubricVersion}.`,
1101
1115
  established: `The report established measured results for ${measured.length} of ${applicable.length} applicable rubric dimensions and recorded ${findings} evidence-backed finding${findings === 1 ? "" : "s"}.`,
1102
1116
  notEstablished: gaps.join(" "),
@@ -1309,6 +1323,7 @@ function renderRelyingPartySummary(summary) {
1309
1323
  return `<section class="relying-party-summary" aria-labelledby="relying-party-heading">
1310
1324
  <h2 id="relying-party-heading">How to read this certificate</h2>
1311
1325
  <dl>
1326
+ <div><dt>Scope</dt><dd>${escapeHtml(summary.scope)}</dd></div>
1312
1327
  <div><dt>What was examined</dt><dd>${escapeHtml(summary.examined)}</dd></div>
1313
1328
  <div><dt>What was established</dt><dd>${escapeHtml(summary.established)}</dd></div>
1314
1329
  <div><dt>What was not established</dt><dd>${escapeHtml(summary.notEstablished)}</dd></div>
@@ -2093,13 +2108,14 @@ function buildWitanInputFromRepo(options) {
2093
2108
  const wholesaleAbstainedReadFailureCriteria = [];
2094
2109
  const wholesaleAbstainedCoverageLimitedCriteria = [];
2095
2110
  const partiallyAbstainedReadFailureSignals = [];
2096
- const partiallyAbstainedCoverageLimitedSignals = [];
2111
+ const partiallyAbstainedLargeFileSignals = [];
2112
+ const partiallyAbstainedOtherCoverageLimitedSignals = [];
2097
2113
  const signals = (tracked.value.signals ?? []).map((signal) => {
2098
2114
  if (!affectedCriteria.has(signal.criterionId)) return signal;
2099
2115
  if (tracked.unattributedCriteria.has(signal.criterionId)) {
2100
2116
  const reasons = tracked.criterionSkipReasons.get(signal.criterionId);
2101
- const { insufficientDataReason: insufficientDataReason2 } = classifyAbstentionReason(reasons);
2102
- (insufficientDataReason2 !== void 0 ? wholesaleAbstainedCoverageLimitedCriteria : wholesaleAbstainedReadFailureCriteria).push(signal.criterionId);
2117
+ const { insufficientDataReason } = classifyAbstentionReason(reasons);
2118
+ (insufficientDataReason !== void 0 ? wholesaleAbstainedCoverageLimitedCriteria : wholesaleAbstainedReadFailureCriteria).push(signal.criterionId);
2103
2119
  return wholesaleCriterionAbstention(signal, reasons);
2104
2120
  }
2105
2121
  const prefix = contentReadSignalKey(signal.criterionId, "");
@@ -2118,40 +2134,63 @@ function buildWitanInputFromRepo(options) {
2118
2134
  }
2119
2135
  }
2120
2136
  if (!anythingSurvives) {
2121
- const { insufficientDataReason: insufficientDataReason2 } = classifyAbstentionReason(droppedReasons);
2122
- (insufficientDataReason2 !== void 0 ? wholesaleAbstainedCoverageLimitedCriteria : wholesaleAbstainedReadFailureCriteria).push(signal.criterionId);
2137
+ const { insufficientDataReason } = classifyAbstentionReason(droppedReasons);
2138
+ (insufficientDataReason !== void 0 ? wholesaleAbstainedCoverageLimitedCriteria : wholesaleAbstainedReadFailureCriteria).push(signal.criterionId);
2123
2139
  return wholesaleCriterionAbstention(signal, droppedReasons);
2124
2140
  }
2125
- const { insufficientDataReason } = classifyAbstentionReason(droppedReasons);
2126
- const isReadFailure = insufficientDataReason === void 0;
2141
+ const nameBuckets = /* @__PURE__ */ new Map();
2127
2142
  for (const name of affectedNames) {
2128
- (isReadFailure ? partiallyAbstainedReadFailureSignals : partiallyAbstainedCoverageLimitedSignals).push(`${signal.criterionId}.${name}`);
2129
- }
2143
+ const nameReasons = skipReasonsForSignal(signal.criterionId, name, tracked);
2144
+ const { insufficientDataReason: nameReason } = classifyAbstentionReason(nameReasons);
2145
+ const bucket = nameReason === void 0 ? "read-failure" : nameReason === "too_large" ? "too-large" : "other-coverage";
2146
+ nameBuckets.set(name, bucket);
2147
+ (bucket === "read-failure" ? partiallyAbstainedReadFailureSignals : bucket === "too-large" ? partiallyAbstainedLargeFileSignals : partiallyAbstainedOtherCoverageLimitedSignals).push(`${signal.criterionId}.${name}`);
2148
+ }
2149
+ const distinctBuckets = new Set(nameBuckets.values());
2150
+ const uniformBucket = distinctBuckets.size === 1 ? [...distinctBuckets][0] : void 0;
2151
+ const affectedList = [...affectedNames].sort().join(", ");
2152
+ const notes = uniformBucket === "read-failure" ? `Cejel abstained on ${affectedList} in this criterion because that content could not be read; other signals in this criterion were computed from readable files and are unaffected.` : uniformBucket === "too-large" ? `Cejel declined ${affectedList} in this criterion under the repository content size limit; other signals in this criterion were computed from readable files and are unaffected. This is a disclosed coverage limit, not a read failure.` : uniformBucket === "other-coverage" ? `Cejel declined ${affectedList} in this criterion under an extension exclusion or a non-regular-file skip; other signals in this criterion were computed from readable files and are unaffected. This is a disclosed coverage limit, not a read failure.` : `Cejel abstained on ${affectedList} in this criterion for a mix of reasons \u2014 some content could not be read, some was declined under the repository content size limit or an extension exclusion; other signals in this criterion were computed from readable files and are unaffected.`;
2130
2153
  return {
2131
2154
  ...signal,
2132
2155
  metrics,
2133
- notes: isReadFailure ? `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.` : `Cejel declined ${[...affectedNames].sort().join(", ")} in this criterion under the repository content size limit or an extension exclusion; other signals in this criterion were computed from readable files and are unaffected. This is a disclosed coverage limit, not a read failure.`
2156
+ notes
2134
2157
  };
2135
2158
  });
2136
2159
  const scanLimitations = [...tracked.value.scanLimitations ?? []];
2137
- if (wholesaleAbstainedReadFailureCriteria.length > 0 && scanLimitations.length < 16) {
2138
- scanLimitations.push(
2160
+ const candidateScanLimitations = [];
2161
+ if (wholesaleAbstainedReadFailureCriteria.length > 0) {
2162
+ candidateScanLimitations.push(
2139
2163
  `${wholesaleAbstainedReadFailureCriteria.length} ${wholesaleAbstainedReadFailureCriteria.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.`
2140
2164
  );
2141
2165
  }
2142
- if (wholesaleAbstainedCoverageLimitedCriteria.length > 0 && scanLimitations.length < 16) {
2143
- scanLimitations.push(
2166
+ if (wholesaleAbstainedCoverageLimitedCriteria.length > 0) {
2167
+ candidateScanLimitations.push(
2144
2168
  `${wholesaleAbstainedCoverageLimitedCriteria.length} ${wholesaleAbstainedCoverageLimitedCriteria.length === 1 ? "criterion" : "criteria"} abstained because relevant repository content was declined under the repository content size limit or excluded from scanning by policy, and the affected signal could not be isolated. This is a disclosed coverage limit, not a read failure, and does not affect the composite score.`
2145
2169
  );
2146
2170
  }
2147
- if (partiallyAbstainedReadFailureSignals.length > 0 && scanLimitations.length < 16) {
2148
- scanLimitations.push(
2171
+ if (partiallyAbstainedReadFailureSignals.length > 0) {
2172
+ candidateScanLimitations.push(
2149
2173
  `${partiallyAbstainedReadFailureSignals.length} ${partiallyAbstainedReadFailureSignals.length === 1 ? "signal" : "signals"} abstained because its own repository content could not be read, while other signals in the same criterion were unaffected: ${[...partiallyAbstainedReadFailureSignals].sort().join(", ")}.`
2150
2174
  );
2151
2175
  }
2152
- if (partiallyAbstainedCoverageLimitedSignals.length > 0 && scanLimitations.length < 16) {
2176
+ if (partiallyAbstainedLargeFileSignals.length > 0) {
2177
+ candidateScanLimitations.push(
2178
+ `${partiallyAbstainedLargeFileSignals.length} ${partiallyAbstainedLargeFileSignals.length === 1 ? "signal" : "signals"} declined a large implementation file that exceeded the repository content size limit, while other signals in the same criterion were unaffected: ${[...partiallyAbstainedLargeFileSignals].sort().join(", ")}. That file is undercounted, not scored as clean or absent \u2014 this is a disclosed coverage limit, not a read failure.`
2179
+ );
2180
+ }
2181
+ if (partiallyAbstainedOtherCoverageLimitedSignals.length > 0) {
2182
+ candidateScanLimitations.push(
2183
+ `${partiallyAbstainedOtherCoverageLimitedSignals.length} ${partiallyAbstainedOtherCoverageLimitedSignals.length === 1 ? "signal" : "signals"} declined under an extension exclusion or a non-regular-file skip, while other signals in the same criterion were unaffected: ${[...partiallyAbstainedOtherCoverageLimitedSignals].sort().join(", ")}. This is a disclosed coverage limit, not a read failure.`
2184
+ );
2185
+ }
2186
+ const availableSlots = Math.max(0, 16 - scanLimitations.length);
2187
+ if (candidateScanLimitations.length <= availableSlots) {
2188
+ scanLimitations.push(...candidateScanLimitations);
2189
+ } else if (availableSlots > 0) {
2190
+ const keep = availableSlots - 1;
2191
+ scanLimitations.push(...candidateScanLimitations.slice(0, keep));
2153
2192
  scanLimitations.push(
2154
- `${partiallyAbstainedCoverageLimitedSignals.length} ${partiallyAbstainedCoverageLimitedSignals.length === 1 ? "signal" : "signals"} declined under the repository content size limit or an extension exclusion, while other signals in the same criterion were unaffected: ${[...partiallyAbstainedCoverageLimitedSignals].sort().join(", ")}. This is a disclosed coverage limit, not a read failure.`
2193
+ `${candidateScanLimitations.length - keep} additional disclosed scan limitation(s) omitted at the 16-entry cap \u2014 see contentReadSummary for full counts.`
2155
2194
  );
2156
2195
  }
2157
2196
  return {
@@ -2182,6 +2221,7 @@ function buildWitanInputFromRepoUntracked(options) {
2182
2221
  const usesV22PackageStartEntrypoint = rubricVersion === WITAN_RUBRIC_VERSION_V22 || rubricVersion === WITAN_RUBRIC_VERSION_V23;
2183
2222
  const usesV23CommandCoverage = rubricVersion === WITAN_RUBRIC_VERSION_V23;
2184
2223
  const usesV23PemPrivateKeyGrammar = rubricVersion === WITAN_RUBRIC_VERSION_V23;
2224
+ const usesV23WithheldPathAbstention = rubricVersion === WITAN_RUBRIC_VERSION_V23;
2185
2225
  const structuralArchetype = classifyRepoArchetype(inventoryFiles, rubricVersion);
2186
2226
  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(
2187
2227
  options.repoPath,
@@ -2219,6 +2259,7 @@ function buildWitanInputFromRepoUntracked(options) {
2219
2259
  usesV22PackageStartEntrypoint,
2220
2260
  usesV23CommandCoverage,
2221
2261
  usesV23PemPrivateKeyGrammar,
2262
+ usesV23WithheldPathAbstention,
2222
2263
  reviewableSourceProof,
2223
2264
  inventoryAbsenceContext,
2224
2265
  scanLimitations
@@ -2830,7 +2871,7 @@ function buildNotApplicableSignal(criterionId, reason) {
2830
2871
  notApplicable: true
2831
2872
  };
2832
2873
  }
2833
- function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23CommandCoverage, useV23PemPrivateKeyGrammar, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
2874
+ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, useV23CommandCoverage, useV23PemPrivateKeyGrammar, useV23WithheldPathAbstention, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
2834
2875
  const signals = [];
2835
2876
  const mono = resolveMonorepoContext(repoPath, scanLimitations);
2836
2877
  const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
@@ -2871,6 +2912,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
2871
2912
  useV27Detectors,
2872
2913
  useV20A3ExplicitGaps,
2873
2914
  useV22PackageStartEntrypoint,
2915
+ useV23WithheldPathAbstention,
2874
2916
  inventoryAbsenceContext
2875
2917
  )
2876
2918
  );
@@ -2891,6 +2933,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
2891
2933
  repoPath,
2892
2934
  repoFiles,
2893
2935
  useV27Detectors,
2936
+ useV23WithheldPathAbstention,
2894
2937
  inventoryAbsenceContext
2895
2938
  )
2896
2939
  );
@@ -2993,12 +3036,16 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
2993
3036
  const ciHasTypecheckCommand = ciWorkflows.some(
2994
3037
  (file) => fileContains(repoPath, file, CI_TYPECHECK_COMMAND_PATTERN)
2995
3038
  );
2996
- const verificationScriptCount = [
2997
- packageScripts.has("test") || Boolean(ciTestWorkflow),
2998
- coverageAnalysis.hasCoverageCommand,
2999
- packageScripts.has("lint") || ciHasLintCommand,
3000
- packageScripts.has("typecheck") || ciHasTypecheckCommand
3001
- ].filter(Boolean).length;
3039
+ const verificationScriptComponents = [
3040
+ { label: "test command", count: hasCredibleTestScript(packageScripts) || Boolean(ciTestWorkflow) ? 1 : 0 },
3041
+ { label: "coverage command", count: coverageAnalysis.hasCoverageCommand ? 1 : 0 },
3042
+ { label: "lint command", count: packageScripts.has("lint") || ciHasLintCommand ? 1 : 0 },
3043
+ {
3044
+ label: "type-check command",
3045
+ count: packageScripts.has("typecheck") || ciHasTypecheckCommand ? 1 : 0
3046
+ }
3047
+ ];
3048
+ const verificationScriptCount = verificationScriptComponents.filter((c) => c.count > 0).length;
3002
3049
  const usesLeanBuiltInTestRunner = packageJson != null && [...packageScripts.values()].some(
3003
3050
  (script) => LEAN_TEST_RUNNER_SCRIPT_PATTERN.test(script)
3004
3051
  ) || testFiles.some((file) => fileContains(repoPath, file, LEAN_TEST_RUNNER_IMPORT_PATTERN));
@@ -3062,7 +3109,9 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
3062
3109
  repoPath,
3063
3110
  reviewableSourceProof,
3064
3111
  repoFiles.length,
3065
- inventoryAbsenceContext
3112
+ inventoryAbsenceContext,
3113
+ verificationScriptComponents,
3114
+ runnerFiles.length
3066
3115
  ) : null;
3067
3116
  }
3068
3117
  if (testFiles.length === 0) {
@@ -3162,19 +3211,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
3162
3211
  "saturating_count",
3163
3212
  {
3164
3213
  components: [
3165
- {
3166
- label: "test command",
3167
- count: packageScripts.has("test") || Boolean(ciTestWorkflow) ? 1 : 0
3168
- },
3169
- { label: "coverage command", count: coverageAnalysis.hasCoverageCommand ? 1 : 0 },
3170
- {
3171
- label: "lint command",
3172
- count: packageScripts.has("lint") || ciHasLintCommand ? 1 : 0
3173
- },
3174
- {
3175
- label: "type-check command",
3176
- count: packageScripts.has("typecheck") || ciHasTypecheckCommand ? 1 : 0
3177
- },
3214
+ ...verificationScriptComponents,
3178
3215
  { label: "test-runner configuration", count: runnerFiles.length }
3179
3216
  ]
3180
3217
  }
@@ -3192,7 +3229,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
3192
3229
  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."
3193
3230
  };
3194
3231
  }
3195
- function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inventoryAbsenceContext) {
3232
+ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inventoryAbsenceContext, verificationScriptComponents, runnerFileCount) {
3196
3233
  const sourcePath = proof.firstReadablePath;
3197
3234
  if (!sourcePath) {
3198
3235
  throw new Error("Cejel invariant: a passing reviewable-source proof must have a readable path");
@@ -3244,7 +3281,12 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inve
3244
3281
  metric(
3245
3282
  "verification_script_ratio",
3246
3283
  "Verification script ratio",
3247
- 0,
3284
+ // Zero test FILES does not mean zero verification signal: a lint/typecheck/coverage
3285
+ // script or CI command is a real, independently-observable fact from package.json/CI
3286
+ // content and must be credited here the same way B3 credits it, or A1 flatly
3287
+ // contradicts B3 on the same repository fact (goal_cejel_detector_false_assertions_
3288
+ // 0_4_9_2026-09-14 finding 3).
3289
+ verificationScriptComponents.filter((c) => c.count > 0).length + runnerFileCount,
3248
3290
  4,
3249
3291
  0.25,
3250
3292
  "ratio",
@@ -3252,11 +3294,8 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inve
3252
3294
  "saturating_count",
3253
3295
  {
3254
3296
  components: [
3255
- { label: "test command", count: 0 },
3256
- { label: "coverage command", count: 0 },
3257
- { label: "lint command", count: 0 },
3258
- { label: "type-check command", count: 0 },
3259
- { label: "test-runner configuration", count: 0 }
3297
+ ...verificationScriptComponents,
3298
+ { label: "test-runner configuration", count: runnerFileCount }
3260
3299
  ]
3261
3300
  }
3262
3301
  ),
@@ -3270,7 +3309,7 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inve
3270
3309
  "No concrete test files were present to establish non-hollow test behavior."
3271
3310
  )
3272
3311
  ],
3273
- notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files.`
3312
+ notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files. Verification-script signals (lint/typecheck/coverage/CI commands) are independent of test-file presence and are still credited above where detected.`
3274
3313
  };
3275
3314
  }
3276
3315
  function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV23PemPrivateKeyGrammar, useV18NativeRls, inventoryAbsenceContext, scanLimitations) {
@@ -3730,7 +3769,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = fal
3730
3769
  }
3731
3770
  return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
3732
3771
  }
3733
- function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
3772
+ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, useV23WithheldPathAbstention = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
3734
3773
  const v20DirectHttpEntrypoint = useV20ExplicitGaps ? findV20DirectHttpServerEntrypointFile(repoPath, repoFiles) : null;
3735
3774
  const v22PackageStartHttpEntrypoint = useV22PackageStartEntrypoint ? findV22PackageStartHttpServerEntrypointFile(repoPath, repoFiles) : null;
3736
3775
  const v20RuntimeContainer = useV20ExplicitGaps ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, true) : null;
@@ -3768,9 +3807,11 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3768
3807
  )
3769
3808
  );
3770
3809
  const healthCheck = healthChecks[0];
3810
+ const healthReadinessRouteReads = (file) => isAuthoredProductionPath(file) && isImplementationFile(file);
3811
+ const observabilityDepthReads = (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file);
3771
3812
  const { hasV20HealthOrReadinessRoute, hasV22PackageStartHealthOrReadinessRoute } = withContentReadSignal("A3", "health_readiness_route", () => ({
3772
3813
  hasV20HealthOrReadinessRoute: useV20ExplicitGaps && repoFiles.some(
3773
- (file) => isAuthoredProductionPath(file) && isImplementationFile(file) && fileContains(repoPath, file, V20_HEALTH_OR_READINESS_ROUTE_PATTERN)
3814
+ (file) => healthReadinessRouteReads(file) && fileContains(repoPath, file, V20_HEALTH_OR_READINESS_ROUTE_PATTERN)
3774
3815
  ),
3775
3816
  hasV22PackageStartHealthOrReadinessRoute: v22PackageStartHttpEntrypoint !== null && fileContains(
3776
3817
  repoPath,
@@ -3778,23 +3819,34 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3778
3819
  V20_HEALTH_OR_READINESS_ROUTE_PATTERN
3779
3820
  )
3780
3821
  }));
3822
+ const healthReadinessRouteWithheld = useV23WithheldPathAbstention && useV20ExplicitGaps && abstainSignalOnWithheldPaths("A3", "health_readiness_route", healthReadinessRouteReads);
3781
3823
  const serverEntrypoint = v22PackageStartHttpEntrypoint ?? findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) ?? v20DirectHttpEntrypoint ?? null;
3782
3824
  const runtimeContainer = useV27Detectors ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, useV20ExplicitGaps) : null;
3783
3825
  const errorBoundaries = repoFiles.filter(
3784
3826
  (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
3785
3827
  );
3786
- const errorBoundary = errorBoundaries[0];
3828
+ const expressErrorMiddlewareFile = errorBoundaries.length > 0 ? void 0 : withContentReadSignal(
3829
+ "A3",
3830
+ "prod_readiness_primitives",
3831
+ () => repoFiles.find(
3832
+ (file) => observabilityDepthReads(file) && fileContains(repoPath, file, EXPRESS_ERROR_MIDDLEWARE_PATTERN) && fileContains(repoPath, file, EXPRESS_MIDDLEWARE_REACHABLE_PATTERN)
3833
+ )
3834
+ );
3835
+ const errorBoundary = errorBoundaries[0] ?? expressErrorMiddlewareFile;
3836
+ if (useV23WithheldPathAbstention && errorBoundaries.length === 0) {
3837
+ abstainSignalOnWithheldPaths("A3", "prod_readiness_primitives", observabilityDepthReads);
3838
+ }
3787
3839
  const observabilityCount = withContentReadSignal(
3788
3840
  "A3",
3789
3841
  "observability_depth",
3790
- () => countFilesContaining(
3791
- repoPath,
3792
- repoFiles.filter(
3793
- (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file)
3794
- ),
3795
- /sentry|otel|opentelemetry|datadog|prometheus|metrics|logger|logtail/i
3796
- )
3842
+ () => countFilesMatchingAny(repoPath, repoFiles.filter(observabilityDepthReads), [
3843
+ /sentry|otel|opentelemetry|datadog|prometheus|metrics|logger|logtail|\b(?:correlation|request|trace)[-_]?id\b|\basynclocalstorage\b/i,
3844
+ /\b(?:pino|winston|bunyan|morgan)\b/
3845
+ ])
3797
3846
  );
3847
+ if (useV23WithheldPathAbstention) {
3848
+ abstainSignalOnWithheldPaths("A3", "observability_depth", observabilityDepthReads);
3849
+ }
3798
3850
  const rollbackSafetyCount = withContentReadSignal(
3799
3851
  "A3",
3800
3852
  "rollback_safety_depth",
@@ -3841,8 +3893,17 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3841
3893
  )
3842
3894
  );
3843
3895
  }
3844
- if (errorBoundary)
3845
- evidence.push(evidenceForRelative(repoPath, errorBoundary, "prod_check", "Error boundary"));
3896
+ if (errorBoundary) {
3897
+ evidence.push(
3898
+ errorBoundary === expressErrorMiddlewareFile ? evidenceForRelativeAtLine(
3899
+ repoPath,
3900
+ errorBoundary,
3901
+ "prod_check",
3902
+ "Error boundary",
3903
+ findFirstMatchingLine(repoPath, errorBoundary, EXPRESS_ERROR_MIDDLEWARE_PATTERN)
3904
+ ) : evidenceForRelative(repoPath, errorBoundary, "prod_check", "Error boundary")
3905
+ );
3906
+ }
3846
3907
  if (evidence.length === 0) {
3847
3908
  if (serverEntrypoint) {
3848
3909
  evidence.push(
@@ -3896,7 +3957,12 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3896
3957
  )
3897
3958
  });
3898
3959
  }
3899
- } else if (serverEntrypoint && withContentReadSignal(
3960
+ } else if (serverEntrypoint && // An absence finding is an assertion that Cejel looked and found nothing. When a file this
3961
+ // signal would have read was withheld, Cejel did not look at everything it selected, so the
3962
+ // honest output is the recorded abstention above and no finding — never "declares no health
3963
+ // or readiness route". This is the whole defect: insufficient_data must not be emitted as a
3964
+ // finding of absence.
3965
+ !healthReadinessRouteWithheld && withContentReadSignal(
3900
3966
  "A3",
3901
3967
  "health_readiness_route",
3902
3968
  () => fileContains(
@@ -3950,7 +4016,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3950
4016
  deployConfigs.length > 0,
3951
4017
  envTemplate,
3952
4018
  healthChecks.length > 0,
3953
- errorBoundaries.length > 0
4019
+ errorBoundary !== void 0
3954
4020
  ].filter(Boolean).length,
3955
4021
  6,
3956
4022
  0.55,
@@ -3967,7 +4033,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3967
4033
  { label: "deployment configuration", count: deployConfigs.length > 0 ? 1 : 0 },
3968
4034
  { label: "environment template", count: envTemplate ? 1 : 0 },
3969
4035
  { label: "health or readiness signal", count: healthChecks.length > 0 ? 1 : 0 },
3970
- { label: "error boundary", count: errorBoundaries.length > 0 ? 1 : 0 }
4036
+ { label: "error boundary", count: errorBoundary !== void 0 ? 1 : 0 }
3971
4037
  ]
3972
4038
  }
3973
4039
  ),
@@ -4211,7 +4277,26 @@ function hasDeclaredVersionConstraint(dependency) {
4211
4277
  if (/[~^<>=!]/.test(trimmed)) return true;
4212
4278
  return /^v?\d/.test(trimmed);
4213
4279
  }
4214
- function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
4280
+ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, useV23WithheldPathAbstention, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
4281
+ const claimImplementationReads = claimImplementationFileReads(useV27Detectors);
4282
+ if (useV23WithheldPathAbstention) {
4283
+ abstainSignalOnWithheldPaths(
4284
+ "A5",
4285
+ "claim_match_rate",
4286
+ (file) => claimImplementationReads(file) || isClaimSourceFile(file)
4287
+ );
4288
+ abstainSignalOnWithheldPaths("A5", "claim_source_depth", isClaimSourceFile);
4289
+ abstainSignalOnWithheldPaths(
4290
+ "A5",
4291
+ "reconciliation_artifact_depth",
4292
+ isClaimRealityReconciliationPath
4293
+ );
4294
+ abstainSignalOnWithheldPaths(
4295
+ "A5",
4296
+ "negative_space_documentation",
4297
+ isNegativeSpaceDocCandidate
4298
+ );
4299
+ }
4215
4300
  const reconciliationArtifacts = useV27Detectors ? findClaimRealityReconciliationArtifacts(repoPath, repoFiles) : [];
4216
4301
  const claimSourceFiles = useV27Detectors ? findClaimSourceFiles(repoFiles) : repoFiles.filter(
4217
4302
  (file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
@@ -4257,7 +4342,7 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, inv
4257
4342
  const negativeSpaceCandidates = /* @__PURE__ */ new Set([
4258
4343
  claimDoc,
4259
4344
  ...allDocFiles,
4260
- ...repoFiles.filter((file) => /(^|\/)(SECURITY|THREAT[_-]?MODEL)\.md$/i.test(file))
4345
+ ...repoFiles.filter(isSecurityOrThreatModelDoc)
4261
4346
  ]);
4262
4347
  const negativeSpaceDoc = withContentReadSignal(
4263
4348
  "A5",
@@ -4345,7 +4430,7 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, inv
4345
4430
  function collectB2PrTraceEvidence(repoPath, repoFiles) {
4346
4431
  const workflows = repoFiles.filter(isCiWorkflow);
4347
4432
  const prTemplate = repoFiles.find(
4348
- (file) => /(^|\/)(pull_request_template|PULL_REQUEST_TEMPLATE)\.md$/.test(file)
4433
+ (file) => /(^|\/)(pull_request_template|PULL_REQUEST_TEMPLATE)(\.md$|\/[^/]+\.md$)/.test(file)
4349
4434
  );
4350
4435
  const branchProtectionDoc = repoFiles.find(
4351
4436
  (file) => /branch.*protection|review.*gate|CODEOWNERS/i.test(file)
@@ -4448,7 +4533,9 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
4448
4533
  CI_TYPECHECK_COMMAND_PATTERN,
4449
4534
  CI_BUILD_COMMAND_PATTERN
4450
4535
  ];
4451
- const scriptDepth = ["test", "lint", "typecheck", "build"].filter((s) => scripts.has(s)).length;
4536
+ const scriptDepth = ["test", "lint", "typecheck", "build"].filter(
4537
+ (s) => s === "test" ? hasCredibleTestScript(scripts) : scripts.has(s)
4538
+ ).length;
4452
4539
  const { repoCategoryHits, rootCategoryHits } = withContentReadSignal(
4453
4540
  "B3",
4454
4541
  "ci_script_depth",
@@ -4481,7 +4568,7 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
4481
4568
  components: [
4482
4569
  ...["test", "lint", "typecheck", "build"].map((name) => ({
4483
4570
  label: `package script: ${name === "typecheck" ? "type-check" : name}`,
4484
- count: scripts.has(name) ? 1 : 0
4571
+ count: (name === "test" ? hasCredibleTestScript(scripts) : scripts.has(name)) ? 1 : 0
4485
4572
  })),
4486
4573
  ...CI_COMMAND_CATEGORIES.map((_pattern, index) => ({
4487
4574
  label: `repository CI command: ${["test", "lint", "type-check", "build"][index]}`,
@@ -4840,6 +4927,10 @@ function isTestFile(file, rubricVersion = WITAN_RUBRIC_VERSION) {
4840
4927
  var NAME_SHAPED_TEST_FILE_PATTERN = /^test(?:-[^/]+)?\.[cm]?[jt]sx?$|\.(test|spec)\.[cm]?[jt]sx?$|(^|\/)__tests__\/|(^|\/)test_[^/]*\.py$|(^|\/)tests?\.py$|_test\.go$|(^|\/)test_[^/]*\.(cpp|cc|cxx)$|_(test|tests)\.(cpp|cc|cxx)$|Tests?\.(java|kt)$|(_test|_spec)\.(rs|rb|php|swift|kt)$/;
4841
4928
  var NAME_SHAPED_TEST_FILE_PATTERN_V17 = /\.tftest\.hcl$|\.bats$|(^|\/)test[^/]*\.m$|_test\.m$/i;
4842
4929
  var TEST_RUNNER_PATTERN = /\b(vitest|jest|mocha|ava|tap|pytest|go test|cargo test|rspec|phpunit|gradle test|mvn test|node\s+--test|node:test)\b/i;
4930
+ function hasCredibleTestScript(scripts) {
4931
+ const script = scripts.get("test");
4932
+ return script != null && TEST_RUNNER_PATTERN.test(script);
4933
+ }
4843
4934
  var CI_TEST_COMMAND_PATTERN = /\b(pytest|go\s+test|cargo\s+test|mvn\s+test|gradle\s+test|dotnet\s+test|ctest|make\s+test|jest|vitest|mocha|rspec|phpunit|pnpm\s+(?:run\s+)?test|npm\s+(?:run\s+)?test|yarn\s+(?:run\s+)?test|node\s+--test|node:test)\b|--target[=\s]+test\b/i;
4844
4935
  var CI_LINT_COMMAND_PATTERN = /\b(eslint|pylint|flake8|black|ruff)\b/i;
4845
4936
  var CI_TYPECHECK_COMMAND_PATTERN = /\b(tsc|mypy|pyright)\b/i;
@@ -5013,8 +5104,8 @@ function looksLikeSecretValue(value) {
5013
5104
  return SECRET_VALUE_BRANDED_PATTERN.test(value) || isGenericSecretValue(value);
5014
5105
  }
5015
5106
  var PLACEHOLDER_SECRET_PATTERN = /(?:^|[-_])(?:your|example|sample|placeholder|dummy|changeme|replace(?:-?me)?|redacted)(?:$|[-_])|^x{3,}$|^\.\.\.|^\$\{[^}]+}$|^<[^>]+>$/i;
5016
- var ENV_TEMPLATE_PATTERN = /(^|\/)\.env(?:\.[^/]+)*\.(?:example|sample|template|dist)$/i;
5017
- var ENV_TEMPLATE_PATTERN_V47 = /(^|\/)\.env(?:\.[^/]+)*\.(?:example|sample|template|dist|exemplo|ejemplo|exemple|esempio|beispiel|voorbeeld)$/i;
5107
+ var ENV_TEMPLATE_PATTERN = /(^|\/)\.env(?:\.[^/]+)?\.(?:example|sample|template|dist)$/i;
5108
+ var ENV_TEMPLATE_PATTERN_V47 = /(^|\/)\.env(?:\.[^/]+)?\.(?:example|sample|template|dist|exemplo|ejemplo|exemple|esempio|beispiel|voorbeeld)$/i;
5018
5109
  function isEnvTemplatePath(path, useV47Detectors = false) {
5019
5110
  return (useV47Detectors ? ENV_TEMPLATE_PATTERN_V47 : ENV_TEMPLATE_PATTERN).test(path);
5020
5111
  }
@@ -5031,27 +5122,34 @@ function readGitTrackedFileOutput(repoPath) {
5031
5122
  if (workTree.stdout.trim() !== "true") return { ok: false, reason: "not_a_repo" };
5032
5123
  return execGit(["ls-files", "--cached"], { cwd: repoPath });
5033
5124
  }
5034
- function parseGitTrackedFiles(repoPath, output, includeHardExcluded) {
5125
+ function parseGitTrackedFiles(repoPath, output, includeHardExcluded, registerWithheld = false) {
5035
5126
  const files = [];
5127
+ const withhold = (file, reason) => {
5128
+ if (registerWithheld) registerWithheldRepoPath(file, reason);
5129
+ };
5036
5130
  for (const file of output.trim().split("\n")) {
5037
5131
  if (file.length === 0) continue;
5038
5132
  const fullPath = join(repoPath, file);
5039
5133
  if (!includeHardExcluded && isHardExcludedPath(file)) {
5040
5134
  recordContentSkip(fullPath, "denied_path", true);
5135
+ withhold(file, "denied_path");
5041
5136
  continue;
5042
5137
  }
5043
5138
  try {
5044
5139
  const stat = lstatSync(fullPath);
5045
5140
  if (!stat.isFile()) {
5046
5141
  recordContentSkip(fullPath, "non_regular_file", true);
5142
+ withhold(file, "non_regular_file");
5047
5143
  continue;
5048
5144
  }
5049
5145
  if (stat.size > MAX_REPOSITORY_CONTENT_BYTES) {
5050
5146
  recordContentSkip(fullPath, "too_large", true);
5147
+ withhold(file, "too_large");
5051
5148
  continue;
5052
5149
  }
5053
5150
  } catch (error) {
5054
5151
  recordFilesystemSkip(fullPath, error, false, true);
5152
+ withhold(file, "unreadable");
5055
5153
  continue;
5056
5154
  }
5057
5155
  if (!isPotentialContentPath(file)) {
@@ -5071,7 +5169,7 @@ function collectRepoFileInventory(repoPath) {
5071
5169
  const tracked = readGitTrackedFileOutput(repoPath);
5072
5170
  if (tracked.ok) {
5073
5171
  return {
5074
- repoFiles: parseGitTrackedFiles(repoPath, tracked.stdout, false),
5172
+ repoFiles: parseGitTrackedFiles(repoPath, tracked.stdout, false, true),
5075
5173
  inventoryFiles: parseGitTrackedFiles(repoPath, tracked.stdout, true),
5076
5174
  repoFileInventoryScope: "tracked-scan-eligible",
5077
5175
  scanLimitations: []
@@ -5169,8 +5267,10 @@ function visitRepoDir(repoPath, dirPath, files, rootDevice) {
5169
5267
  visitRepoDir(repoPath, fullPath, files, rootDevice);
5170
5268
  continue;
5171
5269
  }
5270
+ const repoRelativePath = relative(repoPath, fullPath);
5172
5271
  if (!entry.isFile()) {
5173
5272
  recordContentSkip(fullPath, "non_regular_file");
5273
+ registerWithheldRepoPath(repoRelativePath, "non_regular_file");
5174
5274
  continue;
5175
5275
  }
5176
5276
  let size;
@@ -5178,11 +5278,12 @@ function visitRepoDir(repoPath, dirPath, files, rootDevice) {
5178
5278
  size = statSync(fullPath).size;
5179
5279
  } catch (error) {
5180
5280
  recordFilesystemSkip(fullPath, error);
5281
+ registerWithheldRepoPath(repoRelativePath, "unreadable");
5181
5282
  continue;
5182
5283
  }
5183
- const repoRelativePath = relative(repoPath, fullPath);
5184
5284
  if (size > MAX_REPOSITORY_CONTENT_BYTES) {
5185
5285
  recordContentSkip(fullPath, "too_large");
5286
+ registerWithheldRepoPath(repoRelativePath, "too_large");
5186
5287
  continue;
5187
5288
  }
5188
5289
  if (!isPotentialContentPath(repoRelativePath)) {
@@ -5306,6 +5407,9 @@ function measureNonHollowTestShare(repoPath, files, useV17Detectors = false) {
5306
5407
  function countFilesContaining(repoPath, files, pattern) {
5307
5408
  return files.filter((file) => fileContains(repoPath, file, pattern)).length;
5308
5409
  }
5410
+ function countFilesMatchingAny(repoPath, files, patterns) {
5411
+ return files.filter((file) => patterns.some((pattern) => fileContains(repoPath, file, pattern))).length;
5412
+ }
5309
5413
  function countPatternMatches(repoPath, files, pattern) {
5310
5414
  const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
5311
5415
  const matcher = new RegExp(pattern.source, flags);
@@ -5845,22 +5949,33 @@ function isDocumentationOnlyDeployWorkflow(repoPath, file) {
5845
5949
  function findPackagedApplicationPremiseFiles(repoPath, repoFiles) {
5846
5950
  return repoFiles.filter((file) => /(^|\/)package\.json$/.test(file) && isAuthoredProductionPath(file)).filter((file) => packageJsonDescribesPackagedApplication(repoPath, file, repoFiles));
5847
5951
  }
5952
+ function isClaimSourceFile(file) {
5953
+ return /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file);
5954
+ }
5955
+ function isClaimRealityReconciliationPath(file) {
5956
+ return /(^|\/)claim[-_]reality[-_]reconciliation\.(?:md|mdx|json)$/i.test(file);
5957
+ }
5958
+ function isSecurityOrThreatModelDoc(file) {
5959
+ return /(^|\/)(SECURITY|THREAT[_-]?MODEL)\.md$/i.test(file);
5960
+ }
5961
+ function isNegativeSpaceDocCandidate(file) {
5962
+ return isClaimSourceFile(file) || isSecurityOrThreatModelDoc(file);
5963
+ }
5964
+ function claimImplementationFileReads(authoredOnly) {
5965
+ return (file) => isImplementationFile(file) && (!authoredOnly || isAuthoredProductionPath(file));
5966
+ }
5848
5967
  function findClaimSourceFiles(repoFiles) {
5849
- return repoFiles.filter(
5850
- (file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
5851
- ).sort((left, right) => {
5968
+ return repoFiles.filter(isClaimSourceFile).sort((left, right) => {
5852
5969
  const leftRootReadme = /^(?:README|readme)\.md$/.test(left);
5853
5970
  const rightRootReadme = /^(?:README|readme)\.md$/.test(right);
5854
5971
  return Number(rightRootReadme) - Number(leftRootReadme) || left.localeCompare(right);
5855
5972
  });
5856
5973
  }
5857
5974
  function findClaimImplementationFiles(repoFiles, authoredOnly = false) {
5858
- return repoFiles.filter(
5859
- (file) => isImplementationFile(file) && (!authoredOnly || isAuthoredProductionPath(file))
5860
- );
5975
+ return repoFiles.filter(claimImplementationFileReads(authoredOnly));
5861
5976
  }
5862
5977
  function findClaimRealityReconciliationArtifacts(repoPath, repoFiles) {
5863
- return repoFiles.filter((file) => /(^|\/)claim[-_]reality[-_]reconciliation\.(?:md|mdx|json)$/i.test(file)).filter((file) => isAuthenticatedClaimRealityArtifact(repoPath, file));
5978
+ return repoFiles.filter(isClaimRealityReconciliationPath).filter((file) => isAuthenticatedClaimRealityArtifact(repoPath, file));
5864
5979
  }
5865
5980
  function isAuthenticatedClaimRealityArtifact(repoPath, file) {
5866
5981
  const fullPath = join(repoPath, file);
@@ -6916,7 +7031,9 @@ var MAIN_ENTRYPOINT_FILE_PATTERN = /(^|\/)(main|server|app|wsgi|asgi|index|entry
6916
7031
  var RACK_ENTRYPOINT_FILE_PATTERN = /(^|\/)(?:config\.ru|(?:main|server|app|start|run)\.rb)$/i;
6917
7032
  var SERVER_ENTRYPOINT_PATTERN = /\bapp\.listen\s*\(|\bserver\.listen\s*\(\s*(?:PORT|port|\d+)|http\.ListenAndServe\s*\(|http\.ListenAndServeTLS\s*\(|axum::Server(?:::|\.)|actix_web::HttpServer(?:::|\.)|uvicorn\.run\s*\(/;
6918
7033
  var V20_DIRECT_HTTP_SERVER_PATTERN = /\b(?:http|https)\.createServer\s*\([\s\S]{0,1500}?\)\.listen\s*\(\s*(?:PORT|port|\d+)/;
6919
- var V20_HEALTH_OR_READINESS_ROUTE_PATTERN = /["'`]\/(?:health|ready|readiness|live|liveness)["'`]/i;
7034
+ var V20_HEALTH_OR_READINESS_ROUTE_PATTERN = /["'`][\w${}./-]{0,60}\/(?:(?:health|ready|live)z?|readiness|liveness)(?=["'`/])/i;
7035
+ var EXPRESS_ERROR_MIDDLEWARE_PATTERN = /\(\s*(?:err|error)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*(?:req|request)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*(?:res|response)(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*,\s*next(?:\s*:\s*(?:[^,()<]|<[^<>]*>)*)?\s*\)\s*(?:=>|\{)/i;
7036
+ var EXPRESS_MIDDLEWARE_REACHABLE_PATTERN = /\.use\s*\(|\bmodule\.exports\b|\bexport\s+(?:default\b|function\b|const\b)/;
6920
7037
  var RACK_SERVER_ENTRYPOINT_PATTERN = /Rack::(?:Server|Handler(?:::\w+)?)\.(?:start|run)\s*\(/;
6921
7038
  var RACK_CONFIG_RUN_PATTERN = /^\s*run\s+(?:(?:[A-Z]\w*(?:::\w+)*(?:\.new)?|lambda)\b|->)/m;
6922
7039
  var RUNTIME_CONTAINER_COMMAND_PATTERN = /^\s*(?:CMD|ENTRYPOINT)\s+.*(?:\b(?:start|serve|server|qgis|nginx|apache|gunicorn|uvicorn)\b|manage\.py\s+runserver).*$/im;
@@ -8377,6 +8494,22 @@ function discoverIngestInputs(repoPath, maxMatches = Number.POSITIVE_INFINITY) {
8377
8494
  // src/witan/scoring.ts
8378
8495
  init_esm_shims();
8379
8496
 
8497
+ // src/witan/rubric-fingerprint.ts
8498
+ init_esm_shims();
8499
+ import { createHash as createHash3 } from "crypto";
8500
+ var WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS = Object.freeze({
8501
+ [WITAN_RUBRIC_VERSION_V17]: "sha256:ff0f01abe8c12daa60375d0e18c1aca4a1135a55ab2a80b15f4c037bfc18b4b1",
8502
+ [WITAN_RUBRIC_VERSION_V18]: "sha256:abc35df0fdc0749aa941f47b295059d06c96dd9800af7f79cfef2bc8dae917da",
8503
+ [WITAN_RUBRIC_VERSION_V19]: "sha256:f670fa2cc44beef066fa38767bd587e1a16fb28b03bc904756daaea5a6465e96",
8504
+ [WITAN_RUBRIC_VERSION_V20]: "sha256:9276ce2ce865c2d50c52882ee45bbf1df5fe3c8b6038858b4a147f7a4cedfa98",
8505
+ [WITAN_RUBRIC_VERSION_V21]: "sha256:d86411a7fe108bba4d2b8fe7ff9d4f7f69a60f11ca0c5c3fef8e88f66a9af53a",
8506
+ [WITAN_RUBRIC_VERSION_V22]: "sha256:1bb57dd98d29b43b170f3a4f1c8b3b379b433a2aa9df0821ab9d5115f48cfb2f",
8507
+ [WITAN_RUBRIC_VERSION_V23]: "sha256:b558c0aa4b2f85b734bed2e077298fd55851a1086234d678f10a71cf913ec3cf"
8508
+ });
8509
+ function rubricBehaviourFingerprint(rubricVersion) {
8510
+ return WITAN_RUBRIC_BEHAVIOUR_FINGERPRINTS[rubricVersion];
8511
+ }
8512
+
8380
8513
  // src/witan/rubric.ts
8381
8514
  init_esm_shims();
8382
8515
  var WITAN_RUBRIC = [
@@ -8536,6 +8669,13 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC, toolVersi
8536
8669
  productDisplayName: parsedInput.productDisplayName,
8537
8670
  repo: parsedInput.repo,
8538
8671
  rubricVersion: parsedInput.rubricVersion,
8672
+ // Behavioural identity beside the rubric's name, so a reader holding two certificates can
8673
+ // check whether the scoring that produced them agrees instead of inferring it from a string
8674
+ // the tool's author controls. Derived only from parsedInput.rubricVersion, so it is a
8675
+ // property of the (build, rubric) pair and not of this invocation: two runs of the same
8676
+ // installed version at the same revision stay byte-identical. Absent, never fabricated, for
8677
+ // a rubric this build carries no measurement for.
8678
+ ...rubricBehaviourFingerprint(parsedInput.rubricVersion) ? { rubricBehaviourFingerprint: rubricBehaviourFingerprint(parsedInput.rubricVersion) } : {},
8539
8679
  verdict: abstained ? "insufficient_source" : witanVerdictForScore(overallScore),
8540
8680
  codeTrustScore: abstained ? null : codeTrustScore,
8541
8681
  processTrustScore: abstained ? null : processTrustScore,
package/dist/index.js CHANGED
@@ -21,12 +21,12 @@ import {
21
21
  runCejelScan,
22
22
  serializeWitanReport,
23
23
  verifyWitanAttestationBinding
24
- } from "./chunk-6R3VJGJ2.js";
25
- import "./chunk-KNQBI3IX.js";
26
- import "./chunk-J6R5OHN7.js";
24
+ } from "./chunk-TUUNJP27.js";
25
+ import "./chunk-IHT7B6L7.js";
26
+ import "./chunk-GKYTLWQK.js";
27
27
  import {
28
28
  init_esm_shims
29
- } from "./chunk-V3B2V4WA.js";
29
+ } from "./chunk-PTZU6G4E.js";
30
30
 
31
31
  // src/index.ts
32
32
  init_esm_shims();
@@ -76,6 +76,7 @@ function renderTerminalCertificate(summary, report) {
76
76
  lines.splice(
77
77
  3,
78
78
  0,
79
+ `Scope: ${relyingPartySummary.scope}`,
79
80
  `What was examined: ${relyingPartySummary.examined}`,
80
81
  `What was established: ${relyingPartySummary.established}`,
81
82
  `What was not established: ${relyingPartySummary.notEstablished}`,
package/dist/mcp/index.js CHANGED
@@ -5,20 +5,20 @@ import {
5
5
  renderWitanBadgeSvg,
6
6
  renderWitanHtmlReport,
7
7
  runCejelScan
8
- } from "../chunk-6R3VJGJ2.js";
9
- import "../chunk-KNQBI3IX.js";
8
+ } from "../chunk-TUUNJP27.js";
9
+ import "../chunk-IHT7B6L7.js";
10
10
  import {
11
11
  ZodFirstPartyTypeKind,
12
12
  ZodOptional,
13
13
  external_exports,
14
14
  objectType
15
- } from "../chunk-J6R5OHN7.js";
15
+ } from "../chunk-GKYTLWQK.js";
16
16
  import {
17
17
  __commonJS,
18
18
  __export,
19
19
  __toESM,
20
20
  init_esm_shims
21
- } from "../chunk-V3B2V4WA.js";
21
+ } from "../chunk-PTZU6G4E.js";
22
22
 
23
23
  // node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/compile/codegen/code.js
24
24
  var require_code = __commonJS({
@@ -3,18 +3,18 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  listCejelLlmPackFiles
6
- } from "../../chunk-ERNYMFH4.js";
6
+ } from "../../chunk-IN2LPGGU.js";
7
7
  import {
8
8
  execGit
9
- } from "../../chunk-KNQBI3IX.js";
9
+ } from "../../chunk-IHT7B6L7.js";
10
10
  import {
11
11
  require_typescript
12
- } from "../../chunk-ZPQOZLB7.js";
12
+ } from "../../chunk-IQ42TE5H.js";
13
13
  import {
14
14
  __toESM,
15
15
  init_esm_shims,
16
16
  readRepoText
17
- } from "../../chunk-V3B2V4WA.js";
17
+ } from "../../chunk-PTZU6G4E.js";
18
18
 
19
19
  // src/packs/d-series/index.ts
20
20
  init_esm_shims();
@@ -3,15 +3,15 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  external_exports
6
- } from "../../chunk-J6R5OHN7.js";
6
+ } from "../../chunk-GKYTLWQK.js";
7
7
  import {
8
8
  require_typescript
9
- } from "../../chunk-ZPQOZLB7.js";
9
+ } from "../../chunk-IQ42TE5H.js";
10
10
  import {
11
11
  __toESM,
12
12
  init_esm_shims,
13
13
  readRepoText
14
- } from "../../chunk-V3B2V4WA.js";
14
+ } from "../../chunk-PTZU6G4E.js";
15
15
 
16
16
  // src/packs/decision-contracts/index.ts
17
17
  init_esm_shims();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cejel/cejel",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
4
4
  "mcpName": "io.github.BargLabs/cejel",
5
5
  "private": false,
6
6
  "publishConfig": {