feat: add dry-run workflow execution boundary

This commit is contained in:
john
2026-07-24 22:30:41 +08:00
parent 646178944d
commit c9ad841543
12 changed files with 553 additions and 17 deletions
+60 -7
View File
@@ -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,
};