/**
 * License Pack Loader — SDK consumer of PR-D (pack distribution endpoint).
 *
 * Responsibilities:
 *   1. Parse pack_codes from the install JWT (decoded once at runtime boot).
 *   2. Consult the monthly-verify cache (MonthlyVerifyCache) to determine
 *      which packs are licensed — skip the fetch entirely for unlicensed packs
 *      to avoid a wasteful 403 round-trip.
 *   3. Fetch each pack via GET /api/v1/packs/:packId@latest with the Install
 *      Pack Code as Bearer, using a 1-hour TTL in-process cache.
 *   4. On 403 (license_not_active): log UNLICENSED_PACK_REFUSED locally +
 *      emit to gov-server audit chain (best-effort). Never throw.
 *   5. Load fetched pack rules into the runtime governance engine.
 *
 * Architectural invariants honoured:
 *   Invariant 2  (advisory, never authoritative): every fetch fails-soft.
 *                No throws on transient errors.
 *   Invariant 3  (graceful degradation): if cache is cold AND license-server
 *                is unreachable, agents continue running with Company Pack only.
 *   Invariant 10 (push-receive-only): SDK PULLS pack content.
 *                We never push into customer infrastructure.
 *
 * Integration gap closed: PR-D endpoint exists + tested but had no SDK caller.
 * This module is that caller.
 *
 * @connexum/ai-governance  Task #32 — SDK consumer PR-D
 */
/** 1-hour TTL — lets pack updates propagate within the hour. */
export const PACK_CACHE_TTL_MS = 60 * 60 * 1000;
/**
 * Per-process pack content cache keyed by `{packId}:{code}`.
 * Exported for test inspection/resetting.
 */
export const _packCache = new Map();
/**
 * Reset the pack cache. Call between tests to avoid cross-test pollution.
 */
export function __resetPackCacheForTest() {
    _packCache.clear();
}
/* ------------------------------------------------------------------ */
/*  Core fetch implementation (production)                             */
/* ------------------------------------------------------------------ */
/**
 * Production fetchPack implementation using globalThis.fetch (Node 18+ built-in).
 * Timeout: 10 seconds. Never throws — returns a structured error result instead.
 */
export async function defaultFetchPack(packId, code, licenseServerUrl) {
    const url = `${licenseServerUrl.replace(/\/$/, '')}/api/v1/packs/${encodeURIComponent(packId)}@latest`;
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 10_000);
    try {
        const response = await fetch(url, {
            method: 'GET',
            headers: {
                'Authorization': `Bearer ${code}`,
                'Accept': 'application/json',
            },
            signal: controller.signal,
        });
        let body = null;
        try {
            body = await response.json();
        }
        catch {
            body = null;
        }
        return { status: response.status, body };
    }
    catch (err) {
        // Network error / timeout — return a synthetic 503 so callers treat it
        // as a transient failure (Invariant 2: fails-soft).
        return {
            status: 503,
            body: {
                error: 'network_error',
                message: err instanceof Error ? err.message : String(err),
            },
        };
    }
    finally {
        clearTimeout(timeoutId);
    }
}
/**
 * Production emitUnlicensedPackRefused implementation.
 * Pushes to the gov-server audit chain with a 500ms timeout.
 * Errors are swallowed (best-effort per Invariant 2).
 */
export function makeDefaultEmitUnlicensedPackRefused(opts) {
    return async (entry) => {
        if (!opts.internalAuditToken)
            return;
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), 500);
        try {
            await fetch(`${opts.govServerUrl.replace(/\/$/, '')}/api/v1/audit/internal-emit`, {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${opts.internalAuditToken}`,
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    orgId: entry.orgId,
                    eventType: 'UNLICENSED_PACK_REFUSED',
                    entryData: {
                        kind: 'UNLICENSED_PACK_REFUSED',
                        packId: entry.packId,
                        agentId: entry.agentId,
                        orgId: entry.orgId,
                        timestamp: new Date().toISOString(),
                    },
                }),
                signal: controller.signal,
            });
        }
        catch {
            /* best-effort, swallow */
        }
        finally {
            clearTimeout(timeoutId);
        }
    };
}
/**
 * LicensePackLoader — loads compliance packs at runtime boot using the
 * install JWT pack_codes claim.
 *
 * Usage (called once at CLI init after the install JWT is decoded):
 *
 * ```ts
 * const loader = new LicensePackLoader({
 *   licenseServerUrl: 'https://license.my-cc.io',
 *   orgId: 'acme-corp',
 *   monthlyVerifyCache: scheduler,
 * });
 * const { loaded } = await loader.loadPacksFromJwt(packCodes);
 * // loaded: CompliancePack[] — wire into governance runtime
 * ```
 */
export class LicensePackLoader {
    licenseServerUrl;
    orgId;
    monthlyVerifyCache;
    deps;
    constructor(options) {
        this.licenseServerUrl = options.licenseServerUrl;
        this.orgId = options.orgId;
        this.monthlyVerifyCache = options.monthlyVerifyCache;
        // Merge injected deps with defaults (dependency injection for tests).
        this.deps = {
            fetchPack: options.deps?.fetchPack ?? defaultFetchPack,
            emitUnlicensedPackRefused: options.deps?.emitUnlicensedPackRefused ??
                (async () => { }),
        };
    }
    /**
     * Parse, validate, and load each pack from the install JWT pack_codes array.
     *
     * Flow per code entry:
     *   1. Check monthly-verify cache — skip if packId not in licensedPacks.
     *   2. Check in-process TTL cache — return cached pack if fresh.
     *   3. Fetch from license server (PR-D endpoint).
     *   4. On 200: cache + return loaded pack.
     *   5. On 403: log UNLICENSED_PACK_REFUSED (best-effort audit) + skip.
     *   6. On any other error: log + skip (Invariant 2: fails-soft).
     *
     * @param packCodes - Array decoded from the install JWT `pack_codes` claim.
     * @returns PackLoadSummary with results per entry + array of loaded packs.
     */
    async loadPacksFromJwt(packCodes) {
        if (!Array.isArray(packCodes) || packCodes.length === 0) {
            return { results: [], loaded: [] };
        }
        const licensedPacks = this.monthlyVerifyCache?.getLicensedPacks() ?? null;
        const results = [];
        for (const entry of packCodes) {
            const result = await this._loadSingle(entry, licensedPacks);
            results.push(result);
        }
        const loaded = results
            .filter((r) => r.status === 'loaded' || r.status === 'cached')
            .map((r) => r.pack);
        return { results, loaded };
    }
    /**
     * Load a single pack entry. Never throws.
     */
    async _loadSingle(entry, licensedPacks) {
        const { packId, code, agentId } = entry;
        // -------------------------------------------------------------------
        // Step 1: Pre-check the monthly-verify cache.
        // If cache is populated and this packId is NOT in licensedPacks, skip
        // the fetch entirely (avoid wasteful 403 round-trip per design spec).
        // If cache is null (cold start), allow the fetch to proceed.
        // -------------------------------------------------------------------
        if (licensedPacks !== null && !licensedPacks.includes(packId)) {
            // Don't emit an audit entry for the pre-check skip — the pack was
            // never attempted; the licensedPacks list is the authoritative gate.
            return { status: 'skipped_unlicensed', packId };
        }
        // -------------------------------------------------------------------
        // Step 2: In-process TTL cache check.
        // Key is pack + code (a different agent / code pair should get its
        // own cached response to avoid bleeding between agents).
        // -------------------------------------------------------------------
        const cacheKey = `${packId}:${code}`;
        const cached = _packCache.get(cacheKey);
        if (cached && cached.expiresAt > Date.now()) {
            return { status: 'cached', packId, pack: cached.pack };
        }
        // -------------------------------------------------------------------
        // Step 3: Fetch from license server (PR-D endpoint).
        // -------------------------------------------------------------------
        let fetchResult;
        try {
            fetchResult = await this.deps.fetchPack(packId, code, this.licenseServerUrl);
        }
        catch (err) {
            // Defensive: deps.fetchPack should never throw (it handles errors internally),
            // but if it does we fail-soft per Invariant 2.
            return {
                status: 'fetch_error',
                packId,
                error: err instanceof Error ? err.message : String(err),
            };
        }
        // -------------------------------------------------------------------
        // Step 4: Handle 200 — cache and return.
        // -------------------------------------------------------------------
        if (fetchResult.status === 200) {
            const body = fetchResult.body;
            const pack = body?.pack;
            if (!pack || typeof pack.id !== 'string') {
                return {
                    status: 'fetch_error',
                    packId,
                    error: 'license-server returned 200 but pack body is missing or malformed',
                };
            }
            // Cache with 1-hour TTL.
            _packCache.set(cacheKey, {
                pack,
                expiresAt: Date.now() + PACK_CACHE_TTL_MS,
            });
            return { status: 'loaded', packId, pack };
        }
        // -------------------------------------------------------------------
        // Step 5: Handle 403 (license_not_active) — emit UNLICENSED_PACK_REFUSED.
        // Per Invariant 2: log + skip; do NOT throw.
        // -------------------------------------------------------------------
        if (fetchResult.status === 403) {
            // Log locally.
            console.warn(`[LicensePackLoader] UNLICENSED_PACK_REFUSED packId=${packId} agentId=${agentId} ` +
                `org=${this.orgId} — pack will not be loaded. ` +
                `Upgrade at https://license.my-cc.io/billing/packs/${packId}`);
            // Emit to gov-server audit chain (best-effort, non-blocking).
            void this.deps.emitUnlicensedPackRefused({
                packId,
                agentId,
                orgId: this.orgId,
                code,
            });
            return { status: 'skipped_unlicensed', packId };
        }
        // -------------------------------------------------------------------
        // Step 6: Any other status (5xx, network error, etc.) — fail-soft.
        // -------------------------------------------------------------------
        const errorBody = fetchResult.body;
        const errorMsg = (typeof errorBody?.message === 'string' ? errorBody.message : null) ??
            (typeof errorBody?.error === 'string' ? errorBody.error : null) ??
            `HTTP ${fetchResult.status}`;
        return { status: 'fetch_error', packId, error: errorMsg };
    }
}
//# sourceMappingURL=license-pack-loader.js.map