@euconform/core 1.0.1 → 1.3.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/evidence/bundle.d.ts +23 -0
- package/dist/evidence/bundle.d.ts.map +1 -0
- package/dist/evidence/bundle.js +44 -0
- package/dist/evidence/compliance-evaluation.d.ts +12 -0
- package/dist/evidence/compliance-evaluation.d.ts.map +1 -0
- package/dist/evidence/compliance-evaluation.js +78 -0
- package/dist/evidence/hash.d.ts +2 -0
- package/dist/evidence/hash.d.ts.map +1 -0
- package/dist/evidence/hash.js +4 -0
- package/dist/evidence/index.d.ts +13 -0
- package/dist/evidence/index.d.ts.map +1 -0
- package/dist/evidence/index.js +11 -0
- package/dist/evidence/markdown.d.ts +9 -0
- package/dist/evidence/markdown.d.ts.map +1 -0
- package/dist/evidence/markdown.js +169 -0
- package/dist/evidence/output.d.ts +13 -0
- package/dist/evidence/output.d.ts.map +1 -0
- package/dist/evidence/output.js +547 -0
- package/dist/evidence/types.d.ts +230 -0
- package/dist/evidence/types.d.ts.map +1 -0
- package/dist/evidence/types.js +7 -0
- package/dist/evidence/validate.d.ts +8 -0
- package/dist/evidence/validate.d.ts.map +1 -0
- package/dist/evidence/validate.js +175 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/inference/ollama-client.d.ts +3 -0
- package/dist/inference/ollama-client.d.ts.map +1 -1
- package/dist/inference/ollama-client.js +69 -71
- package/dist/legal-checks/annex-iv-report.d.ts +3 -0
- package/dist/legal-checks/annex-iv-report.d.ts.map +1 -1
- package/dist/legal-checks/annex-iv-report.js +1 -0
- package/dist/legal-checks/deadline-timeline.d.ts +57 -0
- package/dist/legal-checks/deadline-timeline.d.ts.map +1 -0
- package/dist/legal-checks/deadline-timeline.js +265 -0
- package/dist/legal-checks/gap-analysis.d.ts +48 -0
- package/dist/legal-checks/gap-analysis.d.ts.map +1 -0
- package/dist/legal-checks/gap-analysis.js +397 -0
- package/dist/legal-checks/gpai-classifier.d.ts +49 -0
- package/dist/legal-checks/gpai-classifier.d.ts.map +1 -0
- package/dist/legal-checks/gpai-classifier.js +197 -0
- package/dist/legal-checks/index.d.ts +6 -0
- package/dist/legal-checks/index.d.ts.map +1 -1
- package/dist/legal-checks/index.js +3 -0
- package/dist/scanner/aggregator.d.ts +7 -0
- package/dist/scanner/aggregator.d.ts.map +1 -0
- package/dist/scanner/aggregator.js +200 -0
- package/dist/scanner/detectors/ai-framework-detector.d.ts +3 -0
- package/dist/scanner/detectors/ai-framework-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/ai-framework-detector.js +74 -0
- package/dist/scanner/detectors/ai-provider-detector.d.ts +3 -0
- package/dist/scanner/detectors/ai-provider-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/ai-provider-detector.js +100 -0
- package/dist/scanner/detectors/compliance-detector.d.ts +3 -0
- package/dist/scanner/detectors/compliance-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/compliance-detector.js +201 -0
- package/dist/scanner/detectors/config-detector.d.ts +3 -0
- package/dist/scanner/detectors/config-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/config-detector.js +248 -0
- package/dist/scanner/detectors/framework-detector.d.ts +3 -0
- package/dist/scanner/detectors/framework-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/framework-detector.js +141 -0
- package/dist/scanner/detectors/index.d.ts +8 -0
- package/dist/scanner/detectors/index.d.ts.map +1 -0
- package/dist/scanner/detectors/index.js +37 -0
- package/dist/scanner/detectors/local-inference-detector.d.ts +3 -0
- package/dist/scanner/detectors/local-inference-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/local-inference-detector.js +93 -0
- package/dist/scanner/detectors/markdown-detector.d.ts +3 -0
- package/dist/scanner/detectors/markdown-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/markdown-detector.js +139 -0
- package/dist/scanner/detectors/package-json-detector.d.ts +3 -0
- package/dist/scanner/detectors/package-json-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/package-json-detector.js +166 -0
- package/dist/scanner/detectors/rag-detector.d.ts +3 -0
- package/dist/scanner/detectors/rag-detector.d.ts.map +1 -0
- package/dist/scanner/detectors/rag-detector.js +132 -0
- package/dist/scanner/detectors/shared.d.ts +18 -0
- package/dist/scanner/detectors/shared.d.ts.map +1 -0
- package/dist/scanner/detectors/shared.js +36 -0
- package/dist/scanner/discovery.d.ts +14 -0
- package/dist/scanner/discovery.d.ts.map +1 -0
- package/dist/scanner/discovery.js +235 -0
- package/dist/scanner/index.d.ts +9 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +8 -0
- package/package.json +6 -3
- package/src/evidence/bundle.ts +65 -0
- package/src/evidence/compliance-evaluation.ts +114 -0
- package/src/evidence/hash.ts +5 -0
- package/src/evidence/index.ts +51 -0
- package/src/evidence/markdown.ts +226 -0
- package/src/evidence/output.ts +717 -0
- package/src/evidence/types.ts +329 -0
- package/src/evidence/validate.ts +270 -0
- package/src/index.ts +25 -0
- package/src/inference/ollama-client.ts +93 -86
- package/src/legal-checks/annex-iv-report.ts +4 -0
- package/src/legal-checks/deadline-timeline.ts +313 -0
- package/src/legal-checks/gap-analysis.ts +454 -0
- package/src/legal-checks/gpai-classifier.ts +281 -0
- package/src/legal-checks/index.ts +33 -0
- package/src/scanner/aggregator.ts +258 -0
- package/src/scanner/detectors/ai-framework-detector.ts +94 -0
- package/src/scanner/detectors/ai-provider-detector.ts +135 -0
- package/src/scanner/detectors/compliance-detector.ts +232 -0
- package/src/scanner/detectors/config-detector.ts +316 -0
- package/src/scanner/detectors/framework-detector.ts +163 -0
- package/src/scanner/detectors/index.ts +40 -0
- package/src/scanner/detectors/local-inference-detector.ts +124 -0
- package/src/scanner/detectors/markdown-detector.ts +176 -0
- package/src/scanner/detectors/package-json-detector.ts +202 -0
- package/src/scanner/detectors/rag-detector.ts +162 -0
- package/src/scanner/detectors/shared.ts +45 -0
- package/src/scanner/discovery.ts +290 -0
- package/src/scanner/index.ts +9 -0
- package/tests/evidence/bundle.test.ts +128 -0
- package/tests/evidence/ecef-schemas.test.ts +316 -0
- package/tests/evidence/hash.test.ts +25 -0
- package/tests/evidence/markdown.test.ts +92 -0
- package/tests/evidence/output.test.ts +203 -0
- package/tests/fixtures/compliance-good/README.md +14 -0
- package/tests/fixtures/compliance-good/lib/ai-service.ts +17 -0
- package/tests/fixtures/compliance-good/lib/review-flow.ts +11 -0
- package/tests/fixtures/compliance-good/package.json +10 -0
- package/tests/fixtures/compliance-good/tests/bias-check.test.ts +13 -0
- package/tests/fixtures/nextjs-openai/README.md +5 -0
- package/tests/fixtures/nextjs-openai/app/page.tsx +11 -0
- package/tests/fixtures/nextjs-openai/package.json +9 -0
- package/tests/fixtures/ollama-rag/.env.example +2 -0
- package/tests/fixtures/ollama-rag/docker-compose.yml +9 -0
- package/tests/fixtures/ollama-rag/package.json +9 -0
- package/tests/fixtures/ollama-rag/server.ts +21 -0
- package/tests/fixtures/plain-webapp/README.md +3 -0
- package/tests/fixtures/plain-webapp/index.js +5 -0
- package/tests/fixtures/plain-webapp/package.json +8 -0
- package/tests/gpai-classifier.test.ts +125 -0
- package/tests/gpai-timeline.test.ts +55 -0
- package/tests/scanner/aggregator.test.ts +122 -0
- package/tests/scanner/detectors.test.ts +274 -0
- package/tests/scanner/discovery.test.ts +87 -0
- package/vitest.config.ts +1 -0
- package/.turbo/turbo-check-types.log +0 -4
- package/.turbo/turbo-test.log +0 -364
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -3282
- package/coverage/coverage-final.json +0 -20
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -161
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/fairness-metrics.ts.html +0 -1579
- package/coverage/src/index.html +0 -161
- package/coverage/src/index.ts.html +0 -535
- package/coverage/src/inference/browser-inference.ts.html +0 -859
- package/coverage/src/inference/capability-detection.ts.html +0 -2227
- package/coverage/src/inference/index.html +0 -176
- package/coverage/src/inference/index.ts.html +0 -553
- package/coverage/src/inference/inference-factory.ts.html +0 -751
- package/coverage/src/inference/ollama-client.ts.html +0 -1627
- package/coverage/src/legal-checks/annex-iv-report.ts.html +0 -556
- package/coverage/src/legal-checks/bias-metrics.ts.html +0 -1855
- package/coverage/src/legal-checks/data-governance.ts.html +0 -529
- package/coverage/src/legal-checks/human-oversight-logging.ts.html +0 -490
- package/coverage/src/legal-checks/index.html +0 -206
- package/coverage/src/legal-checks/index.ts.html +0 -250
- package/coverage/src/legal-checks/risk-classifier.ts.html +0 -940
- package/coverage/src/legal-checks/robustness-transparency.ts.html +0 -571
- package/coverage/src/metrics/crows-pairs-bias.ts.html +0 -820
- package/coverage/src/metrics/index.html +0 -146
- package/coverage/src/metrics/index.ts.html +0 -151
- package/coverage/src/metrics/stereotype-bias.ts.html +0 -886
- package/coverage/src/risk-engine.ts.html +0 -1660
- package/coverage/src/types.ts.html +0 -547
- package/dist/legal-checks/disclaimer.d.ts +0 -2
- package/dist/legal-checks/disclaimer.d.ts.map +0 -1
- package/dist/legal-checks/disclaimer.js +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { disparateImpact, statisticalParityDifference } from "@euconform/core";
|
|
2
|
+
|
|
3
|
+
describe("Bias Testing", () => {
|
|
4
|
+
it("should check disparate impact", () => {
|
|
5
|
+
const result = disparateImpact([]);
|
|
6
|
+
expect(result).toBeDefined();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("should check statistical parity", () => {
|
|
10
|
+
const result = statisticalParityDifference([]);
|
|
11
|
+
expect(result).toBeDefined();
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
|
|
3
|
+
const client = new OpenAI();
|
|
4
|
+
|
|
5
|
+
export default async function Page() {
|
|
6
|
+
const response = await client.chat.completions.create({
|
|
7
|
+
model: "gpt-4",
|
|
8
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
9
|
+
});
|
|
10
|
+
return <div>AI Response: {response.choices[0]?.message?.content}</div>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ChromaClient } from "chromadb";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { Ollama } from "ollama";
|
|
4
|
+
|
|
5
|
+
const app = express();
|
|
6
|
+
const ollama = new Ollama({ host: "http://localhost:11434" });
|
|
7
|
+
const chroma = new ChromaClient();
|
|
8
|
+
|
|
9
|
+
app.post("/chat", async (req, res) => {
|
|
10
|
+
const collection = await chroma.getCollection({ name: "docs" });
|
|
11
|
+
const results = await collection.query({ queryTexts: [req.body.query] });
|
|
12
|
+
const context = results.documents.flat().join("\n");
|
|
13
|
+
|
|
14
|
+
const response = await ollama.chat({
|
|
15
|
+
model: "llama3.2",
|
|
16
|
+
messages: [{ role: "user", content: `Context: ${context}\n\nQuestion: ${req.body.query}` }],
|
|
17
|
+
});
|
|
18
|
+
res.json({ answer: response.message.content });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
app.listen(3000);
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { classifyGPAICompliance, isSystemicRiskModel } from "../src/legal-checks/gpai-classifier";
|
|
3
|
+
import type { QuizAnswer } from "../src/types";
|
|
4
|
+
|
|
5
|
+
/** Helper: build a full set of GPAI answers with defaults, then override specific ones */
|
|
6
|
+
function buildAnswers(overrides: Record<string, string> = {}): QuizAnswer[] {
|
|
7
|
+
const defaults: Record<string, string> = {
|
|
8
|
+
"gpai-technical-docs": "yes",
|
|
9
|
+
"gpai-downstream-info": "yes",
|
|
10
|
+
"gpai-copyright-policy": "yes",
|
|
11
|
+
"gpai-eu-database": "yes",
|
|
12
|
+
"gpai-open-source": "no",
|
|
13
|
+
"gpai-systemic-risk": "no",
|
|
14
|
+
"gpai-red-teaming": "yes",
|
|
15
|
+
"gpai-incident-reporting": "yes",
|
|
16
|
+
"gpai-cybersecurity": "yes",
|
|
17
|
+
};
|
|
18
|
+
const merged = { ...defaults, ...overrides };
|
|
19
|
+
return Object.entries(merged).map(([questionId, value]) => ({
|
|
20
|
+
questionId,
|
|
21
|
+
value,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("classifyGPAICompliance", () => {
|
|
26
|
+
describe("open-source path (Art. 53(2))", () => {
|
|
27
|
+
it("flags copyright-policy as missing when open-source answers 'no'", () => {
|
|
28
|
+
const answers = buildAnswers({
|
|
29
|
+
"gpai-open-source": "yes",
|
|
30
|
+
"gpai-copyright-policy": "no",
|
|
31
|
+
});
|
|
32
|
+
const result = classifyGPAICompliance(answers);
|
|
33
|
+
const flag = result.flags.find((f) => f.obligation === "copyright-policy");
|
|
34
|
+
expect(flag).toBeDefined();
|
|
35
|
+
expect(flag?.status).toBe("missing");
|
|
36
|
+
expect(flag?.articleRef).toBe("Art. 53(1)(d)");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("flags copyright-policy as partial when open-source answers 'unsure'", () => {
|
|
40
|
+
const answers = buildAnswers({
|
|
41
|
+
"gpai-open-source": "yes",
|
|
42
|
+
"gpai-copyright-policy": "unsure",
|
|
43
|
+
});
|
|
44
|
+
const result = classifyGPAICompliance(answers);
|
|
45
|
+
const flag = result.flags.find((f) => f.obligation === "copyright-policy");
|
|
46
|
+
expect(flag).toBeDefined();
|
|
47
|
+
expect(flag?.status).toBe("partial");
|
|
48
|
+
expect(flag?.articleRef).toBe("Art. 53(1)(d)");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("does NOT flag copyright-policy when open-source answers 'yes'", () => {
|
|
52
|
+
const answers = buildAnswers({
|
|
53
|
+
"gpai-open-source": "yes",
|
|
54
|
+
"gpai-copyright-policy": "yes",
|
|
55
|
+
});
|
|
56
|
+
const result = classifyGPAICompliance(answers);
|
|
57
|
+
const flag = result.flags.find((f) => f.obligation === "copyright-policy");
|
|
58
|
+
expect(flag).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("exempts technical-docs and downstream-info for open-source", () => {
|
|
62
|
+
const answers = buildAnswers({
|
|
63
|
+
"gpai-open-source": "yes",
|
|
64
|
+
"gpai-technical-docs": "no",
|
|
65
|
+
"gpai-downstream-info": "no",
|
|
66
|
+
});
|
|
67
|
+
const result = classifyGPAICompliance(answers);
|
|
68
|
+
expect(result.flags.find((f) => f.obligation === "technical-docs")).toBeUndefined();
|
|
69
|
+
expect(result.flags.find((f) => f.obligation === "downstream-info")).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("includes explanatory note for open-source copyright gap", () => {
|
|
73
|
+
const answers = buildAnswers({
|
|
74
|
+
"gpai-open-source": "yes",
|
|
75
|
+
"gpai-copyright-policy": "unsure",
|
|
76
|
+
});
|
|
77
|
+
const result = classifyGPAICompliance(answers);
|
|
78
|
+
expect(result.notes.some((n) => n.includes("Art. 53(1)(d)"))).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("non-open-source path", () => {
|
|
83
|
+
it("flags all three Art. 53(1) obligations when missing", () => {
|
|
84
|
+
const answers = buildAnswers({
|
|
85
|
+
"gpai-technical-docs": "no",
|
|
86
|
+
"gpai-downstream-info": "no",
|
|
87
|
+
"gpai-copyright-policy": "no",
|
|
88
|
+
"gpai-eu-database": "no",
|
|
89
|
+
});
|
|
90
|
+
const result = classifyGPAICompliance(answers);
|
|
91
|
+
expect(result.level).toBe("non-compliant");
|
|
92
|
+
expect(result.flags.length).toBeGreaterThanOrEqual(4);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("systemic risk (Art. 51)", () => {
|
|
97
|
+
it("detects systemic risk model", () => {
|
|
98
|
+
const answers = buildAnswers({ "gpai-systemic-risk": "yes" });
|
|
99
|
+
expect(isSystemicRiskModel(answers)).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("flags Art. 55 obligations when systemic risk and missing", () => {
|
|
103
|
+
const answers = buildAnswers({
|
|
104
|
+
"gpai-systemic-risk": "yes",
|
|
105
|
+
"gpai-red-teaming": "no",
|
|
106
|
+
"gpai-incident-reporting": "no",
|
|
107
|
+
"gpai-cybersecurity": "no",
|
|
108
|
+
});
|
|
109
|
+
const result = classifyGPAICompliance(answers);
|
|
110
|
+
expect(result.isSystemicRisk).toBe(true);
|
|
111
|
+
expect(result.flags.find((f) => f.obligation === "red-teaming")).toBeDefined();
|
|
112
|
+
expect(result.flags.find((f) => f.obligation === "incident-reporting")).toBeDefined();
|
|
113
|
+
expect(result.flags.find((f) => f.obligation === "cybersecurity")).toBeDefined();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("fully compliant", () => {
|
|
118
|
+
it("returns compliant with no flags when all obligations met", () => {
|
|
119
|
+
const answers = buildAnswers();
|
|
120
|
+
const result = classifyGPAICompliance(answers);
|
|
121
|
+
expect(result.level).toBe("compliant");
|
|
122
|
+
expect(result.flags).toHaveLength(0);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getTimelineForRiskLevel } from "../src/legal-checks/deadline-timeline";
|
|
3
|
+
|
|
4
|
+
// This function will be implemented in Step 3
|
|
5
|
+
import { getTimelineForAudiences } from "../src/legal-checks/deadline-timeline";
|
|
6
|
+
|
|
7
|
+
describe("getTimelineForAudiences", () => {
|
|
8
|
+
it("returns GPAI-relevant phases for ['all', 'gpai'] audiences", () => {
|
|
9
|
+
const phases = getTimelineForAudiences(["all", "gpai"]);
|
|
10
|
+
const phaseIds = phases.map((p) => p.id);
|
|
11
|
+
expect(phaseIds).toContain("phase-2-gpai");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("includes Art. 53 documentation obligation for GPAI audience", () => {
|
|
15
|
+
const phases = getTimelineForAudiences(["all", "gpai"]);
|
|
16
|
+
const phase2 = phases.find((p) => p.id === "phase-2-gpai");
|
|
17
|
+
expect(phase2).toBeDefined();
|
|
18
|
+
const gpaiObligation = phase2?.obligations.find((o) => o.articleRef === "Art. 53");
|
|
19
|
+
expect(gpaiObligation).toBeDefined();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("includes Art. 55 systemic risk obligation for GPAI audience", () => {
|
|
23
|
+
const phases = getTimelineForAudiences(["all", "gpai"]);
|
|
24
|
+
const phase2 = phases.find((p) => p.id === "phase-2-gpai");
|
|
25
|
+
expect(phase2).toBeDefined();
|
|
26
|
+
const systemicObligation = phase2?.obligations.find((o) => o.articleRef === "Art. 55");
|
|
27
|
+
expect(systemicObligation).toBeDefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("does NOT include high-risk Annex III obligations for GPAI audience", () => {
|
|
31
|
+
const phases = getTimelineForAudiences(["all", "gpai"]);
|
|
32
|
+
const phase3 = phases.find((p) => p.id === "phase-3-high-risk-annex-iii");
|
|
33
|
+
if (phase3) {
|
|
34
|
+
const hasAllAudience = phase3.obligations.some((o) =>
|
|
35
|
+
o.audiences.some((a) => ["all", "gpai"].includes(a))
|
|
36
|
+
);
|
|
37
|
+
expect(hasAllAudience).toBe(true);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("minimal risk level does NOT include GPAI obligations (regression)", () => {
|
|
42
|
+
const phases = getTimelineForRiskLevel("minimal");
|
|
43
|
+
const phase2 = phases.find((p) => p.id === "phase-2-gpai");
|
|
44
|
+
if (phase2) {
|
|
45
|
+
const gpaiOnly = phase2.obligations.filter(
|
|
46
|
+
(o) => o.audiences.includes("gpai") && !o.audiences.includes("all")
|
|
47
|
+
);
|
|
48
|
+
const minimalAudiences = ["all", "minimal"];
|
|
49
|
+
for (const obl of gpaiOnly) {
|
|
50
|
+
const matches = obl.audiences.some((a) => minimalAudiences.includes(a));
|
|
51
|
+
expect(matches).toBe(false);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { scanRepository } from "../../src/scanner/aggregator";
|
|
4
|
+
|
|
5
|
+
const FIXTURES = resolve(__dirname, "../fixtures");
|
|
6
|
+
|
|
7
|
+
describe("scanRepository", () => {
|
|
8
|
+
it("scans nextjs-openai fixture", async () => {
|
|
9
|
+
const result = await scanRepository({
|
|
10
|
+
targetPath: resolve(FIXTURES, "nextjs-openai"),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const signalIds = result.signals.map((s) => s.id);
|
|
14
|
+
|
|
15
|
+
// Should detect Next.js framework
|
|
16
|
+
expect(signalIds.some((id) => id.includes("nextjs") || id.includes("next"))).toBe(true);
|
|
17
|
+
|
|
18
|
+
// Should detect OpenAI provider
|
|
19
|
+
expect(signalIds.some((id) => id.includes("openai"))).toBe(true);
|
|
20
|
+
|
|
21
|
+
// Should detect AI disclosure from README
|
|
22
|
+
expect(result.signals.some((s) => s.category === "compliance-disclosure")).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("scans ollama-rag fixture", async () => {
|
|
26
|
+
const result = await scanRepository({
|
|
27
|
+
targetPath: resolve(FIXTURES, "ollama-rag"),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const signalIds = result.signals.map((s) => s.id);
|
|
31
|
+
const categories = result.signals.map((s) => s.category);
|
|
32
|
+
|
|
33
|
+
// Should detect Express framework
|
|
34
|
+
expect(signalIds.some((id) => id.includes("express"))).toBe(true);
|
|
35
|
+
|
|
36
|
+
// Should detect Ollama (local inference)
|
|
37
|
+
expect(categories).toContain("local-inference");
|
|
38
|
+
|
|
39
|
+
// Should detect ChromaDB (RAG)
|
|
40
|
+
expect(categories).toContain("rag");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("scans plain-webapp fixture", async () => {
|
|
44
|
+
const result = await scanRepository({
|
|
45
|
+
targetPath: resolve(FIXTURES, "plain-webapp"),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Should NOT detect any AI signals
|
|
49
|
+
const aiSignals = result.signals.filter(
|
|
50
|
+
(s) =>
|
|
51
|
+
s.category === "ai-provider" ||
|
|
52
|
+
s.category === "ai-framework" ||
|
|
53
|
+
s.category === "local-inference" ||
|
|
54
|
+
s.category === "ai-model"
|
|
55
|
+
);
|
|
56
|
+
expect(aiSignals).toHaveLength(0);
|
|
57
|
+
|
|
58
|
+
// Should have minimal signals (framework at most)
|
|
59
|
+
expect(result.signals.length).toBeLessThan(10);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("scans compliance-good fixture", async () => {
|
|
63
|
+
const result = await scanRepository({
|
|
64
|
+
targetPath: resolve(FIXTURES, "compliance-good"),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const categories = result.signals.map((s) => s.category);
|
|
68
|
+
|
|
69
|
+
// Should detect compliance signals for disclosure, logging, bias
|
|
70
|
+
expect(categories).toContain("compliance-logging");
|
|
71
|
+
// OpenAI is an AI provider
|
|
72
|
+
expect(categories).toContain("ai-provider");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("generates open questions when AI is detected", async () => {
|
|
76
|
+
const result = await scanRepository({
|
|
77
|
+
targetPath: resolve(FIXTURES, "nextjs-openai"),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(result.openQuestions.length).toBeGreaterThan(0);
|
|
81
|
+
|
|
82
|
+
// Should include the market placement question
|
|
83
|
+
const marketQuestion = result.openQuestions.find((q) => q.id === "oq-market-placement");
|
|
84
|
+
expect(marketQuestion).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("generates signal summary", async () => {
|
|
88
|
+
const result = await scanRepository({
|
|
89
|
+
targetPath: resolve(FIXTURES, "nextjs-openai"),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(result.signalSummary.total).toBe(result.signals.length);
|
|
93
|
+
expect(result.signalSummary.total).toBeGreaterThan(0);
|
|
94
|
+
|
|
95
|
+
// byConfidence should have correct totals
|
|
96
|
+
const confidenceTotal =
|
|
97
|
+
result.signalSummary.byConfidence.high +
|
|
98
|
+
result.signalSummary.byConfidence.medium +
|
|
99
|
+
result.signalSummary.byConfidence.low;
|
|
100
|
+
expect(confidenceTotal).toBe(result.signalSummary.total);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("deduplicates signals", async () => {
|
|
104
|
+
const result = await scanRepository({
|
|
105
|
+
targetPath: resolve(FIXTURES, "nextjs-openai"),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Check that signal IDs are unique after deduplication
|
|
109
|
+
const ids = result.signals.map((s) => s.id);
|
|
110
|
+
const uniqueIds = new Set(ids);
|
|
111
|
+
expect(ids.length).toBe(uniqueIds.size);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("records scan scope metadata", async () => {
|
|
115
|
+
const result = await scanRepository({
|
|
116
|
+
targetPath: resolve(FIXTURES, "nextjs-openai"),
|
|
117
|
+
scope: "all",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(result.meta.scanScope).toBe("all");
|
|
121
|
+
});
|
|
122
|
+
});
|