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
@@ -35,6 +35,56 @@ test('shadow observer skips without creating a remote client when mode does not
observed: false,
reason: 'mode_off',
mode: 'off',
executionPlan: null,
});
assert.equal(fetchCalls, 0);
});
test('shadow boundary returns an auditable dry-run plan without calling LangGraph', async () => {
let fetchCalls = 0;
const observer = createWorkflowShadowObserver({
configService: {
async selectEngine() {
return {
engine: 'native',
candidateEngine: 'langgraph',
shadowEngine: null,
fallbackEngine: 'native',
reason: 'execution_gate_disabled',
candidateReason: 'user_allowlist',
mode: 'canary',
configVersion: 8,
bucket: 42,
dryRun: true,
};
},
async getRuntimeState() {
throw new Error('must not load runtime config');
},
},
fetchImpl: async () => {
fetchCalls += 1;
return jsonResponse({});
},
});
const result = await observer({
runId: 'run-dry-1',
requestId: 'request-dry-1',
userId: 'user-1',
});
assert.equal(result.observed, false);
assert.deepEqual(result.executionPlan, {
version: 'workflow-execution-decision-v1',
candidateEngine: 'langgraph',
effectiveEngine: 'native',
fallbackEngine: 'native',
reason: 'execution_gate_disabled',
candidateReason: 'user_allowlist',
configVersion: 8,
bucket: 42,
dryRun: true,
handoffAllowed: false,
});
assert.equal(fetchCalls, 0);
});