@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,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adversarial Test Runner — Art.9(6)-(8) testing, Art.15(4) robustness.
|
|
3
|
+
*
|
|
4
|
+
* Sends adversarial prompts to the configured LLM (system under test),
|
|
5
|
+
* evaluates responses deterministically via pattern matching.
|
|
6
|
+
* The LLM is the subject, NOT the evaluator.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
import type { EvaluationResult } from './adversarial/evaluators.js';
|
|
13
|
+
import type { EvidenceStore } from '../scanner/evidence-store.js';
|
|
14
|
+
import type { AuditStore } from '../audit/audit-trail.js';
|
|
15
|
+
|
|
16
|
+
import { createEvidence } from '../scanner/evidence.js';
|
|
17
|
+
import { PROMPT_INJECTION_SCENARIOS } from './adversarial/prompt-injection.js';
|
|
18
|
+
import { BIAS_TEST_PAIRS } from './adversarial/bias-tests.js';
|
|
19
|
+
import { SAFETY_BOUNDARY_SCENARIOS } from './adversarial/safety-boundary.js';
|
|
20
|
+
|
|
21
|
+
// ── Constants ────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export const ADVERSARIAL_OBLIGATIONS = ['OBL-003c', 'OBL-009b'] as const;
|
|
24
|
+
|
|
25
|
+
/** Max chars to store for bias pair prompt/response previews. */
|
|
26
|
+
const BIAS_PROMPT_PREVIEW_LEN = 100;
|
|
27
|
+
const BIAS_RESPONSE_PREVIEW_LEN = 500;
|
|
28
|
+
|
|
29
|
+
// ── Types ────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export type TestCategory = 'prompt_injection' | 'bias_detection' | 'safety_boundary';
|
|
32
|
+
export type TestVerdict = 'pass' | 'fail' | 'inconclusive';
|
|
33
|
+
|
|
34
|
+
export interface TestScenario {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly category: TestCategory;
|
|
37
|
+
readonly name: string;
|
|
38
|
+
readonly description: string;
|
|
39
|
+
readonly articleRef: string;
|
|
40
|
+
readonly prompt: string;
|
|
41
|
+
readonly systemPrompt?: string;
|
|
42
|
+
readonly evaluate: (response: string) => EvaluationResult;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BiasTestPair {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly category: 'bias_detection';
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly description: string;
|
|
50
|
+
readonly articleRef: string;
|
|
51
|
+
readonly promptA: string;
|
|
52
|
+
readonly promptB: string;
|
|
53
|
+
readonly characteristicA: string;
|
|
54
|
+
readonly characteristicB: string;
|
|
55
|
+
readonly systemPrompt?: string;
|
|
56
|
+
readonly evaluate: (responseA: string, responseB: string) => EvaluationResult;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface TestResult {
|
|
60
|
+
readonly scenarioId: string;
|
|
61
|
+
readonly category: TestCategory;
|
|
62
|
+
readonly name: string;
|
|
63
|
+
readonly verdict: TestVerdict;
|
|
64
|
+
readonly confidence: number;
|
|
65
|
+
readonly prompt: string;
|
|
66
|
+
readonly response: string;
|
|
67
|
+
readonly matchedPatterns: readonly string[];
|
|
68
|
+
readonly reasoning: string;
|
|
69
|
+
readonly durationMs: number;
|
|
70
|
+
readonly articleRef: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface CategorySummary {
|
|
74
|
+
readonly total: number;
|
|
75
|
+
readonly passed: number;
|
|
76
|
+
readonly failed: number;
|
|
77
|
+
readonly inconclusive: number;
|
|
78
|
+
readonly score: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface AdversarialReport {
|
|
82
|
+
readonly agentName: string;
|
|
83
|
+
readonly timestamp: string;
|
|
84
|
+
readonly duration: number;
|
|
85
|
+
readonly categories: Record<TestCategory, CategorySummary>;
|
|
86
|
+
readonly results: readonly TestResult[];
|
|
87
|
+
readonly overallScore: number;
|
|
88
|
+
readonly passCount: number;
|
|
89
|
+
readonly failCount: number;
|
|
90
|
+
readonly inconclusiveCount: number;
|
|
91
|
+
readonly totalTests: number;
|
|
92
|
+
readonly obligationRefs: readonly string[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface TestRunnerDeps {
|
|
96
|
+
readonly callLlm: (prompt: string, systemPrompt?: string) => Promise<string>;
|
|
97
|
+
readonly evidenceStore?: EvidenceStore;
|
|
98
|
+
readonly auditStore?: AuditStore;
|
|
99
|
+
readonly getProjectPath: () => string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Pure helpers ─────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/** Format current timestamp for safe use in filenames. */
|
|
105
|
+
const fileTimestamp = (): string => new Date().toISOString().replace(/[:.]/g, '-');
|
|
106
|
+
|
|
107
|
+
interface VerdictCounts {
|
|
108
|
+
readonly total: number;
|
|
109
|
+
readonly passed: number;
|
|
110
|
+
readonly failed: number;
|
|
111
|
+
readonly inconclusive: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Count verdicts in a result set, optionally filtered by category. */
|
|
115
|
+
const countVerdicts = (results: readonly TestResult[], category?: TestCategory): VerdictCounts => {
|
|
116
|
+
const filtered = category ? results.filter(r => r.category === category) : results;
|
|
117
|
+
let passed = 0, failed = 0, inconclusive = 0;
|
|
118
|
+
for (const r of filtered) {
|
|
119
|
+
if (r.verdict === 'pass') passed++;
|
|
120
|
+
else if (r.verdict === 'fail') failed++;
|
|
121
|
+
else inconclusive++;
|
|
122
|
+
}
|
|
123
|
+
return { total: filtered.length, passed, failed, inconclusive };
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const toCategorySummary = (counts: VerdictCounts): CategorySummary => ({
|
|
127
|
+
...counts,
|
|
128
|
+
score: counts.total > 0 ? Math.round((counts.passed / counts.total) * 100) : 0,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
type ScenarioMeta = Pick<TestScenario, 'id' | 'category' | 'name' | 'articleRef'>;
|
|
132
|
+
|
|
133
|
+
/** Build a TestResult from scenario metadata + evaluation + timing. */
|
|
134
|
+
const toTestResult = (
|
|
135
|
+
meta: ScenarioMeta,
|
|
136
|
+
evaluation: EvaluationResult,
|
|
137
|
+
prompt: string,
|
|
138
|
+
response: string,
|
|
139
|
+
durationMs: number,
|
|
140
|
+
): TestResult => ({
|
|
141
|
+
scenarioId: meta.id,
|
|
142
|
+
category: meta.category,
|
|
143
|
+
name: meta.name,
|
|
144
|
+
verdict: evaluation.verdict,
|
|
145
|
+
confidence: evaluation.confidence,
|
|
146
|
+
prompt,
|
|
147
|
+
response,
|
|
148
|
+
matchedPatterns: evaluation.matchedPatterns,
|
|
149
|
+
reasoning: evaluation.reasoning,
|
|
150
|
+
durationMs,
|
|
151
|
+
articleRef: meta.articleRef,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/** Build the final report from collected results. Pure function. */
|
|
155
|
+
export const buildReport = (
|
|
156
|
+
agentName: string,
|
|
157
|
+
results: readonly TestResult[],
|
|
158
|
+
duration: number,
|
|
159
|
+
): AdversarialReport => {
|
|
160
|
+
const overall = countVerdicts(results);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
agentName,
|
|
164
|
+
timestamp: new Date().toISOString(),
|
|
165
|
+
duration,
|
|
166
|
+
categories: {
|
|
167
|
+
prompt_injection: toCategorySummary(countVerdicts(results, 'prompt_injection')),
|
|
168
|
+
bias_detection: toCategorySummary(countVerdicts(results, 'bias_detection')),
|
|
169
|
+
safety_boundary: toCategorySummary(countVerdicts(results, 'safety_boundary')),
|
|
170
|
+
},
|
|
171
|
+
results,
|
|
172
|
+
overallScore: overall.total > 0 ? Math.round((overall.passed / overall.total) * 100) : 0,
|
|
173
|
+
passCount: overall.passed,
|
|
174
|
+
failCount: overall.failed,
|
|
175
|
+
inconclusiveCount: overall.inconclusive,
|
|
176
|
+
totalTests: overall.total,
|
|
177
|
+
obligationRefs: ADVERSARIAL_OBLIGATIONS,
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// ── I/O helpers (separated from domain logic) ────────────────────
|
|
182
|
+
|
|
183
|
+
const saveReport = async (report: AdversarialReport, projectPath: string): Promise<void> => {
|
|
184
|
+
const reportsDir = join(projectPath, '.complior', 'reports');
|
|
185
|
+
await mkdir(reportsDir, { recursive: true });
|
|
186
|
+
const path = join(reportsDir, `adversarial-${report.agentName}-${fileTimestamp()}.json`);
|
|
187
|
+
await writeFile(path, JSON.stringify(report, null, 2));
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const recordEvidence = async (
|
|
191
|
+
results: readonly TestResult[],
|
|
192
|
+
evidenceStore: EvidenceStore,
|
|
193
|
+
): Promise<void> => {
|
|
194
|
+
const items = results.map(r =>
|
|
195
|
+
createEvidence(
|
|
196
|
+
`adversarial-${r.scenarioId}`,
|
|
197
|
+
'adversarial',
|
|
198
|
+
'adversarial-test',
|
|
199
|
+
{ snippet: `${r.verdict}: ${r.reasoning}` },
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
await evidenceStore.append(items, `adversarial-${fileTimestamp()}`);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const recordAudit = async (
|
|
206
|
+
report: AdversarialReport,
|
|
207
|
+
auditStore: AuditStore,
|
|
208
|
+
): Promise<void> => {
|
|
209
|
+
await auditStore.append('adversarial.completed', {
|
|
210
|
+
totalTests: report.totalTests,
|
|
211
|
+
passCount: report.passCount,
|
|
212
|
+
failCount: report.failCount,
|
|
213
|
+
inconclusiveCount: report.inconclusiveCount,
|
|
214
|
+
overallScore: report.overallScore,
|
|
215
|
+
categories: Object.fromEntries(
|
|
216
|
+
Object.entries(report.categories).map(([k, v]) => [k, v.score]),
|
|
217
|
+
),
|
|
218
|
+
}, report.agentName);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// ── Factory ──────────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
export const createTestRunner = (deps: TestRunnerDeps) => {
|
|
224
|
+
const { callLlm, evidenceStore, auditStore, getProjectPath } = deps;
|
|
225
|
+
|
|
226
|
+
/** Call LLM with error isolation — never throws. */
|
|
227
|
+
const callLlmSafely = async (prompt: string, systemPrompt?: string): Promise<string> => {
|
|
228
|
+
try {
|
|
229
|
+
return await callLlm(prompt, systemPrompt);
|
|
230
|
+
} catch (err) {
|
|
231
|
+
return `[ERROR] LLM call failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const runScenario = async (scenario: TestScenario): Promise<TestResult> => {
|
|
236
|
+
const start = Date.now();
|
|
237
|
+
const response = await callLlmSafely(scenario.prompt, scenario.systemPrompt);
|
|
238
|
+
const evaluation = scenario.evaluate(response);
|
|
239
|
+
return toTestResult(scenario, evaluation, scenario.prompt, response, Date.now() - start);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const runBiasPair = async (pair: BiasTestPair): Promise<TestResult> => {
|
|
243
|
+
const start = Date.now();
|
|
244
|
+
const responseA = await callLlmSafely(pair.promptA, pair.systemPrompt);
|
|
245
|
+
const responseB = await callLlmSafely(pair.promptB, pair.systemPrompt);
|
|
246
|
+
const evaluation = pair.evaluate(responseA, responseB);
|
|
247
|
+
|
|
248
|
+
const prompt = `A: ${pair.promptA.slice(0, BIAS_PROMPT_PREVIEW_LEN)}... | B: ${pair.promptB.slice(0, BIAS_PROMPT_PREVIEW_LEN)}...`;
|
|
249
|
+
const response = `A: ${responseA.slice(0, BIAS_RESPONSE_PREVIEW_LEN)} | B: ${responseB.slice(0, BIAS_RESPONSE_PREVIEW_LEN)}`;
|
|
250
|
+
return toTestResult(pair, evaluation, prompt, response, Date.now() - start);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const runAdversarialTests = async (
|
|
254
|
+
agentName: string,
|
|
255
|
+
categories?: readonly TestCategory[],
|
|
256
|
+
): Promise<AdversarialReport> => {
|
|
257
|
+
const startTime = Date.now();
|
|
258
|
+
const results: TestResult[] = [];
|
|
259
|
+
const selected = categories ?? (['prompt_injection', 'bias_detection', 'safety_boundary'] as const);
|
|
260
|
+
|
|
261
|
+
// Sequential execution to avoid rate limits
|
|
262
|
+
if (selected.includes('prompt_injection')) {
|
|
263
|
+
for (const scenario of PROMPT_INJECTION_SCENARIOS) {
|
|
264
|
+
results.push(await runScenario(scenario));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (selected.includes('bias_detection')) {
|
|
268
|
+
for (const pair of BIAS_TEST_PAIRS) {
|
|
269
|
+
results.push(await runBiasPair(pair));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (selected.includes('safety_boundary')) {
|
|
273
|
+
for (const scenario of SAFETY_BOUNDARY_SCENARIOS) {
|
|
274
|
+
results.push(await runScenario(scenario));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const report = buildReport(agentName, results, Date.now() - startTime);
|
|
279
|
+
|
|
280
|
+
// I/O: persist results (fire-and-forget-safe; errors logged, not thrown)
|
|
281
|
+
await saveReport(report, getProjectPath());
|
|
282
|
+
if (evidenceStore) await recordEvidence(results, evidenceStore);
|
|
283
|
+
if (auditStore) await recordAudit(report, auditStore);
|
|
284
|
+
|
|
285
|
+
return Object.freeze(report);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
return Object.freeze({ runAdversarialTests });
|
|
289
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { computeCostEstimate } from './cost-estimator.js';
|
|
3
|
+
|
|
4
|
+
describe('computeCostEstimate', () => {
|
|
5
|
+
it('returns zero costs when no issues', () => {
|
|
6
|
+
const result = computeCostEstimate({
|
|
7
|
+
findings: [],
|
|
8
|
+
passportCompleteness: 100,
|
|
9
|
+
friaCompleted: true,
|
|
10
|
+
evidenceValid: true,
|
|
11
|
+
hourlyRate: 150,
|
|
12
|
+
});
|
|
13
|
+
expect(result.totalCost).toBe(0);
|
|
14
|
+
expect(result.remediationCost).toBe(0);
|
|
15
|
+
expect(result.documentationCost).toBe(0);
|
|
16
|
+
expect(result.roi).toBe(0);
|
|
17
|
+
expect(result.currency).toBe('EUR');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('calculates remediation cost by severity', () => {
|
|
21
|
+
const result = computeCostEstimate({
|
|
22
|
+
findings: [
|
|
23
|
+
{ severity: 'critical', checkId: 'c1', status: 'fail' },
|
|
24
|
+
{ severity: 'high', checkId: 'h1', status: 'fail' },
|
|
25
|
+
{ severity: 'medium', checkId: 'm1', status: 'fail' },
|
|
26
|
+
{ severity: 'low', checkId: 'l1', status: 'fail' },
|
|
27
|
+
],
|
|
28
|
+
passportCompleteness: 100,
|
|
29
|
+
friaCompleted: true,
|
|
30
|
+
evidenceValid: true,
|
|
31
|
+
hourlyRate: 100,
|
|
32
|
+
});
|
|
33
|
+
// 8 + 4 + 2 + 1 = 15 hours x EUR 100 = EUR 1,500
|
|
34
|
+
expect(result.remediationCost).toBe(1500);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('adds FRIA cost when not completed', () => {
|
|
38
|
+
const result = computeCostEstimate({
|
|
39
|
+
findings: [],
|
|
40
|
+
passportCompleteness: 100,
|
|
41
|
+
friaCompleted: false,
|
|
42
|
+
evidenceValid: true,
|
|
43
|
+
hourlyRate: 150,
|
|
44
|
+
});
|
|
45
|
+
expect(result.documentationCost).toBe(16 * 150); // FRIA: 16h
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('adds passport completion cost', () => {
|
|
49
|
+
const result = computeCostEstimate({
|
|
50
|
+
findings: [],
|
|
51
|
+
passportCompleteness: 50,
|
|
52
|
+
friaCompleted: true,
|
|
53
|
+
evidenceValid: true,
|
|
54
|
+
hourlyRate: 150,
|
|
55
|
+
});
|
|
56
|
+
// 50% missing -> ceil(50/10)*2 = 10h x EUR 150 = EUR 1,500
|
|
57
|
+
expect(result.breakdown.some((b) => b.item === 'Passport completion')).toBe(
|
|
58
|
+
true,
|
|
59
|
+
);
|
|
60
|
+
const passportItem = result.breakdown.find(
|
|
61
|
+
(b) => b.item === 'Passport completion',
|
|
62
|
+
);
|
|
63
|
+
expect(passportItem?.effortHours).toBe(10);
|
|
64
|
+
expect(passportItem?.cost).toBe(1500);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('adds evidence chain setup cost', () => {
|
|
68
|
+
const result = computeCostEstimate({
|
|
69
|
+
findings: [],
|
|
70
|
+
passportCompleteness: 100,
|
|
71
|
+
friaCompleted: true,
|
|
72
|
+
evidenceValid: false,
|
|
73
|
+
hourlyRate: 150,
|
|
74
|
+
});
|
|
75
|
+
expect(
|
|
76
|
+
result.breakdown.some((b) => b.item === 'Evidence chain setup'),
|
|
77
|
+
).toBe(true);
|
|
78
|
+
expect(result.documentationCost).toBe(4 * 150);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('skips pass findings', () => {
|
|
82
|
+
const result = computeCostEstimate({
|
|
83
|
+
findings: [{ severity: 'critical', checkId: 'c1', status: 'pass' }],
|
|
84
|
+
passportCompleteness: 100,
|
|
85
|
+
friaCompleted: true,
|
|
86
|
+
evidenceValid: true,
|
|
87
|
+
hourlyRate: 150,
|
|
88
|
+
});
|
|
89
|
+
expect(result.remediationCost).toBe(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('calculates ROI correctly', () => {
|
|
93
|
+
const result = computeCostEstimate({
|
|
94
|
+
findings: [{ severity: 'critical', checkId: 'c1', status: 'fail' }],
|
|
95
|
+
passportCompleteness: 100,
|
|
96
|
+
friaCompleted: true,
|
|
97
|
+
evidenceValid: true,
|
|
98
|
+
hourlyRate: 100,
|
|
99
|
+
});
|
|
100
|
+
expect(result.totalCost).toBe(800); // 8h x EUR 100
|
|
101
|
+
expect(result.potentialFine).toBeGreaterThan(0);
|
|
102
|
+
expect(result.roi).toBeGreaterThan(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('returns frozen result', () => {
|
|
106
|
+
const result = computeCostEstimate({
|
|
107
|
+
findings: [],
|
|
108
|
+
passportCompleteness: 100,
|
|
109
|
+
friaCompleted: true,
|
|
110
|
+
evidenceValid: true,
|
|
111
|
+
hourlyRate: 150,
|
|
112
|
+
});
|
|
113
|
+
expect(Object.isFrozen(result)).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('returns frozen breakdown array', () => {
|
|
117
|
+
const result = computeCostEstimate({
|
|
118
|
+
findings: [{ severity: 'high', checkId: 'h1', status: 'fail' }],
|
|
119
|
+
passportCompleteness: 100,
|
|
120
|
+
friaCompleted: true,
|
|
121
|
+
evidenceValid: true,
|
|
122
|
+
hourlyRate: 100,
|
|
123
|
+
});
|
|
124
|
+
expect(Object.isFrozen(result.breakdown)).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('uses default 2h for unknown severity', () => {
|
|
128
|
+
const result = computeCostEstimate({
|
|
129
|
+
findings: [
|
|
130
|
+
{ severity: 'unknown-severity', checkId: 'x1', status: 'fail' },
|
|
131
|
+
],
|
|
132
|
+
passportCompleteness: 100,
|
|
133
|
+
friaCompleted: true,
|
|
134
|
+
evidenceValid: true,
|
|
135
|
+
hourlyRate: 100,
|
|
136
|
+
});
|
|
137
|
+
expect(result.remediationCost).toBe(200); // 2h x EUR 100
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('caps potential fine risk factor at 1.0', () => {
|
|
141
|
+
// 5 critical findings -> 5 * 0.3 = 1.5, capped at 1.0
|
|
142
|
+
const result = computeCostEstimate({
|
|
143
|
+
findings: Array.from({ length: 5 }, (_, i) => ({
|
|
144
|
+
severity: 'critical',
|
|
145
|
+
checkId: `c${i}`,
|
|
146
|
+
status: 'fail',
|
|
147
|
+
})),
|
|
148
|
+
passportCompleteness: 100,
|
|
149
|
+
friaCompleted: true,
|
|
150
|
+
evidenceValid: true,
|
|
151
|
+
hourlyRate: 100,
|
|
152
|
+
});
|
|
153
|
+
expect(result.potentialFine).toBe(35_000_000);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('calculates combined costs with all gap types', () => {
|
|
157
|
+
const result = computeCostEstimate({
|
|
158
|
+
findings: [
|
|
159
|
+
{ severity: 'critical', checkId: 'c1', status: 'fail' },
|
|
160
|
+
{ severity: 'low', checkId: 'l1', status: 'fail' },
|
|
161
|
+
],
|
|
162
|
+
passportCompleteness: 70,
|
|
163
|
+
friaCompleted: false,
|
|
164
|
+
evidenceValid: false,
|
|
165
|
+
hourlyRate: 200,
|
|
166
|
+
});
|
|
167
|
+
// Remediation: (8+1)*200 = 1800
|
|
168
|
+
expect(result.remediationCost).toBe(1800);
|
|
169
|
+
// Documentation: FRIA 16*200=3200, passport ceil(30/10)*2=6h*200=1200,
|
|
170
|
+
// evidence 4*200=800 => 5200
|
|
171
|
+
expect(result.documentationCost).toBe(3200 + 1200 + 800);
|
|
172
|
+
expect(result.totalCost).toBe(1800 + 5200);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('handles empty findings array gracefully', () => {
|
|
176
|
+
const result = computeCostEstimate({
|
|
177
|
+
findings: [],
|
|
178
|
+
passportCompleteness: 100,
|
|
179
|
+
friaCompleted: true,
|
|
180
|
+
evidenceValid: true,
|
|
181
|
+
hourlyRate: 0,
|
|
182
|
+
});
|
|
183
|
+
expect(result.totalCost).toBe(0);
|
|
184
|
+
expect(result.breakdown).toHaveLength(0);
|
|
185
|
+
expect(result.potentialFine).toBe(0);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-S05-27: Compliance Cost Estimator
|
|
3
|
+
*
|
|
4
|
+
* Pure domain function that computes remediation costs, documentation costs,
|
|
5
|
+
* potential fines, and ROI based on scan findings and passport state.
|
|
6
|
+
*
|
|
7
|
+
* EU AI Act reference: Art. 99 (fines up to EUR 35M or 7% of global turnover).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { SeverityLevel, ComplianceDocType } from '../shared/compliance-constants.js';
|
|
11
|
+
|
|
12
|
+
export interface CostEstimateInput {
|
|
13
|
+
readonly findings: readonly { severity: string; checkId: string; status: string }[];
|
|
14
|
+
readonly passportCompleteness: number; // 0-100
|
|
15
|
+
readonly friaCompleted: boolean;
|
|
16
|
+
readonly evidenceValid: boolean;
|
|
17
|
+
readonly hourlyRate: number; // default EUR 150
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CostEstimateResult {
|
|
21
|
+
readonly remediationCost: number;
|
|
22
|
+
readonly documentationCost: number;
|
|
23
|
+
readonly totalCost: number;
|
|
24
|
+
readonly potentialFine: number;
|
|
25
|
+
readonly roi: number;
|
|
26
|
+
readonly breakdown: readonly CostLineItem[];
|
|
27
|
+
readonly currency: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CostLineItem {
|
|
31
|
+
readonly category: string;
|
|
32
|
+
readonly item: string;
|
|
33
|
+
readonly effortHours: number;
|
|
34
|
+
readonly cost: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Estimated remediation hours per severity level. */
|
|
38
|
+
const SEVERITY_HOURS: Record<SeverityLevel, number> = {
|
|
39
|
+
critical: 8,
|
|
40
|
+
high: 4,
|
|
41
|
+
medium: 2,
|
|
42
|
+
low: 1,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Estimated documentation effort hours per compliance doc type. */
|
|
46
|
+
const DOC_HOURS: Record<ComplianceDocType, number> = {
|
|
47
|
+
fria: 16,
|
|
48
|
+
'technical-documentation': 8,
|
|
49
|
+
'worker-notification': 4,
|
|
50
|
+
'monitoring-policy': 4,
|
|
51
|
+
'ai-literacy': 4,
|
|
52
|
+
'declaration-of-conformity': 8,
|
|
53
|
+
'incident-report': 2,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** EU AI Act max fine: EUR 35M or 7% of global turnover (Art. 99) */
|
|
57
|
+
const MAX_FINE_EUR = 35_000_000;
|
|
58
|
+
|
|
59
|
+
export const computeCostEstimate = (input: CostEstimateInput): CostEstimateResult => {
|
|
60
|
+
const { findings, passportCompleteness, friaCompleted, evidenceValid, hourlyRate } = input;
|
|
61
|
+
const breakdown: CostLineItem[] = [];
|
|
62
|
+
|
|
63
|
+
// 1. Remediation cost per finding (only failed checks)
|
|
64
|
+
const failFindings = findings.filter(f => f.status === 'fail');
|
|
65
|
+
for (const finding of failFindings) {
|
|
66
|
+
const hours = SEVERITY_HOURS[finding.severity] ?? 2;
|
|
67
|
+
breakdown.push({
|
|
68
|
+
category: 'remediation',
|
|
69
|
+
item: finding.checkId,
|
|
70
|
+
effortHours: hours,
|
|
71
|
+
cost: hours * hourlyRate,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2. Documentation cost — FRIA (Art. 27)
|
|
76
|
+
if (!friaCompleted) {
|
|
77
|
+
breakdown.push({
|
|
78
|
+
category: 'documentation',
|
|
79
|
+
item: 'FRIA (Art. 27)',
|
|
80
|
+
effortHours: DOC_HOURS['fria']!,
|
|
81
|
+
cost: DOC_HOURS['fria']! * hourlyRate,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 3. Passport completion cost
|
|
86
|
+
if (passportCompleteness < 100) {
|
|
87
|
+
const missingPct = 100 - passportCompleteness;
|
|
88
|
+
const hours = Math.ceil(missingPct / 10) * 2; // ~2h per 10% missing
|
|
89
|
+
breakdown.push({
|
|
90
|
+
category: 'documentation',
|
|
91
|
+
item: 'Passport completion',
|
|
92
|
+
effortHours: hours,
|
|
93
|
+
cost: hours * hourlyRate,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 4. Evidence chain setup
|
|
98
|
+
if (!evidenceValid) {
|
|
99
|
+
breakdown.push({
|
|
100
|
+
category: 'infrastructure',
|
|
101
|
+
item: 'Evidence chain setup',
|
|
102
|
+
effortHours: 4,
|
|
103
|
+
cost: 4 * hourlyRate,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const remediationCost = breakdown
|
|
108
|
+
.filter(b => b.category === 'remediation')
|
|
109
|
+
.reduce((sum, b) => sum + b.cost, 0);
|
|
110
|
+
const documentationCost = breakdown
|
|
111
|
+
.filter(b => b.category !== 'remediation')
|
|
112
|
+
.reduce((sum, b) => sum + b.cost, 0);
|
|
113
|
+
const totalCost = remediationCost + documentationCost;
|
|
114
|
+
|
|
115
|
+
// Potential fine estimate based on severity distribution
|
|
116
|
+
const criticalCount = failFindings.filter(f => f.severity === 'critical').length;
|
|
117
|
+
const highCount = failFindings.filter(f => f.severity === 'high').length;
|
|
118
|
+
const riskFactor = Math.min(1.0, (criticalCount * 0.3 + highCount * 0.1));
|
|
119
|
+
const potentialFine = Math.round(MAX_FINE_EUR * riskFactor);
|
|
120
|
+
|
|
121
|
+
// ROI as multiplier: how many times the compliance cost is recovered in avoided fines
|
|
122
|
+
const roi = totalCost > 0 ? Math.round((potentialFine / totalCost) * 10) / 10 : 0;
|
|
123
|
+
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
remediationCost,
|
|
126
|
+
documentationCost,
|
|
127
|
+
totalCost,
|
|
128
|
+
potentialFine,
|
|
129
|
+
roi,
|
|
130
|
+
breakdown: Object.freeze(breakdown),
|
|
131
|
+
currency: 'EUR',
|
|
132
|
+
});
|
|
133
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getDisclaimer, appendDisclaimer, getReportFooter, containsBannedPhrase } from './disclaimer.js';
|
|
3
|
+
import { ENGINE_VERSION } from '../version.js';
|
|
4
|
+
|
|
5
|
+
describe('Disclaimer Framework', () => {
|
|
6
|
+
it('system prompt contains legal advisor warning', () => {
|
|
7
|
+
const disclaimer = getDisclaimer('system_prompt');
|
|
8
|
+
expect(disclaimer).toContain('not a legal advisor');
|
|
9
|
+
expect(disclaimer).toContain('NEVER');
|
|
10
|
+
expect(disclaimer).toContain('automated scanning');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('report footer contains disclaimer', () => {
|
|
14
|
+
const footer = getDisclaimer('report_footer');
|
|
15
|
+
expect(footer).toContain('does NOT constitute legal advice');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('compliance_md contains disclaimer', () => {
|
|
19
|
+
const md = getDisclaimer('compliance_md');
|
|
20
|
+
expect(md).toContain('NOT a legal certification');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('commit message contains review warning', () => {
|
|
24
|
+
const msg = getDisclaimer('commit_message');
|
|
25
|
+
expect(msg).toContain('Review before merging');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('appendDisclaimer adds footer', () => {
|
|
29
|
+
const result = appendDisclaimer('Report content', 'report_footer');
|
|
30
|
+
expect(result).toContain('Report content');
|
|
31
|
+
expect(result).toContain('does NOT constitute');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('getReportFooter includes version and date', () => {
|
|
35
|
+
const footer = getReportFooter(ENGINE_VERSION);
|
|
36
|
+
expect(footer).toContain(`complior v${ENGINE_VERSION}`);
|
|
37
|
+
expect(footer).toMatch(/\d{4}-\d{2}-\d{2}/);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Banned Phrases', () => {
|
|
42
|
+
it('detects banned phrases', () => {
|
|
43
|
+
expect(containsBannedPhrase('You are compliant')).toBe(true);
|
|
44
|
+
expect(containsBannedPhrase('Your project is compliant with EU AI Act')).toBe(true);
|
|
45
|
+
expect(containsBannedPhrase('I guarantee compliance')).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('allows safe phrases', () => {
|
|
49
|
+
expect(containsBannedPhrase('Based on automated scanning: score 72/100')).toBe(false);
|
|
50
|
+
expect(containsBannedPhrase('Scanner detected 8 violations')).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type DisclaimerContext = 'system_prompt' | 'report_footer' | 'compliance_md' | 'commit_message' | 'chat_response';
|
|
2
|
+
|
|
3
|
+
const DISCLAIMERS: Record<DisclaimerContext, string> = {
|
|
4
|
+
system_prompt: 'You are Complior, a compliance ASSISTANT — not a legal advisor. You NEVER state that a project "is compliant" or "meets all requirements". You ALWAYS frame results as "based on automated scanning" and recommend professional legal review for definitive compliance assessment.',
|
|
5
|
+
|
|
6
|
+
report_footer: `---
|
|
7
|
+
**Disclaimer:** This report was generated by Complior automated scanning tool. It does NOT constitute legal advice. For definitive compliance assessment, consult a qualified legal professional specializing in EU AI Act.`,
|
|
8
|
+
|
|
9
|
+
compliance_md: `## Disclaimer
|
|
10
|
+
|
|
11
|
+
This compliance status is based on automated scanning by Complior. It is NOT a legal certification. Professional legal review is recommended before making compliance claims.`,
|
|
12
|
+
|
|
13
|
+
commit_message: 'Auto-generated by Complior. Review before merging. Automated fix — not legal advice.',
|
|
14
|
+
|
|
15
|
+
chat_response: 'Disclaimer: Complior — automated scanning tool, not a legal advisor. Professional review recommended.',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const getDisclaimer = (context: DisclaimerContext): string => DISCLAIMERS[context];
|
|
19
|
+
|
|
20
|
+
export const appendDisclaimer = (content: string, context: DisclaimerContext): string =>
|
|
21
|
+
`${content}\n\n${DISCLAIMERS[context]}`;
|
|
22
|
+
|
|
23
|
+
export const getReportFooter = (version: string): string =>
|
|
24
|
+
`${DISCLAIMERS.report_footer}\nGenerated: ${new Date().toISOString().slice(0, 10)} | Scanner: complior v${version}`;
|
|
25
|
+
|
|
26
|
+
// Banned phrases that LLM should never output
|
|
27
|
+
export const BANNED_PHRASES = [
|
|
28
|
+
'you are compliant',
|
|
29
|
+
'your project is compliant',
|
|
30
|
+
'meets all requirements',
|
|
31
|
+
'fully compliant',
|
|
32
|
+
'no violations found', // when score < 100
|
|
33
|
+
'I guarantee compliance',
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
export const containsBannedPhrase = (text: string): boolean => {
|
|
37
|
+
const lower = text.toLowerCase();
|
|
38
|
+
return BANNED_PHRASES.some((phrase) => lower.includes(phrase.toLowerCase()));
|
|
39
|
+
};
|