@cejel/cejel 0.4.4 → 0.4.6
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 +38 -6
- package/dist/calibration/llm-detector.js +5 -5
- package/dist/{chunk-EW6NAHE4.js → chunk-2IBG5OBF.js} +1 -1
- package/dist/{chunk-EPK7UTIK.js → chunk-BZXBLDCJ.js} +1 -1
- package/dist/{chunk-R4DPQLS2.js → chunk-H55KECVT.js} +17 -0
- package/dist/{chunk-RW6YJVHZ.js → chunk-QRWLHTGR.js} +2 -2
- package/dist/{chunk-PJEKYFVM.js → chunk-VEJLHO5F.js} +981 -282
- package/dist/{chunk-KGFGX3TP.js → chunk-Y6NO5HAI.js} +1 -1
- package/dist/index.js +58 -8
- package/dist/mcp/index.js +4 -4
- package/dist/packs/d-series/index.js +4 -4
- package/dist/packs/decision-contracts/index.js +3 -3
- package/package.json +1 -1
|
@@ -5,19 +5,21 @@ import {
|
|
|
5
5
|
describeGitFailure,
|
|
6
6
|
execGit,
|
|
7
7
|
isExpectedGitAbsence
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-BZXBLDCJ.js";
|
|
9
9
|
import {
|
|
10
10
|
external_exports
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-Y6NO5HAI.js";
|
|
12
12
|
import {
|
|
13
|
+
MAX_REPOSITORY_CONTENT_BYTES,
|
|
13
14
|
init_esm_shims,
|
|
14
15
|
readRepoText,
|
|
15
16
|
readRepoTextPrefix,
|
|
16
17
|
recordContentSkip,
|
|
18
|
+
recordContentSkipForCriteria,
|
|
17
19
|
recordFilesystemSkip,
|
|
18
20
|
trackContentReads,
|
|
19
21
|
withContentReadCriterion
|
|
20
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-H55KECVT.js";
|
|
21
23
|
|
|
22
24
|
// src/witan/schemas.ts
|
|
23
25
|
init_esm_shims();
|
|
@@ -117,10 +119,26 @@ var WitanEvidencePointerSchema = external_exports.object({
|
|
|
117
119
|
}).strict().refine((value) => Boolean(value.url ?? value.path ?? value.contentHash), {
|
|
118
120
|
message: "Witan evidence must include a path, url, or content hash."
|
|
119
121
|
});
|
|
122
|
+
var WitanInventoryScanDerivationSchema = external_exports.object({
|
|
123
|
+
kind: external_exports.literal("inventory-scan"),
|
|
124
|
+
inventoryScope: external_exports.enum(["tracked-scan-eligible", "tracked-complete"]),
|
|
125
|
+
inventoryCount: external_exports.number().int().nonnegative(),
|
|
126
|
+
patternSetId: external_exports.string().min(1).max(180),
|
|
127
|
+
patternCount: external_exports.number().int().positive(),
|
|
128
|
+
matchCount: external_exports.number().int().nonnegative()
|
|
129
|
+
}).strict();
|
|
130
|
+
var WitanFindingDerivationSchema = external_exports.discriminatedUnion("kind", [
|
|
131
|
+
WitanInventoryScanDerivationSchema
|
|
132
|
+
]);
|
|
120
133
|
var WitanFindingSchema = external_exports.object({
|
|
121
134
|
severity: WitanFindingSeveritySchema,
|
|
122
135
|
summary: external_exports.string().min(1).max(500),
|
|
123
|
-
evidence: WitanEvidencePointerSchema
|
|
136
|
+
evidence: WitanEvidencePointerSchema,
|
|
137
|
+
// Absence findings emitted by the native repository detector must name the exact
|
|
138
|
+
// inventory and versioned pattern set whose zero result supports the proposition.
|
|
139
|
+
// This remains optional at the shared schema boundary because positive findings and
|
|
140
|
+
// semantic derivations are separate contracts; native absence emitters require it.
|
|
141
|
+
derivation: WitanFindingDerivationSchema.optional()
|
|
124
142
|
}).strict();
|
|
125
143
|
var WitanCriterionMetricSchema = external_exports.object({
|
|
126
144
|
name: external_exports.string().min(1).max(80),
|
|
@@ -130,7 +148,16 @@ var WitanCriterionMetricSchema = external_exports.object({
|
|
|
130
148
|
kind: external_exports.enum(["ratio", "saturating_count"]).optional(),
|
|
131
149
|
weight: external_exports.number().positive().max(1).default(1),
|
|
132
150
|
unit: external_exports.string().min(1).max(40).optional(),
|
|
133
|
-
description: external_exports.string().min(1).max(300).optional()
|
|
151
|
+
description: external_exports.string().min(1).max(300).optional(),
|
|
152
|
+
presentation: external_exports.object({
|
|
153
|
+
components: external_exports.array(
|
|
154
|
+
external_exports.object({
|
|
155
|
+
label: external_exports.string().min(1).max(120),
|
|
156
|
+
count: external_exports.number().int().nonnegative()
|
|
157
|
+
}).strict()
|
|
158
|
+
).min(1).optional(),
|
|
159
|
+
condition: external_exports.string().min(1).max(240).optional()
|
|
160
|
+
}).strict().optional()
|
|
134
161
|
}).strict();
|
|
135
162
|
var WitanCriterionSignalSchema = external_exports.object({
|
|
136
163
|
criterionId: WitanCriterionIdSchema,
|
|
@@ -312,6 +339,7 @@ var WitanReportSchema = external_exports.preprocess((value) => {
|
|
|
312
339
|
}, StrictWitanReportSchema);
|
|
313
340
|
var WITAN_ATTESTATION_STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
|
|
314
341
|
var WITAN_ATTESTATION_PREDICATE_TYPE = "https://cejel.dev/attestations/scan/v1";
|
|
342
|
+
var WITAN_REPORT_FORMAT_VERSION = "1.0";
|
|
315
343
|
var WitanAttestationOutcomeSchema = external_exports.discriminatedUnion("status", [
|
|
316
344
|
external_exports.object({
|
|
317
345
|
status: external_exports.literal("scored"),
|
|
@@ -341,6 +369,10 @@ var WitanAttestationStatementSchema = external_exports.object({
|
|
|
341
369
|
version: external_exports.string().min(1).max(80)
|
|
342
370
|
}).strict(),
|
|
343
371
|
generatedAt: external_exports.string().datetime({ offset: true }),
|
|
372
|
+
// Optional on read for attestations emitted before Evidence Seam v1. New attestations
|
|
373
|
+
// always carry it, keeping report.json byte-stable while giving paired consumers an
|
|
374
|
+
// explicit report contract version.
|
|
375
|
+
reportFormatVersion: external_exports.string().regex(/^1\.\d+$/).optional(),
|
|
344
376
|
rubricVersion: external_exports.string().min(1).max(120),
|
|
345
377
|
repository: external_exports.object({
|
|
346
378
|
productSlug: external_exports.string().regex(/^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/),
|
|
@@ -367,13 +399,54 @@ var WitanAttestationStatementSchema = external_exports.object({
|
|
|
367
399
|
}).strict()
|
|
368
400
|
}).strict();
|
|
369
401
|
|
|
402
|
+
// src/witan/rubric-version.ts
|
|
403
|
+
init_esm_shims();
|
|
404
|
+
var WITAN_RUBRIC_VERSION_V9 = "witan-rubric-v9-2026-07-22";
|
|
405
|
+
var WITAN_RUBRIC_VERSION_V10 = "witan-rubric-v10-2026-07-23";
|
|
406
|
+
var WITAN_RUBRIC_VERSION_V11 = "witan-rubric-v11-2026-07-23";
|
|
407
|
+
var WITAN_RUBRIC_VERSION_V12 = "witan-rubric-v12-2026-07-23";
|
|
408
|
+
var WITAN_RUBRIC_VERSION_V13 = "witan-rubric-v13-2026-07-23";
|
|
409
|
+
var WITAN_RUBRIC_VERSION_V14 = "witan-rubric-v14-2026-07-23";
|
|
410
|
+
var WITAN_RUBRIC_VERSION_V15 = "witan-rubric-v15-2026-07-24";
|
|
411
|
+
var WITAN_RUBRIC_VERSION_V16 = "witan-rubric-v16-2026-07-24";
|
|
412
|
+
var WITAN_RUBRIC_VERSION_V17 = "witan-rubric-v17-2026-07-24";
|
|
413
|
+
var WITAN_RUBRIC_VERSION_V18 = "witan-rubric-v18-prospective-2026-07-25";
|
|
414
|
+
var WITAN_RUBRIC_VERSION_V19 = "witan-rubric-v19-prospective-2026-08-09";
|
|
415
|
+
var WITAN_RUBRIC_VERSION_V20 = "witan-rubric-v20-prospective-2026-08-10";
|
|
416
|
+
var WITAN_RUBRIC_VERSION_V21 = "witan-rubric-v21-prospective-2026-08-10";
|
|
417
|
+
var WITAN_RUBRIC_VERSION_V22 = "witan-rubric-v22-prospective-2026-08-10";
|
|
418
|
+
var WITAN_LAST_CALIBRATED_RUBRIC_VERSION = WITAN_RUBRIC_VERSION_V17;
|
|
419
|
+
var WITAN_PROSPECTIVE_RUBRIC_VERSIONS = Object.freeze([
|
|
420
|
+
WITAN_RUBRIC_VERSION_V18,
|
|
421
|
+
WITAN_RUBRIC_VERSION_V19,
|
|
422
|
+
WITAN_RUBRIC_VERSION_V20,
|
|
423
|
+
WITAN_RUBRIC_VERSION_V21,
|
|
424
|
+
WITAN_RUBRIC_VERSION_V22
|
|
425
|
+
]);
|
|
426
|
+
var WITAN_SELECTABLE_RUBRIC_VERSIONS = Object.freeze([
|
|
427
|
+
WITAN_LAST_CALIBRATED_RUBRIC_VERSION,
|
|
428
|
+
...WITAN_PROSPECTIVE_RUBRIC_VERSIONS
|
|
429
|
+
]);
|
|
430
|
+
function assertSelectableRubricVersion(rubricVersion) {
|
|
431
|
+
if (WITAN_SELECTABLE_RUBRIC_VERSIONS.includes(rubricVersion)) return;
|
|
432
|
+
throw new Error(
|
|
433
|
+
`Cejel: unrecognized rubric version: "${rubricVersion}". Accepted values: ${WITAN_SELECTABLE_RUBRIC_VERSIONS.join(", ")}.`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
function isProspectiveRubricVersion(rubricVersion) {
|
|
437
|
+
return WITAN_PROSPECTIVE_RUBRIC_VERSIONS.includes(rubricVersion);
|
|
438
|
+
}
|
|
439
|
+
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.";
|
|
440
|
+
|
|
370
441
|
// src/witan/html.ts
|
|
371
442
|
init_esm_shims();
|
|
372
443
|
|
|
373
444
|
// src/witan/abstention.ts
|
|
374
445
|
init_esm_shims();
|
|
375
446
|
var WITAN_NO_MEASUREMENT_REASON = "No free-core rubric criterion produced a measurable signal. Cejel abstains rather than publish a numeric zero for an entirely unmeasured repository.";
|
|
376
|
-
var WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the
|
|
447
|
+
var WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the tracked scan-eligible inventory matched no concrete test file, configured test runner, CI test command, or coverage configuration.";
|
|
448
|
+
var WITAN_DIRECTORY_WALK_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the bounded directory walk matched no concrete test file, configured test runner, CI test command, or coverage configuration.";
|
|
449
|
+
var WITAN_LEGACY_AUTHENTICATED_A1_ABSENCE_SUMMARY = "Reviewable source is present, but the complete tracked inventory contains no concrete test file, configured test runner, CI test command, or coverage configuration.";
|
|
377
450
|
function isWitanNoMeasurementAbstention(report) {
|
|
378
451
|
return report.verdict === "insufficient_source" && report.insufficientSourceReason.startsWith(WITAN_NO_MEASUREMENT_REASON) && report.criteria.every(
|
|
379
452
|
({ status }) => status === "not_applicable" || status === "insufficient_data"
|
|
@@ -476,6 +549,45 @@ init_esm_shims();
|
|
|
476
549
|
|
|
477
550
|
// src/witan/certificate-presentation.ts
|
|
478
551
|
init_esm_shims();
|
|
552
|
+
var CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE = "Product name and slug are caller context and are excluded from certificate byte-comparison claims.";
|
|
553
|
+
var CERTIFICATE_METRIC_REGISTRY = {
|
|
554
|
+
test_to_source_ratio: { displayLabel: "Test-to-source file ratio" },
|
|
555
|
+
coverage_percent: { displayLabel: "Static coverage percentage" },
|
|
556
|
+
verification_script_ratio: { displayLabel: "Verification script ratio" },
|
|
557
|
+
non_hollow_test_share: { displayLabel: "Non-hollow test share" },
|
|
558
|
+
secret_cleanliness: { displayLabel: "Secret cleanliness" },
|
|
559
|
+
env_handling_depth: { displayLabel: "Environment handling depth" },
|
|
560
|
+
rls_policy_count: { displayLabel: "RLS policy count" },
|
|
561
|
+
tenant_scope_ratio: { displayLabel: "Tenant-scoped schema ratio" },
|
|
562
|
+
crypto_comparison_hygiene: { displayLabel: "Crypto comparison hygiene" },
|
|
563
|
+
prod_readiness_primitives: { displayLabel: "Production-readiness basic checks" },
|
|
564
|
+
prod_workflow_depth: {
|
|
565
|
+
displayLabel: "Production workflow depth (automated build/test/release pipeline)"
|
|
566
|
+
},
|
|
567
|
+
observability_depth: { displayLabel: "Observability depth" },
|
|
568
|
+
rollback_safety_depth: { displayLabel: "Rollback and migration-safety depth" },
|
|
569
|
+
dependency_automation_ratio: { displayLabel: "Dependency automation ratio" },
|
|
570
|
+
pinned_dependency_ratio: { displayLabel: "Pinned dependency ratio" },
|
|
571
|
+
lockfile_coverage: { displayLabel: "Lockfile coverage" },
|
|
572
|
+
declared_version_range_ratio: { displayLabel: "Declared version range ratio" },
|
|
573
|
+
dependency_count_sanity: { displayLabel: "Dependency count sanity" },
|
|
574
|
+
claim_match_rate: { displayLabel: "Implementation-to-claim-source file ratio" },
|
|
575
|
+
claim_source_depth: { displayLabel: "Claim source depth" },
|
|
576
|
+
reconciliation_artifact_depth: { displayLabel: "Reconciliation artifact depth" },
|
|
577
|
+
pr_trace_primitives: { displayLabel: "PR trace basic checks" },
|
|
578
|
+
pr_merge_ratio: { displayLabel: "Recent commits with recognizable PR references" },
|
|
579
|
+
ci_script_depth: { displayLabel: "CI verification depth" },
|
|
580
|
+
default_branch_ci_depth: {
|
|
581
|
+
displayLabel: "PR-gate CI workflow count (automated build/test pipeline)"
|
|
582
|
+
},
|
|
583
|
+
audit_artifact_depth: { displayLabel: "Audit artifact depth" },
|
|
584
|
+
audit_freshness_depth: { displayLabel: "Audit freshness depth" },
|
|
585
|
+
kill_switch_fail_safe_present: { displayLabel: "Un-overridable kill-switch present" },
|
|
586
|
+
human_gate_documented: { displayLabel: "Human gate documented" },
|
|
587
|
+
fail_closed_privilege_check: { displayLabel: "Fail-closed privilege check present" },
|
|
588
|
+
privilege_escalation_cleanliness: { displayLabel: "Privilege-escalation cleanliness" },
|
|
589
|
+
protected_path_review_gate: { displayLabel: "Protected-path review gate" }
|
|
590
|
+
};
|
|
479
591
|
var CERTIFICATE_GLOSSARY = [
|
|
480
592
|
{
|
|
481
593
|
key: "labels",
|
|
@@ -491,188 +603,194 @@ var CERTIFICATE_GLOSSARY = [
|
|
|
491
603
|
},
|
|
492
604
|
{
|
|
493
605
|
key: "test-to-source-file-ratio",
|
|
494
|
-
term: "
|
|
606
|
+
term: "Test-to-source file ratio",
|
|
495
607
|
definition: "The number of detected test files compared with detected source files. A larger implementation surface needs a correspondingly visible test surface.",
|
|
496
608
|
metricNames: ["test_to_source_ratio"]
|
|
497
609
|
},
|
|
498
610
|
{
|
|
499
611
|
key: "static-coverage",
|
|
500
|
-
term: "
|
|
612
|
+
term: "Static coverage percentage",
|
|
501
613
|
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
614
|
metricNames: ["coverage_percent"]
|
|
503
615
|
},
|
|
504
616
|
{
|
|
505
617
|
key: "verification-script",
|
|
506
|
-
term: "
|
|
507
|
-
definition: "
|
|
618
|
+
term: "Verification script ratio",
|
|
619
|
+
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
620
|
metricNames: ["verification_script_ratio"]
|
|
509
621
|
},
|
|
510
622
|
{
|
|
511
623
|
key: "non-hollow",
|
|
512
|
-
term: "
|
|
624
|
+
term: "Non-hollow test share",
|
|
513
625
|
definition: "test files that actually assert something rather than being empty or skipped",
|
|
514
626
|
metricNames: ["non_hollow_test_share"]
|
|
515
627
|
},
|
|
516
628
|
{
|
|
517
629
|
key: "secrets",
|
|
518
|
-
term: "
|
|
519
|
-
definition: "
|
|
630
|
+
term: "Secret cleanliness",
|
|
631
|
+
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
632
|
metricNames: ["secret_cleanliness"]
|
|
521
633
|
},
|
|
522
634
|
{
|
|
523
635
|
key: "environment-handling-depth",
|
|
524
|
-
term: "
|
|
525
|
-
definition: "
|
|
636
|
+
term: "Environment handling depth",
|
|
637
|
+
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
638
|
metricNames: ["env_handling_depth"]
|
|
527
639
|
},
|
|
528
640
|
{
|
|
529
641
|
key: "rls-policy-count",
|
|
530
642
|
term: "RLS policy count",
|
|
531
|
-
definition: "
|
|
643
|
+
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
644
|
metricNames: ["rls_policy_count"]
|
|
533
645
|
},
|
|
534
646
|
{
|
|
535
647
|
key: "tenant-scoped-schema-ratio",
|
|
536
|
-
term: "
|
|
537
|
-
definition: "The share of
|
|
648
|
+
term: "Tenant-scoped schema ratio",
|
|
649
|
+
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
650
|
metricNames: ["tenant_scope_ratio"]
|
|
539
651
|
},
|
|
540
652
|
{
|
|
541
653
|
key: "crypto-comparison-hygiene",
|
|
542
|
-
term: "
|
|
543
|
-
definition: "
|
|
654
|
+
term: "Crypto comparison hygiene",
|
|
655
|
+
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
656
|
metricNames: ["crypto_comparison_hygiene"]
|
|
545
657
|
},
|
|
546
658
|
{
|
|
547
|
-
key: "basic-checks",
|
|
548
|
-
term: "basic checks",
|
|
549
|
-
definition: "
|
|
550
|
-
metricNames: ["prod_readiness_primitives"
|
|
659
|
+
key: "production-readiness-basic-checks",
|
|
660
|
+
term: "Production-readiness basic checks",
|
|
661
|
+
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.",
|
|
662
|
+
metricNames: ["prod_readiness_primitives"]
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
key: "pr-trace-basic-checks",
|
|
666
|
+
term: "PR trace basic checks",
|
|
667
|
+
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.",
|
|
668
|
+
metricNames: ["pr_trace_primitives"]
|
|
551
669
|
},
|
|
552
670
|
{
|
|
553
671
|
key: "workflow-depth",
|
|
554
|
-
term: "workflow depth",
|
|
555
|
-
definition: "
|
|
672
|
+
term: "Production workflow depth (automated build/test/release pipeline)",
|
|
673
|
+
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
674
|
metricNames: ["prod_workflow_depth"]
|
|
557
675
|
},
|
|
558
676
|
{
|
|
559
677
|
key: "observability-depth",
|
|
560
|
-
term: "
|
|
561
|
-
definition: "The
|
|
678
|
+
term: "Observability depth",
|
|
679
|
+
definition: "The number of implementation files containing a recognized logging, metrics, tracing, or error-reporting marker, capped at four for scoring.",
|
|
562
680
|
metricNames: ["observability_depth"]
|
|
563
681
|
},
|
|
564
682
|
{
|
|
565
683
|
key: "rollback-and-migration-safety-depth",
|
|
566
|
-
term: "
|
|
567
|
-
definition: "The
|
|
684
|
+
term: "Rollback and migration-safety depth",
|
|
685
|
+
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
686
|
metricNames: ["rollback_safety_depth"]
|
|
569
687
|
},
|
|
570
688
|
{
|
|
571
689
|
key: "dependency-automation-ratio",
|
|
572
|
-
term: "
|
|
690
|
+
term: "Dependency automation ratio",
|
|
573
691
|
definition: "The share of expected automated dependency-update and audit controls that are present. Stale or vulnerable packages otherwise depend on manual discovery.",
|
|
574
692
|
metricNames: ["dependency_automation_ratio"]
|
|
575
693
|
},
|
|
576
694
|
{
|
|
577
695
|
key: "pinned-dependency-ratio",
|
|
578
|
-
term: "
|
|
696
|
+
term: "Pinned dependency ratio",
|
|
579
697
|
definition: "The share of application dependencies fixed to exact versions. Exact inputs make deployed builds more reproducible.",
|
|
580
698
|
metricNames: ["pinned_dependency_ratio"]
|
|
581
699
|
},
|
|
582
700
|
{
|
|
583
701
|
key: "lockfile-coverage",
|
|
584
|
-
term: "
|
|
585
|
-
definition: "
|
|
702
|
+
term: "Lockfile coverage",
|
|
703
|
+
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
704
|
metricNames: ["lockfile_coverage"]
|
|
587
705
|
},
|
|
588
706
|
{
|
|
589
707
|
key: "declared-version-range-ratio",
|
|
590
|
-
term: "
|
|
591
|
-
definition: "
|
|
708
|
+
term: "Declared version range ratio",
|
|
709
|
+
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
710
|
metricNames: ["declared_version_range_ratio"]
|
|
593
711
|
},
|
|
594
712
|
{
|
|
595
713
|
key: "dependency-count-sanity",
|
|
596
|
-
term: "
|
|
597
|
-
definition: "A
|
|
714
|
+
term: "Dependency count sanity",
|
|
715
|
+
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
716
|
metricNames: ["dependency_count_sanity"]
|
|
599
717
|
},
|
|
600
718
|
{
|
|
601
719
|
key: "claim-match-rate",
|
|
602
|
-
term: "claim
|
|
603
|
-
definition: "
|
|
720
|
+
term: "Implementation-to-claim-source file ratio",
|
|
721
|
+
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
722
|
metricNames: ["claim_match_rate"]
|
|
605
723
|
},
|
|
606
724
|
{
|
|
607
725
|
key: "claim-source-depth",
|
|
608
|
-
term: "
|
|
609
|
-
definition: "
|
|
726
|
+
term: "Claim source depth",
|
|
727
|
+
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
728
|
metricNames: ["claim_source_depth"]
|
|
611
729
|
},
|
|
612
730
|
{
|
|
613
731
|
key: "reconciliation-artifact-depth",
|
|
614
|
-
term: "
|
|
615
|
-
definition: "The
|
|
732
|
+
term: "Reconciliation artifact depth",
|
|
733
|
+
definition: "The number of detected dedicated claim-reality reconciliation artifacts, capped at three for scoring.",
|
|
616
734
|
metricNames: ["reconciliation_artifact_depth"]
|
|
617
735
|
},
|
|
618
736
|
{
|
|
619
737
|
key: "recent-pr-merge-ratio",
|
|
620
|
-
term: "
|
|
621
|
-
definition: "
|
|
738
|
+
term: "Recent commits with recognizable PR references",
|
|
739
|
+
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
740
|
metricNames: ["pr_merge_ratio"]
|
|
623
741
|
},
|
|
624
742
|
{
|
|
625
743
|
key: "ci-verification-depth",
|
|
626
744
|
term: "CI verification depth",
|
|
627
|
-
definition: "
|
|
745
|
+
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
746
|
metricNames: ["ci_script_depth"]
|
|
629
747
|
},
|
|
630
748
|
{
|
|
631
749
|
key: "pr-gate-ci-workflow-count",
|
|
632
|
-
term: "PR-gate CI workflow count",
|
|
633
|
-
definition: "
|
|
750
|
+
term: "PR-gate CI workflow count (automated build/test pipeline)",
|
|
751
|
+
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
752
|
metricNames: ["default_branch_ci_depth"]
|
|
635
753
|
},
|
|
636
754
|
{
|
|
637
755
|
key: "audit-artifact-depth",
|
|
638
|
-
term: "
|
|
639
|
-
definition: "
|
|
756
|
+
term: "Audit artifact depth",
|
|
757
|
+
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
758
|
metricNames: ["audit_artifact_depth"]
|
|
641
759
|
},
|
|
642
760
|
{
|
|
643
761
|
key: "audit-freshness-depth",
|
|
644
|
-
term: "
|
|
645
|
-
definition: "
|
|
762
|
+
term: "Audit freshness depth",
|
|
763
|
+
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
764
|
metricNames: ["audit_freshness_depth"]
|
|
647
765
|
},
|
|
648
766
|
{
|
|
649
767
|
key: "un-overridable-kill-switch",
|
|
650
|
-
term: "
|
|
651
|
-
definition: "A
|
|
768
|
+
term: "Un-overridable kill-switch present",
|
|
769
|
+
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
770
|
metricNames: ["kill_switch_fail_safe_present"]
|
|
653
771
|
},
|
|
654
772
|
{
|
|
655
773
|
key: "human-gate-documented",
|
|
656
|
-
term: "
|
|
657
|
-
definition: "A
|
|
774
|
+
term: "Human gate documented",
|
|
775
|
+
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
776
|
metricNames: ["human_gate_documented"]
|
|
659
777
|
},
|
|
660
778
|
{
|
|
661
779
|
key: "fail-closed-privilege-check",
|
|
662
|
-
term: "
|
|
663
|
-
definition: "A
|
|
780
|
+
term: "Fail-closed privilege check present",
|
|
781
|
+
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
782
|
metricNames: ["fail_closed_privilege_check"]
|
|
665
783
|
},
|
|
666
784
|
{
|
|
667
785
|
key: "privilege-escalation-cleanliness",
|
|
668
|
-
term: "
|
|
669
|
-
definition: "
|
|
786
|
+
term: "Privilege-escalation cleanliness",
|
|
787
|
+
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
788
|
metricNames: ["privilege_escalation_cleanliness"]
|
|
671
789
|
},
|
|
672
790
|
{
|
|
673
791
|
key: "protected-path-review-gate",
|
|
674
|
-
term: "
|
|
675
|
-
definition: "A
|
|
792
|
+
term: "Protected-path review gate",
|
|
793
|
+
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
794
|
metricNames: ["protected_path_review_gate"]
|
|
677
795
|
}
|
|
678
796
|
];
|
|
@@ -689,14 +807,8 @@ function glossaryEntryForMetric(metric2) {
|
|
|
689
807
|
metricNames: [metric2.name]
|
|
690
808
|
};
|
|
691
809
|
}
|
|
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
810
|
function formatCertificateMetricLabel(metric2) {
|
|
699
|
-
return
|
|
811
|
+
return CERTIFICATE_METRIC_REGISTRY[metric2.name]?.displayLabel ?? metric2.label;
|
|
700
812
|
}
|
|
701
813
|
function glossaryEntriesForReport(report) {
|
|
702
814
|
const entries = new Map(CERTIFICATE_GLOSSARY.map((entry) => [entry.key, entry]));
|
|
@@ -718,6 +830,62 @@ function labelAlreadyStatesUnit(label, unit) {
|
|
|
718
830
|
const escapedUnit = unit.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
719
831
|
return new RegExp(`(?:^|[\\s-])${escapedUnit}$`, "i").test(label.trim());
|
|
720
832
|
}
|
|
833
|
+
var BINARY_METRIC_COPY = {
|
|
834
|
+
secret_cleanliness: {
|
|
835
|
+
positive: "clean \u2014 no critical secret findings detected",
|
|
836
|
+
negative: "not clean \u2014 one or more critical secret findings detected"
|
|
837
|
+
},
|
|
838
|
+
crypto_comparison_hygiene: {
|
|
839
|
+
positive: "clean \u2014 no insecure comparison or serialization pattern detected",
|
|
840
|
+
negative: "not clean \u2014 an insecure comparison or serialization pattern was detected"
|
|
841
|
+
},
|
|
842
|
+
lockfile_coverage: {
|
|
843
|
+
positive: "present \u2014 at least one covering lockfile detected",
|
|
844
|
+
negative: "missing \u2014 no covering lockfile detected"
|
|
845
|
+
},
|
|
846
|
+
kill_switch_fail_safe_present: {
|
|
847
|
+
positive: "present \u2014 a fail-closed safety toggle was detected",
|
|
848
|
+
negative: "missing \u2014 no fail-closed safety toggle detected"
|
|
849
|
+
},
|
|
850
|
+
human_gate_documented: {
|
|
851
|
+
positive: "present \u2014 privileged operations are documented as human-executed or gated",
|
|
852
|
+
negative: "missing \u2014 no human-gate documentation detected"
|
|
853
|
+
},
|
|
854
|
+
fail_closed_privilege_check: {
|
|
855
|
+
positive: "present \u2014 role membership is checked fail-closed before elevation",
|
|
856
|
+
negative: "missing \u2014 no fail-closed pre-elevation role check detected"
|
|
857
|
+
},
|
|
858
|
+
privilege_escalation_cleanliness: {
|
|
859
|
+
positive: "clean \u2014 no ungated production privilege-escalation pattern detected",
|
|
860
|
+
negative: "not clean \u2014 an ungated production privilege-escalation pattern was detected"
|
|
861
|
+
},
|
|
862
|
+
protected_path_review_gate: {
|
|
863
|
+
positive: "present \u2014 CODEOWNERS or a documented required-review policy was detected",
|
|
864
|
+
negative: "missing \u2014 no CODEOWNERS or documented required-review policy detected"
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
var CONDITIONAL_METRIC_COPY = {
|
|
868
|
+
crypto_comparison_hygiene: "scored because a signing, HMAC, or secret-comparison surface was detected",
|
|
869
|
+
rls_policy_count: "scored because a multi-tenant data surface was detected",
|
|
870
|
+
tenant_scope_ratio: "scored because a multi-tenant data surface was detected",
|
|
871
|
+
human_gate_documented: "scored because a privileged-operation-shaped surface was detected",
|
|
872
|
+
fail_closed_privilege_check: "scored because a privileged-operation-shaped surface was detected",
|
|
873
|
+
kill_switch_fail_safe_present: "shown because a fail-closed kill switch was detected; this positive-only metric is omitted when absent"
|
|
874
|
+
};
|
|
875
|
+
function appendMetricCondition(metric2, value) {
|
|
876
|
+
const condition = metric2.presentation?.condition ?? CONDITIONAL_METRIC_COPY[metric2.name];
|
|
877
|
+
return condition ? `${value}; ${condition}` : value;
|
|
878
|
+
}
|
|
879
|
+
function formatEnumerableMetric(metric2) {
|
|
880
|
+
const components = metric2.presentation?.components;
|
|
881
|
+
if (!components || metric2.max === void 0) return null;
|
|
882
|
+
const present = components.filter((component) => component.count > 0).map(
|
|
883
|
+
(component) => component.count === 1 ? component.label : `${component.label} (${component.count} detected)`
|
|
884
|
+
);
|
|
885
|
+
const missing = components.filter((component) => component.count === 0).map((component) => component.label);
|
|
886
|
+
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)}`;
|
|
887
|
+
return `${value} \u2014 present: ${present.length > 0 ? present.join(", ") : "none"}; missing: ${missing.length > 0 ? missing.join(", ") : "none"}`;
|
|
888
|
+
}
|
|
721
889
|
function formatCertificateMetricValue(criterion, metric2) {
|
|
722
890
|
if (isCoverageNotMeasured(criterion, metric2)) {
|
|
723
891
|
return "no coverage report found \u2014 not measured";
|
|
@@ -726,15 +894,36 @@ function formatCertificateMetricValue(criterion, metric2) {
|
|
|
726
894
|
const tests = formatMetricNumber(metric2.value);
|
|
727
895
|
const sources = formatMetricNumber(metric2.max);
|
|
728
896
|
const comparison = `${tests} test file${metric2.value === 1 ? "" : "s"} / ${sources} source file${metric2.max === 1 ? "" : "s"}`;
|
|
729
|
-
return
|
|
897
|
+
return appendMetricCondition(
|
|
898
|
+
metric2,
|
|
899
|
+
metric2.value > metric2.max ? `${comparison} (credit capped at parity)` : comparison
|
|
900
|
+
);
|
|
730
901
|
}
|
|
902
|
+
const binaryCopy = BINARY_METRIC_COPY[metric2.name];
|
|
903
|
+
if (binaryCopy && metric2.max === 1 && (metric2.value === 0 || metric2.value === 1)) {
|
|
904
|
+
return appendMetricCondition(metric2, metric2.value === 1 ? binaryCopy.positive : binaryCopy.negative);
|
|
905
|
+
}
|
|
906
|
+
if (metric2.name === "pr_merge_ratio" && metric2.max !== void 0) {
|
|
907
|
+
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`;
|
|
908
|
+
return appendMetricCondition(metric2, value);
|
|
909
|
+
}
|
|
910
|
+
const enumerable = formatEnumerableMetric(metric2);
|
|
911
|
+
if (enumerable) return appendMetricCondition(metric2, enumerable);
|
|
731
912
|
const displayUnit = metric2.name === "prod_readiness_primitives" || metric2.name === "pr_trace_primitives" ? "checks" : labelAlreadyStatesUnit(formatCertificateMetricLabel(metric2), metric2.unit) ? void 0 : metric2.unit;
|
|
732
913
|
const unit = displayUnit ? ` ${displayUnit}` : "";
|
|
733
|
-
if (metric2.max === void 0)
|
|
914
|
+
if (metric2.max === void 0) {
|
|
915
|
+
return appendMetricCondition(metric2, `${formatMetricNumber(metric2.value)}${unit}`);
|
|
916
|
+
}
|
|
734
917
|
if (metric2.kind === "saturating_count" && metric2.value > metric2.max) {
|
|
735
|
-
return
|
|
918
|
+
return appendMetricCondition(
|
|
919
|
+
metric2,
|
|
920
|
+
`${formatMetricNumber(metric2.max)}${unit} (capped; ${formatMetricNumber(metric2.value)} raw)`
|
|
921
|
+
);
|
|
736
922
|
}
|
|
737
|
-
return
|
|
923
|
+
return appendMetricCondition(
|
|
924
|
+
metric2,
|
|
925
|
+
`${formatMetricNumber(metric2.value)}/${formatMetricNumber(metric2.max)}${unit}`
|
|
926
|
+
);
|
|
738
927
|
}
|
|
739
928
|
function buildRelyingPartySummary(report) {
|
|
740
929
|
const applicable = report.criteria.filter((criterion) => criterion.status !== "not_applicable");
|
|
@@ -749,6 +938,13 @@ function buildRelyingPartySummary(report) {
|
|
|
749
938
|
"No coverage report or configured threshold was found, so static coverage was not measured; Cejel does not run the subject's tests."
|
|
750
939
|
);
|
|
751
940
|
}
|
|
941
|
+
if (!report.repo.headSha && report.criteria.some(
|
|
942
|
+
(criterion) => criterion.metrics.some((metric2) => metric2.name === "pr_merge_ratio")
|
|
943
|
+
)) {
|
|
944
|
+
gaps.push(
|
|
945
|
+
"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."
|
|
946
|
+
);
|
|
947
|
+
}
|
|
752
948
|
const unmeasured = applicable.filter((criterion) => criterion.status === "insufficient_data");
|
|
753
949
|
if (unmeasured.length > 0) {
|
|
754
950
|
gaps.push(`The report did not measure ${unmeasured.map((criterion) => criterion.id).join(", ")}.`);
|
|
@@ -788,7 +984,7 @@ var METRIC_DERIVED_FINDING_PATTERN = /^[A-Z]\d+ metric-derived score is (\d+\.\d
|
|
|
788
984
|
var REMEDIATION_BY_METRIC = {
|
|
789
985
|
test_to_source_ratio: "add concrete regression tests for the implementation surface",
|
|
790
986
|
coverage_percent: "configure coverage and publish a measured threshold or report",
|
|
791
|
-
verification_script_ratio: "add
|
|
987
|
+
verification_script_ratio: "add the missing test, coverage, lint, or type-check command or a test-runner configuration",
|
|
792
988
|
non_hollow_test_share: "replace skipped or placeholder tests with live assertions",
|
|
793
989
|
env_handling_depth: "document and enforce safe environment-secret handling",
|
|
794
990
|
secret_cleanliness: "remove committed secrets and rotate any exposed credentials",
|
|
@@ -806,7 +1002,7 @@ var REMEDIATION_BY_METRIC = {
|
|
|
806
1002
|
claim_match_rate: "align documented product claims with observable repository evidence",
|
|
807
1003
|
claim_source_depth: "document concrete, checkable product claims",
|
|
808
1004
|
reconciliation_artifact_depth: "publish a claim-to-evidence reconciliation record",
|
|
809
|
-
pr_merge_ratio: "preserve
|
|
1005
|
+
pr_merge_ratio: "preserve recognizable pull-request references in recent commit subjects",
|
|
810
1006
|
pr_trace_primitives: "add pull-request templates and outcome-trace records",
|
|
811
1007
|
ci_script_depth: "run the repository verification commands in CI",
|
|
812
1008
|
default_branch_ci_depth: "run CI on the default branch and pull requests",
|
|
@@ -856,6 +1052,7 @@ function formatMetricMember(criterion, metric2) {
|
|
|
856
1052
|
}
|
|
857
1053
|
|
|
858
1054
|
// src/witan/html.ts
|
|
1055
|
+
var CEJEL_CERTIFICATE_FORMAT_VERSION = "1.0";
|
|
859
1056
|
function renderWitanHtmlReport(report, options = {}) {
|
|
860
1057
|
const codeCriteria = criteriaByCategory(report, "code_trust");
|
|
861
1058
|
const processCriteria = criteriaByCategory(report, "process_trust");
|
|
@@ -874,6 +1071,7 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
874
1071
|
<head>
|
|
875
1072
|
<meta charset="utf-8">
|
|
876
1073
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1074
|
+
<meta name="cejel-certificate-format" content="${CEJEL_CERTIFICATE_FORMAT_VERSION}">
|
|
877
1075
|
<title>Cejel Trust Certificate - ${escapeHtml(report.productDisplayName)}</title>
|
|
878
1076
|
<style>${CERTIFICATE_CSS}</style>
|
|
879
1077
|
</head>
|
|
@@ -889,10 +1087,14 @@ function renderWitanHtmlReport(report, options = {}) {
|
|
|
889
1087
|
<p class="eyebrow">Trust Certificate</p>
|
|
890
1088
|
<h1>${escapeHtml(report.productDisplayName)}</h1>
|
|
891
1089
|
<dl class="meta">
|
|
1090
|
+
<div><dt>Product identity</dt><dd>${escapeHtml(CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE)}</dd></div>
|
|
892
1091
|
${options.generatedAt ? `<div><dt>Date</dt><dd>${escapeHtml(formatDate(options.generatedAt))}</dd></div>` : ""}
|
|
893
1092
|
<div><dt>Run</dt><dd>${escapeHtml(renderRepo(report))}</dd></div>
|
|
894
1093
|
<div><dt>CLI</dt><dd>${escapeHtml(options.cliVersion ? `Cejel ${options.cliVersion}` : "Not recorded")}</dd></div>
|
|
895
1094
|
<div><dt>Rubric</dt><dd>${escapeHtml(report.rubricVersion)}</dd></div>
|
|
1095
|
+
${// Gated on the rubric actually being prospective — a calibrated (v17) certificate's
|
|
1096
|
+
// HTML stays byte-identical to before --rubric-pin existed.
|
|
1097
|
+
isProspectiveRubricVersion(report.rubricVersion) ? `<div><dt>Calibration</dt><dd class="prospective-rubric-notice">${escapeHtml(PROSPECTIVE_RUBRIC_NOTICE)}</dd></div>` : ""}
|
|
896
1098
|
${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
1099
|
</dl>
|
|
898
1100
|
</section>
|
|
@@ -1052,7 +1254,7 @@ function renderNotApplicableGroup(criteria) {
|
|
|
1052
1254
|
}
|
|
1053
1255
|
function renderNotApplicableItem(criterion) {
|
|
1054
1256
|
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>`;
|
|
1257
|
+
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
1258
|
}
|
|
1057
1259
|
function renderCriterionCard(criterion, gitHistoryUnavailable) {
|
|
1058
1260
|
const evidence = [
|
|
@@ -1061,7 +1263,7 @@ function renderCriterionCard(criterion, gitHistoryUnavailable) {
|
|
|
1061
1263
|
];
|
|
1062
1264
|
const metrics = criterion.metrics.map((metric2) => renderMetric(criterion, metric2));
|
|
1063
1265
|
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
|
|
1266
|
+
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
1267
|
return `<article class="criterion">
|
|
1066
1268
|
<div class="criterion-top">
|
|
1067
1269
|
<div>
|
|
@@ -1082,11 +1284,22 @@ function renderCriterionCard(criterion, gitHistoryUnavailable) {
|
|
|
1082
1284
|
</article>`;
|
|
1083
1285
|
}
|
|
1084
1286
|
function renderStatusReconciliation(criterion) {
|
|
1085
|
-
if (criterion.status === "not_applicable"
|
|
1086
|
-
return "";
|
|
1287
|
+
if (criterion.status === "not_applicable") {
|
|
1288
|
+
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>';
|
|
1289
|
+
}
|
|
1290
|
+
if (criterion.status === "insufficient_data") {
|
|
1291
|
+
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>';
|
|
1292
|
+
}
|
|
1293
|
+
if (criterion.status === "unverified") {
|
|
1294
|
+
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
1295
|
}
|
|
1088
1296
|
const numericBand = scoreBandForPresentation(criterion.score);
|
|
1089
|
-
if (numericBand === criterion.status)
|
|
1297
|
+
if (numericBand === criterion.status) {
|
|
1298
|
+
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>`;
|
|
1299
|
+
}
|
|
1300
|
+
if (criterion.status !== "verified" && criterion.status !== "info" && criterion.status !== "warning" && criterion.status !== "critical") {
|
|
1301
|
+
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>`;
|
|
1302
|
+
}
|
|
1090
1303
|
const entry = CERTIFICATE_GLOSSARY.find((candidate) => candidate.key === "labels");
|
|
1091
1304
|
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
1305
|
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 +1316,7 @@ function renderMetric(criterion, metric2) {
|
|
|
1103
1316
|
const formatted = formatCertificateMetricValue(criterion, metric2);
|
|
1104
1317
|
const cappedEntry = formatted.includes("capped") ? CERTIFICATE_GLOSSARY.find((candidate) => candidate.key === "capped") : void 0;
|
|
1105
1318
|
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>`;
|
|
1319
|
+
if (!cappedEntry) return `${label}<span class="metric-value">${escapeHtml(formatted)}</span>`;
|
|
1107
1320
|
const cappedTooltipId = `${tooltipId}-capped`;
|
|
1108
1321
|
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
1322
|
}
|
|
@@ -1274,6 +1487,8 @@ h2 { font-family: var(--serif); font-size: 30px; font-weight: 400; letter-spacin
|
|
|
1274
1487
|
.na-list li { margin: 0; color: var(--muted); }
|
|
1275
1488
|
.na-id { color: var(--faint); font-family: var(--mono); font-size: 11px; margin-right: 6px; }
|
|
1276
1489
|
.na-reason { color: var(--muted); }
|
|
1490
|
+
.na-reconciliation { display: block; margin-top: 4px; color: var(--muted); }
|
|
1491
|
+
.na-reconciliation strong { color: var(--text); }
|
|
1277
1492
|
.criterion { border: 1px solid var(--line); border-radius: 8px; padding: 16px; background: var(--surface-2); }
|
|
1278
1493
|
.criterion-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; }
|
|
1279
1494
|
.criterion-id { color: var(--periwinkle); font-family: var(--mono); font-size: 12px; margin-bottom: 4px; }
|
|
@@ -1288,7 +1503,10 @@ h3 { font-size: 15px; line-height: 1.35; font-weight: 600; margin-bottom: 0; }
|
|
|
1288
1503
|
.criterion-metrics { display: grid; gap: 7px; margin-top: 10px; padding: 0; list-style: none; font-size: 12px; }
|
|
1289
1504
|
.criterion-metrics li { display: flex; justify-content: space-between; gap: 12px; border-bottom: 1px solid rgba(238, 244, 247, .08); padding-bottom: 6px; }
|
|
1290
1505
|
.criterion-metrics strong { color: var(--text); font-weight: 650; }
|
|
1291
|
-
.criterion-metrics span {
|
|
1506
|
+
.criterion-metrics span { color: var(--muted); }
|
|
1507
|
+
.criterion-metrics > li > .metric-value {
|
|
1508
|
+
min-width: 0; white-space: normal; overflow-wrap: anywhere; text-align: right;
|
|
1509
|
+
}
|
|
1292
1510
|
.term-help { position: relative; cursor: help; outline: none; }
|
|
1293
1511
|
.term-help:focus-visible { box-shadow: 0 0 0 2px var(--periwinkle); border-radius: 3px; }
|
|
1294
1512
|
.term-tooltip {
|
|
@@ -1448,6 +1666,7 @@ function createWitanAttestation(report, options) {
|
|
|
1448
1666
|
predicate: {
|
|
1449
1667
|
tool: { name: "cejel", version: options.toolVersion },
|
|
1450
1668
|
generatedAt: options.generatedAt,
|
|
1669
|
+
reportFormatVersion: WITAN_REPORT_FORMAT_VERSION,
|
|
1451
1670
|
rubricVersion: report.rubricVersion,
|
|
1452
1671
|
repository: {
|
|
1453
1672
|
productSlug: report.productSlug,
|
|
@@ -1527,47 +1746,29 @@ function verifyWitanAttestationBinding(statement, report, options = {}) {
|
|
|
1527
1746
|
return { valid: errors.length === 0, errors };
|
|
1528
1747
|
}
|
|
1529
1748
|
|
|
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
1749
|
// src/witan/index.ts
|
|
1566
1750
|
init_esm_shims();
|
|
1567
1751
|
|
|
1568
1752
|
// src/witan/markdown.ts
|
|
1569
1753
|
init_esm_shims();
|
|
1570
1754
|
|
|
1755
|
+
// src/presentation-safety.ts
|
|
1756
|
+
init_esm_shims();
|
|
1757
|
+
var PRESENTATION_CONTROL_PATTERN = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
|
|
1758
|
+
function sanitizePresentationLine(value, options) {
|
|
1759
|
+
const withoutControls = value.replace(PRESENTATION_CONTROL_PATTERN, "").trim();
|
|
1760
|
+
const safeValue = withoutControls.length > 0 ? withoutControls : options.fallback;
|
|
1761
|
+
if (safeValue.length <= options.maxLength) return safeValue;
|
|
1762
|
+
const ellipsis = ".".repeat(Math.min(3, options.maxLength));
|
|
1763
|
+
const contentLimit = Math.max(options.maxLength - ellipsis.length, 0);
|
|
1764
|
+
let prefix = "";
|
|
1765
|
+
for (const character of safeValue) {
|
|
1766
|
+
if (prefix.length + character.length > contentLimit) break;
|
|
1767
|
+
prefix += character;
|
|
1768
|
+
}
|
|
1769
|
+
return `${prefix}${ellipsis}`;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1571
1772
|
// src/witan/repo-signals.ts
|
|
1572
1773
|
init_esm_shims();
|
|
1573
1774
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -1589,6 +1790,71 @@ function stripBom(text) {
|
|
|
1589
1790
|
function usesV17DetectorClosure(rubricVersion) {
|
|
1590
1791
|
return rubricVersion === WITAN_RUBRIC_VERSION_V17 || rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
1591
1792
|
}
|
|
1793
|
+
var LEGACY_INVENTORY_ABSENCE_CONTEXT = {
|
|
1794
|
+
rubricGeneration: "legacy",
|
|
1795
|
+
inventoryProvenance: "directory-walk"
|
|
1796
|
+
};
|
|
1797
|
+
var INVENTORY_SCAN_PATTERN_SETS = {
|
|
1798
|
+
a1AuthenticatedAbsence: {
|
|
1799
|
+
id: "cejel.core-a1.test-integrity-surface.v1",
|
|
1800
|
+
count: 4
|
|
1801
|
+
},
|
|
1802
|
+
a1ConcreteTests: {
|
|
1803
|
+
id: "cejel.core-a1.concrete-test-files.v1",
|
|
1804
|
+
count: 16
|
|
1805
|
+
},
|
|
1806
|
+
a1CoverageConfiguration: {
|
|
1807
|
+
id: "cejel.core-a1.coverage-configuration.v1",
|
|
1808
|
+
count: 10
|
|
1809
|
+
},
|
|
1810
|
+
a2CurrentSecret: {
|
|
1811
|
+
id: "cejel.core-a2.current-secret-shape.v1",
|
|
1812
|
+
count: 3
|
|
1813
|
+
},
|
|
1814
|
+
a2RlsPolicy: {
|
|
1815
|
+
id: "cejel.core-a2.rls-policy.v1",
|
|
1816
|
+
count: 3
|
|
1817
|
+
},
|
|
1818
|
+
a3CiOrReleaseDeploy: {
|
|
1819
|
+
id: "cejel.core-a3.ci-or-release-deploy.v1",
|
|
1820
|
+
count: 12
|
|
1821
|
+
},
|
|
1822
|
+
a4Lockfile: {
|
|
1823
|
+
id: "cejel.core-a4.lockfile.v1",
|
|
1824
|
+
count: 11
|
|
1825
|
+
},
|
|
1826
|
+
a5ClaimRealityArtifact: {
|
|
1827
|
+
id: "cejel.core-a5.claim-reality-artifact.v1",
|
|
1828
|
+
count: 3
|
|
1829
|
+
}
|
|
1830
|
+
};
|
|
1831
|
+
function inventoryScanDerivation(inventoryCount, patternSet, matchCount) {
|
|
1832
|
+
return {
|
|
1833
|
+
kind: "inventory-scan",
|
|
1834
|
+
inventoryScope: "tracked-scan-eligible",
|
|
1835
|
+
inventoryCount,
|
|
1836
|
+
patternSetId: patternSet.id,
|
|
1837
|
+
patternCount: patternSet.count,
|
|
1838
|
+
matchCount
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
function inventoryAbsenceFinding(options) {
|
|
1842
|
+
const { context, summaries } = options;
|
|
1843
|
+
const summary = context.rubricGeneration === "legacy" ? summaries.legacy : context.inventoryProvenance === "tracked-scan-eligible" ? summaries.currentTracked : summaries.currentDirectoryWalk;
|
|
1844
|
+
const emitsDerivation = context.rubricGeneration === "current" && context.inventoryProvenance === "tracked-scan-eligible";
|
|
1845
|
+
return {
|
|
1846
|
+
severity: options.severity,
|
|
1847
|
+
summary,
|
|
1848
|
+
evidence: options.evidence,
|
|
1849
|
+
...emitsDerivation ? {
|
|
1850
|
+
derivation: inventoryScanDerivation(
|
|
1851
|
+
options.inventoryCount,
|
|
1852
|
+
options.patternSet,
|
|
1853
|
+
options.matchCount
|
|
1854
|
+
)
|
|
1855
|
+
} : {}
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1592
1858
|
function buildWitanInputFromRepo(options) {
|
|
1593
1859
|
const tracked = trackContentReads(options.repoPath, () => buildWitanInputFromRepoUntracked(options));
|
|
1594
1860
|
const affectedCriteria = tracked.affectedCriteria;
|
|
@@ -1627,6 +1893,10 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
1627
1893
|
const inventoryFiles = fileInventory.inventoryFiles;
|
|
1628
1894
|
const ignoredTargetReason = repoFiles.length === 0 ? explainIgnoredScanTarget(options.repoPath) : void 0;
|
|
1629
1895
|
const usesV17Detectors = usesV17DetectorClosure(rubricVersion);
|
|
1896
|
+
const inventoryAbsenceContext = {
|
|
1897
|
+
rubricGeneration: usesV17Detectors ? "current" : "legacy",
|
|
1898
|
+
inventoryProvenance: fileInventory.repoFileInventoryScope === "tracked-scan-eligible" ? "tracked-scan-eligible" : "directory-walk"
|
|
1899
|
+
};
|
|
1630
1900
|
const usesV18NativeRls = rubricVersion === WITAN_RUBRIC_VERSION_V18 || rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
1631
1901
|
const usesV19CommitYear = rubricVersion === WITAN_RUBRIC_VERSION_V19 || rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
1632
1902
|
const usesV20A3ExplicitGaps = rubricVersion === WITAN_RUBRIC_VERSION_V20 || rubricVersion === WITAN_RUBRIC_VERSION_V21 || rubricVersion === WITAN_RUBRIC_VERSION_V22;
|
|
@@ -1668,6 +1938,7 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
1668
1938
|
usesV21ExecutedEscalations,
|
|
1669
1939
|
usesV22PackageStartEntrypoint,
|
|
1670
1940
|
reviewableSourceProof,
|
|
1941
|
+
inventoryAbsenceContext,
|
|
1671
1942
|
scanLimitations
|
|
1672
1943
|
);
|
|
1673
1944
|
const archetype = usesV17Detectors && reviewableSourceProof?.passes === true && isV17RescuableAbstention(gatedArchetype.abstentionKind) && coreSignals.some(isMeasuredCriterionSignal) ? {
|
|
@@ -1677,7 +1948,10 @@ function buildWitanInputFromRepoUntracked(options) {
|
|
|
1677
1948
|
} : gatedArchetype;
|
|
1678
1949
|
return {
|
|
1679
1950
|
productSlug: options.productSlug,
|
|
1680
|
-
productDisplayName: options.productDisplayName,
|
|
1951
|
+
productDisplayName: sanitizePresentationLine(options.productDisplayName, {
|
|
1952
|
+
fallback: "Repository",
|
|
1953
|
+
maxLength: 120
|
|
1954
|
+
}),
|
|
1681
1955
|
repo: {
|
|
1682
1956
|
...headSha ? { headSha } : {}
|
|
1683
1957
|
},
|
|
@@ -2274,7 +2548,7 @@ function buildNotApplicableSignal(criterionId, reason) {
|
|
|
2274
2548
|
notApplicable: true
|
|
2275
2549
|
};
|
|
2276
2550
|
}
|
|
2277
|
-
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, reviewableSourceProof, scanLimitations = /* @__PURE__ */ new Set()) {
|
|
2551
|
+
function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, useV19CommitYear, useV20A3ExplicitGaps, useV21ExecutedEscalations, useV22PackageStartEntrypoint, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT, scanLimitations = /* @__PURE__ */ new Set()) {
|
|
2278
2552
|
const signals = [];
|
|
2279
2553
|
const mono = resolveMonorepoContext(repoPath, scanLimitations);
|
|
2280
2554
|
const collectCriterion = (criterionId, collect) => withContentReadCriterion(criterionId, collect);
|
|
@@ -2286,7 +2560,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
2286
2560
|
useV27Detectors,
|
|
2287
2561
|
useV33Detectors,
|
|
2288
2562
|
useV36Detectors,
|
|
2289
|
-
reviewableSourceProof
|
|
2563
|
+
reviewableSourceProof,
|
|
2564
|
+
inventoryAbsenceContext
|
|
2290
2565
|
)
|
|
2291
2566
|
);
|
|
2292
2567
|
const a2Signal = collectCriterion(
|
|
@@ -2300,6 +2575,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
2300
2575
|
useV39Detectors,
|
|
2301
2576
|
useV47Detectors,
|
|
2302
2577
|
useV18NativeRls,
|
|
2578
|
+
inventoryAbsenceContext,
|
|
2303
2579
|
scanLimitations
|
|
2304
2580
|
)
|
|
2305
2581
|
);
|
|
@@ -2310,7 +2586,8 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
2310
2586
|
repoFiles,
|
|
2311
2587
|
useV27Detectors,
|
|
2312
2588
|
useV20A3ExplicitGaps,
|
|
2313
|
-
useV22PackageStartEntrypoint
|
|
2589
|
+
useV22PackageStartEntrypoint,
|
|
2590
|
+
inventoryAbsenceContext
|
|
2314
2591
|
)
|
|
2315
2592
|
);
|
|
2316
2593
|
const a4Signal = collectCriterion(
|
|
@@ -2320,12 +2597,18 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
2320
2597
|
repoFiles,
|
|
2321
2598
|
mono,
|
|
2322
2599
|
useV27Detectors,
|
|
2323
|
-
useV47Detectors
|
|
2600
|
+
useV47Detectors,
|
|
2601
|
+
inventoryAbsenceContext
|
|
2324
2602
|
)
|
|
2325
2603
|
);
|
|
2326
2604
|
const a5Signal = collectCriterion(
|
|
2327
2605
|
"A5",
|
|
2328
|
-
() => collectA5ClaimRealityEvidence(
|
|
2606
|
+
() => collectA5ClaimRealityEvidence(
|
|
2607
|
+
repoPath,
|
|
2608
|
+
repoFiles,
|
|
2609
|
+
useV27Detectors,
|
|
2610
|
+
inventoryAbsenceContext
|
|
2611
|
+
)
|
|
2329
2612
|
);
|
|
2330
2613
|
const b1Signal = buildNotApplicableSignal(
|
|
2331
2614
|
"B1",
|
|
@@ -2376,7 +2659,7 @@ function collectRepoSignals(repoPath, generatedAt, repoFiles, useV27Detectors, u
|
|
|
2376
2659
|
}
|
|
2377
2660
|
return signals;
|
|
2378
2661
|
}
|
|
2379
|
-
function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, reviewableSourceProof) {
|
|
2662
|
+
function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, reviewableSourceProof, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
2380
2663
|
const evidence = [];
|
|
2381
2664
|
const findings = [];
|
|
2382
2665
|
const allTestFiles = findConcreteTestFiles(
|
|
@@ -2473,7 +2756,12 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2473
2756
|
}
|
|
2474
2757
|
}
|
|
2475
2758
|
if (evidence.length === 0 && findings.length === 0 && configuredRunnerFiles.length === 0) {
|
|
2476
|
-
return reviewableSourceProof?.passes === true && reviewableSourceProof.firstReadablePath ? buildA1AuthenticatedAbsenceSignal(
|
|
2759
|
+
return reviewableSourceProof?.passes === true && reviewableSourceProof.firstReadablePath ? buildA1AuthenticatedAbsenceSignal(
|
|
2760
|
+
repoPath,
|
|
2761
|
+
reviewableSourceProof,
|
|
2762
|
+
repoFiles.length,
|
|
2763
|
+
inventoryAbsenceContext
|
|
2764
|
+
) : null;
|
|
2477
2765
|
}
|
|
2478
2766
|
if (testFiles.length === 0) {
|
|
2479
2767
|
const fallback = configuredRunnerFiles[0] ? evidenceForRelative(
|
|
@@ -2483,11 +2771,21 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2483
2771
|
"Configured test runner"
|
|
2484
2772
|
) : useV33Detectors ? void 0 : evidence[0] ?? findings[0]?.evidence;
|
|
2485
2773
|
if (fallback) {
|
|
2486
|
-
findings.push(
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2774
|
+
findings.push(
|
|
2775
|
+
inventoryAbsenceFinding({
|
|
2776
|
+
severity: "warning",
|
|
2777
|
+
summaries: {
|
|
2778
|
+
legacy: "A test runner is configured, but no concrete test files were detected.",
|
|
2779
|
+
currentTracked: "A test runner is configured, but the tracked scan-eligible inventory matched no concrete test file.",
|
|
2780
|
+
currentDirectoryWalk: "A test runner is configured, but the bounded directory walk matched no concrete test file."
|
|
2781
|
+
},
|
|
2782
|
+
evidence: fallback,
|
|
2783
|
+
context: inventoryAbsenceContext,
|
|
2784
|
+
inventoryCount: repoFiles.length,
|
|
2785
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a1ConcreteTests,
|
|
2786
|
+
matchCount: 0
|
|
2787
|
+
})
|
|
2788
|
+
);
|
|
2491
2789
|
}
|
|
2492
2790
|
} else if (coverageFiles.length === 0 && (!useV33Detectors || configuredRunnerFiles.length > 0)) {
|
|
2493
2791
|
if (isLeanTestToolchain) {
|
|
@@ -2505,11 +2803,21 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2505
2803
|
} else {
|
|
2506
2804
|
const firstEvidence = evidence[0];
|
|
2507
2805
|
if (!firstEvidence) return null;
|
|
2508
|
-
findings.push(
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2806
|
+
findings.push(
|
|
2807
|
+
inventoryAbsenceFinding({
|
|
2808
|
+
severity: "info",
|
|
2809
|
+
summaries: {
|
|
2810
|
+
legacy: "Test suite files are present, but no coverage configuration was detected.",
|
|
2811
|
+
currentTracked: "Test suite files are present, but the tracked scan-eligible inventory matched no coverage configuration.",
|
|
2812
|
+
currentDirectoryWalk: "Test suite files are present, but the bounded directory walk matched no coverage configuration."
|
|
2813
|
+
},
|
|
2814
|
+
evidence: firstEvidence,
|
|
2815
|
+
context: inventoryAbsenceContext,
|
|
2816
|
+
inventoryCount: repoFiles.length,
|
|
2817
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a1CoverageConfiguration,
|
|
2818
|
+
matchCount: 0
|
|
2819
|
+
})
|
|
2820
|
+
);
|
|
2513
2821
|
}
|
|
2514
2822
|
}
|
|
2515
2823
|
const nonHollowShare = measureNonHollowTestShare(
|
|
@@ -2548,8 +2856,26 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2548
2856
|
4,
|
|
2549
2857
|
0.25,
|
|
2550
2858
|
"ratio",
|
|
2551
|
-
"
|
|
2552
|
-
"saturating_count"
|
|
2859
|
+
"Counts detected test, coverage, lint, and typecheck commands plus detected test-runner configuration files; raw credit is capped at four.",
|
|
2860
|
+
"saturating_count",
|
|
2861
|
+
{
|
|
2862
|
+
components: [
|
|
2863
|
+
{
|
|
2864
|
+
label: "test command",
|
|
2865
|
+
count: packageScripts.has("test") || Boolean(ciTestWorkflow) ? 1 : 0
|
|
2866
|
+
},
|
|
2867
|
+
{ label: "coverage command", count: packageScripts.has("coverage") ? 1 : 0 },
|
|
2868
|
+
{
|
|
2869
|
+
label: "lint command",
|
|
2870
|
+
count: packageScripts.has("lint") || ciHasLintCommand ? 1 : 0
|
|
2871
|
+
},
|
|
2872
|
+
{
|
|
2873
|
+
label: "type-check command",
|
|
2874
|
+
count: packageScripts.has("typecheck") || ciHasTypecheckCommand ? 1 : 0
|
|
2875
|
+
},
|
|
2876
|
+
{ label: "test-runner configuration", count: runnerFiles.length }
|
|
2877
|
+
]
|
|
2878
|
+
}
|
|
2553
2879
|
),
|
|
2554
2880
|
metric(
|
|
2555
2881
|
"non_hollow_test_share",
|
|
@@ -2564,7 +2890,7 @@ function collectA1TestIntegrityEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
2564
2890
|
notes: "A1 is detected from real test files, test runner configuration, and optional coverage configuration."
|
|
2565
2891
|
};
|
|
2566
2892
|
}
|
|
2567
|
-
function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
|
|
2893
|
+
function buildA1AuthenticatedAbsenceSignal(repoPath, proof, inventoryCount, inventoryAbsenceContext) {
|
|
2568
2894
|
const sourcePath = proof.firstReadablePath;
|
|
2569
2895
|
if (!sourcePath) {
|
|
2570
2896
|
throw new Error("Cejel invariant: a passing reviewable-source proof must have a readable path");
|
|
@@ -2579,11 +2905,19 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
|
|
|
2579
2905
|
criterionId: "A1",
|
|
2580
2906
|
positiveEvidence: [anchor],
|
|
2581
2907
|
findings: [
|
|
2582
|
-
{
|
|
2908
|
+
inventoryAbsenceFinding({
|
|
2583
2909
|
severity: "critical",
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2910
|
+
summaries: {
|
|
2911
|
+
legacy: WITAN_LEGACY_AUTHENTICATED_A1_ABSENCE_SUMMARY,
|
|
2912
|
+
currentTracked: WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY,
|
|
2913
|
+
currentDirectoryWalk: WITAN_DIRECTORY_WALK_AUTHENTICATED_A1_ABSENCE_SUMMARY
|
|
2914
|
+
},
|
|
2915
|
+
evidence: anchor,
|
|
2916
|
+
context: inventoryAbsenceContext,
|
|
2917
|
+
inventoryCount,
|
|
2918
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a1AuthenticatedAbsence,
|
|
2919
|
+
matchCount: 0
|
|
2920
|
+
})
|
|
2587
2921
|
],
|
|
2588
2922
|
metrics: [
|
|
2589
2923
|
metric(
|
|
@@ -2612,8 +2946,17 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
|
|
|
2612
2946
|
4,
|
|
2613
2947
|
0.25,
|
|
2614
2948
|
"ratio",
|
|
2615
|
-
"
|
|
2616
|
-
"saturating_count"
|
|
2949
|
+
"Counts detected test, coverage, lint, and typecheck commands plus detected test-runner configuration files; raw credit is capped at four.",
|
|
2950
|
+
"saturating_count",
|
|
2951
|
+
{
|
|
2952
|
+
components: [
|
|
2953
|
+
{ label: "test command", count: 0 },
|
|
2954
|
+
{ label: "coverage command", count: 0 },
|
|
2955
|
+
{ label: "lint command", count: 0 },
|
|
2956
|
+
{ label: "type-check command", count: 0 },
|
|
2957
|
+
{ label: "test-runner configuration", count: 0 }
|
|
2958
|
+
]
|
|
2959
|
+
}
|
|
2617
2960
|
),
|
|
2618
2961
|
metric(
|
|
2619
2962
|
"non_hollow_test_share",
|
|
@@ -2628,7 +2971,7 @@ function buildA1AuthenticatedAbsenceSignal(repoPath, proof) {
|
|
|
2628
2971
|
notes: `V17 authenticated absence: ${proof.eligibleSourceFileCount} criterion-ratable of ${proof.sourceShapedFileCount} source-shaped files; sampled ${proof.sampledFileCount}; ${proof.readableSampledFileCount} representative-text files.`
|
|
2629
2972
|
};
|
|
2630
2973
|
}
|
|
2631
|
-
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, scanLimitations) {
|
|
2974
|
+
function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33Detectors, useV36Detectors, useV39Detectors, useV47Detectors, useV18NativeRls, inventoryAbsenceContext, scanLimitations) {
|
|
2632
2975
|
const evidence = [];
|
|
2633
2976
|
const findings = [];
|
|
2634
2977
|
const gitignore = repoFiles.find((file) => basename(file) === ".gitignore");
|
|
@@ -2781,16 +3124,26 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2781
3124
|
)
|
|
2782
3125
|
});
|
|
2783
3126
|
} else if (committedEnvFilePath) {
|
|
2784
|
-
findings.push(
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
3127
|
+
findings.push(
|
|
3128
|
+
inventoryAbsenceFinding({
|
|
3129
|
+
severity: useV33Detectors ? "info" : "warning",
|
|
3130
|
+
summaries: {
|
|
3131
|
+
legacy: "A non-template .env file is committed in the current repository tree; no secret-shaped value was detected.",
|
|
3132
|
+
currentTracked: "A tracked non-template .env file is present, while the bounded scan over the tracked scan-eligible inventory matched no secret-shaped value.",
|
|
3133
|
+
currentDirectoryWalk: "A non-template .env file is present in the bounded directory walk, which matched no secret-shaped value."
|
|
3134
|
+
},
|
|
3135
|
+
evidence: evidenceForRelative(
|
|
3136
|
+
repoPath,
|
|
3137
|
+
committedEnvFilePath,
|
|
3138
|
+
"secret_scan",
|
|
3139
|
+
"Committed .env file (no confirmed secret value found)"
|
|
3140
|
+
),
|
|
3141
|
+
context: inventoryAbsenceContext,
|
|
3142
|
+
inventoryCount: repoFiles.length,
|
|
3143
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a2CurrentSecret,
|
|
3144
|
+
matchCount: 0
|
|
3145
|
+
})
|
|
3146
|
+
);
|
|
2794
3147
|
}
|
|
2795
3148
|
if (historySecretScan?.evidence) {
|
|
2796
3149
|
const historyPath = historySecretScan.evidence.path ?? "";
|
|
@@ -2822,16 +3175,26 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2822
3175
|
if (hasTenantWithoutRlsGap) {
|
|
2823
3176
|
const gapEvidence = rlsMigration ?? tenantScopedFile ?? tenantWithoutRlsPremiseFiles[0];
|
|
2824
3177
|
if (gapEvidence) {
|
|
2825
|
-
findings.push(
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
3178
|
+
findings.push(
|
|
3179
|
+
inventoryAbsenceFinding({
|
|
3180
|
+
severity: "warning",
|
|
3181
|
+
summaries: {
|
|
3182
|
+
legacy: "Tenant-scoped schema detected but no row-level-security policies found \u2014 isolation gap.",
|
|
3183
|
+
currentTracked: "A tenant-scoped schema signal is present, while the tracked scan-eligible inventory matched no row-level-security policy.",
|
|
3184
|
+
currentDirectoryWalk: "A tenant-scoped schema signal is present, while the bounded directory walk matched no row-level-security policy."
|
|
3185
|
+
},
|
|
3186
|
+
evidence: evidenceForRelative(
|
|
3187
|
+
repoPath,
|
|
3188
|
+
gapEvidence,
|
|
3189
|
+
"artifact",
|
|
3190
|
+
"Tenant schema without RLS policies"
|
|
3191
|
+
),
|
|
3192
|
+
context: inventoryAbsenceContext,
|
|
3193
|
+
inventoryCount: repoFiles.length,
|
|
3194
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a2RlsPolicy,
|
|
3195
|
+
matchCount: 0
|
|
3196
|
+
})
|
|
3197
|
+
);
|
|
2835
3198
|
}
|
|
2836
3199
|
}
|
|
2837
3200
|
evidence.push(...cryptoHygiene.evidence);
|
|
@@ -2855,7 +3218,22 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2855
3218
|
3,
|
|
2856
3219
|
isMultiTenant ? 0.1 : 0.3,
|
|
2857
3220
|
"practices",
|
|
2858
|
-
"Counts three bounded env-handling practices (0\u20133): template file, gitignore rule, env reads in any supported language."
|
|
3221
|
+
"Counts three bounded env-handling practices (0\u20133): template file, gitignore rule, env reads in any supported language.",
|
|
3222
|
+
void 0,
|
|
3223
|
+
{
|
|
3224
|
+
components: [
|
|
3225
|
+
{ label: "environment template file", count: envExamples.length > 0 ? 1 : 0 },
|
|
3226
|
+
{ label: ".gitignore environment-file rule", count: gitignoreEnvFile ? 1 : 0 },
|
|
3227
|
+
{
|
|
3228
|
+
label: "environment reads in implementation code",
|
|
3229
|
+
count: countFilesContaining(
|
|
3230
|
+
repoPath,
|
|
3231
|
+
repoFiles.filter(isImplementationFile),
|
|
3232
|
+
ENV_READ_PATTERN
|
|
3233
|
+
) > 0 ? 1 : 0
|
|
3234
|
+
}
|
|
3235
|
+
]
|
|
3236
|
+
}
|
|
2859
3237
|
)
|
|
2860
3238
|
];
|
|
2861
3239
|
const isolationMetrics = isMultiTenant ? [
|
|
@@ -2867,7 +3245,8 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2867
3245
|
0.4,
|
|
2868
3246
|
"policies",
|
|
2869
3247
|
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"
|
|
3248
|
+
"saturating_count",
|
|
3249
|
+
{ condition: "scored because a multi-tenant data surface was detected" }
|
|
2871
3250
|
),
|
|
2872
3251
|
metric(
|
|
2873
3252
|
"tenant_scope_ratio",
|
|
@@ -2876,7 +3255,9 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2876
3255
|
Math.max(migrationFiles.length, 1),
|
|
2877
3256
|
0.25,
|
|
2878
3257
|
"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."
|
|
3258
|
+
useV18NativeRls ? `Measures repository-native RLS scope identifiers (${nativeRlsInventory?.scopeIdentifiers.join(", ")}) across data-layer files.` : "Measures tenant/studio/workspace scoping evidence across data-layer files.",
|
|
3259
|
+
void 0,
|
|
3260
|
+
{ condition: "scored because a multi-tenant data surface was detected" }
|
|
2880
3261
|
)
|
|
2881
3262
|
] : [];
|
|
2882
3263
|
const cryptoMetrics = cryptoHygiene.hasSurface ? [
|
|
@@ -2887,7 +3268,11 @@ function collectA2IsolationEvidence(repoPath, repoFiles, useV27Detectors, useV33
|
|
|
2887
3268
|
1,
|
|
2888
3269
|
0.2,
|
|
2889
3270
|
"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."
|
|
3271
|
+
"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.",
|
|
3272
|
+
void 0,
|
|
3273
|
+
{
|
|
3274
|
+
condition: "scored because a signing, HMAC, or secret-comparison surface was detected"
|
|
3275
|
+
}
|
|
2891
3276
|
)
|
|
2892
3277
|
] : [];
|
|
2893
3278
|
return {
|
|
@@ -2994,7 +3379,7 @@ function collectCryptoHygieneEvidence(repoPath, implFiles, useV39Detectors = fal
|
|
|
2994
3379
|
}
|
|
2995
3380
|
return { evidence, findings, hasSurface, clean: hasSurface && !insecureFound };
|
|
2996
3381
|
}
|
|
2997
|
-
function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false) {
|
|
3382
|
+
function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, useV20ExplicitGaps = false, useV22PackageStartEntrypoint = false, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
2998
3383
|
const v20DirectHttpEntrypoint = useV20ExplicitGaps ? findV20DirectHttpServerEntrypointFile(repoPath, repoFiles) : null;
|
|
2999
3384
|
const v22PackageStartHttpEntrypoint = useV22PackageStartEntrypoint ? findV22PackageStartHttpServerEntrypointFile(repoPath, repoFiles) : null;
|
|
3000
3385
|
const v20RuntimeContainer = useV20ExplicitGaps ? findRuntimeContainerEntrypointFile(repoPath, repoFiles, true) : null;
|
|
@@ -3156,11 +3541,21 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3156
3541
|
if (!workflow && releaseDeployConfigs.length === 0) {
|
|
3157
3542
|
const firstEvidence = evidence[0];
|
|
3158
3543
|
if (!firstEvidence) return null;
|
|
3159
|
-
findings.push(
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3544
|
+
findings.push(
|
|
3545
|
+
inventoryAbsenceFinding({
|
|
3546
|
+
severity: "info",
|
|
3547
|
+
summaries: {
|
|
3548
|
+
legacy: "A deployable service surface exists, but no CI or release-deployment automation was detected.",
|
|
3549
|
+
currentTracked: "A deployable service signal is present, while the tracked scan-eligible inventory matched no CI workflow or release-deployment automation.",
|
|
3550
|
+
currentDirectoryWalk: "A deployable service signal is present, while the bounded directory walk matched no CI workflow or release-deployment automation."
|
|
3551
|
+
},
|
|
3552
|
+
evidence: firstEvidence,
|
|
3553
|
+
context: inventoryAbsenceContext,
|
|
3554
|
+
inventoryCount: repoFiles.length,
|
|
3555
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a3CiOrReleaseDeploy,
|
|
3556
|
+
matchCount: 0
|
|
3557
|
+
})
|
|
3558
|
+
);
|
|
3164
3559
|
}
|
|
3165
3560
|
return {
|
|
3166
3561
|
criterionId: "A3",
|
|
@@ -3181,7 +3576,21 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3181
3576
|
6,
|
|
3182
3577
|
0.55,
|
|
3183
3578
|
"checks",
|
|
3184
|
-
"Counts distinct static production-readiness checks instead of treating presence as enough."
|
|
3579
|
+
"Counts distinct static production-readiness checks instead of treating presence as enough.",
|
|
3580
|
+
void 0,
|
|
3581
|
+
{
|
|
3582
|
+
components: [
|
|
3583
|
+
{
|
|
3584
|
+
label: "build or type-check command",
|
|
3585
|
+
count: packageJson && (scripts.has("build") || scripts.has("typecheck")) ? 1 : 0
|
|
3586
|
+
},
|
|
3587
|
+
{ label: "CI workflow", count: workflows.length > 0 ? 1 : 0 },
|
|
3588
|
+
{ label: "deployment configuration", count: deployConfigs.length > 0 ? 1 : 0 },
|
|
3589
|
+
{ label: "environment template", count: envTemplate ? 1 : 0 },
|
|
3590
|
+
{ label: "health or readiness signal", count: healthChecks.length > 0 ? 1 : 0 },
|
|
3591
|
+
{ label: "error boundary", count: errorBoundaries.length > 0 ? 1 : 0 }
|
|
3592
|
+
]
|
|
3593
|
+
}
|
|
3185
3594
|
),
|
|
3186
3595
|
metric(
|
|
3187
3596
|
"prod_workflow_depth",
|
|
@@ -3216,7 +3625,7 @@ function collectA3ProdReadinessEvidence(repoPath, repoFiles, useV27Detectors, us
|
|
|
3216
3625
|
]
|
|
3217
3626
|
};
|
|
3218
3627
|
}
|
|
3219
|
-
function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors, useV47Detectors) {
|
|
3628
|
+
function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors, useV47Detectors, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
3220
3629
|
const evidence = [];
|
|
3221
3630
|
const findings = [];
|
|
3222
3631
|
const manifests = repoFiles.filter(isDependencyManifest);
|
|
@@ -3269,11 +3678,26 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors,
|
|
|
3269
3678
|
const deployableServiceFiles = useV47Detectors ? findDeployableServicePremiseFiles(repoPath, repoFiles, true) : [];
|
|
3270
3679
|
const isAppOrService = (useV47Detectors ? deployableServiceFiles.length > 0 : isDeployableService(repoPath, repoFiles, useV27Detectors)) || packagedApplicationFiles.length > 0;
|
|
3271
3680
|
if (isAppOrService && !hasLockfile && manifest) {
|
|
3272
|
-
findings.push(
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3681
|
+
findings.push(
|
|
3682
|
+
inventoryAbsenceFinding({
|
|
3683
|
+
severity: "critical",
|
|
3684
|
+
summaries: {
|
|
3685
|
+
legacy: "Dependency manifest is present without a detected lockfile \u2014 non-reproducible installs.",
|
|
3686
|
+
currentTracked: "Dependency manifest is present, but the tracked scan-eligible inventory matched no recognized lockfile.",
|
|
3687
|
+
currentDirectoryWalk: "Dependency manifest is present, but the bounded directory walk matched no recognized lockfile."
|
|
3688
|
+
},
|
|
3689
|
+
evidence: evidenceForRelative(
|
|
3690
|
+
repoPath,
|
|
3691
|
+
manifest,
|
|
3692
|
+
"dependency_report",
|
|
3693
|
+
"Dependency manifest"
|
|
3694
|
+
),
|
|
3695
|
+
context: inventoryAbsenceContext,
|
|
3696
|
+
inventoryCount: repoFiles.length + (mono?.sharedFiles.length ?? 0),
|
|
3697
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a4Lockfile,
|
|
3698
|
+
matchCount: 0
|
|
3699
|
+
})
|
|
3700
|
+
);
|
|
3277
3701
|
}
|
|
3278
3702
|
if (manifest && hasSuspiciousDependencies(repoPath, manifest, useV47Detectors)) {
|
|
3279
3703
|
findings.push({
|
|
@@ -3294,7 +3718,14 @@ function collectA4DependencyEvidence(repoPath, repoFiles, mono, useV27Detectors,
|
|
|
3294
3718
|
2,
|
|
3295
3719
|
0.25,
|
|
3296
3720
|
"ratio",
|
|
3297
|
-
"Credits automated dependency updates and package-manager audit hooks."
|
|
3721
|
+
"Credits automated dependency updates and package-manager audit hooks.",
|
|
3722
|
+
void 0,
|
|
3723
|
+
{
|
|
3724
|
+
components: [
|
|
3725
|
+
{ label: "automated dependency-update configuration", count: hasUpdateConfig ? 1 : 0 },
|
|
3726
|
+
{ label: "package-manager audit hook", count: auditConfig ? 1 : 0 }
|
|
3727
|
+
]
|
|
3728
|
+
}
|
|
3298
3729
|
);
|
|
3299
3730
|
const appMetrics = [
|
|
3300
3731
|
metric(
|
|
@@ -3374,7 +3805,7 @@ function hasDeclaredVersionConstraint(dependency) {
|
|
|
3374
3805
|
if (/[~^<>=!]/.test(trimmed)) return true;
|
|
3375
3806
|
return /^v?\d/.test(trimmed);
|
|
3376
3807
|
}
|
|
3377
|
-
function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors) {
|
|
3808
|
+
function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors, inventoryAbsenceContext = LEGACY_INVENTORY_ABSENCE_CONTEXT) {
|
|
3378
3809
|
const reconciliationArtifacts = useV27Detectors ? findClaimRealityReconciliationArtifacts(repoPath, repoFiles) : [];
|
|
3379
3810
|
const claimSourceFiles = useV27Detectors ? findClaimSourceFiles(repoFiles) : repoFiles.filter(
|
|
3380
3811
|
(file) => /^(?:README|readme)\.md$/.test(file) || /^docs\/[^/]+\.(?:md|mdx)$/.test(file)
|
|
@@ -3439,15 +3870,23 @@ function collectA5ClaimRealityEvidence(repoPath, repoFiles, useV27Detectors) {
|
|
|
3439
3870
|
criterionId: "A5",
|
|
3440
3871
|
positiveEvidence: evidence,
|
|
3441
3872
|
findings: reconciliationArtifacts.length > 0 ? [] : [
|
|
3442
|
-
{
|
|
3873
|
+
inventoryAbsenceFinding({
|
|
3443
3874
|
// Proxy-only A5: cap at warning (never critical) — a static proxy is lower-confidence
|
|
3444
3875
|
// than a curated registry or reconciliation artifact. Downgraded to info when the repo
|
|
3445
3876
|
// documents honest negative space: that scoping IS a form of claim-reality discipline,
|
|
3446
3877
|
// not an additional gap on top of the missing dedicated artifact.
|
|
3447
3878
|
severity: negativeSpaceDoc ? "info" : "warning",
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3879
|
+
summaries: {
|
|
3880
|
+
legacy: negativeSpaceDoc ? "Claim source and implementation files are present; no dedicated claim-reality report artifact was supplied, but the repo explicitly documents what it does NOT cover/protect against \u2014 honest scoping, not overclaiming." : "Claim source and implementation files are present, but no dedicated claim-reality report artifact was supplied.",
|
|
3881
|
+
currentTracked: negativeSpaceDoc ? "Claim-source and implementation-file signals are present, while the tracked scan-eligible inventory matched no dedicated claim-reality report artifact; the repo separately documents what it does NOT cover or protect against." : "Claim-source and implementation-file signals are present, while the tracked scan-eligible inventory matched no dedicated claim-reality report artifact.",
|
|
3882
|
+
currentDirectoryWalk: negativeSpaceDoc ? "Claim-source and implementation-file signals are present, while the bounded directory walk matched no dedicated claim-reality report artifact; the repo separately documents what it does NOT cover or protect against." : "Claim-source and implementation-file signals are present, while the bounded directory walk matched no dedicated claim-reality report artifact."
|
|
3883
|
+
},
|
|
3884
|
+
evidence: firstEvidence,
|
|
3885
|
+
context: inventoryAbsenceContext,
|
|
3886
|
+
inventoryCount: repoFiles.length,
|
|
3887
|
+
patternSet: INVENTORY_SCAN_PATTERN_SETS.a5ClaimRealityArtifact,
|
|
3888
|
+
matchCount: 0
|
|
3889
|
+
})
|
|
3451
3890
|
],
|
|
3452
3891
|
// Recalibrated (goal_cejel_launch_hardening_combined_2026-07-06, Phase 3 H1):
|
|
3453
3892
|
// reconciliation_artifact_depth is usually 0 on the generic-proxy path, so its prior 0.3
|
|
@@ -3528,7 +3967,14 @@ function collectB2PrTraceEvidence(repoPath, repoFiles) {
|
|
|
3528
3967
|
0.8,
|
|
3529
3968
|
"signals",
|
|
3530
3969
|
"Measures CI, PR template, and review-gate evidence for pull-request traceability.",
|
|
3531
|
-
"saturating_count"
|
|
3970
|
+
"saturating_count",
|
|
3971
|
+
{
|
|
3972
|
+
components: [
|
|
3973
|
+
{ label: "CI workflow", count: workflows.length },
|
|
3974
|
+
{ label: "pull-request template", count: prTemplate ? 1 : 0 },
|
|
3975
|
+
{ label: "review-gate record", count: branchProtectionDoc ? 1 : 0 }
|
|
3976
|
+
]
|
|
3977
|
+
}
|
|
3532
3978
|
),
|
|
3533
3979
|
// Weight lowered from 0.35 (Phase 3 H1): squash-merge and local-history repos with
|
|
3534
3980
|
// no "Merge pull request"/"#123" commit-subject convention legitimately score 0/N
|
|
@@ -3591,8 +4037,24 @@ function collectB3CiDisciplineEvidence(repoPath, repoFiles, mono) {
|
|
|
3591
4037
|
4,
|
|
3592
4038
|
0.45,
|
|
3593
4039
|
"signals",
|
|
3594
|
-
"Counts
|
|
3595
|
-
"saturating_count"
|
|
4040
|
+
"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.",
|
|
4041
|
+
"saturating_count",
|
|
4042
|
+
{
|
|
4043
|
+
components: [
|
|
4044
|
+
...["test", "lint", "typecheck", "build"].map((name) => ({
|
|
4045
|
+
label: `package script: ${name === "typecheck" ? "type-check" : name}`,
|
|
4046
|
+
count: scripts.has(name) ? 1 : 0
|
|
4047
|
+
})),
|
|
4048
|
+
...CI_COMMAND_CATEGORIES.map((pattern, index) => ({
|
|
4049
|
+
label: `repository CI command: ${["test", "lint", "type-check", "build"][index]}`,
|
|
4050
|
+
count: workflows.some((file) => fileContains(repoPath, file, pattern)) ? 1 : 0
|
|
4051
|
+
})),
|
|
4052
|
+
...mono ? CI_COMMAND_CATEGORIES.map((pattern, index) => ({
|
|
4053
|
+
label: `monorepo-root CI command: ${["test", "lint", "type-check", "build"][index]}`,
|
|
4054
|
+
count: rootWorkflows.some((file) => fileContains(mono.root, file, pattern)) ? 1 : 0
|
|
4055
|
+
})) : []
|
|
4056
|
+
]
|
|
4057
|
+
}
|
|
3596
4058
|
),
|
|
3597
4059
|
metric(
|
|
3598
4060
|
"default_branch_ci_depth",
|
|
@@ -3772,7 +4234,7 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
|
|
|
3772
4234
|
const isV21ExecutedShape = v21ExecutedEscalationFileSet.has(file);
|
|
3773
4235
|
findings.push({
|
|
3774
4236
|
severity: "critical",
|
|
3775
|
-
summary: isV21ExecutedShape ? "
|
|
4237
|
+
summary: isV21ExecutedShape ? "This file contains an authored administrative SQL statement \u2014 role-membership grant, SUPERUSER escalation, or schema-wide table privilege grant \u2014 or executes one through a direct database-driver call, and does not itself contain text matching the human-gate marker pattern." : "This file executes a role-membership GRANT or SUPERUSER escalation and does not itself contain text matching the human-gate marker pattern.",
|
|
3776
4238
|
evidence: evidenceForRelative(
|
|
3777
4239
|
repoPath,
|
|
3778
4240
|
file,
|
|
@@ -3795,7 +4257,11 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
|
|
|
3795
4257
|
1,
|
|
3796
4258
|
0.15,
|
|
3797
4259
|
"present",
|
|
3798
|
-
"Credits a governance/safety toggle that fails closed before any lower-priority config can override it."
|
|
4260
|
+
"Credits a governance/safety toggle that fails closed before any lower-priority config can override it.",
|
|
4261
|
+
void 0,
|
|
4262
|
+
{
|
|
4263
|
+
condition: "shown because a fail-closed kill switch was detected; this positive-only metric is omitted when absent"
|
|
4264
|
+
}
|
|
3799
4265
|
)
|
|
3800
4266
|
] : [];
|
|
3801
4267
|
const hasPrivilegedOpsSurface = humanGateDoc != null || gatedPrivilegeCheckFile != null || executableFiles.length > 0 || v21ExecutedEscalationFiles.length > 0;
|
|
@@ -3812,7 +4278,11 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
|
|
|
3812
4278
|
1,
|
|
3813
4279
|
0.4,
|
|
3814
4280
|
"present",
|
|
3815
|
-
"Credits explicit documentation that privileged/credentialed operations are human-executed, never agent-run."
|
|
4281
|
+
"Credits explicit documentation that privileged/credentialed operations are human-executed, never agent-run.",
|
|
4282
|
+
void 0,
|
|
4283
|
+
{
|
|
4284
|
+
condition: "scored because a privileged-operation-shaped surface was detected"
|
|
4285
|
+
}
|
|
3816
4286
|
),
|
|
3817
4287
|
metric(
|
|
3818
4288
|
"fail_closed_privilege_check",
|
|
@@ -3821,7 +4291,11 @@ function collectB6PrivilegedOpsGatingEvidence(repoPath, repoFiles, useV39Detecto
|
|
|
3821
4291
|
1,
|
|
3822
4292
|
0.3,
|
|
3823
4293
|
"present",
|
|
3824
|
-
"Credits code that checks role membership and fails closed before elevating privilege, instead of attempting elevation blind."
|
|
4294
|
+
"Credits code that checks role membership and fails closed before elevating privilege, instead of attempting elevation blind.",
|
|
4295
|
+
void 0,
|
|
4296
|
+
{
|
|
4297
|
+
condition: "scored because a privileged-operation-shaped surface was detected"
|
|
4298
|
+
}
|
|
3825
4299
|
)
|
|
3826
4300
|
] : [],
|
|
3827
4301
|
metric(
|
|
@@ -4087,10 +4561,20 @@ function parseGitTrackedFiles(repoPath, output, includeHardExcluded) {
|
|
|
4087
4561
|
continue;
|
|
4088
4562
|
}
|
|
4089
4563
|
try {
|
|
4090
|
-
|
|
4564
|
+
const stat = lstatSync(fullPath);
|
|
4565
|
+
if (!stat.isFile()) {
|
|
4091
4566
|
recordContentSkip(fullPath, "non_regular_file", true);
|
|
4092
4567
|
continue;
|
|
4093
4568
|
}
|
|
4569
|
+
if (stat.size > MAX_REPOSITORY_CONTENT_BYTES) {
|
|
4570
|
+
const affectedCriteria = affectedCriteriaForUnavailablePath(file);
|
|
4571
|
+
if (affectedCriteria.length > 0) {
|
|
4572
|
+
recordContentSkipForCriteria(fullPath, "too_large", affectedCriteria, true);
|
|
4573
|
+
} else {
|
|
4574
|
+
recordContentSkip(fullPath, "too_large", true);
|
|
4575
|
+
}
|
|
4576
|
+
continue;
|
|
4577
|
+
}
|
|
4094
4578
|
} catch (error) {
|
|
4095
4579
|
recordFilesystemSkip(fullPath, error, false, true);
|
|
4096
4580
|
continue;
|
|
@@ -4102,6 +4586,39 @@ function parseGitTrackedFiles(repoPath, output, includeHardExcluded) {
|
|
|
4102
4586
|
}
|
|
4103
4587
|
return files.sort();
|
|
4104
4588
|
}
|
|
4589
|
+
function affectedCriteriaForUnavailablePath(file) {
|
|
4590
|
+
const affected = /* @__PURE__ */ new Set();
|
|
4591
|
+
if (isDependencyManifest(file) || isLockfile(file)) affected.add("A4");
|
|
4592
|
+
if (isCiWorkflow(file)) {
|
|
4593
|
+
affected.add("A1");
|
|
4594
|
+
affected.add("A3");
|
|
4595
|
+
affected.add("B2");
|
|
4596
|
+
affected.add("B3");
|
|
4597
|
+
}
|
|
4598
|
+
if (isDeployConfig(file)) {
|
|
4599
|
+
affected.add("A3");
|
|
4600
|
+
affected.add("B2");
|
|
4601
|
+
}
|
|
4602
|
+
if (isTestFile(file)) {
|
|
4603
|
+
affected.add("A1");
|
|
4604
|
+
affected.add("A2");
|
|
4605
|
+
}
|
|
4606
|
+
if (isImplementationFile(file)) {
|
|
4607
|
+
affected.add("A1");
|
|
4608
|
+
affected.add("A2");
|
|
4609
|
+
affected.add("A3");
|
|
4610
|
+
affected.add("B1");
|
|
4611
|
+
affected.add("B6");
|
|
4612
|
+
}
|
|
4613
|
+
if (/\.(?:md|mdx|rst|txt)$/i.test(file)) {
|
|
4614
|
+
affected.add("A5");
|
|
4615
|
+
affected.add("B4");
|
|
4616
|
+
affected.add("B5");
|
|
4617
|
+
affected.add("B6");
|
|
4618
|
+
}
|
|
4619
|
+
if (isAuditFile(file)) affected.add("B5");
|
|
4620
|
+
return [...affected];
|
|
4621
|
+
}
|
|
4105
4622
|
function directoryInventory(repoPath) {
|
|
4106
4623
|
const files = [];
|
|
4107
4624
|
const rootDevice = statSync(repoPath).dev;
|
|
@@ -4114,6 +4631,7 @@ function collectRepoFileInventory(repoPath) {
|
|
|
4114
4631
|
return {
|
|
4115
4632
|
repoFiles: parseGitTrackedFiles(repoPath, tracked.stdout, false),
|
|
4116
4633
|
inventoryFiles: parseGitTrackedFiles(repoPath, tracked.stdout, true),
|
|
4634
|
+
repoFileInventoryScope: "tracked-scan-eligible",
|
|
4117
4635
|
scanLimitations: []
|
|
4118
4636
|
};
|
|
4119
4637
|
}
|
|
@@ -4221,8 +4739,13 @@ function visitRepoDir(repoPath, dirPath, files, rootDevice) {
|
|
|
4221
4739
|
continue;
|
|
4222
4740
|
}
|
|
4223
4741
|
const repoRelativePath = relative(repoPath, fullPath);
|
|
4224
|
-
if (size >
|
|
4225
|
-
|
|
4742
|
+
if (size > MAX_REPOSITORY_CONTENT_BYTES) {
|
|
4743
|
+
const affectedCriteria = affectedCriteriaForUnavailablePath(repoRelativePath);
|
|
4744
|
+
if (affectedCriteria.length > 0) {
|
|
4745
|
+
recordContentSkipForCriteria(fullPath, "too_large", affectedCriteria);
|
|
4746
|
+
} else {
|
|
4747
|
+
recordContentSkip(fullPath, "too_large");
|
|
4748
|
+
}
|
|
4226
4749
|
continue;
|
|
4227
4750
|
}
|
|
4228
4751
|
if (!isPotentialContentPath(repoRelativePath)) {
|
|
@@ -6152,16 +6675,29 @@ function evidenceForRelativeAtLine(repoPath, path, kind, label, line) {
|
|
|
6152
6675
|
...contents ? { contentHash: createHash2("sha256").update(contents).digest("hex") } : {}
|
|
6153
6676
|
};
|
|
6154
6677
|
}
|
|
6155
|
-
function metric(name, label, value, max, weight, unit, description, kind) {
|
|
6678
|
+
function metric(name, label, value, max, weight, unit, description, kind, presentation) {
|
|
6679
|
+
const normalizedValue = Math.max(0, value);
|
|
6680
|
+
if (presentation?.components) {
|
|
6681
|
+
const explainedValue = presentation.components.reduce(
|
|
6682
|
+
(sum, component) => sum + component.count,
|
|
6683
|
+
0
|
|
6684
|
+
);
|
|
6685
|
+
if (explainedValue !== normalizedValue) {
|
|
6686
|
+
throw new Error(
|
|
6687
|
+
`Cejel invariant: presentation components for ${name} total ${explainedValue}, expected ${normalizedValue}`
|
|
6688
|
+
);
|
|
6689
|
+
}
|
|
6690
|
+
}
|
|
6156
6691
|
return {
|
|
6157
6692
|
name,
|
|
6158
6693
|
label,
|
|
6159
|
-
value:
|
|
6694
|
+
value: normalizedValue,
|
|
6160
6695
|
max,
|
|
6161
6696
|
...kind ? { kind } : {},
|
|
6162
6697
|
weight,
|
|
6163
6698
|
unit,
|
|
6164
|
-
description
|
|
6699
|
+
description,
|
|
6700
|
+
...presentation ? { presentation } : {}
|
|
6165
6701
|
};
|
|
6166
6702
|
}
|
|
6167
6703
|
function firstMeaningfulLine(contents) {
|
|
@@ -6201,12 +6737,11 @@ function hasEnvPathInGitHistory(repoPath, scanLimitations) {
|
|
|
6201
6737
|
|
|
6202
6738
|
// src/witan/public-scan.ts
|
|
6203
6739
|
init_esm_shims();
|
|
6204
|
-
import { resolve as resolve2 } from "path";
|
|
6205
6740
|
|
|
6206
6741
|
// src/witan/ingest.ts
|
|
6207
6742
|
init_esm_shims();
|
|
6208
|
-
import { readFileSync as readFileSync4,
|
|
6209
|
-
import { join as join2 } from "path";
|
|
6743
|
+
import { lstatSync as lstatSync3, opendirSync, readFileSync as readFileSync4, realpathSync as realpathSync3 } from "fs";
|
|
6744
|
+
import { join as join2, resolve as resolve3 } from "path";
|
|
6210
6745
|
|
|
6211
6746
|
// src/witan/finding-limits.ts
|
|
6212
6747
|
init_esm_shims();
|
|
@@ -6230,49 +6765,118 @@ function clampFinding(finding) {
|
|
|
6230
6765
|
// src/witan/generic-adapter.ts
|
|
6231
6766
|
init_esm_shims();
|
|
6232
6767
|
import { readFileSync } from "fs";
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
"
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
"
|
|
6245
|
-
|
|
6246
|
-
|
|
6768
|
+
|
|
6769
|
+
// src/witan/ingest-files.ts
|
|
6770
|
+
init_esm_shims();
|
|
6771
|
+
import { lstatSync as lstatSync2, realpathSync as realpathSync2 } from "fs";
|
|
6772
|
+
import { dirname as dirname2, isAbsolute, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
|
|
6773
|
+
var MAX_INGEST_DOCUMENT_BYTES = 8 * 1024 * 1024;
|
|
6774
|
+
function isContainedPath(candidate, directory) {
|
|
6775
|
+
const relativePath = relative2(directory, candidate);
|
|
6776
|
+
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep2}`) && !isAbsolute(relativePath);
|
|
6777
|
+
}
|
|
6778
|
+
function isMissingIngestPath(error) {
|
|
6779
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
6780
|
+
}
|
|
6781
|
+
function resolveIngestFilePath(filePath, inputDirectory = dirname2(filePath)) {
|
|
6782
|
+
const lexicalDirectory = resolve2(inputDirectory);
|
|
6783
|
+
const lexicalCandidate = resolve2(filePath);
|
|
6784
|
+
const directoryStat = lstatSync2(lexicalDirectory);
|
|
6785
|
+
if (directoryStat.isSymbolicLink()) {
|
|
6786
|
+
throw new Error(`Cejel: refusing symlinked ingest directory: ${inputDirectory}`);
|
|
6787
|
+
}
|
|
6788
|
+
if (!directoryStat.isDirectory()) {
|
|
6789
|
+
throw new Error(`Cejel: refusing non-directory ingest boundary: ${inputDirectory}`);
|
|
6790
|
+
}
|
|
6791
|
+
const candidateStat = lstatSync2(lexicalCandidate);
|
|
6792
|
+
if (candidateStat.isSymbolicLink()) {
|
|
6793
|
+
throw new Error(`Cejel: refusing symlinked ingest file: ${filePath}`);
|
|
6794
|
+
}
|
|
6795
|
+
if (!candidateStat.isFile()) {
|
|
6796
|
+
throw new Error(`Cejel: refusing non-regular ingest file: ${filePath}`);
|
|
6797
|
+
}
|
|
6798
|
+
if (candidateStat.size > MAX_INGEST_DOCUMENT_BYTES) {
|
|
6799
|
+
throw new Error(
|
|
6800
|
+
`Cejel: ingest file exceeds the ${MAX_INGEST_DOCUMENT_BYTES.toLocaleString("en-US")} byte budget: ${filePath}`
|
|
6801
|
+
);
|
|
6802
|
+
}
|
|
6803
|
+
const resolvedDirectory = realpathSync2(lexicalDirectory);
|
|
6804
|
+
const resolvedCandidate = realpathSync2(lexicalCandidate);
|
|
6805
|
+
if (!isContainedPath(resolvedCandidate, resolvedDirectory)) {
|
|
6806
|
+
throw new Error(`Cejel: refusing ingest file outside its resolved input directory: ${filePath}`);
|
|
6807
|
+
}
|
|
6808
|
+
return resolvedCandidate;
|
|
6809
|
+
}
|
|
6810
|
+
function isResolvedIngestPathContained(candidate, directory) {
|
|
6811
|
+
return isContainedPath(candidate, directory);
|
|
6812
|
+
}
|
|
6813
|
+
|
|
6814
|
+
// src/witan/generic-adapter.ts
|
|
6815
|
+
var CEJEL_GENERIC_INGEST_CONTRACT_VERSION = "1.0";
|
|
6816
|
+
var CEJEL_GENERIC_INGEST_CONTRACT_MAJOR = 1;
|
|
6817
|
+
var GenericFindingV1Schema = external_exports.object({
|
|
6818
|
+
ruleId: external_exports.string().min(1),
|
|
6819
|
+
severity: external_exports.enum(["critical", "warning", "info"]),
|
|
6820
|
+
message: external_exports.string().min(1),
|
|
6821
|
+
location: external_exports.string().min(1).optional()
|
|
6822
|
+
}).passthrough();
|
|
6823
|
+
var GenericSignalV1Schema = external_exports.object({
|
|
6824
|
+
dimension: external_exports.enum(["A1", "A2", "A3", "A4", "A5", "B1", "B2", "B3", "B4", "B5", "B6"]),
|
|
6825
|
+
weight: external_exports.number().min(0).max(1).default(0.5),
|
|
6826
|
+
findings: external_exports.array(GenericFindingV1Schema).min(1)
|
|
6827
|
+
}).passthrough();
|
|
6828
|
+
var GenericSignalDocumentV1Schema = external_exports.object({
|
|
6829
|
+
version: external_exports.string().regex(/^1\.\d+$/),
|
|
6830
|
+
tool: external_exports.string().trim().min(1),
|
|
6831
|
+
signals: external_exports.array(GenericSignalV1Schema)
|
|
6832
|
+
}).passthrough();
|
|
6247
6833
|
function isGenericSignalDocument(raw) {
|
|
6248
6834
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return false;
|
|
6249
6835
|
const doc = raw;
|
|
6250
6836
|
return typeof doc.tool === "string" && doc.tool.length > 0 && Array.isArray(doc.signals);
|
|
6251
6837
|
}
|
|
6838
|
+
function assertSupportedContractVersion(version) {
|
|
6839
|
+
if (typeof version !== "string") {
|
|
6840
|
+
throw new Error(
|
|
6841
|
+
`Cejel: generic ingest contract version is required; expected "version": "${CEJEL_GENERIC_INGEST_CONTRACT_VERSION}". Refusing to guess an unversioned contract.`
|
|
6842
|
+
);
|
|
6843
|
+
}
|
|
6844
|
+
const match = /^(\d+)\.(\d+)$/.exec(version);
|
|
6845
|
+
if (!match) {
|
|
6846
|
+
throw new Error(
|
|
6847
|
+
`Cejel: invalid generic ingest contract version "${version}"; expected <major>.<minor> (current ${CEJEL_GENERIC_INGEST_CONTRACT_VERSION}).`
|
|
6848
|
+
);
|
|
6849
|
+
}
|
|
6850
|
+
const major = Number(match[1]);
|
|
6851
|
+
if (major !== CEJEL_GENERIC_INGEST_CONTRACT_MAJOR) {
|
|
6852
|
+
throw new Error(
|
|
6853
|
+
`Cejel: unsupported generic ingest contract major version ${major} (declared "${version}"); this Cejel build supports major ${CEJEL_GENERIC_INGEST_CONTRACT_MAJOR}. Refusing to guess.`
|
|
6854
|
+
);
|
|
6855
|
+
}
|
|
6856
|
+
}
|
|
6857
|
+
function formatValidationIssues(error) {
|
|
6858
|
+
return error.issues.map((issue) => `${issue.path.length > 0 ? issue.path.join(".") : "<document>"}: ${issue.message}`).join("; ");
|
|
6859
|
+
}
|
|
6252
6860
|
function parseGenericJson(raw) {
|
|
6253
6861
|
if (!isGenericSignalDocument(raw)) return [];
|
|
6254
|
-
|
|
6255
|
-
const
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
const findings = [];
|
|
6261
|
-
for (const finding of signal.findings ?? []) {
|
|
6262
|
-
const severity = finding.severity;
|
|
6263
|
-
if (!severity || !VALID_SEVERITIES.has(severity)) continue;
|
|
6264
|
-
const ruleId = finding.ruleId ?? "unknown";
|
|
6265
|
-
findings.push({
|
|
6266
|
-
ruleId,
|
|
6267
|
-
severity,
|
|
6268
|
-
message: finding.message ?? ruleId,
|
|
6269
|
-
...finding.location ? { location: finding.location } : {}
|
|
6270
|
-
});
|
|
6271
|
-
}
|
|
6272
|
-
if (findings.length === 0) continue;
|
|
6273
|
-
signals.push({ source, dimension, weight, findings });
|
|
6862
|
+
assertSupportedContractVersion(raw.version);
|
|
6863
|
+
const parsed = GenericSignalDocumentV1Schema.safeParse(raw);
|
|
6864
|
+
if (!parsed.success) {
|
|
6865
|
+
throw new Error(
|
|
6866
|
+
`Cejel: invalid generic ingest contract v${raw.version}: ${formatValidationIssues(parsed.error)}`
|
|
6867
|
+
);
|
|
6274
6868
|
}
|
|
6275
|
-
return signals
|
|
6869
|
+
return parsed.data.signals.map((signal) => ({
|
|
6870
|
+
source: parsed.data.tool,
|
|
6871
|
+
dimension: signal.dimension,
|
|
6872
|
+
weight: signal.weight,
|
|
6873
|
+
findings: signal.findings.map((finding) => ({
|
|
6874
|
+
ruleId: finding.ruleId,
|
|
6875
|
+
severity: finding.severity,
|
|
6876
|
+
message: finding.message,
|
|
6877
|
+
...finding.location ? { location: finding.location } : {}
|
|
6878
|
+
}))
|
|
6879
|
+
}));
|
|
6276
6880
|
}
|
|
6277
6881
|
|
|
6278
6882
|
// src/witan/sarif-adapter.ts
|
|
@@ -6534,30 +7138,35 @@ function rawToolName(raw) {
|
|
|
6534
7138
|
}
|
|
6535
7139
|
var MAX_INGEST_SOURCE_LENGTH = 120;
|
|
6536
7140
|
function sanitizeIngestSource(source) {
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
return `${codePoints.slice(0, MAX_INGEST_SOURCE_LENGTH - 3).join("")}...`;
|
|
7141
|
+
return sanitizePresentationLine(source, {
|
|
7142
|
+
fallback: "unknown",
|
|
7143
|
+
maxLength: MAX_INGEST_SOURCE_LENGTH
|
|
7144
|
+
});
|
|
6542
7145
|
}
|
|
6543
7146
|
function parseIngestFile(filePath, options = {}) {
|
|
7147
|
+
const resolvedFilePath = resolveIngestFilePath(filePath);
|
|
6544
7148
|
let raw;
|
|
6545
7149
|
try {
|
|
6546
|
-
raw = JSON.parse(stripBom(readFileSync4(
|
|
7150
|
+
raw = JSON.parse(stripBom(readFileSync4(resolvedFilePath, "utf8")));
|
|
6547
7151
|
} catch (error) {
|
|
6548
7152
|
const message = error instanceof Error ? error.message : String(error);
|
|
6549
7153
|
throw new Error(`Cejel: could not parse ingest file as JSON: ${filePath} (${message})`);
|
|
6550
7154
|
}
|
|
7155
|
+
const rawCount = countRawEntries(raw);
|
|
7156
|
+
if (options.maxFindingCandidates !== void 0 && rawCount > options.maxFindingCandidates) {
|
|
7157
|
+
throw new Error(
|
|
7158
|
+
`Cejel: ${rawCount.toLocaleString("en-US")} ingest finding candidates exceed the ${options.maxFindingCandidates.toLocaleString("en-US")} remaining retained ingest finding budget; refusing before adapter materialization.`
|
|
7159
|
+
);
|
|
7160
|
+
}
|
|
6551
7161
|
let signals;
|
|
6552
7162
|
if (looksLikeSarif(raw)) signals = parseSarifJson(raw, options.extraSarifDimensionRules);
|
|
6553
7163
|
else if (looksLikeScorecard(raw)) signals = parseScorecardJson(raw);
|
|
6554
7164
|
else if (isGenericSignalDocument(raw)) signals = parseGenericJson(raw);
|
|
6555
7165
|
if (!signals) {
|
|
6556
7166
|
throw new Error(
|
|
6557
|
-
`Cejel: unrecognized ingest file format: ${filePath} \u2014 expected SARIF (a "runs" array), OpenSSF Scorecard JSON (a "checks" array), or the generic Cejel external-signal shape (a "tool" string + "signals" array). See the cejel README "Aggregate your scanners".`
|
|
7167
|
+
`Cejel: unrecognized ingest file format: ${filePath} \u2014 expected SARIF (a "runs" array), OpenSSF Scorecard JSON (a "checks" array), or the versioned generic Cejel external-signal shape (a "version" string + "tool" string + "signals" array). See the cejel README "Aggregate your scanners".`
|
|
6558
7168
|
);
|
|
6559
7169
|
}
|
|
6560
|
-
const rawCount = countRawEntries(raw);
|
|
6561
7170
|
const mappedCount = signals.reduce((sum, signal) => sum + signal.findings.length, 0);
|
|
6562
7171
|
if (rawCount > 0 && mappedCount === 0) {
|
|
6563
7172
|
process.stderr.write(
|
|
@@ -6572,30 +7181,83 @@ function parseIngestFile(filePath, options = {}) {
|
|
|
6572
7181
|
findings: signal.findings.map(clampFinding)
|
|
6573
7182
|
}));
|
|
6574
7183
|
}
|
|
6575
|
-
function expandIngestPattern(pattern) {
|
|
7184
|
+
function expandIngestPattern(pattern, maxMatches = Number.POSITIVE_INFINITY) {
|
|
6576
7185
|
if (!pattern.includes("*")) return [pattern];
|
|
6577
7186
|
const lastSlash = pattern.lastIndexOf("/");
|
|
6578
7187
|
const dir = lastSlash === -1 ? "." : pattern.slice(0, lastSlash);
|
|
6579
7188
|
const filePattern = lastSlash === -1 ? pattern : pattern.slice(lastSlash + 1);
|
|
6580
7189
|
const regexSource = filePattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
6581
7190
|
const regex = new RegExp(`^${regexSource}$`);
|
|
6582
|
-
|
|
7191
|
+
const entries = [];
|
|
6583
7192
|
try {
|
|
6584
|
-
|
|
6585
|
-
|
|
7193
|
+
const directoryStat = lstatSync3(resolve3(dir));
|
|
7194
|
+
if (directoryStat.isSymbolicLink()) {
|
|
7195
|
+
throw new Error(`Cejel: refusing symlinked ingest directory: ${dir}`);
|
|
7196
|
+
}
|
|
7197
|
+
if (!directoryStat.isDirectory()) return [];
|
|
7198
|
+
const directory = opendirSync(dir);
|
|
7199
|
+
try {
|
|
7200
|
+
for (; ; ) {
|
|
7201
|
+
const entry = directory.readSync();
|
|
7202
|
+
if (!entry) break;
|
|
7203
|
+
if (!regex.test(entry.name)) continue;
|
|
7204
|
+
entries.push(entry.name);
|
|
7205
|
+
if (entries.length >= maxMatches) break;
|
|
7206
|
+
}
|
|
7207
|
+
} finally {
|
|
7208
|
+
directory.closeSync();
|
|
7209
|
+
}
|
|
7210
|
+
} catch (error) {
|
|
7211
|
+
if (!isMissingIngestPath(error)) throw error;
|
|
6586
7212
|
return [];
|
|
6587
7213
|
}
|
|
6588
|
-
return entries.
|
|
7214
|
+
return entries.sort().map((name) => {
|
|
7215
|
+
const candidate = join2(dir, name);
|
|
7216
|
+
resolveIngestFilePath(candidate, dir);
|
|
7217
|
+
return candidate;
|
|
7218
|
+
});
|
|
6589
7219
|
}
|
|
6590
|
-
function discoverIngestInputs(repoPath) {
|
|
7220
|
+
function discoverIngestInputs(repoPath, maxMatches = Number.POSITIVE_INFINITY) {
|
|
7221
|
+
const resolvedRoot = realpathSync3(repoPath);
|
|
7222
|
+
const cejelDir = join2(repoPath, ".cejel");
|
|
6591
7223
|
const inputsDir = join2(repoPath, ".cejel", "inputs");
|
|
6592
|
-
|
|
7224
|
+
const entries = [];
|
|
6593
7225
|
try {
|
|
6594
|
-
|
|
6595
|
-
|
|
7226
|
+
const cejelStat = lstatSync3(cejelDir);
|
|
7227
|
+
if (cejelStat.isSymbolicLink()) {
|
|
7228
|
+
throw new Error(`Cejel: refusing symlinked ingest path component: ${cejelDir}`);
|
|
7229
|
+
}
|
|
7230
|
+
if (!cejelStat.isDirectory()) {
|
|
7231
|
+
throw new Error(`Cejel: refusing non-directory ingest path component: ${cejelDir}`);
|
|
7232
|
+
}
|
|
7233
|
+
const inputsStat = lstatSync3(inputsDir);
|
|
7234
|
+
if (inputsStat.isSymbolicLink()) {
|
|
7235
|
+
throw new Error(`Cejel: refusing symlinked ingest path component: ${inputsDir}`);
|
|
7236
|
+
}
|
|
7237
|
+
if (!inputsStat.isDirectory()) {
|
|
7238
|
+
throw new Error(`Cejel: refusing non-directory ingest path component: ${inputsDir}`);
|
|
7239
|
+
}
|
|
7240
|
+
const resolvedInputs = realpathSync3(inputsDir);
|
|
7241
|
+
if (!isResolvedIngestPathContained(resolvedInputs, resolvedRoot)) {
|
|
7242
|
+
throw new Error(`Cejel: refusing auto-discovery directory outside the repository root.`);
|
|
7243
|
+
}
|
|
7244
|
+
const directory = opendirSync(inputsDir);
|
|
7245
|
+
try {
|
|
7246
|
+
for (; ; ) {
|
|
7247
|
+
const entry = directory.readSync();
|
|
7248
|
+
if (!entry) break;
|
|
7249
|
+
if (!/\.(sarif|json)$/i.test(entry.name)) continue;
|
|
7250
|
+
entries.push(entry.name);
|
|
7251
|
+
if (entries.length >= maxMatches) break;
|
|
7252
|
+
}
|
|
7253
|
+
} finally {
|
|
7254
|
+
directory.closeSync();
|
|
7255
|
+
}
|
|
7256
|
+
} catch (error) {
|
|
7257
|
+
if (!isMissingIngestPath(error)) throw error;
|
|
6596
7258
|
return [];
|
|
6597
7259
|
}
|
|
6598
|
-
return entries.
|
|
7260
|
+
return entries.sort().map((name) => resolveIngestFilePath(join2(inputsDir, name), inputsDir));
|
|
6599
7261
|
}
|
|
6600
7262
|
|
|
6601
7263
|
// src/witan/scoring.ts
|
|
@@ -7007,7 +7669,11 @@ function scoreMetrics(metrics) {
|
|
|
7007
7669
|
function capScoreForFindings(score, findings, rubricVersion, criterionId) {
|
|
7008
7670
|
if (findings.some((finding) => finding.severity === "critical")) {
|
|
7009
7671
|
const criticalFindings = findings.filter(({ severity }) => severity === "critical");
|
|
7010
|
-
const authenticatedA1Absence = usesV17DetectorClosure2(rubricVersion) && criterionId === "A1" && criticalFindings.length === 1 &&
|
|
7672
|
+
const authenticatedA1Absence = usesV17DetectorClosure2(rubricVersion) && criterionId === "A1" && criticalFindings.length === 1 && [
|
|
7673
|
+
WITAN_AUTHENTICATED_A1_ABSENCE_SUMMARY,
|
|
7674
|
+
WITAN_DIRECTORY_WALK_AUTHENTICATED_A1_ABSENCE_SUMMARY,
|
|
7675
|
+
WITAN_LEGACY_AUTHENTICATED_A1_ABSENCE_SUMMARY
|
|
7676
|
+
].includes(criticalFindings[0]?.summary ?? "");
|
|
7011
7677
|
return authenticatedA1Absence ? Math.min(score, 1.4) : 1.4;
|
|
7012
7678
|
}
|
|
7013
7679
|
if (findings.some((finding) => finding.severity === "warning")) return Math.min(score, 2.4);
|
|
@@ -7279,6 +7945,8 @@ function roundScore(score) {
|
|
|
7279
7945
|
}
|
|
7280
7946
|
|
|
7281
7947
|
// src/witan/public-scan.ts
|
|
7948
|
+
var MAX_INGEST_DOCUMENTS = 128;
|
|
7949
|
+
var MAX_RETAINED_INGEST_FINDINGS = 1e4;
|
|
7282
7950
|
function scoreRepoWithPublicCejel(options) {
|
|
7283
7951
|
if (options.rubricVersion !== void 0) {
|
|
7284
7952
|
assertSelectableRubricVersion(options.rubricVersion);
|
|
@@ -7306,44 +7974,69 @@ function scoreRepoWithPublicCejel(options) {
|
|
|
7306
7974
|
}
|
|
7307
7975
|
function resolvePublicIngestSignals(options) {
|
|
7308
7976
|
const seen = /* @__PURE__ */ new Set();
|
|
7309
|
-
const
|
|
7977
|
+
const documents = [];
|
|
7978
|
+
const addDocument = (file, provenance) => {
|
|
7979
|
+
const resolved = resolveIngestFilePath(file);
|
|
7980
|
+
if (seen.has(resolved)) return;
|
|
7981
|
+
seen.add(resolved);
|
|
7982
|
+
documents.push({ file: resolved, provenance });
|
|
7983
|
+
if (documents.length > MAX_INGEST_DOCUMENTS) {
|
|
7984
|
+
throw new Error(
|
|
7985
|
+
`Cejel: ${documents.length} unique ingest documents exceed the ${MAX_INGEST_DOCUMENTS} ingest document budget; refusing to produce a partial certificate.`
|
|
7986
|
+
);
|
|
7987
|
+
}
|
|
7988
|
+
};
|
|
7310
7989
|
for (const pattern of options.ingestPatterns ?? []) {
|
|
7311
|
-
const matches = expandIngestPattern(pattern);
|
|
7990
|
+
const matches = expandIngestPattern(pattern, MAX_INGEST_DOCUMENTS + 1);
|
|
7312
7991
|
if (matches.length === 0 && options.warnOnEmptyIngestMatch) {
|
|
7313
7992
|
process.stderr.write(`Cejel: --ingest pattern matched no files: ${pattern}
|
|
7314
7993
|
`);
|
|
7315
7994
|
}
|
|
7316
7995
|
for (const match of matches) {
|
|
7317
|
-
|
|
7318
|
-
if (seen.has(resolved)) continue;
|
|
7319
|
-
seen.add(resolved);
|
|
7320
|
-
explicitFiles.push(match);
|
|
7996
|
+
addDocument(match, "operator_supplied");
|
|
7321
7997
|
}
|
|
7322
7998
|
}
|
|
7323
|
-
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7328
|
-
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7999
|
+
if (options.autoDiscoverIngest) {
|
|
8000
|
+
for (const discovered of discoverIngestInputs(
|
|
8001
|
+
options.repoPath,
|
|
8002
|
+
MAX_INGEST_DOCUMENTS + 1
|
|
8003
|
+
)) {
|
|
8004
|
+
addDocument(discovered, "auto_discovered");
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
8007
|
+
const signals = [];
|
|
8008
|
+
let retainedFindings = 0;
|
|
8009
|
+
for (const document of documents) {
|
|
8010
|
+
const parsed = parseIngestFile(document.file, {
|
|
8011
|
+
maxFindingCandidates: MAX_RETAINED_INGEST_FINDINGS - retainedFindings,
|
|
8012
|
+
provenance: document.provenance
|
|
8013
|
+
});
|
|
8014
|
+
const documentFindings = parsed.reduce((count, signal) => count + signal.findings.length, 0);
|
|
8015
|
+
retainedFindings += documentFindings;
|
|
8016
|
+
if (retainedFindings > MAX_RETAINED_INGEST_FINDINGS) {
|
|
8017
|
+
throw new Error(
|
|
8018
|
+
`Cejel: ${retainedFindings.toLocaleString("en-US")} retained findings exceed the ${MAX_RETAINED_INGEST_FINDINGS.toLocaleString("en-US")} retained ingest finding budget; refusing to truncate evidence.`
|
|
8019
|
+
);
|
|
8020
|
+
}
|
|
8021
|
+
signals.push(...parsed);
|
|
7332
8022
|
}
|
|
7333
8023
|
return signals;
|
|
7334
8024
|
}
|
|
7335
8025
|
|
|
7336
8026
|
// src/product-identity.ts
|
|
7337
8027
|
init_esm_shims();
|
|
7338
|
-
import { existsSync, readFileSync as readFileSync5 } from "fs";
|
|
8028
|
+
import { existsSync, lstatSync as lstatSync4, readFileSync as readFileSync5 } from "fs";
|
|
7339
8029
|
import { basename as basename2, join as join3 } from "path";
|
|
7340
8030
|
var SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$/;
|
|
7341
8031
|
var FALLBACK_SLUG = "repo";
|
|
7342
8032
|
var FALLBACK_DISPLAY_NAME = "Repository";
|
|
7343
|
-
function deriveProductIdentity(repoPath) {
|
|
8033
|
+
function deriveProductIdentity(repoPath, productName) {
|
|
7344
8034
|
const packageJsonPath = join3(repoPath, "package.json");
|
|
7345
|
-
const raw = readPackageName(packageJsonPath) ?? basename2(repoPath);
|
|
7346
|
-
const displayName =
|
|
8035
|
+
const raw = productName ?? readPackageName(packageJsonPath) ?? basename2(repoPath);
|
|
8036
|
+
const displayName = sanitizePresentationLine(raw, {
|
|
8037
|
+
fallback: FALLBACK_DISPLAY_NAME,
|
|
8038
|
+
maxLength: 120
|
|
8039
|
+
});
|
|
7347
8040
|
return {
|
|
7348
8041
|
productSlug: slugify(raw),
|
|
7349
8042
|
productDisplayName: displayName
|
|
@@ -7352,6 +8045,8 @@ function deriveProductIdentity(repoPath) {
|
|
|
7352
8045
|
function readPackageName(packageJsonPath) {
|
|
7353
8046
|
if (!existsSync(packageJsonPath)) return void 0;
|
|
7354
8047
|
try {
|
|
8048
|
+
const stat = lstatSync4(packageJsonPath);
|
|
8049
|
+
if (!stat.isFile() || stat.size > MAX_REPOSITORY_CONTENT_BYTES) return void 0;
|
|
7355
8050
|
const parsed = JSON.parse(readFileSync5(packageJsonPath, "utf8"));
|
|
7356
8051
|
if (typeof parsed === "object" && parsed !== null && "name" in parsed) {
|
|
7357
8052
|
const name = parsed.name;
|
|
@@ -7381,7 +8076,7 @@ var METRIC_DERIVED_FINDING_PATTERN2 = /^[A-Z]\d+ metric-derived score is (\d+\.\
|
|
|
7381
8076
|
var REMEDIATION_BY_METRIC2 = {
|
|
7382
8077
|
test_to_source_ratio: "add concrete regression tests for the implementation surface",
|
|
7383
8078
|
coverage_percent: "configure coverage and publish a measured threshold or report",
|
|
7384
|
-
verification_script_ratio: "add
|
|
8079
|
+
verification_script_ratio: "add the missing test, coverage, lint, or type-check command or a test-runner configuration",
|
|
7385
8080
|
non_hollow_test_share: "replace skipped or placeholder tests with live assertions",
|
|
7386
8081
|
env_handling_depth: "document and enforce safe environment-secret handling",
|
|
7387
8082
|
secret_cleanliness: "remove committed secrets and rotate any exposed credentials",
|
|
@@ -7399,7 +8094,7 @@ var REMEDIATION_BY_METRIC2 = {
|
|
|
7399
8094
|
claim_match_rate: "align documented product claims with observable repository evidence",
|
|
7400
8095
|
claim_source_depth: "document concrete, checkable product claims",
|
|
7401
8096
|
reconciliation_artifact_depth: "publish a claim-to-evidence reconciliation record",
|
|
7402
|
-
pr_merge_ratio: "preserve
|
|
8097
|
+
pr_merge_ratio: "preserve recognizable pull-request references in recent commit subjects",
|
|
7403
8098
|
pr_trace_primitives: "add pull-request templates and outcome-trace records",
|
|
7404
8099
|
ci_script_depth: "run the repository verification commands in CI",
|
|
7405
8100
|
default_branch_ci_depth: "run CI on the default branch and pull requests",
|
|
@@ -7509,7 +8204,7 @@ function buildWitanCliSummary(report) {
|
|
|
7509
8204
|
|
|
7510
8205
|
// src/scan.ts
|
|
7511
8206
|
function runCejelScan(options) {
|
|
7512
|
-
const identity = deriveProductIdentity(options.repoPath);
|
|
8207
|
+
const identity = deriveProductIdentity(options.repoPath, options.productName);
|
|
7513
8208
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7514
8209
|
const report = scoreRepoWithPublicCejel({
|
|
7515
8210
|
productSlug: identity.productSlug,
|
|
@@ -7531,17 +8226,21 @@ function runCejelScan(options) {
|
|
|
7531
8226
|
export {
|
|
7532
8227
|
WitanReportSchema,
|
|
7533
8228
|
formatExternalSourceLine,
|
|
8229
|
+
CALLER_CONTEXT_PRODUCT_IDENTITY_NOTICE,
|
|
7534
8230
|
CERTIFICATE_GLOSSARY,
|
|
7535
8231
|
formatCertificateMetricLabel,
|
|
7536
8232
|
formatCertificateMetricValue,
|
|
7537
8233
|
buildRelyingPartySummary,
|
|
8234
|
+
WITAN_RUBRIC_VERSION_V22,
|
|
8235
|
+
assertSelectableRubricVersion,
|
|
8236
|
+
isProspectiveRubricVersion,
|
|
8237
|
+
PROSPECTIVE_RUBRIC_NOTICE,
|
|
7538
8238
|
renderWitanHtmlReport,
|
|
7539
8239
|
renderWitanBadgeEndpoint,
|
|
7540
8240
|
renderWitanBadgeSvg,
|
|
7541
8241
|
serializeWitanReport,
|
|
7542
8242
|
createWitanAttestation,
|
|
7543
8243
|
verifyWitanAttestationBinding,
|
|
7544
|
-
WITAN_RUBRIC_VERSION_V22,
|
|
7545
8244
|
deriveProductIdentity,
|
|
7546
8245
|
runCejelScan
|
|
7547
8246
|
};
|