"use strict";
/**
 * CompliancePack -- Interface and loader for pluggable compliance frameworks.
 *
 * Each compliance pack defines:
 *   - Required modules and their configuration
 *   - Validation rules
 *   - Provider restrictions
 *   - Retention policies
 *   - Incident notification deadlines
 *
 * Packs are composable: multiple packs can be active simultaneously.
 * When packs conflict, the strictest rule wins.
 *
 * @connexum/ai-governance
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.GovernanceMigrationError = exports.MigrationManifest = exports.VersionedPackRegistry = exports.PackRegistry = void 0;
exports.compareSemver = compareSemver;
const log_1 = require("../util/log");
const check_registry_1 = require("./check-registry");
const log = (0, log_1.createLogger)('compliance-packs');
/**
 * Registry for managing compliance packs.
 *
 * Per-tenant scoping (F-NEW-PACK-HOT-RELOAD-1, 2026-05-04):
 *   activePacksByTenant tracks per-tenant pack selections. When a tenant
 *   has explicit rows, getActiveForTenant() returns those packs only.
 *   When a tenant has NO explicit rows, it falls back to the global
 *   activePacks set (env-var-seeded defaults).
 *
 *   This allows ACTIVE_PACKS env var to seed defaults for all tenants
 *   that haven't individually customised their pack selection, while
 *   tenants that have customised get strict isolation.
 */
class PackRegistry {
    packs = new Map();
    activePacks = new Set();
    /**
     * GAP-S1-01: alias -> canonical pack id mapping.
     * Populated automatically when packs with `aliases` are registered.
     * Keys are stored lowercase for case-insensitive lookup.
     */
    aliasIndex = new Map();
    /**
     * F-NEW-PACK-HOT-RELOAD-1: per-tenant pack selections.
     * Key = tenantId. Value = Set of canonical pack IDs active for that tenant.
     * When a tenant has an entry here, getActiveForTenant() returns ONLY those
     * packs. When absent (and NOT in tenantsWithExplicitMgmt), falls back to
     * the global activePacks (env defaults).
     */
    activePacksByTenant = new Map();
    /**
     * F-NEW-PACK-HOT-RELOAD-1: tracks tenants that have opted into explicit
     * per-tenant pack management (via activateForTenant or deactivateForTenant).
     * Once a tenant opts in, getActiveForTenant() NEVER falls back to global
     * defaults -- it returns only what the tenant has explicitly activated.
     * This prevents a tenant's deactivation from accidentally re-enabling
     * all globally-seeded packs through the fallback path.
     */
    tenantsWithExplicitMgmt = new Set();
    /**
     * F-NEW-PACK-HOT-RELOAD-1: per-tenant mutation version counters.
     * Incremented on every activateForTenant / deactivateForTenant call.
     * PackEvaluator stores the version at cache-build time and compares on
     * cache hit -- if the version has advanced, the cache entry is stale
     * and must be rebuilt. This avoids relying on cross-bus event delivery
     * for cache invalidation.
     */
    tenantVersions = new Map();
    /**
     * Return the current mutation version for a tenant. Returns 0 if the
     * tenant has never been explicitly managed.
     */
    getTenantVersion(tenantId) {
        return this.tenantVersions.get(tenantId) ?? 0;
    }
    /**
     * Register a compliance pack.
     * If the pack declares `aliases`, each alias is indexed so that
     * `get(alias)` and `activate(alias)` resolve to the same pack.
     */
    register(pack) {
        this.packs.set(pack.id, pack);
        // Index aliases (case-insensitive)
        if (pack.aliases) {
            for (const alias of pack.aliases) {
                const key = alias.toLowerCase();
                if (this.packs.has(alias)) {
                    log.warn(`Pack alias '${alias}' shadows an existing pack id — skipping alias registration`);
                }
                else {
                    this.aliasIndex.set(key, pack.id);
                }
            }
        }
        log.info(`Registered compliance pack: ${pack.id} v${pack.version}`);
    }
    /**
     * Resolve a pack id or alias to its canonical id.
     * Returns undefined when neither matches.
     */
    resolveId(packId) {
        if (this.packs.has(packId))
            return packId;
        const canonical = this.aliasIndex.get(packId.toLowerCase());
        if (canonical && this.packs.has(canonical))
            return canonical;
        return undefined;
    }
    /**
     * Activate a pack by ID or alias.
     */
    activate(packId) {
        const canonical = this.resolveId(packId);
        if (!canonical) {
            throw new Error(`Compliance pack '${packId}' not registered. Available: ${Array.from(this.packs.keys()).join(', ')}`);
        }
        this.activePacks.add(canonical);
        log.info(`Activated compliance pack: ${canonical}${canonical !== packId ? ` (via alias '${packId}')` : ''}`);
    }
    /**
     * Deactivate a pack (by canonical id or alias).
     */
    deactivate(packId) {
        const canonical = this.resolveId(packId) ?? packId;
        this.activePacks.delete(canonical);
    }
    /**
     * F-NEW-PACK-HOT-RELOAD-1: activate a pack for a specific tenant.
     * Creates the tenant's pack set if it does not exist yet.
     * Backward-compat: env-var-driven activations via activate() remain in
     * the global activePacks set and serve as fallback for tenants that have
     * NEVER called activateForTenant() or deactivateForTenant().
     */
    activateForTenant(tenantId, packId) {
        const canonical = this.resolveId(packId);
        if (!canonical) {
            throw new Error(`Compliance pack '${packId}' not registered. Available: ${Array.from(this.packs.keys()).join(', ')}`);
        }
        // Mark this tenant as explicitly managed -- future getActiveForTenant()
        // calls will ONLY return this tenant's explicit selections, never global.
        this.tenantsWithExplicitMgmt.add(tenantId);
        if (!this.activePacksByTenant.has(tenantId)) {
            this.activePacksByTenant.set(tenantId, new Set());
        }
        this.activePacksByTenant.get(tenantId).add(canonical);
        // Increment version so PackEvaluator knows its cached index is stale.
        this.tenantVersions.set(tenantId, (this.tenantVersions.get(tenantId) ?? 0) + 1);
        log.info(`Activated compliance pack for tenant: tenantId=${tenantId} packId=${canonical}`);
    }
    /**
     * F-NEW-PACK-HOT-RELOAD-1: deactivate a pack for a specific tenant.
     * Once a tenant has called activateForTenant() or deactivateForTenant(),
     * it is in "explicit management" mode and will NEVER fall back to the
     * global default set -- even if all packs are subsequently deactivated.
     */
    deactivateForTenant(tenantId, packId) {
        const canonical = this.resolveId(packId) ?? packId;
        // Mark as explicitly managed even on deactivation so that a tenant
        // that deactivates all packs does not accidentally inherit the global set.
        this.tenantsWithExplicitMgmt.add(tenantId);
        const tenantPacks = this.activePacksByTenant.get(tenantId);
        if (tenantPacks) {
            tenantPacks.delete(canonical);
            // Keep the (possibly empty) set in the map so getActiveForTenant()
            // can detect "tenant is in explicit management mode with zero packs."
        }
        // Increment version so PackEvaluator knows its cached index is stale.
        this.tenantVersions.set(tenantId, (this.tenantVersions.get(tenantId) ?? 0) + 1);
        log.info(`Deactivated compliance pack for tenant: tenantId=${tenantId} packId=${canonical}`);
    }
    /**
     * F-NEW-PACK-REGISTRY-TENANT-LIFECYCLE: remove all per-tenant pack state
     * for a deleted tenant.
     *
     * Clears three coupled state structures:
     *   1. activePacksByTenant  -- the Set of active pack IDs for this tenant.
     *   2. tenantsWithExplicitMgmt -- the "opted into explicit management" flag.
     *   3. tenantVersions       -- the mutation version counter used by PackEvaluator
     *                             for cache invalidation.
     *
     * After this call, getActiveForTenant(tenantId) returns the global-default
     * pack set (env-var seeds) because the tenant is no longer in explicit
     * management mode. This is safe: a deleted tenant's org should never appear
     * in a subsequent enforcement path. Callers responsible for tenant deletion
     * must call this method to prevent unbounded growth of the Maps.
     *
     * FUTURE CALL SITE: When a full org/tenant deletion lifecycle is implemented
     * (F-NEW-TENANT-DELETION-LIFECYCLE), call `packRegistry.clearTenant(tenantId)`
     * immediately before or after the org record is deleted. The relevant location
     * is wherever `tenantPackActivations` is purged in `compliance-routes.ts`, or
     * a new top-level DELETE /api/v1/tenants/:tenantId endpoint. See the filed
     * gap finding F-NEW-TENANT-DELETION-LIFECYCLE.
     *
     * @param tenantId - The tenant whose pack state should be wiped.
     * @returns The number of pack IDs that were active at the time of clearing.
     */
    clearTenant(tenantId) {
        const tenantPacks = this.activePacksByTenant.get(tenantId);
        const count = tenantPacks?.size ?? 0;
        // 1. Remove the active pack set for this tenant.
        this.activePacksByTenant.delete(tenantId);
        // 2. Remove from explicit-management set so future getActiveForTenant()
        //    falls back to global defaults (harmless for a deleted tenant, but
        //    keeps the data model internally consistent).
        this.tenantsWithExplicitMgmt.delete(tenantId);
        // 3. Remove the version counter. PackEvaluator treats a missing version
        //    as 0, so any cached entry for this tenantId will see a version
        //    mismatch on the next access and rebuild. The next write (if the
        //    tenant ID were ever recycled) would start from version 1.
        this.tenantVersions.delete(tenantId);
        log.info(`tenant:packs-cleared tenantId=${tenantId} packsCleared=${count}`);
        return count;
    }
    /**
     * F-NEW-PACK-HOT-RELOAD-1: get active packs for a specific tenant.
     *
     * Resolution order:
     *   1. If tenant is in explicit management mode (tenantsWithExplicitMgmt):
     *      return ONLY the packs explicitly activated for this tenant.
     *      An empty set means no packs are active (even if global has packs).
     *   2. Otherwise fall back to global activePacks (env-var / boot defaults).
     *
     * Tenants enter explicit management mode on their FIRST call to
     * activateForTenant() or deactivateForTenant(). Once in that mode,
     * the global set is never consulted for that tenant.
     */
    getActiveForTenant(tenantId) {
        if (this.tenantsWithExplicitMgmt.has(tenantId)) {
            const tenantPacks = this.activePacksByTenant.get(tenantId);
            if (!tenantPacks || tenantPacks.size === 0)
                return [];
            return Array.from(tenantPacks)
                .map(id => this.packs.get(id))
                .filter(Boolean);
        }
        // Tenant has never explicitly managed packs -- fall back to global defaults.
        return this.getActive();
    }
    /**
     * Get all active packs.
     * Returns the global (env-var-seeded) set. Use getActiveForTenant() in
     * enforcement paths where per-tenant isolation is required.
     *
     * @deprecated in enforcement paths -- prefer getActiveForTenant(tenantId).
     * Kept for backward compatibility: report endpoints, prewarm, boot checks,
     * and single-tenant deployments that never call activateForTenant().
     */
    getActive() {
        return Array.from(this.activePacks)
            .map(id => this.packs.get(id))
            .filter(Boolean);
    }
    /**
     * Get a pack by ID or alias.
     */
    get(packId) {
        const canonical = this.resolveId(packId);
        return canonical ? (this.packs.get(canonical) ?? null) : null;
    }
    /**
     * GAP-S1-01: Return all registered alias -> canonical id mappings.
     * Used by alignment tests to verify every public code resolves.
     */
    getAliasIndex() {
        return this.aliasIndex;
    }
    /**
     * Get all registered packs.
     */
    getAll() {
        return Array.from(this.packs.values());
    }
    /**
     * Get merged module requirements from all active packs.
     * When packs overlap, required=true wins over required=false.
     */
    getMergedModules() {
        const moduleMap = new Map();
        for (const pack of this.getActive()) {
            for (const mod of pack.requiredModules) {
                const existing = moduleMap.get(mod.name);
                if (!existing || (!existing.required && mod.required)) {
                    moduleMap.set(mod.name, mod);
                }
            }
        }
        return Array.from(moduleMap.values());
    }
    /**
     * Get merged provider policy (strictest wins).
     */
    getMergedProviderPolicy() {
        const blocked = new Set();
        const required = new Set();
        const reasons = {};
        for (const pack of this.getActive()) {
            for (const provider of pack.providerPolicy.blockedProviders) {
                blocked.add(provider);
            }
            for (const cap of pack.providerPolicy.requiredCapabilities) {
                required.add(cap);
            }
            for (const [provider, reason] of Object.entries(pack.providerPolicy.blockReasons)) {
                reasons[provider] = reasons[provider]
                    ? `${reasons[provider]}; ${reason}`
                    : reason;
            }
        }
        return {
            blockedProviders: Array.from(blocked),
            requiredCapabilities: Array.from(required),
            blockReasons: reasons,
        };
    }
    /**
     * Get the strictest retention policy across active packs.
     */
    getMergedRetentionPolicy() {
        let maxAudit = 0;
        let maxSession = 0;
        let maxIncident = 0;
        let reference = '';
        for (const pack of this.getActive()) {
            if (pack.retentionPolicy.auditLogRetentionDays > maxAudit) {
                maxAudit = pack.retentionPolicy.auditLogRetentionDays;
                reference = pack.retentionPolicy.reference;
            }
            maxSession = Math.max(maxSession, pack.retentionPolicy.sessionRetentionDays);
            maxIncident = Math.max(maxIncident, pack.retentionPolicy.incidentRetentionDays);
        }
        return {
            auditLogRetentionDays: maxAudit,
            sessionRetentionDays: maxSession,
            incidentRetentionDays: maxIncident,
            reference,
        };
    }
    /**
     * Get the tightest incident deadline across active packs.
     */
    getMergedIncidentPolicy() {
        let minHours = Infinity;
        let reference = '';
        const recipients = new Set();
        let formalRequired = false;
        for (const pack of this.getActive()) {
            if (pack.incidentPolicy.notificationDeadlineHours < minHours) {
                minHours = pack.incidentPolicy.notificationDeadlineHours;
                reference = pack.incidentPolicy.reference;
            }
            for (const r of pack.incidentPolicy.requiredRecipients) {
                recipients.add(r);
            }
            if (pack.incidentPolicy.formalNotificationRequired) {
                formalRequired = true;
            }
        }
        return {
            notificationDeadlineHours: minHours === Infinity ? 72 : minHours,
            reference,
            requiredRecipients: Array.from(recipients),
            formalNotificationRequired: formalRequired,
        };
    }
    /**
     * Validate current governance setup against all active packs.
     *
     * P1C-03 contract: every validator runs through ComplianceCheckRegistry.
     * Unregistered check ids HARD-FAIL with a governance-defect verdict
     * (ComplianceCheckRegistry.run returns passed=false with defect-language
     * detail). There is no neutral-pass or soft-landing path. Callers that
     * do not supply a registry get the defaultCheckRegistry, which has
     * every production check id registered and is verified at server boot.
     */
    validate(opts) {
        const activeModules = new Set();
        const packConfig = {};
        const activePackIds = [];
        for (const pack of this.getActive()) {
            activePackIds.push(pack.id);
            for (const m of pack.requiredModules) {
                if (m.required)
                    activeModules.add(m.name);
            }
            for (const [mod, cfg] of Object.entries(pack.moduleConfig || {})) {
                packConfig[mod] = { ...(packConfig[mod] || {}), ...cfg };
            }
        }
        const defaultCtx = {
            activeModules: Array.from(activeModules),
            activePackIds,
            packConfig,
            evidence: {},
        };
        const ctx = opts?.context ?? defaultCtx;
        // Default to the shared defaultCheckRegistry so callers that never
        // supply a registry still get real evaluations instead of a silent
        // pass. A missing check id in that registry routes through
        // ComplianceCheckRegistry.run() which HARD-FAILs by contract.
        const registry = opts?.registry ?? check_registry_1.defaultCheckRegistry;
        return this.getActive().map(pack => {
            // Per-pack scoping for packConfig. Without this, two packs that
            // configure the same module name (for example fedramp and glba
            // both setting attestation-manager.vulnerabilityScanCadenceDays)
            // would corrupt each other under last-writer-wins inside the
            // shared defaultCtx, causing each pack's validator to see the
            // other pack's value. Each pack's validators must only see its
            // own moduleConfig.
            const perPackConfig = {};
            for (const [mod, cfg] of Object.entries(pack.moduleConfig || {})) {
                perPackConfig[mod] = { ...cfg };
            }
            const packCtx = {
                ...ctx,
                packConfig: perPackConfig,
            };
            const checks = pack.validators.map(v => {
                const result = registry.run(v.check, packCtx);
                return {
                    validatorId: v.id,
                    description: v.description,
                    passed: result.passed,
                    detail: result.detail,
                };
            });
            const passedCount = checks.filter(c => c.passed).length;
            return {
                packId: pack.id,
                passed: passedCount === checks.length,
                checks,
                score: checks.length > 0 ? Math.round((passedCount / checks.length) * 100) : 100,
                totalChecks: checks.length,
                passedChecks: passedCount,
            };
        });
    }
    // ---------------------------------------------------------------------------
    // F-NEW-PACK-MULTI-REPLICA-ISOLATION: channel-driven cache refresh
    // ---------------------------------------------------------------------------
    /**
     * Called by the PackChangeChannel subscriber when a NOTIFY message arrives
     * from another replica.
     *
     * Applies the same mutation that the route handler would apply on the
     * receiving replica — without going through the HTTP layer or the DB write
     * path (the DB was already written by the publishing replica).
     *
     * If the pack is not registered on this replica (should not happen in
     * production because all replicas share the same pack catalogue) the call
     * is a no-op with a warning.
     *
     * @param orgId   Tenant that owns the change.
     * @param packId  Canonical pack ID.
     * @param action  'activate' | 'deactivate'
     */
    refreshFromChannel(orgId, packId, action) {
        const canonical = this.resolveId(packId);
        if (!canonical && action === 'activate') {
            log.warn(`[pack-channel] Received activate for unknown pack '${packId}' — skipped`);
            return;
        }
        if (action === 'activate') {
            // Identical to activateForTenant() but without the throw for missing pack.
            this.tenantsWithExplicitMgmt.add(orgId);
            if (!this.activePacksByTenant.has(orgId)) {
                this.activePacksByTenant.set(orgId, new Set());
            }
            this.activePacksByTenant.get(orgId).add(canonical);
            this.tenantVersions.set(orgId, (this.tenantVersions.get(orgId) ?? 0) + 1);
            log.info(`[pack-channel] Applied activate orgId=${orgId} packId=${canonical}`);
        }
        else {
            // Identical to deactivateForTenant() but safe when pack was never registered.
            this.tenantsWithExplicitMgmt.add(orgId);
            const tenantPacks = this.activePacksByTenant.get(orgId);
            if (tenantPacks) {
                tenantPacks.delete(canonical ?? packId);
            }
            this.tenantVersions.set(orgId, (this.tenantVersions.get(orgId) ?? 0) + 1);
            log.info(`[pack-channel] Applied deactivate orgId=${orgId} packId=${canonical ?? packId}`);
        }
    }
}
exports.PackRegistry = PackRegistry;
// ---------------------------------------------------------------------------
// W3: Parallel versions registry
// ---------------------------------------------------------------------------
/**
 * Semver comparator -- returns negative/zero/positive like Array.sort.
 * Handles `\d+.\d+.\d+` format only (the structural-consistency test enforces this).
 */
function compareSemver(a, b) {
    const parse = (s) => s.split('.').map(Number);
    const [aMaj, aMin, aPatch] = parse(a);
    const [bMaj, bMin, bPatch] = parse(b);
    return aMaj !== bMaj ? aMaj - bMaj
        : aMin !== bMin ? aMin - bMin
            : aPatch - bPatch;
}
/**
 * VersionedPackRegistry: extends PackRegistry with parallel version support.
 *
 * Each pack ID maps to a Map<semver, CompliancePack> (multiple concurrent
 * versions in memory). A single "active version" per pack ID is tracked
 * and is the version returned by getActiveVersion().
 *
 * Migration manifest integration:
 *   - At construction time, the registry checks the migration manifest
 *     for deprecation warnings (< 90 days) and hard-refuses (past
 *     deprecation date).
 */
class VersionedPackRegistry extends PackRegistry {
    /** Map<packId, Map<semver, CompliancePack>> */
    versionedPacks = new Map();
    /** Map<packId, semver> -- the currently active (pinned) version per pack */
    activeVersions = new Map();
    /**
     * Register a specific version of a pack.
     * Also registers it in the parent PackRegistry (latest wins there).
     */
    registerVersion(pack) {
        const { id, version } = pack;
        if (!this.versionedPacks.has(id)) {
            this.versionedPacks.set(id, new Map());
        }
        this.versionedPacks.get(id).set(version, pack);
        // Also register in parent (latest version wins the base registry)
        const current = this.get(id);
        if (!current || compareSemver(version, current.version) > 0) {
            super.register(pack);
        }
        // Set active version if not already pinned
        if (!this.activeVersions.has(id)) {
            this.activeVersions.set(id, version);
        }
    }
    /**
     * Get the currently active version of a pack.
     * Falls back to the parent registry if no versioned entry exists.
     */
    getActiveVersion(packId) {
        const activeVer = this.activeVersions.get(packId);
        if (activeVer) {
            return this.versionedPacks.get(packId)?.get(activeVer);
        }
        return this.get(packId) ?? undefined;
    }
    /**
     * Get a specific semver of a pack.
     */
    getVersion(packId, semver) {
        return this.versionedPacks.get(packId)?.get(semver);
    }
    /**
     * List all registered semver strings for a pack, sorted ascending.
     */
    listVersions(packId) {
        const versions = this.versionedPacks.get(packId);
        if (!versions)
            return [];
        return Array.from(versions.keys()).sort(compareSemver);
    }
    /**
     * Pin a project to a specific pack version for classify-time use.
     * Used by W2 project pinning + W3 upgrade flow.
     *
     * @param packId - Pack to pin
     * @param semver - Version to activate
     * @throws if the version is not registered
     */
    pinVersion(packId, semver) {
        if (!this.versionedPacks.get(packId)?.has(semver)) {
            throw new Error(`Pack version not registered: ${packId}@${semver}. ` +
                `Available: ${this.listVersions(packId).join(', ')}`);
        }
        this.activeVersions.set(packId, semver);
        log.info(`Pinned ${packId} to version ${semver}`);
    }
    /**
     * Get a CompliancePack for classify-time use, respecting project version pins.
     *
     * @param packId - Pack to look up
     * @param projectVersions - Per-project selectedPackVersions map
     */
    getForProject(packId, projectVersions) {
        const pinned = projectVersions?.[packId];
        if (pinned) {
            return this.getVersion(packId, pinned) ?? this.getActiveVersion(packId);
        }
        return this.getActiveVersion(packId);
    }
}
exports.VersionedPackRegistry = VersionedPackRegistry;
/**
 * MigrationManifest -- loaded at boot. Provides:
 *   - Boot warnings when any loaded pack version is within 90 days of deprecation.
 *   - Boot refusal when any loaded pack version is past its deprecation date.
 *
 * Deprecation window: deprecated versions remain enforceable for a minimum of
 * 180 days. Boot-warning at 90 days. Hard-refuse at the declared deprecation date.
 */
class MigrationManifest {
    entries = [];
    /** Resolved at construction time for testable clock injection */
    nowFn;
    constructor(entries, nowFn) {
        this.entries = entries;
        this.nowFn = nowFn ?? (() => new Date());
    }
    /**
     * Check all registered packs against the manifest.
     *
     * @returns An object with warnings (within 90 days) and errors (past date).
     *   Call this at boot time. If errors is non-empty, refuse to start.
     */
    check(loadedPacks) {
        const warnings = [];
        const errors = [];
        const now = this.nowFn();
        for (const entry of this.entries) {
            const match = loadedPacks.find((p) => p.id === entry.packId && p.version === entry.deprecatedVersion);
            if (!match)
                continue; // Not loaded, no issue
            const depDate = new Date(entry.deprecationDate);
            const daysUntil = Math.ceil((depDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
            if (daysUntil <= 0) {
                errors.push(`[GOVERNANCE BOOT ERROR] Pack ${entry.packId}@${entry.deprecatedVersion} ` +
                    `has passed its deprecation date (${entry.deprecationDate}). ` +
                    `Upgrade to ${entry.upgradeTo ?? 'latest'}. ` +
                    `${entry.breakingChangeNotes ?? ''}`.trim());
            }
            else if (daysUntil <= 90) {
                warnings.push(`[GOVERNANCE BOOT WARN] Pack ${entry.packId}@${entry.deprecatedVersion} ` +
                    `will be deprecated on ${entry.deprecationDate} (in ${daysUntil} days). ` +
                    `Plan upgrade to ${entry.upgradeTo ?? 'latest'}. ` +
                    `${entry.breakingChangeNotes ?? ''}`.trim());
            }
        }
        return { warnings, errors };
    }
    /**
     * Validate and apply boot checks. Writes warnings to stderr and throws
     * GovernanceMigrationError if any packs are past their deprecation date.
     */
    applyBootChecks(loadedPacks) {
        const { warnings, errors } = this.check(loadedPacks);
        for (const w of warnings) {
            process.stderr.write(w + '\n');
        }
        if (errors.length > 0) {
            const msg = errors.join('\n');
            process.stderr.write(msg + '\n');
            throw new GovernanceMigrationError(msg, errors);
        }
    }
    getEntries() {
        return this.entries.slice();
    }
}
exports.MigrationManifest = MigrationManifest;
class GovernanceMigrationError extends Error {
    violations;
    constructor(message, violations) {
        super(message);
        this.violations = violations;
        this.name = 'GovernanceMigrationError';
    }
}
exports.GovernanceMigrationError = GovernanceMigrationError;
//# sourceMappingURL=index.js.map