/**
 * SLSA Provenance Generator
 *
 * Generates SLSA v1.0 provenance statements for certifications.
 *
 * @module sbom/provenance
 */
import { createHash } from "crypto";
import { readFile, access } from "fs/promises";
import { join } from "path";
import { logger } from "../logger.js";
/**
 * Default builder ID
 */
const DEFAULT_BUILDER_ID = "https://github.com/RCOLKITT/vaspera-hardening-mcp";
/**
 * Default build type for Vaspera certifications
 */
const VASPERA_BUILD_TYPE = "https://vaspera.dev/certification/v1";
/**
 * Check if file exists
 */
async function fileExists(filePath) {
    try {
        await access(filePath);
        return true;
    }
    catch (error) {
        logger.debug("provenance.file_not_found", { path: filePath, error: String(error) });
        return false;
    }
}
/**
 * Calculate SHA-256 digest of a string
 */
export function sha256Digest(content) {
    return createHash("sha256").update(content).digest("hex");
}
/**
 * Calculate SHA-256 digest of a file
 */
export async function sha256File(filePath) {
    const content = await readFile(filePath);
    return createHash("sha256").update(content).digest("hex");
}
/**
 * Get Git commit SHA if in a git repo
 */
async function getGitCommit(projectPath) {
    const gitHeadPath = join(projectPath, ".git", "HEAD");
    if (!(await fileExists(gitHeadPath))) {
        return undefined;
    }
    try {
        const headContent = await readFile(gitHeadPath, "utf-8");
        const trimmed = headContent.trim();
        // Check if HEAD points to a ref
        if (trimmed.startsWith("ref: ")) {
            const refPath = join(projectPath, ".git", trimmed.slice(5));
            if (await fileExists(refPath)) {
                const sha = await readFile(refPath, "utf-8");
                return sha.trim();
            }
        }
        else {
            // Detached HEAD - directly contains SHA
            return trimmed;
        }
    }
    catch (error) {
        logger.debug("provenance.git_commit_failed", { projectPath, error: String(error) });
        return undefined;
    }
    return undefined;
}
/**
 * Get Git remote URL
 */
async function getGitRemoteUrl(projectPath) {
    const configPath = join(projectPath, ".git", "config");
    if (!(await fileExists(configPath))) {
        return undefined;
    }
    try {
        const configContent = await readFile(configPath, "utf-8");
        const match = configContent.match(/\[remote "origin"\][^\[]*url\s*=\s*(.+)/);
        if (match) {
            return match[1].trim();
        }
    }
    catch (error) {
        logger.debug("provenance.git_remote_failed", { projectPath, error: String(error) });
        return undefined;
    }
    return undefined;
}
/**
 * Create build definition for a certification
 */
async function createBuildDefinition(certification, options) {
    const projectPath = certification.metadata.project_path;
    // Resolved dependencies (files that were scanned)
    const resolvedDependencies = [];
    // Add package.json as a dependency if it exists
    const packageJsonPath = join(projectPath, "package.json");
    if (await fileExists(packageJsonPath)) {
        resolvedDependencies.push({
            uri: "file://package.json",
            digest: {
                sha256: await sha256File(packageJsonPath),
            },
            name: "package.json",
        });
    }
    // Add package-lock.json if it exists
    const packageLockPath = join(projectPath, "package-lock.json");
    if (await fileExists(packageLockPath)) {
        resolvedDependencies.push({
            uri: "file://package-lock.json",
            digest: {
                sha256: await sha256File(packageLockPath),
            },
            name: "package-lock.json",
        });
    }
    // External parameters from certification config
    const externalParameters = {
        agents: certification.metadata.agents_requested,
        ...options?.externalParameters,
    };
    // Internal parameters (environment info)
    const internalParameters = {
        node_version: process.version,
        platform: process.platform,
        arch: process.arch,
        ...options?.internalParameters,
    };
    return {
        buildType: options?.buildType || VASPERA_BUILD_TYPE,
        externalParameters,
        internalParameters,
        resolvedDependencies,
    };
}
/**
 * Create run details for a certification
 */
function createRunDetails(certification, options) {
    const runDetails = {
        builder: {
            id: options?.builderId || DEFAULT_BUILDER_ID,
            version: {
                "vaspera-hardening": "1.1.0",
            },
        },
        metadata: {
            invocationId: certification.metadata.id,
            startedOn: certification.metadata.started_at,
            finishedOn: certification.metadata.completed_at,
        },
    };
    // Add byproducts (findings summary)
    if (certification.consensus) {
        runDetails.byproducts = [
            {
                uri: `vaspera://certification/${certification.metadata.id}/consensus`,
                mediaType: "application/json",
                content: Buffer.from(JSON.stringify({
                    level: certification.consensus.certification_level,
                    score: certification.consensus.overall_score,
                    total_findings: certification.consensus.total_findings,
                    by_severity: certification.consensus.by_severity,
                })).toString("base64"),
            },
        ];
    }
    return runDetails;
}
/**
 * Generate SLSA provenance for a certification
 */
export async function generateProvenance(certification, artifactContent, options) {
    const projectPath = certification.metadata.project_path;
    // Get git info for subject
    const gitCommit = await getGitCommit(projectPath);
    const gitRemote = await getGitRemoteUrl(projectPath);
    // Create subject (what the provenance is about)
    const subjectName = gitRemote
        ? gitRemote.replace(/\.git$/, "").replace(/^git@github\.com:/, "https://github.com/")
        : certification.metadata.project_name;
    const subjectDigest = {
        sha256: sha256Digest(artifactContent),
    };
    if (gitCommit) {
        subjectDigest.gitCommit = gitCommit;
    }
    // Create provenance predicate
    const predicate = {
        buildDefinition: await createBuildDefinition(certification, options),
        runDetails: createRunDetails(certification, options),
    };
    // Create in-toto statement
    const statement = {
        _type: "https://in-toto.io/Statement/v1",
        subject: [
            {
                name: subjectName,
                digest: subjectDigest,
            },
        ],
        predicateType: "https://slsa.dev/provenance/v1",
        predicate,
    };
    return statement;
}
/**
 * Generate provenance as JSON string
 */
export async function generateProvenanceJson(certification, artifactContent, options) {
    const provenance = await generateProvenance(certification, artifactContent, options);
    return JSON.stringify(provenance, null, 2);
}
/**
 * Generate compact provenance summary
 */
export function generateProvenanceSummary(provenance) {
    const subject = provenance.subject[0];
    const buildDef = provenance.predicate.buildDefinition;
    const runDetails = provenance.predicate.runDetails;
    const lines = [
        `Subject: ${subject.name}`,
        `Digest: sha256:${subject.digest.sha256?.slice(0, 12)}...`,
        `Builder: ${runDetails.builder.id}`,
        `Build Type: ${buildDef.buildType}`,
    ];
    if (runDetails.metadata?.startedOn) {
        lines.push(`Started: ${runDetails.metadata.startedOn}`);
    }
    if (runDetails.metadata?.finishedOn) {
        lines.push(`Finished: ${runDetails.metadata.finishedOn}`);
    }
    return lines.join("\n");
}
/**
 * Verify provenance matches a certification
 */
export function verifyProvenance(provenance, certification, artifactContent) {
    const errors = [];
    // Check predicate type
    if (provenance.predicateType !== "https://slsa.dev/provenance/v1") {
        errors.push(`Invalid predicate type: ${provenance.predicateType}`);
    }
    // Check subject digest
    const expectedDigest = sha256Digest(artifactContent);
    if (provenance.subject[0]?.digest.sha256 !== expectedDigest) {
        errors.push("Subject digest does not match artifact content");
    }
    // Check invocation ID matches certification ID
    const invocationId = provenance.predicate.runDetails.metadata?.invocationId;
    if (invocationId !== certification.metadata.id) {
        errors.push(`Invocation ID mismatch: ${invocationId} vs ${certification.metadata.id}`);
    }
    return {
        valid: errors.length === 0,
        errors,
    };
}
//# sourceMappingURL=provenance.js.map