"use strict";
/**
 * SubscriptionGate -- S-03
 *
 * Runtime subscription enforcement. Wired into GovernanceRuntime boot.
 * Resolves the VendorCode from env (CXNI_VENDOR_CODE) or .governance.json,
 * exchanges it for an Entitlement Token, and enforces fail-closed policy.
 *
 * Failure modes:
 *   SUBSCRIPTION_NOT_CONFIGURED  -- no vendor code found
 *   SUBSCRIPTION_INACTIVE        -- vendor code is revoked
 *   SUBSCRIPTION_GRACE           -- payment failed, within grace window (proceed + warn)
 *
 * Pack floors (strictest-wins):
 *   HIPAA / HITECH: grace window = 24h
 *   PCI DSS:        grace window = 24h
 *   SOC 2:          grace window = 48h
 *   Default:        grace window = 72h
 *
 * @connexum/ai-governance S-03
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubscriptionGate = exports.GRACE_WINDOWS_MS = exports.SubscriptionInactiveError = exports.SubscriptionNotConfiguredError = void 0;
exports.resolveGraceWindowMs = resolveGraceWindowMs;
exports.checkSubscriptionGate = checkSubscriptionGate;
exports.signOfflineGraceAcknowledgement = signOfflineGraceAcknowledgement;
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const crypto = __importStar(require("node:crypto"));
const entitlement_client_js_1 = require("./entitlement-client.js");
// ---------------------------------------------------------------------------
// Errors
// ---------------------------------------------------------------------------
class SubscriptionNotConfiguredError extends Error {
    constructor() {
        super('SUBSCRIPTION_NOT_CONFIGURED: No vendor code found for this governance installation. ' +
            'Set CXNI_VENDOR_CODE in your environment or run `npx @connexum/ai-governance init` ' +
            'to configure a subscription. See docs/INSTALL.md#subscription for instructions.');
        this.name = 'SubscriptionNotConfiguredError';
    }
}
exports.SubscriptionNotConfiguredError = SubscriptionNotConfiguredError;
class SubscriptionInactiveError extends Error {
    subscriptionStatus;
    constructor(status, detail) {
        super(`SUBSCRIPTION_INACTIVE: Your @connexum/ai-governance subscription is ${status}. ` +
            (detail ?? 'Visit the billing portal to restore your subscription.'));
        this.name = 'SubscriptionInactiveError';
        this.subscriptionStatus = status;
    }
}
exports.SubscriptionInactiveError = SubscriptionInactiveError;
// ---------------------------------------------------------------------------
// Grace window constants
// ---------------------------------------------------------------------------
/** Grace window floor in ms per pack type */
exports.GRACE_WINDOWS_MS = {
    hipaa: 24 * 60 * 60 * 1000,
    hitech: 24 * 60 * 60 * 1000,
    pci_dss: 24 * 60 * 60 * 1000,
    soc2: 48 * 60 * 60 * 1000,
    soc_2: 48 * 60 * 60 * 1000,
    default: 72 * 60 * 60 * 1000,
};
/**
 * Return the strictest (shortest) grace window for the given active pack IDs.
 * Strictest-wins: the shortest window determines the deadline.
 */
function resolveGraceWindowMs(activePackIds) {
    let floor = exports.GRACE_WINDOWS_MS.default;
    for (const id of activePackIds) {
        const packFloor = exports.GRACE_WINDOWS_MS[id.toLowerCase()];
        if (packFloor !== undefined && packFloor < floor) {
            floor = packFloor;
        }
    }
    return floor;
}
// ---------------------------------------------------------------------------
// SubscriptionGate
// ---------------------------------------------------------------------------
class SubscriptionGate {
    cfg;
    client;
    currentStatus;
    revalidateTimer;
    lastGraceWarnDate;
    constructor(config) {
        this.cfg = {
            activePackIds: [],
            governanceConfigPath: path.join(process.cwd(), '.governance.json'),
            revalidateIntervalMs: 24 * 60 * 60 * 1000,
            onStatusChange: () => undefined,
            emitAuditEvent: () => undefined,
            ...config,
        };
    }
    /**
     * Boot gate: resolve vendor code, exchange for token, enforce policy.
     *
     * Throws SubscriptionNotConfiguredError or SubscriptionInactiveError
     * on hard failures. Logs a warning and returns 'grace' for grace mode.
     */
    async boot() {
        const vendorCode = this._resolveVendorCode();
        if (!vendorCode) {
            throw new SubscriptionNotConfiguredError();
        }
        this.client = new entitlement_client_js_1.EntitlementClient({
            vendorCode,
            licenseServerUrl: this.cfg.licenseServerUrl,
            cache: new entitlement_client_js_1.InMemoryEntitlementCache(),
        });
        let token;
        try {
            token = await this.client.getToken();
        }
        catch (err) {
            if (err instanceof entitlement_client_js_1.EntitlementError && err.code === 'vendor_code_not_found') {
                throw new SubscriptionNotConfiguredError();
            }
            if (err instanceof entitlement_client_js_1.EntitlementError && err.code === 'subscription_inactive') {
                throw new SubscriptionInactiveError('revoked');
            }
            // Network failure -- fail-closed unless explicitly configured otherwise
            throw new SubscriptionInactiveError('revoked', `License server unreachable: ${err instanceof Error ? err.message : String(err)}`);
        }
        const { status, graceUntil } = token.claims;
        this.currentStatus = status;
        if (status === 'revoked') {
            throw new SubscriptionInactiveError('revoked');
        }
        if (status === 'grace') {
            this._emitGraceWarning(token.claims);
            // Start background revalidation
            this._startRevalidation();
            return 'grace';
        }
        // Active
        this._startRevalidation();
        return 'active';
    }
    /**
     * Stop background revalidation timer (clean shutdown).
     */
    stop() {
        if (this.revalidateTimer !== undefined) {
            clearInterval(this.revalidateTimer);
            this.revalidateTimer = undefined;
        }
    }
    /**
     * Force an immediate revalidation check.
     */
    async revalidateNow() {
        if (!this.client) {
            throw new SubscriptionNotConfiguredError();
        }
        const status = await this.client.revalidate();
        const previous = this.currentStatus ?? 'active';
        if (status !== previous) {
            this.currentStatus = status;
            const token = await this.client.getToken().catch(() => null);
            this.cfg.onStatusChange(previous, status, token?.claims.graceUntil);
        }
        return status;
    }
    /**
     * Return the current cached subscription status.
     * Returns undefined if boot() has not been called.
     */
    getStatus() {
        return this.currentStatus;
    }
    // ---------------------------------------------------------------------------
    // Internal
    // ---------------------------------------------------------------------------
    _resolveVendorCode() {
        // Priority 1: environment variable
        const fromEnv = process.env.CXNI_VENDOR_CODE?.trim();
        if (fromEnv)
            return fromEnv;
        // Priority 2: .governance.json
        try {
            if (fs.existsSync(this.cfg.governanceConfigPath)) {
                const raw = fs.readFileSync(this.cfg.governanceConfigPath, 'utf-8');
                const json = JSON.parse(raw);
                const sub = json.subscription;
                const code = sub?.vendorCode;
                if (typeof code === 'string' && code)
                    return code;
            }
        }
        catch {
            // Config file unreadable -- treat as missing
        }
        return undefined;
    }
    _startRevalidation() {
        if (this.cfg.revalidateIntervalMs <= 0)
            return;
        if (this.revalidateTimer !== undefined)
            return;
        this.revalidateTimer = setInterval(async () => {
            try {
                await this.revalidateNow();
            }
            catch {
                // If revalidation throws (network error), keep current state
                // but emit a warning
                this.cfg.emitAuditEvent('subscription.revalidation.failed', {
                    timestamp: new Date().toISOString(),
                });
            }
        }, this.cfg.revalidateIntervalMs);
        if (this.revalidateTimer.unref)
            this.revalidateTimer.unref();
    }
    _emitGraceWarning(claims) {
        const today = new Date().toISOString().split('T')[0];
        if (this.lastGraceWarnDate === today)
            return; // Once per day
        this.lastGraceWarnDate = today;
        const graceWindowMs = resolveGraceWindowMs(this.cfg.activePackIds);
        this.cfg.emitAuditEvent('subscription.grace.warning', {
            tier: claims.tier,
            graceUntil: claims.graceUntil,
            graceWindowMs,
            packFloorApplied: this.cfg.activePackIds.find((id) => (exports.GRACE_WINDOWS_MS[id.toLowerCase()] ?? exports.GRACE_WINDOWS_MS.default) === graceWindowMs) ?? 'default',
            timestamp: new Date().toISOString(),
        });
    }
}
exports.SubscriptionGate = SubscriptionGate;
// ---------------------------------------------------------------------------
// Standalone utility: checkSubscriptionGate
// ---------------------------------------------------------------------------
/**
 * One-shot gate check (no background revalidation). Used for hook scripts
 * and CLI pre-flight. Returns the status or throws.
 */
async function checkSubscriptionGate(licenseServerUrl, activePackIds = []) {
    const gate = new SubscriptionGate({ licenseServerUrl, activePackIds });
    try {
        return await gate.boot();
    }
    finally {
        gate.stop();
    }
}
// ---------------------------------------------------------------------------
// HMAC-signed nonce for offline grace acknowledgement (S-07 offline mode)
// ---------------------------------------------------------------------------
function signOfflineGraceAcknowledgement(vendorCode, acknowledgedAt, secret) {
    return crypto
        .createHmac('sha256', secret)
        .update(`${vendorCode}:${acknowledgedAt}`)
        .digest('hex');
}
//# sourceMappingURL=subscription-gate.js.map