@cejel/cejel 0.6.8 → 0.6.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.
@@ -5,7 +5,7 @@ import {
5
5
  describeGitFailure,
6
6
  execGit,
7
7
  isExpectedGitAbsence
8
- } from "./chunk-NIWSYJ6N.js";
8
+ } from "./chunk-2UMHJ23V.js";
9
9
  import {
10
10
  init_esm_shims
11
11
  } from "./chunk-AWLKQUI4.js";
@@ -5,7 +5,7 @@ import {
5
5
  renderWitanBadgeSvg,
6
6
  renderWitanHtmlReport,
7
7
  runCejelScan
8
- } from "./chunk-KJVFCZZ3.js";
8
+ } from "./chunk-4YOIR3E6.js";
9
9
  import {
10
10
  ZodFirstPartyTypeKind,
11
11
  ZodOptional,
package/dist/index.js CHANGED
@@ -3,15 +3,17 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  serveCejelMcpOverStdio
6
- } from "./chunk-KNTBED5T.js";
6
+ } from "./chunk-S5B5KMSO.js";
7
7
  import {
8
8
  CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE,
9
9
  CERTIFICATE_GLOSSARY,
10
10
  EXTERNAL_FINDINGS_DISPLAY_LIMIT,
11
+ METRIC_DERIVED_FINDING_PATTERN,
11
12
  PROSPECTIVE_RUBRIC_NOTICE,
12
13
  WITAN_RUBRIC_VERSION_V22,
13
14
  WitanReportSchema,
14
15
  assertSelectableRubricVersion,
16
+ buildPlainSummary,
15
17
  buildRelyingPartySummary,
16
18
  buildVerdictExplanation,
17
19
  computeApplicableMeasuredCoverage,
@@ -25,10 +27,14 @@ import {
25
27
  formatDimensionsMeasuredSentence,
26
28
  formatExternalSourceLine,
27
29
  formatLowConfidenceSentence,
30
+ formatVerdictCapSentence,
31
+ glossaryEntryForRubric,
28
32
  importedAdjustmentSentence,
29
33
  inheritedFindingNote,
30
34
  inputBindingHeadline,
31
35
  isProspectiveRubricVersion,
36
+ isWitanAbstentionLabel,
37
+ renderWitanAbstentionHeadline,
32
38
  renderWitanBadgeEndpoint,
33
39
  renderWitanBadgeSvg,
34
40
  renderWitanHtmlReport,
@@ -37,9 +43,10 @@ import {
37
43
  serializeWitanReport,
38
44
  stripTerminalControls,
39
45
  verifyRecordedInputDigests,
40
- verifyWitanAttestationBinding
41
- } from "./chunk-KJVFCZZ3.js";
42
- import "./chunk-NIWSYJ6N.js";
46
+ verifyWitanAttestationBinding,
47
+ witanHistoryAbstentionInstruction
48
+ } from "./chunk-4YOIR3E6.js";
49
+ import "./chunk-2UMHJ23V.js";
43
50
  import {
44
51
  external_exports
45
52
  } from "./chunk-AMVP7L26.js";
@@ -61,7 +68,7 @@ init_esm_shims();
61
68
  init_esm_shims();
62
69
  var WITAN_ISSUANCE_STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
63
70
  var WITAN_ISSUANCE_PREDICATE_TYPE = "https://cejel.dev/attestations/issuance/v1";
64
- var WITAN_ISSUANCE_FORMAT_VERSION = "1.0";
71
+ var WITAN_ISSUANCE_FORMAT_VERSION = "1.1";
65
72
  var WITAN_ISSUANCE_SIGNATURE_NAMESPACE = "cejel-issuance";
66
73
  var WITAN_ISSUANCE_PRINCIPAL = "issuance@barglabs.ai";
67
74
  var WITAN_ISSUER_NAME = "Barg Labs Inc.";
@@ -89,6 +96,9 @@ var WitanIssuanceStatementSchema = external_exports.object({
89
96
  toolVersion: external_exports.string().min(1).max(80),
90
97
  rubricVersion: external_exports.string().min(1).max(120),
91
98
  rubricBehaviourFingerprint: external_exports.string().regex(/^sha256:[a-f0-9]{64}$/).optional(),
99
+ // The scan time the reproduction replayed: the attested scan's predicate.generatedAt
100
+ // (issuance format 1.1 and later). Optional on read, so a 1.0 issuance still verifies.
101
+ generatedAt: external_exports.string().datetime({ offset: true }).optional(),
92
102
  reportByteIdentical: external_exports.literal(true)
93
103
  }).strict(),
94
104
  issuedAt: external_exports.string().datetime({ offset: true }),
@@ -153,6 +163,7 @@ function createWitanIssuanceStatement(report, options) {
153
163
  toolVersion: options.toolVersion,
154
164
  rubricVersion: report.rubricVersion,
155
165
  ...report.rubricBehaviourFingerprint ? { rubricBehaviourFingerprint: report.rubricBehaviourFingerprint } : {},
166
+ ...options.reproductionGeneratedAt ? { generatedAt: options.reproductionGeneratedAt } : {},
156
167
  reportByteIdentical: true
157
168
  },
158
169
  issuedAt: options.issuedAt,
@@ -161,6 +172,40 @@ function createWitanIssuanceStatement(report, options) {
161
172
  }
162
173
  });
163
174
  }
175
+ var ISO_8601_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
176
+ function readReproductionGeneratedAt(attestation, now) {
177
+ const predicate = typeof attestation === "object" && attestation !== null ? attestation.predicate : void 0;
178
+ const value = typeof predicate === "object" && predicate !== null ? predicate.generatedAt : void 0;
179
+ if (value === void 0 || value === null) {
180
+ return {
181
+ ok: false,
182
+ reason: "the attestation's predicate.generatedAt is missing, so the reproduction has no scan time to replay"
183
+ };
184
+ }
185
+ const instant = typeof value === "string" ? Date.parse(value) : Number.NaN;
186
+ if (typeof value !== "string" || !ISO_8601_INSTANT.test(value) || !Number.isFinite(instant)) {
187
+ return {
188
+ ok: false,
189
+ reason: `the attestation's predicate.generatedAt is not a valid ISO 8601 time: ${JSON.stringify(value)}`
190
+ };
191
+ }
192
+ if (instant > now.getTime()) {
193
+ return {
194
+ ok: false,
195
+ reason: `the attestation's predicate.generatedAt (${value}) is later than this issuer's clock (${now.toISOString()})`
196
+ };
197
+ }
198
+ return { ok: true, generatedAt: value };
199
+ }
200
+ var NEW_YEAR_WINDOW_MS = 24 * 60 * 60 * 1e3;
201
+ function isWithinDayOfNewYearUtc(generatedAt) {
202
+ const instant = Date.parse(generatedAt);
203
+ if (!Number.isFinite(instant)) return false;
204
+ const year = new Date(instant).getUTCFullYear();
205
+ const sinceNewYear = instant - Date.UTC(year, 0, 1);
206
+ const untilNewYear = Date.UTC(year + 1, 0, 1) - instant;
207
+ return sinceNewYear <= NEW_YEAR_WINDOW_MS || untilNewYear <= NEW_YEAR_WINDOW_MS;
208
+ }
164
209
  function verifyWitanIssuanceBinding(statement, options) {
165
210
  const parsed = WitanIssuanceStatementSchema.safeParse(statement);
166
211
  if (!parsed.success) {
@@ -500,8 +545,15 @@ function normalizeRepoRelativePath(raw) {
500
545
  if (path.split("/").some((segment) => segment === ".." || segment === "")) return void 0;
501
546
  return path;
502
547
  }
503
- function fingerprint(rule, path, line, identity) {
504
- return createHash2("sha256").update(JSON.stringify([rule, path, line, identity])).digest("hex");
548
+ var METRIC_DERIVED_IDENTITY = "metric-derived";
549
+ var NUMBER_PLACEHOLDER = "<n>";
550
+ var NUMBER_PATTERN = /\d+(?:\.\d+)?/g;
551
+ function findingIdentity(summary) {
552
+ if (METRIC_DERIVED_FINDING_PATTERN.test(summary)) return METRIC_DERIVED_IDENTITY;
553
+ return summary.replace(NUMBER_PATTERN, NUMBER_PLACEHOLDER);
554
+ }
555
+ function fingerprint(rule, path, line, identity, ordinal) {
556
+ return createHash2("sha256").update(JSON.stringify([rule, path, line, identity, ordinal])).digest("hex");
505
557
  }
506
558
  function exportGitLabCodeQuality(report) {
507
559
  const ingestedCount = (report.consumedSignals ?? []).reduce(
@@ -519,6 +571,7 @@ function exportGitLabCodeQuality(report) {
519
571
  const entries = [];
520
572
  let excludedUnlocated = 0;
521
573
  const seen = /* @__PURE__ */ new Set();
574
+ const groupSizes = /* @__PURE__ */ new Map();
522
575
  for (const criterion of report.criteria) {
523
576
  for (const finding of criterion.findings) {
524
577
  const path = finding.evidence.path === void 0 ? void 0 : normalizeRepoRelativePath(finding.evidence.path);
@@ -527,9 +580,14 @@ function exportGitLabCodeQuality(report) {
527
580
  excludedUnlocated += 1;
528
581
  continue;
529
582
  }
530
- const print = fingerprint(criterion.id, path, line, finding.summary);
531
- if (seen.has(print)) continue;
532
- seen.add(print);
583
+ const exact = JSON.stringify([criterion.id, path, line, finding.summary]);
584
+ if (seen.has(exact)) continue;
585
+ seen.add(exact);
586
+ const identity = findingIdentity(finding.summary);
587
+ const group = JSON.stringify([criterion.id, path, line, identity]);
588
+ const ordinal = groupSizes.get(group) ?? 0;
589
+ groupSizes.set(group, ordinal + 1);
590
+ const print = fingerprint(criterion.id, path, line, identity, ordinal);
533
591
  entries.push({
534
592
  description: finding.summary,
535
593
  check_name: criterion.id,
@@ -652,9 +710,14 @@ function writeOutputFiles(dir, files, what) {
652
710
  // src/terminal.ts
653
711
  init_esm_shims();
654
712
  function renderMinScoreAbstentionFailure(summary, minScore) {
655
- if (summary.verdict !== "Insufficient source" && summary.verdict !== "Insufficient evidence") {
713
+ if (!isWitanAbstentionLabel(summary.verdict)) {
656
714
  throw new Error("A minimum-score abstention message requires an abstained Cejel summary.");
657
715
  }
716
+ const instruction = witanHistoryAbstentionInstruction(summary.verdict);
717
+ if (instruction !== void 0) {
718
+ return `Cejel: cannot evaluate the required minimum ${minScore.toFixed(1)}/4.0 because the scan is ${summary.verdict.toLowerCase()}, with no composite score and no verdict. ${instruction}
719
+ `;
720
+ }
658
721
  return `Cejel: cannot evaluate the required minimum ${minScore.toFixed(1)}/4.0 because this repository has ${summary.verdict.toLowerCase()}.
659
722
  `;
660
723
  }
@@ -673,20 +736,21 @@ function renderMinScoreCoverageFailure(coverage, minScore) {
673
736
  `;
674
737
  }
675
738
  function renderTerminalCertificate(summary, report) {
676
- const abstained = summary.verdict === "Insufficient source" || summary.verdict === "Insufficient evidence";
739
+ const abstained = isWitanAbstentionLabel(summary.verdict);
677
740
  if (!abstained && (summary.overallScore === null || summary.codeTrustScore === null || summary.processTrustScore === null)) {
678
741
  throw new Error("A scored Cejel summary must carry numeric headline scores.");
679
742
  }
680
743
  const lines = abstained ? [
681
744
  `Cejel Trust Certificate \u2014 ${stripTerminalControls(summary.productDisplayName)}`,
682
745
  "",
683
- `${summary.verdict} to certify.`,
746
+ renderWitanAbstentionHeadline(summary.verdict),
684
747
  ` ${summary.insufficientSourceReason === void 0 ? "No ratable source or measurable evidence was found." : stripTerminalControls(summary.insufficientSourceReason)}`,
685
748
  ""
686
749
  ] : [
687
750
  `Cejel Trust Certificate \u2014 ${stripTerminalControls(summary.productDisplayName)}`,
688
751
  "",
689
752
  `Overall: ${formatScore(summary.overallScore)}/4.0 (${summary.verdict})`,
753
+ ...report && formatVerdictCapSentence(report) ? [` ${formatVerdictCapSentence(report)}`] : [],
690
754
  ` Code trust: ${formatScore(summary.codeTrustScore)}/4.0`,
691
755
  ` Process trust: ${formatScore(summary.processTrustScore)}/4.0`,
692
756
  ...report ? [` ${formatDimensionsMeasuredSentence(report)}`] : [],
@@ -707,6 +771,7 @@ function renderTerminalCertificate(summary, report) {
707
771
  ""
708
772
  );
709
773
  }
774
+ if (report) lines.push(...renderPlainSummaryLines(buildPlainSummary(report), summary, abstained));
710
775
  if (summary.scanLimitations.length > 0) {
711
776
  lines.push(
712
777
  "LIMITED EVIDENCE \u2014 the score and verdict are qualified by scan limitations:",
@@ -716,7 +781,7 @@ function renderTerminalCertificate(summary, report) {
716
781
  }
717
782
  if (summary.historyLimitations?.length) {
718
783
  lines.push(
719
- "HISTORY LIMITATION \u2014 scored as usual, but some criteria read truncated history:",
784
+ abstained ? "HISTORY LIMITATION \u2014 some criteria read truncated history:" : "HISTORY LIMITATION \u2014 scored as usual, but some criteria read truncated history:",
720
785
  ...summary.historyLimitations.map((limitation) => ` - ${stripTerminalControls(limitation)}`),
721
786
  ""
722
787
  );
@@ -812,7 +877,7 @@ function renderTerminalCertificate(summary, report) {
812
877
  }
813
878
  }
814
879
  lines.push("", "Plain-language glossary:");
815
- for (const entry of CERTIFICATE_GLOSSARY) {
880
+ for (const entry of CERTIFICATE_GLOSSARY.map((curated) => glossaryEntryForRubric(curated, report?.rubricVersion))) {
816
881
  lines.push(` ${entry.term} \u2014 ${entry.definition}`);
817
882
  }
818
883
  if (report && isProspectiveRubricVersion(report.rubricVersion)) {
@@ -821,6 +886,43 @@ function renderTerminalCertificate(summary, report) {
821
886
  return `${lines.join("\n")}
822
887
  `;
823
888
  }
889
+ var PLAIN_TERMINAL_ITEM_LIMIT = 3;
890
+ function renderPlainSummaryLines(plain, summary, abstained) {
891
+ const section = (heading, leads, items, empty) => {
892
+ const shown = items.slice(0, PLAIN_TERMINAL_ITEM_LIMIT);
893
+ const remaining = items.length - shown.length;
894
+ return [
895
+ `${heading} (${items.length}):`,
896
+ ...leads.filter((lead) => lead !== void 0).map((lead) => ` ${lead}`),
897
+ ...items.length === 0 ? [` ${empty}`] : shown.map((item) => ` - ${stripTerminalControls(item)}`),
898
+ ...remaining > 0 ? [` ...and ${remaining} more in certificate.html.`] : []
899
+ ];
900
+ };
901
+ const itemLine = (item) => `${item.area} \u2014 ${item.text}${item.location !== void 0 ? ` (${item.location})` : ""}`;
902
+ const wrongFindings = plain.wrong.filter((item) => item.source.kind === "finding").length;
903
+ const otherFindings = summary.findingCount - wrongFindings;
904
+ const topFindingsNote = !abstained && otherFindings > 0 ? `Top findings below counts ${summary.findingCount} finding${summary.findingCount === 1 ? "" : "s"} in all, including ${otherFindings} informational finding${otherFindings === 1 ? "" : "s"} not listed here.` : void 0;
905
+ return [
906
+ "In plain words (the full lists, with links to the evidence, are in certificate.html):",
907
+ ...plain.prospectiveNotice !== void 0 ? [` ${plain.prospectiveNotice}`] : [],
908
+ ...section("What holds", [plain.areasLine, plain.holdsLead], plain.holds.map(itemLine), plain.holdsNote ?? ""),
909
+ ...section(
910
+ "What is wrong",
911
+ [plain.wrongNote, topFindingsNote],
912
+ plain.wrong.map((item) => `${item.level === "critical" ? "Critical" : "Warning"}: ${itemLine(item)}`),
913
+ "Cejel recorded no critical or warning-level problem in what it checked."
914
+ ),
915
+ ...section("What is not known", [], plain.notKnown.map(itemLine), "Cejel listed no gap here; anything it did not measure is still not established."),
916
+ ...section(
917
+ "What to check next",
918
+ [],
919
+ plain.checkNext.map((check) => `${check.area} \u2014 ${check.text}`),
920
+ "Nothing to check from these lists."
921
+ ),
922
+ plain.notCovered,
923
+ ""
924
+ ];
925
+ }
824
926
  function formatScore(score) {
825
927
  if (score === null) throw new Error("A scored Cejel summary must carry numeric scores.");
826
928
  return score.toFixed(1);
@@ -1095,7 +1197,13 @@ async function runWitanCli(args, rubricVersion) {
1095
1197
  },
1096
1198
  { name: "badge.json", contents: JSON.stringify(renderWitanBadgeEndpoint(report), null, 2) },
1097
1199
  { name: "badge.svg", contents: renderWitanBadgeSvg(report) },
1098
- { name: "summary.json", contents: JSON.stringify(summary, null, 2) }
1200
+ // plainSummary (goal_cejel_four_question_summary_2026-11-02) is added here, at the one place
1201
+ // summary.json is written, so buildWitanCliSummary's shape (the MCP and HTTP responses) and
1202
+ // every key before it are unchanged.
1203
+ {
1204
+ name: "summary.json",
1205
+ contents: JSON.stringify({ ...summary, plainSummary: buildPlainSummary(report) }, null, 2)
1206
+ }
1099
1207
  ],
1100
1208
  "scan output"
1101
1209
  );
@@ -1541,6 +1649,12 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1541
1649
  );
1542
1650
  return 1;
1543
1651
  }
1652
+ const scanTime = readReproductionGeneratedAt(attestationArtifact.value, /* @__PURE__ */ new Date());
1653
+ if (!scanTime.ok) {
1654
+ process.stderr.write(`Cejel: refusing to issue \u2014 ${scanTime.reason}.
1655
+ `);
1656
+ return 1;
1657
+ }
1544
1658
  let keyContents;
1545
1659
  try {
1546
1660
  keyContents = readFileSync(options.keyPath, "utf8");
@@ -1563,7 +1677,8 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1563
1677
  // comparison below, as it always has.
1564
1678
  ...suppliedReport.inputBinding?.policy === "strict" ? { strictIngest: true } : {},
1565
1679
  ...suppliedReport.inputBinding?.scannedSubject.repository ? { subjectRepository: suppliedReport.inputBinding.scannedSubject.repository } : {},
1566
- toolVersion: cliVersion()
1680
+ toolVersion: cliVersion(),
1681
+ generatedAt: scanTime.generatedAt
1567
1682
  });
1568
1683
  const reproducedBytes = serializeWitanReport(reproduction.report);
1569
1684
  const reproducedSha256 = createHash3("sha256").update(reproducedBytes, "utf8").digest("hex");
@@ -1577,6 +1692,12 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1577
1692
  " Check that the checkout is at the certificate revision, that the @cejel/cejel version matches",
1578
1693
  " report.json's toolVersion, and that --product-name/--name/--rubric-pin/--ingest match the",
1579
1694
  " original invocation. An issuance that cannot reproduce is not issued.",
1695
+ // The reproduction replays the scan's instant, but v17's B4 reads that instant's year in
1696
+ // the local time zone. Near a new year two hosts can disagree on it; name the setting,
1697
+ // never guess a zone.
1698
+ ...isWithinDayOfNewYearUtc(scanTime.generatedAt) ? [
1699
+ ` The scan's generatedAt (${scanTime.generatedAt}) is within 24 hours of a new year in UTC, and v17's B4 freshness year is read in the scan host's local time: an issuer in another time zone may need to re-run with the TZ environment variable set to the scan host's.`
1700
+ ] : [],
1580
1701
  ""
1581
1702
  ].join("\n")
1582
1703
  );
@@ -1588,6 +1709,7 @@ ${attestationBinding.errors.map((error) => ` - ${error}`).join("\n")}
1588
1709
  issuerKeyFingerprint: issuerKey.fingerprint,
1589
1710
  toolVersion: cliVersion(),
1590
1711
  issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
1712
+ reproductionGeneratedAt: scanTime.generatedAt,
1591
1713
  engagementRef: options.engagementRef
1592
1714
  });
1593
1715
  const issuancePath = join2(options.outDir, ISSUANCE_OUTPUT_FILE);
package/dist/mcp/index.js CHANGED
@@ -3,9 +3,9 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  serveCejelMcpOverStdio
6
- } from "../chunk-KNTBED5T.js";
7
- import "../chunk-KJVFCZZ3.js";
8
- import "../chunk-NIWSYJ6N.js";
6
+ } from "../chunk-S5B5KMSO.js";
7
+ import "../chunk-4YOIR3E6.js";
8
+ import "../chunk-2UMHJ23V.js";
9
9
  import "../chunk-AMVP7L26.js";
10
10
  import {
11
11
  init_esm_shims
@@ -3,10 +3,10 @@ import { createRequire as __cejelCreateRequire } from 'node:module';
3
3
  const require = __cejelCreateRequire(import.meta.url);
4
4
  import {
5
5
  listCejelLlmPackFiles
6
- } from "../../chunk-WXPGEQBE.js";
6
+ } from "../../chunk-RRPQHEBU.js";
7
7
  import {
8
8
  execGit
9
- } from "../../chunk-NIWSYJ6N.js";
9
+ } from "../../chunk-2UMHJ23V.js";
10
10
  import {
11
11
  require_typescript
12
12
  } from "../../chunk-6LVNNJTG.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cejel/cejel",
3
- "version": "0.6.8",
3
+ "version": "0.6.11",
4
4
  "mcpName": "io.github.BargLabs/cejel",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -31,7 +31,7 @@
31
31
  "type": "git",
32
32
  "url": "git+https://github.com/BargLabs/cejel.git"
33
33
  },
34
- "packageManager": "pnpm@9.0.0",
34
+ "packageManager": "pnpm@9.0.0+sha512.b4106707c7225b1748b61595953ccbebff97b54ad05d002aa3635f633b9c53cd666f7ce9b8bc44704f1fa048b9a49b55371ab2d9e9d667d1efe2ef1514bcd513",
35
35
  "type": "module",
36
36
  "bin": {
37
37
  "cejel": "dist/index.js",