/**
 * Plugin/Extension system for AI governance.
 *
 * Enables custom rules, checks, and reporting without forking
 * the core package. Plugins are discovered, validated, and loaded
 * at initialization time.
 *
 * SYSTEMIC-H2 / SYSTEMIC-H4 (2026-04-21): the four post-hook dispatchers
 * (`executePostAction`, `executeOnViolation`, `executeOnReport`,
 * `executeOnScan`) used to swallow plugin exceptions silently with
 * "Non-fatal" comments. That defeated SOC 2 CC7.3 system-operations
 * monitoring and could lose DORA Art. 17 incident notifications.
 *
 * The registry now emits, on every plugin exception:
 *
 *   1. A structured WARN log entry via the injected logger.
 *   2. A per-plugin + per-method sliding-window error counter
 *      (60-minute window). Callers read this with
 *      `getPluginErrorCount()` / `getRecentPluginErrors()`.
 *   3. An audit-chain `plugin-error` event via `onPluginError`.
 *   4. A CRITICAL alert via `onErrorThresholdBreach` the first time a
 *      plugin+method crosses the configured hourly threshold (default 5).
 *      Hysteresis prevents repeated firing until the hourly rate drops
 *      back below the resolve level.
 *
 * Continue-on-error is preserved: one plugin failing NEVER halts the
 * other plugins. The fix is about VISIBILITY of errors, not changing
 * the resilience semantics.
 *
 * @connexum/ai-governance
 */
import { type TrustedPluginEntry } from '../security/plugin-integrity';
/**
 * Thrown (or surfaced as a result-entry) when a plugin fails runtime
 * per-dispatch SHA-256 re-verification. Indicates the plugin bytes changed
 * after load-time registration (TOCTOU window).
 */
export declare class PluginRuntimeIntegrityError extends Error {
    readonly code: 'PLUGIN_RUNTIME_INTEGRITY_FAILED';
    readonly pluginId: string;
    readonly expected: string;
    readonly actual: string;
    readonly dispatchId: string;
    constructor(opts: {
        pluginId: string;
        expected: string;
        actual: string;
        dispatchId: string;
    });
}
export type PluginHook = 'preAction' | 'postAction' | 'onViolation' | 'onAuditEntry' | 'onReport' | 'onScan';
export interface PluginManifest {
    /** Unique plugin identifier (e.g., 'custom-pii-scanner') */
    name: string;
    /** SemVer version */
    version: string;
    /** Human-readable description */
    description: string;
    /** Author or organization */
    author?: string;
    /** Which hooks this plugin listens to */
    hooks: PluginHook[];
    /** Minimum @connexum/ai-governance version required */
    minGovernanceVersion?: string;
    /** Plugin priority (lower runs first, default: 100) */
    priority?: number;
}
export interface PluginContext {
    /** Current action being evaluated */
    toolName?: string;
    toolInput?: Record<string, unknown>;
    /** Agent performing the action */
    agentId?: string;
    /** Current tenant (if multi-tenancy enabled) */
    tenantId?: string;
    /** Additional data from the hook point */
    data?: Record<string, unknown>;
}
export interface PluginResult {
    /** Whether the plugin allows the action to proceed */
    allow: boolean;
    /** Reason for decision */
    reason?: string;
    /** Additional metadata to attach to audit log */
    metadata?: Record<string, unknown>;
}
export interface GovernancePlugin {
    /** Plugin manifest */
    manifest: PluginManifest;
    /** Called when the plugin is loaded */
    initialize?(): void;
    /** Called before an action (preAction hook) */
    preAction?(context: PluginContext): PluginResult;
    /** Called after an action (postAction hook) */
    postAction?(context: PluginContext): void;
    /** Called when a violation is detected */
    onViolation?(context: PluginContext): void;
    /** Called for each audit entry */
    onAuditEntry?(context: PluginContext): void;
    /** Called when generating a report */
    onReport?(context: PluginContext): Record<string, unknown> | void;
    /** Called during agent scanning */
    onScan?(context: PluginContext): Record<string, unknown> | void;
    /** Called when the plugin is unloaded */
    destroy?(): void;
}
/**
 * Hook method a plugin exception was caught in. Used as a dimension of
 * the sliding-window counter so "onViolation crashes" can be alerted
 * separately from "onReport crashes".
 */
export type PluginErrorMethod = 'preAction' | 'postAction' | 'onViolation' | 'onReport' | 'onScan';
/**
 * Structured error event emitted when a plugin throws. Consumers wire
 * this into the governance audit chain so the failure is chained
 * alongside other compliance events. The callback MUST NOT throw; any
 * exception is caught and logged to stderr by the registry.
 */
export interface PluginErrorEvent {
    /** Stable event type for audit-chain indexing. */
    type: 'plugin-error';
    /** Plugin manifest `name`. */
    pluginId: string;
    /** Hook method that threw. */
    method: PluginErrorMethod;
    /** Sanitized `err.message`; capped to 500 chars to prevent log floods. */
    errorMessage: string;
    /**
     * First 3 stack frames of the exception (newline-joined). Sanitized to
     * strip absolute paths just like denial reasons. Empty string if the
     * stack is unavailable.
     */
    errorStack: string;
    /** ISO-8601 timestamp of when the exception was observed. */
    timestamp: string;
    /**
     * Current count of errors for this (pluginId, method) inside the
     * sliding 60-minute window. Populated after the push, so the first
     * error is 1.
     */
    windowErrorCount: number;
}
/**
 * Payload for `onErrorThresholdBreach`. Fired once each time
 * (pluginId, method) transitions from "under threshold" to "at or above
 * threshold" within the sliding window. Hysteresis prevents re-fire
 * until the rate drops back below the resolve level.
 */
export interface PluginErrorThresholdBreach {
    pluginId: string;
    method: PluginErrorMethod;
    /** Count in the current window at the moment of breach. */
    currentCount: number;
    /** The configured threshold that was crossed. */
    threshold: number;
    /** Up to the last 3 error messages from the window, oldest-first. */
    recentErrors: string[];
    /** ISO-8601 of the breach moment. */
    firedAt: string;
}
/**
 * Minimal logger shape the registry needs. `Logger` from `util/log.ts`
 * satisfies this, and tests can pass a spy matching the same methods.
 */
export interface PluginRegistryLogger {
    warn(message: string, data?: Record<string, unknown>): void;
    error?(message: string, data?: Record<string, unknown>): void;
}
/**
 * Emitted as an INFO audit event when a plugin passes load-time SHA-256
 * verification and is admitted into the registry.
 */
export interface PluginIntegrityVerifiedEvent {
    type: 'plugin:integrity-verified';
    pluginName: string;
    sha256: string;
    timestamp: string;
}
/**
 * Emitted as a CRITICAL audit event when a plugin is rejected because its
 * hash does not match the trusted manifest (or it is unregistered).
 */
export interface PluginIntegrityDeniedEvent {
    type: 'plugin:integrity-denied';
    pluginName: string;
    reason: 'hash-mismatch' | 'unregistered-plugin' | 'empty-manifest';
    expected?: string;
    actual?: string;
    timestamp: string;
}
/**
 * Emitted as a WARN audit event when integrity checking is disabled via the
 * `disableIntegrity: true` escape hatch. Auditors must see this on the chain.
 */
export interface PluginIntegrityDisabledEvent {
    type: 'plugin:integrity-disabled';
    pluginName: string;
    timestamp: string;
}
export type PluginIntegrityAuditEvent = PluginIntegrityVerifiedEvent | PluginIntegrityDeniedEvent | PluginIntegrityDisabledEvent;
export interface PluginRuntimeIntegrityVerifiedEvent {
    type: 'plugin:integrity-runtime-verified';
    pluginId: string;
    sha256: string;
    dispatchId: string;
    timestamp: string;
}
export interface PluginRuntimeIntegrityMismatchEvent {
    type: 'plugin:integrity-runtime-mismatch';
    pluginId: string;
    expected: string;
    actual: string;
    dispatchId: string;
    timestamp: string;
}
export interface PluginRuntimeIntegrityBypassedEvent {
    type: 'plugin:integrity-runtime-bypassed';
    pluginId: string;
    dispatchId: string;
    timestamp: string;
}
export type PluginRuntimeIntegrityAuditEvent = PluginRuntimeIntegrityVerifiedEvent | PluginRuntimeIntegrityMismatchEvent | PluginRuntimeIntegrityBypassedEvent;
export interface PluginRegistryConfig {
    /** Directory to scan for plugins */
    pluginDir?: string;
    /** Maximum number of plugins (default: 50) */
    maxPlugins?: number;
    /**
     * Structured logger for plugin-error WARN emissions. If absent, the
     * registry falls back to `console.warn` so compliance visibility is
     * NEVER silently dropped, but operators should inject a real logger.
     */
    logger?: PluginRegistryLogger;
    /**
     * Audit sink invoked once per plugin exception. Wire this to the
     * governance audit chain (e.g. `storage.appendAuditChain`). The
     * callback must not throw; exceptions are caught and logged.
     */
    onPluginError?: (event: PluginErrorEvent) => void;
    /**
     * Called the first time (pluginId, method) crosses the configured
     * threshold within the sliding window. Wire this to
     * `AlertThresholdStore.pushAlert` with metric `plugin.error_rate`.
     * Reuses hysteresis: will not re-fire until the rate resolves.
     */
    onErrorThresholdBreach?: (breach: PluginErrorThresholdBreach) => void;
    /**
     * Errors per (pluginId, method) per hour that trip an alert. Default 5.
     */
    errorRateThresholdPerHour?: number;
    /**
     * Trusted plugin manifest map. Keyed by plugin name; value is the manifest
     * entry containing the expected SHA-256 hash.
     *
     * When absent or undefined, integrity checking is still active but behaves
     * as if the manifest is empty -- all plugin loads will fail with
     * `empty-manifest` unless `disableIntegrity: true` is also set.
     *
     * Populate by calling `loadTrustedPluginManifest()` from
     * `src/security/plugin-integrity.ts` at boot time.
     */
    trustedPlugins?: Map<string, TrustedPluginEntry>;
    /**
     * Supply the source code of each plugin being registered so the registry
     * can compute and verify the SHA-256 hash at load time.
     *
     * Map key: plugin name (must match `GovernancePlugin.manifest.name`).
     * Map value: the raw source string or Buffer at the time of registration.
     *
     * If a plugin name is not present in this map, the registry cannot compute
     * a hash -- the load is denied unless `disableIntegrity: true` is set.
     */
    pluginSources?: Map<string, string | Buffer>;
    /**
     * Disable load-time integrity checking. Default: `false` (fail-closed).
     *
     * Set to `true` ONLY in development environments. When enabled, plugins
     * load without hash verification, but a WARN audit event
     * `plugin:integrity-disabled` is emitted for every load so auditors can
     * see the bypass on the chain.
     */
    disableIntegrity?: boolean;
    /**
     * Audit sink for plugin integrity events (verified / denied / disabled).
     * Wire this to the governance audit chain alongside `onPluginError`.
     * The callback must not throw; exceptions are caught and logged.
     */
    onIntegrityEvent?: (event: PluginIntegrityAuditEvent) => void;
    pluginIntegrity?: {
        runtimeRecheckCadence?: number | 'off';
    };
    onRuntimeIntegrityEvent?: (event: PluginRuntimeIntegrityAuditEvent) => void;
}
export declare class PluginValidationError extends Error {
    pluginName: string;
    reason: string;
    constructor(pluginName: string, reason: string);
}
export declare class PluginRegistry {
    private plugins;
    private config;
    /**
     * Per (pluginId, method) rolling arrays of error timestamps in ms.
     * Entries older than PLUGIN_ERROR_WINDOW_MS are pruned on every push.
     * Also carries the last few error messages so threshold alerts can
     * include recent-error context without re-scanning.
     */
    private errorWindows;
    /**
     * Hysteresis state for the threshold alert. `true` means we are
     * currently "fired" for that (pluginId, method) and must NOT re-emit
     * another breach event until the rate drops back below the resolve
     * level. Modeled to match the AlertThresholdStore pattern.
     */
    private firedState;
    constructor(config?: PluginRegistryConfig);
    /**
     * Register a plugin programmatically.
     *
     * PLUGIN-INTEGRITY-01: before admission, the plugin's source is verified
     * against the trusted manifest. The check is fail-closed by default:
     *   - If `disableIntegrity` is false (default) and the hash does not match
     *     or the plugin is not in the manifest, registration throws
     *     `PluginIntegrityError` and emits a `plugin:integrity-denied` CRITICAL
     *     audit event.
     *   - If `disableIntegrity` is true, the plugin is admitted without hash
     *     verification but a `plugin:integrity-disabled` WARN event is emitted
     *     so the bypass is recorded on the audit chain.
     *   - When integrity checking passes, a `plugin:integrity-verified` INFO
     *     event is emitted.
     *
     * Integrity checking is only active when `trustedPlugins` is explicitly
     * provided in the config. If `trustedPlugins` is omitted (legacy / first-boot
     * / test scenario), registration proceeds without hash verification.
     *
     * When `trustedPlugins` is present, callers must also supply source code via:
     *   a) `pluginSources` map in the registry config (source keyed by name), or
     *   b) The `sourceOverride` parameter on this call.
     *
     * If neither is available and integrity is active, the load is denied.
     */
    register(plugin: GovernancePlugin, sourceOverride?: string | Buffer): void;
    /**
     * Unregister a plugin by name.
     */
    unregister(name: string): boolean;
    /**
     * Get a registered plugin by name.
     */
    getPlugin(name: string): GovernancePlugin | undefined;
    /**
     * List all registered plugins.
     */
    listPlugins(): PluginManifest[];
    /**
     * Execute all preAction hooks in priority order.
     * Returns DENY if any plugin denies. Otherwise ALLOW.
     */
    executePreAction(context: PluginContext): PluginResult;
    /**
     * Execute all postAction hooks in priority order.
     *
     * SYSTEMIC-H2: plugin exceptions no longer "silently swallowed". Each
     * throw emits a WARN log, increments the hourly counter, emits a
     * `plugin-error` audit event, and (if the hourly threshold is
     * crossed) fires a CRITICAL alert. Execution continues across the
     * remaining plugins to preserve resilience.
     */
    executePostAction(context: PluginContext): void;
    /**
     * Execute all onViolation hooks.
     *
     * SYSTEMIC-H2: see executePostAction note. A violation-handler crash
     * here used to look identical to "no violations" to the operator,
     * breaking DORA Art. 17 incident-notification obligations. Now every
     * crash is logged, counted, audit-chained, and alertable.
     */
    executeOnViolation(context: PluginContext): void;
    /**
     * Execute all onReport hooks and collect results.
     *
     * SYSTEMIC-H4: a plugin that throws here used to cause its report
     * section to silently disappear, leaving operators to present
     * structurally-incomplete reports to auditors. The result set now
     * ALWAYS carries a per-plugin status entry:
     *   - `{ status: 'ok', plugin, ...pluginOutput }` on success
     *   - `{ status: 'error', plugin, errorMessage }` on failure
     *
     * Callers (report generators) must aggregate the `error` entries
     * into `incompletePluginCount` + `incompletePlugins` and MUST
     * surface them prominently in rendered output (banner, not footnote).
     * See `summarizeReportPluginStatuses()` below.
     */
    executeOnReport(context: PluginContext): Record<string, unknown>[];
    /**
     * Execute all onScan hooks and collect results.
     *
     * SYSTEMIC-H2: scan crashes follow the same visibility contract as
     * the other hooks. The result array carries only successful plugin
     * outputs (scans feed into detection engines, not rendered reports),
     * but the failure is always logged / counted / audit-chained.
     */
    executeOnScan(context: PluginContext): Record<string, unknown>[];
    /**
     * Current number of errors for `(pluginId, method)` inside the
     * sliding 60-minute window. Prunes expired entries before returning.
     * Exposed so callers (dashboards, tests) can read the counter
     * without triggering a new event.
     */
    getPluginErrorCount(pluginId: string, method: PluginErrorMethod): number;
    /**
     * Returns up to `limit` most recent error messages (oldest-first
     * within the window) for `(pluginId, method)`. Useful when the alert
     * sink wants richer context than the numeric count.
     */
    getRecentPluginErrors(pluginId: string, method: PluginErrorMethod, limit?: number): string[];
    /**
     * Reset all plugin-error counters + hysteresis state. Intended for
     * test setup between cases; NOT for production hot paths (would
     * erase operator signal).
     */
    resetPluginErrorCountersForTest(): void;
    /**
     * Summarize report-hook results into an incomplete-plugin surface
     * suitable for top-level report metadata + banners. Renderers MUST
     * promote `incompletePluginCount > 0` to a banner, not a footnote.
     */
    static summarizeReportPluginStatuses(results: ReadonlyArray<Record<string, unknown>>): {
        pluginStatuses: Array<{
            pluginId: string;
            status: 'ok' | 'error';
            errorMessage?: string;
        }>;
        incompletePluginCount: number;
        incompletePlugins: string[];
        banner: string | null;
    };
    /**
     * Activate/deactivate a plugin.
     */
    setActive(name: string, active: boolean): boolean;
    /**
     * Check if a plugin is registered and active.
     */
    isActive(name: string): boolean;
    /**
     * Get the count of registered plugins.
     */
    get pluginCount(): number;
    /**
     * Discover plugins from the plugin directory.
     * Returns manifest info without loading.
     */
    discoverPlugins(): string[];
    simulateByteMutationForTest(pluginName: string, mutatedSource: string | Buffer): void;
    private generateDispatchId;
    private emitRuntimeIntegrityEvent;
    private enforceIntegrityAtDispatch;
    /**
     * Enforce the load-time integrity contract for a plugin being registered.
     *
     * Integrity checking is ONLY active when `trustedPlugins` is explicitly
     * provided in the registry config. If `trustedPlugins` is undefined, the
     * registry is in "unconfigured" mode (e.g. pre-manifest first-boot or
     * test fixtures that pre-date PLUGIN-INTEGRITY-01) and registration
     * proceeds without hash verification.
     *
     * When `trustedPlugins` IS provided, enforcement is fail-closed:
     *   1. `disableIntegrity: true` -- emit WARN event, return (bypass).
     *   2. Source available + hash matches manifest -- emit INFO event, return.
     *   3. Source missing, hash mismatch, or unregistered -- emit CRITICAL
     *      event, throw `PluginIntegrityError`.
     *
     * Governance config note: callers operating under HIPAA, SOC 2, or PCI DSS
     * packs SHOULD supply `trustedPlugins` to activate the check. Leaving it
     * undefined is acceptable only in development and test environments.
     */
    private enforceIntegrityAtLoad;
    /**
     * Deliver a plugin integrity audit event to the registered sink.
     * Exceptions from the sink are caught and logged to stderr -- a broken
     * audit sink must not crash the registration path.
     */
    private emitIntegrityEvent;
    /**
     * SYSTEMIC-H2 central visibility pipeline for plugin exceptions.
     *
     * Ordering is deliberate:
     *   1. Sanitize the error once so downstream consumers all see the
     *      same safe message (no absolute paths, no stack frames).
     *   2. Push timestamp into the sliding-window counter.
     *   3. Emit the WARN log (always, regardless of whether callbacks
     *      are wired). This guarantees at least one operator-visible
     *      signal even in a misconfigured deployment.
     *   4. Emit the structured audit event via `onPluginError`.
     *   5. Check the hourly threshold with hysteresis. Fire the
     *      CRITICAL alert once per (pluginId, method) crossing.
     *
     * No step is permitted to throw back into the calling dispatcher;
     * each is wrapped so one broken consumer cannot mask the visibility
     * of other consumers.
     */
    private handlePluginError;
    /**
     * Prune sliding-window entries older than PLUGIN_ERROR_WINDOW_MS.
     * Keeps timestamps + messages arrays index-aligned.
     */
    private pruneExpired;
    /**
     * Sanitize a stack string: strip absolute paths, keep only the first
     * 3 frames, flatten whitespace. Matches the denial-reason sanitizer
     * so operators see a consistent shape across plugin failure modes.
     */
    private sanitizePluginStack;
    private validatePlugin;
    private getOrderedPlugins;
    private sanitizePluginMessage;
}
//# sourceMappingURL=plugin-system.d.ts.map