"use strict";
/**
 * BaseOrchestratorAdapter -- Abstract base class for all orchestrator adapters.
 *
 * Provides common implementations of:
 *   - Reviewer-queue integration (routing REQUIRE_APPROVAL to configured tiers)
 *   - Canonical audit-event emission (Section 3.2 taxonomy)
 *   - Plan-tree DFS with cycle detection (AC 3, AC 22)
 *   - Per-role policy dispatch via the role registry (Section 3.3)
 *   - Error translation scaffold
 *
 * Fail-closed rules (Thomas philosophy, AC 28):
 *   - initialize() without reviewerQueues binding throws ReviewerRoutingError
 *   - Missing role throws RoleNotRegisteredError at first dispatch
 *   - REQUIRE_APPROVAL with no resolvable tier throws ReviewerRoutingError
 *   - Plan with no steps, a cycle, or undefined deps throws PlanValidationError
 *
 * Concrete adapters override only framework-specific methods.
 *
 * Spec: P3-ORCH-01, AC 3, AC 28
 * @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.BaseOrchestratorAdapter = void 0;
const crypto = __importStar(require("crypto"));
const index_1 = require("./index");
const shared_1 = require("./shared");
const agent_message_envelope_1 = require("../security/agent-message-envelope");
const _LOCAL_SECRET_PATTERNS = [
    { name: 'anthropic-api-key', re: /sk-ant-api[-\w]{20,}/i },
    { name: 'openai-api-key', re: /sk-[A-Za-z0-9]{32,}/i },
    { name: 'aws-access-key', re: /AKIA[A-Z0-9]{16}/i },
    { name: 'github-token', re: /ghp_[A-Za-z0-9]{36}/i },
    { name: 'jwt-token', re: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/ },
    { name: 'private-key-header', re: /-----BEGIN\s+(RSA\s+)?PRIVATE KEY-----/ },
    { name: 'generic-api-key', re: /api[_-]?key\s*[:=]\s*["']?[A-Za-z0-9_\-]{16,}["']?/i },
];
const _LOCAL_PHI_PATTERNS = [
    /\b\d{3}-\d{2}-\d{4}\b/, // SSN pattern
    /\b(patient_id|mrn|dob|date_of_birth|diagnosis|ssn|npi)\s*[:=]/i,
    /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{1,4}\b/, // credit-card-like
];
// ---------------------------------------------------------------------------
// BaseOrchestratorAdapter
// ---------------------------------------------------------------------------
class BaseOrchestratorAdapter {
    config;
    reviewerQueues;
    roles = new Map();
    _initialized = false;
    // -- Canonical audit-event sink (in-process; concrete adapters may override) --
    canonicalEvents = [];
    // -- Circuit-breaker state ------------------------------------------------
    circuitBreakerOpen = false;
    consecutiveFailures = 0;
    circuitBreakerThreshold = 3;
    // ---------------------------------------------------------------------------
    // initialize (AC 28: fail-closed on missing reviewer-queue binding)
    // ---------------------------------------------------------------------------
    initialize(config) {
        // Fail closed: reviewer-queue binding is mandatory
        const hasAnyQueue = Object.keys(config.reviewerQueues ?? {}).length > 0;
        if (!hasAnyQueue) {
            throw new index_1.ReviewerRoutingError({
                message: `Adapter "${this.adapterId}" initialized without any reviewer-queue binding. ` +
                    'Provide at least one tier in reviewerQueues or the adapter cannot route REQUIRE_APPROVAL decisions.',
                requestedTier: 'none',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        this.config = config;
        this.reviewerQueues = config.reviewerQueues;
        this.circuitBreakerThreshold = config.circuitBreakerThreshold ?? 3;
        // Register roles
        this.roles = new Map();
        for (const reg of config.roles ?? []) {
            this.roles.set(reg.role, reg);
        }
        this._initialized = true;
        // ASI-07 config safety warnings (Shield audit F-3 / F-6, 2026-05-29) — once
        // at init, never per-message (no log spam).
        const ias = config.interAgentSigning;
        if (ias) {
            // F-3: replay protection is silently off if a verifier is configured
            // (resolvePublicKey) without a nonce store. Signed messages can then be
            // replayed within the clock-skew window.
            if (ias.resolvePublicKey && !ias.nonceSeen) {
                process.stderr.write(`[ASI-07][WARN] Adapter "${this.adapterId}": inter-agent signing verifies signatures but ` +
                    `nonceSeen/recordNonce are not wired — REPLAY PROTECTION IS DISABLED (a signed message can be ` +
                    `replayed within the skew window). Inject a NonceStore to enable it.\n`);
            }
            // F-6: enforcement='warn' (default) is detective-only — invalid inbound
            // messages are allowed with a governance flag, not blocked.
            if (ias.enforcement !== 'block') {
                process.stderr.write(`[ASI-07][WARN] Adapter "${this.adapterId}": inter-agent signing enforcement is "warn" (default) — ` +
                    `invalid inbound messages are ALLOWED with an audit flag. Set enforcement:'block' to deny them, ` +
                    `or ensure your policy layer consumes orchestrator:message-rejected events (Invariant I-2).\n`);
            }
        }
    }
    assertInitialized() {
        if (!this._initialized) {
            throw new index_1.OrchestratorError({
                message: `Adapter "${this.adapterId}" has not been initialized. Call initialize() first.`,
                code: 'ADAPTER_NOT_INITIALIZED',
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
    }
    // ---------------------------------------------------------------------------
    // Canonical audit-event emission
    // ---------------------------------------------------------------------------
    /**
     * Emit a canonical audit event. The base implementation pushes to an
     * in-process buffer; concrete adapters may override to also forward to
     * an EventBus or external sink.
     */
    emitCanonical(eventName, payload) {
        const event = (0, shared_1.buildCanonicalAuditEvent)(this.adapterId, this.orchestratorId, eventName, payload);
        this.canonicalEvents.push(event);
        return event;
    }
    /** Return a copy of accumulated canonical events (for testing / reporting). */
    getCanonicalEvents() {
        return [...this.canonicalEvents];
    }
    // ---------------------------------------------------------------------------
    // ASI-07: inter-agent message signing (per-message Ed25519)
    //
    // Every concrete adapter inherits these — that is what makes ASI-07 coverage
    // zero-caveat across all 16 orchestrators rather than wired into a chosen
    // few. Signing/verification are pure in-process crypto (Invariant I-10) and
    // NEVER throw on the agent path (Invariants I-2/I-3): a bad inbound message
    // becomes a governance decision, never a crash.
    // ---------------------------------------------------------------------------
    /** True when this adapter instance is configured to sign/verify inter-agent messages. */
    get interAgentSigningEnabled() {
        return !!this.config?.interAgentSigning;
    }
    /**
     * Sign an outbound inter-agent message from `fromAgentId`. Returns a signed
     * envelope, or `null` when signing is not configured (caller sends unsigned;
     * the scanner flags that statically as an ASI-07 gap). Emits
     * `orchestrator:message-signed` on success.
     */
    signOutboundMessage(fromAgentId, payload) {
        const cfg = this.config?.interAgentSigning;
        if (!cfg?.signingKeyPem)
            return null;
        const envelope = (0, agent_message_envelope_1.signAgentMessage)({
            payload,
            agentId: fromAgentId,
            privateKeyPem: cfg.signingKeyPem,
        });
        this.emitCanonical('orchestrator:message-signed', {
            fromAgentId,
            nonce: envelope.nonce,
            signedAt: envelope.timestamp,
        });
        return envelope;
    }
    /**
     * Verify an inbound inter-agent message. Returns the structured verify
     * result (never throws). Emits `orchestrator:message-verified` on success or
     * `orchestrator:message-rejected` on any failure. When signing is not
     * configured, returns a benign `valid:true` pass-through so unsigned legacy
     * traffic is not blocked (the scanner is what flags the missing capability).
     */
    verifyInboundMessage(envelope) {
        const cfg = this.config?.interAgentSigning;
        if (!cfg?.resolvePublicKey) {
            return {
                valid: true,
                status: 'valid',
                agentId: envelope?.agentId ?? null,
                reason: 'Inter-agent signing not configured on this adapter — verification skipped (advisory).',
            };
        }
        const result = (0, agent_message_envelope_1.verifyAgentMessage)(envelope, {
            resolvePublicKey: cfg.resolvePublicKey,
            nonceSeen: cfg.nonceSeen,
            recordNonce: cfg.recordNonce,
            maxClockSkewMs: cfg.maxClockSkewMs,
        });
        if (result.valid) {
            this.emitCanonical('orchestrator:message-verified', {
                fromAgentId: result.agentId,
                nonce: envelope?.nonce,
            });
        }
        else {
            this.emitCanonical('orchestrator:message-rejected', {
                fromAgentId: result.agentId,
                status: result.status,
                reason: result.reason,
            });
        }
        return result;
    }
    /**
     * Map an inbound message verification to a delegation governance action,
     * honoring the configured enforcement mode. NEVER throws (I-3).
     *   - valid                      → ALLOW
     *   - invalid + enforcement warn  → WARN (proceed, flagged)  [default]
     *   - invalid + enforcement block → DENY
     * The deciding org's policy consumes this; the platform never hard-gates.
     */
    enforceInboundDelegationSignature(envelope) {
        const result = this.verifyInboundMessage(envelope);
        if (result.valid)
            return { action: 'ALLOW', result };
        const mode = this.config?.interAgentSigning?.enforcement ?? 'warn';
        return { action: mode === 'block' ? 'DENY' : 'WARN', result };
    }
    // ---------------------------------------------------------------------------
    // Reviewer-queue routing (Section 3.4)
    // ---------------------------------------------------------------------------
    async routeToReviewer(tier, ctx) {
        this.assertInitialized();
        const queueId = this.reviewerQueues[tier];
        if (!queueId) {
            // Fail closed: no queue binding for the requested tier
            this.emitCanonical('orchestrator:unregistered-role', {
                requestedTier: tier,
                subject: ctx.subject,
            });
            throw new index_1.ReviewerRoutingError({
                message: `No reviewer-queue binding for tier "${tier}" on adapter "${this.adapterId}". ` +
                    'Configure the tier in reviewerQueues or declare a fallback tier in governance-runtime config.',
                requestedTier: tier,
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        const entryId = crypto.randomUUID();
        this.emitCanonical('orchestrator:reviewer-routed', {
            tier,
            queueId,
            entryId,
            subject: ctx.subject,
            requestingAgent: ctx.requestingAgent.name,
        });
        return {
            queueEntryId: entryId,
            resolvedTier: tier,
        };
    }
    // ---------------------------------------------------------------------------
    // Per-role policy dispatch (Section 3.3)
    // ---------------------------------------------------------------------------
    async dispatchRolePolicy(role, ctx) {
        this.assertInitialized();
        const registration = this.roles.get(role);
        if (!registration) {
            this.emitCanonical('orchestrator:unregistered-role', {
                role,
                registeredRoles: [...this.roles.keys()],
            });
            throw new index_1.RoleNotRegisteredError({
                message: `Role "${role}" is not registered with adapter "${this.adapterId}". ` +
                    `Registered roles: [${[...this.roles.keys()].join(', ')}].`,
                role,
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        // Fail closed: if no policy pack, deny
        if (!registration.policyPack) {
            return {
                type: 'DENY',
                reason: `Role "${role}" has no associated policy pack. Fail closed per Thomas philosophy.`,
                auditId: crypto.randomUUID(),
            };
        }
        // Default: allow if role + pack are registered (concrete adapters apply richer logic)
        return {
            type: 'ALLOW',
            reason: `Role "${role}" dispatched against policy pack "${registration.policyPack}".`,
            auditId: crypto.randomUUID(),
        };
    }
    // ---------------------------------------------------------------------------
    // Plan-tree traversal with cycle detection (AC 3, AC 22)
    // ---------------------------------------------------------------------------
    async traversePlanTree(root, visitor) {
        this.assertInitialized();
        // Validate before traversal (AC 22)
        this.validatePlanStructure(root);
        // Build adjacency: stepId -> steps that depend on it (children in tree)
        const stepMap = new Map();
        for (const step of root.steps) {
            stepMap.set(step.stepId, step);
        }
        // DFS
        const visited = new Set();
        const dfs = async (stepId, depth) => {
            if (visited.has(stepId))
                return;
            visited.add(stepId);
            const step = stepMap.get(stepId);
            if (!step)
                return;
            this.emitCanonical('orchestrator:plan-step-begin', { stepId, depth });
            await visitor.visitStep(step, depth);
            this.emitCanonical('orchestrator:plan-step-end', { stepId, depth });
            // Find child steps (steps that list this stepId as a dependency)
            for (const [childId, childStep] of stepMap) {
                if (childStep.dependencies.includes(stepId)) {
                    await dfs(childId, depth + 1);
                }
            }
        };
        // Start from root steps (steps with no dependencies)
        const rootSteps = root.steps.filter((s) => s.dependencies.length === 0);
        for (const step of rootSteps) {
            await dfs(step.stepId, 0);
        }
        // Visit any disconnected steps not reachable from roots
        for (const step of root.steps) {
            if (!visited.has(step.stepId)) {
                await dfs(step.stepId, 0);
            }
        }
    }
    /**
     * Validate plan structure. Throws PlanValidationError on:
     *   - Empty steps array
     *   - Cycle in the dependency graph
     *   - Dependency referencing an undefined stepId
     * This is called synchronously inside traversePlanTree and prePlan.
     */
    validatePlanStructure(plan) {
        if (plan.steps.length === 0) {
            throw new index_1.PlanValidationError({
                message: `Plan "${plan.planId}" has no steps. Empty plans are not permitted.`,
                planId: plan.planId,
                failedStepIds: [],
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        const undefinedDeps = (0, shared_1.findUndefinedDependencies)(plan.steps);
        if (undefinedDeps.length > 0) {
            throw new index_1.PlanValidationError({
                message: `Plan "${plan.planId}" contains undefined dependency IDs: [${undefinedDeps.join(', ')}].`,
                planId: plan.planId,
                failedStepIds: undefinedDeps,
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
        const cycleNodes = (0, shared_1.detectPlanCycle)(plan.steps);
        if (cycleNodes.length > 0) {
            throw new index_1.PlanValidationError({
                message: `Plan "${plan.planId}" contains a cycle involving steps: [${cycleNodes.join(', ')}].`,
                planId: plan.planId,
                failedStepIds: cycleNodes,
                orchestratorId: this.orchestratorId,
                adapterId: this.adapterId,
            });
        }
    }
    // ---------------------------------------------------------------------------
    // postActionClassify -- G-series classification for tool outputs (C1 fix)
    // ---------------------------------------------------------------------------
    /**
     * Run G-series classification on a tool/action output.
     *
     * C1 fix: orchestrator tool outputs were not running through the nine-guard
     * G-series classification. This method provides the hook so every adapter's
     * onToolEnd / handleToolEnd calls it, ensuring tool results are screened for
     * secrets (G2), PHI (G3), and policy violations before being passed to the
     * calling agent.
     *
     * Implementation: performs a lightweight in-process scan using known secret
     * and PHI indicator patterns (sufficient for local enforcement), emits a
     * canonical `orchestrator:tool-output-classified` event with the
     * classification result, and returns true if the output is clean.
     *
     * If `config.governanceRuntime` exposes a classify endpoint, concrete adapters
     * should override this method to also call it. The base implementation is
     * always available as a safe fallback.
     *
     * @param auditId   The audit ID from the preceding preTask/preAction decision.
     * @param output    The raw tool output (any type; converted to string for scan).
     * @param agentName Name of the agent for logging context.
     * @param packBinding Active compliance packs for classification scope.
     * @returns true if the output passes classification; false if guards triggered.
     */
    postActionClassify(auditId, output, agentName, packBinding = []) {
        const outputText = typeof output === 'string'
            ? output
            : typeof output === 'object' && output !== null
                ? JSON.stringify(output)
                : String(output ?? '');
        // H9a: G2 also scans base64-decoded content to prevent encoding bypass.
        // Attackers may base64-encode a secret to evade plain-text pattern matching.
        // We decode any plausible base64 blobs (length >= 20, valid base64 chars)
        // and run the same secret patterns against the decoded content.
        const base64Blobs = outputText.match(/[A-Za-z0-9+/]{20,}={0,2}/g) ?? [];
        let decodedOutputText = outputText;
        for (const blob of base64Blobs) {
            try {
                const decoded = Buffer.from(blob, 'base64').toString('utf-8');
                // Only append if decoding produced valid UTF-8 printable text
                if (/^[\x20-\x7E\t\n\r]+$/.test(decoded)) {
                    decodedOutputText += '\n' + decoded;
                }
            }
            catch {
                // Ignore decode errors
            }
        }
        // Lightweight in-process G-series scan (G2: secrets, G3: PHI indicators)
        // Scan both original and decoded content for G2; original only for G3 (PHI)
        const secretPattern = _LOCAL_SECRET_PATTERNS.find((p) => p.re.test(outputText) || p.re.test(decodedOutputText));
        const phiPattern = _LOCAL_PHI_PATTERNS.find((p) => p.test(outputText));
        const clean = !secretPattern && !phiPattern;
        const guardsTriggered = [];
        if (secretPattern)
            guardsTriggered.push(`G2:secret:${secretPattern.name}`);
        if (phiPattern)
            guardsTriggered.push(`G3:phi`);
        this.emitCanonical('orchestrator:tool-output-classified', {
            auditId,
            agentName,
            packBinding,
            clean,
            guardsTriggered,
            outputSampleLength: outputText.length,
        });
        if (!clean) {
            // eslint-disable-next-line no-console
            console.warn(`[GOVERNANCE] G-series postActionClassify: tool output flagged ` +
                `(agent=${agentName}, audit=${auditId}). ` +
                `Guards triggered: [${guardsTriggered.join(', ')}]. (C1: postActionClassify)`);
        }
        return clean;
    }
    // ---------------------------------------------------------------------------
    // Circuit-breaker helpers
    // ---------------------------------------------------------------------------
    recordSuccess() {
        this.consecutiveFailures = 0;
    }
    recordFailure() {
        this.consecutiveFailures += 1;
        if (this.consecutiveFailures >= this.circuitBreakerThreshold) {
            this.circuitBreakerOpen = true;
            this.emitCanonical('orchestrator:circuit-breaker-tripped', {
                consecutiveFailures: this.consecutiveFailures,
                threshold: this.circuitBreakerThreshold,
                adapterId: this.adapterId,
            });
        }
    }
    isCircuitOpen() {
        return this.circuitBreakerOpen;
    }
    // ---------------------------------------------------------------------------
    // Error translation (default implementation)
    // ---------------------------------------------------------------------------
    translateError(raw) {
        if (raw instanceof index_1.OrchestratorError)
            return raw;
        const message = raw instanceof Error ? raw.message : String(raw);
        return new index_1.OrchestratorError({
            message,
            code: 'ORCHESTRATOR_ERROR',
            orchestratorId: this.orchestratorId,
            adapterId: this.adapterId,
            raw,
        });
    }
}
exports.BaseOrchestratorAdapter = BaseOrchestratorAdapter;
//# sourceMappingURL=base.js.map