/**
 * GovernanceAdapter -- Platform-agnostic adapter interface for multi-LLM governance.
 *
 * Maps governance enforcement to any AI platform's hook system.
 * Claude Code uses PreToolUse/PostToolUse; other platforms have their own
 * equivalents. This adapter layer normalizes them all.
 *
 * Pure TypeScript, no external dependencies.
 *
 * @connexum/ai-governance
 */
import * as crypto from 'crypto';
// ---------------------------------------------------------------------------
// ActionDecision -- outcome of a pre-action governance check
// ---------------------------------------------------------------------------
export var ActionDecisionType;
(function (ActionDecisionType) {
    ActionDecisionType["ALLOW"] = "ALLOW";
    ActionDecisionType["DENY"] = "DENY";
    ActionDecisionType["REQUIRE_APPROVAL"] = "REQUIRE_APPROVAL";
})(ActionDecisionType || (ActionDecisionType = {}));
// ---------------------------------------------------------------------------
// BaseGovernanceAdapter -- abstract base with sensible defaults
// ---------------------------------------------------------------------------
export class BaseGovernanceAdapter {
    _name = '';
    _initialized = false;
    config = null;
    get name() {
        return this._name;
    }
    get initialized() {
        return this._initialized;
    }
    initialize(config) {
        this._name = config.adapterName;
        this.config = config;
        this._initialized = true;
    }
    /**
     * Default: deny all actions. Subclasses override for enforcement.
     * A governance product must default to DENY -- never ALLOW.
     *
     * When humanInTheLoop is enabled (default), DENY becomes REQUIRE_APPROVAL
     * so a human can review every block before it takes effect.
     */
    preAction(_context) {
        // AIAG-022: enforce that initialize() was called. Previously this guard
        // was defined but never invoked, so an adapter could silently skip setup
        // and return the deny fallback.
        this.ensureInitialized();
        const decision = {
            type: ActionDecisionType.DENY,
            reason: 'No governance rules configured - default deny',
        };
        return this.applyHumanInTheLoop(decision);
    }
    /**
     * Convert DENY decisions to REQUIRE_APPROVAL when human-in-the-loop is enabled.
     * All blocks route through human review. No action is hard-blocked without
     * a human seeing it first.
     */
    applyHumanInTheLoop(decision) {
        const hitlEnabled = this.config?.humanInTheLoop !== false; // default true
        if (hitlEnabled && decision.type === ActionDecisionType.DENY) {
            return {
                type: ActionDecisionType.REQUIRE_APPROVAL,
                reason: decision.reason,
                metadata: {
                    ...decision.metadata,
                    originalDecision: 'DENY',
                    requiresHumanReview: true,
                },
            };
        }
        return decision;
    }
    /**
     * Default: no-op. Subclasses override for audit logging.
     */
    postAction(_context, _result) {
        // No-op by default
    }
    /**
     * Verify an agent's identity token.
     *
     * SYSTEMIC-H1 (fail-open audit, 2026-04-21): this method is FAIL-CLOSED
     * on missing or invalid tokens. A compliance product cannot silently
     * attest a pass-through identity. EU AI Act Art. 13/17 and SOC 2 CC6.1
     * require every agent action to be bound to a verifiable actor.
     *
     * Outcomes:
     *   - `identityToken` present + valid HMAC  -> verified: true
     *   - `identityToken` present + invalid     -> verified: false
     *     (reason: AGENT_IDENTITY_TOKEN_INVALID)
     *   - `identityToken` absent                -> verified: false
     *     (reason: AGENT_IDENTITY_TOKEN_MISSING)
     *   - no `secretSeed` configured            -> verified: false
     *     (reason: AGENT_IDENTITY_SECRET_MISSING). The operator must
     *     configure an issuer before identity verification can succeed.
     *
     * Every negative outcome also emits an audit event via
     * `AdapterConfig.auditCallback` so the failure is chained.
     */
    verifyAgentIdentity(agent) {
        const agentId = agent.name;
        if (!agent.identityToken) {
            const allowUnverified = this.config?.allowUnverifiedIdentity === true;
            if (allowUnverified) {
                // Permissive migration path: pass but WARN on every single call so
                // the ongoing cost is visible in the audit chain. No "once per
                // session" throttle -- operators must see the full drumbeat.
                const permissiveDetail = `agent '${agentId}' has no identityToken; permissive mode ` +
                    '(allowUnverifiedIdentity=true) allowed the call. Identity ' +
                    'verification is required per SOC 2 CC6.1 and EU AI Act Art. 13. ' +
                    'Configure an identity issuer and provide the token at agent ' +
                    'registration to exit permissive mode.';
                this.emitAuditEvent({
                    type: 'agent-identity-token-missing',
                    severity: 'WARN',
                    agentId,
                    agentName: agent.name,
                    reason: 'AGENT_IDENTITY_TOKEN_MISSING',
                    detail: permissiveDetail,
                    timestamp: new Date().toISOString(),
                });
                return {
                    verified: true,
                    reason: 'AGENT_IDENTITY_UNVERIFIED_ALLOWED',
                    detail: permissiveDetail,
                };
            }
            const missingDetail = `agent '${agentId}' has no identityToken; identity verification is ` +
                'required per SOC 2 CC6.1 and EU AI Act Art. 13. Configure an ' +
                'identity issuer and provide the token at agent registration.';
            this.emitAuditEvent({
                type: 'agent-identity-verification-failed',
                severity: 'WARN',
                agentId,
                agentName: agent.name,
                reason: 'AGENT_IDENTITY_TOKEN_MISSING',
                detail: missingDetail,
                timestamp: new Date().toISOString(),
            });
            return {
                verified: false,
                reason: 'AGENT_IDENTITY_TOKEN_MISSING',
                detail: missingDetail,
            };
        }
        // Validate token against HMAC of agent name using secretSeed from config or env
        const secretSeed = this.config?.platformOptions?.['secretSeed']
            || process.env.GOVERNANCE_SECRET_SEED
            || '';
        if (!secretSeed) {
            const noSecretDetail = `agent '${agentId}' presented an identityToken, but no secretSeed ` +
                'is configured. Cannot verify HMAC. Set ' +
                'AdapterConfig.platformOptions.secretSeed or GOVERNANCE_SECRET_SEED.';
            this.emitAuditEvent({
                type: 'agent-identity-verification-failed',
                severity: 'WARN',
                agentId,
                agentName: agent.name,
                reason: 'AGENT_IDENTITY_SECRET_MISSING',
                detail: noSecretDetail,
                timestamp: new Date().toISOString(),
            });
            return {
                verified: false,
                reason: 'AGENT_IDENTITY_SECRET_MISSING',
                detail: noSecretDetail,
            };
        }
        const expectedToken = crypto
            .createHmac('sha256', secretSeed)
            .update(agent.name)
            .digest('hex');
        let tokensEqual = false;
        try {
            tokensEqual = crypto.timingSafeEqual(Buffer.from(agent.identityToken, 'hex'), Buffer.from(expectedToken, 'hex'));
        }
        catch {
            // Buffer length mismatch or invalid hex -- treat as invalid.
            tokensEqual = false;
        }
        if (!tokensEqual) {
            const invalidDetail = `agent '${agentId}' presented an identityToken that did not match ` +
                'the issuer-expected HMAC signature. Treated as a forgery attempt.';
            this.emitAuditEvent({
                type: 'agent-identity-verification-failed',
                severity: 'WARN',
                agentId,
                agentName: agent.name,
                reason: 'AGENT_IDENTITY_TOKEN_INVALID',
                detail: invalidDetail,
                timestamp: new Date().toISOString(),
            });
            return {
                verified: false,
                reason: 'AGENT_IDENTITY_TOKEN_INVALID',
                detail: invalidDetail,
            };
        }
        return {
            verified: true,
            reason: 'AGENT_IDENTITY_VERIFIED',
            detail: `agent '${agentId}' identityToken verified against issuer HMAC.`,
        };
    }
    /**
     * Emit an audit event via the configured callback, if any. No-op if
     * `auditCallback` is not wired. Thrown exceptions from the callback are
     * caught so a broken audit sink cannot crash the governance path; the
     * error is surfaced on stderr so operators notice the drop.
     */
    emitAuditEvent(event) {
        const sink = this.config?.auditCallback;
        if (!sink)
            return;
        try {
            sink(event);
        }
        catch (err) {
            try {
                // eslint-disable-next-line no-console
                console.error('[adapter.auditCallback] threw while emitting identity event', { type: event.type, agentId: event.agentId, error: err?.message ?? String(err) });
            }
            catch {
                // last-resort: swallow. stderr itself is unavailable.
            }
        }
    }
    /**
     * Guard: throws if adapter is used before initialization.
     */
    ensureInitialized() {
        if (!this._initialized) {
            throw new Error(`Adapter '${this._name || '(unnamed)'}' must be initialized before use. ` +
                'Call initialize(config) first.');
        }
    }
}
// ---------------------------------------------------------------------------
// Helper: create an ActionDecision
// ---------------------------------------------------------------------------
export function allow(reason, metadata) {
    return { type: ActionDecisionType.ALLOW, reason, metadata };
}
export function deny(reason, metadata) {
    return { type: ActionDecisionType.DENY, reason, metadata };
}
export function requireApproval(reason, metadata) {
    return { type: ActionDecisionType.REQUIRE_APPROVAL, reason, metadata };
}
// ---------------------------------------------------------------------------
// Helper: create an ActionContext
// ---------------------------------------------------------------------------
export function createActionContext(toolName, input, agent, sessionInfo) {
    return {
        toolName,
        input,
        agent,
        timestamp: new Date().toISOString(),
        sessionInfo,
    };
}
// ---------------------------------------------------------------------------
// Boot-gate: identity config vs active compliance packs (SYSTEMIC-H1)
// ---------------------------------------------------------------------------
/**
 * Pack IDs that MUST NOT coexist with `allowUnverifiedIdentity: true`.
 * Each of these frameworks mandates a verifiable actor binding for every
 * agent action. Permissive identity mode is a direct violation.
 *
 *   - HIPAA 164.312(a)(2)(i): unique user identification
 *   - SOC 2 CC6.1: authenticated access
 *   - EU AI Act Art. 13, 17: traceability + identifiability of AI actors
 *
 * Pack IDs are compared case-insensitively and include common spellings
 * ("soc2", "soc-2", "soc_2", "eu-ai-act", "eu_ai_act", "euai").
 */
export const IDENTITY_STRICT_PACK_IDS = Object.freeze([
    'hipaa',
    'soc2',
    'soc_2',
    'soc-2',
    'eu_ai_act',
    'eu-ai-act',
    'euai',
]);
/**
 * Boot-time check: refuse to start when permissive identity mode is
 * configured alongside an identity-strict compliance pack. Callers
 * (server boot, runtime initialize) MUST invoke this before serving
 * traffic.
 *
 * Throws with a structured operator-triage message on incompatibility.
 * Emits a CRITICAL audit event via `config.auditCallback` (if supplied)
 * before throwing, so the refusal is chained alongside other boot-gate
 * refusals.
 */
export function assertIdentityConfigCompatibility(config, activePackIds) {
    if (config.allowUnverifiedIdentity !== true)
        return;
    const normalized = new Set(activePackIds.map((p) => String(p).toLowerCase()));
    const offenders = IDENTITY_STRICT_PACK_IDS.filter((p) => normalized.has(p));
    if (offenders.length === 0)
        return;
    const offenderList = offenders.join(', ');
    const detail = 'SECURITY: allowUnverifiedIdentity=true is incompatible with the ' +
        `following active compliance pack(s): ${offenderList}. ` +
        'These packs require a verifiable actor binding per HIPAA ' +
        '164.312(a)(2)(i) / SOC 2 CC6.1 / EU AI Act Art. 13 and 17. ' +
        'Refusing to boot. Either disable allowUnverifiedIdentity, or ' +
        'deactivate the incompatible pack(s).';
    const callback = config.auditCallback;
    if (callback) {
        try {
            callback({
                type: 'agent-identity-permissive-mode-boot-refused',
                severity: 'CRITICAL',
                agentId: config.adapterName,
                agentName: config.adapterName,
                reason: 'INCOMPATIBLE_PACK_ACTIVE',
                detail,
                timestamp: new Date().toISOString(),
            });
        }
        catch {
            // auditCallback must not block the refusal.
        }
    }
    throw new Error(detail);
}
//# sourceMappingURL=index.js.map