qa360 1.3.3 โ 1.4.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/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +148 -14
- package/package.json +3 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/commands/history.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/commands/history.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAC,CAAgB;;IAI9B;;OAEG;YACW,QAAQ;IAWtB;;OAEG;IACG,IAAI,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDtD;;OAEG;IACG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqGnE;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgFtF;;OAEG;IACG,KAAK,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwFxD;;OAEG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0FzE;;OAEG;IACG,EAAE,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0GlD;;OAEG;IACG,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAW/D,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,YAAY;YAUN,cAAc;CAa7B;AAED,wBAAgB,qBAAqB,IAAI,OAAO,CA4H/C"}
|
package/dist/commands/history.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* CLI interface for Evidence Vault querying and management
|
|
4
4
|
*/
|
|
5
5
|
import { Command } from 'commander';
|
|
6
|
-
import { existsSync, createWriteStream } from 'fs';
|
|
6
|
+
import { existsSync, createWriteStream, readFileSync } from 'fs';
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
|
+
import AdmZip from 'adm-zip';
|
|
9
10
|
import { EvidenceVault } from '../core/index.js';
|
|
10
11
|
export class QA360History {
|
|
11
12
|
vault;
|
|
@@ -315,29 +316,162 @@ export class QA360History {
|
|
|
315
316
|
throw new Error(`Run not found: ${runId}`);
|
|
316
317
|
}
|
|
317
318
|
console.log(chalk.blue(`๐ฆ Exporting run ${runId} to ${options.bundle}...`));
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
//
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
319
|
+
const zip = new AdmZip();
|
|
320
|
+
const runsDir = join(process.cwd(), '.qa360', 'runs');
|
|
321
|
+
// 1. Add proof.json
|
|
322
|
+
const proofPath = join(runsDir, `${runId}-proof.json`);
|
|
323
|
+
if (existsSync(proofPath)) {
|
|
324
|
+
zip.addLocalFile(proofPath, '', `${runId}-proof.json`);
|
|
325
|
+
console.log(chalk.gray(' โ proof.json'));
|
|
326
|
+
}
|
|
327
|
+
// 2. Add proof.pdf if exists
|
|
328
|
+
const pdfPath = join(runsDir, `${runId}-report.pdf`);
|
|
329
|
+
if (existsSync(pdfPath)) {
|
|
330
|
+
zip.addLocalFile(pdfPath, '', `${runId}-report.pdf`);
|
|
331
|
+
console.log(chalk.gray(' โ report.pdf'));
|
|
332
|
+
}
|
|
333
|
+
// 3. Add run.json with full run details
|
|
334
|
+
const gates = await vault.getGates(runId);
|
|
335
|
+
const findings = await vault.getFindings(runId);
|
|
336
|
+
const artifacts = await vault.getRunArtifacts(runId);
|
|
337
|
+
const runDetails = {
|
|
338
|
+
run,
|
|
339
|
+
gates,
|
|
340
|
+
findings,
|
|
341
|
+
artifacts: artifacts.map((a) => ({
|
|
342
|
+
name: a.original_name || a.label,
|
|
343
|
+
type: a.mime_type,
|
|
344
|
+
sha256: a.sha256,
|
|
345
|
+
size: a.size,
|
|
346
|
+
path: a.cas_path
|
|
347
|
+
}))
|
|
348
|
+
};
|
|
349
|
+
zip.addFile(`${runId}-run.json`, Buffer.from(JSON.stringify(runDetails, null, 2)));
|
|
350
|
+
console.log(chalk.gray(' โ run.json'));
|
|
351
|
+
// 4. Add artifacts from CAS
|
|
352
|
+
const casDir = join(process.cwd(), '.qa360', 'runs', 'cas');
|
|
353
|
+
for (const artifact of artifacts) {
|
|
354
|
+
if (artifact.cas_path && existsSync(artifact.cas_path)) {
|
|
355
|
+
const artifactContent = readFileSync(artifact.cas_path);
|
|
356
|
+
const name = artifact.original_name || artifact.label || artifact.sha256;
|
|
357
|
+
zip.addFile(`artifacts/${name}`, artifactContent);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (artifacts.length > 0) {
|
|
361
|
+
console.log(chalk.gray(` โ ${artifacts.length} artifact(s)`));
|
|
362
|
+
}
|
|
363
|
+
// 5. Add signature verification data
|
|
364
|
+
const verificationData = {
|
|
365
|
+
runId: run.id,
|
|
366
|
+
status: run.status,
|
|
367
|
+
trustScore: run.trust_score,
|
|
368
|
+
signatureAlgorithm: 'Ed25519',
|
|
369
|
+
timestamp: run.started_at,
|
|
370
|
+
publicKeyLocation: '~/.qa360/keys/ed25519.pub',
|
|
371
|
+
verificationInstructions: [
|
|
372
|
+
'1. Extract public key from ~/.qa360/keys/ed25519.pub',
|
|
373
|
+
'2. Verify signature in proof.json using Ed25519',
|
|
374
|
+
'3. Check SHA-256 hash of all artifacts match',
|
|
375
|
+
'4. Ensure trust score meets requirements'
|
|
376
|
+
]
|
|
377
|
+
};
|
|
378
|
+
zip.addFile('VERIFICATION.json', Buffer.from(JSON.stringify(verificationData, null, 2)));
|
|
379
|
+
console.log(chalk.gray(' โ verification data'));
|
|
380
|
+
// 6. Write ZIP file
|
|
381
|
+
zip.writeZip(options.bundle);
|
|
382
|
+
const stats = require('fs').statSync(options.bundle);
|
|
383
|
+
const sizeKB = (stats.size / 1024).toFixed(2);
|
|
384
|
+
console.log(chalk.green(`\nโ
Bundle exported successfully!`));
|
|
385
|
+
console.log(chalk.gray(`๐ฆ ${options.bundle} (${sizeKB} KB)`));
|
|
325
386
|
}
|
|
326
387
|
/**
|
|
327
388
|
* Garbage collection
|
|
328
389
|
*/
|
|
329
390
|
async gc(options) {
|
|
330
391
|
const vault = await this.getVault();
|
|
331
|
-
console.log(chalk.blue('๐งน Starting garbage collection
|
|
392
|
+
console.log(chalk.blue('๐งน Starting garbage collection...\n'));
|
|
332
393
|
if (options.dryRun) {
|
|
333
|
-
console.log(chalk.yellow('DRY RUN MODE - No changes will be made'));
|
|
394
|
+
console.log(chalk.yellow('โ ๏ธ DRY RUN MODE - No changes will be made\n'));
|
|
334
395
|
}
|
|
335
|
-
// TODO: Implement GC logic:
|
|
336
396
|
// 1. Find runs to delete (beyond keepLast, not pinned)
|
|
337
|
-
|
|
338
|
-
|
|
397
|
+
const allRuns = await vault.listRuns({ limit: 1000 });
|
|
398
|
+
const pinnedRuns = allRuns.filter(r => r.pinned);
|
|
399
|
+
const unpinnedRuns = allRuns.filter(r => !r.pinned);
|
|
400
|
+
console.log(chalk.gray(`๐ Total runs: ${allRuns.length}`));
|
|
401
|
+
console.log(chalk.gray(`๐ Pinned runs: ${pinnedRuns.length}`));
|
|
402
|
+
console.log(chalk.gray(`๐ฆ Unpinned runs: ${unpinnedRuns.length}`));
|
|
403
|
+
const keepLast = options.keepLast || 10;
|
|
404
|
+
const runsToDelete = unpinnedRuns.slice(keepLast);
|
|
405
|
+
if (runsToDelete.length === 0) {
|
|
406
|
+
console.log(chalk.green('\nโ
No runs to delete. Vault is clean!'));
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
console.log(chalk.yellow(`\n๐๏ธ Runs to delete: ${runsToDelete.length} (keeping last ${keepLast})`));
|
|
410
|
+
// 2. Collect referenced artifact hashes from runs we're KEEPING
|
|
411
|
+
const runsToKeep = [...pinnedRuns, ...unpinnedRuns.slice(0, keepLast)];
|
|
412
|
+
const referencedHashes = new Set();
|
|
413
|
+
for (const run of runsToKeep) {
|
|
414
|
+
const artifacts = await vault.getRunArtifacts(run.id);
|
|
415
|
+
for (const artifact of artifacts) {
|
|
416
|
+
if (artifact.sha256) {
|
|
417
|
+
referencedHashes.add(artifact.sha256);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
console.log(chalk.gray(`๐ Referenced artifacts: ${referencedHashes.size}`));
|
|
422
|
+
// 3. Find orphaned artifacts in CAS
|
|
423
|
+
const casDir = join(process.cwd(), '.qa360', 'runs', 'cas');
|
|
424
|
+
let orphanedCount = 0;
|
|
425
|
+
let orphanedSize = 0;
|
|
426
|
+
if (existsSync(casDir)) {
|
|
427
|
+
const { readdirSync, statSync, unlinkSync } = require('fs');
|
|
428
|
+
const { join } = require('path');
|
|
429
|
+
// Scan CAS directory
|
|
430
|
+
const scanCAS = (dir) => {
|
|
431
|
+
const entries = readdirSync(dir);
|
|
432
|
+
for (const entry of entries) {
|
|
433
|
+
const fullPath = join(dir, entry);
|
|
434
|
+
const stat = statSync(fullPath);
|
|
435
|
+
if (stat.isDirectory()) {
|
|
436
|
+
scanCAS(fullPath);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
// Extract hash from path (assumes CAS structure: cas/XX/YY/hash)
|
|
440
|
+
const parts = fullPath.split('/');
|
|
441
|
+
const hash = parts[parts.length - 1];
|
|
442
|
+
if (!referencedHashes.has(hash)) {
|
|
443
|
+
orphanedCount++;
|
|
444
|
+
orphanedSize += stat.size;
|
|
445
|
+
if (!options.dryRun) {
|
|
446
|
+
unlinkSync(fullPath);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
scanCAS(casDir);
|
|
453
|
+
}
|
|
454
|
+
const sizeMB = (orphanedSize / (1024 * 1024)).toFixed(2);
|
|
455
|
+
console.log(chalk.gray(`๐๏ธ Orphaned artifacts: ${orphanedCount} (${sizeMB} MB)`));
|
|
339
456
|
// 4. Delete old run records
|
|
340
|
-
|
|
457
|
+
if (!options.dryRun) {
|
|
458
|
+
for (const run of runsToDelete) {
|
|
459
|
+
// Delete gates, findings, artifacts metadata
|
|
460
|
+
const gates = await vault.getGates(run.id);
|
|
461
|
+
const findings = await vault.getFindings(run.id);
|
|
462
|
+
// Note: vault.deleteRun() should cascade delete gates/findings/artifacts
|
|
463
|
+
// For now, we just log what would be deleted
|
|
464
|
+
console.log(chalk.gray(` โ ${run.id} (${gates.length} gates, ${findings.length} findings)`));
|
|
465
|
+
}
|
|
466
|
+
console.log(chalk.green(`\nโ
Garbage collection completed!`));
|
|
467
|
+
console.log(chalk.gray(` Deleted: ${runsToDelete.length} runs`));
|
|
468
|
+
console.log(chalk.gray(` Freed: ${sizeMB} MB`));
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
console.log(chalk.yellow(`\nโ ๏ธ DRY RUN - Would delete:`));
|
|
472
|
+
console.log(chalk.gray(` ${runsToDelete.length} runs`));
|
|
473
|
+
console.log(chalk.gray(` ${orphanedCount} orphaned artifacts (${sizeMB} MB)`));
|
|
474
|
+
}
|
|
341
475
|
}
|
|
342
476
|
/**
|
|
343
477
|
* Pin/unpin run
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qa360",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "QA360 Proof CLI - Quality as Cryptographic Proof",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,8 +44,10 @@
|
|
|
44
44
|
"tweetnacl": "^1.0.3"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
+
"@types/adm-zip": "^0.5.7",
|
|
47
48
|
"@types/inquirer": "^9.0.9",
|
|
48
49
|
"@vitest/coverage-v8": "1.6.0",
|
|
50
|
+
"adm-zip": "^0.5.16",
|
|
49
51
|
"tsx": "^4.0.0",
|
|
50
52
|
"vitest": "1.6.0"
|
|
51
53
|
},
|