@cejel/cejel 0.4.9 → 0.4.11

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
@@ -36,7 +36,10 @@ untouched holdout, every preregistered gate to pass, and a separately recorded p
36
36
 
37
37
  ### Measured recall (bounded)
38
38
 
39
- > **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,
40
43
  > preregistered, in-scope held-out fixture set of 30 named defects, the released public default
41
44
  > (v17, tree `a857f0b3`) cited **16/30**, two-sided 95% Wilson interval **[36.1%, 69.8%]**; the
42
45
  > prospective v22 detector (commit `8a289ea`, tree `10960a03`) cited **24/30**, two-sided 95%
@@ -89,7 +92,7 @@ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
89
92
  ```
90
93
 
91
94
  > **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
92
- > 0.4.9 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
95
+ > 0.4.11 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
93
96
  > signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
94
97
  > it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
95
98
  > GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM and an
@@ -112,7 +115,7 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
112
115
  npx @cejel/cejel@latest .
113
116
  ```
114
117
 
115
- > **Distribution note:** Cejel `0.4.9` is the coordinated release version for npm,
118
+ > **Distribution note:** Cejel `0.4.11` is the coordinated release version for npm,
116
119
  > standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry.
117
120
 
118
121
  `npx` can reuse a stale cached package. Force the current npm release with the `@latest`
@@ -174,16 +177,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
174
177
  This is cryptographically signed provenance. It is distinct from Apple Developer ID or
175
178
  Microsoft Authenticode code-signing.
176
179
 
177
- **Docker / OCI.** The current container release is `0.4.9`:
180
+ **Docker / OCI.** The current container release is `0.4.11`:
178
181
 
179
182
  ```bash
180
- docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.4.9
183
+ docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.4.11
181
184
  ```
182
185
 
183
186
  The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
184
187
 
185
188
  ```bash
186
- docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.4.9 .
189
+ docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.4.11 .
187
190
  ```
188
191
 
189
192
  The OCI image carries an SBOM, maximum-mode build provenance, and a signed registry
@@ -513,7 +516,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
513
516
  the host path with the repository OpenClaw should allow Cejel to read:
514
517
 
515
518
  ```bash
516
- 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.9"]}'
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.11"]}'
517
520
  npx -y openclaw@latest mcp doctor cejel-oci --probe
518
521
  ```
519
522
 
@@ -5,20 +5,20 @@ import {
5
5
  deriveProductIdentity,
6
6
  runCejelScan,
7
7
  serializeWitanReport
8
- } from "../chunk-TUUNJP27.js";
8
+ } from "../chunk-UBCIGOIP.js";
9
9
  import {
10
10
  listCejelLlmPackFiles
11
- } from "../chunk-IN2LPGGU.js";
12
- import "../chunk-IHT7B6L7.js";
11
+ } from "../chunk-M6YYPJSN.js";
12
+ import "../chunk-63XQDYQ5.js";
13
13
  import {
14
14
  external_exports
15
- } from "../chunk-GKYTLWQK.js";
15
+ } from "../chunk-FSMZV3Y6.js";
16
16
  import {
17
17
  init_esm_shims,
18
18
  readRepoText,
19
19
  recordContentSkip,
20
20
  recordFilesystemSkip
21
- } from "../chunk-PTZU6G4E.js";
21
+ } from "../chunk-O3MHSK25.js";
22
22
 
23
23
  // src/calibration/llm-detector.ts
24
24
  init_esm_shims();
@@ -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-PTZU6G4E.js";
6
+ } from "./chunk-O3MHSK25.js";
7
7
 
8
8
  // src/witan/git-exec.ts
9
9
  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-PTZU6G4E.js";
7
+ } from "./chunk-O3MHSK25.js";
8
8
 
9
9
  // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
10
10
  var external_exports = {};
@@ -5,10 +5,10 @@ import {
5
5
  describeGitFailure,
6
6
  execGit,
7
7
  isExpectedGitAbsence
8
- } from "./chunk-IHT7B6L7.js";
8
+ } from "./chunk-63XQDYQ5.js";
9
9
  import {
10
10
  init_esm_shims
11
- } from "./chunk-PTZU6G4E.js";
11
+ } from "./chunk-O3MHSK25.js";
12
12
 
13
13
  // src/packs/llm/files.ts
14
14
  init_esm_shims();
@@ -171,7 +171,8 @@ function trackContentReads(_repoPath, collect) {
171
171
  unattributedCriteria: /* @__PURE__ */ new Set(),
172
172
  criterionSkipReasons: /* @__PURE__ */ new Map(),
173
173
  signalSkipReasons: /* @__PURE__ */ new Map(),
174
- withheldPaths: /* @__PURE__ */ new Map()
174
+ withheldPaths: /* @__PURE__ */ new Map(),
175
+ withheldPathIntersections: /* @__PURE__ */ new Map()
175
176
  };
176
177
  activeSession = session;
177
178
  try {
@@ -225,16 +226,42 @@ function registerWithheldRepoPath(relativePath, reason) {
225
226
  if (!session) return;
226
227
  addSkipReason(session.withheldPaths, relativePath, reason);
227
228
  }
228
- function abstainSignalOnWithheldPaths(criterionId, signalId, wouldHaveRead) {
229
+ function withheldRepoPaths() {
230
+ const session = activeSession;
231
+ if (!session) return [];
232
+ return [...session.withheldPaths].map(([path2, reasons]) => {
233
+ const intersection = session.withheldPathIntersections.get(path2);
234
+ return {
235
+ path: path2,
236
+ reasons,
237
+ signalsAdmitting: intersection ? [...intersection.signalsAdmitting].sort() : [],
238
+ actedOn: intersection?.actedOn ?? false
239
+ };
240
+ }).sort((left, right) => left.path.localeCompare(right.path));
241
+ }
242
+ function abstainSignalOnWithheldPaths(criterionId, signalId, wouldHaveRead, act) {
229
243
  const session = activeSession;
230
244
  if (!session) return false;
231
- let abstained = false;
245
+ let admitted = false;
246
+ const signalKey = `${criterionId}.${signalId}`;
232
247
  for (const [path2, reasons] of session.withheldPaths) {
233
248
  if (!wouldHaveRead(path2)) continue;
234
- abstained = true;
235
- for (const reason of reasons) attributeSkip(session, criterionId, [signalId], reason);
249
+ admitted = true;
250
+ const existing = session.withheldPathIntersections.get(path2);
251
+ if (existing) {
252
+ existing.signalsAdmitting.add(signalKey);
253
+ if (act) existing.actedOn = true;
254
+ } else {
255
+ session.withheldPathIntersections.set(path2, {
256
+ signalsAdmitting: /* @__PURE__ */ new Set([signalKey]),
257
+ actedOn: act
258
+ });
259
+ }
260
+ if (act) {
261
+ for (const reason of reasons) attributeSkip(session, criterionId, [signalId], reason);
262
+ }
236
263
  }
237
- return abstained;
264
+ return admitted && act;
238
265
  }
239
266
  function recordFilesystemSkip(path2, error, deniedContext = false, deduplicate = true) {
240
267
  const errno = errnoClass(error);
@@ -315,6 +342,7 @@ export {
315
342
  withContentReadSignals,
316
343
  recordContentSkip,
317
344
  registerWithheldRepoPath,
345
+ withheldRepoPaths,
318
346
  abstainSignalOnWithheldPaths,
319
347
  recordFilesystemSkip,
320
348
  readRepoText,
@@ -7,7 +7,7 @@ import {
7
7
  __filename,
8
8
  __require,
9
9
  init_esm_shims
10
- } from "./chunk-PTZU6G4E.js";
10
+ } from "./chunk-O3MHSK25.js";
11
11
 
12
12
  // node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/typescript.js
13
13
  var require_typescript = __commonJS({
@@ -5,10 +5,10 @@ import {
5
5
  describeGitFailure,
6
6
  execGit,
7
7
  isExpectedGitAbsence
8
- } from "./chunk-IHT7B6L7.js";
8
+ } from "./chunk-63XQDYQ5.js";
9
9
  import {
10
10
  external_exports
11
- } from "./chunk-GKYTLWQK.js";
11
+ } from "./chunk-FSMZV3Y6.js";
12
12
  import {
13
13
  MAX_REPOSITORY_CONTENT_BYTES,
14
14
  abstainSignalOnWithheldPaths,
@@ -23,8 +23,9 @@ import {
23
23
  trackContentReads,
24
24
  withContentReadCriterion,
25
25
  withContentReadSignal,
26
- withContentReadSignals
27
- } from "./chunk-PTZU6G4E.js";
26
+ withContentReadSignals,
27
+ withheldRepoPaths
28
+ } from "./chunk-O3MHSK25.js";
28
29
 
29
30
  // src/witan/schemas.ts
30
31
  init_esm_shims();
@@ -226,6 +227,12 @@ var WitanContentReadSummarySchema = external_exports.object({
226
227
  });
227
228
  }
228
229
  });
230
+ var WitanWithheldPathSchema = external_exports.object({
231
+ path: external_exports.string().min(1).max(700),
232
+ reason: WitanContentReadSkipReasonSchema,
233
+ signalsAdmitting: external_exports.array(external_exports.string().min(1).max(160)),
234
+ actedOn: external_exports.boolean()
235
+ }).strict();
229
236
  var WitanRepoRefSchema = external_exports.object({
230
237
  // Retained for backward-compatible verification of reports emitted through v0.4.0.
231
238
  // New reports omit the checkout path so their bytes are independent of run location.
@@ -243,7 +250,11 @@ var WitanReportInputSchema = external_exports.object({
243
250
  archetype: WitanRepoArchetypeSchema.optional(),
244
251
  insufficientSourceReason: external_exports.string().min(1).max(2e3).optional(),
245
252
  scanLimitations: external_exports.array(external_exports.string().min(1).max(1e3)).max(16).default([]),
246
- contentReadSummary: WitanContentReadSummarySchema.optional()
253
+ contentReadSummary: WitanContentReadSummarySchema.optional(),
254
+ // Always populated by buildWitanInputFromRepo, including as [] when nothing was withheld —
255
+ // presence with an empty array is itself the "nothing withheld" statement, distinct from
256
+ // absence (a report predating this field, which does not claim one way or the other).
257
+ withheldPaths: external_exports.array(WitanWithheldPathSchema).default([])
247
258
  }).strict();
248
259
  var WitanCriterionScoreSchema = external_exports.object({
249
260
  id: WitanCriterionIdSchema,
@@ -331,6 +342,10 @@ var WitanReportCommonSchema = external_exports.object({
331
342
  archetype: WitanRepoArchetypeSchema.optional(),
332
343
  scanLimitations: external_exports.array(external_exports.string().min(1).max(1e3)).max(16).optional(),
333
344
  contentReadSummary: WitanContentReadSummarySchema.optional(),
345
+ // Additive-optional (report format 1.3): absent means a report produced before this field
346
+ // existed, never "nothing withheld" — createWitanReport always sets it (possibly to []) on any
347
+ // report it builds, so absence here is a legacy-report signal, not a claim about the scan.
348
+ withheldPaths: external_exports.array(WitanWithheldPathSchema).optional(),
334
349
  // The @cejel/cejel version that produced this report. Absent on reports from before this
335
350
  // field existed and on any caller that does not supply one (createWitanReport never defaults
336
351
  // it). Constant within an installed version, so re-running the same version on the same
@@ -378,7 +393,7 @@ var WitanReportSchema = external_exports.preprocess((value) => {
378
393
  }, StrictWitanReportSchema);
379
394
  var WITAN_ATTESTATION_STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
380
395
  var WITAN_ATTESTATION_PREDICATE_TYPE = "https://cejel.dev/attestations/scan/v1";
381
- var WITAN_REPORT_FORMAT_VERSION = "1.2";
396
+ var WITAN_REPORT_FORMAT_VERSION = "1.3";
382
397
  var WitanAttestationOutcomeSchema = external_exports.discriminatedUnion("status", [
383
398
  external_exports.object({
384
399
  status: external_exports.literal("scored"),
@@ -1066,6 +1081,44 @@ function buildNextSteps(report, unmeasured, notApplicable, a1, coverageMetric) {
1066
1081
  }
1067
1082
  return steps;
1068
1083
  }
1084
+ var WITHHELD_PATH_READ_FAILURE_REASONS = /* @__PURE__ */ new Set([
1085
+ "unreadable",
1086
+ "denied_path"
1087
+ ]);
1088
+ var WITHHELD_PATH_REASON_LABELS = {
1089
+ too_large: "too large",
1090
+ non_regular_file: "not a regular file",
1091
+ unreadable: "unreadable",
1092
+ denied_path: "denied path",
1093
+ excluded_by_extension: "excluded by extension"
1094
+ };
1095
+ function describeWithheldReasons(entries) {
1096
+ return [...new Set(entries.map((entry) => WITHHELD_PATH_REASON_LABELS[entry.reason]))].sort().join(", ");
1097
+ }
1098
+ function buildWithheldPathsGapSentence(report) {
1099
+ const withheldPaths = report.withheldPaths;
1100
+ if (withheldPaths === void 0) return void 0;
1101
+ if (withheldPaths.length === 0) {
1102
+ return "No content was withheld from any signal.";
1103
+ }
1104
+ const count = withheldPaths.length;
1105
+ const entryWord = count === 1 ? "content entry was" : "content entries were";
1106
+ const pronoun = count === 1 ? "it" : "them";
1107
+ const reasonLabel = describeWithheldReasons(withheldPaths);
1108
+ const intersecting = withheldPaths.filter((entry) => entry.signalsAdmitting.length > 0);
1109
+ if (intersecting.length === 0) {
1110
+ return `${count} ${entryWord} withheld (${reasonLabel}); no signal that would have read ${pronoun} selected ${pronoun}, so no result depends on ${pronoun}.`;
1111
+ }
1112
+ const signals = [...new Set(intersecting.flatMap((entry) => entry.signalsAdmitting))].sort();
1113
+ const signalList = signals.join(", ");
1114
+ const actedOn = intersecting.some((entry) => entry.actedOn);
1115
+ if (actedOn) {
1116
+ const qualifier = intersecting.every((entry) => !WITHHELD_PATH_READ_FAILURE_REASONS.has(entry.reason)) ? "a disclosed coverage limit, not a read failure" : "content Cejel could not read";
1117
+ return `${count} ${entryWord} withheld (${reasonLabel}); ${signalList} abstained on ${pronoun} rather than assert an absence \u2014 ${qualifier}.`;
1118
+ }
1119
+ const reportsOnWhatItRead = signals.length === 1 ? "that signal reports on what it read" : "these signals report on what they read";
1120
+ return `${count} ${entryWord} withheld (${reasonLabel}); ${signalList} would have read ${pronoun}. Under this rubric ${reportsOnWhatItRead}; the prospective v23 rubric abstains instead.`;
1121
+ }
1069
1122
  function buildRelyingPartySummary(report) {
1070
1123
  const applicable = report.criteria.filter((criterion) => criterion.status !== "not_applicable");
1071
1124
  const measured = applicable.filter((criterion) => criterion.status !== "insufficient_data");
@@ -1106,6 +1159,10 @@ function buildRelyingPartySummary(report) {
1106
1159
  `${skipped} content entr${skipped === 1 ? "y was" : "ies were"} skipped for the reasons itemized in the certificate.`
1107
1160
  );
1108
1161
  }
1162
+ const withheldPathsSentence = buildWithheldPathsGapSentence(report);
1163
+ if (withheldPathsSentence !== void 0) {
1164
+ gaps.push(withheldPathsSentence);
1165
+ }
1109
1166
  gaps.push(
1110
1167
  "Anything absent from the measured evidence was not established: no measurement and no finding are not clearance."
1111
1168
  );
@@ -2200,6 +2257,14 @@ function buildWitanInputFromRepo(options) {
2200
2257
  contentReadSummary: tracked.summary
2201
2258
  };
2202
2259
  }
2260
+ function buildWithheldPathsReportField() {
2261
+ return withheldRepoPaths().map(({ path, reasons, signalsAdmitting, actedOn }) => ({
2262
+ path,
2263
+ reason: [...reasons].sort()[0],
2264
+ signalsAdmitting: [...signalsAdmitting],
2265
+ actedOn
2266
+ }));
2267
+ }
2203
2268
  function buildWitanInputFromRepoUntracked(options) {
2204
2269
  assertRepoPathExists(options.repoPath);
2205
2270
  const generatedAt = options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
@@ -2283,6 +2348,9 @@ function buildWitanInputFromRepoUntracked(options) {
2283
2348
  archetype: archetype.archetype,
2284
2349
  ...ignoredTargetReason ?? archetype.insufficientSourceReason ? { insufficientSourceReason: ignoredTargetReason ?? archetype.insufficientSourceReason } : {},
2285
2350
  ...scanLimitations.size > 0 ? { scanLimitations: [...scanLimitations] } : {},
2351
+ // Always present, even as [] — presence is the "nothing was withheld" statement itself, not
2352
+ // merely the absence of a limitation (goal_cejel_withheld_paths_always_disclosed_2026-09-22).
2353
+ withheldPaths: buildWithheldPathsReportField(),
2286
2354
  signals: [
2287
2355
  ...coreSignals,
2288
2356
  ...(options.domainCollectors ?? []).map((collect) => collect(options.repoPath, repoFiles)),
@@ -3807,7 +3875,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3807
3875
  )
3808
3876
  );
3809
3877
  const healthCheck = healthChecks[0];
3810
- const healthReadinessRouteReads = (file) => isAuthoredProductionPath(file) && isImplementationFile(file);
3878
+ const healthReadinessRouteReads = (file) => isAuthoredProductionPath(file) && (isImplementationFile(file) || HEALTH_ROUTE_FUNCTIONS_DIR_PATTERN.test(file));
3811
3879
  const observabilityDepthReads = (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && isImplementationFile(file);
3812
3880
  const { hasV20HealthOrReadinessRoute, hasV22PackageStartHealthOrReadinessRoute } = withContentReadSignal("A3", "health_readiness_route", () => ({
3813
3881
  hasV20HealthOrReadinessRoute: useV20ExplicitGaps && repoFiles.some(
@@ -3819,22 +3887,33 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3819
3887
  V20_HEALTH_OR_READINESS_ROUTE_PATTERN
3820
3888
  )
3821
3889
  }));
3822
- const healthReadinessRouteWithheld = useV23WithheldPathAbstention && useV20ExplicitGaps && abstainSignalOnWithheldPaths("A3", "health_readiness_route", healthReadinessRouteReads);
3890
+ const healthReadinessRouteWithheld = useV20ExplicitGaps && abstainSignalOnWithheldPaths(
3891
+ "A3",
3892
+ "health_readiness_route",
3893
+ healthReadinessRouteReads,
3894
+ useV23WithheldPathAbstention
3895
+ );
3823
3896
  const serverEntrypoint = v22PackageStartHttpEntrypoint ?? findServerEntrypointFile(repoPath, repoFiles, useV27Detectors) ?? v20DirectHttpEntrypoint ?? null;
3824
3897
  const runtimeContainer = useV27Detectors ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, useV20ExplicitGaps) : null;
3825
3898
  const errorBoundaries = repoFiles.filter(
3826
3899
  (file) => (!useV27Detectors || isAuthoredProductionPath(file)) && /error-boundary|error\.(tsx|jsx|ts|js)$/.test(file)
3827
3900
  );
3901
+ const errorBoundaryFileReads = (file) => observabilityDepthReads(file) || (!useV27Detectors || isAuthoredProductionPath(file)) && /(^|\/)server\//.test(file) && /\.(?:mjs|cjs)$/.test(file);
3828
3902
  const expressErrorMiddlewareFile = errorBoundaries.length > 0 ? void 0 : withContentReadSignal(
3829
3903
  "A3",
3830
3904
  "prod_readiness_primitives",
3831
3905
  () => repoFiles.find(
3832
- (file) => observabilityDepthReads(file) && fileContains(repoPath, file, EXPRESS_ERROR_MIDDLEWARE_PATTERN) && fileContains(repoPath, file, EXPRESS_MIDDLEWARE_REACHABLE_PATTERN)
3906
+ (file) => errorBoundaryFileReads(file) && fileContains(repoPath, file, EXPRESS_ERROR_MIDDLEWARE_PATTERN) && fileMatchesOutsideLineComments(repoPath, file, EXPRESS_MIDDLEWARE_REACHABLE_PATTERN)
3833
3907
  )
3834
3908
  );
3835
3909
  const errorBoundary = errorBoundaries[0] ?? expressErrorMiddlewareFile;
3836
- if (useV23WithheldPathAbstention && errorBoundaries.length === 0) {
3837
- abstainSignalOnWithheldPaths("A3", "prod_readiness_primitives", observabilityDepthReads);
3910
+ if (errorBoundaries.length === 0) {
3911
+ abstainSignalOnWithheldPaths(
3912
+ "A3",
3913
+ "prod_readiness_primitives",
3914
+ errorBoundaryFileReads,
3915
+ useV23WithheldPathAbstention
3916
+ );
3838
3917
  }
3839
3918
  const observabilityCount = withContentReadSignal(
3840
3919
  "A3",
@@ -3844,9 +3923,12 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
3844
3923
  /\b(?:pino|winston|bunyan|morgan)\b/
3845
3924
  ])
3846
3925
  );
3847
- if (useV23WithheldPathAbstention) {
3848
- abstainSignalOnWithheldPaths("A3", "observability_depth", observabilityDepthReads);
3849
- }
3926
+ abstainSignalOnWithheldPaths(
3927
+ "A3",
3928
+ "observability_depth",
3929
+ observabilityDepthReads,
3930
+ useV23WithheldPathAbstention
3931
+ );
3850
3932
  const rollbackSafetyCount = withContentReadSignal(
3851
3933
  "A3",
3852
3934
  "rollback_safety_depth",
@@ -4279,24 +4361,30 @@ function hasDeclaredVersionConstraint(dependency) {
4279
4361
  }
4280
4362
  function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, useV23WithheldPathAbstention, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
4281
4363
  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
- }
4364
+ abstainSignalOnWithheldPaths(
4365
+ "A5",
4366
+ "claim_match_rate",
4367
+ (file) => claimImplementationReads(file) || isClaimSourceFile(file),
4368
+ useV23WithheldPathAbstention
4369
+ );
4370
+ abstainSignalOnWithheldPaths(
4371
+ "A5",
4372
+ "claim_source_depth",
4373
+ isClaimSourceFile,
4374
+ useV23WithheldPathAbstention
4375
+ );
4376
+ abstainSignalOnWithheldPaths(
4377
+ "A5",
4378
+ "reconciliation_artifact_depth",
4379
+ isClaimRealityReconciliationPath,
4380
+ useV23WithheldPathAbstention
4381
+ );
4382
+ abstainSignalOnWithheldPaths(
4383
+ "A5",
4384
+ "negative_space_documentation",
4385
+ isNegativeSpaceDocCandidate,
4386
+ useV23WithheldPathAbstention
4387
+ );
4300
4388
  const reconciliationArtifacts = useV27Detectors ? findClaimRealityReconciliationArtifacts(repoPath, repoFiles) : [];
4301
4389
  const claimSourceFiles = useV27Detectors ? findClaimSourceFiles(repoFiles) : repoFiles.filter(
4302
4390
  (file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
@@ -7031,9 +7119,10 @@ var MAIN_ENTRYPOINT_FILE_PATTERN = /(^|\/)(main|server|app|wsgi|asgi|index|entry
7031
7119
  var RACK_ENTRYPOINT_FILE_PATTERN = /(^|\/)(?:config\.ru|(?:main|server|app|start|run)\.rb)$/i;
7032
7120
  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*\(/;
7033
7121
  var V20_DIRECT_HTTP_SERVER_PATTERN = /\b(?:http|https)\.createServer\s*\([\s\S]{0,1500}?\)\.listen\s*\(\s*(?:PORT|port|\d+)/;
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)/;
7122
+ var HEALTH_ROUTE_FUNCTIONS_DIR_PATTERN = /(^|\/)functions\/.*\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|cpp|cc|cxx|c|h|hpp|kt|swift|php)$/;
7123
+ var V20_HEALTH_OR_READINESS_ROUTE_PATTERN = /["'`](?:[\w${}./-]{0,60}\/[_-]?(?:(?:health|ready|live)z?|readiness|liveness|up)(?=["'`/])|(?:healthz|readiness|liveness)(?=["'`]))|@Get\s*\(\s*(["'`])health\1\s*\)/i;
7124
+ 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;
7125
+ var EXPRESS_MIDDLEWARE_REACHABLE_PATTERN = /\.use\s*\(|\bmodule\.exports\b|\bexport\s+(?:default\b|function\b|const\b|class\b)/;
7037
7126
  var RACK_SERVER_ENTRYPOINT_PATTERN = /Rack::(?:Server|Handler(?:::\w+)?)\.(?:start|run)\s*\(/;
7038
7127
  var RACK_CONFIG_RUN_PATTERN = /^\s*run\s+(?:(?:[A-Z]\w*(?:::\w+)*(?:\.new)?|lambda)\b|->)/m;
7039
7128
  var RUNTIME_CONTAINER_COMMAND_PATTERN = /^\s*(?:CMD|ENTRYPOINT)\s+.*(?:\b(?:start|serve|server|qgis|nginx|apache|gunicorn|uvicorn)\b|manage\.py\s+runserver).*$/im;
@@ -7225,6 +7314,12 @@ function fileContains(repoPath, file, pattern) {
7225
7314
  if (!isRegularFile(fullPath)) return false;
7226
7315
  return pattern.test(readRepoText(fullPath, "utf8"));
7227
7316
  }
7317
+ function fileMatchesOutsideLineComments(repoPath, file, pattern) {
7318
+ const fullPath = join(repoPath, file);
7319
+ if (!isRegularFile(fullPath)) return false;
7320
+ const withoutLineComments = readRepoText(fullPath, "utf8").replace(/(?<!:)\/\/.*$/gm, "");
7321
+ return pattern.test(withoutLineComments);
7322
+ }
7228
7323
  var PEM_PRIVATE_KEY_ASSIGNMENT_PATTERN = /(?:["'])?([A-Za-z_$][\w$.-]*)(?:["'])?\s*[:=]\s*(?:\|[-+0-9]*\s*\r?\n[ \t]*)?["'`]?-----BEGIN((?: (?:RSA|DSA|EC|OPENSSH|ENCRYPTED))? PRIVATE KEY)-----([\s\S]{0,32768}?)-----END\2-----/g;
7229
7324
  var BARE_PEM_PRIVATE_KEY_PATTERN = /["'`]?-----BEGIN((?: (?:RSA|DSA|EC|OPENSSH|ENCRYPTED))? PRIVATE KEY)-----([\s\S]{0,32768}?)-----END\1-----/g;
7230
7325
  var BARE_PEM_KEY_FILE_PATTERN = /(?:^|\/)id_(?:rsa|dsa|ecdsa|ed25519)$|\.(?:pem|key)$/i;
@@ -7308,6 +7403,8 @@ function findCommittedSecretInFile(repoPath, file, useV36Detectors = false, useV
7308
7403
  if (pemSecret) return pemSecret;
7309
7404
  }
7310
7405
  if (useV39Detectors && isEnvHistoryPath(file, useV36Detectors)) {
7406
+ const databaseUrlCredential = findDatabaseUrlUserinfoCredential(secretScanContents);
7407
+ if (databaseUrlCredential) return databaseUrlCredential;
7311
7408
  const weakCredential = findWeakExplicitEnvCredential(secretScanContents);
7312
7409
  if (weakCredential) return weakCredential;
7313
7410
  }
@@ -7421,6 +7518,8 @@ function findSecretFingerprintsInFile(repoPath, file, useV36Detectors = false, u
7421
7518
  if (pemSecret) fingerprints.push(pemSecret.valueFingerprint);
7422
7519
  }
7423
7520
  if (useV39Detectors && isEnvHistoryPath(file, useV36Detectors)) {
7521
+ const databaseUrlCredential = findDatabaseUrlUserinfoCredential(scanContents);
7522
+ if (databaseUrlCredential) fingerprints.push(databaseUrlCredential.valueFingerprint);
7424
7523
  const weakCredential = findWeakExplicitEnvCredential(scanContents);
7425
7524
  if (weakCredential) fingerprints.push(weakCredential.valueFingerprint);
7426
7525
  }
@@ -7531,6 +7630,46 @@ function findWeakExplicitEnvCredential(contents) {
7531
7630
  }
7532
7631
  return null;
7533
7632
  }
7633
+ function findDatabaseUrlUserinfoCredential(contents) {
7634
+ const lines = contents.split(/\r?\n/);
7635
+ for (const [index, line] of lines.entries()) {
7636
+ if (/^\s*#/.test(line)) continue;
7637
+ const match = /^\s*(?:export\s+)?(DATABASE_URL)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s#]+))/i.exec(line);
7638
+ const identifier = match?.[1];
7639
+ const rawValue = match?.[2] ?? match?.[3] ?? match?.[4];
7640
+ if (!identifier || !rawValue) continue;
7641
+ let parsed;
7642
+ try {
7643
+ parsed = new URL(rawValue);
7644
+ } catch {
7645
+ continue;
7646
+ }
7647
+ if (parsed.protocol !== "postgres:" && parsed.protocol !== "postgresql:" || !parsed.username || !parsed.password) {
7648
+ continue;
7649
+ }
7650
+ let password;
7651
+ try {
7652
+ password = decodeURIComponent(parsed.password);
7653
+ } catch {
7654
+ continue;
7655
+ }
7656
+ if (isPlaceholderSecretValue(password)) continue;
7657
+ const characterClasses = [
7658
+ /[a-z]/.test(password) ? "lower" : "",
7659
+ /[A-Z]/.test(password) ? "upper" : "",
7660
+ /\d/.test(password) ? "digit" : "",
7661
+ /[^A-Za-z0-9]/.test(password) ? "symbol" : ""
7662
+ ].filter(Boolean).join("+");
7663
+ return {
7664
+ identifier,
7665
+ line: index + 1,
7666
+ valueLength: password.length,
7667
+ characterClasses: characterClasses || "other",
7668
+ valueFingerprint: createHash2("sha256").update(password).digest("hex")
7669
+ };
7670
+ }
7671
+ return null;
7672
+ }
7534
7673
  function findDefaultAdministrativeCredential(contents) {
7535
7674
  const lines = contents.split(/\r?\n/);
7536
7675
  for (const [index, line] of lines.entries()) {
@@ -7608,6 +7747,12 @@ function collectHistorySecretEvidence(repoPath, excludeHead, currentSecretFinger
7608
7747
  secretMatch = pemMatch;
7609
7748
  }
7610
7749
  }
7750
+ if (!secretMatch && useV39Detectors && isEnvHistoryPath(entry.path, useV36Detectors, useV47Detectors)) {
7751
+ const databaseUrlCredential = findDatabaseUrlUserinfoCredential(scanContents);
7752
+ if (databaseUrlCredential && !currentFingerprints.has(databaseUrlCredential.valueFingerprint)) {
7753
+ secretMatch = databaseUrlCredential;
7754
+ }
7755
+ }
7611
7756
  if (!secretMatch && useV39Detectors && isEnvHistoryPath(entry.path, useV36Detectors, useV47Detectors)) {
7612
7757
  const weakCredential = findWeakExplicitEnvCredential(scanContents);
7613
7758
  if (weakCredential && !currentFingerprints.has(weakCredential.valueFingerprint)) {
@@ -8684,6 +8829,10 @@ function createWitanReport(input, inputSignals, rubric = WITAN_RUBRIC, toolVersi
8684
8829
  ...consumedSignals.length > 0 ? { consumedSignals } : {},
8685
8830
  ...parsedInput.scanLimitations.length > 0 ? { scanLimitations: parsedInput.scanLimitations } : {},
8686
8831
  ...parsedInput.contentReadSummary ? { contentReadSummary: parsedInput.contentReadSummary } : {},
8832
+ // Always set alongside contentReadSummary: parsedInput.withheldPaths defaults to [] on the
8833
+ // input schema, so this is never omitted for a report this build produces — its presence
8834
+ // (even empty) is the disclosure itself (goal_cejel_withheld_paths_always_disclosed_2026-09-22).
8835
+ withheldPaths: parsedInput.withheldPaths,
8687
8836
  // Only surface the full per-category map for rubrics with more than two buckets —
8688
8837
  // the default two-category rubric is fully represented by codeTrustScore/processTrustScore.
8689
8838
  ...!abstained && categoryOrder.length > 2 ? { categoryScores: categoryScoreMap } : {},