import { DEFAULT_ORCHESTRATED_WORKFLOWS, ORCHESTRATOR_MODE, WORKFLOW_ENGINE, normalizeStringList, normalizeWorkflowEngineId, normalizeWorkflowName, stableRolloutBucket, } from './contracts.mjs'; import { listPhase5ExecutorAdapters } from './executor-gateway.mjs'; const CONFIG_TABLE = 'h5_orchestrator_admin_config'; const CONFIG_SCOPE = 'global'; const EXECUTION_HANDOFF_IMPLEMENTED = true; const CONFIG_SCHEMA_PROMISES = new WeakMap(); function normalizeBoolean(value, fallback = false) { if (value == null || value === '') return fallback; if (typeof value === 'boolean') return value; const normalized = String(value).trim().toLowerCase(); if (['1', 'true', 'yes', 'on'].includes(normalized)) return true; if (['0', 'false', 'no', 'off'].includes(normalized)) return false; return fallback; } function clampInteger(value, fallback, min, max) { const number = Number(value); if (!Number.isFinite(number)) return fallback; return Math.min(max, Math.max(min, Math.floor(number))); } function normalizeMode(value, fallback = ORCHESTRATOR_MODE.OFF) { const normalized = String(value ?? '').trim().toLowerCase(); return Object.values(ORCHESTRATOR_MODE).includes(normalized) ? normalized : fallback; } function normalizeServiceUrl(value) { const raw = String(value ?? '').trim().replace(/\/$/, ''); if (!raw) return ''; try { const url = new URL(raw); if (!['http:', 'https:'].includes(url.protocol)) return ''; if ((url.pathname && url.pathname !== '/') || url.search) return ''; url.username = ''; url.password = ''; url.hash = ''; return url.origin.slice(0, 2048); } catch { return ''; } } function isLoopbackServiceUrl(value) { try { const hostname = new URL(value).hostname.toLowerCase(); return hostname === 'localhost' || hostname === '::1' || hostname === '[::1]' || /^127(?:\.\d{1,3}){3}$/.test(hostname); } catch { return false; } } function allowedServiceOrigins(env = process.env) { const configured = [ env.MEMIND_ORCHESTRATOR_URL, ...String(env.MEMIND_ORCHESTRATOR_ALLOWED_ORIGINS ?? '').split(','), ] .map(normalizeServiceUrl) .filter(Boolean); return new Set(configured); } function isServiceUrlAllowed(value, env = process.env) { const normalized = normalizeServiceUrl(value); if (!normalized) return false; return isLoopbackServiceUrl(normalized) || allowedServiceOrigins(env).has(normalized); } function parseJsonLike(value, fallback) { if (value == null || value === '') return fallback; if (typeof value === 'object') return value; try { return JSON.parse(String(value)); } catch { return fallback; } } function clone(value) { if (typeof structuredClone === 'function') return structuredClone(value); return JSON.parse(JSON.stringify(value)); } export function defaultOrchestratorConfig() { return { mode: ORCHESTRATOR_MODE.OFF, primaryEngine: WORKFLOW_ENGINE.LANGGRAPH, fallbackEngine: WORKFLOW_ENGINE.NATIVE, serviceUrl: '', requestTimeoutMs: 5000, rolloutPercent: 0, userAllowlist: [], workflowAllowlist: [...DEFAULT_ORCHESTRATED_WORKFLOWS], fallbackToNative: true, requireHealthy: true, executionEnabled: false, }; } export function normalizeOrchestratorConfig(input = {}, fallback = defaultOrchestratorConfig()) { const source = input && typeof input === 'object' && !Array.isArray(input) ? input : {}; const base = fallback && typeof fallback === 'object' ? fallback : defaultOrchestratorConfig(); const workflowAllowlist = normalizeStringList( source.workflowAllowlist ?? base.workflowAllowlist, { normalize: (item) => normalizeWorkflowName(item, ''), itemLimit: 128 }, ); return { mode: normalizeMode(source.mode, base.mode), primaryEngine: normalizeWorkflowEngineId(source.primaryEngine, base.primaryEngine), fallbackEngine: normalizeWorkflowEngineId(source.fallbackEngine, base.fallbackEngine), serviceUrl: normalizeServiceUrl(source.serviceUrl ?? base.serviceUrl), requestTimeoutMs: clampInteger( source.requestTimeoutMs, base.requestTimeoutMs, 500, 60_000, ), rolloutPercent: clampInteger(source.rolloutPercent, base.rolloutPercent, 0, 100), userAllowlist: normalizeStringList(source.userAllowlist ?? base.userAllowlist, { itemLimit: 128, }), workflowAllowlist: workflowAllowlist.length ? workflowAllowlist : [...DEFAULT_ORCHESTRATED_WORKFLOWS], fallbackToNative: normalizeBoolean(source.fallbackToNative, base.fallbackToNative), requireHealthy: normalizeBoolean(source.requireHealthy, base.requireHealthy), executionEnabled: normalizeBoolean(source.executionEnabled, base.executionEnabled), }; } function runtimeState(config, env = process.env) { const killSwitch = normalizeBoolean(env.MEMIND_ORCHESTRATOR_KILL_SWITCH, false); const environmentShadowObservationGate = normalizeBoolean( env.MEMIND_ORCHESTRATOR_SHADOW_OBSERVATION_ENABLED, false, ); const environmentExecutionGate = normalizeBoolean( env.MEMIND_ORCHESTRATOR_EXECUTION_HANDOFF_ENABLED, false, ); const environmentPageDataValidationGate = normalizeBoolean( env.MEMIND_ORCHESTRATOR_PAGE_DATA_VALIDATION_GATE_ENABLED, false, ); const configured = config.primaryEngine !== WORKFLOW_ENGINE.LANGGRAPH || Boolean(config.serviceUrl); let reason = null; if (killSwitch) reason = 'kill_switch'; else if (config.mode === ORCHESTRATOR_MODE.OFF) reason = 'mode_off'; else if (!configured) reason = 'service_url_missing'; else if ( config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH && !isServiceUrlAllowed(config.serviceUrl, env) ) reason = 'service_url_not_allowed'; const executionModeSelected = [ORCHESTRATOR_MODE.CANARY, ORCHESTRATOR_MODE.ACTIVE] .includes(config.mode); const executionHandoffRequested = config.executionEnabled && executionModeSelected && config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH; const executionHandoffEnabled = !reason && EXECUTION_HANDOFF_IMPLEMENTED && environmentExecutionGate && executionHandoffRequested && config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH; const shadowObservationRequested = config.mode === ORCHESTRATOR_MODE.SHADOW && config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH; const shadowObservationEnabled = !reason && environmentShadowObservationGate && shadowObservationRequested; const pageDataValidationGateEnabled = environmentPageDataValidationGate && shadowObservationEnabled; return { killSwitch, configured, effective: !reason, reason: reason ?? ( executionModeSelected && config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH && !executionHandoffEnabled ? 'execution_gate_disabled' : null ), executesLangGraph: executionHandoffEnabled, plansLangGraph: !reason && executionModeSelected && config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH, shadowsLangGraph: shadowObservationEnabled, shadowObservation: { requested: shadowObservationRequested, enabled: shadowObservationEnabled, reason: shadowObservationEnabled ? null : !shadowObservationRequested ? 'shadow_observation_not_requested' : reason ? reason : 'environment_shadow_observation_gate_disabled', environmentGate: environmentShadowObservationGate, }, pageDataValidationGate: { requested: environmentPageDataValidationGate, enabled: pageDataValidationGateEnabled, failClosed: true, reason: pageDataValidationGateEnabled ? null : !environmentPageDataValidationGate ? 'environment_page_data_validation_gate_disabled' : !shadowObservationEnabled ? 'shadow_observation_not_enabled' : 'page_data_validation_gate_disabled', environmentGate: environmentPageDataValidationGate, }, executionHandoff: { implemented: EXECUTION_HANDOFF_IMPLEMENTED, requested: executionHandoffRequested, enabled: executionHandoffEnabled, reason: executionHandoffEnabled ? null : !EXECUTION_HANDOFF_IMPLEMENTED ? 'execution_handoff_not_implemented' : !environmentExecutionGate ? 'environment_execution_gate_disabled' : 'execution_handoff_not_requested', environmentGate: environmentExecutionGate, }, }; } function engineCatalog(config) { return [ { id: WORKFLOW_ENGINE.NATIVE, label: 'Native Agent Run', kind: 'built-in', configured: true, capabilities: ['existing-runtime', 'tool-gateway'], }, { id: WORKFLOW_ENGINE.LANGGRAPH, label: 'LangGraph Orchestrator', kind: 'remote', configured: Boolean(config.serviceUrl), capabilities: ['durable-execution', 'interrupt', 'streaming'], }, ]; } async function probeServiceHealth(config, { fetchImpl = globalThis.fetch, } = {}) { const checkedAt = Date.now(); if (!config.serviceUrl) { return { checkedAt, ok: false, status: 'unconfigured', latencyMs: 0, httpStatus: null, details: null, }; } if (typeof fetchImpl !== 'function') { return { checkedAt, ok: false, status: 'fetch_unavailable', latencyMs: 0, httpStatus: null, details: null, }; } const startedAt = Date.now(); const controller = new AbortController(); const timer = setTimeout( () => controller.abort(), Math.max(500, Number(config.requestTimeoutMs) || 5000), ); try { const response = await fetchImpl(`${config.serviceUrl}/ready`, { headers: { accept: 'application/json' }, signal: controller.signal, }); const body = await response.json().catch(() => null); const healthy = response.ok && body?.status === 'ok'; return { checkedAt, ok: healthy, status: healthy ? 'healthy' : 'unhealthy', latencyMs: Math.max(0, Date.now() - startedAt), httpStatus: response.status, details: body && typeof body === 'object' ? { service: body.service == null ? null : String(body.service).slice(0, 128), checkpoint: body.checkpoint && typeof body.checkpoint === 'object' ? { kind: body.checkpoint.kind == null ? null : String(body.checkpoint.kind).slice(0, 64), durable: Boolean(body.checkpoint.durable), } : null, execution: body.execution == null ? null : String(body.execution).slice(0, 64), executorGateway: body.executorGateway && typeof body.executorGateway === 'object' ? { dispatchImplemented: body.executorGateway.dispatchImplemented === true, executionEnabled: body.executorGateway.executionEnabled === true, store: body.executorGateway.store && typeof body.executorGateway.store === 'object' ? { kind: body.executorGateway.store.kind == null ? null : String(body.executorGateway.store.kind).slice(0, 64), durable: body.executorGateway.store.durable === true, } : null, } : null, } : null, }; } catch (error) { return { checkedAt, ok: false, status: error?.name === 'AbortError' ? 'timeout' : 'unreachable', latencyMs: Math.max(0, Date.now() - startedAt), httpStatus: null, details: null, }; } finally { clearTimeout(timer); } } async function ensureConfigTable(pool) { const existing = CONFIG_SCHEMA_PROMISES.get(pool); if (existing) return existing; const initializing = pool.query(` CREATE TABLE IF NOT EXISTS ${CONFIG_TABLE} ( config_scope VARCHAR(32) PRIMARY KEY, config_json JSON NOT NULL, config_version INT NOT NULL DEFAULT 1, updated_by CHAR(36) NULL, updated_at BIGINT NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `).catch((error) => { CONFIG_SCHEMA_PROMISES.delete(pool); throw error; }); CONFIG_SCHEMA_PROMISES.set(pool, initializing); return initializing; } async function loadStoredState(pool) { await ensureConfigTable(pool); const [rows] = await pool.query( `SELECT config_json, config_version, updated_by, updated_at FROM ${CONFIG_TABLE} WHERE config_scope = ? LIMIT 1`, [CONFIG_SCOPE], ); const row = rows[0]; if (!row) return null; return { config: normalizeOrchestratorConfig(parseJsonLike(row.config_json, {})), configVersion: Math.max(1, Number(row.config_version ?? 1) || 1), updatedBy: row.updated_by ?? null, updatedAt: Number(row.updated_at ?? 0) || null, }; } export function createOrchestratorAdminConfigService(pool, { env = process.env, fetchImpl = globalThis.fetch, } = {}) { async function loadEffectiveState() { const stored = await loadStoredState(pool); if (stored) return { ...stored, source: 'admin-db' }; const config = normalizeOrchestratorConfig({ mode: env.MEMIND_ORCHESTRATOR_MODE, serviceUrl: env.MEMIND_ORCHESTRATOR_URL, }); const fromEnv = Boolean(env.MEMIND_ORCHESTRATOR_MODE || env.MEMIND_ORCHESTRATOR_URL); return { config, configVersion: 1, updatedBy: null, updatedAt: null, source: fromEnv ? 'env' : 'default', }; } return { ensureSchema() { return ensureConfigTable(pool); }, async getAdminConfig() { const state = await loadEffectiveState(); return { ...state, runtime: runtimeState(state.config, env), engines: engineCatalog(state.config), executors: listPhase5ExecutorAdapters(), }; }, async updateAdminConfig(patch = {}, { updatedBy = null } = {}) { const stored = await loadStoredState(pool); const current = stored?.config ?? defaultOrchestratorConfig(); const nextConfig = normalizeOrchestratorConfig( { ...current, ...(patch?.config ?? patch) }, current, ); const configVersion = (stored?.configVersion ?? 0) + 1; const now = Date.now(); await pool.query( `INSERT INTO ${CONFIG_TABLE} (config_scope, config_json, config_version, updated_by, updated_at) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE config_json = VALUES(config_json), config_version = VALUES(config_version), updated_by = VALUES(updated_by), updated_at = VALUES(updated_at)`, [CONFIG_SCOPE, JSON.stringify(nextConfig), configVersion, updatedBy, now], ); return this.getAdminConfig(); }, async getRuntimeState({ probe = false } = {}) { const state = await loadEffectiveState(); return { ...state, runtime: runtimeState(state.config, env), engines: engineCatalog(state.config), executors: listPhase5ExecutorAdapters(), ...(probe ? { serviceHealth: await probeServiceHealth(state.config, { fetchImpl }) } : {}), }; }, async selectEngine({ runId, requestId, userId = null, workflowName, } = {}) { const state = await loadEffectiveState(); const config = state.config; const runtime = runtimeState(config, env); const normalizedWorkflow = normalizeWorkflowName(workflowName, ''); const workflowMatched = config.workflowAllowlist.includes(normalizedWorkflow); const base = { engine: WORKFLOW_ENGINE.NATIVE, candidateEngine: WORKFLOW_ENGINE.NATIVE, shadowEngine: null, fallbackEngine: config.fallbackToNative ? config.fallbackEngine : null, mode: config.mode, workflowMatched, configVersion: state.configVersion, reason: runtime.reason, candidateReason: null, dryRun: false, }; if (!runtime.effective || !workflowMatched) { return { ...base, reason: runtime.reason ?? 'workflow_not_enabled' }; } const rolloutKey = runId || requestId || `${userId ?? ''}:${normalizedWorkflow}`; const bucket = stableRolloutBucket(rolloutKey); const explicitlyAllowed = Boolean(userId && config.userAllowlist.includes(String(userId))); const percentAllowed = bucket < config.rolloutPercent; const rolloutSelected = explicitlyAllowed || percentAllowed; const rolloutReason = explicitlyAllowed ? 'user_allowlist' : percentAllowed ? 'percentage' : 'canary_not_selected'; if (config.mode === ORCHESTRATOR_MODE.SHADOW) { return { ...base, shadowEngine: config.primaryEngine, reason: 'shadow', candidateEngine: rolloutSelected ? config.primaryEngine : WORKFLOW_ENGINE.NATIVE, candidateReason: rolloutReason, bucket, dryRun: true, }; } if (config.mode === ORCHESTRATOR_MODE.ACTIVE) { const selected = { ...base, candidateEngine: config.primaryEngine, candidateReason: 'active', }; if (config.primaryEngine === WORKFLOW_ENGINE.NATIVE) { return { ...selected, reason: 'active' }; } return runtime.executionHandoff.enabled ? { ...selected, engine: config.primaryEngine, reason: 'active' } : { ...selected, reason: 'execution_gate_disabled', dryRun: true }; } if (config.mode === ORCHESTRATOR_MODE.CANARY) { if (!rolloutSelected) { return { ...base, reason: 'canary_not_selected', bucket }; } const selected = { ...base, candidateEngine: config.primaryEngine, candidateReason: rolloutReason, bucket, }; if (config.primaryEngine === WORKFLOW_ENGINE.NATIVE) { return { ...selected, reason: rolloutReason }; } return runtime.executionHandoff.enabled ? { ...selected, engine: config.primaryEngine, reason: rolloutReason } : { ...selected, reason: 'execution_gate_disabled', dryRun: true }; } return base; }, }; } export const orchestratorAdminConfigInternals = { CONFIG_SCOPE, CONFIG_TABLE, engineCatalog, EXECUTION_HANDOFF_IMPLEMENTED, isServiceUrlAllowed, normalizeServiceUrl, probeServiceHealth, runtimeState, };