/**
 * Insurance Certificate Signer — signed envelope of a AgentTrustReport.
 *
 * Per Thomas Layer 0 2026-05-06 trademark positioning:
 *   "The Sovereignty Certificate is the regulatory credential."
 *
 * Wraps a AgentTrustReport (slice 31) in a tamper-evident Ed25519-signed
 * envelope that downstream parties — partner orgs, insurers, auditors,
 * regulators — can verify without contacting our service.
 *
 * Verification is OFFLINE per Locked Invariant 4: cache the issuer
 * public key once, verify any number of certificates against it.
 *
 * Uses the same canonical-JSON serialisation as signed-manifest (W2.x
 * crypto), so signatures verify deterministically across SDKs.
 *
 * @connexum/ai-governance  Insurance Certificate / Sovereignty Credential
 */
import * as crypto from 'node:crypto';
import { ManifestSignatureError } from './signed-manifest.js';
/** Maximum certificate age before verification rejects it. Default 90 days. */
export const DEFAULT_CERT_MAX_AGE_DAYS = 90;
function canonicalJson(value) {
    if (value === null || typeof value !== 'object')
        return JSON.stringify(value);
    if (Array.isArray(value)) {
        return '[' + value.map((v) => canonicalJson(v)).join(',') + ']';
    }
    const obj = value;
    const keys = Object.keys(obj).sort();
    return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonicalJson(obj[k])).join(',') + '}';
}
export function issueInsuranceCertificate(input) {
    if (!input.issuerOrgId || input.issuerOrgId.trim() === '') {
        throw new ManifestSignatureError('MALFORMED', 'issuerOrgId is required');
    }
    const subjectOrgId = input.subjectOrgId ?? input.report.orgId;
    const issuedAt = input.nowIso ?? new Date().toISOString();
    const nonce = crypto.randomBytes(16).toString('hex');
    const payload = {
        issuerOrgId: input.issuerOrgId,
        subjectOrgId,
        class: input.certClass ?? 'standard',
        report: input.report,
        nonce,
        issuedAt,
        expiresAt: input.expiresAt,
    };
    const sig = crypto.sign(null, Buffer.from(canonicalJson(payload), 'utf8'), input.privateKey);
    return {
        payload,
        signature: sig.toString('base64url'),
        alg: 'ed25519',
    };
}
function err(code, message) {
    const e = new Error(message);
    e.code = code;
    e.name = 'CertificateVerifyError';
    return e;
}
/**
 * Verify a certificate offline against the issuer's cached public key.
 * Returns the validated payload OR throws CertificateVerifyError.
 *
 * Validity window:
 *   - explicit expiresAt if present
 *   - else issuedAt + maxAgeDays (default 90)
 * Plus issuedAt cannot be in the future beyond a 5-minute clock-skew tolerance.
 */
export function verifyInsuranceCertificate(cert, publicKey, options = {}) {
    if (!cert || !cert.payload || !cert.signature || cert.alg !== 'ed25519') {
        throw err('MALFORMED', 'certificate missing payload / signature / alg');
    }
    if (options.expectedIssuerOrgId && cert.payload.issuerOrgId !== options.expectedIssuerOrgId) {
        throw err('ISSUER_MISMATCH', `expected issuer ${options.expectedIssuerOrgId} but got ${cert.payload.issuerOrgId}`);
    }
    const issuedAtMs = new Date(cert.payload.issuedAt).getTime();
    if (Number.isNaN(issuedAtMs))
        throw err('MALFORMED', 'issuedAt is not a valid timestamp');
    const nowMs = options.nowMs ?? Date.now();
    const skewMs = 5 * 60 * 1000;
    if (issuedAtMs - nowMs > skewMs) {
        throw err('NOT_YET_VALID', 'issuedAt is in the future beyond clock-skew tolerance');
    }
    // Validity window
    let expiryMs;
    if (cert.payload.expiresAt) {
        expiryMs = new Date(cert.payload.expiresAt).getTime();
        if (Number.isNaN(expiryMs))
            throw err('MALFORMED', 'expiresAt is not a valid timestamp');
    }
    else {
        const maxAgeMs = (options.maxAgeDays ?? DEFAULT_CERT_MAX_AGE_DAYS) * 24 * 60 * 60 * 1000;
        expiryMs = issuedAtMs + maxAgeMs;
    }
    if (nowMs > expiryMs) {
        throw err('EXPIRED', 'certificate is past its validity window');
    }
    const canonical = canonicalJson(cert.payload);
    const sig = Buffer.from(cert.signature, 'base64url');
    const ok = crypto.verify(null, Buffer.from(canonical, 'utf8'), publicKey, sig);
    if (!ok)
        throw err('INVALID_SIGNATURE', 'Ed25519 signature did not verify');
    return cert.payload;
}
/**
 * Compact certificate fingerprint for dashboard cards / partner-share links.
 * SHA-256 of the canonical payload, base64url first 16 chars.
 */
export function certificateFingerprint(cert) {
    const canonical = canonicalJson(cert.payload);
    const hash = crypto.createHash('sha256').update(canonical).digest();
    return hash.toString('base64url').slice(0, 16);
}
//# sourceMappingURL=insurance-certificate.js.map