@cejel/cejel 0.4.1 → 0.4.3
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 +8 -8
- package/dist/{chunk-HG5NAFH5.js → chunk-ITHPLYKX.js} +392 -47
- package/dist/index.js +37 -3
- package/dist/mcp/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
|
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
> **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
|
|
71
|
-
> 0.4.
|
|
71
|
+
> 0.4.3 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
|
|
72
72
|
> signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
|
|
73
73
|
> it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
|
|
74
74
|
> GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM and an
|
|
@@ -91,7 +91,7 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
|
|
|
91
91
|
npx @cejel/cejel@latest .
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
> **Distribution note:** Cejel `0.4.
|
|
94
|
+
> **Distribution note:** Cejel `0.4.3` is the coordinated release version for npm,
|
|
95
95
|
> standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry.
|
|
96
96
|
|
|
97
97
|
`npx` can reuse a stale cached package. Force the current npm release with the `@latest`
|
|
@@ -105,7 +105,7 @@ If a root `package.json` still carries a template name, override only the certif
|
|
|
105
105
|
name while keeping the repository-derived stable slug:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
npx @cejel/cejel . --name "Customer Portal"
|
|
108
|
+
npx @cejel/cejel@latest . --name "Customer Portal"
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
The npm package is scoped as `@cejel/cejel`; its executable remains the short command `cejel`.
|
|
@@ -140,16 +140,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
|
|
|
140
140
|
This is cryptographically signed provenance. It is distinct from Apple Developer ID or
|
|
141
141
|
Microsoft Authenticode code-signing.
|
|
142
142
|
|
|
143
|
-
**Docker / OCI.** The current container release is `0.4.
|
|
143
|
+
**Docker / OCI.** The current container release is `0.4.3`:
|
|
144
144
|
|
|
145
145
|
```bash
|
|
146
|
-
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.4.
|
|
146
|
+
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.4.3
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
|
|
150
150
|
|
|
151
151
|
```bash
|
|
152
|
-
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.4.
|
|
152
|
+
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.4.3 .
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
The OCI image carries an SBOM, maximum-mode build provenance, and a signed registry
|
|
@@ -162,7 +162,7 @@ projects, the public Cejel repository itself, and one explicitly labeled transpa
|
|
|
162
162
|
snapshot from the private studio monorepo where Cejel was built, with a per-repository
|
|
163
163
|
evidence report for every row under [`leaderboard/reports/`](./leaderboard/reports/). The
|
|
164
164
|
board is also hosted at [cejel.dev](https://cejel.dev). Every score is produced by the same
|
|
165
|
-
sealed public scorer used by `npx @cejel/cejel .`; no private domain collector contributes.
|
|
165
|
+
sealed public scorer used by `npx @cejel/cejel@latest .`; no private domain collector contributes.
|
|
166
166
|
For each public-repository row, check out the immutable source commit printed in its report
|
|
167
167
|
and run the public scorer to reproduce the score, verdict, measured coverage, and evidence.
|
|
168
168
|
The private Alfred snapshot discloses its limitation instead: its source commit and withheld
|
|
@@ -426,7 +426,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
|
|
|
426
426
|
the host path with the repository OpenClaw should allow Cejel to read:
|
|
427
427
|
|
|
428
428
|
```bash
|
|
429
|
-
openclaw mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.4.
|
|
429
|
+
openclaw mcp set cejel-oci '{"command":"docker","args":["run","--rm","-i","-v","/absolute/path/to/repo:/workspace:ro","ghcr.io/barglabs/cejel:0.4.3"]}'
|
|
430
430
|
openclaw mcp doctor cejel-oci --probe
|
|
431
431
|
```
|
|
432
432
|
|
|
@@ -4531,6 +4531,317 @@ function collectExternalFindings(consumedSignals) {
|
|
|
4531
4531
|
|
|
4532
4532
|
// src/witan/finding-presentation.ts
|
|
4533
4533
|
init_esm_shims();
|
|
4534
|
+
|
|
4535
|
+
// src/witan/certificate-presentation.ts
|
|
4536
|
+
init_esm_shims();
|
|
4537
|
+
var CERTIFICATE_GLOSSARY = [
|
|
4538
|
+
{
|
|
4539
|
+
key: "labels",
|
|
4540
|
+
term: "labels / why the labels differ",
|
|
4541
|
+
definition: "Three labels can appear on one line because they answer three different questions. Finding severity describes how serious one issue is. The dimension band summarizes the criterion evidence. The numeric band comes from the weighted score.",
|
|
4542
|
+
metricNames: []
|
|
4543
|
+
},
|
|
4544
|
+
{
|
|
4545
|
+
key: "capped",
|
|
4546
|
+
term: "capped",
|
|
4547
|
+
definition: "Credit stops increasing after the maximum defined by the scoring rubric, even when the raw count is higher. Caps are fixed per rubric version because they are part of what was calibrated. Extra volume therefore cannot outweigh the rest of the rubric.",
|
|
4548
|
+
metricNames: []
|
|
4549
|
+
},
|
|
4550
|
+
{
|
|
4551
|
+
key: "test-to-source-file-ratio",
|
|
4552
|
+
term: "test-to-source file ratio",
|
|
4553
|
+
definition: "The number of detected test files compared with detected source files. A larger implementation surface needs a correspondingly visible test surface.",
|
|
4554
|
+
metricNames: ["test_to_source_ratio"]
|
|
4555
|
+
},
|
|
4556
|
+
{
|
|
4557
|
+
key: "static-coverage",
|
|
4558
|
+
term: "static coverage",
|
|
4559
|
+
definition: "A percentage read from a coverage report or threshold that the repository itself publishes. Cejel does not run the repository's tests. The scoring rubric defines how much credit the published percentage receives.",
|
|
4560
|
+
metricNames: ["coverage_percent"]
|
|
4561
|
+
},
|
|
4562
|
+
{
|
|
4563
|
+
key: "verification-script",
|
|
4564
|
+
term: "verification script",
|
|
4565
|
+
definition: "the checks that run automatically: tests, linting, type-checking, build",
|
|
4566
|
+
metricNames: ["verification_script_ratio"]
|
|
4567
|
+
},
|
|
4568
|
+
{
|
|
4569
|
+
key: "non-hollow",
|
|
4570
|
+
term: "non-hollow",
|
|
4571
|
+
definition: "test files that actually assert something rather than being empty or skipped",
|
|
4572
|
+
metricNames: ["non_hollow_test_share"]
|
|
4573
|
+
},
|
|
4574
|
+
{
|
|
4575
|
+
key: "secrets",
|
|
4576
|
+
term: "secrets",
|
|
4577
|
+
definition: "credentials like API keys and tokens",
|
|
4578
|
+
metricNames: ["secret_cleanliness"]
|
|
4579
|
+
},
|
|
4580
|
+
{
|
|
4581
|
+
key: "environment-handling-depth",
|
|
4582
|
+
term: "environment handling depth",
|
|
4583
|
+
definition: "The visible controls for keeping environment-specific configuration and credentials out of source. Unsafe configuration handling can expose production access.",
|
|
4584
|
+
metricNames: ["env_handling_depth"]
|
|
4585
|
+
},
|
|
4586
|
+
{
|
|
4587
|
+
key: "rls-policy-count",
|
|
4588
|
+
term: "RLS policy count",
|
|
4589
|
+
definition: "The number of detected database row-level security policies. Those policies can enforce which rows each tenant or user may reach.",
|
|
4590
|
+
metricNames: ["rls_policy_count"]
|
|
4591
|
+
},
|
|
4592
|
+
{
|
|
4593
|
+
key: "tenant-scoped-schema-ratio",
|
|
4594
|
+
term: "tenant-scoped schema ratio",
|
|
4595
|
+
definition: "The share of relevant database tables with a visible tenant boundary. Missing tenant scope can allow one customer's data to mix with another's.",
|
|
4596
|
+
metricNames: ["tenant_scope_ratio"]
|
|
4597
|
+
},
|
|
4598
|
+
{
|
|
4599
|
+
key: "crypto-comparison-hygiene",
|
|
4600
|
+
term: "crypto comparison hygiene",
|
|
4601
|
+
definition: "Comparisons of secret values that take the same time whether or not the values match. Attackers therefore cannot recover secrets by measuring response times.",
|
|
4602
|
+
metricNames: ["crypto_comparison_hygiene"]
|
|
4603
|
+
},
|
|
4604
|
+
{
|
|
4605
|
+
key: "basic-checks",
|
|
4606
|
+
term: "basic checks",
|
|
4607
|
+
definition: "In code trust, production-readiness basic checks cover build or type-check commands, automated pipelines, deployment configuration, environment templates, health checks, and error boundaries. In process trust, PR trace basic checks cover automated pipelines, pull-request templates, and review-gate records. Missing checks leave part of the acceptance path unsupported.",
|
|
4608
|
+
metricNames: ["prod_readiness_primitives", "pr_trace_primitives"]
|
|
4609
|
+
},
|
|
4610
|
+
{
|
|
4611
|
+
key: "workflow-depth",
|
|
4612
|
+
term: "workflow depth",
|
|
4613
|
+
definition: "Workflows here are automated build, test, or release pipelines, such as GitHub Actions, not team policy. This measures how many relevant pipeline stages Cejel can see rather than how many workflow files exist. A deeper pipeline checks more of the path to a release.",
|
|
4614
|
+
metricNames: ["prod_workflow_depth"]
|
|
4615
|
+
},
|
|
4616
|
+
{
|
|
4617
|
+
key: "observability-depth",
|
|
4618
|
+
term: "observability depth",
|
|
4619
|
+
definition: "The visible health, logging, and error-reporting controls. Operators need evidence that failures can be detected and investigated.",
|
|
4620
|
+
metricNames: ["observability_depth"]
|
|
4621
|
+
},
|
|
4622
|
+
{
|
|
4623
|
+
key: "rollback-and-migration-safety-depth",
|
|
4624
|
+
term: "rollback and migration-safety depth",
|
|
4625
|
+
definition: "The visible procedures and checks for reversing a release or changing stored data safely. Recovery and data changes are high-risk parts of accepting software.",
|
|
4626
|
+
metricNames: ["rollback_safety_depth"]
|
|
4627
|
+
},
|
|
4628
|
+
{
|
|
4629
|
+
key: "dependency-automation-ratio",
|
|
4630
|
+
term: "dependency automation ratio",
|
|
4631
|
+
definition: "The share of expected automated dependency-update and audit controls that are present. Stale or vulnerable packages otherwise depend on manual discovery.",
|
|
4632
|
+
metricNames: ["dependency_automation_ratio"]
|
|
4633
|
+
},
|
|
4634
|
+
{
|
|
4635
|
+
key: "pinned-dependency-ratio",
|
|
4636
|
+
term: "pinned dependency ratio",
|
|
4637
|
+
definition: "The share of application dependencies fixed to exact versions. Exact inputs make deployed builds more reproducible.",
|
|
4638
|
+
metricNames: ["pinned_dependency_ratio"]
|
|
4639
|
+
},
|
|
4640
|
+
{
|
|
4641
|
+
key: "lockfile-coverage",
|
|
4642
|
+
term: "lockfile coverage",
|
|
4643
|
+
definition: "Whether the package manifests are backed by committed lockfiles. Lockfiles record the exact dependency versions selected for a build.",
|
|
4644
|
+
metricNames: ["lockfile_coverage"]
|
|
4645
|
+
},
|
|
4646
|
+
{
|
|
4647
|
+
key: "declared-version-range-ratio",
|
|
4648
|
+
term: "declared version range ratio",
|
|
4649
|
+
definition: "The share of library dependencies with an explicit compatible version range. Unconstrained dependencies can change without a deliberate source change.",
|
|
4650
|
+
metricNames: ["declared_version_range_ratio"]
|
|
4651
|
+
},
|
|
4652
|
+
{
|
|
4653
|
+
key: "dependency-count-sanity",
|
|
4654
|
+
term: "dependency count sanity",
|
|
4655
|
+
definition: "A bounded check for an unusually long list of direct dependencies for a library. Every direct dependency adds maintenance and supply-chain exposure.",
|
|
4656
|
+
metricNames: ["dependency_count_sanity"]
|
|
4657
|
+
},
|
|
4658
|
+
{
|
|
4659
|
+
key: "claim-match-rate",
|
|
4660
|
+
term: "claim match rate",
|
|
4661
|
+
definition: "The share of documented product claims that match visible repository evidence. An acceptance claim should be checkable against the delivered revision.",
|
|
4662
|
+
metricNames: ["claim_match_rate"]
|
|
4663
|
+
},
|
|
4664
|
+
{
|
|
4665
|
+
key: "claim-source-depth",
|
|
4666
|
+
term: "claim source depth",
|
|
4667
|
+
definition: "How much concrete, checkable claim material is present. Vague or missing claims give a relying party nothing specific to verify.",
|
|
4668
|
+
metricNames: ["claim_source_depth"]
|
|
4669
|
+
},
|
|
4670
|
+
{
|
|
4671
|
+
key: "reconciliation-artifact-depth",
|
|
4672
|
+
term: "reconciliation artifact depth",
|
|
4673
|
+
definition: "The visible records connecting stated claims to evidence. A relying party needs to trace what was promised to what was inspected.",
|
|
4674
|
+
metricNames: ["reconciliation_artifact_depth"]
|
|
4675
|
+
},
|
|
4676
|
+
{
|
|
4677
|
+
key: "recent-pr-merge-ratio",
|
|
4678
|
+
term: "recent PR merge ratio",
|
|
4679
|
+
definition: "The share of recent visible commits associated with merged pull requests. Review history helps a relying party inspect how changes reached the revision.",
|
|
4680
|
+
metricNames: ["pr_merge_ratio"]
|
|
4681
|
+
},
|
|
4682
|
+
{
|
|
4683
|
+
key: "ci-verification-depth",
|
|
4684
|
+
term: "CI verification depth",
|
|
4685
|
+
definition: "The number of test, lint, type-check, and build categories Cejel sees running in continuous integration. Automated gates make checks repeatable for each change.",
|
|
4686
|
+
metricNames: ["ci_script_depth"]
|
|
4687
|
+
},
|
|
4688
|
+
{
|
|
4689
|
+
key: "pr-gate-ci-workflow-count",
|
|
4690
|
+
term: "PR-gate CI workflow count",
|
|
4691
|
+
definition: "Workflows here are automated build and test pipelines, such as GitHub Actions, not team policy. This counts pipelines that visibly run on pull requests or the default branch. Checks that never gate changes cannot support the acceptance decision.",
|
|
4692
|
+
metricNames: ["default_branch_ci_depth"]
|
|
4693
|
+
},
|
|
4694
|
+
{
|
|
4695
|
+
key: "audit-artifact-depth",
|
|
4696
|
+
term: "audit artifact depth",
|
|
4697
|
+
definition: "How many durable audit records such as changelogs, incident notes, or security records are present. Past operational decisions should remain inspectable.",
|
|
4698
|
+
metricNames: ["audit_artifact_depth"]
|
|
4699
|
+
},
|
|
4700
|
+
{
|
|
4701
|
+
key: "audit-freshness-depth",
|
|
4702
|
+
term: "audit freshness depth",
|
|
4703
|
+
definition: "How recently the visible audit records were maintained. Stale records may not describe the revision being accepted.",
|
|
4704
|
+
metricNames: ["audit_freshness_depth"]
|
|
4705
|
+
},
|
|
4706
|
+
{
|
|
4707
|
+
key: "un-overridable-kill-switch",
|
|
4708
|
+
term: "un-overridable kill-switch",
|
|
4709
|
+
definition: "A stop control that ordinary automation cannot bypass. Operators need a dependable way to halt privileged behavior.",
|
|
4710
|
+
metricNames: ["kill_switch_fail_safe_present"]
|
|
4711
|
+
},
|
|
4712
|
+
{
|
|
4713
|
+
key: "human-gate-documented",
|
|
4714
|
+
term: "human gate documented",
|
|
4715
|
+
definition: "A written requirement for a person to approve sensitive operations. High-impact actions should not rely only on unattended automation.",
|
|
4716
|
+
metricNames: ["human_gate_documented"]
|
|
4717
|
+
},
|
|
4718
|
+
{
|
|
4719
|
+
key: "fail-closed-privilege-check",
|
|
4720
|
+
term: "fail-closed privilege check",
|
|
4721
|
+
definition: "A permission check that refuses the operation when it cannot reach a confident allow decision. Errors must not silently grant elevated access.",
|
|
4722
|
+
metricNames: ["fail_closed_privilege_check"]
|
|
4723
|
+
},
|
|
4724
|
+
{
|
|
4725
|
+
key: "privilege-escalation-cleanliness",
|
|
4726
|
+
term: "privilege-escalation cleanliness",
|
|
4727
|
+
definition: "The absence of detected patterns that unsafely widen permissions. Unnecessary elevation increases the impact of a mistake or compromise.",
|
|
4728
|
+
metricNames: ["privilege_escalation_cleanliness"]
|
|
4729
|
+
},
|
|
4730
|
+
{
|
|
4731
|
+
key: "protected-path-review-gate",
|
|
4732
|
+
term: "protected-path review gate",
|
|
4733
|
+
definition: "A rule requiring review before sensitive files or directories can change. Critical controls deserve an explicit second check.",
|
|
4734
|
+
metricNames: ["protected_path_review_gate"]
|
|
4735
|
+
}
|
|
4736
|
+
];
|
|
4737
|
+
var GLOSSARY_BY_METRIC = new Map(
|
|
4738
|
+
CERTIFICATE_GLOSSARY.flatMap(
|
|
4739
|
+
(entry) => entry.metricNames.map((metricName) => [metricName, entry])
|
|
4740
|
+
)
|
|
4741
|
+
);
|
|
4742
|
+
function glossaryEntryForMetric(metric2) {
|
|
4743
|
+
return GLOSSARY_BY_METRIC.get(metric2.name) ?? {
|
|
4744
|
+
key: `metric-${metric2.name.replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}`,
|
|
4745
|
+
term: formatCertificateMetricLabel(metric2),
|
|
4746
|
+
definition: metric2.description ? `${metric2.description} This measurement contributes to the criterion score.` : "A measured input to this criterion. This measurement contributes to the criterion score.",
|
|
4747
|
+
metricNames: [metric2.name]
|
|
4748
|
+
};
|
|
4749
|
+
}
|
|
4750
|
+
var CERTIFICATE_METRIC_LABELS = {
|
|
4751
|
+
prod_readiness_primitives: "Production-readiness basic checks",
|
|
4752
|
+
pr_trace_primitives: "PR trace basic checks",
|
|
4753
|
+
prod_workflow_depth: "Production workflow depth (automated build/test/release pipeline)",
|
|
4754
|
+
default_branch_ci_depth: "PR-gate CI workflow count (automated build/test pipeline)"
|
|
4755
|
+
};
|
|
4756
|
+
function formatCertificateMetricLabel(metric2) {
|
|
4757
|
+
return CERTIFICATE_METRIC_LABELS[metric2.name] ?? metric2.label;
|
|
4758
|
+
}
|
|
4759
|
+
function glossaryEntriesForReport(report) {
|
|
4760
|
+
const entries = new Map(CERTIFICATE_GLOSSARY.map((entry) => [entry.key, entry]));
|
|
4761
|
+
for (const metric2 of report.criteria.flatMap((criterion) => criterion.metrics)) {
|
|
4762
|
+
const entry = glossaryEntryForMetric(metric2);
|
|
4763
|
+
entries.set(entry.key, entry);
|
|
4764
|
+
}
|
|
4765
|
+
return [...entries.values()];
|
|
4766
|
+
}
|
|
4767
|
+
function isCoverageNotMeasured(criterion, metric2) {
|
|
4768
|
+
if (metric2.name !== "coverage_percent" || metric2.value !== 0) return false;
|
|
4769
|
+
return ![
|
|
4770
|
+
...criterion.evidence,
|
|
4771
|
+
...criterion.findings.map((finding) => finding.evidence)
|
|
4772
|
+
].some((evidence) => evidence.kind === "coverage");
|
|
4773
|
+
}
|
|
4774
|
+
function labelAlreadyStatesUnit(label, unit) {
|
|
4775
|
+
if (!unit) return false;
|
|
4776
|
+
const escapedUnit = unit.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4777
|
+
return new RegExp(`(?:^|[\\s-])${escapedUnit}$`, "i").test(label.trim());
|
|
4778
|
+
}
|
|
4779
|
+
function formatCertificateMetricValue(criterion, metric2) {
|
|
4780
|
+
if (isCoverageNotMeasured(criterion, metric2)) {
|
|
4781
|
+
return "no coverage report found \u2014 not measured";
|
|
4782
|
+
}
|
|
4783
|
+
if (metric2.name === "test_to_source_ratio" && metric2.max !== void 0) {
|
|
4784
|
+
const tests = formatMetricNumber(metric2.value);
|
|
4785
|
+
const sources = formatMetricNumber(metric2.max);
|
|
4786
|
+
const comparison = `${tests} test file${metric2.value === 1 ? "" : "s"} / ${sources} source file${metric2.max === 1 ? "" : "s"}`;
|
|
4787
|
+
return metric2.value > metric2.max ? `${comparison} (credit capped at parity)` : comparison;
|
|
4788
|
+
}
|
|
4789
|
+
const displayUnit = metric2.name === "prod_readiness_primitives" || metric2.name === "pr_trace_primitives" ? "checks" : labelAlreadyStatesUnit(formatCertificateMetricLabel(metric2), metric2.unit) ? void 0 : metric2.unit;
|
|
4790
|
+
const unit = displayUnit ? ` ${displayUnit}` : "";
|
|
4791
|
+
if (metric2.max === void 0) return `${formatMetricNumber(metric2.value)}${unit}`;
|
|
4792
|
+
if (metric2.kind === "saturating_count" && metric2.value > metric2.max) {
|
|
4793
|
+
return `${formatMetricNumber(metric2.max)}${unit} (capped; ${formatMetricNumber(metric2.value)} raw)`;
|
|
4794
|
+
}
|
|
4795
|
+
return `${formatMetricNumber(metric2.value)}/${formatMetricNumber(metric2.max)}${unit}`;
|
|
4796
|
+
}
|
|
4797
|
+
function buildRelyingPartySummary(report) {
|
|
4798
|
+
const applicable = report.criteria.filter((criterion) => criterion.status !== "not_applicable");
|
|
4799
|
+
const measured = applicable.filter((criterion) => criterion.status !== "insufficient_data");
|
|
4800
|
+
const findings = report.criteria.reduce((sum, criterion) => sum + criterion.findings.length, 0);
|
|
4801
|
+
const revision = report.repo.headSha ? ` at revision ${report.repo.headSha}` : " from the repository snapshot named in this report; no Git revision was available";
|
|
4802
|
+
const a1 = report.criteria.find((criterion) => criterion.id === "A1");
|
|
4803
|
+
const coverageMetric = a1?.metrics.find((metric2) => metric2.name === "coverage_percent");
|
|
4804
|
+
const gaps = [];
|
|
4805
|
+
if (a1 && coverageMetric && isCoverageNotMeasured(a1, coverageMetric)) {
|
|
4806
|
+
gaps.push(
|
|
4807
|
+
"No coverage report or configured threshold was found, so static coverage was not measured; Cejel does not run the subject's tests."
|
|
4808
|
+
);
|
|
4809
|
+
}
|
|
4810
|
+
const unmeasured = applicable.filter((criterion) => criterion.status === "insufficient_data");
|
|
4811
|
+
if (unmeasured.length > 0) {
|
|
4812
|
+
gaps.push(`The report did not measure ${unmeasured.map((criterion) => criterion.id).join(", ")}.`);
|
|
4813
|
+
}
|
|
4814
|
+
const notApplicable = report.criteria.filter((criterion) => criterion.status === "not_applicable");
|
|
4815
|
+
if (notApplicable.length > 0) {
|
|
4816
|
+
gaps.push(
|
|
4817
|
+
`${notApplicable.map((criterion) => criterion.id).join(", ")} ${notApplicable.length === 1 ? "was" : "were"} marked not applicable and therefore not examined.`
|
|
4818
|
+
);
|
|
4819
|
+
}
|
|
4820
|
+
if ((report.scanLimitations?.length ?? 0) > 0) {
|
|
4821
|
+
const count = report.scanLimitations?.length ?? 0;
|
|
4822
|
+
gaps.push(`${count} scan limitation${count === 1 ? "" : "s"} qualifies the readable evidence.`);
|
|
4823
|
+
}
|
|
4824
|
+
if ((report.contentReadSummary?.skipped ?? 0) > 0) {
|
|
4825
|
+
const skipped = report.contentReadSummary?.skipped ?? 0;
|
|
4826
|
+
gaps.push(
|
|
4827
|
+
`${skipped} content entr${skipped === 1 ? "y was" : "ies were"} skipped for the reasons itemized in the certificate.`
|
|
4828
|
+
);
|
|
4829
|
+
}
|
|
4830
|
+
gaps.push(
|
|
4831
|
+
"Anything absent from the measured evidence was not established: no measurement and no finding are not clearance."
|
|
4832
|
+
);
|
|
4833
|
+
return {
|
|
4834
|
+
examined: `Cejel examined the repository evidence recorded for ${report.productDisplayName}${revision} under ${report.rubricVersion}.`,
|
|
4835
|
+
established: `The report established measured results for ${measured.length} of ${applicable.length} applicable rubric dimensions and recorded ${findings} evidence-backed finding${findings === 1 ? "" : "s"}.`,
|
|
4836
|
+
notEstablished: gaps.join(" "),
|
|
4837
|
+
next: "Review the cited evidence and open or unverified items, reproduce the scan at the reported revision when one is available, and obtain missing measurements or resolve limitations before deciding whether to accept the code."
|
|
4838
|
+
};
|
|
4839
|
+
}
|
|
4840
|
+
function formatMetricNumber(value) {
|
|
4841
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
4842
|
+
}
|
|
4843
|
+
|
|
4844
|
+
// src/witan/finding-presentation.ts
|
|
4534
4845
|
var METRIC_DERIVED_FINDING_PATTERN = /^[A-Z]\d+ metric-derived score is (\d+\.\d)\/4\.0, in the (critical|warning) band — no single finding drove this; it reflects the combined metric weighting below\.$/;
|
|
4535
4846
|
var REMEDIATION_BY_METRIC = {
|
|
4536
4847
|
test_to_source_ratio: "add concrete regression tests for the implementation surface",
|
|
@@ -4575,18 +4886,18 @@ function renderFindingSummary(criterion, finding) {
|
|
|
4575
4886
|
if (drivers.length === 0) {
|
|
4576
4887
|
return `${criterion.id} dimension band is ${band} at ${score}/4.0. To improve this dimension, add stronger measurable evidence for ${criterion.title.toLowerCase()}.`;
|
|
4577
4888
|
}
|
|
4578
|
-
const measurements = drivers.map(formatMetricMember).join("; ");
|
|
4889
|
+
const measurements = drivers.map((metric2) => formatMetricMember(criterion, metric2)).join("; ");
|
|
4579
4890
|
const actions = Array.from(
|
|
4580
4891
|
new Set(
|
|
4581
4892
|
drivers.map(
|
|
4582
|
-
(metric2) => REMEDIATION_BY_METRIC[metric2.name] ?? `raise the measured ${metric2.
|
|
4893
|
+
(metric2) => REMEDIATION_BY_METRIC[metric2.name] ?? `raise the measured ${formatCertificateMetricLabel(metric2).toLowerCase()}`
|
|
4583
4894
|
)
|
|
4584
4895
|
)
|
|
4585
4896
|
).join("; ");
|
|
4586
4897
|
return `${criterion.id} dimension band is ${band} at ${score}/4.0. Lowest contributing measurements: ${measurements}. To improve: ${actions}.`;
|
|
4587
4898
|
}
|
|
4588
4899
|
function selectLowestDrivers(metrics) {
|
|
4589
|
-
return [...metrics].filter((metric2) => metric2.max !== void 0).sort((left, right) => {
|
|
4900
|
+
return [...metrics].filter((metric2) => metric2.max !== void 0 && normalizedMetricValue(metric2) < 1).sort((left, right) => {
|
|
4590
4901
|
const leftRatio = normalizedMetricValue(left);
|
|
4591
4902
|
const rightRatio = normalizedMetricValue(right);
|
|
4592
4903
|
if (leftRatio !== rightRatio) return leftRatio - rightRatio;
|
|
@@ -4598,17 +4909,8 @@ function normalizedMetricValue(metric2) {
|
|
|
4598
4909
|
if (metric2.max === void 0) return 1;
|
|
4599
4910
|
return Math.min(metric2.value / metric2.max, 1);
|
|
4600
4911
|
}
|
|
4601
|
-
function formatMetricMember(metric2) {
|
|
4602
|
-
|
|
4603
|
-
const maximum = metric2.max;
|
|
4604
|
-
if (maximum === void 0) return `${metric2.label} ${formatMetricValue(metric2.value)}${unit}`;
|
|
4605
|
-
if (metric2.kind === "saturating_count" && metric2.value > maximum) {
|
|
4606
|
-
return `${metric2.label} ${formatMetricValue(maximum)}${unit} capped (${formatMetricValue(metric2.value)} raw)`;
|
|
4607
|
-
}
|
|
4608
|
-
return `${metric2.label} ${formatMetricValue(metric2.value)}/${formatMetricValue(maximum)}${unit}`;
|
|
4609
|
-
}
|
|
4610
|
-
function formatMetricValue(value) {
|
|
4611
|
-
return Number.isInteger(value) ? String(value) : value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
4912
|
+
function formatMetricMember(criterion, metric2) {
|
|
4913
|
+
return `${formatCertificateMetricLabel(metric2)} ${formatCertificateMetricValue(criterion, metric2)}`;
|
|
4612
4914
|
}
|
|
4613
4915
|
|
|
4614
4916
|
// src/witan/html.ts
|
|
@@ -4624,6 +4926,7 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
4624
4926
|
const externalFindings = collectExternalFindings(report.consumedSignals ?? []);
|
|
4625
4927
|
const coverage = computeMeasuredCoverage(report);
|
|
4626
4928
|
const gitHistoryUnavailable = !report.repo.headSha;
|
|
4929
|
+
const relyingPartySummary = buildRelyingPartySummary(report);
|
|
4627
4930
|
return `<!doctype html>
|
|
4628
4931
|
<html lang="en">
|
|
4629
4932
|
<head>
|
|
@@ -4666,6 +4969,8 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
4666
4969
|
</div>
|
|
4667
4970
|
</header>
|
|
4668
4971
|
|
|
4972
|
+
${renderRelyingPartySummary(relyingPartySummary)}
|
|
4973
|
+
|
|
4669
4974
|
${(report.scanLimitations?.length ?? 0) > 0 ? `<section class="scan-limitations-section evidence-section" aria-label="Scan limitations">
|
|
4670
4975
|
<h2>Scan limitations</h2>
|
|
4671
4976
|
<ul>${(report.scanLimitations ?? []).map(
|
|
@@ -4698,11 +5003,32 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
4698
5003
|
<ul>${renderExternalFindingItems(externalFindings).join("")}</ul>
|
|
4699
5004
|
</article>
|
|
4700
5005
|
</section>` : ""}
|
|
5006
|
+
|
|
5007
|
+
${renderGlossary(glossaryEntriesForReport(report))}
|
|
4701
5008
|
</main>
|
|
4702
5009
|
</body>
|
|
4703
5010
|
</html>
|
|
4704
5011
|
`;
|
|
4705
5012
|
}
|
|
5013
|
+
function renderRelyingPartySummary(summary) {
|
|
5014
|
+
return `<section class="relying-party-summary" aria-labelledby="relying-party-heading">
|
|
5015
|
+
<h2 id="relying-party-heading">How to read this certificate</h2>
|
|
5016
|
+
<dl>
|
|
5017
|
+
<div><dt>What was examined</dt><dd>${escapeHtml(summary.examined)}</dd></div>
|
|
5018
|
+
<div><dt>What was established</dt><dd>${escapeHtml(summary.established)}</dd></div>
|
|
5019
|
+
<div><dt>What was not established</dt><dd>${escapeHtml(summary.notEstablished)}</dd></div>
|
|
5020
|
+
<div><dt>What to do next</dt><dd>${escapeHtml(summary.next)}</dd></div>
|
|
5021
|
+
</dl>
|
|
5022
|
+
</section>`;
|
|
5023
|
+
}
|
|
5024
|
+
function renderGlossary(entries) {
|
|
5025
|
+
return `<section class="glossary" aria-labelledby="glossary-heading">
|
|
5026
|
+
<h2 id="glossary-heading">Plain-language glossary</h2>
|
|
5027
|
+
<dl>${entries.map(
|
|
5028
|
+
(entry) => `<div><dt id="glossary-${escapeAttribute(entry.key)}">${escapeHtml(entry.term)}</dt><dd>${escapeHtml(entry.definition)}</dd></div>`
|
|
5029
|
+
).join("")}</dl>
|
|
5030
|
+
</section>`;
|
|
5031
|
+
}
|
|
4706
5032
|
function renderContentReadSummary(report) {
|
|
4707
5033
|
const summary = report.contentReadSummary;
|
|
4708
5034
|
if (!summary || summary.skipped === 0) return "";
|
|
@@ -4791,7 +5117,7 @@ function renderCriterionCard(criterion, gitHistoryUnavailable) {
|
|
|
4791
5117
|
...criterion.evidence.map(renderEvidencePointer),
|
|
4792
5118
|
...criterion.findings.map((finding) => renderFindingEvidence(criterion, finding))
|
|
4793
5119
|
];
|
|
4794
|
-
const metrics = criterion.metrics.map(renderMetric);
|
|
5120
|
+
const metrics = criterion.metrics.map((metric2) => renderMetric(criterion, metric2));
|
|
4795
5121
|
const statusReconciliation = renderStatusReconciliation(criterion);
|
|
4796
5122
|
const historyWarning = gitHistoryUnavailable && criterion.metrics.some((metric2) => metric2.name === "pr_merge_ratio") ? `<p class="scan-warning"><strong>Scan limitation:</strong> Git history was unavailable (for example, this may be a source tarball), so the recent PR merge ratio is not a measured repository outcome and its displayed zero may undercount ${escapeHtml(criterion.id)}. Re-scan a full Git clone for this signal.</p>` : "";
|
|
4797
5123
|
return `<article class="criterion">
|
|
@@ -4800,7 +5126,7 @@ function renderCriterionCard(criterion, gitHistoryUnavailable) {
|
|
|
4800
5126
|
<div class="criterion-id">${escapeHtml(criterion.id)}</div>
|
|
4801
5127
|
<h3>${escapeHtml(criterion.title)}</h3>
|
|
4802
5128
|
</div>
|
|
4803
|
-
${renderStatusChip(criterion.status)}
|
|
5129
|
+
${renderStatusChip(criterion.id, criterion.status)}
|
|
4804
5130
|
</div>
|
|
4805
5131
|
<div class="criterion-score">${criterion.status === "not_applicable" ? "N/A" : criterion.status === "insufficient_data" ? "No data" : formatScore(criterion.score)}</div>
|
|
4806
5132
|
${statusReconciliation}
|
|
@@ -4819,7 +5145,9 @@ function renderStatusReconciliation(criterion) {
|
|
|
4819
5145
|
}
|
|
4820
5146
|
const numericBand = scoreBandForPresentation(criterion.score);
|
|
4821
5147
|
if (numericBand === criterion.status) return "";
|
|
4822
|
-
|
|
5148
|
+
const entry = CERTIFICATE_GLOSSARY.find((candidate) => candidate.key === "labels");
|
|
5149
|
+
const help = entry ? `<span class="term-help" tabindex="0" aria-describedby="tooltip-${criterion.id}-labels"><strong>Why the labels differ:</strong><span class="term-tooltip" id="tooltip-${criterion.id}-labels" role="tooltip">${escapeHtml(entry.definition)}</span></span>` : "<strong>Why the labels differ:</strong>";
|
|
5150
|
+
return `<p class="status-explanation">${help} the ${escapeHtml(criterion.status)} dimension band is calibrated from criterion evidence thresholds and findings, independently of the ${formatScore(criterion.score)}/4.0 weighted score (which falls in the ${numericBand} numeric band).</p>`;
|
|
4823
5151
|
}
|
|
4824
5152
|
function scoreBandForPresentation(score) {
|
|
4825
5153
|
if (score >= 3.5) return "verified";
|
|
@@ -4827,20 +5155,24 @@ function scoreBandForPresentation(score) {
|
|
|
4827
5155
|
if (score >= 1.5) return "warning";
|
|
4828
5156
|
return "critical";
|
|
4829
5157
|
}
|
|
4830
|
-
function renderMetric(metric2) {
|
|
4831
|
-
const
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
const
|
|
4836
|
-
return
|
|
5158
|
+
function renderMetric(criterion, metric2) {
|
|
5159
|
+
const entry = glossaryEntryForMetric(metric2);
|
|
5160
|
+
const tooltipId = `tooltip-${criterion.id}-${entry.key}`;
|
|
5161
|
+
const formatted = formatCertificateMetricValue(criterion, metric2);
|
|
5162
|
+
const cappedEntry = formatted.includes("capped") ? CERTIFICATE_GLOSSARY.find((candidate) => candidate.key === "capped") : void 0;
|
|
5163
|
+
const label = `<span class="term-help" tabindex="0" aria-describedby="${escapeAttribute(tooltipId)}"><strong>${escapeHtml(formatCertificateMetricLabel(metric2))}</strong><span class="term-tooltip" id="${escapeAttribute(tooltipId)}" role="tooltip">${escapeHtml(entry.definition)}</span></span>`;
|
|
5164
|
+
if (!cappedEntry) return `${label}<span>${escapeHtml(formatted)}</span>`;
|
|
5165
|
+
const cappedTooltipId = `${tooltipId}-capped`;
|
|
5166
|
+
return `${label}<span class="term-help metric-value" tabindex="0" aria-describedby="${escapeAttribute(cappedTooltipId)}">${escapeHtml(formatted)}<span class="term-tooltip" id="${escapeAttribute(cappedTooltipId)}" role="tooltip">${escapeHtml(cappedEntry.definition)}</span></span>`;
|
|
4837
5167
|
}
|
|
4838
5168
|
function renderCategoryScore(score, coverage, category) {
|
|
4839
5169
|
const categoryCoverage = coverage.byCategory.find((entry) => entry.category === category);
|
|
4840
5170
|
return categoryCoverage !== void 0 && categoryCoverage.measured === 0 || score === null ? "not measured" : formatScore(score);
|
|
4841
5171
|
}
|
|
4842
|
-
function renderStatusChip(status) {
|
|
4843
|
-
|
|
5172
|
+
function renderStatusChip(criterionId, status) {
|
|
5173
|
+
const entry = CERTIFICATE_GLOSSARY.find((candidate) => candidate.key === "labels");
|
|
5174
|
+
const tooltipId = `tooltip-${criterionId}-status-labels`;
|
|
5175
|
+
return `<span class="status term-help" data-status="${status}" tabindex="0" aria-describedby="${tooltipId}">dimension band: ${status}${entry ? `<span class="term-tooltip" id="${tooltipId}" role="tooltip">${escapeHtml(entry.definition)}</span>` : ""}</span>`;
|
|
4844
5176
|
}
|
|
4845
5177
|
function renderEvidenceListItem(criterion, evidence) {
|
|
4846
5178
|
return `<li><strong>${escapeHtml(criterion.id)} - ${escapeHtml(criterion.title)}</strong><span>${renderEvidencePointer(evidence)}</span></li>`;
|
|
@@ -4904,9 +5236,6 @@ function renderMachineVerdict(verdict) {
|
|
|
4904
5236
|
function formatScore(score) {
|
|
4905
5237
|
return score.toFixed(1);
|
|
4906
5238
|
}
|
|
4907
|
-
function formatMetricValue2(value) {
|
|
4908
|
-
return Number.isInteger(value) ? value.toString() : value.toFixed(2);
|
|
4909
|
-
}
|
|
4910
5239
|
function formatDate(value) {
|
|
4911
5240
|
return value.slice(0, 10);
|
|
4912
5241
|
}
|
|
@@ -4982,6 +5311,13 @@ dd { margin: 0; color: var(--muted); overflow-wrap: anywhere; }
|
|
|
4982
5311
|
.subscores { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; color: var(--muted); font-family: var(--mono); font-size: 12px; }
|
|
4983
5312
|
.coverage-note { margin-top: 8px; color: var(--faint); font-family: var(--mono); font-size: 11px; }
|
|
4984
5313
|
.source-counts { margin: 8px 0 0; padding-left: 18px; font-family: var(--mono); font-size: 12px; color: var(--muted); }
|
|
5314
|
+
.relying-party-summary, .glossary { margin-top: 28px; border: 1px solid var(--line-strong); border-radius: 8px; background: var(--surface); padding: 22px; }
|
|
5315
|
+
.relying-party-summary dl { display: grid; gap: 14px; margin: 0; }
|
|
5316
|
+
.relying-party-summary dl div { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 18px; }
|
|
5317
|
+
.relying-party-summary dt, .glossary dt { color: var(--text); }
|
|
5318
|
+
.glossary dl { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 22px; margin: 0; }
|
|
5319
|
+
.glossary dl div { border-top: 1px solid var(--line); padding-top: 10px; }
|
|
5320
|
+
.glossary dd { margin-top: 4px; font-size: 13px; }
|
|
4985
5321
|
.trust-grid, .evidence-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 28px; }
|
|
4986
5322
|
.scan-limitations-section { margin-top: 28px; border-color: rgba(231, 191, 114, .44); }
|
|
4987
5323
|
.scan-limitations-section .scan-warning { list-style: none; margin-left: -18px; }
|
|
@@ -5011,6 +5347,19 @@ h3 { font-size: 15px; line-height: 1.35; font-weight: 600; margin-bottom: 0; }
|
|
|
5011
5347
|
.criterion-metrics li { display: flex; justify-content: space-between; gap: 12px; border-bottom: 1px solid rgba(238, 244, 247, .08); padding-bottom: 6px; }
|
|
5012
5348
|
.criterion-metrics strong { color: var(--text); font-weight: 650; }
|
|
5013
5349
|
.criterion-metrics span { white-space: nowrap; color: var(--muted); }
|
|
5350
|
+
.term-help { position: relative; cursor: help; outline: none; }
|
|
5351
|
+
.term-help:focus-visible { box-shadow: 0 0 0 2px var(--periwinkle); border-radius: 3px; }
|
|
5352
|
+
.term-tooltip {
|
|
5353
|
+
position: absolute; z-index: 100; left: 0; bottom: calc(100% + 8px); width: min(320px, 75vw);
|
|
5354
|
+
visibility: hidden; opacity: 0; pointer-events: none; white-space: normal;
|
|
5355
|
+
border: 1px solid var(--line-strong); border-radius: 6px; padding: 9px 11px;
|
|
5356
|
+
background: #101b2a; color: var(--text); font-family: var(--sans); font-size: 12px;
|
|
5357
|
+
font-weight: 400; line-height: 1.45; text-transform: none; letter-spacing: normal;
|
|
5358
|
+
box-shadow: 0 10px 28px rgba(0, 0, 0, .55);
|
|
5359
|
+
transition: opacity .12s ease;
|
|
5360
|
+
}
|
|
5361
|
+
.term-help:hover > .term-tooltip, .term-help:focus-within > .term-tooltip { visibility: visible; opacity: 1; }
|
|
5362
|
+
.metric-value .term-tooltip { left: auto; right: 0; }
|
|
5014
5363
|
.status { flex: none; border-radius: 999px; padding: 4px 9px; font-family: var(--mono); font-size: 11px; border: 1px solid var(--line-strong); }
|
|
5015
5364
|
.status[data-status="verified"] { color: var(--teal); background: var(--teal-weak); border-color: rgba(47, 200, 166, .42); }
|
|
5016
5365
|
.status[data-status="info"] { color: var(--periwinkle); background: var(--periwinkle-weak); border-color: rgba(147, 166, 218, .4); }
|
|
@@ -5029,6 +5378,7 @@ a:hover { text-decoration: underline; text-underline-offset: 2px; }
|
|
|
5029
5378
|
@media (max-width: 840px) {
|
|
5030
5379
|
.certificate { padding: 28px 18px 48px; }
|
|
5031
5380
|
.hero-grid, .trust-grid, .evidence-grid { grid-template-columns: 1fr; }
|
|
5381
|
+
.relying-party-summary dl div, .glossary dl { grid-template-columns: 1fr; }
|
|
5032
5382
|
h1 { font-size: 46px; }
|
|
5033
5383
|
}
|
|
5034
5384
|
`;
|
|
@@ -6784,7 +7134,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors) {
|
|
|
6784
7134
|
metrics: [
|
|
6785
7135
|
metric(
|
|
6786
7136
|
"prod_readiness_primitives",
|
|
6787
|
-
"Production-readiness
|
|
7137
|
+
"Production-readiness basic checks",
|
|
6788
7138
|
[
|
|
6789
7139
|
packageJson && (scripts.has("build") || scripts.has("typecheck")),
|
|
6790
7140
|
workflows.length > 0,
|
|
@@ -6795,8 +7145,8 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors) {
|
|
|
6795
7145
|
].filter(Boolean).length,
|
|
6796
7146
|
6,
|
|
6797
7147
|
0.55,
|
|
6798
|
-
"
|
|
6799
|
-
"Counts distinct static production-readiness
|
|
7148
|
+
"checks",
|
|
7149
|
+
"Counts distinct static production-readiness checks instead of treating presence as enough."
|
|
6800
7150
|
),
|
|
6801
7151
|
metric(
|
|
6802
7152
|
"prod_workflow_depth",
|
|
@@ -7137,7 +7487,7 @@ function collectB2PrTraceEvidence(repoPath, repoFiles) {
|
|
|
7137
7487
|
// sat well under half credit here even with a healthy PR-trace posture.
|
|
7138
7488
|
metric(
|
|
7139
7489
|
"pr_trace_primitives",
|
|
7140
|
-
"PR trace
|
|
7490
|
+
"PR trace basic checks",
|
|
7141
7491
|
workflows.length + (prTemplate ? 1 : 0) + (branchProtectionDoc ? 1 : 0),
|
|
7142
7492
|
2,
|
|
7143
7493
|
0.8,
|
|
@@ -10982,18 +11332,18 @@ function renderFindingSummary2(criterion, finding) {
|
|
|
10982
11332
|
if (drivers.length === 0) {
|
|
10983
11333
|
return `${criterion.id} dimension band is ${band} at ${score}/4.0. To improve this dimension, add stronger measurable evidence for ${criterion.title.toLowerCase()}.`;
|
|
10984
11334
|
}
|
|
10985
|
-
const measurements = drivers.map(formatMetricMember2).join("; ");
|
|
11335
|
+
const measurements = drivers.map((metric2) => formatMetricMember2(criterion, metric2)).join("; ");
|
|
10986
11336
|
const actions = Array.from(
|
|
10987
11337
|
new Set(
|
|
10988
11338
|
drivers.map(
|
|
10989
|
-
(metric2) => REMEDIATION_BY_METRIC2[metric2.name] ?? `raise the measured ${metric2.
|
|
11339
|
+
(metric2) => REMEDIATION_BY_METRIC2[metric2.name] ?? `raise the measured ${formatCertificateMetricLabel(metric2).toLowerCase()}`
|
|
10990
11340
|
)
|
|
10991
11341
|
)
|
|
10992
11342
|
).join("; ");
|
|
10993
11343
|
return `${criterion.id} dimension band is ${band} at ${score}/4.0. Lowest contributing measurements: ${measurements}. To improve: ${actions}.`;
|
|
10994
11344
|
}
|
|
10995
11345
|
function selectLowestDrivers2(metrics) {
|
|
10996
|
-
return [...metrics].filter((metric2) => metric2.max !== void 0).sort((left, right) => {
|
|
11346
|
+
return [...metrics].filter((metric2) => metric2.max !== void 0 && normalizedMetricValue2(metric2) < 1).sort((left, right) => {
|
|
10997
11347
|
const leftRatio = normalizedMetricValue2(left);
|
|
10998
11348
|
const rightRatio = normalizedMetricValue2(right);
|
|
10999
11349
|
if (leftRatio !== rightRatio) return leftRatio - rightRatio;
|
|
@@ -11005,17 +11355,8 @@ function normalizedMetricValue2(metric2) {
|
|
|
11005
11355
|
if (metric2.max === void 0) return 1;
|
|
11006
11356
|
return Math.min(metric2.value / metric2.max, 1);
|
|
11007
11357
|
}
|
|
11008
|
-
function formatMetricMember2(metric2) {
|
|
11009
|
-
|
|
11010
|
-
const maximum = metric2.max;
|
|
11011
|
-
if (maximum === void 0) return `${metric2.label} ${formatMetricValue3(metric2.value)}${unit}`;
|
|
11012
|
-
if (metric2.kind === "saturating_count" && metric2.value > maximum) {
|
|
11013
|
-
return `${metric2.label} ${formatMetricValue3(maximum)}${unit} capped (${formatMetricValue3(metric2.value)} raw)`;
|
|
11014
|
-
}
|
|
11015
|
-
return `${metric2.label} ${formatMetricValue3(metric2.value)}/${formatMetricValue3(maximum)}${unit}`;
|
|
11016
|
-
}
|
|
11017
|
-
function formatMetricValue3(value) {
|
|
11018
|
-
return Number.isInteger(value) ? String(value) : value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
11358
|
+
function formatMetricMember2(criterion, metric2) {
|
|
11359
|
+
return `${formatCertificateMetricLabel(metric2)} ${formatCertificateMetricValue(criterion, metric2)}`;
|
|
11019
11360
|
}
|
|
11020
11361
|
|
|
11021
11362
|
// src/summary.ts
|
|
@@ -11104,6 +11445,10 @@ export {
|
|
|
11104
11445
|
external_exports,
|
|
11105
11446
|
WitanReportSchema,
|
|
11106
11447
|
formatExternalSourceLine,
|
|
11448
|
+
CERTIFICATE_GLOSSARY,
|
|
11449
|
+
formatCertificateMetricLabel,
|
|
11450
|
+
formatCertificateMetricValue,
|
|
11451
|
+
buildRelyingPartySummary,
|
|
11107
11452
|
renderWitanHtmlReport,
|
|
11108
11453
|
renderWitanBadgeEndpoint,
|
|
11109
11454
|
renderWitanBadgeSvg,
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
import { createRequire as __cejelCreateRequire } from 'node:module';
|
|
3
3
|
const require = __cejelCreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
|
+
CERTIFICATE_GLOSSARY,
|
|
5
6
|
WitanReportSchema,
|
|
7
|
+
buildRelyingPartySummary,
|
|
6
8
|
createWitanAttestation,
|
|
9
|
+
formatCertificateMetricLabel,
|
|
10
|
+
formatCertificateMetricValue,
|
|
7
11
|
formatExternalSourceLine,
|
|
8
12
|
renderWitanBadgeEndpoint,
|
|
9
13
|
renderWitanBadgeSvg,
|
|
@@ -11,7 +15,7 @@ import {
|
|
|
11
15
|
runCejelScan,
|
|
12
16
|
serializeWitanReport,
|
|
13
17
|
verifyWitanAttestationBinding
|
|
14
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-ITHPLYKX.js";
|
|
15
19
|
import {
|
|
16
20
|
init_esm_shims
|
|
17
21
|
} from "./chunk-LZVKQV3R.js";
|
|
@@ -39,7 +43,7 @@ function renderMinScoreLimitationFailure(summary, minScore) {
|
|
|
39
43
|
return `Cejel: cannot evaluate the required minimum ${minScore.toFixed(1)}/4.0 because the scan has limited evidence (${summary.scanLimitations.length} limitation${summary.scanLimitations.length === 1 ? "" : "s"}). Resolve the reported limitations and re-scan.
|
|
40
44
|
`;
|
|
41
45
|
}
|
|
42
|
-
function renderTerminalCertificate(summary) {
|
|
46
|
+
function renderTerminalCertificate(summary, report) {
|
|
43
47
|
const abstained = summary.verdict === "Insufficient source" || summary.verdict === "Insufficient evidence";
|
|
44
48
|
if (!abstained && (summary.overallScore === null || summary.codeTrustScore === null || summary.processTrustScore === null)) {
|
|
45
49
|
throw new Error("A scored Cejel summary must carry numeric headline scores.");
|
|
@@ -58,6 +62,18 @@ function renderTerminalCertificate(summary) {
|
|
|
58
62
|
` Process trust: ${formatScore(summary.processTrustScore)}/4.0`,
|
|
59
63
|
""
|
|
60
64
|
];
|
|
65
|
+
if (report) {
|
|
66
|
+
const relyingPartySummary = buildRelyingPartySummary(report);
|
|
67
|
+
lines.splice(
|
|
68
|
+
2,
|
|
69
|
+
0,
|
|
70
|
+
`What was examined: ${relyingPartySummary.examined}`,
|
|
71
|
+
`What was established: ${relyingPartySummary.established}`,
|
|
72
|
+
`What was not established: ${relyingPartySummary.notEstablished}`,
|
|
73
|
+
`What to do next: ${relyingPartySummary.next}`,
|
|
74
|
+
""
|
|
75
|
+
);
|
|
76
|
+
}
|
|
61
77
|
if (summary.scanLimitations.length > 0) {
|
|
62
78
|
lines.push(
|
|
63
79
|
"LIMITED EVIDENCE \u2014 the score and verdict are qualified by scan limitations:",
|
|
@@ -85,6 +101,20 @@ function renderTerminalCertificate(summary) {
|
|
|
85
101
|
}
|
|
86
102
|
lines.push("");
|
|
87
103
|
}
|
|
104
|
+
const measurements = report?.criteria.flatMap(
|
|
105
|
+
(criterion) => criterion.metrics.map((metric) => ({
|
|
106
|
+
criterionId: criterion.id,
|
|
107
|
+
label: formatCertificateMetricLabel(metric),
|
|
108
|
+
value: formatCertificateMetricValue(criterion, metric)
|
|
109
|
+
}))
|
|
110
|
+
);
|
|
111
|
+
if (!abstained && (measurements?.length ?? 0) > 0) {
|
|
112
|
+
lines.push("Measurements:");
|
|
113
|
+
for (const measurement of measurements ?? []) {
|
|
114
|
+
lines.push(` ${measurement.criterionId} \u2014 ${measurement.label}: ${measurement.value}`);
|
|
115
|
+
}
|
|
116
|
+
lines.push("");
|
|
117
|
+
}
|
|
88
118
|
if (abstained) {
|
|
89
119
|
lines.push("See report.json / certificate.html for the full per-criterion detail.");
|
|
90
120
|
} else if (summary.topFindings.length === 0) {
|
|
@@ -118,6 +148,10 @@ function renderTerminalCertificate(summary) {
|
|
|
118
148
|
lines.push(` ...and ${remaining} more \u2014 see report.json for the full list.`);
|
|
119
149
|
}
|
|
120
150
|
}
|
|
151
|
+
lines.push("", "Plain-language glossary:");
|
|
152
|
+
for (const entry of CERTIFICATE_GLOSSARY) {
|
|
153
|
+
lines.push(` ${entry.term} \u2014 ${entry.definition}`);
|
|
154
|
+
}
|
|
121
155
|
return `${lines.join("\n")}
|
|
122
156
|
`;
|
|
123
157
|
}
|
|
@@ -271,7 +305,7 @@ async function runWitanFreeCli(args) {
|
|
|
271
305
|
writeFileSync(join(options.outDir, "badge.svg"), renderWitanBadgeSvg(report), "utf8");
|
|
272
306
|
writeFileSync(join(options.outDir, "summary.json"), JSON.stringify(summary, null, 2), "utf8");
|
|
273
307
|
if (!options.quiet) {
|
|
274
|
-
process.stdout.write(renderTerminalCertificate(summary));
|
|
308
|
+
process.stdout.write(renderTerminalCertificate(summary, report));
|
|
275
309
|
process.stdout.write(
|
|
276
310
|
`
|
|
277
311
|
Wrote:
|
package/dist/mcp/index.js
CHANGED