feat(debug): 流程调试增强(全局调试/单步/节点mock/变更影响/旧mock兼容)#768
Conversation
以 DebugContext 为中心重设计流程调试能力,覆盖全局调试、单步调试、 统一调试上下文、变更重置规则、mock 单步与失败注入、调试态可观测、 调试历史与输入复用等需求(req1-req14),并对标 Dify/Coze 调试体验。 Co-authored-by: Cursor <cursoragent@cursor.com>
基于 2026-06-24 设计文档,按 TDD 分步编排 req1-req14 的后端实现: Phase1 调试上下文模型与统一读、Phase2 全局调试、Phase3 单步与 mock 失败注入、Phase4 变更重置与旧 mock 兼容。落地三项关键决策: SecretSingleJsonField 加密、模型归属 bkflow.template、单步用微型 DEBUG 任务。 Co-authored-by: Cursor <cursoragent@cursor.com>
按计划自洽性评审修复 5 处问题: - 单步 real 用 get_node_id_map 精确定位活动 runtime id,避免误读 start/end 事件 - 单步 real 从 elapsed_time 落库 duration_ms,对齐 req12 - node_mock 改为纯配置,不再误标节点 finished/failed, mock 角标由 execution_mode 体现 - 补 sync 子节点结构(并行网关/子流程)校准注记 - global_run 的 inputs 初值注入升级为阻塞校准并补单测 新增 test_node_mock_does_not_mark_status、 test_step_run_real_targets_activity_and_records_duration 两个回归测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
代码评审发现 SecretSingleJsonField 遇嵌套 dict/list 会抛 ValueError, 而 inputs/outputs/mock_outputs/global_vars 等调试快照天然嵌套。 决策:这些快照不加密,统一用 models.JSONField;同步更新决策TencentBlueKing#1、Task1.1 模型与测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- reset_impact 不再 get_or_create,改为 filter().first(),无基线直接返回空,避免 VIEW 权限下写入 DebugContext - build_dependency_graph 深拷贝 constants,避免 classify_constants 就地污染共享树 - 补充数据流传播/删除节点/拓扑变更/无基线只读 四类用例 Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
DebugContext 的锁原本仅在 GET /debug/context 触发回写时释放;前端关闭或 轮询中断会让模板长期停留在 running/terminating 且无 reaper 释放。新增 _reclaim_stale_lock:持锁超过 TTL(BKFLOW_DEBUG_LOCK_TTL_SECONDS,默认 600s) 时以 CAS 原子复位为 idle 并尽力撤销孤儿任务,接入 global_run/reset/step_run/ node_mock/context_var 的并发预检。 Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
审查总结
本 PR 实现了完整的流程调试增强功能,架构清晰、测试覆盖充分。以下列出需关注的问题:
Important 级别 (3 条)
sync_from_debug_task中循环内逐条查询DebugNodeState(N+1)_apply_legacy_mock_scheme循环内逐条查询TemplateMockData(N+1)DebugViewSet序列化器缺少space_id字段,TemplateRelatedResourcePermission鉴权会 ValidationError
Minor 级别 (2 条)
mock_execute非失败路径会对TaskMockData发起两次重复 DB 查询sync_from_debug_task循环内逐条ns.save()可优化为bulk_update
整体设计扎实,CAS 抢锁/超时回收/孤儿清理/幂等重入均有考虑。建议优先修复权限字段缺失和 N+1 问题。
| acts_outputs = self._acts_outputs() | ||
|
|
||
| for tpl_node_id, runtime_id in id_map.items(): | ||
| ns = DebugNodeState.objects.filter(debug_context=ctx, node_id=tpl_node_id).first() |
There was a problem hiding this comment.
⚡ N+1 查询:循环内逐条 DebugNodeState.objects.filter(...).first()。建议在循环外一次性 {ns.node_id: ns for ns in DebugNodeState.objects.filter(debug_context=ctx)} 批量加载后按字典取值。
| """ | ||
| targets = self._legacy_scheme_nodes().intersection(new_node_ids) | ||
| for node_id in targets: | ||
| default_md = TemplateMockData.objects.filter( |
There was a problem hiding this comment.
⚡ N+1 查询:targets 集合循环内逐条查 TemplateMockData。建议批量查询 TemplateMockData.objects.filter(template_id=..., node_id__in=targets, is_default=True) 后用 dict 索引。
|
|
||
|
|
||
| class DebugViewSet(GenericViewSet): | ||
| permission_classes = [AdminPermission | SpaceSuperuserPermission | TemplateRelatedResourcePermission] |
There was a problem hiding this comment.
🔒 TemplateRelatedResourcePermission.has_permission 内部用 TemplateRelatedResourceSerializer 校验请求参数,该序列化器同时要求 space_id 和 template_id。但 Debug 各序列化器只传了 template_id,对依赖 token 鉴权的非 admin 用户会导致 400 ValidationError。需在 Debug 序列化器中补上 space_id 字段(或在 permission 内从 template_id 反查 space_id)。
| return True | ||
|
|
||
| def mock_execute(self, data, parent_data): | ||
| taskflow_id = parent_data.get_one_of_inputs("task_id") |
There was a problem hiding this comment.
✨ _get_mock_fail_info 和后续 get_mock_outputs 各自调用 get_taskflow_mock_data(两次 DB 查询同一行)。非失败节点走到第 83 行时会第二次查询。可考虑在 mock_execute 入口只查一次 mock_data 后分别取 fail_nodes 和 outputs。
| for out_key, var_key in acts_outputs.get(tpl_node_id, {}).items(): | ||
| if out_key in outputs: | ||
| ctx.global_vars[var_key] = outputs[out_key] | ||
| ns.save() |
There was a problem hiding this comment.
✨ 循环内逐条 ns.save() 可改为收集后 bulk_update(updated_nodes, ["status", "duration_ms", "log_ref", "outputs", "error_detail"]),减少 DB round-trip。
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #768 +/- ##
==========================================
+ Coverage 83.10% 83.39% +0.28%
==========================================
Files 307 312 +5
Lines 18167 18917 +750
==========================================
+ Hits 15098 15776 +678
- Misses 3069 3141 +72 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
faae4c2 to
87934c7
Compare
There was a problem hiding this comment.
增量审查总结
之前报告的 5 个问题(N+1 查询 ×2、权限 space_id 缺失、mock_execute 重复查询、循环内 ns.save())在本次提交中均未修复,不再重复评论。
新发现问题 (1 条 — 🚨 Critical)
get_node_id_map端点返回Response(mapping)裸 dict,但DebugService消费时按{"result": True, "data": {...}}协议解析(id_map_resp.get("result"))。两端格式不一致会导致sync_from_debug_task和_step_run_real中拿到的id_map永远为空,调试结果无法回写。需把视图响应统一为OperationResult格式,或修改 Service 端解析逻辑。
| task_instance = self.get_object() | ||
| activities = (task_instance.execution_data or {}).get("activities", {}) | ||
| mapping = {act.get("template_node_id", act_id): act_id for act_id, act in activities.items()} | ||
| return Response(mapping) |
There was a problem hiding this comment.
🚨 响应格式不匹配:此处返回裸 mapping dict,但 DebugService.sync_from_debug_task 和 _step_run_real 按 {"result": True, "data": {...}} 解析(id_map_resp.get("result"))。建议改为 return Response({"result": True, "data": mapping, "message": ""}) 或使用 OperationResult,与 get_states/operate 等端点保持一致。
Summary
为 BKFlow 模板编辑引入一套增强版「流程调试」能力,复用 bamboo-engine 在 Interface 侧编排,前端通过
^debug路由直连(不经 APIGW)。核心能力:
create_method=DEBUG创建并启动任务 → 懒回写节点态/全局变量/log_ref。DEBUG任务从普通任务列表与统计中排除。step_run支持 mock 直出与 real 微型任务(最小start→node→endpipeline);compute_can_step依据数据依赖判定可单步性并回报缺失变量。fail_nodes/errors物化,引擎侧BKFlowBaseService.mock_execute/mock_schedule向后兼容注入失败)。TemplateMockScheme勾选节点初始化为mock并填充默认TemplateMockData,不覆盖用户选择,无需 data migration。BKFLOW_DEBUG_LOCK_TTL_SECONDS,默认 600s)时 CAS 复位为 idle 并尽力撤销孤儿任务,避免前端关闭/轮询中断导致模板卡死。权限:6 个写操作(global_run/reset/terminate/step_run/node_mock/context_var)走 MOCK 权限,4 个读操作(context/input_schema/history/reset_impact)走 VIEW 权限;空间/模板维度严格隔离。
设计与计划见
docs/specs/2026-06-24-debug-enhancement-redesign-design.md与docs/plans/2026-06-25-debug-enhancement-redesign.md。Test Plan
pytest tests/interface/template/debug/(68 passed)test_debug_create_instance.py/test_node_id_map.py/test_bkflow_base_plugin_service.py(25 passed)tests/engine/task tests/engine/statistics:256 passed(5 个settings.redis_inst失败为本地无 redis 的既有环境问题,非本分支引入,相关测试文件不在本 PR 改动范围)DEBUG任务不出现在任务列表与统计Notes / Follow-ups
JSONField存储(SecretSingleJsonField不支持嵌套结构)。DEBUG任务 TTL 清理、模板删除时级联清理DebugContext/DebugNodeState。Made with Cursor