@complior/engine 0.9.0
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/.well-known/ai-compliance.json +16 -0
- package/COMPLIANCE.md +64 -0
- package/data/data-integrity.test.ts +75 -0
- package/data/eval/eval-mappings.json +33 -0
- package/data/llm/model-pricing.json +15 -0
- package/data/llm/model-routing.json +36 -0
- package/data/onboarding/risk-profile.json +17 -0
- package/data/regulations/eu-ai-act/README.md +245 -0
- package/data/regulations/eu-ai-act/applicability-tree.json +160 -0
- package/data/regulations/eu-ai-act/cross-mapping.json +175 -0
- package/data/regulations/eu-ai-act/localization.json +186 -0
- package/data/regulations/eu-ai-act/obligations.json +3981 -0
- package/data/regulations/eu-ai-act/regulation-meta.json +482 -0
- package/data/regulations/eu-ai-act/scoring.json +342 -0
- package/data/regulations/eu-ai-act/technical-requirements.json +2590 -0
- package/data/regulations/eu-ai-act/timeline.json +160 -0
- package/data/regulations/jurisdictions/at.json +15 -0
- package/data/regulations/jurisdictions/be.json +15 -0
- package/data/regulations/jurisdictions/bg.json +15 -0
- package/data/regulations/jurisdictions/cy.json +15 -0
- package/data/regulations/jurisdictions/cz.json +15 -0
- package/data/regulations/jurisdictions/de.json +15 -0
- package/data/regulations/jurisdictions/dk.json +15 -0
- package/data/regulations/jurisdictions/ee.json +15 -0
- package/data/regulations/jurisdictions/es.json +15 -0
- package/data/regulations/jurisdictions/fi.json +15 -0
- package/data/regulations/jurisdictions/fr.json +15 -0
- package/data/regulations/jurisdictions/gr.json +15 -0
- package/data/regulations/jurisdictions/hr.json +15 -0
- package/data/regulations/jurisdictions/hu.json +15 -0
- package/data/regulations/jurisdictions/ie.json +15 -0
- package/data/regulations/jurisdictions/is.json +15 -0
- package/data/regulations/jurisdictions/it.json +15 -0
- package/data/regulations/jurisdictions/li.json +15 -0
- package/data/regulations/jurisdictions/lt.json +15 -0
- package/data/regulations/jurisdictions/lu.json +15 -0
- package/data/regulations/jurisdictions/lv.json +15 -0
- package/data/regulations/jurisdictions/mt.json +15 -0
- package/data/regulations/jurisdictions/nl.json +15 -0
- package/data/regulations/jurisdictions/no.json +15 -0
- package/data/regulations/jurisdictions/pl.json +15 -0
- package/data/regulations/jurisdictions/pt.json +15 -0
- package/data/regulations/jurisdictions/ro.json +15 -0
- package/data/regulations/jurisdictions/se.json +15 -0
- package/data/regulations/jurisdictions/si.json +15 -0
- package/data/regulations/jurisdictions/sk.json +15 -0
- package/data/scanner/check-id-categories.json +81 -0
- package/data/scanner/confidence-params.json +16 -0
- package/data/scanner/limits.json +4 -0
- package/data/schemas/http-contract-sample.json +79 -0
- package/data/schemas/http-contract.json +144 -0
- package/data/semgrep-rules/bare-call.yaml +37 -0
- package/data/semgrep-rules/injection.yaml +73 -0
- package/data/semgrep-rules/missing-error-handling.yaml +58 -0
- package/data/semgrep-rules/unsafe-deser.yaml +65 -0
- package/data/templates/eu-ai-act/ai-literacy.md +184 -0
- package/data/templates/eu-ai-act/art5-screening.md +131 -0
- package/data/templates/eu-ai-act/data-governance.md +145 -0
- package/data/templates/eu-ai-act/declaration-of-conformity.md +161 -0
- package/data/templates/eu-ai-act/fria.md +127 -0
- package/data/templates/eu-ai-act/gpai-systemic-risk.md +150 -0
- package/data/templates/eu-ai-act/gpai-transparency.md +166 -0
- package/data/templates/eu-ai-act/incident-report.md +188 -0
- package/data/templates/eu-ai-act/instructions-for-use.md +202 -0
- package/data/templates/eu-ai-act/monitoring-policy.md +110 -0
- package/data/templates/eu-ai-act/qms.md +180 -0
- package/data/templates/eu-ai-act/risk-management-system.md +123 -0
- package/data/templates/eu-ai-act/technical-documentation.md +287 -0
- package/data/templates/eu-ai-act/worker-notification.md +143 -0
- package/data/templates/policies/biometrics-ai-policy.md +214 -0
- package/data/templates/policies/critical-infra-ai-policy.md +228 -0
- package/data/templates/policies/education-ai-policy.md +184 -0
- package/data/templates/policies/finance-ai-policy.md +191 -0
- package/data/templates/policies/healthcare-ai-policy.md +197 -0
- package/data/templates/policies/hr-ai-policy.md +178 -0
- package/data/templates/policies/legal-ai-policy.md +189 -0
- package/data/templates/policies/migration-ai-policy.md +239 -0
- package/engine.log +7 -0
- package/package.json +74 -0
- package/src/composition-root.ts +791 -0
- package/src/data/eval/conformity-tests.test.ts +122 -0
- package/src/data/eval/ct-1-transparency.ts +106 -0
- package/src/data/eval/ct-10-gpai.ts +25 -0
- package/src/data/eval/ct-11-industry.ts +42 -0
- package/src/data/eval/ct-2-oversight.ts +41 -0
- package/src/data/eval/ct-3-explanation.ts +14 -0
- package/src/data/eval/ct-4-bias.ts +83 -0
- package/src/data/eval/ct-5-accuracy.ts +41 -0
- package/src/data/eval/ct-6-robustness.ts +81 -0
- package/src/data/eval/ct-7-prohibited.ts +52 -0
- package/src/data/eval/ct-8-logging.ts +68 -0
- package/src/data/eval/ct-9-risk-awareness.ts +33 -0
- package/src/data/eval/deterministic-evaluator.ts +120 -0
- package/src/data/eval/index.ts +55 -0
- package/src/data/eval/judge-prompts.ts +146 -0
- package/src/data/eval/llm-judged-tests.ts +279 -0
- package/src/data/eval/llm-tests.test.ts +83 -0
- package/src/data/eval/remediation/ct-1-transparency.ts +91 -0
- package/src/data/eval/remediation/ct-10-gpai.ts +94 -0
- package/src/data/eval/remediation/ct-11-industry.ts +94 -0
- package/src/data/eval/remediation/ct-2-oversight.ts +71 -0
- package/src/data/eval/remediation/ct-3-explanation.ts +70 -0
- package/src/data/eval/remediation/ct-4-bias.ts +70 -0
- package/src/data/eval/remediation/ct-5-accuracy.ts +70 -0
- package/src/data/eval/remediation/ct-6-robustness.ts +70 -0
- package/src/data/eval/remediation/ct-7-prohibited.ts +94 -0
- package/src/data/eval/remediation/ct-8-logging.ts +94 -0
- package/src/data/eval/remediation/ct-9-risk-awareness.ts +94 -0
- package/src/data/eval/remediation/index.ts +89 -0
- package/src/data/eval/remediation/owasp-art5.ts +15 -0
- package/src/data/eval/remediation/owasp-llm01.ts +72 -0
- package/src/data/eval/remediation/owasp-llm02.ts +72 -0
- package/src/data/eval/remediation/owasp-llm03.ts +15 -0
- package/src/data/eval/remediation/owasp-llm04.ts +15 -0
- package/src/data/eval/remediation/owasp-llm05.ts +15 -0
- package/src/data/eval/remediation/owasp-llm06.ts +15 -0
- package/src/data/eval/remediation/owasp-llm07.ts +15 -0
- package/src/data/eval/remediation/owasp-llm08.ts +15 -0
- package/src/data/eval/remediation/owasp-llm09.ts +15 -0
- package/src/data/eval/remediation/owasp-llm10.ts +15 -0
- package/src/data/eval/remediation/remediation.test.ts +229 -0
- package/src/data/eval/remediation/test-mapping.ts +290 -0
- package/src/data/eval/security-rubrics.ts +381 -0
- package/src/data/finding-explanations.json +453 -0
- package/src/data/industry-patterns.ts +161 -0
- package/src/data/registry-cards.ts +368 -0
- package/src/data/regulation/index.ts +5 -0
- package/src/data/regulation/jurisdiction-data.test.ts +73 -0
- package/src/data/regulation/jurisdiction-data.ts +65 -0
- package/src/data/regulation/regulation-data.ts +19 -0
- package/src/data/regulation/regulation-loader.test.ts +107 -0
- package/src/data/regulation/regulation-loader.ts +56 -0
- package/src/data/scanner-constants.ts +46 -0
- package/src/data/schemas/schemas-core.ts +140 -0
- package/src/data/schemas/schemas-supplementary.ts +211 -0
- package/src/data/schemas/schemas.ts +28 -0
- package/src/data/security/attack-probes.test.ts +62 -0
- package/src/data/security/attack-probes.ts +496 -0
- package/src/data/security/eu-ai-act-security.ts +40 -0
- package/src/data/security/index.ts +19 -0
- package/src/data/security/mitre-atlas.test.ts +43 -0
- package/src/data/security/mitre-atlas.ts +93 -0
- package/src/data/security/nist-ai-rmf.ts +43 -0
- package/src/data/security/owasp-llm-top10.test.ts +60 -0
- package/src/data/security/owasp-llm-top10.ts +138 -0
- package/src/data/template-registry.ts +53 -0
- package/src/data/tool-versions.json +22 -0
- package/src/domain/audit/audit-package.test.ts +152 -0
- package/src/domain/audit/audit-package.ts +166 -0
- package/src/domain/audit/audit-trail.test.ts +121 -0
- package/src/domain/audit/audit-trail.ts +174 -0
- package/src/domain/audit/index.ts +8 -0
- package/src/domain/audit/permissions-matrix.test.ts +136 -0
- package/src/domain/audit/permissions-matrix.ts +121 -0
- package/src/domain/certification/adversarial/bias-tests.ts +95 -0
- package/src/domain/certification/adversarial/evaluators.ts +304 -0
- package/src/domain/certification/adversarial/index.ts +11 -0
- package/src/domain/certification/adversarial/prompt-injection.ts +103 -0
- package/src/domain/certification/adversarial/safety-boundary.ts +132 -0
- package/src/domain/certification/aiuc1-readiness.test.ts +236 -0
- package/src/domain/certification/aiuc1-readiness.ts +298 -0
- package/src/domain/certification/aiuc1-requirements.ts +235 -0
- package/src/domain/certification/index.ts +10 -0
- package/src/domain/certification/redteam-runner.test.ts +97 -0
- package/src/domain/certification/redteam-runner.ts +205 -0
- package/src/domain/certification/test-runner.test.ts +232 -0
- package/src/domain/certification/test-runner.ts +289 -0
- package/src/domain/cost/cost-estimator.test.ts +187 -0
- package/src/domain/cost/cost-estimator.ts +133 -0
- package/src/domain/disclaimer.test.ts +52 -0
- package/src/domain/disclaimer.ts +39 -0
- package/src/domain/documents/ai-enricher.test.ts +120 -0
- package/src/domain/documents/ai-enricher.ts +159 -0
- package/src/domain/documents/document-generator.test.ts +318 -0
- package/src/domain/documents/document-generator.ts +239 -0
- package/src/domain/documents/index.ts +9 -0
- package/src/domain/documents/passport-helpers.ts +25 -0
- package/src/domain/documents/policy-generator.test.ts +252 -0
- package/src/domain/documents/policy-generator.ts +94 -0
- package/src/domain/documents/worker-notification-generator.test.ts +162 -0
- package/src/domain/documents/worker-notification-generator.ts +141 -0
- package/src/domain/eval/adapters/adapter-port.ts +94 -0
- package/src/domain/eval/adapters/adapters.test.ts +303 -0
- package/src/domain/eval/adapters/anthropic-adapter.ts +57 -0
- package/src/domain/eval/adapters/auto-detect.ts +104 -0
- package/src/domain/eval/adapters/create-chat-adapter.ts +106 -0
- package/src/domain/eval/adapters/custom-adapter.ts +74 -0
- package/src/domain/eval/adapters/http-adapter.ts +66 -0
- package/src/domain/eval/adapters/index.ts +7 -0
- package/src/domain/eval/adapters/ollama-adapter.ts +48 -0
- package/src/domain/eval/adapters/openai-adapter.ts +58 -0
- package/src/domain/eval/adapters/with-timeout.ts +25 -0
- package/src/domain/eval/conformity-score.test.ts +161 -0
- package/src/domain/eval/conformity-score.ts +135 -0
- package/src/domain/eval/eval-constants.ts +55 -0
- package/src/domain/eval/eval-evidence.test.ts +85 -0
- package/src/domain/eval/eval-evidence.ts +103 -0
- package/src/domain/eval/eval-fix-generator.test.ts +421 -0
- package/src/domain/eval/eval-fix-generator.ts +205 -0
- package/src/domain/eval/eval-passport.test.ts +82 -0
- package/src/domain/eval/eval-passport.ts +89 -0
- package/src/domain/eval/eval-remediation-report.test.ts +682 -0
- package/src/domain/eval/eval-remediation-report.ts +170 -0
- package/src/domain/eval/eval-report.ts +108 -0
- package/src/domain/eval/eval-runner.test.ts +609 -0
- package/src/domain/eval/eval-runner.ts +593 -0
- package/src/domain/eval/eval-to-findings.test.ts +293 -0
- package/src/domain/eval/eval-to-findings.ts +83 -0
- package/src/domain/eval/index.ts +31 -0
- package/src/domain/eval/llm-judge.test.ts +139 -0
- package/src/domain/eval/llm-judge.ts +168 -0
- package/src/domain/eval/remediation-types.ts +90 -0
- package/src/domain/eval/security-integration.test.ts +196 -0
- package/src/domain/eval/security-integration.ts +136 -0
- package/src/domain/eval/types.test.ts +173 -0
- package/src/domain/eval/types.ts +244 -0
- package/src/domain/eval/verdict-utils.ts +45 -0
- package/src/domain/fixer/create-fixer.ts +101 -0
- package/src/domain/fixer/diff.ts +70 -0
- package/src/domain/fixer/fix-history.ts +23 -0
- package/src/domain/fixer/fixer.test.ts +306 -0
- package/src/domain/fixer/index.ts +9 -0
- package/src/domain/fixer/strategies/bandit-fix.ts +61 -0
- package/src/domain/fixer/strategies/bias-testing.ts +49 -0
- package/src/domain/fixer/strategies/ci-compliance.ts +57 -0
- package/src/domain/fixer/strategies/content-marking.ts +45 -0
- package/src/domain/fixer/strategies/cve-upgrade.ts +66 -0
- package/src/domain/fixer/strategies/data-governance.ts +65 -0
- package/src/domain/fixer/strategies/disclosure.ts +69 -0
- package/src/domain/fixer/strategies/doc-code-sync.ts +53 -0
- package/src/domain/fixer/strategies/documentation.ts +59 -0
- package/src/domain/fixer/strategies/error-handler.ts +63 -0
- package/src/domain/fixer/strategies/hitl-gate.ts +67 -0
- package/src/domain/fixer/strategies/index.ts +61 -0
- package/src/domain/fixer/strategies/kill-switch-test.ts +85 -0
- package/src/domain/fixer/strategies/kill-switch.ts +53 -0
- package/src/domain/fixer/strategies/license-fix.ts +57 -0
- package/src/domain/fixer/strategies/log-retention.ts +40 -0
- package/src/domain/fixer/strategies/logging.ts +59 -0
- package/src/domain/fixer/strategies/metadata.ts +45 -0
- package/src/domain/fixer/strategies/permission-guard.ts +84 -0
- package/src/domain/fixer/strategies/record-keeping.ts +69 -0
- package/src/domain/fixer/strategies/secret-rotation.ts +52 -0
- package/src/domain/fixer/strategies.test.ts +341 -0
- package/src/domain/fixer/template-engine.test.ts +64 -0
- package/src/domain/fixer/template-engine.ts +38 -0
- package/src/domain/fixer/types.ts +88 -0
- package/src/domain/frameworks/aiuc1-framework.test.ts +159 -0
- package/src/domain/frameworks/aiuc1-framework.ts +126 -0
- package/src/domain/frameworks/collect-foundation-metrics.test.ts +96 -0
- package/src/domain/frameworks/collect-foundation-metrics.ts +34 -0
- package/src/domain/frameworks/eu-ai-act-framework.test.ts +117 -0
- package/src/domain/frameworks/eu-ai-act-framework.ts +100 -0
- package/src/domain/frameworks/framework-registry.test.ts +91 -0
- package/src/domain/frameworks/framework-registry.ts +38 -0
- package/src/domain/frameworks/index.ts +8 -0
- package/src/domain/frameworks/mitre-atlas-framework.test.ts +53 -0
- package/src/domain/frameworks/mitre-atlas-framework.ts +53 -0
- package/src/domain/frameworks/owasp-llm-framework.test.ts +77 -0
- package/src/domain/frameworks/owasp-llm-framework.ts +54 -0
- package/src/domain/frameworks/score-plugin-framework.ts +117 -0
- package/src/domain/fria/fria-generator.test.ts +273 -0
- package/src/domain/fria/fria-generator.ts +366 -0
- package/src/domain/import/promptfoo-importer.test.ts +103 -0
- package/src/domain/import/promptfoo-importer.ts +151 -0
- package/src/domain/onboarding/guided-onboarding.test.ts +144 -0
- package/src/domain/onboarding/guided-onboarding.ts +135 -0
- package/src/domain/passport/builder/domain-mapper.ts +9 -0
- package/src/domain/passport/builder/manifest-builder.test.ts +546 -0
- package/src/domain/passport/builder/manifest-builder.ts +535 -0
- package/src/domain/passport/builder/manifest-diff.test.ts +105 -0
- package/src/domain/passport/builder/manifest-diff.ts +89 -0
- package/src/domain/passport/builder/manifest-files.ts +17 -0
- package/src/domain/passport/crypto-signer.test.ts +93 -0
- package/src/domain/passport/crypto-signer.ts +157 -0
- package/src/domain/passport/discovery/agent-discovery.test.ts +296 -0
- package/src/domain/passport/discovery/agent-discovery.ts +325 -0
- package/src/domain/passport/discovery/autonomy-analyzer.test.ts +141 -0
- package/src/domain/passport/discovery/autonomy-analyzer.ts +113 -0
- package/src/domain/passport/discovery/permission-scanner.test.ts +191 -0
- package/src/domain/passport/discovery/permission-scanner.ts +414 -0
- package/src/domain/passport/export/a2a-mapper.ts +75 -0
- package/src/domain/passport/export/aiuc1-mapper.ts +126 -0
- package/src/domain/passport/export/export.test.ts +207 -0
- package/src/domain/passport/export/index.ts +41 -0
- package/src/domain/passport/export/nist-mapper.ts +227 -0
- package/src/domain/passport/import/a2a-importer.test.ts +133 -0
- package/src/domain/passport/import/a2a-importer.ts +156 -0
- package/src/domain/passport/import/index.ts +2 -0
- package/src/domain/passport/index.ts +32 -0
- package/src/domain/passport/obligation-field-map.test.ts +113 -0
- package/src/domain/passport/obligation-field-map.ts +117 -0
- package/src/domain/passport/passport-validator.test.ts +156 -0
- package/src/domain/passport/passport-validator.ts +126 -0
- package/src/domain/passport/scan-to-compliance.test.ts +336 -0
- package/src/domain/passport/scan-to-compliance.ts +166 -0
- package/src/domain/passport/test-generator.test.ts +93 -0
- package/src/domain/passport/test-generator.ts +136 -0
- package/src/domain/proxy/index.ts +11 -0
- package/src/domain/proxy/json-rpc.test.ts +72 -0
- package/src/domain/proxy/json-rpc.ts +53 -0
- package/src/domain/proxy/policy-engine.test.ts +259 -0
- package/src/domain/proxy/policy-engine.ts +137 -0
- package/src/domain/proxy/proxy-bridge.ts +125 -0
- package/src/domain/proxy/proxy-interceptor.test.ts +184 -0
- package/src/domain/proxy/proxy-interceptor.ts +120 -0
- package/src/domain/proxy/proxy-types.ts +35 -0
- package/src/domain/registry/compute-agent-score.test.ts +279 -0
- package/src/domain/registry/compute-agent-score.ts +162 -0
- package/src/domain/reporter/audit-report.test.ts +87 -0
- package/src/domain/reporter/audit-report.ts +116 -0
- package/src/domain/reporter/badge-generator.test.ts +54 -0
- package/src/domain/reporter/badge-generator.ts +40 -0
- package/src/domain/reporter/compliance-md.ts +45 -0
- package/src/domain/reporter/index.ts +7 -0
- package/src/domain/reporter/pdf-renderer.ts +282 -0
- package/src/domain/reporter/share.test.ts +92 -0
- package/src/domain/reporter/share.ts +80 -0
- package/src/domain/scanner/ast/swc-analyzer.test.ts +49 -0
- package/src/domain/scanner/ast/swc-analyzer.ts +124 -0
- package/src/domain/scanner/attestations.ts +97 -0
- package/src/domain/scanner/checks/ai-disclosure.test.ts +90 -0
- package/src/domain/scanner/checks/ai-disclosure.ts +54 -0
- package/src/domain/scanner/checks/ai-literacy.ts +163 -0
- package/src/domain/scanner/checks/behavioral-constraints.test.ts +167 -0
- package/src/domain/scanner/checks/behavioral-constraints.ts +86 -0
- package/src/domain/scanner/checks/compliance-metadata.ts +63 -0
- package/src/domain/scanner/checks/content-marking.ts +74 -0
- package/src/domain/scanner/checks/dep-deep-scan.test.ts +318 -0
- package/src/domain/scanner/checks/dep-deep-scan.ts +137 -0
- package/src/domain/scanner/checks/documentation.test.ts +88 -0
- package/src/domain/scanner/checks/documentation.ts +79 -0
- package/src/domain/scanner/checks/git-history.test.ts +120 -0
- package/src/domain/scanner/checks/git-history.ts +163 -0
- package/src/domain/scanner/checks/gpai-systemic-risk.test.ts +84 -0
- package/src/domain/scanner/checks/gpai-systemic-risk.ts +98 -0
- package/src/domain/scanner/checks/gpai-transparency.ts +94 -0
- package/src/domain/scanner/checks/index.ts +28 -0
- package/src/domain/scanner/checks/industry/index.ts +40 -0
- package/src/domain/scanner/checks/industry/industry.test.ts +287 -0
- package/src/domain/scanner/checks/interaction-logging.test.ts +113 -0
- package/src/domain/scanner/checks/interaction-logging.ts +142 -0
- package/src/domain/scanner/checks/nhi-scanner.test.ts +158 -0
- package/src/domain/scanner/checks/nhi-scanner.ts +78 -0
- package/src/domain/scanner/checks/passport-completeness.test.ts +127 -0
- package/src/domain/scanner/checks/passport-completeness.ts +82 -0
- package/src/domain/scanner/checks/passport-presence.test.ts +56 -0
- package/src/domain/scanner/checks/passport-presence.ts +78 -0
- package/src/domain/scanner/checks/pattern-check-factory.ts +70 -0
- package/src/domain/scanner/checks/permission-scanner.test.ts +279 -0
- package/src/domain/scanner/checks/permission-scanner.ts +90 -0
- package/src/domain/scanner/checks/presence-check-factory.test.ts +124 -0
- package/src/domain/scanner/checks/presence-check-factory.ts +275 -0
- package/src/domain/scanner/compliance-diff.test.ts +165 -0
- package/src/domain/scanner/compliance-diff.ts +138 -0
- package/src/domain/scanner/confidence.test.ts +235 -0
- package/src/domain/scanner/confidence.ts +156 -0
- package/src/domain/scanner/constants.ts +13 -0
- package/src/domain/scanner/create-scanner.ts +573 -0
- package/src/domain/scanner/cross-layer.test.ts +372 -0
- package/src/domain/scanner/cross-layer.ts +232 -0
- package/src/domain/scanner/data/ai-packages.ts +82 -0
- package/src/domain/scanner/debt-calculator.test.ts +89 -0
- package/src/domain/scanner/debt-calculator.ts +111 -0
- package/src/domain/scanner/drift.test.ts +191 -0
- package/src/domain/scanner/drift.ts +73 -0
- package/src/domain/scanner/evidence-store.test.ts +207 -0
- package/src/domain/scanner/evidence-store.ts +195 -0
- package/src/domain/scanner/evidence.test.ts +104 -0
- package/src/domain/scanner/evidence.ts +71 -0
- package/src/domain/scanner/external/bandit-runner.test.ts +45 -0
- package/src/domain/scanner/external/bandit-runner.ts +90 -0
- package/src/domain/scanner/external/checks.ts +321 -0
- package/src/domain/scanner/external/dedup.test.ts +79 -0
- package/src/domain/scanner/external/dedup.ts +94 -0
- package/src/domain/scanner/external/detect-secrets-runner.test.ts +58 -0
- package/src/domain/scanner/external/detect-secrets-runner.ts +81 -0
- package/src/domain/scanner/external/external-scanner.test.ts +221 -0
- package/src/domain/scanner/external/external-scanner.ts +36 -0
- package/src/domain/scanner/external/finding-mapper.test.ts +95 -0
- package/src/domain/scanner/external/finding-mapper.ts +138 -0
- package/src/domain/scanner/external/index.ts +15 -0
- package/src/domain/scanner/external/mappings.ts +93 -0
- package/src/domain/scanner/external/modelscan-runner.test.ts +35 -0
- package/src/domain/scanner/external/modelscan-runner.ts +101 -0
- package/src/domain/scanner/external/path-utils.ts +8 -0
- package/src/domain/scanner/external/runner-port.ts +45 -0
- package/src/domain/scanner/external/semgrep-runner.test.ts +52 -0
- package/src/domain/scanner/external/semgrep-runner.ts +94 -0
- package/src/domain/scanner/external/types.ts +32 -0
- package/src/domain/scanner/finding-attribution.test.ts +444 -0
- package/src/domain/scanner/finding-attribution.ts +195 -0
- package/src/domain/scanner/finding-explainer.test.ts +157 -0
- package/src/domain/scanner/finding-explainer.ts +73 -0
- package/src/domain/scanner/fix-diff-builder.test.ts +272 -0
- package/src/domain/scanner/fix-diff-builder.ts +477 -0
- package/src/domain/scanner/import-graph.test.ts +162 -0
- package/src/domain/scanner/import-graph.ts +198 -0
- package/src/domain/scanner/languages/adapter.test.ts +105 -0
- package/src/domain/scanner/languages/adapter.ts +239 -0
- package/src/domain/scanner/layers/index.ts +24 -0
- package/src/domain/scanner/layers/layer1-files.ts +54 -0
- package/src/domain/scanner/layers/layer2-docs.test.ts +1207 -0
- package/src/domain/scanner/layers/layer2-docs.ts +297 -0
- package/src/domain/scanner/layers/layer2-parsing.ts +217 -0
- package/src/domain/scanner/layers/layer3-config.test.ts +187 -0
- package/src/domain/scanner/layers/layer3-config.ts +279 -0
- package/src/domain/scanner/layers/layer3-parsers.ts +73 -0
- package/src/domain/scanner/layers/layer4-patterns.test.ts +397 -0
- package/src/domain/scanner/layers/layer4-patterns.ts +216 -0
- package/src/domain/scanner/layers/layer5-docs.test.ts +99 -0
- package/src/domain/scanner/layers/layer5-docs.ts +250 -0
- package/src/domain/scanner/layers/layer5-llm.test.ts +146 -0
- package/src/domain/scanner/layers/layer5-llm.ts +262 -0
- package/src/domain/scanner/layers/layer5-targeted.test.ts +93 -0
- package/src/domain/scanner/layers/layer5-targeted.ts +233 -0
- package/src/domain/scanner/layers/lockfile-parsers.test.ts +320 -0
- package/src/domain/scanner/layers/lockfile-parsers.ts +184 -0
- package/src/domain/scanner/regulation-version.test.ts +54 -0
- package/src/domain/scanner/regulation-version.ts +23 -0
- package/src/domain/scanner/role-filter.test.ts +116 -0
- package/src/domain/scanner/role-filter.ts +51 -0
- package/src/domain/scanner/rules/banned-packages-data.ts +553 -0
- package/src/domain/scanner/rules/banned-packages-sdk.ts +65 -0
- package/src/domain/scanner/rules/banned-packages.test.ts +249 -0
- package/src/domain/scanner/rules/banned-packages.ts +55 -0
- package/src/domain/scanner/rules/comment-filter.test.ts +115 -0
- package/src/domain/scanner/rules/comment-filter.ts +297 -0
- package/src/domain/scanner/rules/index.ts +9 -0
- package/src/domain/scanner/rules/nhi-patterns.test.ts +128 -0
- package/src/domain/scanner/rules/nhi-patterns.ts +60 -0
- package/src/domain/scanner/rules/pattern-rules.ts +1152 -0
- package/src/domain/scanner/sbom.test.ts +136 -0
- package/src/domain/scanner/sbom.ts +103 -0
- package/src/domain/scanner/scan-cache.test.ts +136 -0
- package/src/domain/scanner/scan-cache.ts +115 -0
- package/src/domain/scanner/scanner.test.ts +125 -0
- package/src/domain/scanner/score-calculator.test.ts +363 -0
- package/src/domain/scanner/score-calculator.ts +189 -0
- package/src/domain/scanner/security-score.test.ts +107 -0
- package/src/domain/scanner/security-score.ts +116 -0
- package/src/domain/scanner/source-filter.ts +24 -0
- package/src/domain/scanner/validators.ts +223 -0
- package/src/domain/shared/compliance-constants.ts +48 -0
- package/src/domain/shared/disclosure-patterns.ts +16 -0
- package/src/domain/shared/index.ts +6 -0
- package/src/domain/shared/parse-dependencies.ts +21 -0
- package/src/domain/supply-chain/dependency-analyzer.ts +138 -0
- package/src/domain/supply-chain/index.ts +3 -0
- package/src/domain/supply-chain/supply-chain.test.ts +211 -0
- package/src/domain/supply-chain/types.ts +32 -0
- package/src/domain/whatif/config-fixer.ts +187 -0
- package/src/domain/whatif/index.ts +6 -0
- package/src/domain/whatif/scenario-engine.ts +121 -0
- package/src/domain/whatif/simulate-actions.test.ts +161 -0
- package/src/domain/whatif/simulate-actions.ts +114 -0
- package/src/domain/whatif/whatif.test.ts +135 -0
- package/src/e2e/gaps-e2e.test.ts +259 -0
- package/src/e2e/smoke.test.ts +101 -0
- package/src/hooks/hooks-export.test.ts +81 -0
- package/src/hooks/installer.ts +113 -0
- package/src/http/cors.test.ts +38 -0
- package/src/http/create-router.ts +259 -0
- package/src/http/routes/agent.route.ts +380 -0
- package/src/http/routes/audit.route.ts +66 -0
- package/src/http/routes/badge.route.ts +23 -0
- package/src/http/routes/cert.route.ts +66 -0
- package/src/http/routes/chat.route.ts +228 -0
- package/src/http/routes/cost.route.ts +33 -0
- package/src/http/routes/debt.route.ts +29 -0
- package/src/http/routes/disclaimer.route.ts +64 -0
- package/src/http/routes/eval.route.ts +161 -0
- package/src/http/routes/events.route.test.ts +108 -0
- package/src/http/routes/events.route.ts +71 -0
- package/src/http/routes/external-scan.route.ts +24 -0
- package/src/http/routes/file.route.ts +54 -0
- package/src/http/routes/fix.route.ts +219 -0
- package/src/http/routes/frameworks.route.test.ts +66 -0
- package/src/http/routes/frameworks.route.ts +36 -0
- package/src/http/routes/git.route.ts +27 -0
- package/src/http/routes/guided-onboarding.route.ts +65 -0
- package/src/http/routes/import.route.ts +64 -0
- package/src/http/routes/jurisdiction.route.ts +22 -0
- package/src/http/routes/obligations.route.test.ts +122 -0
- package/src/http/routes/obligations.route.ts +110 -0
- package/src/http/routes/onboarding.route.ts +53 -0
- package/src/http/routes/provider.route.ts +42 -0
- package/src/http/routes/proxy.route.ts +40 -0
- package/src/http/routes/redteam.route.ts +84 -0
- package/src/http/routes/report.route.ts +29 -0
- package/src/http/routes/scan.route.ts +104 -0
- package/src/http/routes/share.route.ts +44 -0
- package/src/http/routes/shell.route.ts +27 -0
- package/src/http/routes/status.route.ts +66 -0
- package/src/http/routes/supply-chain.route.ts +121 -0
- package/src/http/routes/sync.route.ts +328 -0
- package/src/http/routes/tools.route.ts +29 -0
- package/src/http/routes/whatif.route.ts +96 -0
- package/src/http/utils/validation.ts +31 -0
- package/src/index.ts +1 -0
- package/src/infra/bundle-fetcher.ts +77 -0
- package/src/infra/cache-storage.ts +34 -0
- package/src/infra/event-bus.ts +31 -0
- package/src/infra/file-collector.ts +61 -0
- package/src/infra/file-ops-adapter.ts +95 -0
- package/src/infra/file-watcher.test.ts +90 -0
- package/src/infra/file-watcher.ts +106 -0
- package/src/infra/git-adapter.ts +93 -0
- package/src/infra/git-history-adapter.ts +41 -0
- package/src/infra/headless-browser.ts +178 -0
- package/src/infra/llm-adapter.test.ts +83 -0
- package/src/infra/llm-adapter.ts +86 -0
- package/src/infra/logger.ts +27 -0
- package/src/infra/project-config.test.ts +74 -0
- package/src/infra/project-config.ts +35 -0
- package/src/infra/rate-limiter.test.ts +36 -0
- package/src/infra/rate-limiter.ts +34 -0
- package/src/infra/retry.ts +46 -0
- package/src/infra/saas-client.ts +123 -0
- package/src/infra/search-adapter.ts +113 -0
- package/src/infra/shell-adapter.ts +68 -0
- package/src/infra/tool-manager.test.ts +99 -0
- package/src/infra/tool-manager.ts +197 -0
- package/src/llm/agents/agent-modes.test.ts +44 -0
- package/src/llm/agents/modes.ts +68 -0
- package/src/llm/routing/cost-routing.test.ts +37 -0
- package/src/llm/routing/cost-tracker.ts +74 -0
- package/src/llm/routing/model-routing.test.ts +79 -0
- package/src/llm/routing/model-routing.ts +38 -0
- package/src/llm/routing/pricing.ts +19 -0
- package/src/llm/sse-protocol.ts +77 -0
- package/src/llm/tool-definitions.ts +83 -0
- package/src/llm/tool-executors.ts +80 -0
- package/src/llm/tools/types.ts +13 -0
- package/src/mcp/create-mcp-stack.ts +82 -0
- package/src/mcp/handlers.ts +245 -0
- package/src/mcp/index.ts +28 -0
- package/src/mcp/mcp-server.test.ts +80 -0
- package/src/mcp/server.ts +79 -0
- package/src/mcp/tools.ts +48 -0
- package/src/onboarding/auto-detect.ts +164 -0
- package/src/onboarding/onboarding.test.ts +89 -0
- package/src/onboarding/profile.ts +169 -0
- package/src/onboarding/questions.ts +112 -0
- package/src/onboarding/wizard.ts +66 -0
- package/src/output/github-issue.ts +32 -0
- package/src/output/json-output.ts +67 -0
- package/src/ports/browser.port.ts +23 -0
- package/src/ports/events.port.ts +28 -0
- package/src/ports/llm.port.ts +23 -0
- package/src/ports/logger.port.ts +6 -0
- package/src/ports/process.port.ts +6 -0
- package/src/ports/scanner.port.ts +15 -0
- package/src/server.ts +134 -0
- package/src/services/badge-service.ts +67 -0
- package/src/services/chat-service.test.ts +162 -0
- package/src/services/chat-service.ts +152 -0
- package/src/services/cost-service.ts +52 -0
- package/src/services/debt-service.ts +65 -0
- package/src/services/eval-integration.test.ts +132 -0
- package/src/services/eval-service.test.ts +373 -0
- package/src/services/eval-service.ts +463 -0
- package/src/services/external-scan-service.ts +60 -0
- package/src/services/file-service.ts +37 -0
- package/src/services/fix-service.test.ts +470 -0
- package/src/services/fix-service.ts +648 -0
- package/src/services/framework-service.test.ts +159 -0
- package/src/services/framework-service.ts +67 -0
- package/src/services/onboarding-service.ts +165 -0
- package/src/services/passport-audit.ts +244 -0
- package/src/services/passport-documents.ts +258 -0
- package/src/services/passport-service-utils.ts +72 -0
- package/src/services/passport-service.test.ts +251 -0
- package/src/services/passport-service.ts +339 -0
- package/src/services/proxy-service.ts +81 -0
- package/src/services/report-service.ts +72 -0
- package/src/services/scan-service.test.ts +470 -0
- package/src/services/scan-service.ts +335 -0
- package/src/services/share-service.ts +108 -0
- package/src/services/shared/backup.ts +23 -0
- package/src/services/status-service.ts +38 -0
- package/src/services/undo-service.test.ts +190 -0
- package/src/services/undo-service.ts +144 -0
- package/src/test-helpers/factories.ts +116 -0
- package/src/types/common.schemas.ts +147 -0
- package/src/types/common.types.ts +292 -0
- package/src/types/contract.test.ts +217 -0
- package/src/types/errors.ts +52 -0
- package/src/types/framework.types.ts +87 -0
- package/src/types/passport-schemas.ts +241 -0
- package/src/types/passport.types.ts +296 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type { CheckResult } from '../../../types/common.types.js';
|
|
2
|
+
import type { DocQualityLevel } from '../../../types/passport.types.js';
|
|
3
|
+
import type { ScanContext } from '../../../ports/scanner.port.js';
|
|
4
|
+
import { DOCUMENT_VALIDATORS } from '../validators.js';
|
|
5
|
+
import {
|
|
6
|
+
parseMarkdownHeadings,
|
|
7
|
+
headingMatches,
|
|
8
|
+
extractSectionContents,
|
|
9
|
+
extractGroupedSectionContent,
|
|
10
|
+
measureSemanticDepth,
|
|
11
|
+
hasAiReviewMarker,
|
|
12
|
+
} from './layer2-parsing.js';
|
|
13
|
+
|
|
14
|
+
// Re-export for backward compatibility
|
|
15
|
+
export { measureSectionDepth, measureSemanticDepth } from './layer2-parsing.js';
|
|
16
|
+
export type { SectionDepth, SemanticDepth } from './layer2-parsing.js';
|
|
17
|
+
|
|
18
|
+
// --- Types ---
|
|
19
|
+
|
|
20
|
+
export interface ValidatorSection {
|
|
21
|
+
readonly title: string;
|
|
22
|
+
readonly required: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DocumentValidator {
|
|
26
|
+
readonly document: string;
|
|
27
|
+
readonly obligation: string;
|
|
28
|
+
readonly article: string;
|
|
29
|
+
readonly file_patterns: readonly string[];
|
|
30
|
+
readonly required_sections: readonly ValidatorSection[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type L2Status = 'VALID' | 'PARTIAL' | 'SHALLOW' | 'EMPTY';
|
|
34
|
+
|
|
35
|
+
export interface L2CheckResult {
|
|
36
|
+
readonly obligationId: string;
|
|
37
|
+
readonly article: string;
|
|
38
|
+
readonly document: string;
|
|
39
|
+
readonly status: L2Status;
|
|
40
|
+
readonly foundSections: readonly string[];
|
|
41
|
+
readonly missingSections: readonly string[];
|
|
42
|
+
readonly totalRequired: number;
|
|
43
|
+
readonly matchedRequired: number;
|
|
44
|
+
readonly shallowSections?: readonly string[];
|
|
45
|
+
readonly sectionFeedback?: readonly string[]; // per-section actionable feedback
|
|
46
|
+
readonly completenessScore?: number; // 0-100 overall document quality
|
|
47
|
+
readonly docQuality: DocQualityLevel;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// --- Validator Loading ---
|
|
51
|
+
|
|
52
|
+
export const loadValidators = (): readonly DocumentValidator[] => DOCUMENT_VALIDATORS;
|
|
53
|
+
|
|
54
|
+
/** Marker injected by Complior fix into auto-generated scaffold files. */
|
|
55
|
+
const SCAFFOLD_MARKER = '<!-- COMPLIOR:SCAFFOLD -->';
|
|
56
|
+
const hasScaffoldMarker = (content: string): boolean => content.includes(SCAFFOLD_MARKER);
|
|
57
|
+
|
|
58
|
+
/** Derive doc quality from L2 status + AI review marker presence + scaffold marker. */
|
|
59
|
+
const classifyDocQuality = (status: L2Status, content: string): DocQualityLevel =>
|
|
60
|
+
hasAiReviewMarker(content)
|
|
61
|
+
? 'reviewed'
|
|
62
|
+
: hasScaffoldMarker(content)
|
|
63
|
+
? 'scaffold'
|
|
64
|
+
: (status === 'SHALLOW' || status === 'EMPTY' || status === 'PARTIAL') ? 'scaffold' : 'draft';
|
|
65
|
+
|
|
66
|
+
// --- L2 Check Logic ---
|
|
67
|
+
|
|
68
|
+
const findMatchingFile = (
|
|
69
|
+
validator: DocumentValidator,
|
|
70
|
+
files: readonly { readonly relativePath: string; readonly content: string }[],
|
|
71
|
+
): { readonly relativePath: string; readonly content: string } | undefined => {
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
const filename = file.relativePath.split('/').pop() ?? '';
|
|
74
|
+
for (const pattern of validator.file_patterns) {
|
|
75
|
+
if (filename.toLowerCase() === pattern.toLowerCase()) {
|
|
76
|
+
return file;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const validateDocument = (
|
|
84
|
+
validator: DocumentValidator,
|
|
85
|
+
content: string,
|
|
86
|
+
): L2CheckResult => {
|
|
87
|
+
const obligationId = validator.obligation;
|
|
88
|
+
const headings = parseMarkdownHeadings(content);
|
|
89
|
+
const requiredSections = validator.required_sections.filter((s) => s.required);
|
|
90
|
+
|
|
91
|
+
if (content.trim().length === 0 || headings.length === 0) {
|
|
92
|
+
return {
|
|
93
|
+
obligationId,
|
|
94
|
+
article: validator.article,
|
|
95
|
+
document: validator.document,
|
|
96
|
+
status: 'EMPTY',
|
|
97
|
+
foundSections: [],
|
|
98
|
+
missingSections: requiredSections.map((s) => s.title),
|
|
99
|
+
totalRequired: requiredSections.length,
|
|
100
|
+
matchedRequired: 0,
|
|
101
|
+
docQuality: classifyDocQuality('EMPTY', content),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const found: string[] = [];
|
|
106
|
+
const missing: string[] = [];
|
|
107
|
+
|
|
108
|
+
for (const section of requiredSections) {
|
|
109
|
+
const matched = headings.some((h) => headingMatches(h, section.title));
|
|
110
|
+
if (matched) {
|
|
111
|
+
found.push(section.title);
|
|
112
|
+
} else {
|
|
113
|
+
missing.push(section.title);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If all headings present, check content depth for SHALLOW detection
|
|
118
|
+
if (missing.length === 0 && found.length > 0) {
|
|
119
|
+
const sectionContents = extractSectionContents(content);
|
|
120
|
+
const shallowSections: string[] = [];
|
|
121
|
+
const feedbackItems: string[] = [];
|
|
122
|
+
let totalQualityScore = 0;
|
|
123
|
+
let scoredSections = 0;
|
|
124
|
+
|
|
125
|
+
for (const sectionTitle of found) {
|
|
126
|
+
// Find matching heading in extracted contents
|
|
127
|
+
const matchedHeading = [...sectionContents.keys()].find((h) =>
|
|
128
|
+
headingMatches(h, sectionTitle),
|
|
129
|
+
);
|
|
130
|
+
if (matchedHeading !== undefined) {
|
|
131
|
+
// Use grouped extraction to include child sub-headings (### under ##)
|
|
132
|
+
const sectionContent = extractGroupedSectionContent(content, sectionTitle);
|
|
133
|
+
const semantic = measureSemanticDepth(sectionContent, sectionTitle);
|
|
134
|
+
if (semantic.isShallow) {
|
|
135
|
+
shallowSections.push(sectionTitle);
|
|
136
|
+
}
|
|
137
|
+
if (semantic.feedback && !semantic.feedback.endsWith(': adequate')) {
|
|
138
|
+
feedbackItems.push(semantic.feedback);
|
|
139
|
+
}
|
|
140
|
+
totalQualityScore += semantic.qualityScore;
|
|
141
|
+
scoredSections++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const shallowRatio = found.length > 0 ? shallowSections.length / found.length : 0;
|
|
146
|
+
const completenessScore = scoredSections > 0
|
|
147
|
+
? Math.round(totalQualityScore / scoredSections)
|
|
148
|
+
: 0;
|
|
149
|
+
const status: L2Status = shallowRatio > 0.5
|
|
150
|
+
? 'SHALLOW'
|
|
151
|
+
: completenessScore < 50
|
|
152
|
+
? 'PARTIAL'
|
|
153
|
+
: 'VALID';
|
|
154
|
+
|
|
155
|
+
const docQuality = classifyDocQuality(status, content);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
obligationId,
|
|
159
|
+
article: validator.article,
|
|
160
|
+
document: validator.document,
|
|
161
|
+
status,
|
|
162
|
+
foundSections: found,
|
|
163
|
+
missingSections: missing,
|
|
164
|
+
totalRequired: requiredSections.length,
|
|
165
|
+
matchedRequired: found.length,
|
|
166
|
+
shallowSections: shallowSections.length > 0 ? shallowSections : undefined,
|
|
167
|
+
sectionFeedback: feedbackItems.length > 0 ? feedbackItems : undefined,
|
|
168
|
+
completenessScore,
|
|
169
|
+
docQuality,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const status: L2Status = missing.length === 0
|
|
174
|
+
? 'VALID'
|
|
175
|
+
: found.length === 0
|
|
176
|
+
? 'EMPTY'
|
|
177
|
+
: 'PARTIAL';
|
|
178
|
+
|
|
179
|
+
const docQuality = classifyDocQuality(status, content);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
obligationId,
|
|
183
|
+
article: validator.article,
|
|
184
|
+
document: validator.document,
|
|
185
|
+
status,
|
|
186
|
+
foundSections: found,
|
|
187
|
+
missingSections: missing,
|
|
188
|
+
totalRequired: requiredSections.length,
|
|
189
|
+
matchedRequired: found.length,
|
|
190
|
+
docQuality,
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// --- Scanner Integration ---
|
|
195
|
+
|
|
196
|
+
export const runLayer2 = (ctx: ScanContext): readonly L2CheckResult[] => {
|
|
197
|
+
const validators = loadValidators();
|
|
198
|
+
const results: L2CheckResult[] = [];
|
|
199
|
+
|
|
200
|
+
for (const validator of validators) {
|
|
201
|
+
const file = findMatchingFile(validator, ctx.files);
|
|
202
|
+
if (file === undefined) continue; // L1 didn't find it — L2 skips
|
|
203
|
+
|
|
204
|
+
results.push(validateDocument(validator, file.content));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return results;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const layer2ToCheckResults = (l2Results: readonly L2CheckResult[]): readonly CheckResult[] => {
|
|
211
|
+
return l2Results.map((r): CheckResult => {
|
|
212
|
+
if (r.status === 'VALID') {
|
|
213
|
+
// Scaffold docs should never pass — they need manual editing
|
|
214
|
+
if (r.docQuality === 'scaffold') {
|
|
215
|
+
return {
|
|
216
|
+
type: 'fail',
|
|
217
|
+
checkId: `l2-${r.document}`,
|
|
218
|
+
message: `${r.article}: ${r.document} — document is scaffold/template quality, fill placeholder fields to upgrade`,
|
|
219
|
+
severity: 'low',
|
|
220
|
+
obligationId: r.obligationId,
|
|
221
|
+
articleReference: r.article,
|
|
222
|
+
fix: `Edit ${r.document} — replace [bracketed] placeholders with actual data`,
|
|
223
|
+
docQuality: r.docQuality,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
type: 'pass',
|
|
228
|
+
checkId: `l2-${r.document}`,
|
|
229
|
+
message: `${r.article}: ${r.document} — all ${r.totalRequired} required sections present`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (r.status === 'SHALLOW') {
|
|
234
|
+
const shallowList = r.shallowSections?.join(', ') ?? 'multiple sections';
|
|
235
|
+
const feedbackSuffix = r.sectionFeedback && r.sectionFeedback.length > 0
|
|
236
|
+
? `. Suggestions: ${r.sectionFeedback.join('. ')}`
|
|
237
|
+
: '';
|
|
238
|
+
const scoreSuffix = r.completenessScore !== undefined
|
|
239
|
+
? ` (quality: ${r.completenessScore}/100)`
|
|
240
|
+
: '';
|
|
241
|
+
return {
|
|
242
|
+
type: 'fail',
|
|
243
|
+
checkId: `l2-${r.document}`,
|
|
244
|
+
message: `${r.article}: ${r.document} — headings present but content is shallow in: ${shallowList}${scoreSuffix}`,
|
|
245
|
+
severity: 'medium',
|
|
246
|
+
obligationId: r.obligationId,
|
|
247
|
+
articleReference: r.article,
|
|
248
|
+
fix: `Expand shallow sections in ${r.document} with details (dates, specifics, lists): ${shallowList}${feedbackSuffix}`,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (r.status === 'PARTIAL') {
|
|
253
|
+
// Two PARTIAL triggers: missing sections OR low content quality
|
|
254
|
+
if (r.missingSections.length > 0) {
|
|
255
|
+
return {
|
|
256
|
+
type: 'fail',
|
|
257
|
+
checkId: `l2-${r.document}`,
|
|
258
|
+
message: `${r.article}: ${r.document} — missing sections: ${r.missingSections.join(', ')} (${r.matchedRequired}/${r.totalRequired})`,
|
|
259
|
+
severity: 'medium',
|
|
260
|
+
obligationId: r.obligationId,
|
|
261
|
+
articleReference: r.article,
|
|
262
|
+
fix: `Add missing sections to ${r.document}: ${r.missingSections.join(', ')}`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// All headings present but content quality < 50
|
|
266
|
+
const scoreSuffix = r.completenessScore !== undefined
|
|
267
|
+
? ` (quality: ${r.completenessScore}/100)`
|
|
268
|
+
: '';
|
|
269
|
+
const feedbackSuffix = r.sectionFeedback && r.sectionFeedback.length > 0
|
|
270
|
+
? `. Suggestions: ${r.sectionFeedback.join('. ')}`
|
|
271
|
+
: '';
|
|
272
|
+
const scaffoldHint = r.docQuality === 'scaffold'
|
|
273
|
+
? '. Remove <!-- COMPLIOR:SCAFFOLD --> comment after editing to upgrade to draft quality'
|
|
274
|
+
: '';
|
|
275
|
+
return {
|
|
276
|
+
type: 'fail',
|
|
277
|
+
checkId: `l2-${r.document}`,
|
|
278
|
+
message: `${r.article}: ${r.document} — all sections present but content needs enrichment${scoreSuffix}`,
|
|
279
|
+
severity: 'low',
|
|
280
|
+
obligationId: r.obligationId,
|
|
281
|
+
articleReference: r.article,
|
|
282
|
+
fix: `Enrich content in ${r.document} — add specifics, dates, tables${feedbackSuffix}${scaffoldHint}`,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// EMPTY
|
|
287
|
+
return {
|
|
288
|
+
type: 'fail',
|
|
289
|
+
checkId: `l2-${r.document}`,
|
|
290
|
+
message: `${r.article}: ${r.document} — document is empty or has no headings (0/${r.totalRequired} required sections)`,
|
|
291
|
+
severity: 'high',
|
|
292
|
+
obligationId: r.obligationId,
|
|
293
|
+
articleReference: r.article,
|
|
294
|
+
fix: `Populate ${r.document} with required sections: ${r.missingSections.join(', ')}`,
|
|
295
|
+
};
|
|
296
|
+
});
|
|
297
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
export interface SectionDepth {
|
|
2
|
+
readonly wordCount: number;
|
|
3
|
+
readonly sentenceCount: number;
|
|
4
|
+
readonly hasLists: boolean;
|
|
5
|
+
readonly hasTables: boolean;
|
|
6
|
+
readonly hasSpecifics: boolean;
|
|
7
|
+
readonly isShallow: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const HEADING_REGEX = /^#{1,4}\s+(.+)$/gm;
|
|
11
|
+
|
|
12
|
+
export const parseMarkdownHeadings = (content: string): readonly string[] => {
|
|
13
|
+
const headings: string[] = [];
|
|
14
|
+
let match: RegExpExecArray | null;
|
|
15
|
+
while ((match = HEADING_REGEX.exec(content)) !== null) {
|
|
16
|
+
headings.push(match[1].trim());
|
|
17
|
+
}
|
|
18
|
+
return headings;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const normalize = (text: string): string =>
|
|
22
|
+
text.toLowerCase().replace(/[\s_-]+/g, ' ').trim();
|
|
23
|
+
|
|
24
|
+
export const headingMatches = (heading: string, sectionTitle: string): boolean =>
|
|
25
|
+
normalize(heading).includes(normalize(sectionTitle));
|
|
26
|
+
|
|
27
|
+
const LIST_REGEX = /^[\s]*[-*•]\s+|^\s*\d+\.\s+/m;
|
|
28
|
+
const TABLE_REGEX = /\|.*\|.*\|/;
|
|
29
|
+
const SPECIFICS_REGEX = /\b\d{4}[-/]\d{2}[-/]\d{2}\b|\b\d+%|\b\d+\.\d+\b|€|Art\.\s*\d+/;
|
|
30
|
+
|
|
31
|
+
export const extractSectionContents = (content: string): ReadonlyMap<string, string> => {
|
|
32
|
+
const sections = new Map<string, string>();
|
|
33
|
+
const lines = content.split('\n');
|
|
34
|
+
let currentHeading: string | null = null;
|
|
35
|
+
let currentContent: string[] = [];
|
|
36
|
+
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
const headingMatch = /^#{1,4}\s+(.+)$/.exec(line);
|
|
39
|
+
if (headingMatch) {
|
|
40
|
+
if (currentHeading !== null) {
|
|
41
|
+
sections.set(currentHeading, currentContent.join('\n'));
|
|
42
|
+
}
|
|
43
|
+
currentHeading = headingMatch[1].trim();
|
|
44
|
+
currentContent = [];
|
|
45
|
+
} else if (currentHeading !== null) {
|
|
46
|
+
currentContent.push(line);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (currentHeading !== null) {
|
|
51
|
+
sections.set(currentHeading, currentContent.join('\n'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return sections;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extract content for a target heading, including all child sub-headings
|
|
59
|
+
* until the next heading of equal or higher level.
|
|
60
|
+
* Example: for "## System Elements", aggregates ### 2.1, ### 2.2, etc.
|
|
61
|
+
*/
|
|
62
|
+
export const extractGroupedSectionContent = (
|
|
63
|
+
content: string,
|
|
64
|
+
targetHeading: string,
|
|
65
|
+
): string => {
|
|
66
|
+
const lines = content.split('\n');
|
|
67
|
+
let capturing = false;
|
|
68
|
+
let targetLevel = 0;
|
|
69
|
+
const captured: string[] = [];
|
|
70
|
+
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
const match = /^(#{1,4})\s+(.+)$/.exec(line);
|
|
73
|
+
if (match) {
|
|
74
|
+
const level = match[1].length;
|
|
75
|
+
const heading = match[2].trim();
|
|
76
|
+
if (!capturing && headingMatches(heading, targetHeading)) {
|
|
77
|
+
capturing = true;
|
|
78
|
+
targetLevel = level;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (capturing && level <= targetLevel) {
|
|
82
|
+
break; // next sibling or parent — stop
|
|
83
|
+
}
|
|
84
|
+
// child heading — include its content
|
|
85
|
+
}
|
|
86
|
+
if (capturing) {
|
|
87
|
+
captured.push(line);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return captured.join('\n');
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// --- Semantic Depth (E-12 enhancement) ---
|
|
95
|
+
|
|
96
|
+
export interface SemanticDepth extends SectionDepth {
|
|
97
|
+
readonly hasNumericMetrics: boolean; // percentages, counts, thresholds
|
|
98
|
+
readonly hasLegalReferences: boolean; // "Art. X", "GDPR", "ISO"
|
|
99
|
+
readonly hasDateReferences: boolean; // dates, deadlines, "annually"
|
|
100
|
+
readonly hasActionItems: boolean; // "must", "shall", "required to"
|
|
101
|
+
readonly hasMeasurableTargets: boolean; // "99.9%", ">95%", "within 24 hours"
|
|
102
|
+
readonly placeholderCount: number; // [TODO], [Name], [Company] etc.
|
|
103
|
+
readonly qualityScore: number; // 0-100 per section
|
|
104
|
+
readonly feedback: string; // actionable recommendation
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const NUMERIC_METRICS_REGEX = /\b\d+(\.\d+)?%|\b\d+(\.\d+)?\s*(ms|seconds?|minutes?|hours?|days?|requests?|errors?|users?)|\b[<>≥≤]=?\s*\d+/;
|
|
108
|
+
const LEGAL_REF_REGEX = /\bArt\.\s*\d+|\bGDPR\b|\bISO\s*\d+|\bNIST\b|\bAIUC|EU\s*AI\s*Act|Regulation\s*\(EU\)/i;
|
|
109
|
+
const DATE_REF_REGEX = /\b\d{4}[-/]\d{2}[-/]\d{2}\b|\b(annually|quarterly|monthly|weekly|bi-annual|semi-annual)\b|\b(Q[1-4]\s*20\d{2}|H[12]\s*20\d{2})\b/i;
|
|
110
|
+
const ACTION_REGEX = /\b(must|shall|required to|obliged to|needs? to|will implement|will ensure)\b/i;
|
|
111
|
+
const MEASURABLE_REGEX = /\b\d+(\.\d+)?%|\bwithin\s+\d+\s*(hours?|days?|minutes?)|\b(SLA|KPI|SLO|threshold|target|benchmark)\b/i;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Detect bracketed placeholder patterns like [Name], [TODO: ...], [Company Name].
|
|
115
|
+
* Excludes markdown links [text](url) via negative lookahead, and checkboxes [x]/[ ] via min length.
|
|
116
|
+
*/
|
|
117
|
+
const PLACEHOLDER_BRACKET_REGEX = /\[[^\]]{2,60}\](?!\()/g;
|
|
118
|
+
|
|
119
|
+
export const measureSemanticDepth = (content: string, sectionTitle: string): SemanticDepth => {
|
|
120
|
+
const base = measureSectionDepth(content);
|
|
121
|
+
const trimmed = content.trim();
|
|
122
|
+
|
|
123
|
+
const hasNumericMetrics = NUMERIC_METRICS_REGEX.test(trimmed);
|
|
124
|
+
const hasLegalReferences = LEGAL_REF_REGEX.test(trimmed);
|
|
125
|
+
const hasDateReferences = DATE_REF_REGEX.test(trimmed);
|
|
126
|
+
const hasActionItems = ACTION_REGEX.test(trimmed);
|
|
127
|
+
const hasMeasurableTargets = MEASURABLE_REGEX.test(trimmed);
|
|
128
|
+
|
|
129
|
+
// Placeholder detection: [Name], [TODO: Company], [H/M/L/N], etc.
|
|
130
|
+
const placeholderMatches = trimmed.match(PLACEHOLDER_BRACKET_REGEX) ?? [];
|
|
131
|
+
const placeholderCount = placeholderMatches.filter(m =>
|
|
132
|
+
!/^\[[ xX]\]$/.test(m) && // not checkbox
|
|
133
|
+
!/^\[!\w/.test(m), // not image alt start
|
|
134
|
+
).length;
|
|
135
|
+
|
|
136
|
+
// Quality score: weighted sum of semantic signals
|
|
137
|
+
let qualityScore = 0;
|
|
138
|
+
if (base.wordCount >= 50) qualityScore += 20;
|
|
139
|
+
else if (base.wordCount >= 20) qualityScore += 10;
|
|
140
|
+
if (base.hasLists) qualityScore += 10;
|
|
141
|
+
if (base.hasTables) qualityScore += 10;
|
|
142
|
+
if (base.hasSpecifics) qualityScore += 10;
|
|
143
|
+
if (hasNumericMetrics) qualityScore += 15;
|
|
144
|
+
if (hasLegalReferences) qualityScore += 10;
|
|
145
|
+
if (hasDateReferences) qualityScore += 5;
|
|
146
|
+
if (hasActionItems) qualityScore += 10;
|
|
147
|
+
if (hasMeasurableTargets) qualityScore += 10;
|
|
148
|
+
|
|
149
|
+
// Placeholder penalty: scaffold documents have many [TODO], [Name], etc.
|
|
150
|
+
if (placeholderCount > 0) {
|
|
151
|
+
qualityScore -= placeholderCount * 10;
|
|
152
|
+
if (placeholderCount >= 3) qualityScore -= 20; // heavy scaffold penalty
|
|
153
|
+
}
|
|
154
|
+
qualityScore = Math.max(0, qualityScore);
|
|
155
|
+
|
|
156
|
+
// Generate feedback
|
|
157
|
+
const suggestions: string[] = [];
|
|
158
|
+
if (placeholderCount > 0) suggestions.push(`Fill ${placeholderCount} placeholder(s) — replace [bracketed] fields with actual data`);
|
|
159
|
+
if (!hasNumericMetrics) suggestions.push('Add numeric metrics (accuracy %, response time, thresholds)');
|
|
160
|
+
if (!hasLegalReferences) suggestions.push('Add legal references (Art. numbers, standards)');
|
|
161
|
+
if (!hasDateReferences) suggestions.push('Add timeline (dates, review frequency)');
|
|
162
|
+
if (!hasMeasurableTargets) suggestions.push('Add measurable targets (KPIs, SLAs)');
|
|
163
|
+
if (base.wordCount < 50) suggestions.push(`Expand content (currently ${base.wordCount} words, minimum 50 recommended)`);
|
|
164
|
+
if (!base.hasLists && !base.hasTables) suggestions.push('Add structured content (lists or tables)');
|
|
165
|
+
|
|
166
|
+
const feedback = suggestions.length > 0
|
|
167
|
+
? `Section "${sectionTitle}": ${suggestions.join('; ')}`
|
|
168
|
+
: `Section "${sectionTitle}": adequate`;
|
|
169
|
+
|
|
170
|
+
// Override isShallow based on semantic quality
|
|
171
|
+
const isShallow = qualityScore < 30;
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
...base,
|
|
175
|
+
isShallow,
|
|
176
|
+
hasNumericMetrics,
|
|
177
|
+
hasLegalReferences,
|
|
178
|
+
hasDateReferences,
|
|
179
|
+
hasActionItems,
|
|
180
|
+
hasMeasurableTargets,
|
|
181
|
+
placeholderCount,
|
|
182
|
+
qualityScore,
|
|
183
|
+
feedback,
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// --- AI Review Marker Detection ---
|
|
188
|
+
|
|
189
|
+
export const AI_REVIEW_MARKER_REGEX = /<!--\s*complior:reviewed\s+(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s*-->/;
|
|
190
|
+
|
|
191
|
+
export const hasAiReviewMarker = (content: string): boolean =>
|
|
192
|
+
AI_REVIEW_MARKER_REGEX.test(content);
|
|
193
|
+
|
|
194
|
+
export const extractReviewDate = (content: string): string | undefined =>
|
|
195
|
+
content.match(AI_REVIEW_MARKER_REGEX)?.[1];
|
|
196
|
+
|
|
197
|
+
// --- Section Depth ---
|
|
198
|
+
|
|
199
|
+
export const measureSectionDepth = (content: string): SectionDepth => {
|
|
200
|
+
const trimmed = content.trim();
|
|
201
|
+
const words = trimmed.split(/\s+/).filter((w) => w.length > 0);
|
|
202
|
+
const sentences = trimmed.split(/[.!?]+/).filter((s) => s.trim().length > 0);
|
|
203
|
+
const hasLists = LIST_REGEX.test(trimmed);
|
|
204
|
+
const hasTables = TABLE_REGEX.test(trimmed);
|
|
205
|
+
const hasSpecifics = SPECIFICS_REGEX.test(trimmed);
|
|
206
|
+
|
|
207
|
+
const isShallow = words.length < 50 && !hasLists && !hasTables && !hasSpecifics;
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
wordCount: words.length,
|
|
211
|
+
sentenceCount: sentences.length,
|
|
212
|
+
hasLists,
|
|
213
|
+
hasTables,
|
|
214
|
+
hasSpecifics,
|
|
215
|
+
isShallow,
|
|
216
|
+
};
|
|
217
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { runLayer3, layer3ToCheckResults } from './layer3-config.js';
|
|
3
|
+
import { createScanFile, createScanCtx } from '../../../test-helpers/factories.js';
|
|
4
|
+
|
|
5
|
+
describe('runLayer3', () => {
|
|
6
|
+
it('detects AI SDK in package.json (npm project)', () => {
|
|
7
|
+
const ctx = createScanCtx([
|
|
8
|
+
createScanFile('package.json', JSON.stringify({
|
|
9
|
+
dependencies: {
|
|
10
|
+
'openai': '^4.56.0',
|
|
11
|
+
'express': '^4.18.0',
|
|
12
|
+
},
|
|
13
|
+
})),
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const results = runLayer3(ctx);
|
|
17
|
+
const sdkFindings = results.filter((r) => r.type === 'ai-sdk-detected');
|
|
18
|
+
|
|
19
|
+
expect(sdkFindings.length).toBeGreaterThan(0);
|
|
20
|
+
expect(sdkFindings.some((r) => r.message.includes('OpenAI'))).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('detects prohibited package in requirements.txt (pip project)', () => {
|
|
24
|
+
const ctx = createScanCtx([
|
|
25
|
+
createScanFile('requirements.txt', `anthropic>=0.7.0
|
|
26
|
+
deepface==1.0.2
|
|
27
|
+
flask>=2.0.0
|
|
28
|
+
`),
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const results = runLayer3(ctx);
|
|
32
|
+
const banned = results.filter((r) => r.type === 'banned-package');
|
|
33
|
+
const sdks = results.filter((r) => r.type === 'ai-sdk-detected');
|
|
34
|
+
|
|
35
|
+
expect(banned).toHaveLength(1);
|
|
36
|
+
expect(banned[0].status).toBe('PROHIBITED');
|
|
37
|
+
expect(banned[0].packageName).toBe('deepface');
|
|
38
|
+
expect(banned[0].article).toBe('Art. 5(1)(f)');
|
|
39
|
+
expect(banned[0].message).toContain('Art. 5 REVIEW:');
|
|
40
|
+
expect(banned[0].message).toContain('Verify:');
|
|
41
|
+
expect(banned[0].bannedPackage).toBeDefined();
|
|
42
|
+
expect(banned[0].bannedPackage?.prohibitedWhen).toContain('workplace or educational');
|
|
43
|
+
|
|
44
|
+
expect(sdks.some((r) => r.message.includes('Anthropic'))).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('detects AI SDK in go.mod (Go project)', () => {
|
|
48
|
+
const ctx = createScanCtx([
|
|
49
|
+
createScanFile('go.mod', `module myapp
|
|
50
|
+
|
|
51
|
+
go 1.21
|
|
52
|
+
|
|
53
|
+
require (
|
|
54
|
+
github.com/sashabaranov/go-openai v1.20.0
|
|
55
|
+
github.com/gin-gonic/gin v1.9.1
|
|
56
|
+
)
|
|
57
|
+
`),
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const results = runLayer3(ctx);
|
|
61
|
+
const sdkFindings = results.filter((r) => r.type === 'ai-sdk-detected');
|
|
62
|
+
|
|
63
|
+
expect(sdkFindings).toHaveLength(1);
|
|
64
|
+
expect(sdkFindings[0].message).toContain('OpenAI');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('aggregates mixed dependencies (npm + pip + .env)', () => {
|
|
68
|
+
const ctx = createScanCtx([
|
|
69
|
+
createScanFile('package.json', JSON.stringify({
|
|
70
|
+
dependencies: { 'openai': '^4.0.0' },
|
|
71
|
+
})),
|
|
72
|
+
createScanFile('requirements.txt', 'anthropic>=0.7.0\n'),
|
|
73
|
+
createScanFile('.env', 'OPENAI_API_KEY=sk-xxx\nLOG_LEVEL=info\nSENTRY_DSN=https://xxx\n'),
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
const results = runLayer3(ctx);
|
|
77
|
+
const sdkFindings = results.filter((r) => r.type === 'ai-sdk-detected');
|
|
78
|
+
const envFindings = results.filter((r) => r.type === 'env-config');
|
|
79
|
+
|
|
80
|
+
// At least 2 AI SDKs (openai from npm, anthropic from pip)
|
|
81
|
+
expect(sdkFindings.length).toBeGreaterThanOrEqual(2);
|
|
82
|
+
|
|
83
|
+
// .env has API key, LOG_LEVEL, and SENTRY_DSN — all OK
|
|
84
|
+
const okEnvs = envFindings.filter((r) => r.status === 'OK');
|
|
85
|
+
expect(okEnvs.length).toBeGreaterThan(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('detects log retention in docker-compose.override.yml', () => {
|
|
89
|
+
const ctx = createScanCtx([
|
|
90
|
+
createScanFile('docker-compose.override.yml', `services:
|
|
91
|
+
app:
|
|
92
|
+
logging:
|
|
93
|
+
driver: json-file
|
|
94
|
+
options:
|
|
95
|
+
max-size: "50m"
|
|
96
|
+
max-file: "60"
|
|
97
|
+
`),
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
const results = runLayer3(ctx);
|
|
101
|
+
const retention = results.filter((r) => r.type === 'log-retention');
|
|
102
|
+
|
|
103
|
+
expect(retention).toHaveLength(1);
|
|
104
|
+
expect(retention[0].status).toBe('OK');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('skips bias-testing warning when bias-testing config file present', () => {
|
|
108
|
+
const ctx = createScanCtx([
|
|
109
|
+
createScanFile('package.json', JSON.stringify({
|
|
110
|
+
dependencies: { 'openai': '^4.56.0' },
|
|
111
|
+
})),
|
|
112
|
+
createScanFile('bias-testing.config.json', '{ "enabled": true }'),
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
const results = runLayer3(ctx);
|
|
116
|
+
const biasFindings = results.filter((r) => r.type === 'missing-bias-testing');
|
|
117
|
+
|
|
118
|
+
expect(biasFindings).toHaveLength(0);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('returns no AI SDK findings for non-AI project', () => {
|
|
122
|
+
const ctx = createScanCtx([
|
|
123
|
+
createScanFile('package.json', JSON.stringify({
|
|
124
|
+
dependencies: {
|
|
125
|
+
'express': '^4.18.0',
|
|
126
|
+
'react': '^18.2.0',
|
|
127
|
+
'lodash': '^4.17.0',
|
|
128
|
+
},
|
|
129
|
+
})),
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
const results = runLayer3(ctx);
|
|
133
|
+
const sdkFindings = results.filter((r) => r.type === 'ai-sdk-detected');
|
|
134
|
+
const bannedFindings = results.filter((r) => r.type === 'banned-package');
|
|
135
|
+
|
|
136
|
+
expect(sdkFindings).toHaveLength(0);
|
|
137
|
+
expect(bannedFindings).toHaveLength(0);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('layer3ToCheckResults', () => {
|
|
142
|
+
it('converts PROHIBITED finding to critical fail with contextual fix', () => {
|
|
143
|
+
const results = layer3ToCheckResults([{
|
|
144
|
+
type: 'banned-package',
|
|
145
|
+
status: 'PROHIBITED',
|
|
146
|
+
message: 'Art. 5 REVIEW: "deepface" detected — Emotion recognition. Prohibited under Art. 5(1)(f) when: Infers emotions in workplace or educational settings, except for medical or safety purposes. Verify: Is this used to detect emotions of employees or students? (Medical/safety use is exempt)',
|
|
147
|
+
obligationId: 'eu-ai-act-OBL-002',
|
|
148
|
+
article: 'Art. 5(1)(f)',
|
|
149
|
+
packageName: 'deepface',
|
|
150
|
+
ecosystem: 'pip',
|
|
151
|
+
penalty: '€35M or 7% turnover',
|
|
152
|
+
bannedPackage: {
|
|
153
|
+
name: 'deepface',
|
|
154
|
+
ecosystem: 'pip',
|
|
155
|
+
reason: 'Emotion recognition',
|
|
156
|
+
obligationId: 'eu-ai-act-OBL-002',
|
|
157
|
+
article: 'Art. 5(1)(f)',
|
|
158
|
+
penalty: '€35M or 7% turnover',
|
|
159
|
+
prohibitedWhen: 'Infers emotions in workplace or educational settings, except for medical or safety purposes',
|
|
160
|
+
verifyMessage: 'Is this used to detect emotions of employees or students? (Medical/safety use is exempt)',
|
|
161
|
+
},
|
|
162
|
+
}]);
|
|
163
|
+
|
|
164
|
+
expect(results).toHaveLength(1);
|
|
165
|
+
expect(results[0].type).toBe('fail');
|
|
166
|
+
if (results[0].type === 'fail') {
|
|
167
|
+
expect(results[0].severity).toBe('critical');
|
|
168
|
+
expect(results[0].message).toContain('Art. 5 REVIEW:');
|
|
169
|
+
expect(results[0].message).toContain('Verify:');
|
|
170
|
+
expect(results[0].fix).toContain('Verify your use case');
|
|
171
|
+
expect(results[0].fix).toContain('Document your use case to confirm compliance');
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('converts OK finding to pass', () => {
|
|
176
|
+
const results = layer3ToCheckResults([{
|
|
177
|
+
type: 'ai-sdk-detected',
|
|
178
|
+
status: 'OK',
|
|
179
|
+
message: 'AI SDK detected: OpenAI (openai@4.56.0) in npm',
|
|
180
|
+
packageName: 'openai',
|
|
181
|
+
ecosystem: 'npm',
|
|
182
|
+
}]);
|
|
183
|
+
|
|
184
|
+
expect(results).toHaveLength(1);
|
|
185
|
+
expect(results[0].type).toBe('pass');
|
|
186
|
+
});
|
|
187
|
+
});
|