qa360 1.0.4 → 1.1.1
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/dist/commands/history.js +1 -1
- package/dist/commands/pack.js +1 -1
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +1 -1
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/serve.js +1 -1
- package/dist/commands/verify.js +1 -1
- package/dist/core/adapters/gitleaks-secrets.d.ts +115 -0
- package/dist/core/adapters/gitleaks-secrets.d.ts.map +1 -0
- package/dist/core/adapters/gitleaks-secrets.js +410 -0
- package/dist/core/adapters/k6-perf.d.ts +86 -0
- package/dist/core/adapters/k6-perf.d.ts.map +1 -0
- package/dist/core/adapters/k6-perf.js +398 -0
- package/dist/core/adapters/osv-deps.d.ts +124 -0
- package/dist/core/adapters/osv-deps.d.ts.map +1 -0
- package/dist/core/adapters/osv-deps.js +372 -0
- package/dist/core/adapters/playwright-api.d.ts +82 -0
- package/dist/core/adapters/playwright-api.d.ts.map +1 -0
- package/dist/core/adapters/playwright-api.js +252 -0
- package/dist/core/adapters/playwright-ui.d.ts +115 -0
- package/dist/core/adapters/playwright-ui.d.ts.map +1 -0
- package/dist/core/adapters/playwright-ui.js +346 -0
- package/dist/core/adapters/semgrep-sast.d.ts +100 -0
- package/dist/core/adapters/semgrep-sast.d.ts.map +1 -0
- package/dist/core/adapters/semgrep-sast.js +322 -0
- package/dist/core/adapters/zap-dast.d.ts +134 -0
- package/dist/core/adapters/zap-dast.d.ts.map +1 -0
- package/dist/core/adapters/zap-dast.js +424 -0
- package/dist/core/hooks/compose.d.ts +62 -0
- package/dist/core/hooks/compose.d.ts.map +1 -0
- package/dist/core/hooks/compose.js +225 -0
- package/dist/core/hooks/runner.d.ts +69 -0
- package/dist/core/hooks/runner.d.ts.map +1 -0
- package/dist/core/hooks/runner.js +303 -0
- package/dist/core/index.d.ts +74 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +39 -0
- package/dist/core/pack/migrator.d.ts +52 -0
- package/dist/core/pack/migrator.d.ts.map +1 -0
- package/dist/core/pack/migrator.js +304 -0
- package/dist/core/pack/validator.d.ts +43 -0
- package/dist/core/pack/validator.d.ts.map +1 -0
- package/dist/core/pack/validator.js +292 -0
- package/dist/core/proof/bundle.d.ts +138 -0
- package/dist/core/proof/bundle.d.ts.map +1 -0
- package/dist/core/proof/bundle.js +160 -0
- package/dist/core/proof/canonicalize.d.ts +48 -0
- package/dist/core/proof/canonicalize.d.ts.map +1 -0
- package/dist/core/proof/canonicalize.js +105 -0
- package/dist/core/proof/index.d.ts +14 -0
- package/dist/core/proof/index.d.ts.map +1 -0
- package/dist/core/proof/index.js +18 -0
- package/dist/core/proof/schema.d.ts +218 -0
- package/dist/core/proof/schema.d.ts.map +1 -0
- package/dist/core/proof/schema.js +263 -0
- package/dist/core/proof/signer.d.ts +112 -0
- package/dist/core/proof/signer.d.ts.map +1 -0
- package/dist/core/proof/signer.js +226 -0
- package/dist/core/proof/verifier.d.ts +98 -0
- package/dist/core/proof/verifier.d.ts.map +1 -0
- package/dist/core/proof/verifier.js +302 -0
- package/dist/core/runner/phase3-runner.d.ts +102 -0
- package/dist/core/runner/phase3-runner.d.ts.map +1 -0
- package/dist/core/runner/phase3-runner.js +471 -0
- package/dist/core/secrets/crypto.d.ts +76 -0
- package/dist/core/secrets/crypto.d.ts.map +1 -0
- package/dist/core/secrets/crypto.js +225 -0
- package/dist/core/secrets/manager.d.ts +77 -0
- package/dist/core/secrets/manager.d.ts.map +1 -0
- package/dist/core/secrets/manager.js +219 -0
- package/dist/core/security/redaction-patterns-extended.d.ts +28 -0
- package/dist/core/security/redaction-patterns-extended.d.ts.map +1 -0
- package/dist/core/security/redaction-patterns-extended.js +247 -0
- package/dist/core/security/redactor.d.ts +72 -0
- package/dist/core/security/redactor.d.ts.map +1 -0
- package/dist/core/security/redactor.js +279 -0
- package/dist/core/serve/diagnostics-collector.d.ts +33 -0
- package/dist/core/serve/diagnostics-collector.d.ts.map +1 -0
- package/dist/core/serve/diagnostics-collector.js +149 -0
- package/dist/core/serve/health-checker.d.ts +45 -0
- package/dist/core/serve/health-checker.d.ts.map +1 -0
- package/dist/core/serve/health-checker.js +219 -0
- package/dist/core/serve/index.d.ts +9 -0
- package/dist/core/serve/index.d.ts.map +1 -0
- package/dist/core/serve/index.js +8 -0
- package/dist/core/serve/metrics-collector.d.ts +25 -0
- package/dist/core/serve/metrics-collector.d.ts.map +1 -0
- package/dist/core/serve/metrics-collector.js +322 -0
- package/dist/core/serve/process-manager.d.ts +37 -0
- package/dist/core/serve/process-manager.d.ts.map +1 -0
- package/dist/core/serve/process-manager.js +213 -0
- package/dist/core/serve/server.d.ts +37 -0
- package/dist/core/serve/server.d.ts.map +1 -0
- package/dist/core/serve/server.js +191 -0
- package/dist/core/types/pack-v1.d.ts +162 -0
- package/dist/core/types/pack-v1.d.ts.map +1 -0
- package/dist/core/types/pack-v1.js +5 -0
- package/dist/core/types/trust-score.d.ts +70 -0
- package/dist/core/types/trust-score.d.ts.map +1 -0
- package/dist/core/types/trust-score.js +191 -0
- package/dist/core/vault/cas.d.ts +87 -0
- package/dist/core/vault/cas.d.ts.map +1 -0
- package/dist/core/vault/cas.js +255 -0
- package/dist/core/vault/index.d.ts +205 -0
- package/dist/core/vault/index.d.ts.map +1 -0
- package/dist/core/vault/index.js +631 -0
- package/package.json +13 -6
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Core Observability Server
|
|
3
|
+
* Endpoints: /health, /diag, /metrics, /cancel
|
|
4
|
+
*/
|
|
5
|
+
import { createServer } from 'http';
|
|
6
|
+
import { URL } from 'url';
|
|
7
|
+
import { SecurityRedactor } from '../security/redactor.js';
|
|
8
|
+
import { HealthChecker } from './health-checker.js';
|
|
9
|
+
import { DiagnosticsCollector } from './diagnostics-collector.js';
|
|
10
|
+
import { MetricsCollector } from './metrics-collector.js';
|
|
11
|
+
import { ProcessManager } from './process-manager.js';
|
|
12
|
+
export class QA360Server {
|
|
13
|
+
config;
|
|
14
|
+
server;
|
|
15
|
+
healthChecker;
|
|
16
|
+
diagnosticsCollector;
|
|
17
|
+
metricsCollector;
|
|
18
|
+
processManager;
|
|
19
|
+
redactor;
|
|
20
|
+
startTime;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.healthChecker = new HealthChecker();
|
|
24
|
+
this.diagnosticsCollector = new DiagnosticsCollector();
|
|
25
|
+
this.metricsCollector = new MetricsCollector();
|
|
26
|
+
this.processManager = new ProcessManager();
|
|
27
|
+
this.redactor = SecurityRedactor.forLogs();
|
|
28
|
+
this.startTime = Date.now();
|
|
29
|
+
}
|
|
30
|
+
async start() {
|
|
31
|
+
if (this.server)
|
|
32
|
+
return;
|
|
33
|
+
await new Promise((resolve, reject) => {
|
|
34
|
+
const srv = createServer(this.handleRequest.bind(this));
|
|
35
|
+
srv.on('error', (err) => {
|
|
36
|
+
if (err?.code === 'EADDRINUSE') {
|
|
37
|
+
this.log('error', 'Port already in use', { port: this.config.port, code: 'HLT003' });
|
|
38
|
+
reject(Object.assign(new Error('Port already in use'), { code: 'HLT003' }));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
reject(err);
|
|
42
|
+
});
|
|
43
|
+
srv.listen(this.config.port, this.config.host ?? '127.0.0.1', () => {
|
|
44
|
+
this.server = srv;
|
|
45
|
+
this.log('info', 'QA360 Server started', {
|
|
46
|
+
port: this.config.port,
|
|
47
|
+
host: this.config.host || '127.0.0.1',
|
|
48
|
+
metrics: this.config.metrics ?? true
|
|
49
|
+
});
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async stop() {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
if (this.server) {
|
|
57
|
+
this.server.close(() => {
|
|
58
|
+
this.log('info', 'QA360 Server stopped');
|
|
59
|
+
resolve();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
resolve();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async handleRequest(req, res) {
|
|
68
|
+
const url = new URL(req.url || '/', `http://${req.headers.host}`);
|
|
69
|
+
const method = req.method?.toUpperCase() || 'GET';
|
|
70
|
+
try {
|
|
71
|
+
const response = await this.routeRequest(method, url.pathname, req);
|
|
72
|
+
// Set headers
|
|
73
|
+
Object.entries(response.headers).forEach(([key, value]) => {
|
|
74
|
+
res.setHeader(key, value);
|
|
75
|
+
});
|
|
76
|
+
res.statusCode = response.status;
|
|
77
|
+
res.end(response.body);
|
|
78
|
+
this.log('info', 'Request handled', {
|
|
79
|
+
method,
|
|
80
|
+
path: url.pathname,
|
|
81
|
+
status: response.status
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
86
|
+
this.log('error', 'Request failed', { method, path: url.pathname, error: errorMessage });
|
|
87
|
+
res.statusCode = 500;
|
|
88
|
+
res.setHeader('Content-Type', 'application/json');
|
|
89
|
+
res.end(JSON.stringify({
|
|
90
|
+
error: 'Internal Server Error',
|
|
91
|
+
code: 'SRV001',
|
|
92
|
+
message: errorMessage
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async routeRequest(method, path, req) {
|
|
97
|
+
// Health endpoint
|
|
98
|
+
if (method === 'GET' && path === '/health') {
|
|
99
|
+
return await this.handleHealth();
|
|
100
|
+
}
|
|
101
|
+
// Diagnostics endpoint
|
|
102
|
+
if (method === 'GET' && path === '/diag') {
|
|
103
|
+
return await this.handleDiagnostics();
|
|
104
|
+
}
|
|
105
|
+
// Metrics endpoint
|
|
106
|
+
if (method === 'GET' && path === '/metrics') {
|
|
107
|
+
return await this.handleMetrics();
|
|
108
|
+
}
|
|
109
|
+
// Cancel endpoint
|
|
110
|
+
if (method === 'POST' && path.startsWith('/cancel/')) {
|
|
111
|
+
const runId = path.split('/')[2];
|
|
112
|
+
return await this.handleCancel(runId, req);
|
|
113
|
+
}
|
|
114
|
+
// 404 Not Found
|
|
115
|
+
return {
|
|
116
|
+
status: 404,
|
|
117
|
+
headers: { 'Content-Type': 'application/json' },
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
error: 'Not Found',
|
|
120
|
+
code: 'SRV404',
|
|
121
|
+
message: `Endpoint ${method} ${path} not found`
|
|
122
|
+
})
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async handleHealth() {
|
|
126
|
+
const health = await this.healthChecker.check();
|
|
127
|
+
const status = health.status === 'ok' ? 200 : health.status === 'degraded' ? 200 : 503;
|
|
128
|
+
return {
|
|
129
|
+
status,
|
|
130
|
+
headers: { 'Content-Type': 'application/json' },
|
|
131
|
+
body: JSON.stringify({
|
|
132
|
+
...health,
|
|
133
|
+
uptime_s: Math.floor((Date.now() - this.startTime) / 1000)
|
|
134
|
+
}, null, 2)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async handleDiagnostics() {
|
|
138
|
+
const diagnostics = await this.diagnosticsCollector.collect();
|
|
139
|
+
return {
|
|
140
|
+
status: 200,
|
|
141
|
+
headers: { 'Content-Type': 'application/json' },
|
|
142
|
+
body: JSON.stringify(diagnostics, null, 2)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async handleMetrics() {
|
|
146
|
+
if (!this.config.metrics) {
|
|
147
|
+
return {
|
|
148
|
+
status: 404,
|
|
149
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
150
|
+
body: 'Metrics disabled'
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const metrics = await this.metricsCollector.collect();
|
|
154
|
+
return {
|
|
155
|
+
status: 200,
|
|
156
|
+
headers: { 'Content-Type': 'text/plain; version=0.0.4' },
|
|
157
|
+
body: metrics
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async handleCancel(runId, req) {
|
|
161
|
+
if (!runId) {
|
|
162
|
+
return {
|
|
163
|
+
status: 400,
|
|
164
|
+
headers: { 'Content-Type': 'application/json' },
|
|
165
|
+
body: JSON.stringify({
|
|
166
|
+
error: 'Bad Request',
|
|
167
|
+
code: 'SRV400',
|
|
168
|
+
message: 'runId is required'
|
|
169
|
+
})
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
const result = await this.processManager.cancelRun(runId);
|
|
173
|
+
return {
|
|
174
|
+
status: result.cancelled ? 200 : 404,
|
|
175
|
+
headers: { 'Content-Type': 'application/json' },
|
|
176
|
+
body: JSON.stringify(result)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
log(level, message, meta = {}) {
|
|
180
|
+
if (!this.config.verbose && level === 'info')
|
|
181
|
+
return;
|
|
182
|
+
const logEntry = {
|
|
183
|
+
timestamp: new Date().toISOString(),
|
|
184
|
+
level,
|
|
185
|
+
message,
|
|
186
|
+
service: 'qa360-server',
|
|
187
|
+
...(meta ? (this.redactor ? this.redactor.redactObject(meta) : meta) : {})
|
|
188
|
+
};
|
|
189
|
+
console.log(JSON.stringify(logEntry));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Pack v1 TypeScript Definitions
|
|
3
|
+
* Generated from pack.schema.json
|
|
4
|
+
*/
|
|
5
|
+
export type QualityGate = 'api_smoke' | 'ui' | 'perf' | 'sast' | 'dast' | 'a11y';
|
|
6
|
+
export type DataProfile = 'demo' | 'staging' | 'production';
|
|
7
|
+
export type RetryPattern = 'ECONNRESET' | 'ETIMEDOUT' | '502' | '503' | '504' | 'element-detached' | 'navigation-timeout';
|
|
8
|
+
export type TraceLevel = 'none' | 'basic' | 'detailed';
|
|
9
|
+
export interface ApiTarget {
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
smoke?: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface WebTarget {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
pages?: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface PackTargets {
|
|
18
|
+
api?: ApiTarget;
|
|
19
|
+
web?: WebTarget;
|
|
20
|
+
}
|
|
21
|
+
export interface PackBudgets {
|
|
22
|
+
perf_p95_ms?: number;
|
|
23
|
+
a11y_min?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface PackSecurity {
|
|
26
|
+
sast?: {
|
|
27
|
+
max_high?: number;
|
|
28
|
+
max_critical?: number;
|
|
29
|
+
max_medium?: number;
|
|
30
|
+
};
|
|
31
|
+
secrets?: {
|
|
32
|
+
max_findings?: number;
|
|
33
|
+
};
|
|
34
|
+
dast?: {
|
|
35
|
+
max_high?: number;
|
|
36
|
+
max_critical?: number;
|
|
37
|
+
max_medium?: number;
|
|
38
|
+
timeout_ms?: number;
|
|
39
|
+
};
|
|
40
|
+
deps?: {
|
|
41
|
+
max_high?: number;
|
|
42
|
+
max_critical?: number;
|
|
43
|
+
max_medium?: number;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export interface PackData {
|
|
47
|
+
profile?: DataProfile;
|
|
48
|
+
seed?: number;
|
|
49
|
+
}
|
|
50
|
+
export type HookAction = {
|
|
51
|
+
run: string;
|
|
52
|
+
timeout?: number;
|
|
53
|
+
} | {
|
|
54
|
+
compose: 'up' | 'down';
|
|
55
|
+
timeout?: number;
|
|
56
|
+
} | {
|
|
57
|
+
wait_on: string;
|
|
58
|
+
timeout?: number;
|
|
59
|
+
method?: 'http' | 'tcp';
|
|
60
|
+
};
|
|
61
|
+
export interface PackHooks {
|
|
62
|
+
beforeAll?: HookAction[];
|
|
63
|
+
beforeEach?: HookAction[];
|
|
64
|
+
afterEach?: HookAction[];
|
|
65
|
+
afterAll?: HookAction[];
|
|
66
|
+
}
|
|
67
|
+
export interface PackExecution {
|
|
68
|
+
hook_timeout_ms?: number;
|
|
69
|
+
on_failure?: 'stop' | 'continue';
|
|
70
|
+
limits?: {
|
|
71
|
+
cpu?: string;
|
|
72
|
+
memory?: string;
|
|
73
|
+
};
|
|
74
|
+
compose_file?: string;
|
|
75
|
+
retry_on?: RetryPattern[];
|
|
76
|
+
max_retries?: number;
|
|
77
|
+
fail_on_readiness?: boolean;
|
|
78
|
+
timeout?: number;
|
|
79
|
+
}
|
|
80
|
+
export interface PackObservability {
|
|
81
|
+
metrics?: boolean;
|
|
82
|
+
trace?: TraceLevel;
|
|
83
|
+
}
|
|
84
|
+
export interface PackEnvironment {
|
|
85
|
+
[key: string]: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* QA360 Pack Configuration v1
|
|
89
|
+
* Complete type definition for pack.yml files
|
|
90
|
+
*/
|
|
91
|
+
export interface PackConfigV1 {
|
|
92
|
+
version: 1;
|
|
93
|
+
name: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
gates: QualityGate[];
|
|
96
|
+
targets?: PackTargets;
|
|
97
|
+
budgets?: PackBudgets;
|
|
98
|
+
security?: PackSecurity;
|
|
99
|
+
data?: PackData;
|
|
100
|
+
hooks?: PackHooks;
|
|
101
|
+
execution?: PackExecution;
|
|
102
|
+
observability?: PackObservability;
|
|
103
|
+
environment?: PackEnvironment;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Pack validation result
|
|
107
|
+
*/
|
|
108
|
+
export interface PackValidationResult {
|
|
109
|
+
valid: boolean;
|
|
110
|
+
errors: PackValidationError[];
|
|
111
|
+
warnings: PackValidationWarning[];
|
|
112
|
+
}
|
|
113
|
+
export interface PackValidationError {
|
|
114
|
+
code: string;
|
|
115
|
+
path: string;
|
|
116
|
+
message: string;
|
|
117
|
+
suggestion?: string;
|
|
118
|
+
}
|
|
119
|
+
export interface PackValidationWarning {
|
|
120
|
+
code: string;
|
|
121
|
+
path: string;
|
|
122
|
+
message: string;
|
|
123
|
+
suggestion?: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Pack migration result
|
|
127
|
+
*/
|
|
128
|
+
export interface PackMigrationResult {
|
|
129
|
+
success: boolean;
|
|
130
|
+
fromVersion: string;
|
|
131
|
+
toVersion: string;
|
|
132
|
+
changes: PackMigrationChange[];
|
|
133
|
+
warnings: string[];
|
|
134
|
+
}
|
|
135
|
+
export interface PackMigrationChange {
|
|
136
|
+
type: 'added' | 'modified' | 'removed' | 'renamed';
|
|
137
|
+
path: string;
|
|
138
|
+
oldValue?: any;
|
|
139
|
+
newValue?: any;
|
|
140
|
+
reason: string;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Legacy pack configuration (v0.9.x)
|
|
144
|
+
* For migration compatibility
|
|
145
|
+
*/
|
|
146
|
+
export interface LegacyPackConfig {
|
|
147
|
+
name: string;
|
|
148
|
+
version?: string;
|
|
149
|
+
description?: string;
|
|
150
|
+
adapters?: string[];
|
|
151
|
+
tests?: Array<{
|
|
152
|
+
name: string;
|
|
153
|
+
adapter: string;
|
|
154
|
+
config: any;
|
|
155
|
+
}>;
|
|
156
|
+
environment?: Record<string, string>;
|
|
157
|
+
hooks?: {
|
|
158
|
+
beforeAll?: string[];
|
|
159
|
+
afterAll?: string[];
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=pack-v1.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack-v1.d.ts","sourceRoot":"","sources":["../../../src/core/types/pack-v1.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Trust Score Calculator
|
|
3
|
+
* Calcul du Trust Score pondéré basé sur les résultats des gates
|
|
4
|
+
*/
|
|
5
|
+
export interface TrustScoreWeights {
|
|
6
|
+
ui?: number;
|
|
7
|
+
api?: number;
|
|
8
|
+
perf?: number;
|
|
9
|
+
a11y?: number;
|
|
10
|
+
sast?: number;
|
|
11
|
+
deps?: number;
|
|
12
|
+
secrets?: number;
|
|
13
|
+
dast?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface GateResult {
|
|
16
|
+
name: string;
|
|
17
|
+
success: boolean;
|
|
18
|
+
score: number;
|
|
19
|
+
weight: number;
|
|
20
|
+
details?: {
|
|
21
|
+
passed?: number;
|
|
22
|
+
total?: number;
|
|
23
|
+
duration?: number;
|
|
24
|
+
errors?: string[];
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface TrustScoreResult {
|
|
28
|
+
overallScore: number;
|
|
29
|
+
weightedScore: number;
|
|
30
|
+
gateResults: GateResult[];
|
|
31
|
+
weights: TrustScoreWeights;
|
|
32
|
+
recommendation: 'EXCELLENT' | 'GOOD' | 'ACCEPTABLE' | 'NEEDS_IMPROVEMENT' | 'CRITICAL';
|
|
33
|
+
summary: {
|
|
34
|
+
totalGates: number;
|
|
35
|
+
passedGates: number;
|
|
36
|
+
failedGates: number;
|
|
37
|
+
averageScore: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export declare class TrustScoreCalculator {
|
|
41
|
+
private static readonly DEFAULT_WEIGHTS;
|
|
42
|
+
/**
|
|
43
|
+
* Calculate Trust Score from gate results
|
|
44
|
+
*/
|
|
45
|
+
static calculateTrustScore(gateResults: Omit<GateResult, 'weight'>[], customWeights?: Partial<TrustScoreWeights>): TrustScoreResult;
|
|
46
|
+
/**
|
|
47
|
+
* Get recommendation based on score and results
|
|
48
|
+
*/
|
|
49
|
+
private static getRecommendation;
|
|
50
|
+
/**
|
|
51
|
+
* Generate Trust Score badge
|
|
52
|
+
*/
|
|
53
|
+
static generateBadge(trustScore: TrustScoreResult): string;
|
|
54
|
+
/**
|
|
55
|
+
* Get badge color based on score
|
|
56
|
+
*/
|
|
57
|
+
private static getBadgeColor;
|
|
58
|
+
/**
|
|
59
|
+
* Generate detailed Trust Score report
|
|
60
|
+
*/
|
|
61
|
+
static generateReport(trustScore: TrustScoreResult): string;
|
|
62
|
+
/**
|
|
63
|
+
* Validate weights configuration
|
|
64
|
+
*/
|
|
65
|
+
static validateWeights(weights: Partial<TrustScoreWeights>): {
|
|
66
|
+
valid: boolean;
|
|
67
|
+
errors: string[];
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=trust-score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trust-score.d.ts","sourceRoot":"","sources":["../../../src/core/types/trust-score.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,cAAc,EAAE,WAAW,GAAG,MAAM,GAAG,YAAY,GAAG,mBAAmB,GAAG,UAAU,CAAC;IACvF,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CASrC;IAEF;;OAEG;IACH,MAAM,CAAC,mBAAmB,CACxB,WAAW,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,EACzC,aAAa,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GACzC,gBAAgB;IA6DnB;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAoBhC;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,UAAU,EAAE,gBAAgB,GAAG,MAAM;IAQ1D;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAQ5B;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,gBAAgB,GAAG,MAAM;IA+D3D;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE;CAqBlG"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 Trust Score Calculator
|
|
3
|
+
* Calcul du Trust Score pondéré basé sur les résultats des gates
|
|
4
|
+
*/
|
|
5
|
+
export class TrustScoreCalculator {
|
|
6
|
+
static DEFAULT_WEIGHTS = {
|
|
7
|
+
ui: 20,
|
|
8
|
+
api: 20,
|
|
9
|
+
perf: 15,
|
|
10
|
+
a11y: 10,
|
|
11
|
+
sast: 15,
|
|
12
|
+
deps: 10,
|
|
13
|
+
secrets: 5,
|
|
14
|
+
dast: 5
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Calculate Trust Score from gate results
|
|
18
|
+
*/
|
|
19
|
+
static calculateTrustScore(gateResults, customWeights) {
|
|
20
|
+
const weights = { ...this.DEFAULT_WEIGHTS, ...customWeights };
|
|
21
|
+
// Normalize weights to sum to 100
|
|
22
|
+
const totalWeight = Object.values(weights).reduce((sum, w) => sum + (w || 0), 0);
|
|
23
|
+
const normalizedWeights = {};
|
|
24
|
+
for (const [gate, weight] of Object.entries(weights)) {
|
|
25
|
+
if (weight && weight > 0) {
|
|
26
|
+
normalizedWeights[gate] = (weight / totalWeight) * 100;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Apply weights to gate results
|
|
30
|
+
const weightedGateResults = gateResults.map(gate => ({
|
|
31
|
+
...gate,
|
|
32
|
+
weight: normalizedWeights[gate.name] || 0
|
|
33
|
+
}));
|
|
34
|
+
// Calculate weighted score
|
|
35
|
+
let weightedScore = 0;
|
|
36
|
+
let totalAppliedWeight = 0;
|
|
37
|
+
for (const gate of weightedGateResults) {
|
|
38
|
+
if (gate.weight > 0) {
|
|
39
|
+
weightedScore += (gate.score * gate.weight) / 100;
|
|
40
|
+
totalAppliedWeight += gate.weight;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Normalize if not all gates are present
|
|
44
|
+
if (totalAppliedWeight > 0 && totalAppliedWeight < 100) {
|
|
45
|
+
weightedScore = (weightedScore / totalAppliedWeight) * 100;
|
|
46
|
+
}
|
|
47
|
+
// Calculate overall score (simple average)
|
|
48
|
+
const overallScore = gateResults.length > 0
|
|
49
|
+
? gateResults.reduce((sum, gate) => sum + gate.score, 0) / gateResults.length
|
|
50
|
+
: 0;
|
|
51
|
+
// Calculate summary
|
|
52
|
+
const summary = {
|
|
53
|
+
totalGates: gateResults.length,
|
|
54
|
+
passedGates: gateResults.filter(g => g.success).length,
|
|
55
|
+
failedGates: gateResults.filter(g => !g.success).length,
|
|
56
|
+
averageScore: overallScore
|
|
57
|
+
};
|
|
58
|
+
// Determine recommendation
|
|
59
|
+
const recommendation = this.getRecommendation(weightedScore, summary);
|
|
60
|
+
return {
|
|
61
|
+
overallScore: Math.round(overallScore),
|
|
62
|
+
weightedScore: Math.round(weightedScore),
|
|
63
|
+
gateResults: weightedGateResults,
|
|
64
|
+
weights: normalizedWeights,
|
|
65
|
+
recommendation,
|
|
66
|
+
summary
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get recommendation based on score and results
|
|
71
|
+
*/
|
|
72
|
+
static getRecommendation(score, summary) {
|
|
73
|
+
// Critical if any security gates failed
|
|
74
|
+
const hasSecurityFailures = summary.failedGates > 0;
|
|
75
|
+
if (score >= 90 && !hasSecurityFailures) {
|
|
76
|
+
return 'EXCELLENT';
|
|
77
|
+
}
|
|
78
|
+
else if (score >= 80 && !hasSecurityFailures) {
|
|
79
|
+
return 'GOOD';
|
|
80
|
+
}
|
|
81
|
+
else if (score >= 70) {
|
|
82
|
+
return 'ACCEPTABLE';
|
|
83
|
+
}
|
|
84
|
+
else if (score >= 50) {
|
|
85
|
+
return 'NEEDS_IMPROVEMENT';
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
return 'CRITICAL';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Generate Trust Score badge
|
|
93
|
+
*/
|
|
94
|
+
static generateBadge(trustScore) {
|
|
95
|
+
const score = trustScore.weightedScore;
|
|
96
|
+
const color = this.getBadgeColor(score);
|
|
97
|
+
const label = `Trust Score: ${score}%`;
|
|
98
|
+
return `https://img.shields.io/badge/Trust%20Score-${score}%25-${color}`;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get badge color based on score
|
|
102
|
+
*/
|
|
103
|
+
static getBadgeColor(score) {
|
|
104
|
+
if (score >= 90)
|
|
105
|
+
return 'brightgreen';
|
|
106
|
+
if (score >= 80)
|
|
107
|
+
return 'green';
|
|
108
|
+
if (score >= 70)
|
|
109
|
+
return 'yellow';
|
|
110
|
+
if (score >= 50)
|
|
111
|
+
return 'orange';
|
|
112
|
+
return 'red';
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Generate detailed Trust Score report
|
|
116
|
+
*/
|
|
117
|
+
static generateReport(trustScore) {
|
|
118
|
+
let report = `# QA360 Trust Score Report\n\n`;
|
|
119
|
+
report += `## Overall Assessment\n`;
|
|
120
|
+
report += `- **Trust Score**: ${trustScore.weightedScore}% (weighted)\n`;
|
|
121
|
+
report += `- **Simple Average**: ${trustScore.overallScore}%\n`;
|
|
122
|
+
report += `- **Recommendation**: ${trustScore.recommendation}\n`;
|
|
123
|
+
report += `- **Gates Passed**: ${trustScore.summary.passedGates}/${trustScore.summary.totalGates}\n\n`;
|
|
124
|
+
report += `## Gate Results\n\n`;
|
|
125
|
+
for (const gate of trustScore.gateResults) {
|
|
126
|
+
const status = gate.success ? '✅' : '❌';
|
|
127
|
+
report += `### ${status} ${gate.name.toUpperCase()} Gate\n`;
|
|
128
|
+
report += `- **Score**: ${gate.score}%\n`;
|
|
129
|
+
report += `- **Weight**: ${gate.weight.toFixed(1)}%\n`;
|
|
130
|
+
report += `- **Status**: ${gate.success ? 'PASSED' : 'FAILED'}\n`;
|
|
131
|
+
if (gate.details) {
|
|
132
|
+
if (gate.details.passed !== undefined && gate.details.total !== undefined) {
|
|
133
|
+
report += `- **Tests**: ${gate.details.passed}/${gate.details.total} passed\n`;
|
|
134
|
+
}
|
|
135
|
+
if (gate.details.duration) {
|
|
136
|
+
report += `- **Duration**: ${gate.details.duration}ms\n`;
|
|
137
|
+
}
|
|
138
|
+
if (gate.details.errors && gate.details.errors.length > 0) {
|
|
139
|
+
report += `- **Errors**: ${gate.details.errors.length} found\n`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
report += '\n';
|
|
143
|
+
}
|
|
144
|
+
report += `## Weights Configuration\n\n`;
|
|
145
|
+
for (const [gate, weight] of Object.entries(trustScore.weights)) {
|
|
146
|
+
if (weight && weight > 0) {
|
|
147
|
+
report += `- **${gate.toUpperCase()}**: ${weight.toFixed(1)}%\n`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
report += `\n## Recommendations\n\n`;
|
|
151
|
+
switch (trustScore.recommendation) {
|
|
152
|
+
case 'EXCELLENT':
|
|
153
|
+
report += `🎉 Excellent quality! Your application meets the highest standards.\n`;
|
|
154
|
+
break;
|
|
155
|
+
case 'GOOD':
|
|
156
|
+
report += `👍 Good quality with minor areas for improvement.\n`;
|
|
157
|
+
break;
|
|
158
|
+
case 'ACCEPTABLE':
|
|
159
|
+
report += `⚠️ Acceptable quality but consider addressing failed gates.\n`;
|
|
160
|
+
break;
|
|
161
|
+
case 'NEEDS_IMPROVEMENT':
|
|
162
|
+
report += `🔧 Significant improvements needed. Focus on failed gates.\n`;
|
|
163
|
+
break;
|
|
164
|
+
case 'CRITICAL':
|
|
165
|
+
report += `🚨 Critical issues found. Immediate attention required.\n`;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
return report;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Validate weights configuration
|
|
172
|
+
*/
|
|
173
|
+
static validateWeights(weights) {
|
|
174
|
+
const errors = [];
|
|
175
|
+
// Check for negative weights
|
|
176
|
+
for (const [gate, weight] of Object.entries(weights)) {
|
|
177
|
+
if (weight !== undefined && weight < 0) {
|
|
178
|
+
errors.push(`Weight for ${gate} cannot be negative: ${weight}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Check total weight
|
|
182
|
+
const totalWeight = Object.values(weights).reduce((sum, w) => sum + (w || 0), 0);
|
|
183
|
+
if (totalWeight === 0) {
|
|
184
|
+
errors.push('Total weight cannot be zero');
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
valid: errors.length === 0,
|
|
188
|
+
errors
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|