feat: add dry-run workflow execution boundary
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
|
||||
const CONFIG_TABLE = 'h5_orchestrator_admin_config';
|
||||
const CONFIG_SCOPE = 'global';
|
||||
const EXECUTION_HANDOFF_IMPLEMENTED = false;
|
||||
|
||||
function normalizeBoolean(value, fallback = false) {
|
||||
if (value == null || value === '') return fallback;
|
||||
@@ -113,17 +114,42 @@ function runtimeState(config, env = process.env) {
|
||||
if (killSwitch) reason = 'kill_switch';
|
||||
else if (config.mode === ORCHESTRATOR_MODE.OFF) reason = 'mode_off';
|
||||
else if (!configured) reason = 'service_url_missing';
|
||||
const executionModeSelected = [ORCHESTRATOR_MODE.CANARY, ORCHESTRATOR_MODE.ACTIVE]
|
||||
.includes(config.mode);
|
||||
const executionHandoffRequested = executionModeSelected
|
||||
&& config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH;
|
||||
const executionHandoffEnabled = !reason
|
||||
&& EXECUTION_HANDOFF_IMPLEMENTED
|
||||
&& executionHandoffRequested
|
||||
&& config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH;
|
||||
return {
|
||||
killSwitch,
|
||||
configured,
|
||||
effective: !reason,
|
||||
reason,
|
||||
executesLangGraph: !reason
|
||||
&& [ORCHESTRATOR_MODE.CANARY, ORCHESTRATOR_MODE.ACTIVE].includes(config.mode)
|
||||
reason: reason ?? (
|
||||
executionModeSelected
|
||||
&& config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH
|
||||
&& !executionHandoffEnabled
|
||||
? 'execution_gate_disabled'
|
||||
: null
|
||||
),
|
||||
executesLangGraph: executionHandoffEnabled,
|
||||
plansLangGraph: !reason
|
||||
&& executionModeSelected
|
||||
&& config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH,
|
||||
shadowsLangGraph: !reason
|
||||
&& config.mode === ORCHESTRATOR_MODE.SHADOW
|
||||
&& config.primaryEngine === WORKFLOW_ENGINE.LANGGRAPH,
|
||||
executionHandoff: {
|
||||
implemented: EXECUTION_HANDOFF_IMPLEMENTED,
|
||||
requested: executionHandoffRequested,
|
||||
enabled: executionHandoffEnabled,
|
||||
reason: executionHandoffEnabled
|
||||
? null
|
||||
: EXECUTION_HANDOFF_IMPLEMENTED
|
||||
? 'execution_handoff_not_requested'
|
||||
: 'phase3_dry_run_only',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -324,12 +350,15 @@ export function createOrchestratorAdminConfigService(pool, {
|
||||
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' };
|
||||
@@ -342,16 +371,39 @@ export function createOrchestratorAdminConfigService(pool, {
|
||||
};
|
||||
}
|
||||
if (config.mode === ORCHESTRATOR_MODE.ACTIVE) {
|
||||
return { ...base, engine: config.primaryEngine, reason: '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) {
|
||||
const explicitlyAllowed = Boolean(userId && config.userAllowlist.includes(String(userId)));
|
||||
const rolloutKey = runId || requestId || `${userId ?? ''}:${normalizedWorkflow}`;
|
||||
const bucket = stableRolloutBucket(rolloutKey);
|
||||
const percentAllowed = bucket < config.rolloutPercent;
|
||||
return explicitlyAllowed || percentAllowed
|
||||
? { ...base, engine: config.primaryEngine, reason: explicitlyAllowed ? 'user_allowlist' : 'percentage', bucket }
|
||||
: { ...base, reason: 'canary_not_selected', bucket };
|
||||
if (!explicitlyAllowed && !percentAllowed) {
|
||||
return { ...base, reason: 'canary_not_selected', bucket };
|
||||
}
|
||||
const candidateReason = explicitlyAllowed ? 'user_allowlist' : 'percentage';
|
||||
const selected = {
|
||||
...base,
|
||||
candidateEngine: config.primaryEngine,
|
||||
candidateReason,
|
||||
bucket,
|
||||
};
|
||||
if (config.primaryEngine === WORKFLOW_ENGINE.NATIVE) {
|
||||
return { ...selected, reason: candidateReason };
|
||||
}
|
||||
return runtime.executionHandoff.enabled
|
||||
? { ...selected, engine: config.primaryEngine, reason: candidateReason }
|
||||
: { ...selected, reason: 'execution_gate_disabled', dryRun: true };
|
||||
}
|
||||
return base;
|
||||
},
|
||||
@@ -362,6 +414,7 @@ export const orchestratorAdminConfigInternals = {
|
||||
CONFIG_SCOPE,
|
||||
CONFIG_TABLE,
|
||||
engineCatalog,
|
||||
EXECUTION_HANDOFF_IMPLEMENTED,
|
||||
probeServiceHealth,
|
||||
runtimeState,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user