"use strict";
/**
 * ToolProviderHealthVerifier -- Active per-provider health verification.
 *
 * BUILD_PLAN_V3_FULL_LIFECYCLE.md line 236:
 *   "Tool-provider health verification surfaced on agent detail page
 *    (provider still compliant under active pack)"
 *
 * WHY: ProviderCompliance answers "Does Anthropic offer a BAA?" at config
 * time. This module answers a different question: "Right now, is this
 * specific provider still healthy for this specific agent under its bound
 * packs?" Provider posture changes underneath you. You signed a BAA on
 * Jan 1; the provider revokes their BAA framework on Mar 15; your
 * HIPAA-bound agent keeps calling them. Without active verification, you
 * find out at the next audit.
 *
 * Four-signal composite (all must pass for HEALTHY):
 *   1. Risk score: ProviderCompliance.check() pass (score derived from
 *      compliance posture) must exceed pack threshold (>= 0.85 strict,
 *      >= 0.70 non-strict).
 *   2. Agreements: TenantProviderAgreementRegistry.requireActive() must
 *      not throw (no missing BAA/DPA/SCC/BCR kinds).
 *   3. Pack compliance: provider capabilities satisfy all pack-required
 *      modes (data-residency, encryption-at-rest).
 *   4. Connectivity: operator-supplied probe callback must respond within
 *      5 s. Optional signal -- absent probe skips this check.
 *
 * State machine:
 *   HEALTHY   -- all four signals green.
 *   DEGRADED  -- 1+ signal warning (agreement expiring <= 30 days; score
 *                0.05 below threshold). Agent continues; operator alerted.
 *   UNHEALTHY -- 1+ signal hard-failed. Agent invocations blocked.
 *
 * Gate ordering (PreToolUse chain):
 *   AFTER capability-change detection (PR #31)
 *   AFTER autonomy spectrum (PR #24)
 *   BEFORE tool rate limit (PR #14)
 *
 * Compliance bindings:
 *   HIPAA 45 CFR 164.308(b): BAA requirement per covered entity
 *   HIPAA 45 CFR 164.308(a)(1)(ii)(D): evaluation of technical controls
 *   SOC 2 CC9.2: vendor monitoring
 *   GDPR Art. 28: controller-processor DPA requirement
 *   DORA Art. 28-30: ICT third-party risk continuous assessment
 *
 * @connexum/ai-governance
 */
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.DEFAULT_NON_STRICT_INTERVAL_MS = exports.DEFAULT_STRICT_INTERVAL_MS = exports.AGREEMENT_EXPIRY_WARNING_DAYS = exports.PROBE_TIMEOUT_MS = exports.NON_STRICT_SCORE_THRESHOLD = exports.STRICT_SCORE_THRESHOLD = exports.STRICT_PACK_IDS = exports.ToolProviderHealthVerifier = exports.HealthPermissionError = exports.EmptyPackBindingHealthError = exports.ToolProviderUnhealthyError = void 0;
exports.defaultProviderHealthCheckIntervalMs = defaultProviderHealthCheckIntervalMs;
const crypto = __importStar(require("node:crypto"));
const log_1 = require("../util/log");
const log = (0, log_1.createLogger)('tool-provider-health');
// ---------------------------------------------------------------------------
// Errors
// ---------------------------------------------------------------------------
class ToolProviderUnhealthyError extends Error {
    orgId;
    tenantId;
    agentId;
    providerId;
    packBinding;
    constructor(opts) {
        super(`Provider '${opts.providerId}' is UNHEALTHY for agent '${opts.agentId}' ` +
            `(tenant: ${opts.tenantId}). Tool invocation blocked. Detail: ${opts.detail}`);
        this.name = 'ToolProviderUnhealthyError';
        this.orgId = opts.orgId;
        this.tenantId = opts.tenantId;
        this.agentId = opts.agentId;
        this.providerId = opts.providerId;
        this.packBinding = opts.packBinding;
    }
}
exports.ToolProviderUnhealthyError = ToolProviderUnhealthyError;
class EmptyPackBindingHealthError extends Error {
    constructor(operation) {
        super(`Operation '${operation}' requires a non-empty packBinding. ` +
            `Every health verification must be tied to at least one compliance pack.`);
        this.name = 'EmptyPackBindingHealthError';
    }
}
exports.EmptyPackBindingHealthError = EmptyPackBindingHealthError;
class HealthPermissionError extends Error {
    operatorId;
    constructor(operatorId, action, reason) {
        super(`Permission denied for operator '${operatorId}' attempting '${action}': ${reason}. ` +
            `Only ADMIN-role operators may configure health probe schedules.`);
        this.name = 'HealthPermissionError';
        this.operatorId = operatorId;
    }
}
exports.HealthPermissionError = HealthPermissionError;
// ---------------------------------------------------------------------------
// STRICT_PACK_IDS -- packs that require the higher 0.85 risk-score threshold
// and the 5-minute probe interval.
// ---------------------------------------------------------------------------
const STRICT_PACK_IDS = new Set([
    'hipaa',
    'gdpr',
    'pci-dss',
    'pcidss',
    'dora',
    'hitech',
    'fedramp',
    'cmmc2',
    'cjis',
    'itar',
]);
exports.STRICT_PACK_IDS = STRICT_PACK_IDS;
const STRICT_SCORE_THRESHOLD = 0.85;
exports.STRICT_SCORE_THRESHOLD = STRICT_SCORE_THRESHOLD;
const NON_STRICT_SCORE_THRESHOLD = 0.70;
exports.NON_STRICT_SCORE_THRESHOLD = NON_STRICT_SCORE_THRESHOLD;
const DEGRADED_SCORE_BAND = 0.05;
const PROBE_TIMEOUT_MS = 5_000;
exports.PROBE_TIMEOUT_MS = PROBE_TIMEOUT_MS;
const AGREEMENT_EXPIRY_WARNING_DAYS = 30;
exports.AGREEMENT_EXPIRY_WARNING_DAYS = AGREEMENT_EXPIRY_WARNING_DAYS;
const DEFAULT_STRICT_INTERVAL_MS = 300_000; // 5 min
exports.DEFAULT_STRICT_INTERVAL_MS = DEFAULT_STRICT_INTERVAL_MS;
const DEFAULT_NON_STRICT_INTERVAL_MS = 3_600_000; // 1 hr
exports.DEFAULT_NON_STRICT_INTERVAL_MS = DEFAULT_NON_STRICT_INTERVAL_MS;
// ---------------------------------------------------------------------------
// ToolProviderHealthVerifier
// ---------------------------------------------------------------------------
class ToolProviderHealthVerifier {
    providerCompliance;
    agreementRegistry;
    operatorRoles;
    /** In-memory status cache. Key: `${orgId}::${tenantId}::${agentId}::${providerId}` */
    statusCache = new Map();
    /** Operator-supplied probe callbacks. Key: providerId */
    probes = new Map();
    /** Status-change subscribers. */
    subscribers = [];
    /** SHA-256 of the last audit event emitted (for chaining). */
    lastAuditSha = null;
    /** Active probe schedulers. */
    schedulers = new Map();
    constructor(opts) {
        this.providerCompliance = opts.providerCompliance;
        this.agreementRegistry = opts.agreementRegistry;
        this.operatorRoles = opts.operatorRoles;
    }
    // -------------------------------------------------------------------------
    // setProbe
    // -------------------------------------------------------------------------
    /**
     * Wire an operator-supplied connectivity probe for a provider.
     * Optional: not all providers expose a cheap health endpoint.
     * Probe is ONE of FOUR signals; a green probe with failing agreements
     * still produces UNHEALTHY.
     */
    setProbe(providerId, probeCallback) {
        this.probes.set(providerId.toLowerCase(), probeCallback);
        log.info(`Probe registered for provider '${providerId}'`);
    }
    // -------------------------------------------------------------------------
    // subscribeStatusChanges
    // -------------------------------------------------------------------------
    /**
     * Subscribe to status transitions. Callback fires synchronously after each
     * status change. Multiple subscribers are supported.
     */
    subscribeStatusChanges(callback) {
        this.subscribers.push(callback);
    }
    // -------------------------------------------------------------------------
    // scheduleProbe
    // -------------------------------------------------------------------------
    /**
     * Schedule periodic health probes for an agent-provider pair.
     * Requires 'governance.configure' permission (ADMIN role).
     *
     * Default intervalMs:
     *   - Active agent under strict pack: 300_000 (5 min)
     *   - Active agent under non-strict pack: 3_600_000 (1 hr)
     *
     * @throws HealthPermissionError when operator lacks ADMIN role.
     * @throws EmptyPackBindingHealthError when packBinding is empty.
     */
    scheduleProbe(orgId, tenantId, agentId, providerId, intervalMs, packBinding, operatorId) {
        // Pack-binding primacy invariant
        if (!packBinding || packBinding.length === 0) {
            throw new EmptyPackBindingHealthError('scheduleProbe');
        }
        // Permission check
        const permCheck = this.operatorRoles.checkPermission(operatorId, 'governance.configure');
        if (!permCheck.allowed) {
            throw new HealthPermissionError(operatorId, 'scheduleProbe', permCheck.reason);
        }
        const schedKey = this.cacheKey(orgId, tenantId, agentId, providerId);
        // Clear existing timer if any
        const existing = this.schedulers.get(schedKey);
        if (existing) {
            clearInterval(existing.timer);
        }
        const timer = setInterval(() => {
            this.verifyHealth(orgId, tenantId, agentId, providerId, packBinding).catch(err => {
                log.warn(`Scheduled probe failed for ${schedKey}`, { err: String(err) });
            });
        }, intervalMs);
        // Allow Node.js to exit even if timers are still registered
        if (timer.unref)
            timer.unref();
        this.schedulers.set(schedKey, {
            orgId,
            tenantId,
            agentId,
            providerId,
            intervalMs,
            packBinding,
            timer,
        });
        log.info(`Probe scheduled for ${schedKey} every ${intervalMs} ms`);
    }
    /**
     * Cancel a scheduled probe. Cleans up the interval timer.
     */
    cancelSchedule(orgId, tenantId, agentId, providerId) {
        const key = this.cacheKey(orgId, tenantId, agentId, providerId);
        const entry = this.schedulers.get(key);
        if (entry) {
            clearInterval(entry.timer);
            this.schedulers.delete(key);
        }
    }
    // -------------------------------------------------------------------------
    // getStatus
    // -------------------------------------------------------------------------
    /**
     * Return the last computed status without forcing a fresh check.
     * Returns null when no check has been performed yet.
     *
     * Note: this is a read-only accessor. There is no public setter -- status
     * entries are computed-only (PT-PROVIDER-HEALTH-3 defence: cache-poisoning
     * prevention).
     */
    getStatus(orgId, tenantId, agentId, providerId) {
        const entry = this.statusCache.get(this.cacheKey(orgId, tenantId, agentId, providerId));
        return entry ? entry.result : null;
    }
    // -------------------------------------------------------------------------
    // verifyHealth
    // -------------------------------------------------------------------------
    /**
     * Run all four signals and return a composite health result.
     * Updates the in-memory cache and emits TOOL_PROVIDER_HEALTH_CHANGED if
     * the status transitions.
     *
     * @throws EmptyPackBindingHealthError when packBinding is empty.
     */
    async verifyHealth(orgId, tenantId, agentId, providerId, packBinding) {
        // Pack-binding primacy invariant
        if (!packBinding || packBinding.length === 0) {
            throw new EmptyPackBindingHealthError('verifyHealth');
        }
        const normalizedProviderId = providerId.toLowerCase();
        const isStrict = packBinding.some(p => STRICT_PACK_IDS.has(p.toLowerCase()));
        const threshold = isStrict ? STRICT_SCORE_THRESHOLD : NON_STRICT_SCORE_THRESHOLD;
        // Signal 1: Risk score
        const riskScoreSignal = this.evaluateRiskScore(normalizedProviderId, packBinding, threshold);
        // Signal 2: Agreements
        const agreementSignal = this.evaluateAgreements(tenantId, normalizedProviderId, packBinding);
        // Signal 3: Pack compliance modes
        const packComplianceSignal = this.evaluatePackCompliance(normalizedProviderId, packBinding);
        // Signal 4: Connectivity probe (async, 5 s timeout)
        const connectivitySignal = await this.evaluateConnectivity(normalizedProviderId);
        const signals = {
            riskScore: riskScoreSignal,
            agreements: agreementSignal,
            packCompliance: packComplianceSignal,
            connectivity: connectivitySignal,
        };
        const status = this.deriveStatus(signals);
        const checkedAtNs = process.hrtime.bigint();
        const result = {
            status,
            signals,
            checkedAtNs,
            orgId,
            tenantId,
            agentId,
            providerId: normalizedProviderId,
            packBinding,
        };
        // Update cache + emit events if status changed
        const cacheKey = this.cacheKey(orgId, tenantId, agentId, providerId);
        const prior = this.statusCache.get(cacheKey);
        const priorStatus = prior ? prior.result.status : null;
        this.statusCache.set(cacheKey, { result, computedAtMs: Date.now() });
        if (priorStatus !== status) {
            const severity = this.transitionSeverity(priorStatus, status);
            this.emitAuditEvent({
                type: 'TOOL_PROVIDER_HEALTH_CHANGED',
                severity,
                orgId,
                tenantId,
                agentId,
                providerId: normalizedProviderId,
                priorStatus,
                currentStatus: status,
                packBinding,
                signals,
            });
            for (const sub of this.subscribers) {
                try {
                    sub(orgId, tenantId, agentId, normalizedProviderId, priorStatus, status);
                }
                catch (subErr) {
                    log.warn('Status-change subscriber threw', { err: String(subErr) });
                }
            }
        }
        return result;
    }
    // -------------------------------------------------------------------------
    // Pre-tool gate
    // -------------------------------------------------------------------------
    /**
     * PreToolUse gate. Call this when an agent attempts to invoke a
     * provider-bound tool.
     *
     * Behaviour:
     *   - If cached status is fresh (< intervalMs old), uses cache.
     *   - If cache is stale (>= intervalMs), forces a re-check
     *     (PT-PROVIDER-HEALTH-1 stale-status attack mitigation).
     *   - UNHEALTHY: throws ToolProviderUnhealthyError (fail-closed).
     *   - DEGRADED: emits TOOL_PROVIDER_DEGRADED_USAGE WARNING, allows.
     *   - HEALTHY: passes silently.
     *
     * @throws ToolProviderUnhealthyError when provider is UNHEALTHY.
     * @throws EmptyPackBindingHealthError when packBinding is empty.
     */
    async enforceGate(orgId, tenantId, agentId, providerId, packBinding, intervalMs) {
        if (!packBinding || packBinding.length === 0) {
            throw new EmptyPackBindingHealthError('enforceGate');
        }
        const cacheKey = this.cacheKey(orgId, tenantId, agentId, providerId);
        const cached = this.statusCache.get(cacheKey);
        const effectiveIntervalMs = intervalMs ?? this.defaultIntervalMs(packBinding);
        let result;
        if (!cached || Date.now() - cached.computedAtMs >= effectiveIntervalMs) {
            // PT-PROVIDER-HEALTH-1: stale-status attack mitigation --
            // force re-check when cache is older than interval.
            result = await this.verifyHealth(orgId, tenantId, agentId, providerId, packBinding);
        }
        else {
            result = cached.result;
        }
        if (result.status === 'UNHEALTHY') {
            const unhealthyDetail = this.buildUnhealthyDetail(result.signals);
            throw new ToolProviderUnhealthyError({
                orgId,
                tenantId,
                agentId,
                providerId,
                packBinding,
                detail: unhealthyDetail,
            });
        }
        if (result.status === 'DEGRADED') {
            this.emitAuditEvent({
                type: 'TOOL_PROVIDER_DEGRADED_USAGE',
                severity: 'WARNING',
                orgId,
                tenantId,
                agentId,
                providerId: providerId.toLowerCase(),
                priorStatus: result.status,
                currentStatus: result.status,
                packBinding,
            });
            log.warn(`Agent '${agentId}' invoked provider '${providerId}' while health is DEGRADED`, { orgId, tenantId });
        }
    }
    // -------------------------------------------------------------------------
    // Private: signal evaluators
    // -------------------------------------------------------------------------
    evaluateRiskScore(providerId, packBinding, threshold) {
        const check = this.providerCompliance.check(providerId);
        // Derive a 0-1 risk score from the compliance check result.
        // ProviderCompliance does not yet expose a numeric getScore() method.
        // We derive an approximation from the check output:
        //   - Unknown / hard-blocked provider: 0.0
        //   - Allowed with blockedByPacks non-empty (permissive mode): 0.50
        //   - Allowed, no reasons: score based on capability count relative to
        //     what the packs require.
        let score;
        if (!check.allowed) {
            score = 0.0;
        }
        else if (check.blockedByPacks.length > 0) {
            score = 0.50;
        }
        else {
            // Count how many pack-required capabilities the provider actually has.
            const caps = new Set(check.complianceCaps.map(c => c.toLowerCase()));
            const required = this.requiredCapabilitiesForPacks(packBinding);
            if (required.length === 0) {
                // No specific requirements: full score.
                score = 1.0;
            }
            else {
                const matched = required.filter(c => caps.has(c.toLowerCase())).length;
                score = matched / required.length;
            }
        }
        const hardFail = score < threshold;
        const inWarnBand = !hardFail && score < threshold + DEGRADED_SCORE_BAND;
        let detail;
        if (hardFail) {
            detail = `Risk score ${score.toFixed(3)} is below threshold ${threshold} for provider '${providerId}'.`;
        }
        else if (inWarnBand) {
            detail = `Risk score ${score.toFixed(3)} is within warning band (threshold ${threshold}); monitor closely.`;
        }
        else {
            detail = `Risk score ${score.toFixed(3)} exceeds threshold ${threshold}.`;
        }
        return { ok: !hardFail, warning: inWarnBand, score, threshold, detail };
    }
    evaluateAgreements(tenantId, providerId, packBinding) {
        // Determine required agreement kinds from packs.
        const requiredKinds = this.requiredAgreementKinds(packBinding);
        if (requiredKinds.length === 0) {
            return {
                ok: true,
                warning: false,
                missingKinds: [],
                expiringKinds: [],
                detail: 'No agreement kinds required by bound packs.',
            };
        }
        // PT-PROVIDER-HEALTH-2 defence: even if probe is green, agreements must pass.
        let missing = [];
        try {
            this.agreementRegistry.requireActive(tenantId, providerId, requiredKinds, packBinding);
        }
        catch (err) {
            const errMsg = err instanceof Error ? err.message : String(err);
            // Extract missing kinds from the error message or default to all required.
            // MissingTenantProviderAgreementError exposes missingKinds property.
            const asTyped = err;
            missing = asTyped.missingKinds ?? requiredKinds;
            log.warn(`Agreement check failed for tenant '${tenantId}' provider '${providerId}'`, {
                error: errMsg,
            });
        }
        // Check for expiring agreements (DEGRADED signal).
        let expiringKinds = [];
        try {
            const expiring = this.agreementRegistry.listExpiringWithinForProvider(tenantId, providerId, AGREEMENT_EXPIRY_WARNING_DAYS);
            expiringKinds = expiring.map(r => r.agreementKind);
        }
        catch {
            // Best-effort; do not block the gate on introspection failure.
        }
        const ok = missing.length === 0;
        const warning = ok && expiringKinds.length > 0;
        let detail;
        if (!ok) {
            detail = `Missing required agreement kind(s): [${missing.join(', ')}] with provider '${providerId}' for tenant '${tenantId}'.`;
        }
        else if (warning) {
            detail = `Agreement kind(s) [${expiringKinds.join(', ')}] expiring within ${AGREEMENT_EXPIRY_WARNING_DAYS} days.`;
        }
        else {
            detail = `All required agreement kinds [${requiredKinds.join(', ')}] are active.`;
        }
        return { ok, warning, missingKinds: missing, expiringKinds, detail };
    }
    evaluatePackCompliance(providerId, packBinding) {
        const check = this.providerCompliance.check(providerId);
        const caps = new Set(check.complianceCaps.map(c => c.toLowerCase()));
        // Check pack-required modes: data residency, encryption-at-rest.
        const packRequiredModes = this.packRequiredModes(packBinding);
        const missing = packRequiredModes.filter(m => !caps.has(m.toLowerCase()));
        const ok = missing.length === 0;
        const detail = ok
            ? `Provider satisfies all pack-required modes [${packRequiredModes.join(', ') || 'none'}].`
            : `Provider missing required modes: [${missing.join(', ')}].`;
        return { ok, warning: false, missingModes: missing, detail };
    }
    async evaluateConnectivity(providerId) {
        const probe = this.probes.get(providerId.toLowerCase());
        if (!probe) {
            return {
                ok: true,
                warning: false,
                skipped: true,
                detail: 'No probe registered for this provider; connectivity signal skipped.',
            };
        }
        const startMs = Date.now();
        try {
            await Promise.race([
                probe(),
                new Promise((_, reject) => setTimeout(() => reject(new Error(`Probe timed out after ${PROBE_TIMEOUT_MS} ms`)), PROBE_TIMEOUT_MS)),
            ]);
            const latencyMs = Date.now() - startMs;
            return {
                ok: true,
                warning: false,
                skipped: false,
                latencyMs,
                detail: `Probe succeeded in ${latencyMs} ms.`,
            };
        }
        catch (err) {
            const latencyMs = Date.now() - startMs;
            const errMsg = err instanceof Error ? err.message : String(err);
            log.warn(`Connectivity probe failed for provider '${providerId}'`, { error: errMsg });
            return {
                ok: false,
                warning: false,
                skipped: false,
                latencyMs,
                detail: `Probe failed: ${errMsg}`,
            };
        }
    }
    // -------------------------------------------------------------------------
    // Private: status derivation
    // -------------------------------------------------------------------------
    deriveStatus(signals) {
        const { riskScore, agreements, packCompliance, connectivity } = signals;
        // UNHEALTHY: any hard failure
        if (!riskScore.ok ||
            !agreements.ok ||
            !packCompliance.ok ||
            !connectivity.ok) {
            return 'UNHEALTHY';
        }
        // DEGRADED: any warning
        if (riskScore.warning ||
            agreements.warning ||
            packCompliance.warning ||
            connectivity.warning) {
            return 'DEGRADED';
        }
        return 'HEALTHY';
    }
    // -------------------------------------------------------------------------
    // Private: audit chain emission
    // -------------------------------------------------------------------------
    emitAuditEvent(event) {
        const fullEvent = {
            ...event,
            timestamp: new Date().toISOString(),
            prevSha: this.lastAuditSha ?? undefined,
        };
        const eventJson = JSON.stringify(fullEvent);
        this.lastAuditSha = crypto.createHash('sha256').update(eventJson).digest('hex');
        log.info(`[AUDIT] ${fullEvent.type} | ${fullEvent.severity}`, {
            orgId: fullEvent.orgId,
            tenantId: fullEvent.tenantId,
            agentId: fullEvent.agentId,
            providerId: fullEvent.providerId,
            prior: fullEvent.priorStatus,
            current: fullEvent.currentStatus,
            prevSha: fullEvent.prevSha,
        });
    }
    // -------------------------------------------------------------------------
    // Private: helpers
    // -------------------------------------------------------------------------
    cacheKey(orgId, tenantId, agentId, providerId) {
        return `${orgId}::${tenantId}::${agentId}::${providerId.toLowerCase()}`;
    }
    transitionSeverity(prior, current) {
        if (current === 'UNHEALTHY')
            return 'ERROR';
        if (current === 'DEGRADED')
            return 'WARNING';
        // Recovery transitions
        return 'NOTICE';
    }
    defaultIntervalMs(packBinding) {
        const isStrict = packBinding.some(p => STRICT_PACK_IDS.has(p.toLowerCase()));
        return isStrict ? DEFAULT_STRICT_INTERVAL_MS : DEFAULT_NON_STRICT_INTERVAL_MS;
    }
    /**
     * Derive required capability strings for a set of packs.
     * HIPAA requires BAA; GDPR requires GDPR-DPA; PCI-DSS requires SOC2-TYPE2.
     */
    requiredCapabilitiesForPacks(packBinding) {
        const caps = new Set();
        for (const pack of packBinding) {
            const p = pack.toLowerCase();
            if (p === 'hipaa' || p === 'hitech') {
                caps.add('BAA');
                caps.add('SOC2-TYPE2');
            }
            if (p === 'gdpr' || p === 'uk-gdpr') {
                caps.add('GDPR-DPA');
            }
            if (p === 'pci-dss' || p === 'pcidss') {
                caps.add('SOC2-TYPE2');
            }
            if (p === 'dora') {
                caps.add('SOC2-TYPE2');
            }
        }
        return Array.from(caps);
    }
    /**
     * Derive required agreement kinds from packs.
     * HIPAA: BAA | GDPR: DPA | DORA: DPA | PCI-DSS: DPA
     */
    requiredAgreementKinds(packBinding) {
        const kinds = new Set();
        for (const pack of packBinding) {
            const p = pack.toLowerCase();
            if (p === 'hipaa' || p === 'hitech')
                kinds.add('BAA');
            if (p === 'gdpr' || p === 'uk-gdpr' || p === 'dora' || p === 'pci-dss' || p === 'pcidss') {
                kinds.add('DPA');
            }
        }
        return Array.from(kinds);
    }
    /**
     * Derive required provider operational modes from packs.
     * E.g. HIPAA requires encryption-at-rest.
     */
    packRequiredModes(packBinding) {
        const modes = new Set();
        for (const pack of packBinding) {
            const p = pack.toLowerCase();
            if (p === 'hipaa' || p === 'hitech') {
                modes.add('BAA');
            }
            if (p === 'gdpr' || p === 'uk-gdpr') {
                modes.add('GDPR-DPA');
            }
        }
        return Array.from(modes);
    }
    buildUnhealthyDetail(signals) {
        const parts = [];
        if (!signals.riskScore.ok)
            parts.push(`RiskScore: ${signals.riskScore.detail}`);
        if (!signals.agreements.ok)
            parts.push(`Agreements: ${signals.agreements.detail}`);
        if (!signals.packCompliance.ok)
            parts.push(`PackCompliance: ${signals.packCompliance.detail}`);
        if (!signals.connectivity.ok)
            parts.push(`Connectivity: ${signals.connectivity.detail}`);
        return parts.join(' | ');
    }
}
exports.ToolProviderHealthVerifier = ToolProviderHealthVerifier;
// ---------------------------------------------------------------------------
// defaultProviderHealthCheckIntervalMs -- exported for pack authors
// ---------------------------------------------------------------------------
/**
 * Returns the default probe interval for a given pack strictness level.
 * Pack authors use this in their CompliancePack definition.
 */
function defaultProviderHealthCheckIntervalMs(isStrict) {
    return isStrict ? DEFAULT_STRICT_INTERVAL_MS : DEFAULT_NON_STRICT_INTERVAL_MS;
}
//# sourceMappingURL=tool-provider-health.js.map