/**
 * DecisionEvent Emitter
 *
 * CONSTITUTION REFERENCE: PROMPT 0, PROMPT 2
 *
 * PURPOSE: Emit CLI UX-level DecisionEvents to ruvector-service.
 *
 * EMISSION RULES:
 * - At most ONE DecisionEvent per agent invocation
 * - Async, non-blocking emission only
 * - NEVER persists locally
 * - MAY fail silently (UX must not depend on emission)
 *
 * DATA RULES:
 * - Only emit UX telemetry and interaction data
 * - Never emit raw inputs or sensitive data
 * - Always use hashes for reproducibility
 */
import * as crypto from 'node:crypto';
import { CLI_UX_AGENT_ID, CLI_UX_AGENT_VERSION, } from './types.js';
/**
 * Builder for constructing DecisionEvents.
 * Ensures all required fields are set before emission.
 */
export class DecisionEventBuilder {
    trace_id;
    command;
    subcommand;
    decision_type = 'cli_ux_orchestration';
    inputs_hash = '';
    result_type = 'success';
    artifact_ref;
    services_invoked = [];
    confidence = {
        value: 1.0,
        basis: 'command_resolution',
    };
    constraints = {
        permissions: [],
        environment: {
            is_local: true,
            is_production: false,
            execution_gate_enabled: true,
            auth_present: false,
        },
        scope: [],
    };
    execution_ref = null;
    constructor(options) {
        this.trace_id = options.trace_id;
        this.command = options.command;
        this.subcommand = options.subcommand;
    }
    /**
     * Set decision type.
     */
    withDecisionType(type) {
        this.decision_type = type;
        return this;
    }
    /**
     * Set inputs hash from raw inputs.
     * Computes hash, never stores raw data.
     */
    withInputsHash(inputs) {
        this.inputs_hash = this.computeHash(inputs);
        return this;
    }
    /**
     * Set result type.
     */
    withResultType(type) {
        this.result_type = type;
        return this;
    }
    /**
     * Set artifact reference.
     */
    withArtifactRef(ref) {
        this.artifact_ref = ref;
        return this;
    }
    /**
     * Add services that were invoked.
     */
    withServicesInvoked(services) {
        this.services_invoked = services;
        return this;
    }
    /**
     * Set confidence score.
     */
    withConfidence(confidence) {
        this.confidence = confidence;
        return this;
    }
    /**
     * Set constraints applied.
     */
    withConstraints(constraints) {
        this.constraints = constraints;
        return this;
    }
    /**
     * Set environment context.
     */
    withEnvironment(env) {
        this.constraints = {
            ...this.constraints,
            environment: env,
        };
        return this;
    }
    /**
     * Set execution reference from downstream service.
     */
    withExecutionRef(ref) {
        this.execution_ref = ref;
        return this;
    }
    /**
     * Build the DecisionEvent.
     */
    build() {
        const outputs = {
            command: this.command,
            subcommand: this.subcommand,
            result_type: this.result_type,
            artifact_ref: this.artifact_ref,
            services_invoked: this.services_invoked,
        };
        return {
            agent_id: CLI_UX_AGENT_ID,
            agent_version: CLI_UX_AGENT_VERSION,
            decision_type: this.decision_type,
            inputs_hash: this.inputs_hash,
            outputs,
            confidence: this.confidence,
            constraints_applied: this.constraints,
            execution_ref: this.execution_ref,
            timestamp: new Date().toISOString(),
            trace_id: this.trace_id,
        };
    }
    /**
     * Compute hash of inputs for reproducibility.
     */
    computeHash(inputs) {
        const canonical = JSON.stringify(inputs, this.sortKeys);
        return crypto.createHash('sha256').update(canonical).digest('hex').slice(0, 16);
    }
    sortKeys(_key, value) {
        if (value && typeof value === 'object' && !Array.isArray(value)) {
            return Object.keys(value)
                .sort()
                .reduce((sorted, key) => {
                sorted[key] = value[key];
                return sorted;
            }, {});
        }
        return value;
    }
}
/**
 * Emits DecisionEvents to ruvector-service.
 *
 * CRITICAL: Emission is async and non-blocking.
 * Failures are logged but do not affect UX flow.
 */
export class DecisionEventEmitter {
    config;
    constructor(config = {}) {
        this.config = {
            ruvector_url: config.ruvector_url ?? process.env['RUVECTOR_SERVICE_URL'] ?? '',
            enabled: config.enabled ?? !!config.ruvector_url,
            timeout: config.timeout ?? 5000,
            auth_token: config.auth_token ?? '',
        };
    }
    /**
     * Check if emission is enabled.
     */
    isEnabled() {
        return this.config.enabled && !!this.config.ruvector_url;
    }
    /**
     * Emit a DecisionEvent.
     *
     * RULES:
     * - Async, non-blocking
     * - Never throws - failures are silent
     * - Only one event per invocation should be emitted
     */
    async emit(event) {
        if (!this.isEnabled()) {
            return {
                success: false,
                reason: 'emission_disabled',
            };
        }
        try {
            const controller = new AbortController();
            const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
            const headers = {
                'Content-Type': 'application/json',
                'X-Trace-ID': event.trace_id,
                'X-Agent-ID': event.agent_id,
                'X-Agent-Version': event.agent_version,
            };
            if (this.config.auth_token) {
                headers['Authorization'] = `Bearer ${this.config.auth_token}`;
            }
            const response = await fetch(`${this.config.ruvector_url}/v1/decision-events`, {
                method: 'POST',
                headers,
                body: JSON.stringify(event),
                signal: controller.signal,
            });
            clearTimeout(timeoutId);
            if (!response.ok) {
                return {
                    success: false,
                    reason: 'http_error',
                    status_code: response.status,
                };
            }
            return {
                success: true,
                event_id: event.trace_id,
            };
        }
        catch (error) {
            // Silent failure - emission must not affect UX
            return {
                success: false,
                reason: error instanceof Error && error.name === 'AbortError' ? 'timeout' : 'network_error',
            };
        }
    }
    /**
     * Emit without waiting (fire and forget).
     * Failures are logged to stderr — no silent swallowing.
     */
    emitAsync(event) {
        if (!this.isEnabled()) {
            return;
        }
        // Fire and forget - don't await, but DO surface errors
        this.emit(event).catch((error) => {
            const message = error instanceof Error ? error.message : String(error);
            process.stderr.write(`[decision-event] Emission failed: ${message}\n`);
        });
    }
}
/**
 * Emits telemetry events compatible with LLM-Observatory.
 */
export class TelemetryEmitter {
    config;
    buffer = [];
    flushTimer = null;
    constructor(config = {}) {
        this.config = {
            enabled: config.enabled ?? false,
            endpoint: config.endpoint ?? '',
            batch_size: config.batch_size ?? 10,
            flush_interval: config.flush_interval ?? 5000,
        };
        if (this.config.enabled && this.config.endpoint) {
            this.startFlushTimer();
        }
    }
    /**
     * Record a telemetry event.
     */
    record(event_type, agent_id, trace_id, data) {
        if (!this.config.enabled) {
            return;
        }
        const event = {
            event_type,
            agent_id,
            timestamp: new Date().toISOString(),
            trace_id,
            data,
        };
        this.buffer.push(event);
        if (this.buffer.length >= this.config.batch_size) {
            this.flush();
        }
    }
    /**
     * Flush buffered events.
     */
    async flush() {
        if (this.buffer.length === 0 || !this.config.endpoint) {
            return;
        }
        const events = this.buffer.splice(0, this.buffer.length);
        try {
            const response = await fetch(this.config.endpoint, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ events }),
            });
            if (!response.ok) {
                process.stderr.write(`[telemetry] Flush failed: HTTP ${response.status}\n`);
            }
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            process.stderr.write(`[telemetry] Flush failed: ${message}\n`);
        }
    }
    /**
     * Stop the flush timer.
     */
    stop() {
        if (this.flushTimer) {
            clearInterval(this.flushTimer);
            this.flushTimer = null;
        }
    }
    startFlushTimer() {
        this.flushTimer = setInterval(() => {
            this.flush();
        }, this.config.flush_interval);
    }
}
// ============================================================================
// Factory Functions
// ============================================================================
/**
 * Create a DecisionEvent builder.
 */
export function createDecisionEventBuilder(options) {
    return new DecisionEventBuilder(options);
}
/**
 * Create a DecisionEvent emitter.
 */
export function createDecisionEventEmitter(config) {
    return new DecisionEventEmitter(config);
}
/**
 * Create a telemetry emitter.
 */
export function createTelemetryEmitter(config) {
    return new TelemetryEmitter(config);
}
//# sourceMappingURL=decision-event.js.map