refactor: unify workflow runtime boundary

This commit is contained in:
jxxghp
2026-08-24 03:43:47 +08:00
parent 371383f0a8
commit 9f76fc9dec
12 changed files with 154 additions and 34 deletions
+7 -1
View File
@@ -145,7 +145,13 @@ def configure_plugin_system_services():
configure_module_runtime(lambda: ModuleManager())
from app.application.site.query import SiteQueryService, configure_site_query_service
from app.application.site.health import SiteHealthService, configure_site_health_service
from app.application.workflow import WorkflowQueryService, configure_workflow_query
from app.application.workflow import (
WorkflowQueryService,
configure_workflow_query,
configure_workflow_runtime,
)
from app.workflow import WorkFlowManager
configure_workflow_runtime(lambda: WorkFlowManager())
from app.application.agentdata import configure_agent_data_ports
from app.db.oper.agentchat import AgentChatOper
from app.db.oper.downloadfailure import DownloadFailureOper
+4 -4
View File
@@ -14,7 +14,7 @@
"workflow_to_db": []
},
"edge_count": 6544,
"edge_sha256": "44812359c634235d327fdc499bc0ccf93e24923fba24bed4a29fc14adc26bf84",
"edge_sha256": "562471ed16680ca403b6cdb7661b20b1200c53b75da5e162efe4e7400cde174b",
"edges": [
"app -> app.runtime",
"app -> app.runtime.compat",
@@ -1637,7 +1637,6 @@
"app.api.dependencies.workflow -> app.startup",
"app.api.dependencies.workflow -> app.startup.composition",
"app.api.dependencies.workflow -> app.startup.composition.context",
"app.api.dependencies.workflow -> app.workflow",
"app.api.deps -> app.api",
"app.api.deps -> app.api.dependencies",
"app.api.deps -> app.api.dependencies.agent",
@@ -2417,7 +2416,6 @@
"app.api.endpoints.workflow -> app.schemas.response",
"app.api.endpoints.workflow -> app.schemas.types",
"app.api.endpoints.workflow -> app.schemas.workflow",
"app.api.endpoints.workflow -> app.workflow",
"app.api.response -> app.schemas",
"app.api.response -> app.schemas.common",
"app.api.response -> app.schemas.response",
@@ -3480,6 +3478,7 @@
"app.chain.workflow -> app.application",
"app.chain.workflow -> app.application.chain",
"app.chain.workflow -> app.application.chain.data",
"app.chain.workflow -> app.application.workflow",
"app.chain.workflow -> app.chain",
"app.chain.workflow -> app.runtime",
"app.chain.workflow -> app.runtime.config",
@@ -3488,7 +3487,6 @@
"app.chain.workflow -> app.schemas",
"app.chain.workflow -> app.schemas.types",
"app.chain.workflow -> app.schemas.workflow",
"app.chain.workflow -> app.workflow",
"app.cli -> app.application",
"app.cli -> app.application.backup",
"app.cli -> app.doctor",
@@ -6336,6 +6334,8 @@
"app.startup.initializers.scheduler -> app.scheduler",
"app.startup.initializers.transfer -> app.chain",
"app.startup.initializers.transfer -> app.chain.transfer",
"app.startup.initializers.workflow -> app.application",
"app.startup.initializers.workflow -> app.application.workflow",
"app.startup.initializers.workflow -> app.workflow",
"app.startup.lifecycle -> app.adapters",
"app.startup.lifecycle -> app.adapters.external",
+14
View File
@@ -926,6 +926,20 @@ def test_runtime_consumers_use_command_application_facade():
assert violations == {}
def test_runtime_consumers_use_workflow_application_facade():
"""WorkFlowManager concrete 实现只允许 startup 组合根直接依赖。"""
allowed = {"app.startup.initializers.workflow"}
violations = {
module_name: dependencies & {"app.workflow"}
for module_name, dependencies in _build_module_graph().items()
if not module_name.startswith("app.workflow")
and module_name not in allowed
and "app.workflow" in dependencies
}
assert violations == {}
def test_modules_read_deployment_settings_through_runtime_port():
"""宿主 Module 不得绕过 runtime 配置端口直接依赖 Settings 实例。"""
violations: list[str] = []
+19 -19
View File
@@ -142,7 +142,7 @@ def test_workflow_executor_preserves_trigger_context(monkeypatch):
[],
results={"A": run_action},
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(
workflow_module.global_vars,
"workflow_resume",
@@ -189,7 +189,7 @@ def test_workflow_executor_resumes_downstream_nodes(monkeypatch):
context=_encoded_context(ActionContext()),
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -214,7 +214,7 @@ def test_workflow_executor_restores_structured_context(monkeypatch):
},
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -232,7 +232,7 @@ def test_workflow_executor_reports_incremental_progress(monkeypatch):
progresses = []
fake_manager = _FakeWorkflowManager(calls)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -272,7 +272,7 @@ def test_workflow_executor_skips_false_condition_branch(monkeypatch):
],
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -325,7 +325,7 @@ def test_workflow_executor_all_success_join_waits_parallel_branches(monkeypatch)
],
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -367,7 +367,7 @@ def test_workflow_executor_any_success_join_runs_after_available_branch(monkeypa
],
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -399,7 +399,7 @@ def test_workflow_executor_all_done_join_can_continue_after_failure(monkeypatch)
],
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -438,7 +438,7 @@ def test_workflow_executor_exclusive_branch_uses_first_matching_flow(monkeypatch
],
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -475,7 +475,7 @@ def test_workflow_executor_passes_declared_inputs(monkeypatch):
],
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -516,7 +516,7 @@ def test_workflow_executor_uses_contract_inputs(monkeypatch):
],
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -543,7 +543,7 @@ def test_workflow_executor_persists_structured_state(monkeypatch):
}
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -576,7 +576,7 @@ def test_workflow_executor_restores_outputs_from_execution_state(monkeypatch):
],
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -603,7 +603,7 @@ def test_workflow_executor_keeps_execution_state_dict_for_non_json_leaf(monkeypa
}
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -634,7 +634,7 @@ def test_workflow_chain_process_serializes_circular_context(monkeypatch):
)
fake_oper = _FakeWorkflowOper(workflow)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "WorkflowOper", lambda: fake_oper)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -683,7 +683,7 @@ def test_workflow_executor_concurrency_key_serializes_parallel_nodes(monkeypatch
execution_config={"max_workers": 2},
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -721,7 +721,7 @@ def test_workflow_executor_filter_action_replaces_artifact_outputs(monkeypatch):
],
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -772,7 +772,7 @@ def test_workflow_executor_filter_action_replaces_with_empty_outputs(monkeypatch
],
)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: False)
@@ -789,7 +789,7 @@ def test_workflow_executor_stop_is_not_success(monkeypatch):
calls = []
fake_manager = _FakeWorkflowManager(calls)
monkeypatch.setattr(workflow_module, "WorkFlowManager", lambda: fake_manager)
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
monkeypatch.setattr(workflow_module.global_vars, "workflow_resume", lambda workflow_id: None)
monkeypatch.setattr(workflow_module.global_vars, "is_workflow_stopped", lambda workflow_id: True)
+29
View File
@@ -0,0 +1,29 @@
"""工作流运行时 Application Facade 回归。"""
import pytest
from app.application import workflow as workflow_application
def test_workflow_runtime_facade_preserves_registered_identity(monkeypatch) -> None:
"""Application Facade 必须返回组合根登记的同一个运行时对象。"""
runtime = object()
monkeypatch.setattr(
workflow_application,
"_workflow_runtime_provider",
lambda: runtime,
)
assert workflow_application.get_workflow_manager() is runtime
def test_workflow_runtime_facade_fails_before_composition(monkeypatch) -> None:
"""未装配时不得隐式创建第二个 WorkFlowManager Singleton。"""
monkeypatch.setattr(
workflow_application,
"_workflow_runtime_provider",
workflow_application._unconfigured_workflow_runtime,
)
with pytest.raises(RuntimeError, match="工作流运行时尚未由启动组合根装配"):
workflow_application.get_workflow_manager()