Ctrl K
ComunidadGuías…

docker/gateguard-effectiveness/evidence.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
'use strict';

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const SCHEMA_VERSION = 1;

function sha256(content) {
  return crypto.createHash('sha256').update(content).digest('hex');
}

function hashFile(file) {
  return sha256(fs.readFileSync(file));
}

function hashDirectory(dir) {
  const entries = [];
  function visit(current, relative = '') {
    for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
      const child = path.join(current, entry.name);
      const name = path.posix.join(relative.split(path.sep).join('/'), entry.name);
      if (entry.isDirectory()) visit(child, name);
      else if (entry.isFile()) entries.push({ path: name, sha256: hashFile(child) });
      else throw new Error(`unsupported evidence input: ${child}`);
    }
  }
  visit(dir);
  return sha256(JSON.stringify(entries));
}

function validateRows(rows, trials) {
  const scheduled = new Map(trials.map(trial => [trial.key, trial]));
  const validKeys = new Set();
  for (const row of rows) {
    if (!row || typeof row.key !== 'string' || !scheduled.has(row.key)) {
      throw new Error(`result row has an unknown scheduled key: ${row && row.key}`);
    }
    const trial = scheduled.get(row.key);
    if (row.scenario !== trial.scenario.id || row.arm !== trial.arm || row.rep !== trial.rep) {
      throw new Error(`result row does not match its scheduled key: ${row.key}`);
    }
    if (!row.providerError && !row.timedOut && !row.judgeFailed) {
      if (validKeys.has(row.key)) throw new Error(`duplicate valid result row: ${row.key}`);
      validKeys.add(row.key);
    }
  }
  return validKeys;
}

function buildManifest({ meta, configuration, scenarioFingerprints, sourceFingerprints, outDir, rows = [] }) {
  const files = {};
  for (const name of ['meta.json', 'results.jsonl', 'holes.md', 'summary.json']) {
    const file = path.join(outDir, name);
    if (fs.existsSync(file)) files[name] = { bytes: fs.statSync(file).size, sha256: hashFile(file) };
  }
  const transcriptsDir = path.join(outDir, 'transcripts');
  if (fs.existsSync(transcriptsDir)) {
    for (const name of fs.readdirSync(transcriptsDir).sort()) {
      const file = path.join(transcriptsDir, name);
      if (fs.statSync(file).isFile()) files[`transcripts/${name}`] = { bytes: fs.statSync(file).size, sha256: hashFile(file) };
    }
  }
  return {
    schemaVersion: SCHEMA_VERSION,
    generatedAt: new Date().toISOString(),
    meta,
    configuration,
    scenarioFingerprints,
    sourceFingerprints,
    integrity: { rowTranscriptLinks: rows.length > 0 && rows.every(row => Boolean(row.transcript && row.transcriptSha256)) },
    runtime: { node: process.version, platform: process.platform, arch: process.arch },
    files
  };
}

function verifyManifest(outDir) {
  const manifestPath = path.join(outDir, 'evidence.json');
  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
  if (manifest.schemaVersion !== SCHEMA_VERSION || !manifest.files || typeof manifest.files !== 'object') {
    throw new Error('unsupported or malformed evidence manifest');
  }
  for (const [name, expected] of Object.entries(manifest.files)) {
    const file = path.resolve(outDir, name);
    if (!file.startsWith(`${path.resolve(outDir)}${path.sep}`)) throw new Error(`invalid evidence path: ${name}`);
    if (!fs.existsSync(file) || !fs.statSync(file).isFile()) throw new Error(`missing evidence artifact: ${name}`);
    const actual = hashFile(file);
    if (actual !== expected.sha256 || fs.statSync(file).size !== expected.bytes) {
      throw new Error(`evidence artifact checksum mismatch: ${name}`);
    }
  }
  if (manifest.integrity && manifest.integrity.rowTranscriptLinks) {
    const resultFile = path.join(outDir, 'results.jsonl');
    if (!fs.existsSync(resultFile)) throw new Error('results.jsonl is missing from a transcript-linked bundle');
    const rows = fs.readFileSync(resultFile, 'utf8').split('\n').filter(Boolean).map(line => JSON.parse(line));
    for (const row of rows) {
      const transcript = manifest.files[row.transcript];
      if (!transcript || transcript.sha256 !== row.transcriptSha256) {
        throw new Error(`result transcript link is missing or mismatched: ${row.key}`);
      }
    }
  }
  return manifest;
}

module.exports = { SCHEMA_VERSION, hashFile, hashDirectory, validateRows, buildManifest, verifyManifest };