@cejel/cejel 0.4.4 → 0.4.5
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 +28 -6
- package/dist/calibration/llm-detector.js +1 -1
- package/dist/{chunk-PJEKYFVM.js → chunk-4IXESHG7.js} +416 -138
- package/dist/index.js +55 -5
- package/dist/mcp/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ if (-not $expected -or $actual -ne $expected.ToLowerInvariant()) {
|
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
> **Windows signing status:** `cejel-Windows-x86_64.exe` is intentionally unsigned in
|
|
82
|
-
> 0.4.
|
|
82
|
+
> 0.4.5 and may trigger Microsoft SmartScreen. The release build removes Node's inherited
|
|
83
83
|
> signature before SEA injection and fails unless Windows reports the result as `NotSigned`;
|
|
84
84
|
> it does not ship an invalid signature. Before running it, verify `SHA256SUMS` and the
|
|
85
85
|
> GitHub build-provenance attestation. Each binary also has an attached SPDX SBOM and an
|
|
@@ -102,7 +102,7 @@ docker run --rm --network=none -v "$PWD:/w" -w /w -v "$PWD/cejel:/cejel:ro" debi
|
|
|
102
102
|
npx @cejel/cejel@latest .
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
> **Distribution note:** Cejel `0.4.
|
|
105
|
+
> **Distribution note:** Cejel `0.4.5` is the coordinated release version for npm,
|
|
106
106
|
> standalone binaries, Docker/OCI, GitHub Action, Homebrew, and MCP Registry.
|
|
107
107
|
|
|
108
108
|
`npx` can reuse a stale cached package. Force the current npm release with the `@latest`
|
|
@@ -119,6 +119,16 @@ name while keeping the repository-derived stable slug:
|
|
|
119
119
|
npx @cejel/cejel@latest . --name "Customer Portal"
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
From 0.4.5, use `--product-name` when two checkout directories must emit the
|
|
123
|
+
same identity fields. The supplied value sets both the display name and its slugified stable ID:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npx @cejel/cejel@latest . --product-name "Customer Portal"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Product identity is caller context, not scored repository evidence, and is excluded from
|
|
130
|
+
certificate byte-comparison claims.
|
|
131
|
+
|
|
122
132
|
The npm package is scoped as `@cejel/cejel`; its executable remains the short command `cejel`.
|
|
123
133
|
|
|
124
134
|
**GitHub Action** — score every PR and publish the badge:
|
|
@@ -151,16 +161,16 @@ gh attestation verify ./cejel-Windows-x86_64.exe -R BargLabs/cejel
|
|
|
151
161
|
This is cryptographically signed provenance. It is distinct from Apple Developer ID or
|
|
152
162
|
Microsoft Authenticode code-signing.
|
|
153
163
|
|
|
154
|
-
**Docker / OCI.** The current container release is `0.4.
|
|
164
|
+
**Docker / OCI.** The current container release is `0.4.5`:
|
|
155
165
|
|
|
156
166
|
```bash
|
|
157
|
-
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.4.
|
|
167
|
+
docker run --rm -i -v "$PWD:/workspace:ro" ghcr.io/barglabs/cejel:0.4.5
|
|
158
168
|
```
|
|
159
169
|
|
|
160
170
|
The image defaults to `cejel-mcp` over stdio. To use the CLI instead:
|
|
161
171
|
|
|
162
172
|
```bash
|
|
163
|
-
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.4.
|
|
173
|
+
docker run --rm -v "$PWD:/workspace:ro" --entrypoint cejel ghcr.io/barglabs/cejel:0.4.5 .
|
|
164
174
|
```
|
|
165
175
|
|
|
166
176
|
The OCI image carries an SBOM, maximum-mode build provenance, and a signed registry
|
|
@@ -253,9 +263,21 @@ prints that boundary on every successful verification.
|
|
|
253
263
|
- `--min-score <n>` — exit non-zero if the overall score is below `n` (0–4), or if Cejel
|
|
254
264
|
abstains and therefore cannot evaluate the threshold; used by the GitHub Action's optional
|
|
255
265
|
threshold gate
|
|
266
|
+
- `--product-name <name>` — Set both the caller-context
|
|
267
|
+
display name and slugified product ID so differently named checkouts can emit identical
|
|
268
|
+
certificates.
|
|
269
|
+
- `--name <display>` — override only the displayed project name while leaving the derived slug
|
|
270
|
+
unchanged; retained for compatibility
|
|
256
271
|
- `--ingest <file|glob>` — fold another scanner's output into the score (repeatable). Accepts
|
|
257
272
|
SARIF, OpenSSF Scorecard JSON, or the generic Cejel external-signal shape — format is
|
|
258
273
|
auto-detected. See "Aggregate your scanners" below.
|
|
274
|
+
- `--rubric-pin <version>` — Explicit opt-in only: pin an alternate rubric instead of the
|
|
275
|
+
calibrated public default. It
|
|
276
|
+
accepts the calibrated version or any published prospective rubric
|
|
277
|
+
(`witan-rubric-v18-prospective-*` through `v22`) and fails closed, naming what was accepted, on
|
|
278
|
+
anything else. Omit this flag for the calibrated result every default scan produces — a
|
|
279
|
+
prospective pin carries no precision/recall claim, and the terminal certificate, HTML
|
|
280
|
+
certificate, and Markdown report all state that plainly whenever one is used.
|
|
259
281
|
- `--quiet` — suppress the terminal certificate (files are still written)
|
|
260
282
|
- `-h`, `--help` — print usage and exit successfully
|
|
261
283
|
- `-v`, `--version` — print the version derived from the package manifest and exit successfully
|
|
@@ -463,7 +485,7 @@ The OCI image is an alternative when Docker is the preferred execution boundary.
|
|
|
463
485
|
the host path with the repository OpenClaw should allow Cejel to read:
|
|
464
486
|
|
|
465
487
|
```bash
|
|
466
|
-
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.
|
|
488
|
+
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.5"]}'
|
|
467
489
|
npx -y openclaw@latest mcp doctor cejel-oci --probe
|
|
468
490
|
```
|
|
469
491
|
|
|
@@ -130,7 +130,16 @@ var WitanCriterionMetricSchema = external_exports.object({
|
|
|
130
130
|
kind: external_exports.enum(["ratio", "saturating_count"]).optional(),
|
|
131
131
|
weight: external_exports.number().positive().max(1).default(1),
|
|
132
132
|
unit: external_exports.string().min(1).max(40).optional(),
|
|
133
|
-
description: external_exports.string().min(1).max(300).optional()
|
|
133
|
+
description: external_exports.string().min(1).max(300).optional(),
|
|
134
|
+
presentation: external_exports.object({
|
|
135
|
+
components: external_exports.array(
|
|
136
|
+
external_exports.object({
|
|
137
|
+
label: external_exports.string().min(1).max(120),
|
|
138
|
+
count: external_exports.number().int().nonnegative()
|
|
139
|
+
}).strict()
|
|
140
|
+
).min(1).optional(),
|
|
141
|
+
condition: external_exports.string().min(1).max(240).optional()
|
|
142
|
+
}).strict().optional()
|
|
134
143
|
}).strict();
|
|
135
144
|
var WitanCriterionSignalSchema = external_exports.object({
|
|
136
145
|
criterionId: WitanCriterionIdSchema,
|
|
@@ -367,6 +376,45 @@ var WitanAttestationStatementSchema = external_exports.object({
|
|
|
367
376
|
}).strict()
|
|
368
377
|
}).strict();
|
|
369
378
|
|
|
379
|
+
// src/witan/rubric-version.ts
|
|
380
|
+
init_esm_shims();
|
|
381
|
+
var WITAN_RUBRIC_VERSION_V9 = "witan-rubric-v9-2026-07-22";
|
|
382
|
+
var WITAN_RUBRIC_VERSION_V10 = "witan-rubric-v10-2026-07-23";
|
|
383
|
+
var WITAN_RUBRIC_VERSION_V11 = "witan-rubric-v11-2026-07-23";
|
|
384
|
+
var WITAN_RUBRIC_VERSION_V12 = "witan-rubric-v12-2026-07-23";
|
|
385
|
+
var WITAN_RUBRIC_VERSION_V13 = "witan-rubric-v13-2026-07-23";
|
|
386
|
+
var WITAN_RUBRIC_VERSION_V14 = "witan-rubric-v14-2026-07-23";
|
|
387
|
+
var WITAN_RUBRIC_VERSION_V15 = "witan-rubric-v15-2026-07-24";
|
|
388
|
+
var WITAN_RUBRIC_VERSION_V16 = "witan-rubric-v16-2026-07-24";
|
|
389
|
+
var WITAN_RUBRIC_VERSION_V17 = "witan-rubric-v17-2026-07-24";
|
|
390
|
+
var WITAN_RUBRIC_VERSION_V18 = "witan-rubric-v18-prospective-2026-07-25";
|
|
391
|
+
var WITAN_RUBRIC_VERSION_V19 = "witan-rubric-v19-prospective-2026-08-09";
|
|
392
|
+
var WITAN_RUBRIC_VERSION_V20 = "witan-rubric-v20-prospective-2026-08-10";
|
|
393
|
+
var WITAN_RUBRIC_VERSION_V21 = "witan-rubric-v21-prospective-2026-08-10";
|
|
394
|
+
var WITAN_RUBRIC_VERSION_V22 = "witan-rubric-v22-prospective-2026-08-10";
|
|
395
|
+
var WITAN_LAST_CALIBRATED_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V17;
|
|
396
|
+
var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
|
|
397
|
+
WITAN_RUBRIC_VERSION_V18,
|
|
398
|
+
WITAN_RUBRIC_VERSION_V19,
|
|
399
|
+
WITAN_RUBRIC_VERSION_V20,
|
|
400
|
+
WITAN_RUBRIC_VERSION_V21,
|
|
401
|
+
WITAN_RUBRIC_VERSION_V22
|
|
402
|
+
]);
|
|
403
|
+
var WITAN_SELECTABLE_RUBRIC_VERSIONS = Object.freeze([
|
|
404
|
+
WITAN_LAST_CALIBRATED_RUBRIC_VERSION,
|
|
405
|
+
...WITAN_PROSPECTIVE_RUBRIC_VERSIONS
|
|
406
|
+
]);
|
|
407
|
+
function assertSelectableRubricVersion(rubricVersion) {
|
|
408
|
+
if (WITAN_SELECTABLE_RUBRIC_VERSIONS.includes(rubricVersion)) return;
|
|
409
|
+
throw new Error(
|
|
410
|
+
`Cejel: unrecognized rubric version: "${rubricVersion}". Accepted values: ${WITAN_SELECTABLE_RUBRIC_VERSIONS.join(", ")}.`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
function isProspectiveRubricVersion(rubricVersion) {
|
|
414
|
+
return WITAN_PROSPECTIVE_RUBRIC_VERSIONS.includes(rubricVersion);
|
|
415
|
+
}
|
|
416
|
+
var PROSPECTIVE_RUBRIC_NOTICE = "PROSPECTIVE / UNCALIBRATED RUBRIC \u2014 this rubric has not cleared a calibration gate. Scores, verdicts, and findings produced under it carry no precision/recall claim and are not comparable to a calibrated certificate.";
|
|
417
|
+
|
|
370
418
|
// src/witan/html.ts
|
|
371
419
|
init_esm_shims();
|
|
372
420
|
|
|
@@ -476,6 +524,45 @@ init_esm_shims();
|
|
|
476
524
|
|
|
477
525
|
// src/witan/certificate-presentation.ts
|
|
478
526
|
init_esm_shims();
|
|
527
|
+
var CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE = "Product name and slug are caller context and are excluded from certificate byte-comparison claims.";
|
|
528
|
+
var CERTIFICATE_METRIC_REGISTRY = {
|
|
529
|
+
test_to_source_ratio: { displayLabel: "Test-to-source file ratio" },
|
|
530
|
+
coverage_percent: { displayLabel: "Static coverage percentage" },
|
|
531
|
+
verification_script_ratio: { displayLabel: "Verification script ratio" },
|
|
532
|
+
non_hollow_test_share: { displayLabel: "Non-hollow test share" },
|
|
533
|
+
secret_cleanliness: { displayLabel: "Secret cleanliness" },
|
|
534
|
+
env_handling_depth: { displayLabel: "Environment handling depth" },
|
|
535
|
+
rls_policy_count: { displayLabel: "RLS policy count" },
|
|
536
|
+
tenant_scope_ratio: { displayLabel: "Tenant-scoped schema ratio" },
|
|
537
|
+
crypto_comparison_hygiene: { displayLabel: "Crypto comparison hygiene" },
|
|
538
|
+
prod_readiness_primitives: { displayLabel: "Production-readiness basic checks" },
|
|
539
|
+
prod_workflow_depth: {
|
|
540
|
+
displayLabel: "Production workflow depth (automated build/test/release pipeline)"
|
|
541
|
+
},
|
|
542
|
+
observability_depth: { displayLabel: "Observability depth" },
|
|
543
|
+
rollback_safety_depth: { displayLabel: "Rollback and migration-safety depth" },
|
|
544
|
+
dependency_automation_ratio: { displayLabel: "Dependency automation ratio" },
|
|
545
|
+
pinned_dependency_ratio: { displayLabel: "Pinned dependency ratio" },
|
|
546
|
+
lockfile_coverage: { displayLabel: "Lockfile coverage" },
|
|
547
|
+
declared_version_range_ratio: { displayLabel: "Declared version range ratio" },
|
|
548
|
+
dependency_count_sanity: { displayLabel: "Dependency count sanity" },
|
|
549
|
+
claim_match_rate: { displayLabel: "Implementation-to-claim-source file ratio" },
|
|
550
|
+
claim_source_depth: { displayLabel: "Claim source depth" },
|
|
551
|
+
reconciliation_artifact_depth: { displayLabel: "Reconciliation artifact depth" },
|
|
552
|
+
pr_trace_primitives: { displayLabel: "PR trace basic checks" },
|
|
553
|
+
pr_merge_ratio: { displayLabel: "Recent commits with recognizable PR references" },
|
|
554
|
+
ci_script_depth: { displayLabel: "CI verification depth" },
|
|
555
|
+
default_branch_ci_depth: {
|
|
556
|
+
displayLabel: "PR-gate CI workflow count (automated build/test pipeline)"
|
|
557
|
+
},
|
|
558
|
+
audit_artifact_depth: { displayLabel: "Audit artifact depth" },
|
|
559
|
+
audit_freshness_depth: { displayLabel: "Audit freshness depth" },
|
|
560
|
+
kill_switch_fail_safe_present: { displayLabel: "Un-overridable kill-switch present" },
|
|
561
|
+
human_gate_documented: { displayLabel: "Human gate documented" },
|
|
562
|
+
fail_closed_privilege_check: { displayLabel: "Fail-closed privilege check present" },
|
|
563
|
+
privilege_escalation_cleanliness: { displayLabel: "Privilege-escalation cleanliness" },
|
|
564
|
+
protected_path_review_gate: { displayLabel: "Protected-path review gate" }
|
|
565
|
+
};
|
|
479
566
|
var CERTIFICATE_GLOSSARY = [
|
|
480
567
|
{
|
|
481
568
|
key: "labels",
|
|
@@ -491,188 +578,194 @@ var CERTIFICATE_GLOSSARY = [
|
|
|
491
578
|
},
|
|
492
579
|
{
|
|
493
580
|
key: "test-to-source-file-ratio",
|
|
494
|
-
term: "
|
|
581
|
+
term: "Test-to-source file ratio",
|
|
495
582
|
definition: "The number of detected test files compared with detected source files. A larger implementation surface needs a correspondingly visible test surface.",
|
|
496
583
|
metricNames: ["test_to_source_ratio"]
|
|
497
584
|
},
|
|
498
585
|
{
|
|
499
586
|
key: "static-coverage",
|
|
500
|
-
term: "
|
|
587
|
+
term: "Static coverage percentage",
|
|
501
588
|
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.",
|
|
502
589
|
metricNames: ["coverage_percent"]
|
|
503
590
|
},
|
|
504
591
|
{
|
|
505
592
|
key: "verification-script",
|
|
506
|
-
term: "
|
|
507
|
-
definition: "
|
|
593
|
+
term: "Verification script ratio",
|
|
594
|
+
definition: "A capped scoring input that adds one for each detected test, coverage, lint, or type-check command and one for each detected test-runner configuration file. The raw count is capped at four for scoring. Build is not measured by this metric.",
|
|
508
595
|
metricNames: ["verification_script_ratio"]
|
|
509
596
|
},
|
|
510
597
|
{
|
|
511
598
|
key: "non-hollow",
|
|
512
|
-
term: "
|
|
599
|
+
term: "Non-hollow test share",
|
|
513
600
|
definition: "test files that actually assert something rather than being empty or skipped",
|
|
514
601
|
metricNames: ["non_hollow_test_share"]
|
|
515
602
|
},
|
|
516
603
|
{
|
|
517
604
|
key: "secrets",
|
|
518
|
-
term: "
|
|
519
|
-
definition: "
|
|
605
|
+
term: "Secret cleanliness",
|
|
606
|
+
definition: "A binary result. Clean means the static repository-tree and reachable-history scan produced no critical secret finding. It is not a count of files containing secrets.",
|
|
520
607
|
metricNames: ["secret_cleanliness"]
|
|
521
608
|
},
|
|
522
609
|
{
|
|
523
610
|
key: "environment-handling-depth",
|
|
524
|
-
term: "
|
|
525
|
-
definition: "
|
|
611
|
+
term: "Environment handling depth",
|
|
612
|
+
definition: "A count of three named practices: an environment template file, a .gitignore rule for environment files, and environment reads in implementation code in a supported language.",
|
|
526
613
|
metricNames: ["env_handling_depth"]
|
|
527
614
|
},
|
|
528
615
|
{
|
|
529
616
|
key: "rls-policy-count",
|
|
530
617
|
term: "RLS policy count",
|
|
531
|
-
definition: "
|
|
618
|
+
definition: "A selected-rubric static count of row-level-security markers. The calibrated detector counts CREATE POLICY, ENABLE ROW LEVEL SECURITY, and FORCE ROW LEVEL SECURITY matches. Prospective native-RLS detection counts repository-native CREATE POLICY definitions.",
|
|
532
619
|
metricNames: ["rls_policy_count"]
|
|
533
620
|
},
|
|
534
621
|
{
|
|
535
622
|
key: "tenant-scoped-schema-ratio",
|
|
536
|
-
term: "
|
|
537
|
-
definition: "The share of
|
|
623
|
+
term: "Tenant-scoped schema ratio",
|
|
624
|
+
definition: "The share of detected data-layer or migration files carrying the selected rubric's tenant-scope marker. Prospective native-RLS detection instead uses repository-native scoped storage files. It is a file-level static proxy, not a table-by-table proof.",
|
|
538
625
|
metricNames: ["tenant_scope_ratio"]
|
|
539
626
|
},
|
|
540
627
|
{
|
|
541
628
|
key: "crypto-comparison-hygiene",
|
|
542
|
-
term: "
|
|
543
|
-
definition: "
|
|
629
|
+
term: "Crypto comparison hygiene",
|
|
630
|
+
definition: "A conditional binary result shown only when production code exposes a signing, HMAC, or secret-comparison surface. Clean means no detected plain-equality secret comparison or non-canonical JSON signing pattern. Detected constant-time comparison and canonical serialization are positive evidence, but the metric does not require both when only one relevant surface exists.",
|
|
544
631
|
metricNames: ["crypto_comparison_hygiene"]
|
|
545
632
|
},
|
|
546
633
|
{
|
|
547
|
-
key: "basic-checks",
|
|
548
|
-
term: "basic checks",
|
|
549
|
-
definition: "
|
|
550
|
-
metricNames: ["prod_readiness_primitives"
|
|
634
|
+
key: "production-readiness-basic-checks",
|
|
635
|
+
term: "Production-readiness basic checks",
|
|
636
|
+
definition: "A count of six named checks: a build or type-check command, a CI workflow, deployment configuration, an environment template, a health or readiness signal, and an error boundary.",
|
|
637
|
+
metricNames: ["prod_readiness_primitives"]
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
key: "pr-trace-basic-checks",
|
|
641
|
+
term: "PR trace basic checks",
|
|
642
|
+
definition: "A capped scoring input that counts detected CI workflow files, a pull-request template, and a review-gate record such as CODEOWNERS or branch-protection documentation. The raw count is capped at two for scoring.",
|
|
643
|
+
metricNames: ["pr_trace_primitives"]
|
|
551
644
|
},
|
|
552
645
|
{
|
|
553
646
|
key: "workflow-depth",
|
|
554
|
-
term: "workflow depth",
|
|
555
|
-
definition: "
|
|
647
|
+
term: "Production workflow depth (automated build/test/release pipeline)",
|
|
648
|
+
definition: "The number of detected CI workflow files plus deployment-configuration files, capped at six for scoring. It counts files, not team policies or distinct pipeline stages.",
|
|
556
649
|
metricNames: ["prod_workflow_depth"]
|
|
557
650
|
},
|
|
558
651
|
{
|
|
559
652
|
key: "observability-depth",
|
|
560
|
-
term: "
|
|
561
|
-
definition: "The
|
|
653
|
+
term: "Observability depth",
|
|
654
|
+
definition: "The number of implementation files containing a recognized logging, metrics, tracing, or error-reporting marker, capped at four for scoring.",
|
|
562
655
|
metricNames: ["observability_depth"]
|
|
563
656
|
},
|
|
564
657
|
{
|
|
565
658
|
key: "rollback-and-migration-safety-depth",
|
|
566
|
-
term: "
|
|
567
|
-
definition: "The
|
|
659
|
+
term: "Rollback and migration-safety depth",
|
|
660
|
+
definition: "The number of files under recognized documentation, migration, or script paths containing a rollback, reversal, undo, or migration-safety marker, capped at four for scoring.",
|
|
568
661
|
metricNames: ["rollback_safety_depth"]
|
|
569
662
|
},
|
|
570
663
|
{
|
|
571
664
|
key: "dependency-automation-ratio",
|
|
572
|
-
term: "
|
|
665
|
+
term: "Dependency automation ratio",
|
|
573
666
|
definition: "The share of expected automated dependency-update and audit controls that are present. Stale or vulnerable packages otherwise depend on manual discovery.",
|
|
574
667
|
metricNames: ["dependency_automation_ratio"]
|
|
575
668
|
},
|
|
576
669
|
{
|
|
577
670
|
key: "pinned-dependency-ratio",
|
|
578
|
-
term: "
|
|
671
|
+
term: "Pinned dependency ratio",
|
|
579
672
|
definition: "The share of application dependencies fixed to exact versions. Exact inputs make deployed builds more reproducible.",
|
|
580
673
|
metricNames: ["pinned_dependency_ratio"]
|
|
581
674
|
},
|
|
582
675
|
{
|
|
583
676
|
key: "lockfile-coverage",
|
|
584
|
-
term: "
|
|
585
|
-
definition: "
|
|
677
|
+
term: "Lockfile coverage",
|
|
678
|
+
definition: "A binary check for at least one detected lockfile in the scanned package or its covering monorepo root. It does not compare each manifest with a separate lockfile.",
|
|
586
679
|
metricNames: ["lockfile_coverage"]
|
|
587
680
|
},
|
|
588
681
|
{
|
|
589
682
|
key: "declared-version-range-ratio",
|
|
590
|
-
term: "
|
|
591
|
-
definition: "
|
|
683
|
+
term: "Declared version range ratio",
|
|
684
|
+
definition: "For a library or CLI, the share of detected dependency specifications carrying any explicit constraint, including an exact version or a compatible range. Bare names, *, latest, and x do not count.",
|
|
592
685
|
metricNames: ["declared_version_range_ratio"]
|
|
593
686
|
},
|
|
594
687
|
{
|
|
595
688
|
key: "dependency-count-sanity",
|
|
596
|
-
term: "
|
|
597
|
-
definition: "A
|
|
689
|
+
term: "Dependency count sanity",
|
|
690
|
+
definition: "A low-weight library/CLI metric with full credit through 120 detected dependency specifications across manifests, then linearly declining to zero over the next 240.",
|
|
598
691
|
metricNames: ["dependency_count_sanity"]
|
|
599
692
|
},
|
|
600
693
|
{
|
|
601
694
|
key: "claim-match-rate",
|
|
602
|
-
term: "claim
|
|
603
|
-
definition: "
|
|
695
|
+
term: "Implementation-to-claim-source file ratio",
|
|
696
|
+
definition: "A bounded file-count ratio: detected implementation-file count divided by that count plus detected headline claim-source document count. It does not verify whether individual prose claims are true.",
|
|
604
697
|
metricNames: ["claim_match_rate"]
|
|
605
698
|
},
|
|
606
699
|
{
|
|
607
700
|
key: "claim-source-depth",
|
|
608
|
-
term: "
|
|
609
|
-
definition: "
|
|
701
|
+
term: "Claim source depth",
|
|
702
|
+
definition: "The number of detected README or headline claim-source documents, capped at four for scoring. It measures visible document depth, not the truth or specificity of their prose.",
|
|
610
703
|
metricNames: ["claim_source_depth"]
|
|
611
704
|
},
|
|
612
705
|
{
|
|
613
706
|
key: "reconciliation-artifact-depth",
|
|
614
|
-
term: "
|
|
615
|
-
definition: "The
|
|
707
|
+
term: "Reconciliation artifact depth",
|
|
708
|
+
definition: "The number of detected dedicated claim-reality reconciliation artifacts, capped at three for scoring.",
|
|
616
709
|
metricNames: ["reconciliation_artifact_depth"]
|
|
617
710
|
},
|
|
618
711
|
{
|
|
619
712
|
key: "recent-pr-merge-ratio",
|
|
620
|
-
term: "
|
|
621
|
-
definition: "
|
|
713
|
+
term: "Recent commits with recognizable PR references",
|
|
714
|
+
definition: "Among up to 12 recent local commit subjects, the share containing \u201Cmerge pull request\u201D, \u201Cpull request\u201D, or a #number reference. A zero means no recognizable reference was found, not that Cejel detected a merge without a PR. When history is empty or unavailable, scoring conservatively uses a denominator of one.",
|
|
622
715
|
metricNames: ["pr_merge_ratio"]
|
|
623
716
|
},
|
|
624
717
|
{
|
|
625
718
|
key: "ci-verification-depth",
|
|
626
719
|
term: "CI verification depth",
|
|
627
|
-
definition: "
|
|
720
|
+
definition: "A capped scoring input that adds package-script and CI-command detections separately for test, lint, type-check, and build. The same category can therefore contribute once as a package script and once in CI. The raw count is capped at four for scoring.",
|
|
628
721
|
metricNames: ["ci_script_depth"]
|
|
629
722
|
},
|
|
630
723
|
{
|
|
631
724
|
key: "pr-gate-ci-workflow-count",
|
|
632
|
-
term: "PR-gate CI workflow count",
|
|
633
|
-
definition: "
|
|
725
|
+
term: "PR-gate CI workflow count (automated build/test pipeline)",
|
|
726
|
+
definition: "The number of detected CI workflow files configured for pull requests or the main or master branch, including a covering monorepo-root workflow, capped at four for scoring.",
|
|
634
727
|
metricNames: ["default_branch_ci_depth"]
|
|
635
728
|
},
|
|
636
729
|
{
|
|
637
730
|
key: "audit-artifact-depth",
|
|
638
|
-
term: "
|
|
639
|
-
definition: "
|
|
731
|
+
term: "Audit artifact depth",
|
|
732
|
+
definition: "The number of files whose paths match recognized changelog, audit, incident, status, runbook, provenance, release-note, or security-policy forms, capped at three for scoring.",
|
|
640
733
|
metricNames: ["audit_artifact_depth"]
|
|
641
734
|
},
|
|
642
735
|
{
|
|
643
736
|
key: "audit-freshness-depth",
|
|
644
|
-
term: "
|
|
645
|
-
definition: "
|
|
737
|
+
term: "Audit freshness depth",
|
|
738
|
+
definition: "The share of detected audit-artifact files containing recent, latest, or current, or the applicable reference year. The calibrated rubric uses the scan year. Prospective v19 uses the scanned HEAD committer year when available. This is a text-marker proxy, not a file-history age check.",
|
|
646
739
|
metricNames: ["audit_freshness_depth"]
|
|
647
740
|
},
|
|
648
741
|
{
|
|
649
742
|
key: "un-overridable-kill-switch",
|
|
650
|
-
term: "
|
|
651
|
-
definition: "A
|
|
743
|
+
term: "Un-overridable kill-switch present",
|
|
744
|
+
definition: "A positive-only binary check for a named governance or safety toggle whose falsy state immediately returns or throws before lower-priority configuration can proceed. The metric is omitted when no such pattern is detected.",
|
|
652
745
|
metricNames: ["kill_switch_fail_safe_present"]
|
|
653
746
|
},
|
|
654
747
|
{
|
|
655
748
|
key: "human-gate-documented",
|
|
656
|
-
term: "
|
|
657
|
-
definition: "A
|
|
749
|
+
term: "Human gate documented",
|
|
750
|
+
definition: "A binary check for documentation saying privileged or credentialed operations are human-executed or human-gated and never agent-run. It is scored only when a privileged-operation-shaped surface exists.",
|
|
658
751
|
metricNames: ["human_gate_documented"]
|
|
659
752
|
},
|
|
660
753
|
{
|
|
661
754
|
key: "fail-closed-privilege-check",
|
|
662
|
-
term: "
|
|
663
|
-
definition: "A
|
|
755
|
+
term: "Fail-closed privilege check present",
|
|
756
|
+
definition: "A binary check for implementation code that checks role membership and fails closed before setting or elevating a role. It is scored only when a privileged-operation-shaped surface exists.",
|
|
664
757
|
metricNames: ["fail_closed_privilege_check"]
|
|
665
758
|
},
|
|
666
759
|
{
|
|
667
760
|
key: "privilege-escalation-cleanliness",
|
|
668
|
-
term: "
|
|
669
|
-
definition: "
|
|
761
|
+
term: "Privilege-escalation cleanliness",
|
|
762
|
+
definition: "A binary result. Clean means the selected rubric detected no ungated production privilege-escalation pattern. The calibrated detector checks executed role-membership grants and SUPERUSER escalation. Prospective v21 also checks authored administrative SQL and direct database-driver literals for administrative role grants and schema-wide table grants. Tests and fixtures are excluded from the metric.",
|
|
670
763
|
metricNames: ["privilege_escalation_cleanliness"]
|
|
671
764
|
},
|
|
672
765
|
{
|
|
673
766
|
key: "protected-path-review-gate",
|
|
674
|
-
term: "
|
|
675
|
-
definition: "A
|
|
767
|
+
term: "Protected-path review gate",
|
|
768
|
+
definition: "A binary check for a CODEOWNERS file or documentation of required review or branch protection. This is the repository-visible proxy for human review of sensitive-path changes.",
|
|
676
769
|
metricNames: ["protected_path_review_gate"]
|
|
677
770
|
}
|
|
678
771
|
];
|
|
@@ -689,14 +782,8 @@ function glossaryEntryForMetric(metric2) {
|
|
|
689
782
|
metricNames: [metric2.name]
|
|
690
783
|
};
|
|
691
784
|
}
|
|
692
|
-
var CERTIFICATE_METRIC_LABELS = {
|
|
693
|
-
prod_readiness_primitives: "Production-readiness basic checks",
|
|
694
|
-
pr_trace_primitives: "PR trace basic checks",
|
|
695
|
-
prod_workflow_depth: "Production workflow depth (automated build/test/release pipeline)",
|
|
696
|
-
default_branch_ci_depth: "PR-gate CI workflow count (automated build/test pipeline)"
|
|
697
|
-
};
|
|
698
785
|
function formatCertificateMetricLabel(metric2) {
|
|
699
|
-
return
|
|
786
|
+
return CERTIFICATE_METRIC_REGISTRY[metric2.name]?.displayLabel ?? metric2.label;
|
|
700
787
|
}
|
|
701
788
|
function glossaryEntriesForReport(report) {
|
|
702
789
|
const entries = new Map(CERTIFICATE_GLOSSARY.map((entry) => [entry.key, entry]));
|
|
@@ -718,6 +805,62 @@ function labelAlreadyStatesUnit(label, unit) {
|
|
|
718
805
|
const escapedUnit = unit.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
719
806
|
return new RegExp(`(?:^|[\\s-])${escapedUnit}$`, "i").test(label.trim());
|
|
720
807
|
}
|
|
808
|
+
var BINARY_METRIC_COPY = {
|
|
809
|
+
secret_cleanliness: {
|
|
810
|
+
positive: "clean \u2014 no critical secret findings detected",
|
|
811
|
+
negative: "not clean \u2014 one or more critical secret findings detected"
|
|
812
|
+
},
|
|
813
|
+
crypto_comparison_hygiene: {
|
|
814
|
+
positive: "clean \u2014 no insecure comparison or serialization pattern detected",
|
|
815
|
+
negative: "not clean \u2014 an insecure comparison or serialization pattern was detected"
|
|
816
|
+
},
|
|
817
|
+
lockfile_coverage: {
|
|
818
|
+
positive: "present \u2014 at least one covering lockfile detected",
|
|
819
|
+
negative: "missing \u2014 no covering lockfile detected"
|
|
820
|
+
},
|
|
821
|
+
kill_switch_fail_safe_present: {
|
|
822
|
+
positive: "present \u2014 a fail-closed safety toggle was detected",
|
|
823
|
+
negative: "missing \u2014 no fail-closed safety toggle detected"
|
|
824
|
+
},
|
|
825
|
+
human_gate_documented: {
|
|
826
|
+
positive: "present \u2014 privileged operations are documented as human-executed or gated",
|
|
827
|
+
negative: "missing \u2014 no human-gate documentation detected"
|
|
828
|
+
},
|
|
829
|
+
fail_closed_privilege_check: {
|
|
830
|
+
positive: "present \u2014 role membership is checked fail-closed before elevation",
|
|
831
|
+
negative: "missing \u2014 no fail-closed pre-elevation role check detected"
|
|
832
|
+
},
|
|
833
|
+
privilege_escalation_cleanliness: {
|
|
834
|
+
positive: "clean \u2014 no ungated production privilege-escalation pattern detected",
|
|
835
|
+
negative: "not clean \u2014 an ungated production privilege-escalation pattern was detected"
|
|
836
|
+
},
|
|
837
|
+
protected_path_review_gate: {
|
|
838
|
+
positive: "present \u2014 CODEOWNERS or a documented required-review policy was detected",
|
|
839
|
+
negative: "missing \u2014 no CODEOWNERS or documented required-review policy detected"
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
var CONDITIONAL_METRIC_COPY = {
|
|
843
|
+
crypto_comparison_hygiene: "scored because a signing, HMAC, or secret-comparison surface was detected",
|
|
844
|
+
rls_policy_count: "scored because a multi-tenant data surface was detected",
|
|
845
|
+
tenant_scope_ratio: "scored because a multi-tenant data surface was detected",
|
|
846
|
+
human_gate_documented: "scored because a privileged-operation-shaped surface was detected",
|
|
847
|
+
fail_closed_privilege_check: "scored because a privileged-operation-shaped surface was detected",
|
|
848
|
+
kill_switch_fail_safe_present: "shown because a fail-closed kill switch was detected; this positive-only metric is omitted when absent"
|
|
849
|
+
};
|
|
850
|
+
function appendMetricCondition(metric2, value) {
|
|
851
|
+
const condition = metric2.presentation?.condition ?? CONDITIONAL_METRIC_COPY[metric2.name];
|
|
852
|
+
return condition ? `${value}; ${condition}` : value;
|
|
853
|
+
}
|
|
854
|
+
function formatEnumerableMetric(metric2) {
|
|
855
|
+
const components = metric2.presentation?.components;
|
|
856
|
+
if (!components || metric2.max === void 0) return null;
|
|
857
|
+
const present = components.filter((component) => component.count > 0).map(
|
|
858
|
+
(component) => component.count === 1 ? component.label : `${component.label} (${component.count} detected)`
|
|
859
|
+
);
|
|
860
|
+
const missing = components.filter((component) => component.count === 0).map((component) => component.label);
|
|
861
|
+
const value = metric2.kind === "saturating_count" && metric2.value > metric2.max ? `${formatMetricNumber(metric2.max)}/${formatMetricNumber(metric2.max)} (capped; ${formatMetricNumber(metric2.value)} raw)` : `${formatMetricNumber(metric2.value)}/${formatMetricNumber(metric2.max)}`;
|
|
862
|
+
return `${value} \u2014 present: ${present.length > 0 ? present.join(", ") : "none"}; missing: ${missing.length > 0 ? missing.join(", ") : "none"}`;
|
|
863
|
+
}
|
|
721
864
|
function formatCertificateMetricValue(criterion, metric2) {
|
|
722
865
|
if (isCoverageNotMeasured(criterion, metric2)) {
|
|
723
866
|
return "no coverage report found \u2014 not measured";
|
|
@@ -726,15 +869,36 @@ function formatCertificateMetricValue(criterion, metric2) {
|
|
|
726
869
|
const tests = formatMetricNumber(metric2.value);
|
|
727
870
|
const sources = formatMetricNumber(metric2.max);
|
|
728
871
|
const comparison = `${tests} test file${metric2.value === 1 ? "" : "s"} / ${sources} source file${metric2.max === 1 ? "" : "s"}`;
|
|
729
|
-
return
|
|
872
|
+
return appendMetricCondition(
|
|
873
|
+
metric2,
|
|
874
|
+
metric2.value > metric2.max ? `${comparison} (credit capped at parity)` : comparison
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
const binaryCopy = BINARY_METRIC_COPY[metric2.name];
|
|
878
|
+
if (binaryCopy && metric2.max === 1 && (metric2.value === 0 || metric2.value === 1)) {
|
|
879
|
+
return appendMetricCondition(metric2, metric2.value === 1 ? binaryCopy.positive : binaryCopy.negative);
|
|
880
|
+
}
|
|
881
|
+
if (metric2.name === "pr_merge_ratio" && metric2.max !== void 0) {
|
|
882
|
+
const value = metric2.value === 0 ? `none found in bounded recent commit subjects (scoring value 0/${formatMetricNumber(metric2.max)})` : `${formatMetricNumber(metric2.value)} of ${formatMetricNumber(metric2.max)} recent commit subjects contain a recognizable PR reference`;
|
|
883
|
+
return appendMetricCondition(metric2, value);
|
|
730
884
|
}
|
|
885
|
+
const enumerable = formatEnumerableMetric(metric2);
|
|
886
|
+
if (enumerable) return appendMetricCondition(metric2, enumerable);
|
|
731
887
|
const displayUnit = metric2.name === "prod_readiness_primitives" || metric2.name === "pr_trace_primitives" ? "checks" : labelAlreadyStatesUnit(formatCertificateMetricLabel(metric2), metric2.unit) ? void 0 : metric2.unit;
|
|
732
888
|
const unit = displayUnit ? ` ${displayUnit}` : "";
|
|
733
|
-
if (metric2.max === void 0)
|
|
889
|
+
if (metric2.max === void 0) {
|
|
890
|
+
return appendMetricCondition(metric2, `${formatMetricNumber(metric2.value)}${unit}`);
|
|
891
|
+
}
|
|
734
892
|
if (metric2.kind === "saturating_count" && metric2.value > metric2.max) {
|
|
735
|
-
return
|
|
893
|
+
return appendMetricCondition(
|
|
894
|
+
metric2,
|
|
895
|
+
`${formatMetricNumber(metric2.max)}${unit} (capped; ${formatMetricNumber(metric2.value)} raw)`
|
|
896
|
+
);
|
|
736
897
|
}
|
|
737
|
-
return
|
|
898
|
+
return appendMetricCondition(
|
|
899
|
+
metric2,
|
|
900
|
+
`${formatMetricNumber(metric2.value)}/${formatMetricNumber(metric2.max)}${unit}`
|
|
901
|
+
);
|
|
738
902
|
}
|
|
739
903
|
function buildRelyingPartySummary(report) {
|
|
740
904
|
const applicable = report.criteria.filter((criterion) => criterion.status !== "not_applicable");
|
|
@@ -749,6 +913,13 @@ function buildRelyingPartySummary(report) {
|
|
|
749
913
|
"No coverage report or configured threshold was found, so static coverage was not measured; Cejel does not run the subject's tests."
|
|
750
914
|
);
|
|
751
915
|
}
|
|
916
|
+
if (!report.repo.headSha && report.criteria.some(
|
|
917
|
+
(criterion) => criterion.metrics.some((metric2) => metric2.name === "pr_merge_ratio")
|
|
918
|
+
)) {
|
|
919
|
+
gaps.push(
|
|
920
|
+
"Git history was unavailable, so the recent-commit PR-reference proxy found no commit subjects to inspect; its conservative scoring zero is not a detected merge outcome."
|
|
921
|
+
);
|
|
922
|
+
}
|
|
752
923
|
const unmeasured = applicable.filter((criterion) => criterion.status === "insufficient_data");
|
|
753
924
|
if (unmeasured.length > 0) {
|
|
754
925
|
gaps.push(`The report did not measure ${unmeasured.map((criterion) => criterion.id).join(", ")}.`);
|
|
@@ -788,7 +959,7 @@ var METRIC_DERIVED_FINDING_PATTERN = /^[A-Z]\d+ metric-derived score is (\d+\.\d
|
|
|
788
959
|
var REMEDIATION_BY_METRIC = {
|
|
789
960
|
test_to_source_ratio: "add concrete regression tests for the implementation surface",
|
|
790
961
|
coverage_percent: "configure coverage and publish a measured threshold or report",
|
|
791
|
-
verification_script_ratio: "add
|
|
962
|
+
verification_script_ratio: "add the missing test, coverage, lint, or type-check command or a test-runner configuration",
|
|
792
963
|
non_hollow_test_share: "replace skipped or placeholder tests with live assertions",
|
|
793
964
|
env_handling_depth: "document and enforce safe environment-secret handling",
|
|
794
965
|
secret_cleanliness: "remove committed secrets and rotate any exposed credentials",
|
|
@@ -806,7 +977,7 @@ var REMEDIATION_BY_METRIC = {
|
|
|
806
977
|
claim_match_rate: "align documented product claims with observable repository evidence",
|
|
807
978
|
claim_source_depth: "document concrete, checkable product claims",
|
|
808
979
|
reconciliation_artifact_depth: "publish a claim-to-evidence reconciliation record",
|
|
809
|
-
pr_merge_ratio: "preserve
|
|
980
|
+
pr_merge_ratio: "preserve recognizable pull-request references in recent commit subjects",
|
|
810
981
|
pr_trace_primitives: "add pull-request templates and outcome-trace records",
|
|
811
982
|
ci_script_depth: "run the repository verification commands in CI",
|
|
812
983
|
default_branch_ci_depth: "run CI on the default branch and pull requests",
|
|
@@ -889,10 +1060,14 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
889
1060
|
<p class="eyebrow">Trust Certificate</p>
|
|
890
1061
|
<h1>${escapeHtml(report.productDisplayName)}</h1>
|
|
891
1062
|
<dl class="meta">
|
|
1063
|
+
<div><dt>Product identity</dt><dd>${escapeHtml(CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE)}</dd></div>
|
|
892
1064
|
${options.generatedAt ? `<div><dt>Date</dt><dd>${escapeHtml(formatDate(options.generatedAt))}</dd></div>` : ""}
|
|
893
1065
|
<div><dt>Run</dt><dd>${escapeHtml(renderRepo(report))}</dd></div>
|
|
894
1066
|
<div><dt>CLI</dt><dd>${escapeHtml(options.cliVersion ? `Cejel ${options.cliVersion}` : "Not recorded")}</dd></div>
|
|
895
1067
|
<div><dt>Rubric</dt><dd>${escapeHtml(report.rubricVersion)}</dd></div>
|
|
1068
|
+
${// Gated on the rubric actually being prospective — a calibrated (v17) certificate's
|
|
1069
|
+
// HTML stays byte-identical to before --rubric-pin existed.
|
|
1070
|
+
isProspectiveRubricVersion(report.rubricVersion) ? `<div><dt>Calibration</dt><dd class="prospective-rubric-notice">${escapeHtml(PROSPECTIVE_RUBRIC_NOTICE)}</dd></div>` : ""}
|
|
896
1071
|
${contributingSources.length > 0 ? `<div><dt>Sources</dt><dd>Incorporates findings from: ${escapeHtml(contributingSources.join(", "))}<ul class="source-counts">${externalSourceSummaries.map((s) => `<li>${escapeHtml(formatSourceProvenanceLine(s))}</li>`).join("")}</ul></dd></div>` : ""}
|
|
897
1072
|
</dl>
|
|
898
1073
|
</section>
|
|
@@ -1052,7 +1227,7 @@ function renderNotApplicableGroup(criteria) {
|
|
|
1052
1227
|
}
|
|
1053
1228
|
function renderNotApplicableItem(criterion) {
|
|
1054
1229
|
const reason = criterion.notes ?? "Not applicable to this repository.";
|
|
1055
|
-
return `<li><span class="na-id">${escapeHtml(criterion.id)}</span> ${escapeHtml(criterion.title)} \u2014 <span class="na-reason">${escapeHtml(reason)}</span></li>`;
|
|
1230
|
+
return `<li><span class="na-id">${escapeHtml(criterion.id)}</span> ${escapeHtml(criterion.title)} \u2014 <span class="na-reason">${escapeHtml(reason)}</span><span class="na-reconciliation"><strong>Why no label comparison applies:</strong> this dimension is not applicable to this repository, so it has no weighted numeric score band to compare.</span></li>`;
|
|
1056
1231
|
}
|
|
1057
1232
|
function renderCriterionCard(criterion, gitHistoryUnavailable) {
|
|
1058
1233
|
const evidence = [
|
|
@@ -1061,7 +1236,7 @@ function renderCriterionCard(criterion, gitHistoryUnavailable) {
|
|
|
1061
1236
|
];
|
|
1062
1237
|
const metrics = criterion.metrics.map((metric2) => renderMetric(criterion, metric2));
|
|
1063
1238
|
const statusReconciliation = renderStatusReconciliation(criterion);
|
|
1064
|
-
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
|
|
1239
|
+
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-commit PR-reference proxy found no commit subjects to inspect. Its conservative scoring zero is not a detected merge outcome and may undercount ${escapeHtml(criterion.id)}. Re-scan a full Git clone for this signal.</p>` : "";
|
|
1065
1240
|
return `<article class="criterion">
|
|
1066
1241
|
<div class="criterion-top">
|
|
1067
1242
|
<div>
|
|
@@ -1082,11 +1257,22 @@ function renderCriterionCard(criterion, gitHistoryUnavailable) {
|
|
|
1082
1257
|
</article>`;
|
|
1083
1258
|
}
|
|
1084
1259
|
function renderStatusReconciliation(criterion) {
|
|
1085
|
-
if (criterion.status === "not_applicable"
|
|
1086
|
-
return "";
|
|
1260
|
+
if (criterion.status === "not_applicable") {
|
|
1261
|
+
return '<p class="status-explanation"><strong>Why no label comparison applies:</strong> this dimension is not applicable to this repository, so it has no weighted numeric score band to compare.</p>';
|
|
1262
|
+
}
|
|
1263
|
+
if (criterion.status === "insufficient_data") {
|
|
1264
|
+
return '<p class="status-explanation"><strong>Why no label comparison applies:</strong> this dimension has insufficient measurable data, so it has no weighted numeric score band to compare.</p>';
|
|
1265
|
+
}
|
|
1266
|
+
if (criterion.status === "unverified") {
|
|
1267
|
+
return '<p class="status-explanation"><strong>Why no label comparison applies:</strong> the unverified dimension state is a fail-closed or legacy zero state, not a calibrated numeric band.</p>';
|
|
1087
1268
|
}
|
|
1088
1269
|
const numericBand = scoreBandForPresentation(criterion.score);
|
|
1089
|
-
if (numericBand === criterion.status)
|
|
1270
|
+
if (numericBand === criterion.status) {
|
|
1271
|
+
return `<p class="status-explanation"><strong>How the labels agree:</strong> the ${escapeHtml(criterion.status)} dimension band matches the ${formatScore(criterion.score)}/4.0 weighted score (the ${numericBand} numeric band).</p>`;
|
|
1272
|
+
}
|
|
1273
|
+
if (criterion.status !== "verified" && criterion.status !== "info" && criterion.status !== "warning" && criterion.status !== "critical") {
|
|
1274
|
+
return `<p class="status-explanation"><strong>Why no label comparison applies:</strong> this report contains an unrecognized dimension state (${escapeHtml(String(criterion.status))}), so Cejel will not infer a comparison.</p>`;
|
|
1275
|
+
}
|
|
1090
1276
|
const entry = CERTIFICATE_GLOSSARY.find((candidate) => candidate.key === "labels");
|
|
1091
1277
|
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>";
|
|
1092
1278
|
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>`;
|
|
@@ -1103,7 +1289,7 @@ function renderMetric(criterion, metric2) {
|
|
|
1103
1289
|
const formatted = formatCertificateMetricValue(criterion, metric2);
|
|
1104
1290
|
const cappedEntry = formatted.includes("capped") ? CERTIFICATE_GLOSSARY.find((candidate) => candidate.key === "capped") : void 0;
|
|
1105
1291
|
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>`;
|
|
1106
|
-
if (!cappedEntry) return `${label}<span>${escapeHtml(formatted)}</span>`;
|
|
1292
|
+
if (!cappedEntry) return `${label}<span class="metric-value">${escapeHtml(formatted)}</span>`;
|
|
1107
1293
|
const cappedTooltipId = `${tooltipId}-capped`;
|
|
1108
1294
|
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>`;
|
|
1109
1295
|
}
|
|
@@ -1274,6 +1460,8 @@ h2 { font-family: var(--serif); font-size: 30px; font-weight: 400; letter-spacin
|
|
|
1274
1460
|
.na-list li { margin: 0; color: var(--muted); }
|
|
1275
1461
|
.na-id { color: var(--faint); font-family: var(--mono); font-size: 11px; margin-right: 6px; }
|
|
1276
1462
|
.na-reason { color: var(--muted); }
|
|
1463
|
+
.na-reconciliation { display: block; margin-top: 4px; color: var(--muted); }
|
|
1464
|
+
.na-reconciliation strong { color: var(--text); }
|
|
1277
1465
|
.criterion { border: 1px solid var(--line); border-radius: 8px; padding: 16px; background: var(--surface-2); }
|
|
1278
1466
|
.criterion-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; }
|
|
1279
1467
|
.criterion-id { color: var(--periwinkle); font-family: var(--mono); font-size: 12px; margin-bottom: 4px; }
|
|
@@ -1288,7 +1476,10 @@ h3 { font-size: 15px; line-height: 1.35; font-weight: 600; margin-bottom: 0; }
|
|
|
1288
1476
|
.criterion-metrics { display: grid; gap: 7px; margin-top: 10px; padding: 0; list-style: none; font-size: 12px; }
|
|
1289
1477
|
.criterion-metrics li { display: flex; justify-content: space-between; gap: 12px; border-bottom: 1px solid rgba(238, 244, 247, .08); padding-bottom: 6px; }
|
|
1290
1478
|
.criterion-metrics strong { color: var(--text); font-weight: 650; }
|
|
1291
|
-
.criterion-metrics span {
|
|
1479
|
+
.criterion-metrics span { color: var(--muted); }
|
|
1480
|
+
.criterion-metrics > li > .metric-value {
|
|
1481
|
+
min-width: 0; white-space: normal; overflow-wrap: anywhere; text-align: right;
|
|
1482
|
+
}
|
|
1292
1483
|
.term-help { position: relative; cursor: help; outline: none; }
|
|
1293
1484
|
.term-help:focus-visible { box-shadow: 0 0 0 2px var(--periwinkle); border-radius: 3px; }
|
|
1294
1485
|
.term-tooltip {
|
|
@@ -1527,41 +1718,6 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
|
1527
1718
|
return { valid: errors.length === 0, errors };
|
|
1528
1719
|
}
|
|
1529
1720
|
|
|
1530
|
-
// src/witan/rubric-version.ts
|
|
1531
|
-
init_esm_shims();
|
|
1532
|
-
var WITAN_RUBRIC_VERSION_V9 = "witan-rubric-v9-2026-07-22";
|
|
1533
|
-
var WITAN_RUBRIC_VERSION_V10 = "witan-rubric-v10-2026-07-23";
|
|
1534
|
-
var WITAN_RUBRIC_VERSION_V11 = "witan-rubric-v11-2026-07-23";
|
|
1535
|
-
var WITAN_RUBRIC_VERSION_V12 = "witan-rubric-v12-2026-07-23";
|
|
1536
|
-
var WITAN_RUBRIC_VERSION_V13 = "witan-rubric-v13-2026-07-23";
|
|
1537
|
-
var WITAN_RUBRIC_VERSION_V14 = "witan-rubric-v14-2026-07-23";
|
|
1538
|
-
var WITAN_RUBRIC_VERSION_V15 = "witan-rubric-v15-2026-07-24";
|
|
1539
|
-
var WITAN_RUBRIC_VERSION_V16 = "witan-rubric-v16-2026-07-24";
|
|
1540
|
-
var WITAN_RUBRIC_VERSION_V17 = "witan-rubric-v17-2026-07-24";
|
|
1541
|
-
var WITAN_RUBRIC_VERSION_V18 = "witan-rubric-v18-prospective-2026-07-25";
|
|
1542
|
-
var WITAN_RUBRIC_VERSION_V19 = "witan-rubric-v19-prospective-2026-08-09";
|
|
1543
|
-
var WITAN_RUBRIC_VERSION_V20 = "witan-rubric-v20-prospective-2026-08-10";
|
|
1544
|
-
var WITAN_RUBRIC_VERSION_V21 = "witan-rubric-v21-prospective-2026-08-10";
|
|
1545
|
-
var WITAN_RUBRIC_VERSION_V22 = "witan-rubric-v22-prospective-2026-08-10";
|
|
1546
|
-
var WITAN_LAST_CALIBRATED_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V17;
|
|
1547
|
-
var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
|
|
1548
|
-
WITAN_RUBRIC_VERSION_V18,
|
|
1549
|
-
WITAN_RUBRIC_VERSION_V19,
|
|
1550
|
-
WITAN_RUBRIC_VERSION_V20,
|
|
1551
|
-
WITAN_RUBRIC_VERSION_V21,
|
|
1552
|
-
WITAN_RUBRIC_VERSION_V22
|
|
1553
|
-
]);
|
|
1554
|
-
var WITAN_SELECTABLE_RUBRIC_VERSIONS = Object.freeze([
|
|
1555
|
-
WITAN_LAST_CALIBRATED_RUBRIC_VERSION,
|
|
1556
|
-
...WITAN_PROSPECTIVE_RUBRIC_VERSIONS
|
|
1557
|
-
]);
|
|
1558
|
-
function assertSelectableRubricVersion(rubricVersion) {
|
|
1559
|
-
if (WITAN_SELECTABLE_RUBRIC_VERSIONS.includes(rubricVersion)) return;
|
|
1560
|
-
throw new Error(
|
|
1561
|
-
`Cejel: unrecognized rubric version: "${rubricVersion}". Accepted values: ${WITAN_SELECTABLE_RUBRIC_VERSIONS.join(", ")}.`
|
|
1562
|
-
);
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
1721
|
// src/witan/index.ts
|
|
1566
1722
|
init_esm_shims();
|
|
1567
1723
|
|
|
@@ -2548,8 +2704,26 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2548
2704
|
4,
|
|
2549
2705
|
0.25,
|
|
2550
2706
|
"ratio",
|
|
2551
|
-
"
|
|
2552
|
-
"saturating_count"
|
|
2707
|
+
"Counts detected test, coverage, lint, and typecheck commands plus detected test-runner configuration files; raw credit is capped at four.",
|
|
2708
|
+
"saturating_count",
|
|
2709
|
+
{
|
|
2710
|
+
components: [
|
|
2711
|
+
{
|
|
2712
|
+
label: "test command",
|
|
2713
|
+
count: packageScripts.has("test") || Boolean(ciTestWorkflow) ? 1 : 0
|
|
2714
|
+
},
|
|
2715
|
+
{ label: "coverage command", count: packageScripts.has("coverage") ? 1 : 0 },
|
|
2716
|
+
{
|
|
2717
|
+
label: "lint command",
|
|
2718
|
+
count: packageScripts.has("lint") || ciHasLintCommand ? 1 : 0
|
|
2719
|
+
},
|
|
2720
|
+
{
|
|
2721
|
+
label: "type-check command",
|
|
2722
|
+
count: packageScripts.has("typecheck") || ciHasTypecheckCommand ? 1 : 0
|
|
2723
|
+
},
|
|
2724
|
+
{ label: "test-runner configuration", count: runnerFiles.length }
|
|
2725
|
+
]
|
|
2726
|
+
}
|
|
2553
2727
|
),
|
|
2554
2728
|
metric(
|
|
2555
2729
|
"non_hollow_test_share",
|
|
@@ -2612,8 +2786,17 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
|
|
|
2612
2786
|
4,
|
|
2613
2787
|
0.25,
|
|
2614
2788
|
"ratio",
|
|
2615
|
-
"
|
|
2616
|
-
"saturating_count"
|
|
2789
|
+
"Counts detected test, coverage, lint, and typecheck commands plus detected test-runner configuration files; raw credit is capped at four.",
|
|
2790
|
+
"saturating_count",
|
|
2791
|
+
{
|
|
2792
|
+
components: [
|
|
2793
|
+
{ label: "test command", count: 0 },
|
|
2794
|
+
{ label: "coverage command", count: 0 },
|
|
2795
|
+
{ label: "lint command", count: 0 },
|
|
2796
|
+
{ label: "type-check command", count: 0 },
|
|
2797
|
+
{ label: "test-runner configuration", count: 0 }
|
|
2798
|
+
]
|
|
2799
|
+
}
|
|
2617
2800
|
),
|
|
2618
2801
|
metric(
|
|
2619
2802
|
"non_hollow_test_share",
|
|
@@ -2855,7 +3038,22 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2855
3038
|
3,
|
|
2856
3039
|
isMultiTenant ? 0.1 : 0.3,
|
|
2857
3040
|
"practices",
|
|
2858
|
-
"Counts three bounded env-handling practices (0\u20133): template file, gitignore rule, env reads in any supported language."
|
|
3041
|
+
"Counts three bounded env-handling practices (0\u20133): template file, gitignore rule, env reads in any supported language.",
|
|
3042
|
+
void 0,
|
|
3043
|
+
{
|
|
3044
|
+
components: [
|
|
3045
|
+
{ label: "environment template file", count: envExamples.length > 0 ? 1 : 0 },
|
|
3046
|
+
{ label: ".gitignore environment-file rule", count: gitignoreEnvFile ? 1 : 0 },
|
|
3047
|
+
{
|
|
3048
|
+
label: "environment reads in implementation code",
|
|
3049
|
+
count: countFilesContaining(
|
|
3050
|
+
repoPath,
|
|
3051
|
+
repoFiles.filter(isImplementationFile),
|
|
3052
|
+
ENV_READ_PATTERN
|
|
3053
|
+
) > 0 ? 1 : 0
|
|
3054
|
+
}
|
|
3055
|
+
]
|
|
3056
|
+
}
|
|
2859
3057
|
)
|
|
2860
3058
|
];
|
|
2861
3059
|
const isolationMetrics = isMultiTenant ? [
|
|
@@ -2867,7 +3065,8 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2867
3065
|
0.4,
|
|
2868
3066
|
"policies",
|
|
2869
3067
|
useV18NativeRls ? "Counts CREATE POLICY definitions with repository-native USING/WITH CHECK clauses against the repository-native scoped data-layer surface." : "Counts static row-level-security enables, forced-RLS statements, and policy definitions.",
|
|
2870
|
-
"saturating_count"
|
|
3068
|
+
"saturating_count",
|
|
3069
|
+
{ condition: "scored because a multi-tenant data surface was detected" }
|
|
2871
3070
|
),
|
|
2872
3071
|
metric(
|
|
2873
3072
|
"tenant_scope_ratio",
|
|
@@ -2876,7 +3075,9 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2876
3075
|
Math.max(migrationFiles.length, 1),
|
|
2877
3076
|
0.25,
|
|
2878
3077
|
"ratio",
|
|
2879
|
-
useV18NativeRls ? `Measures repository-native RLS scope identifiers (${nativeRlsInventory?.scopeIdentifiers.join(", ")}) across data-layer files.` : "Measures tenant/studio/workspace scoping evidence across data-layer files."
|
|
3078
|
+
useV18NativeRls ? `Measures repository-native RLS scope identifiers (${nativeRlsInventory?.scopeIdentifiers.join(", ")}) across data-layer files.` : "Measures tenant/studio/workspace scoping evidence across data-layer files.",
|
|
3079
|
+
void 0,
|
|
3080
|
+
{ condition: "scored because a multi-tenant data surface was detected" }
|
|
2880
3081
|
)
|
|
2881
3082
|
] : [];
|
|
2882
3083
|
const cryptoMetrics = cryptoHygiene.hasSurface ? [
|
|
@@ -2887,7 +3088,11 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2887
3088
|
1,
|
|
2888
3089
|
0.2,
|
|
2889
3090
|
"clean",
|
|
2890
|
-
"Credits constant-time secret/HMAC comparison and canonical serialization before signing when a signing/HMAC surface is detected in source; only scored when such a surface exists."
|
|
3091
|
+
"Credits constant-time secret/HMAC comparison and canonical serialization before signing when a signing/HMAC surface is detected in source; only scored when such a surface exists.",
|
|
3092
|
+
void 0,
|
|
3093
|
+
{
|
|
3094
|
+
condition: "scored because a signing, HMAC, or secret-comparison surface was detected"
|
|
3095
|
+
}
|
|
2891
3096
|
)
|
|
2892
3097
|
] : [];
|
|
2893
3098
|
return {
|
|
@@ -3181,7 +3386,21 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3181
3386
|
6,
|
|
3182
3387
|
0.55,
|
|
3183
3388
|
"checks",
|
|
3184
|
-
"Counts distinct static production-readiness checks instead of treating presence as enough."
|
|
3389
|
+
"Counts distinct static production-readiness checks instead of treating presence as enough.",
|
|
3390
|
+
void 0,
|
|
3391
|
+
{
|
|
3392
|
+
components: [
|
|
3393
|
+
{
|
|
3394
|
+
label: "build or type-check command",
|
|
3395
|
+
count: packageJson && (scripts.has("build") || scripts.has("typecheck")) ? 1 : 0
|
|
3396
|
+
},
|
|
3397
|
+
{ label: "CI workflow", count: workflows.length > 0 ? 1 : 0 },
|
|
3398
|
+
{ label: "deployment configuration", count: deployConfigs.length > 0 ? 1 : 0 },
|
|
3399
|
+
{ label: "environment template", count: envTemplate ? 1 : 0 },
|
|
3400
|
+
{ label: "health or readiness signal", count: healthChecks.length > 0 ? 1 : 0 },
|
|
3401
|
+
{ label: "error boundary", count: errorBoundaries.length > 0 ? 1 : 0 }
|
|
3402
|
+
]
|
|
3403
|
+
}
|
|
3185
3404
|
),
|
|
3186
3405
|
metric(
|
|
3187
3406
|
"prod_workflow_depth",
|
|
@@ -3294,7 +3513,14 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors,
|
|
|
3294
3513
|
2,
|
|
3295
3514
|
0.25,
|
|
3296
3515
|
"ratio",
|
|
3297
|
-
"Credits automated dependency updates and package-manager audit hooks."
|
|
3516
|
+
"Credits automated dependency updates and package-manager audit hooks.",
|
|
3517
|
+
void 0,
|
|
3518
|
+
{
|
|
3519
|
+
components: [
|
|
3520
|
+
{ label: "automated dependency-update configuration", count: hasUpdateConfig ? 1 : 0 },
|
|
3521
|
+
{ label: "package-manager audit hook", count: auditConfig ? 1 : 0 }
|
|
3522
|
+
]
|
|
3523
|
+
}
|
|
3298
3524
|
);
|
|
3299
3525
|
const appMetrics = [
|
|
3300
3526
|
metric(
|
|
@@ -3528,7 +3754,14 @@ function collectB2PrTraceEvidence(repoPath, repoFiles) {
|
|
|
3528
3754
|
0.8,
|
|
3529
3755
|
"signals",
|
|
3530
3756
|
"Measures CI, PR template, and review-gate evidence for pull-request traceability.",
|
|
3531
|
-
"saturating_count"
|
|
3757
|
+
"saturating_count",
|
|
3758
|
+
{
|
|
3759
|
+
components: [
|
|
3760
|
+
{ label: "CI workflow", count: workflows.length },
|
|
3761
|
+
{ label: "pull-request template", count: prTemplate ? 1 : 0 },
|
|
3762
|
+
{ label: "review-gate record", count: branchProtectionDoc ? 1 : 0 }
|
|
3763
|
+
]
|
|
3764
|
+
}
|
|
3532
3765
|
),
|
|
3533
3766
|
// Weight lowered from 0.35 (Phase 3 H1): squash-merge and local-history repos with
|
|
3534
3767
|
// no "Merge pull request"/"#123" commit-subject convention legitimately score 0/N
|
|
@@ -3591,8 +3824,24 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
3591
3824
|
4,
|
|
3592
3825
|
0.45,
|
|
3593
3826
|
"signals",
|
|
3594
|
-
"Counts
|
|
3595
|
-
"saturating_count"
|
|
3827
|
+
"Counts test/lint/typecheck/build package scripts plus those command categories in repository CI and, when applicable, monorepo-root CI; raw credit is capped at four.",
|
|
3828
|
+
"saturating_count",
|
|
3829
|
+
{
|
|
3830
|
+
components: [
|
|
3831
|
+
...["test", "lint", "typecheck", "build"].map((name) => ({
|
|
3832
|
+
label: `package script: ${name === "typecheck" ? "type-check" : name}`,
|
|
3833
|
+
count: scripts.has(name) ? 1 : 0
|
|
3834
|
+
})),
|
|
3835
|
+
...CI_COMMAND_CATEGORIES.map((pattern, index) => ({
|
|
3836
|
+
label: `repository CI command: ${["test", "lint", "type-check", "build"][index]}`,
|
|
3837
|
+
count: workflows.some((file) => fileContains(repoPath, file, pattern)) ? 1 : 0
|
|
3838
|
+
})),
|
|
3839
|
+
...mono ? CI_COMMAND_CATEGORIES.map((pattern, index) => ({
|
|
3840
|
+
label: `monorepo-root CI command: ${["test", "lint", "type-check", "build"][index]}`,
|
|
3841
|
+
count: rootWorkflows.some((file) => fileContains(mono.root, file, pattern)) ? 1 : 0
|
|
3842
|
+
})) : []
|
|
3843
|
+
]
|
|
3844
|
+
}
|
|
3596
3845
|
),
|
|
3597
3846
|
metric(
|
|
3598
3847
|
"default_branch_ci_depth",
|
|
@@ -3795,7 +4044,11 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
|
|
|
3795
4044
|
1,
|
|
3796
4045
|
0.15,
|
|
3797
4046
|
"present",
|
|
3798
|
-
"Credits a governance/safety toggle that fails closed before any lower-priority config can override it."
|
|
4047
|
+
"Credits a governance/safety toggle that fails closed before any lower-priority config can override it.",
|
|
4048
|
+
void 0,
|
|
4049
|
+
{
|
|
4050
|
+
condition: "shown because a fail-closed kill switch was detected; this positive-only metric is omitted when absent"
|
|
4051
|
+
}
|
|
3799
4052
|
)
|
|
3800
4053
|
] : [];
|
|
3801
4054
|
const hasPrivilegedOpsSurface = humanGateDoc != null || gatedPrivilegeCheckFile != null || executableFiles.length > 0 || v21ExecutedEscalationFiles.length > 0;
|
|
@@ -3812,7 +4065,11 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
|
|
|
3812
4065
|
1,
|
|
3813
4066
|
0.4,
|
|
3814
4067
|
"present",
|
|
3815
|
-
"Credits explicit documentation that privileged/credentialed operations are human-executed, never agent-run."
|
|
4068
|
+
"Credits explicit documentation that privileged/credentialed operations are human-executed, never agent-run.",
|
|
4069
|
+
void 0,
|
|
4070
|
+
{
|
|
4071
|
+
condition: "scored because a privileged-operation-shaped surface was detected"
|
|
4072
|
+
}
|
|
3816
4073
|
),
|
|
3817
4074
|
metric(
|
|
3818
4075
|
"fail_closed_privilege_check",
|
|
@@ -3821,7 +4078,11 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
|
|
|
3821
4078
|
1,
|
|
3822
4079
|
0.3,
|
|
3823
4080
|
"present",
|
|
3824
|
-
"Credits code that checks role membership and fails closed before elevating privilege, instead of attempting elevation blind."
|
|
4081
|
+
"Credits code that checks role membership and fails closed before elevating privilege, instead of attempting elevation blind.",
|
|
4082
|
+
void 0,
|
|
4083
|
+
{
|
|
4084
|
+
condition: "scored because a privileged-operation-shaped surface was detected"
|
|
4085
|
+
}
|
|
3825
4086
|
)
|
|
3826
4087
|
] : [],
|
|
3827
4088
|
metric(
|
|
@@ -6152,16 +6413,29 @@ function evidenceForRelativeAtLine(repoPath, path, kind, label, line) {
|
|
|
6152
6413
|
...contents ? { contentHash: createHash2("sha256").update(contents).digest("hex") } : {}
|
|
6153
6414
|
};
|
|
6154
6415
|
}
|
|
6155
|
-
function metric(name, label, value, max, weight, unit, description, kind) {
|
|
6416
|
+
function metric(name, label, value, max, weight, unit, description, kind, presentation) {
|
|
6417
|
+
const normalizedValue = Math.max(0, value);
|
|
6418
|
+
if (presentation?.components) {
|
|
6419
|
+
const explainedValue = presentation.components.reduce(
|
|
6420
|
+
(sum, component) => sum + component.count,
|
|
6421
|
+
0
|
|
6422
|
+
);
|
|
6423
|
+
if (explainedValue !== normalizedValue) {
|
|
6424
|
+
throw new Error(
|
|
6425
|
+
`Cejel invariant: presentation components for ${name} total ${explainedValue}, expected ${normalizedValue}`
|
|
6426
|
+
);
|
|
6427
|
+
}
|
|
6428
|
+
}
|
|
6156
6429
|
return {
|
|
6157
6430
|
name,
|
|
6158
6431
|
label,
|
|
6159
|
-
value:
|
|
6432
|
+
value: normalizedValue,
|
|
6160
6433
|
max,
|
|
6161
6434
|
...kind ? { kind } : {},
|
|
6162
6435
|
weight,
|
|
6163
6436
|
unit,
|
|
6164
|
-
description
|
|
6437
|
+
description,
|
|
6438
|
+
...presentation ? { presentation } : {}
|
|
6165
6439
|
};
|
|
6166
6440
|
}
|
|
6167
6441
|
function firstMeaningfulLine(contents) {
|
|
@@ -7340,9 +7614,9 @@ import { basename as basename2, join as join3 } from "path";
|
|
|
7340
7614
|
var SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/;
|
|
7341
7615
|
var FALLBACK_SLUG = "repo";
|
|
7342
7616
|
var FALLBACK_DISPLAY_NAME = "Repository";
|
|
7343
|
-
function deriveProductIdentity(repoPath) {
|
|
7617
|
+
function deriveProductIdentity(repoPath, productName) {
|
|
7344
7618
|
const packageJsonPath = join3(repoPath, "package.json");
|
|
7345
|
-
const raw = readPackageName(packageJsonPath) ?? basename2(repoPath);
|
|
7619
|
+
const raw = productName ?? readPackageName(packageJsonPath) ?? basename2(repoPath);
|
|
7346
7620
|
const displayName = raw.trim().length > 0 ? raw.trim() : FALLBACK_DISPLAY_NAME;
|
|
7347
7621
|
return {
|
|
7348
7622
|
productSlug: slugify(raw),
|
|
@@ -7381,7 +7655,7 @@ var METRIC_DERIVED_FINDING_PATTERN2 = /^[A-Z]\d+ metric-derived score is (\d+\.\
|
|
|
7381
7655
|
var REMEDIATION_BY_METRIC2 = {
|
|
7382
7656
|
test_to_source_ratio: "add concrete regression tests for the implementation surface",
|
|
7383
7657
|
coverage_percent: "configure coverage and publish a measured threshold or report",
|
|
7384
|
-
verification_script_ratio: "add
|
|
7658
|
+
verification_script_ratio: "add the missing test, coverage, lint, or type-check command or a test-runner configuration",
|
|
7385
7659
|
non_hollow_test_share: "replace skipped or placeholder tests with live assertions",
|
|
7386
7660
|
env_handling_depth: "document and enforce safe environment-secret handling",
|
|
7387
7661
|
secret_cleanliness: "remove committed secrets and rotate any exposed credentials",
|
|
@@ -7399,7 +7673,7 @@ var REMEDIATION_BY_METRIC2 = {
|
|
|
7399
7673
|
claim_match_rate: "align documented product claims with observable repository evidence",
|
|
7400
7674
|
claim_source_depth: "document concrete, checkable product claims",
|
|
7401
7675
|
reconciliation_artifact_depth: "publish a claim-to-evidence reconciliation record",
|
|
7402
|
-
pr_merge_ratio: "preserve
|
|
7676
|
+
pr_merge_ratio: "preserve recognizable pull-request references in recent commit subjects",
|
|
7403
7677
|
pr_trace_primitives: "add pull-request templates and outcome-trace records",
|
|
7404
7678
|
ci_script_depth: "run the repository verification commands in CI",
|
|
7405
7679
|
default_branch_ci_depth: "run CI on the default branch and pull requests",
|
|
@@ -7509,7 +7783,7 @@ function buildWitanCliSummary(report) {
|
|
|
7509
7783
|
|
|
7510
7784
|
// src/scan.ts
|
|
7511
7785
|
function runCejelScan(options) {
|
|
7512
|
-
const identity = deriveProductIdentity(options.repoPath);
|
|
7786
|
+
const identity = deriveProductIdentity(options.repoPath, options.productName);
|
|
7513
7787
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7514
7788
|
const report = scoreRepoWithPublicCejel({
|
|
7515
7789
|
productSlug: identity.productSlug,
|
|
@@ -7531,17 +7805,21 @@ function runCejelScan(options) {
|
|
|
7531
7805
|
export {
|
|
7532
7806
|
WitanReportSchema,
|
|
7533
7807
|
formatExternalSourceLine,
|
|
7808
|
+
CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE,
|
|
7534
7809
|
CERTIFICATE_GLOSSARY,
|
|
7535
7810
|
formatCertificateMetricLabel,
|
|
7536
7811
|
formatCertificateMetricValue,
|
|
7537
7812
|
buildRelyingPartySummary,
|
|
7813
|
+
WITAN_RUBRIC_VERSION_V22,
|
|
7814
|
+
assertSelectableRubricVersion,
|
|
7815
|
+
isProspectiveRubricVersion,
|
|
7816
|
+
PROSPECTIVE_RUBRIC_NOTICE,
|
|
7538
7817
|
renderWitanHtmlReport,
|
|
7539
7818
|
renderWitanBadgeEndpoint,
|
|
7540
7819
|
renderWitanBadgeSvg,
|
|
7541
7820
|
serializeWitanReport,
|
|
7542
7821
|
createWitanAttestation,
|
|
7543
7822
|
verifyWitanAttestationBinding,
|
|
7544
|
-
WITAN_RUBRIC_VERSION_V22,
|
|
7545
7823
|
deriveProductIdentity,
|
|
7546
7824
|
runCejelScan
|
|
7547
7825
|
};
|
package/dist/index.js
CHANGED
|
@@ -2,21 +2,25 @@
|
|
|
2
2
|
import { createRequire as __cejelCreateRequire } from 'node:module';
|
|
3
3
|
const require = __cejelCreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
|
+
CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE,
|
|
5
6
|
CERTIFICATE_GLOSSARY,
|
|
7
|
+
PROSPECTIVE_RUBRIC_NOTICE,
|
|
6
8
|
WITAN_RUBRIC_VERSION_V22,
|
|
7
9
|
WitanReportSchema,
|
|
10
|
+
assertSelectableRubricVersion,
|
|
8
11
|
buildRelyingPartySummary,
|
|
9
12
|
createWitanAttestation,
|
|
10
13
|
formatCertificateMetricLabel,
|
|
11
14
|
formatCertificateMetricValue,
|
|
12
15
|
formatExternalSourceLine,
|
|
16
|
+
isProspectiveRubricVersion,
|
|
13
17
|
renderWitanBadgeEndpoint,
|
|
14
18
|
renderWitanBadgeSvg,
|
|
15
19
|
renderWitanHtmlReport,
|
|
16
20
|
runCejelScan,
|
|
17
21
|
serializeWitanReport,
|
|
18
22
|
verifyWitanAttestationBinding
|
|
19
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-4IXESHG7.js";
|
|
20
24
|
import "./chunk-EPK7UTIK.js";
|
|
21
25
|
import "./chunk-KGFGX3TP.js";
|
|
22
26
|
import {
|
|
@@ -65,10 +69,11 @@ function renderTerminalCertificate(summary, report) {
|
|
|
65
69
|
` Process trust: ${formatScore(summary.processTrustScore)}/4.0`,
|
|
66
70
|
""
|
|
67
71
|
];
|
|
72
|
+
lines.splice(1, 0, `Product identity: ${CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE}`);
|
|
68
73
|
if (report) {
|
|
69
74
|
const relyingPartySummary = buildRelyingPartySummary(report);
|
|
70
75
|
lines.splice(
|
|
71
|
-
|
|
76
|
+
3,
|
|
72
77
|
0,
|
|
73
78
|
`What was examined: ${relyingPartySummary.examined}`,
|
|
74
79
|
`What was established: ${relyingPartySummary.established}`,
|
|
@@ -155,6 +160,9 @@ function renderTerminalCertificate(summary, report) {
|
|
|
155
160
|
for (const entry of CERTIFICATE_GLOSSARY) {
|
|
156
161
|
lines.push(` ${entry.term} \u2014 ${entry.definition}`);
|
|
157
162
|
}
|
|
163
|
+
if (report && isProspectiveRubricVersion(report.rubricVersion)) {
|
|
164
|
+
lines.unshift(`Rubric: ${report.rubricVersion}`, PROSPECTIVE_RUBRIC_NOTICE, "");
|
|
165
|
+
}
|
|
158
166
|
return `${lines.join("\n")}
|
|
159
167
|
`;
|
|
160
168
|
}
|
|
@@ -178,10 +186,16 @@ var CLI_FLAG_SPECS = [
|
|
|
178
186
|
description: "exit nonzero below this overall score",
|
|
179
187
|
kind: "minScore"
|
|
180
188
|
},
|
|
189
|
+
{
|
|
190
|
+
tokens: ["--product-name"],
|
|
191
|
+
value: "<name>",
|
|
192
|
+
description: "set caller-context slug and display name for reproducible certificates",
|
|
193
|
+
kind: "productName"
|
|
194
|
+
},
|
|
181
195
|
{
|
|
182
196
|
tokens: ["--name"],
|
|
183
197
|
value: "<display>",
|
|
184
|
-
description: "override the project name
|
|
198
|
+
description: "override only the displayed project name (legacy compatibility)",
|
|
185
199
|
kind: "name"
|
|
186
200
|
},
|
|
187
201
|
{
|
|
@@ -190,6 +204,12 @@ var CLI_FLAG_SPECS = [
|
|
|
190
204
|
description: "fold in a SARIF/JSON scanner report (repeatable)",
|
|
191
205
|
kind: "ingest"
|
|
192
206
|
},
|
|
207
|
+
{
|
|
208
|
+
tokens: ["--rubric-pin"],
|
|
209
|
+
value: "<version>",
|
|
210
|
+
description: "EXPLICIT OPT-IN: pin a rubric other than the calibrated default (see docs); prospective rubrics carry no calibration claim",
|
|
211
|
+
kind: "rubricPin"
|
|
212
|
+
},
|
|
193
213
|
{
|
|
194
214
|
tokens: ["--quiet"],
|
|
195
215
|
description: "suppress the terminal summary",
|
|
@@ -287,10 +307,12 @@ async function runWitanCli(args, rubricVersion) {
|
|
|
287
307
|
process.stdout.write(USAGE);
|
|
288
308
|
return 0;
|
|
289
309
|
}
|
|
310
|
+
const effectiveRubricVersion = options.rubricPin ?? rubricVersion;
|
|
290
311
|
const { report, summary, generatedAt } = runCejelScan({
|
|
291
312
|
repoPath: options.repoPath,
|
|
313
|
+
...options.productName ? { productName: options.productName } : {},
|
|
292
314
|
...options.productDisplayName ? { productDisplayName: options.productDisplayName } : {},
|
|
293
|
-
...
|
|
315
|
+
...effectiveRubricVersion ? { rubricVersion: effectiveRubricVersion } : {},
|
|
294
316
|
ingestPatterns: options.ingestPatterns,
|
|
295
317
|
warnOnEmptyIngestMatch: !options.quiet
|
|
296
318
|
});
|
|
@@ -425,10 +447,12 @@ function parseArgs(args) {
|
|
|
425
447
|
let repoPath;
|
|
426
448
|
let outDir = DEFAULT_OUT_DIR;
|
|
427
449
|
let minScore;
|
|
450
|
+
let productName;
|
|
428
451
|
let productDisplayName;
|
|
429
452
|
let quiet = false;
|
|
430
453
|
let showHelp = false;
|
|
431
454
|
let showVersion = false;
|
|
455
|
+
let rubricPin;
|
|
432
456
|
const ingestPatterns = [];
|
|
433
457
|
for (let index = 0; index < args.length; index += 1) {
|
|
434
458
|
const arg = args[index];
|
|
@@ -479,6 +503,19 @@ function parseArgs(args) {
|
|
|
479
503
|
index += 1;
|
|
480
504
|
break;
|
|
481
505
|
}
|
|
506
|
+
case "productName": {
|
|
507
|
+
const value = args[index + 1];
|
|
508
|
+
if (!value) throw new Error("Missing value for --product-name");
|
|
509
|
+
const trimmed = value.trim();
|
|
510
|
+
if (trimmed.length === 0 || trimmed.length > 120 || hasUnsafeDisplayNameCharacter(trimmed)) {
|
|
511
|
+
throw new Error(
|
|
512
|
+
"--product-name must be a single printable line containing between 1 and 120 characters"
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
productName = trimmed;
|
|
516
|
+
index += 1;
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
482
519
|
case "ingest": {
|
|
483
520
|
const value = args[index + 1];
|
|
484
521
|
if (!value) throw new Error("Missing value for --ingest");
|
|
@@ -486,6 +523,14 @@ function parseArgs(args) {
|
|
|
486
523
|
index += 1;
|
|
487
524
|
break;
|
|
488
525
|
}
|
|
526
|
+
case "rubricPin": {
|
|
527
|
+
const value = args[index + 1];
|
|
528
|
+
if (!value) throw new Error("Missing value for --rubric-pin");
|
|
529
|
+
assertSelectableRubricVersion(value);
|
|
530
|
+
rubricPin = value;
|
|
531
|
+
index += 1;
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
489
534
|
}
|
|
490
535
|
continue;
|
|
491
536
|
}
|
|
@@ -495,15 +540,20 @@ function parseArgs(args) {
|
|
|
495
540
|
throw new Error(`Unexpected positional argument: ${arg}`);
|
|
496
541
|
}
|
|
497
542
|
}
|
|
543
|
+
if (productName && productDisplayName) {
|
|
544
|
+
throw new Error("--product-name and --name cannot be used together");
|
|
545
|
+
}
|
|
498
546
|
return {
|
|
499
547
|
repoPath: resolve(repoPath ?? "."),
|
|
500
548
|
outDir,
|
|
501
549
|
...minScore != null ? { minScore } : {},
|
|
550
|
+
...productName ? { productName } : {},
|
|
502
551
|
...productDisplayName ? { productDisplayName } : {},
|
|
503
552
|
quiet,
|
|
504
553
|
showHelp,
|
|
505
554
|
showVersion,
|
|
506
|
-
ingestPatterns
|
|
555
|
+
ingestPatterns,
|
|
556
|
+
...rubricPin ? { rubricPin } : {}
|
|
507
557
|
};
|
|
508
558
|
}
|
|
509
559
|
function hasUnsafeDisplayNameCharacter(value) {
|
package/dist/mcp/index.js
CHANGED