qa360 1.4.3 → 1.4.5
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.
|
@@ -23,7 +23,6 @@ export interface DoctorResult {
|
|
|
23
23
|
}
|
|
24
24
|
export declare class QA360Doctor {
|
|
25
25
|
private qa360Dir;
|
|
26
|
-
private toolsDir;
|
|
27
26
|
private keysDir;
|
|
28
27
|
private runsDir;
|
|
29
28
|
diagnose(options?: {
|
|
@@ -40,7 +39,6 @@ export declare class QA360Doctor {
|
|
|
40
39
|
private checkSecrets;
|
|
41
40
|
private checkCryptoKeys;
|
|
42
41
|
private checkProofKeys;
|
|
43
|
-
private checkProofsDirectory;
|
|
44
42
|
private checkProofRoundtrip;
|
|
45
43
|
private performFixes;
|
|
46
44
|
private generateResult;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
* QA360 Doctor Command - Environment diagnostic and auto-fix
|
|
3
3
|
*/
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
|
-
import { existsSync, mkdirSync
|
|
5
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import ora from 'ora';
|
|
9
9
|
export class QA360Doctor {
|
|
10
10
|
qa360Dir = process.env.QA360_HOME || join(process.cwd(), '.qa360');
|
|
11
|
-
toolsDir = join(this.qa360Dir, 'tools');
|
|
12
11
|
keysDir = join(this.qa360Dir, 'keys');
|
|
13
12
|
runsDir = join(this.qa360Dir, 'runs');
|
|
14
13
|
async diagnose(options = {}) {
|
|
@@ -74,7 +73,6 @@ export class QA360Doctor {
|
|
|
74
73
|
checks.push(await this.checkCryptoKeys());
|
|
75
74
|
// 7. Proof System (Phase 2)
|
|
76
75
|
checks.push(await this.checkProofKeys());
|
|
77
|
-
checks.push(await this.checkProofsDirectory());
|
|
78
76
|
checks.push(await this.checkProofRoundtrip());
|
|
79
77
|
return checks;
|
|
80
78
|
}
|
|
@@ -111,7 +109,8 @@ export class QA360Doctor {
|
|
|
111
109
|
}
|
|
112
110
|
}
|
|
113
111
|
async checkDirectories() {
|
|
114
|
-
|
|
112
|
+
// Only check essential directories - tools/ is deprecated
|
|
113
|
+
const dirs = [this.qa360Dir, this.keysDir, this.runsDir];
|
|
115
114
|
const missing = dirs.filter(dir => !existsSync(dir));
|
|
116
115
|
if (missing.length === 0) {
|
|
117
116
|
return {
|
|
@@ -193,7 +192,9 @@ export class QA360Doctor {
|
|
|
193
192
|
}
|
|
194
193
|
}
|
|
195
194
|
async checkSecrets() {
|
|
196
|
-
const secretsFile = join(this.qa360Dir, 'secrets.
|
|
195
|
+
const secretsFile = join(this.qa360Dir, 'secrets.enc');
|
|
196
|
+
const oldSecretsFile = join(this.qa360Dir, 'secrets.json');
|
|
197
|
+
// Secrets are optional - this is informational only
|
|
197
198
|
if (existsSync(secretsFile)) {
|
|
198
199
|
return {
|
|
199
200
|
id: 'gestion_secrets',
|
|
@@ -202,26 +203,33 @@ export class QA360Doctor {
|
|
|
202
203
|
message: 'Fichier secrets configuré ✅'
|
|
203
204
|
};
|
|
204
205
|
}
|
|
206
|
+
// Old secrets file exists but not new one
|
|
207
|
+
if (existsSync(oldSecretsFile)) {
|
|
208
|
+
return {
|
|
209
|
+
id: 'gestion_secrets',
|
|
210
|
+
name: 'Gestion Secrets',
|
|
211
|
+
status: 'ok',
|
|
212
|
+
message: 'Fichier secrets (ancien format) configuré ℹ️'
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// No secrets file is OK - secrets are optional
|
|
205
216
|
return {
|
|
206
217
|
id: 'gestion_secrets',
|
|
207
218
|
name: 'Gestion Secrets',
|
|
208
|
-
status: '
|
|
209
|
-
message: 'Aucun fichier secrets
|
|
210
|
-
code: 'QX006',
|
|
211
|
-
fix: async () => {
|
|
212
|
-
const defaultSecrets = {
|
|
213
|
-
version: '1.0.0',
|
|
214
|
-
encrypted: false,
|
|
215
|
-
secrets: {}
|
|
216
|
-
};
|
|
217
|
-
writeFileSync(secretsFile, JSON.stringify(defaultSecrets, null, 2));
|
|
218
|
-
}
|
|
219
|
+
status: 'ok',
|
|
220
|
+
message: 'Aucun fichier secrets (optionnel) ℹ️'
|
|
219
221
|
};
|
|
220
222
|
}
|
|
221
223
|
async checkCryptoKeys() {
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
224
|
+
// Updated to use new key filenames (ed25519.key / ed25519.pub)
|
|
225
|
+
const privateKeyFile = join(this.keysDir, 'ed25519.key');
|
|
226
|
+
const publicKeyFile = join(this.keysDir, 'ed25519.pub');
|
|
227
|
+
// Also check for old filenames for backward compatibility
|
|
228
|
+
const oldPrivateKeyFile = join(this.keysDir, 'qa360.key');
|
|
229
|
+
const oldPublicKeyFile = join(this.keysDir, 'qa360.pub');
|
|
230
|
+
const hasNewKeys = existsSync(privateKeyFile) && existsSync(publicKeyFile);
|
|
231
|
+
const hasOldKeys = existsSync(oldPrivateKeyFile) && existsSync(oldPublicKeyFile);
|
|
232
|
+
if (hasNewKeys) {
|
|
225
233
|
return {
|
|
226
234
|
id: 'crypto_keys',
|
|
227
235
|
name: 'Clés Cryptographiques',
|
|
@@ -229,24 +237,20 @@ export class QA360Doctor {
|
|
|
229
237
|
message: 'Paire de clés Ed25519 présente ✅'
|
|
230
238
|
};
|
|
231
239
|
}
|
|
240
|
+
if (hasOldKeys) {
|
|
241
|
+
return {
|
|
242
|
+
id: 'crypto_keys',
|
|
243
|
+
name: 'Clés Cryptographiques',
|
|
244
|
+
status: 'ok',
|
|
245
|
+
message: 'Paire de clés Ed25519 présente (ancien format) ℹ️'
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// Note: Keys are auto-generated by ensureProofKeys, so this is just informational
|
|
232
249
|
return {
|
|
233
250
|
id: 'crypto_keys',
|
|
234
251
|
name: 'Clés Cryptographiques',
|
|
235
|
-
status: '
|
|
236
|
-
message: '
|
|
237
|
-
code: 'QX007',
|
|
238
|
-
fix: async () => {
|
|
239
|
-
const { generateKeyPair } = await import('crypto');
|
|
240
|
-
generateKeyPair('ed25519', {
|
|
241
|
-
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
242
|
-
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
243
|
-
}, (err, publicKey, privateKey) => {
|
|
244
|
-
if (err)
|
|
245
|
-
throw err;
|
|
246
|
-
writeFileSync(publicKeyFile, publicKey);
|
|
247
|
-
writeFileSync(privateKeyFile, privateKey);
|
|
248
|
-
});
|
|
249
|
-
}
|
|
252
|
+
status: 'ok',
|
|
253
|
+
message: 'Clés Ed25519 seront générées automatiquement ℹ️'
|
|
250
254
|
};
|
|
251
255
|
}
|
|
252
256
|
async checkProofKeys() {
|
|
@@ -294,38 +298,6 @@ export class QA360Doctor {
|
|
|
294
298
|
};
|
|
295
299
|
}
|
|
296
300
|
}
|
|
297
|
-
async checkProofsDirectory() {
|
|
298
|
-
const proofsDir = join(this.qa360Dir, 'proofs');
|
|
299
|
-
try {
|
|
300
|
-
if (existsSync(proofsDir)) {
|
|
301
|
-
return {
|
|
302
|
-
id: 'proofs_directory',
|
|
303
|
-
name: 'Proofs Directory',
|
|
304
|
-
status: 'ok',
|
|
305
|
-
message: `Proofs directory exists (${proofsDir}) ✅`
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
return {
|
|
309
|
-
id: 'proofs_directory',
|
|
310
|
-
name: 'Proofs Directory',
|
|
311
|
-
status: 'warning',
|
|
312
|
-
message: 'Proofs directory missing',
|
|
313
|
-
code: 'D004',
|
|
314
|
-
fix: async () => {
|
|
315
|
-
mkdirSync(proofsDir, { recursive: true, mode: 0o755 });
|
|
316
|
-
}
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
catch (error) {
|
|
320
|
-
return {
|
|
321
|
-
id: 'proofs_directory',
|
|
322
|
-
name: 'Proofs Directory',
|
|
323
|
-
status: 'error',
|
|
324
|
-
message: `Cannot create proofs directory: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
325
|
-
code: 'D004'
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
301
|
async checkProofRoundtrip() {
|
|
330
302
|
try {
|
|
331
303
|
const coreModule = await import('../core/index.js');
|
package/dist/commands/history.js
CHANGED
|
@@ -39,7 +39,8 @@ export class QA360History {
|
|
|
39
39
|
filters.since = new Date(options.since);
|
|
40
40
|
}
|
|
41
41
|
const runs = await vault.listRuns(filters);
|
|
42
|
-
|
|
42
|
+
// Support both --json and --format json
|
|
43
|
+
if (options.json || options.format === 'json') {
|
|
43
44
|
console.log(JSON.stringify(runs, null, 2));
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
@@ -72,14 +73,21 @@ export class QA360History {
|
|
|
72
73
|
*/
|
|
73
74
|
async get(runId, options) {
|
|
74
75
|
const vault = await this.getVault();
|
|
75
|
-
|
|
76
|
+
// Try exact match first, then prefix match for shortened IDs
|
|
77
|
+
let run = await vault.getRun(runId);
|
|
78
|
+
if (!run && runId.length < 36) {
|
|
79
|
+
// Try prefix search for shortened IDs (e.g., "6ce422f7")
|
|
80
|
+
run = await vault.getRunByPrefix(runId);
|
|
81
|
+
}
|
|
76
82
|
if (!run) {
|
|
77
|
-
throw new Error(`Run not found: ${runId}`);
|
|
83
|
+
throw new Error(`Run not found: ${runId}${runId.length < 36 ? ' (try using the full UUID from "qa360 history list")' : ''}`);
|
|
78
84
|
}
|
|
85
|
+
// Use the full UUID from the found run for subsequent lookups
|
|
86
|
+
const fullRunId = run.id;
|
|
79
87
|
const [gates, findings, artifacts] = await Promise.all([
|
|
80
|
-
vault.getGates(
|
|
81
|
-
vault.getFindings(
|
|
82
|
-
vault.getRunArtifacts(
|
|
88
|
+
vault.getGates(fullRunId),
|
|
89
|
+
vault.getFindings(fullRunId),
|
|
90
|
+
vault.getRunArtifacts(fullRunId)
|
|
83
91
|
]);
|
|
84
92
|
const runDetails = {
|
|
85
93
|
run,
|
|
@@ -98,7 +106,7 @@ export class QA360History {
|
|
|
98
106
|
return;
|
|
99
107
|
}
|
|
100
108
|
// Display formatted output
|
|
101
|
-
console.log(chalk.bold(`\n🔍 Run Details: ${
|
|
109
|
+
console.log(chalk.bold(`\n🔍 Run Details: ${fullRunId}\n`));
|
|
102
110
|
console.log(chalk.blue('📊 Overview:'));
|
|
103
111
|
console.log(` Status: ${this.formatStatus(run.status)}`);
|
|
104
112
|
console.log(` Trust Score: ${run.trust_score ? `${run.trust_score}%` : 'Not calculated'}`);
|
|
@@ -560,7 +568,8 @@ export function createHistoryCommands() {
|
|
|
560
568
|
.option('-s, --status <status>', 'Filter by status (passed|failed|cancelled|error)')
|
|
561
569
|
.option('-g, --gate <gate>', 'Filter by gate name')
|
|
562
570
|
.option('--since <date>', 'Show runs since date (ISO format)')
|
|
563
|
-
.option('--json', 'Output as JSON')
|
|
571
|
+
.option('--json', 'Output as JSON (alias for --format json)')
|
|
572
|
+
.option('--format <format>', 'Output format (json|table)', 'table')
|
|
564
573
|
.action(async (options) => {
|
|
565
574
|
try {
|
|
566
575
|
await history.list(options);
|
|
@@ -140,6 +140,11 @@ export declare class EvidenceVault {
|
|
|
140
140
|
* Get run by ID
|
|
141
141
|
*/
|
|
142
142
|
getRun(runId: string): Promise<RunRecord | null>;
|
|
143
|
+
/**
|
|
144
|
+
* Get run by ID prefix (e.g., "6ce422f7" for shortened display)
|
|
145
|
+
* Returns null if multiple or no runs match the prefix
|
|
146
|
+
*/
|
|
147
|
+
getRunByPrefix(prefix: string): Promise<RunRecord | null>;
|
|
143
148
|
/**
|
|
144
149
|
* Get run by run_key
|
|
145
150
|
*/
|
package/dist/core/vault/index.js
CHANGED
|
@@ -169,6 +169,22 @@ export class EvidenceVault {
|
|
|
169
169
|
});
|
|
170
170
|
});
|
|
171
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Get run by ID prefix (e.g., "6ce422f7" for shortened display)
|
|
174
|
+
* Returns null if multiple or no runs match the prefix
|
|
175
|
+
*/
|
|
176
|
+
async getRunByPrefix(prefix) {
|
|
177
|
+
const allRuns = await this.listRuns({});
|
|
178
|
+
const matching = allRuns.filter(r => r.id.startsWith(prefix));
|
|
179
|
+
if (matching.length === 0) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
if (matching.length > 1) {
|
|
183
|
+
// Ambiguous prefix - return null to indicate error
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
return matching[0];
|
|
187
|
+
}
|
|
172
188
|
/**
|
|
173
189
|
* Get run by run_key
|
|
174
190
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qa360",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.5",
|
|
4
4
|
"description": "QA360 Proof CLI - Quality as Cryptographic Proof",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@playwright/test": "^1.49.0",
|
|
35
|
+
"adm-zip": "^0.5.16",
|
|
35
36
|
"ajv": "^8.17.1",
|
|
36
37
|
"ajv-draft-04": "^1.0.0",
|
|
37
38
|
"ajv-formats": "^2.1.1",
|
|
@@ -48,7 +49,6 @@
|
|
|
48
49
|
"@types/adm-zip": "^0.5.7",
|
|
49
50
|
"@types/inquirer": "^9.0.9",
|
|
50
51
|
"@vitest/coverage-v8": "1.6.0",
|
|
51
|
-
"adm-zip": "^0.5.16",
|
|
52
52
|
"tsx": "^4.0.0",
|
|
53
53
|
"vitest": "1.6.0"
|
|
54
54
|
},
|