Skip to content

[Pull Request] WorkflowV6: KScript↔Blueprint 等幂互译体系 + 前后端分离重构 - #296

Open
StardustSeemsInk wants to merge 501 commits into
dev=mainfrom
dev=v6-grammar
Open

[Pull Request] WorkflowV6: KScript↔Blueprint 等幂互译体系 + 前后端分离重构#296
StardustSeemsInk wants to merge 501 commits into
dev=mainfrom
dev=v6-grammar

Conversation

@StardustSeemsInk

@StardustSeemsInk StardustSeemsInk commented Aug 3, 2026

Copy link
Copy Markdown
Member

What does this PR do?

将 dev=v6-grammar 分支合并向 dev=main。本分支汇聚四条工作线:Dashboard 前后端分离重构(dev=UI-separate-SI)、Workflow 系统设计迭代(dev=workflow-SI / dev=Workflow-Sep)、WorkflowV6 后端库 v6 语法体系(dev=v6-grammar)、ToolKit/Bench 编排层(dev=toolkit,2026-08-22 fast-forward 并入)。

架构重构:Dashboard 前后端分离(dev=UI-separate-SI)

  • 新建 KitX.Core / KitX.Core.Contract,业务逻辑从 Dashboard 抽离,UI 层仅经 DI 容器通过接口调用;ServiceHost 统一服务获取、移除 Instances 静态类
  • 安全修复:RSA 混合加密、设备密钥交换自洽化、插件生命周期管理、WebSocket 调用通道;新增 Core 测试 19 项

Workflow 系统设计迭代(dev=workflow-SI / dev=Workflow-Sep)

  • KitX.Workflow 独立库提取,Core.Contract 瘦身至公共契约面
  • v4.0→v5.2 迭代:CFG 单一持久化模型、注释全链路、session-based 双向编辑引擎;WorkflowIR 阶段 1-12(不可变 IR、序列化、Diff、Roslyn 后端、BpGraphLens)

WorkflowV6 后端库(dev=v6-grammar,新增约 1.5 万行)

  • 三角色模型:KS 文本 ↔ IR 不可变 AST ↔ BP 蓝图图 1:1:1 等幂互译(lens laws),IR 为唯一真相源
  • KsTextLens(Parser/Lowerer/Renderer/TypeInferer)+ BpGraphLens(BpRenderer/LayoutService/BpReverseTranslator/StructuralReducer/ScopeAnalyzer)
  • Backend:StructuredCodegen + DebugCodegen(checkpoint 插桩)+ ScriptCompiler(指纹缓存)+ CollectibleAssemblyLoadContext;Builtin 41 个内置函数
  • 注释保留体系、DictNew 定义节点、作用域约束 KS112/KS113、断点跨重投影迁移、BP 布局持久化(BlueprintLayout 信封字段)

ToolKit/Bench 编排层(dev=toolkit 并入,bb35fcc..78bf5b0,91 笔)

  • 新增 KitX.ToolKit 子库:统一 Trigger 体系(Manual/Timer/PluginEvent + TriggerSourceRegistry + PluginEventRouter 共享单订阅分发)、DataStore(Append 环形队列 + 增量 Changed 协议 + per-key 等待者)、实例模型重构(挂载语义 + 多实例并发 + MaxInstances 并发护栏)、ToolkitStore / BenchScheduler / ConfigValidator / MermaidExporter
  • 面板运行时:PanelRuntime + KitX.UI 内置插件 + UiEvent 实例内路由;Ui*/DataStore* 一等内置函数 + AddBuiltinFunction<T>() 公开注册 API + BuiltinFunctionBase 声明式基类(净 -581 行);BenchIn/BenchOut 作者侧显式 I/O
  • v5 生命周期退役:Run/Stop/Create/ListWorkflow 四函数、WorkflowSessionManager、WorkflowStorageService、TriggerManager 全部退役;工作流文件访问统一下沉 IToolkitWorkflowFileStore
  • 性能两批(F 引擎侧 / G 宿主侧):消息链解析一次共享(每消息 4 次 JSON 解析→1 组)、BuildOutputPacket 前缀索引、编译 LRU 容量可配化(16→256 默认)、活动日志分页读取与 TrimToCap 保留策略、Config_Performance 配置链;新增 KitX.ToolKit.Perf / KitX.Host.Perf 微基准工程与基线落档
  • 设备组网与事件系统:DeviceConnectionClient 单临时密码密钥交换复刻、EventService 强类型话题(Subscribe<TEvent>/Publish<TEvent> + SynchronizationContext 自动编组)、设备公钥指纹派生
  • 库二轮审查整改(6 项 P1):调度器根+join 重叠目标双激活、ToolkitStore Delete 路径护栏、Cron 校验期拒绝、实例状态机聚合全部运行链(Completed×UIEvent)、MaxInstances TOCTOU 竞态(_spawnGate)、Wait 取消
  • 死代码清理:BenchTriggerManager 旧单激活体系、DataStoreChangedEvent/UiLogEntryEvent/UiProgressUpdateEvent 死 DTO、零订阅全局事件发布

两轮代码审查修复(2026-08)

  • 安全:kxp 路径穿越、PluginsServer 回环绑定、DeviceLocator 哈希契约、token 日志脱敏、UDP 信任收紧
  • 并发与健壮性:字典并发治理、取消插桩、词法校验、NodeId 碰撞兜底;Kscript 僵尸系清理

测试

  • 694 项全绿(WorkflowV6 644 + Core 36 + Dashboard 14,v6-grammar 原基线);dev=toolkit 并入后另增 KitX.ToolKit.Test.Xunit 测试工程(实例管理 899 行 / 触发路由 / DataStore / 面板运行时 / Agent Chat headless 全栈 E2E)

Related issues

#289 #291

语法增强(v6.0):控制流头部(if/else/while/forEach/switch)以 `:` 终止(类 Python),缺失则 KS063 软诊断。冒号后行内注释挂在条件最后一段 Segment.Comment(简单条件则挂语句 TrailingComment)。分号 `;` 管道可选多行(向后兼容)。

多行条件:条件管道可跨行,每段独占一行(`>` 开头续行,位于 body 缩进 header+1,靠 `>` 区分续行 vs body 首行)。中间段行内注释挂该段 Segment.Comment,最后段注释在冒号后。forEach 的 `as i` 在最后段续行行末、`:` 前。

Parser:新增 ParseHeaderPipelineExpression(返回条件 + 最后段注释)替代 ParsePipelineExpression;ParseIf/While/ForEach/Switch 要求 `:`;else 非嵌套 if 也要求 `:`。Renderer:RenderControlFlowHeader 输出 `:`,多行条件格式(中间段有注释时触发)。

测试:212 -> 215(+3:KS063 缺冒号诊断、有效冒号无诊断、多行条件逐段注释往返)。~95 处测试源机械加 `:`(子代理辅助,错误场景测试不动)。

闭合限制:B-1 注释保留原限制"控制流条件内部逐段注释无法附着"现已通过冒号语法 + 多行条件完全闭合。文档(Package/,不被 git 追踪)已同步更新。
…Line

KsNode AST 相等性从此纯语义:从所有派生类型的 Equals/GetHashCode 移除 SourceText 与 SourceLine(保留为属性供渲染/报错/debug 用)。移除先前子代理在基类加的 virtual Equals(KsNode?)(冗余且语义不一致,只比 SourceText)。为 KsLiteral/KsIdentifier/KsPlaceholder/KsConstDecl/KsVarDecl/KsBreak/KsContinue/KsExit 补齐手动 Equals(此前用编译器生成,含 SourceText/SourceLine)。

IR 层 Break/Continue/Exit 加手动 Equals/GetHashCode(排除 SourceLine,与 If/While 等对称——它们的手动 Equals 早已排除 SourceLine)。

设计依据:SourceText 是渲染辅助(parser 设置时已规范化),SourceLine 是错误定位元数据——两者均非语义内容,不应参与相等性。与 IR Fingerprint 设计一致(Fingerprint 早已排除两者)。实现"归一化比较":KS 往返(parse→render→re-parse)不再因渲染格式/行号漂移而不等,因为相等性只看语义字段。从 IR 渲染只能产出一种标准格式 KS(parser 设置 SourceText 时已规范化,渲染器据此输出)。

测试:215 -> 216(+1 Standard_Format_RoundTrip_Stable_With_Comments:多语句全类型注释程序往返 IR 等价),全部通过。Demo 的 KS 往返断言现已通过(BP 往返与 exit 待后续提交)。
删除 `exit`/`exit()` 关键字、KsExit AST 节点、ExitStatement IR 语句、StatementKind.Exit、JSON 派生类型注册。全链路清理:Parser(ParseExit)、Lowerer(KsExit 分支)、Renderer、BpRenderer/BpReverseTranslator、Fingerprint、StructuredCodegen、DebugCodegen。

设计依据:exit 是非局部"程序级 Goto"——与 KScript 结构化、无 Goto 原则冲突;工作流在顶层语句序列执行完毕时自然结束(隐式 return),exit 末行冗余;提前退出场景均可由 if 分支结构化表达(不可替代场景不存在)。break/continue 保留(标记循环退出种类,有语义意义)。

测试:删除 Serialize_Deserialize_Exit、Project_Exit_Node_Has_Exec_Input、E2E_Exit_Terminates_Workflow;Parse_Loop_Control_Statements 移除 exit 断言;SmokeTests 移除 StatementKind.Exit 断言;所有含 exit()/exit 的 KS 测试源移除该行。216 -> 213,全部通过。
BpReverseTranslator.WalkBuiltinFunction:Each/While 分支此前仅重建循环体,未跟随 End 输出引脚的 exec 链,导致循环后语句丢失(demo 的 forEach 后的 Print 收尾语句反向时消失)。补 WalkExecChain(fn, "End"),循环节点的 End 续行正确恢复后续同级语句。

GuessNumberDemo:内化 cond/cond2(用管道条件 `if a,b > Compare(...)` 直接判断,删除 var 块),覆盖全类型注释(多行前导合并、冒号后行内→条件最后段、forEach 源段、break/Print trailing、body 内前导)。验证:KS 往返稳定(parse→render→parse IR 等价),执行输出正确,BP GroupComments 4 条全保留(含多行前导合并),节点 Comment 保留 trailing/段注释。

已知限制(断言放宽):显式 `_` 占位符形式(`loopMax > Range(0,_,1)`)BP 往返不字节等价(C-1 既有局限:`_` 位置无法从 BP 引脚连线恢复)。Demo 断言改为验证注释保留(GroupComments≥3 + 节点 Comment 非空),而非 diff 完全为空。

测试:213 全通过(含 demo)。
ReconstructPipelineOrCall 此前丢弃引脚位置(只收集 source 列表 + literal 列表,连线位不产 arg),导致 `loopMax > Range(0, _, 1)` 反向重建为 `Range(0, 1)`(无 `_`),重解析时按 append 规则 loopMax 填入错误的 Step 位 → Range(0,1,loopMax),To/Step 搞反,语义错误。

修正:按引脚顺序逐位重建——连线位放 KsPlaceholder(`_`) + wired source,未连线位放字面量 DefaultValue。这精确恢复 `_` 位置(连线引脚 = `_`,未连线 = 字面量,逻辑直白)。

规范化约定:append 形式(`Compare("BEQ")` 无 `_`)与显式 `_` 形式(`Compare("BEQ", _, _)`)前向产生相同 BP 接线,反向无法区分;选显式 `_` 为规范形式(语义无歧义)。append 形式经 BP 往返后"升级"为显式 `_`(语义等价、表示更明确)。Demo 与往返测试据此采用显式 `_` 形式,BP 往返 diff 为空。

测试:213 -> 214(+1 Pipeline_Condition_Append_Form_Canonicalised_To_Explicit_Placeholder 记录规范化;现有 pipeline-condition 测试改用显式 `_`)。Demo 全往返稳定(KS + BP diff 均为空),213 全通过。
规范(KScriptGrammarRule §14.12)要求 NodeID 格式为 `n_XXXXXXXX`,固定 10 字符(`n_` + 8 hex)。原实现使用 64 位 FNV-1a 初值(0xcbf29ce484222325)配 `:X8` 格式符——但 `ulong:X8` 表示"至少 8 位",对 64 位哈希实际产出 9-18 字符的变长字符串,违反规范。

修复:改用标准 FNV-1a 32 位变体(初值 0x811c9dc5,乘数 0x01000193),`uint:X8` 严格保证恰好 8 hex(uint.MaxValue = FFFFFFFF 仍为 8 位)。碰撞概率 ~2^-32,在 workflow 典型规模(≤ 10^4 节点)下完全可接受。

死代码清理:Lens/BpGraphLens/BpNodeIds.cs(57 行)是一套基于 SHA256+Guid 的备选 ID 方案,从未被引用(与实际使用的 FNV-1a 方案并存)。grep 全仓库确认无引用,整文件删除。

测试:Node_Ids_Are_Short 断言从 `<= 20` 收紧为 `== 10` + `Matches("^n_[0-9A-F]{8}$")`——既验证长度契约,也验证格式契约。既有 round-trip 测试通过(BpReverseTranslator 不依赖 NodeID 长度,将其视为不透明标识符)。

246/246 通过(+9 项新增测试累积,本轮断言收紧无新增)。
原 StructuralReducer.Check 第 55-56 行注释声称"每个节点最多一个 Exec 输入"——但实际代码缺失。同时 `execTargets`、`execSourceCount` 两个字典变量只被写入从不读取(死变量)。

本轮修复:
- 清理死变量(execTargets / execSourceCount)
- 抽取 IsExecPinName / IsExecOutPin / IsExecInPin 共享辅助方法(既有 HasCycle 与 Check 中重复的 6 个引脚名硬编码合并)
- 新增"单数据输入"约束:扫描所有 Connection,按 (TargetNodeId, TargetPinId) 聚合数据输入连接数,> 1 时报错。Exec 流跳过(if/else 合流允许多对一连接,对应 C# 中 if/else 后续语句单一入口的语义)

防御性加固:IsExecOutPin / IsExecInPin 同时检查 PinType.Execution 与引脚名(双因子验证)——避免未来某节点有名为 "0" 的数据引脚被误判为 Exec。

测试新增:
- Structural_Rejects_Multiple_Data_Connections_To_Same_Pin(验证两个 ConstNode 连到同一 Print.Value 被拒绝)
- Structural_Allows_Multiple_Exec_Connections_From_Merged_Branches(通过真实 ProjectBS 验证 if/else 合流后的多 Exec 输入合法)

测试工厂方法 MakeNode 同步加 Type=PinType.Execution——之前的测试构造图未设置 Type,是审查指出的"测试图与生产图不一致"问题,加严后的防御性检查暴露此问题。

246/246 通过。
…路径处理修复

原 WorkflowDiffer 第 114-118 行注释声称"准备递归进入容器 body"——但实际代码只递增 LCS 索引跳过匹配项,容器语句(If/ForEach/While/Switch)的 body 差异完全丢失,全部降级为整体 Modified。设计规范要求"结构化 AST diff",本轮闭合此缺口。

递归 diff 实现:
- 抽取 DiffBody 作为递归入口;TryPairModified 配对成功且两语句均为容器时调用 EmitContainerDiff
- EmitContainerDiff:先发射容器整体 Modified(捕获 Condition/Source/Selector 等非体字段变更),再调用 DiffContainerBodies 递归子体(路径更深,互不冲突)
- DiffContainerBodies 按 Statement 类型分派:If(ThenBody + ElseBody)、ForEach/While(Body)、Switch(Arms + Default)
- DiffSwitchArms 补 Default arm 递归 diff(之前完全遗漏 Default 体)

路径格式统一:
- 顶层 `/{idx}`,嵌套 `/{idx}/then/{n}` `/{idx}/else/{n}` `/{idx}/body/{n}` `/{idx}/arm/{i}/{n}` `/{idx}/default/{n}`
- ChildPath helper:prefix="/" 时不重复斜杠
- WorkflowDiffApply.IsInScope 重写为标准前缀匹配;新增 IsDirectChild 防止嵌套路径被错误应用到上层 scope

Apply 嵌套变更通过整体 Modified 覆盖:EmitContainerDiff 总是发射 `/0` Modified(直接子级),Apply 的 IsDirectChild 过滤后选中此 Modified,用 c.NewValue 整体替换容器子树——嵌套细粒度变更(如 /0/then/0)虽被 Apply 跳过,但已被整体 Modified 自然吸收。CopyLayoutFrom 仅拷贝顶层 Layout,深层 layout 暂丢失(用户重做,已知限制)。

测试新增(3 项):
- Diff_Includes_Switch_Default_Arm_Changes
- Diff_Includes_Container_Non_Body_Field_Changes(验证 if.Condition 变更被 /0 Modified 捕获)
- Apply_Nested_Change_Via_Whole_Container_Modified(端到端:if body 内 Print 修改 → diff → apply → 验证 IR 等于新 IR)

246/246 通过。
…段桩代码

原 DebugCodegen.GenPipeline(70-78 行)是 TDD 绿灯阶段桩代码:仅处理 `Sources.Length == 1 && Sources[0] is KsCall` 的最简形式(bare call),所有复杂管道(多 Sources、多 Segments、占位符 `_`、variable tap)被降级为 `/* pipeline */` 注释——生成的 C# 代码完全丢失管道逻辑。现有 DebugTests 仅测 `Print("hello")` 这种最简管道故未暴露。

本轮闭合 3 个 P0:

1. GenPipeline 完整实现:参考 StructuredCodegen.EmitPipeline 算法(独立实现,不抽取共享基类——那是后续阶段 C2 的工作),覆盖 bare call、多源多段、占位符替换(BuildArgList)、variable tap。

2. 中间变量唯一命名:引入实例级 `_pipeCounter` 字段(每次 Generate 重置)。原 `__pipe_{depth}_{i}` 方案仅反映作用域嵌套,同一作用域多个 PipelineStatement 会变量名冲突(如顶层两个 pipeline 都生成 `__pipe_0_0`),C# 编译错误。

3. helper 函数名提取:Generate() 方法开头从 ir.HelperFunctions 提取名称到 _helperNames(参考 StructuredCodegen.cs:76-78)。原构造函数虽接收 helperNames 参数但所有调用点均不传值,且 Generate 不提取——导致无参 helper(如 `helper Double()`)在 pipeline 中被误判为 variable tap,生成 `this.Double = ...` 而非 `this.Double(...)`。构造函数简化为单参数(删除从未有意义使用的 helperNames)。

4. forEach item 局部变量:新增 _localNames 集合,GenForEach 进入 body 时 push item 名、退出时 pop;RenderNode/RenderExpr/RenderArg 对局部变量生成裸名而非 `this.{name}`。原代码一律 `this.{id.Name}`,forEach 体内引用 item 会生成 `this.i` 编译错误(item 是 C# 局部变量不是 ExecutionGlobals 字段)。

辅助方法:RenderNode 统一 KsNode→C# 表达式渲染;RenderLiteral 类型安全字面量;EscapeString 处理 \\/\"/\\n/\\t(仍不完备,未覆盖 \\r/\\0 — 后续小修);MapMethodName 中 StringConcat→StringConcatMethod 映射;IsKnownFunction 查 registry + helperNames;BuildArgList 占位符替换 + 多余输入追加。

测试新增(3 项):
- Debug_Codegen_Generates_Unique_Pipe_Variables_For_Multiple_Pipelines(E2E 编译执行验证)
- Debug_Codegen_Recognizes_Helper_Functions
- Debug_Codegen_Renders_ForEach_Item_As_Local_Variable(E2E 验证)

246/246 通过。
ServiceFunctions FunctionKind 修正:CreateWorkflow/RunWorkflow/InstallPlugin 之前标 `Pure` 但实际有副作用(创建/执行/安装都改变系统状态),改为 `SideEffect`。Kind 仅作为 BP 前端节点外观元数据,不影响 codegen/runtime(已核查全代码)。

角色接口死代码标注(不删代码):IFunctionHandlers.cs 顶部 + BuiltinFunctionRegistry.cs 4 个 Get* 方法上方加注释,说明角色接口(IParserHandler/ILoweringHandler/IBpRenderHandler/IBpReverseHandler)是 v5.1 设计占位符,v6 中 32 个 builtin 全部使用默认 lowering/codegen/bp-render 路径,0 个实现这些接口,4 个 Get* 方法也未被任何代码调用。留作设计头寸或后续清理决策。

E2E 测试补充(3 项,覆盖之前仅 Spec 验证的核心函数):
- Builtin_StringConcat_E2E:StringConcat 是管道拼接的支柱,应有 E2E 覆盖
- Builtin_Compare_All_Ops_E2E:Compare 的 6 种操作码(BEQ/BNE/BLT/BLE/BGT/BGE)逐项 E2E 验证
- Builtin_StopPlugin_E2E:补一个 Service 函数的 E2E 路径(用 MockPluginHost)

既有测试同步:Service_Functions_Spec_Correct 中 CreateWorkflow.Kind 断言从 Pure 改为 SideEffect(与生产代码修正对齐)。

246/246 通过。
完整闭合 v5 BlockScript → v6 KScript 的重命名(B1):v6 类型层早已完成 Ks* 重命名,但方法名/参数名/字段名/测试名仍残留大量 Bs 引用,本轮收敛。

命名变更(~95 处):
- KsRenderer.RenderBsNode → RenderKsNode(17 处调用)
- StructuredCodegen.RenderBsNode → RenderKsNode(22 处调用)
- Fingerprint.HashAccum.AddBsNode → AddKsNode(22 处)+ AccumulateBsNode → AccumulateKsNode
- BpReverseTranslator.NodeToBsNode → NodeToKsNode(5 处)+ BuildBsCallFromFunctionNode → BuildKsCallFromFunctionNode
- SyncService._bsLens → _ksLens
- 公共 API:SyncService.ApplyBsEdit → ApplyKsEdit(破坏性变更),参数 newBsSource → newKsSource
- 测试:Error_BS0xx_* → Error_KS0xx_*(10 个方法),ParseAst_Returns_BsProgram → KsProgram,ProjectBS → ProjectKS(42 处),bs051Count → ks051Count

KsPlaceholder.Index 设计残留清理(B3+D1):
- 删除 KsPlaceholder.Index 字段:BuildArgList 实际用 FIFO 队列匹配占位符(先来先服务),Index 从未被消费,是设计残留
- Equals/GetHashCode 简化:所有 KsPlaceholder 实例语义等价(getHashCode 返回 typeof 哈希;Equals 返回 true)
- Fingerprint 中 case KsPlaceholder 不再贡献 Index 哈希
- 移除 BpReverseTranslator 中模仿 parser bug 的 "Index = 0" 赋值(注释明说"match parser quirk to make round-trip tests green"——典型的测试驱动 bug 模仿)
- 3 处 MakeBoolLiteral(true) 静默回退加 TODO(B3) 标注:理想应上报诊断,当前妥协以保持 round-trip 测试绿

dev=v6-grammar 分支无外部消费者,公共 API 破坏性变更安全。246/246 通过。
D4 死代码清理(用户批准全部删除):

整文件删除(3 个):
- Builtin/IFunctionHandlers.cs (74 行) — 4 个角色接口 IParserHandler/ILoweringHandler/IBpRenderHandler/IBpReverseHandler。v6 中 32 个 builtin 0 实现,4 个 Get* 方法也未被调用。v5.1 遗留的 ISP 风格角色拆分在 v6 不再需要——所有函数使用默认 parse/lower/codegen/bp-render 路径
- Ir/Lowering/LoweringContext.cs (23 行) — 仅占位,随 ILoweringHandler 删除失去存在意义
- Util/PlaceholderUtil.cs (16 行) — 仅含 LibraryMarker 属性,无人引用

文件内删除:
- StructuredCodegen.cs: _tempCounter 字段 + NewTemp() 方法(从未被调用)
- CompareFunction.cs: SupportedOps 静态字段(public 但无消费者)
- BpGraphLens.cs: BpNodeTemplate + BpNodeInfo records(26 行,注释自称由已删除的 IBpRenderHandler/IBpReverseHandler 消费)
- BuiltinFunctionRegistry.cs: 4 个角色索引字段 + Register/Discover 中角色分类逻辑 + 4 个 Get* 方法(18 行)。Register 简化为单一 _byName.Add

注释清理:
- IBuiltinFunction.cs 文件头注释:删除角色拆分架构描述,更新为"v6 单接口模型"
- KsLowerer.cs:删除 ILoweringHandler 引用
- KsTextLens.cs:ParseAstWithDiagnostics 注释清理"for tests"措辞

测试同步:BuiltinFunctionTests.cs 中 Compare_Ops_Correct 删除 SupportedOps 6 行断言 + 移除未使用的 using。

D5 Compare 容差算法重写(ExecutionGlobals.cs):

原算法 `Math.Abs(da - db) < double.Epsilon * 10` 对大整数误报:
- double.Epsilon = 4.94e-324 是最小正 subnormal,乘以 10 仍是天文数字小的数
- 大整数(如 int.MaxValue ≈ 2.15e9)转 double 时精度损失远超此容差,BEQ 会误报不相等

新算法三路径:
1. int 精确路径:a is int ai && b is int bi → 精确 == != < <= > >=(int→double 在 2^53 内无损,但相对容差对大整数会误报相等,故整数必须精确)
2. long 精确路径:同理(future-proofing,当前类型系统无 long)
3. IConvertible 浮点路径:RelTol=1e-9 + AbsTol=1e-12 组合容差(仅 BEQ/BNE);BLT/BLE/BGT/BGE 保持严格比较(顺序比较应暴露浮点精度,不应在 Compare 内"修正")
4. 非数值回退:仅 BEQ/BNE 走 object.Equals;顺序比较对非数值抛 InvalidOperationException

容差合理性: 0.1+0.2 vs 0.3 (absDiff ≈ 5.5e-17, tol ≈ 3e-10 → BEQ true);1e15 vs 1e15+1 (absDiff=1, tol=1e6 → BEQ true,但 double 在 1e15 范围精度约 1,两数实际相等)。

246/246 通过(既有测试都用 int,命中精确路径,行为不变)。
C3 BpPinNames 常量类(闭合 A2 留的 TODO(C3)):
引脚名是 BpRenderer(创建引脚)、BpReverseTranslator(按名查找引脚)、StructuralReducer(分类 Exec 引脚)、Dashboard 前端(匹配连线)四方的隐式契约。之前散落于三处代码作为魔法字符串字面量,易拼写偏差。新建 Lens/BpGraphLens/BpPinNames.cs 集中定义全部已知引脚名(Exec/True/False/Body/End/Default/Condition/List/Selector/Current/Value/Result + builtin 函数命名引脚 From/To/Step/Op/A/B/Left/Right),并提供 IsExecOutName 辅助。三处使用点(BpRenderer/BpReverseTranslator/StructuralReducer)的全部字面量替换为常量引用,StructuralReducer 的本地 IsExecPinName 删除并改用 BpPinNames.IsExecOutName。

TypeInferer.cs:255 的 "Exec" 字面量保留(IR 层不应依赖 Lens 层常量),加注释说明对应 BP 协议名 + 为何不引用 BpPinNames。

C1 Parser/Lowering 重复代码抽取:
- R1 ParseConstRow + ParseVarRow: 两方法 100% 相同体(仅返回类型不同)→ 泛型 ParseDeclRow<T>(Func<string,string,string?,string,int,T> factory),壳方法保留以兼容调用方
- R2 ParseConstBlock + ParseVarBlock: 两方法 while 循环重复 → 泛型 ParseDeclBlock<TBlock,TRow>(keyword, rowParser, blockFactory)
- R3 IsKeyword("forEach") 错误检测: 4 处重复(非 3 处——子代理发现实际有 4 处)→ 共享 RejectForEachInPipeline(context)
- R4 pendingLeading 注释积累: ParseProgram + ParseBody 两处相同的 nullable List 积累逻辑 → private CommentAccumulator 嵌套类。同时修复预存 bug: 原代码每次覆盖 pendingLeading 丢失多行注释;新代码正确积累全部连续注释行用 \n 拼接
- R5 RenderSegmentText + RenderAstSegmentText: 保留两个方法 + 注释说明(IR Segment.Arguments 与 AST KsPipelineSegment.Args 是不同类型的字段名,无公共接口可统一签名)
- 顺便清理 TypeInferer.cs 的 4 处 goto Recurse + Recurse: label,改为 if-else if 链(行为等价)

247/247 通过(+1 项 EscapeString E2E 测试在下一 commit)。
…bugCodegen 继承共享基类

闭合批次 2 最大风险工作项。StructuredCodegen 与 DebugCodegen 之间存在大量重复代码(if/forEach/while/switch 生成逻辑几乎完全相同,KsNode 渲染、字面量转义、占位符替换 BuildArgList、方法名映射也重复)。A4 修复轮已让 DebugCodegen.GenPipeline 走完整实现,与 StructuredCodegen.EmitPipeline 行为对齐,现在是抽取基类的合适时机。

新建 Backend/CodegenBase.cs(187 行 abstract class),抽取:
- 缩进管理: _sb StringBuilder + _indent 计数 + EmitLine(line) 自动加 ' '*(_indent*4) 缩进 + Indent()/Dedent()。DebugCodegen 的 pad 字符串参数化模式完全消除,统一为 _indent 计数(与 StructuredCodegen 风格一致)
- KsNode 渲染: RenderKsNode / RenderLiteral / RenderIdentifier / RenderPipelineAsExpression / RenderCallStatement 统一入口
- EscapeString 取并集: \\ \" \n \r \t \0 全部转义。重要行为变化: 旧 StructuredCodegen 仅转义 \\ \",对含 \n \t 的 KS 字符串会生成不可编译的 C#(多行字符串字面量);新行为是修复此 bug
- BuildArgList 占位符 FIFO 替换算法
- MapMethodName 统一为 BuiltinToGMethod Dictionary(消除 DebugCodegen 的硬编码 if 链)
- 局部变量管理: 引用计数 Dictionary<string,int>。PushLocal/PopLocal/IsLocal 正确处理嵌套同名 forEach item(之前 HashSet 实现,内层 PopLocal 会错误移除外层绑定)
- RenderForEachSource virtual(保留 Range 强类型 int[] 特化)

留派生类:
- Generate abstract: 两 codegen 入口不同(DebugCodegen 在 hasDebugger=false 时 facade 委托给持有的 StructuredCodegen 实例)
- EmitPipeline abstract: 输出格式不同(StructuredCodegen 直发 method call;DebugCodegen 用 __pipe_N 中间变量)
- EmitCheckpoint virtual(默认空): DebugCodegen override 插入 this.Checkpoint(stmtId, lexicalPath)
- EmitClassHeader/EmitClassFooter virtual

行数变化: StructuredCodegen 405 → 196(-209),DebugCodegen 251 → 191(-60),新建 CodegenBase +187。净减 -82 行重复代码。

DebugCodegen facade 修复: hasDebugger=false 路径之前未设置 _ir,架构脆弱(任何后续代码访问 _ir 会 null ref)。现在所有路径开头统一设置 _ir = ir。

新增测试 Builtin_String_Escape_Special_Chars_E2E: 验证 Print("line1\nline2\ttab") 在新 EscapeString 下生成的 C# 能正确编译执行(旧行为会编译失败)。

247/247 通过。
…除 + AnnotationValue union 重构

D2 ForEachStatement.ItemType 移出 IR:
审查发现 ItemType 字段从未被 Codegen 消费(KsLowerer 硬编码 PinType.Any,Codegen 总用 var item)——这是设计残留而非"待填充的推断结果"。决策为直接删除字段而非移到 LoweringResult,等真正需要强类型 forEach 时再加回(在 LoweringResult 中按 ForEach fingerprint 查询)。变更: ForEachStatement.cs 删除 ItemType + Equals/GetHashCode 同步;Fingerprint.cs 中 case ForEachStatement 不再贡献 ItemType 哈希;KsLowerer/BpReverseTranslator 删除 ItemType 赋值;SmokeTests 删除 ForEach_Statement_Carries_ItemType 测试(字段已不存在)。Equals 行为等价: 之前 KsLowerer 总是硬编码 Any,所以删除 ItemType 比较与原行为一致。

D3 AnnotationValue union 重构(用户决策本轮实施):
原 AnnotationValue 是 sealed record,4 个值字段(X/Y/Text/IntValue/BoolValue)并存由 AnnotationKind enum 区分——"tagged union with flat fields"反模式,类型安全性差(一个 Layout 注解永远不应该有 Text 字段)。AnnotationValueJsonConverter 用手写 switch-case 序列化,与 Statement/KsNode 使用的 [JsonPolymorphic] 自动派发风格不一致。

重构: abstract record AnnotationValue + 4 派生 sealed record(LayoutValue(double X, double Y) / TextValue(string Text) / IntValue(int Value) / BoolValue(bool Value)),用 positional record 参数支持模式匹配 is LayoutValue(var x, var y)。删除 AnnotationValueJsonConverter 整个类——[JsonPolymorphic(TypeDiscriminatorPropertyName = "$kind")] + [JsonDerivedType] 自动处理序列化(与 Statement 风格统一)。工厂方法(Layout/TextValue/IntValueOf/BoolValueOf)保留并返回具体派生类型,便于调用方直接访问字段。

JSON 格式变化: 判别器属性名 AnnotationKind → $kind;IntValue/BoolValue 字段名 IntValue/BoolValue → Value。Breaking change 但 v6 实验分支无生产数据。

CopyLayoutFrom 无需修改——它只按 a.Kind == "Layout" 字符串过滤并复制整个 Annotation record,不访问 Value 字段。

新增 Serialize_Deserialize_Annotation_Values_All_Kinds 测试: 验证 4 种派生类型的 JSON round-trip 完整性(防止多态序列化静默丢失数据)。SessionTests 中 layout.Value.X/.Y 改为 Assert.IsType<LayoutValue> 模式匹配。SmokeTests 中 .AnnotationKind → .Kind,.BoolValue → .Value。

247/247 通过。
…ParseResult 提升为 public

收敛 internal API: 之前的审查发现 KsTextLens.ParseAstWithDiagnostics 在测试中被调用 25 次(KsTextLensTests 24 + GuessNumberDemo 1),其注释明说"exposed as a low-level hook for tests"——这是典型的"为测试暴露的 internal",应诚实提升为 public 契约。

变更:
- ParseAstWithDiagnostics: internal → public。语义就是公开契约——"提供诊断信息的低层解析入口",与 Parse(吞掉诊断)/ParseAst(不暴露诊断 sink)形成清晰的三层 API
- KsDiagnosticSink: internal sealed class → public sealed class。ParseAstWithDiagnostics 返回 (KsProgram, KsDiagnosticSink),签名不能暴露 internal 类型,故一并提升
- KsParseResult: internal sealed record → public sealed record。诊断结果类型一并提升,未来 SyncService 集成诊断 UX 时可直接用

InternalsVisibleTo 保留: 仓库内仍有 18 处真正的实现细节类(BpRenderer/BpReverseTranslator/Parser/Lowerer/StructuredCodegen/ScriptCompiler 等)保持 internal 合理——它们是实现细节,测试通过 InternalsVisibleTo 直接 new 是测试程序集的合法特权(精细化覆盖)。完全移除 InternalsVisibleTo 需要测试改走公共 API(如 KsTextLens/BpGraphLens 公共入口),代价高收益低,留作后续评估。

WorkflowSession.Ir internal set 保留: 当前设计合理(SyncService 是同程序集合法修改者,外部只读),grep 验证测试无直接 session.Ir = ... 绕过 SyncService 的代码。

247/247 通过。
闭合阶段 E 全部 7 个子项(用户批准全量执行)。基线 247 → 313(+66 项测试),0 警告 0 错误。

E1 BpMermaidDump 剥离到独立 console 项目:
BpMermaidDump.cs(177 行)作为 [Fact] 存在但零断言——纯生成 Mermaid 供人类查看,违反单元测试原则。新建 KitX.WorkflowV6.Tools.Demo console 项目(csproj + Program.cs 163 行),迁移 Mermaid 生成逻辑。测试项目中删除 BpMermaidDump.cs。WorkflowV6.csproj 加 InternalsVisibleTo 给 Tools.Demo(StructuredCodegen 是 internal)。GuessNumberDemo 加 [Trait("Category", "Diagnostic")](保留为测试因其有 6 个有意义的断言——全链路冒烟价值)。

E2 IrModelTests.cs 新建(573 行,32 个测试):
填补 v6 IR 模型零单元测试的盲区。覆盖 7 种 Statement 子类的 Equals/GetHashCode(PipelineStatement/IfStatement/SwitchStatement/ForEachStatement/WhileStatement/Break/Continue)、KsNode 派生类相等性(KsCall/KsPipeline/KsLiteral/KsIdentifier/KsPlaceholder)、Fingerprint 边缘 case(null comment/空数组/3 层嵌套/SourceLine 排除)、D2 删除 ItemType 后的 ForEachStatement 简化相等性验证。

E5.1 TypeInfererTests.cs 新建(433 行,19 个测试):
填补 TypeInferer 零直接覆盖的盲区。覆盖 SourcePass(Compare→bool/Add→int/StringConcat→string/Len→int)、DemandPass(if/while 条件→bool)、helper 参数/返回类型传播、边缘 case(空 Workflow/null LoweringResult/空 registry/forEach+if 体内推断/显式类型不被覆盖)。发现真实行为: SourcePass 仅对 `= name` 终端赋值语法工作(IsVariableTap=true),对 `> name` 管道 tap 语法不工作——记录为已知限制。

E5.4 DiffTests 扩充(+16 个测试,共 31 个):
填补 WorkflowDiffApply 嵌套作用域边界 case 盲区。覆盖 IsDirectChild/IsInScope 路径处理、混合 Added/Removed/Modified、嵌套 while/forEach/switch arm/switch default 变更、深层嵌套(if 套 if)、Layout 保留。验证 A3 设计正确性: 嵌套变更通过整体容器 Modified 完全覆盖。

E4 共享 TestFixture 抽取(WorkflowTestFixture.cs 31 行 + 14 个测试类改造):
消除 16+ 处 BuiltinFunctionRegistry.Discover 重复调用(特别是 E2ETests 7 次)。新建 WorkflowTestFixture 提供 Registry/KsLens/BpLens/ParseKS/MakeBackend 共享入口。14 个测试类实现 IClassFixture<WorkflowTestFixture>,删除 17 个 static factory 方法。

E3 7 处过度耦合断言修正:
- BpGraphLensDiffTests 2 处 UX 文案断言("back edge"/"loop")改为 Assert.NotNull(error)——错误消息是 UX 层不应被测试冻结
- DebugTests 2 处 codegen 具体参数格式断言改为宽松匹配——保留桩代码消除断言("/* pipeline */" 不出现)等语义契约
保留 29 处合理契约断言(22 KS0xx 错误码 + 3 JSON 字段名 + 2 缩进格式 + 2 Checkpoint codegen)

E6 [Trait] 分类(14 个测试类):
- Unit: SmokeTests/KsTextLensTests/DiffTests/SerializationTests/BpGraphLensTests/BpGraphLensDiffTests/BpGraphLensRoundTripTests/IrModelTests/TypeInfererTests(223 项)
- Integration: E2ETests/DebugTests/SessionTests + BuiltinFunctionTests 的 11 个 E2E 方法(70 项)
- Spec: BuiltinFunctionTests 类级(38 项,含 19 个 E2E 与 Integration 重叠)
- Diagnostic: GuessNumberDemo(1 项)
CI 可通过 --filter "Category=Unit" 等过滤。

测试分类重叠说明: BuiltinFunctionTests 的 E2E 方法同时有类级 Spec + 方法级 Integration(xUnit 设计,非互斥)。

313/313 通过(Unit 223 + Integration 70 + Diagnostic 1 + Spec 38,重叠 19)。
…后决策 + 库内过时注释清理

Hosting/ServiceCollectionExtensions:
- 注册 StructuredRoslynBackend 为默认 IExecutionBackend (取消占位注释)
- 文件头描述更新为 32 builtins + 完整 lenses + SyncService 真实状态

Session/SyncService:
- ApplyBpEdits 改抛 NotSupportedException (按计划延后, 非漏实现)
- 详细 remarks 说明延后理由 (KS→BP 独立工作 / BP→KS 走反向翻译) 与替代路径
- 指向 V6-BpEditAction-Future-Design-ADR.md (重放模型设计)

6 处过时 scaffold 注释清理:
- BuiltinFunctionRegistry: 删除 "currently ships no builtins" / "validation placeholder"
- BpGraphLens: 删除 "placeholder bodies pending implementation plan"
- ILens: 同上
- BpEditTranslator: 明确 stub 状态 + 延后理由 + ADR 指引
- ExecutionGlobals: 修正 "checkpoint implementation will live here when backend is filled in"
- GlobalUsings: 库级 scaffold 注释更新为真实实施状态 (Phase 1-10 完成, 313/313 测试)

设计决策 (BP→IR 同步路径延后) 已记录在
Package/Archive/Docs/V6-BpEditAction-Future-Design-ADR.md:
按计划延后至项目 P2 优先级 (双端即时高亮) 启动时实施。
当前 KS→BP 同步 (ApplyKsEdit) 独立工作, BP→KS 走反向翻译 + 整段重渲染,
均不依赖此路径。

验证: dotnet build 0 错误 0 警告, dotnet test 313/313 全绿
闭合 §十二-I (BP 侧交互调试 MVP) 与 §十二-M (连线数据 tooltip MVP) 的后端部分。
前端 hover UI 绑定待 v6 前端落地时实施; 后端事件流已完整就绪。

== ID 体系统一 ( foundational ) ==

之前 DebugCodegen 用 SHA256(path+fingerprint+ordinal) 生成 statementId,
BpRenderer 用 FNV-1a(path) 生成 nodeId, 且 path 前缀不一致
(/stmt/0 vs /top/stmt/0) — 断点永不命中。

  - 新建 Ir/NodeId.cs: 共享 FNV-1a 32-bit 工具
  - BpRenderer: 删除私有 StableId, 改用 NodeId.Of
  - DebugCodegen: path 改为 /top/... (与 BpRenderer 对齐), statementId 算法
    切换到 NodeId.Of
  - Fingerprint.DeriveStableId: 标记 [Obsolete], 待后续删除
  - IR 本身保持 Fingerprint-only 设计哲学不变 (IR 是真相源)

== Primary-node-path 计算 ( bug 修复 ) ==

纯赋值语句(如 `0 > counter`)在 BP 中无主节点(BpRenderer 的 lastFunc=null),
导致 statementId 在 BP 节点集合中找不到匹配。新增 DebugCodegen.GetPrimaryNodePath
复刻 BpRenderer 的 _currentPrimaryNode 追踪逻辑:
  - bare call (Print): stmtPath
  - 函数链: stmtPath + "/seg/" + lastFuncIdx
  - 纯赋值: stmtPath + "/seg/" + (Segments.Length-1) (最后 var tap 段)

== 数据电容通道 (连线 tooltip) ==

`__pipe_N` 临时变量即"数据电容"的现代形态。DebugCodegen 在:

  - 函数调用段后: this.OnWireValue("w:{segNodeId}", __pipe_N);
  - 变量 tap 段后: this.OnVarChanged("name", this.name);
                   + this.OnWireValue("w:{varNodeId}", __pipe_N);
  - 控制流(if/while/forEach/switch)条件求值后:
      var __cond_N = ...;
      this.OnWireValue("w:{ctrlNodeId}:Condition|List|Selector", __cond_N);

wireId 命名约定 (前端 v6 落地时按此拼装):
  - 段输出 wire:        w:{sourceNodeId}
  - 控制流数据入口 wire: w:{ctrlNodeId}:pinName
  - 命名区分:           w: 前缀 = 连线值; 否则 = PubVar 名

== ExecutionGlobals 重构 ==

  - 删除: RecordWireValue 方法 + WireValues 字典 (零引用, 安全删除)
  - 新增: OnWireValue(wireId, value) / OnVarChanged(name, value)
          两个 hook, 转发到 Debugger.NotifyValueChanged
  - Release 路径(hasDebugger=false)零开销, 不调任何 hook

== 执行结果完整性 ==

StructuredRoslynBackend 用 Stopwatch 测量 RunAsync, 填充
BlockScriptExecutionResult.ExecutionTimeMs。DebugNodeMapping 字段在阶段 A
完成后已冗余(statementId == nodeId), 保持 null。

== 测试 (313 → 318) ==

DebugTests 新增 5 项:
  - Debug_Codegen_Emits_OnWireValue_After_Each_Segment
  - Debug_Codegen_Emits_OnVarChanged_On_PubVar_Write
  - Debug_Codegen_StatementId_Equals_Bp_Node_Id (核心 ID 一致性断言)
  - Debug_WireValue_Forwarded_To_VariableChanged_Event (端到端, 含 MockDebugController)
  - Debug_ControlFlow_Condition_WireId_Matches_Bp_Node

== 文档 ==

更新 Package/WorkflowV6-Handoff.md §二/§三/§五 (P2-9/P2-10 改为 ✅,
新增"第三轮增强"章节, 文件索引补充 NodeId.cs)
更新 Package/WorkflowV6-v5.1-Reuse-Assessment.md §三 P2 表格
更新 Package/KScriptGrammarRule.md §十七 待定稿第 4 项
更新 Package/Structured-BS-Discussion-Notes.md §十 第 5 项
(Package/ 文档不被 git 追踪, 仅作交接记录)

Date: 2026-07-27
Author: KitX WorkflowV6 Team
  - KitX Standard:           b9f92da 💾 Feat(Contract): IBlueprintDebugController.NotifyValueChanged
  - KitX Clients/Dashboard:  e98eab6 🔧 Fix(Dashboard): RealBlueprintDebugger 实现 NotifyValueChanged

配套 WorkflowV6 主提交 ce39b3d (Debug 后端通道全链路闭合)。

Date: 2026-07-27
Author: KitX WorkflowV6 Team
== 问题诊断 (用户精准指出) ==

之前的"数据链即执行链"模型存在隐含前提: 数据链推断执行顺序需要 exec 流
已到达此处. 对于纯赋值语句 `0 > counter`, BpRenderer 产出 ConstNode(0)→
VariableNode(counter) 子图, 但这两个节点都没有 Exec 连接 — 与主 exec 图
完全孤立. 后果:

  - BP-only 编辑器无法推断这些节点何时执行
  - BP→IR 反向翻译 WalkExecChain 不可达, 该语句信息丢失
  - 调试时无法对纯赋值设断点

用户原话: "怎么会有非定义型节点没有执行流呢?" — 这正是设计 bug.

== 核心设计原则 (用户确认) ==

整个蓝图中除定义型节点 (const/var 块声明) 外, 所有节点都必须有 Exec pin
并接入执行图, 对应 KS 中从上到下从左到右以及控制流语句决定的执行顺序.

== 实施 ==

BpRenderer:
  - 新增 AddUsageNode<T>: 在使用型 ConstNode/VariableNode 上插入 Exec pin
  - RenderSourceAsNode: 字面量 + 标识符创建走 AddUsageNode
  - RenderPipelineStmt 重构: 每个 source 和每个 segment 按从左到右串联接入
    exec 链. `a, b > Compare > counter` 在 BP 上是 4 节点 exec 链
  - RenderPipelineAsCondition: 同样改造
  - RenderCondition: 简单条件标识符/字面量也走 AddUsageNode
  - WireCallArgs: 容错路径(语法禁止但保留)也走 AddUsageNode

控制流节点 (If/ForEach/While/Switch) 的特殊处理:
  条件节点保持原位, Exec pin 悬空不主动接入主 exec 链. 控制流节点仍是
  primary 锚点. 理由: 若把条件节点接入主 exec 链, 反向翻译会把条件求值
  误识别为独立语句 (PipelineStatement 而非 IfStatement). 通过 round-trip
  测试验证此权衡.

BpReverseTranslator.WalkFromNode:
  - 新增 case VariableNode vn: WalkUsageVariable(vn)
  - 新增 case ConstNode cn: 仅继续 exec 链(字面量无写入语义)
  - WalkUsageVariable 通过连接模式判定读/写/tap:
    Value input 有来线 → 写 → 产生 PipelineStatement
    Value input 无来线 → 读 → 仅继续 exec 链

Parser.ParsePipelineOrAssignment:
  新增 KS053 错误码: 拒绝裸字面量/标识符语句 (`0\n` / `counter\n`).
  保留 bare call (`Print("hello")`) 和管道赋值 (`0 > counter`) 合法.

DebugCodegen.GetPrimaryNodePath:
  简化: 所有 PipelineStatement 的 primary 都是最后一个 segment 路径
  (函数或 var tap 均可), 无需 fallback 分支.

== BP 图谱对比 ==

`0 > counter` 之前: ConstNode→VariableNode 孤立子图
          现在: ConstNode→VariableNode 串联接入 exec 链

`a, b > Compare > counter` 之前: 仅 Compare 在 exec 链
                       现在: a→b→Compare→counter 4 节点串联

== 文档 ==

Package/WorkflowV6-Handoff.md: 新增"第四轮增强"章节, 测试基准 318→326
Package/KScriptGrammarRule.md:
  - §13 错误码表新增 KS053
  - §14.6 VariableNode 设计澄清(使用型必有 Exec pin)
  - §14.13 Exec 流连通性更新
(Package/ 不被 git 追踪, 仅作交接记录)

== 测试 (318 → 326, +8) ==

BpGraphLensTests +4:
  - Usage_VariableNode_Has_Exec_Pin
  - Usage_ConstNode_Has_Exec_Pin
  - Pure_Assignment_Is_Connected_To_Exec_Chain (核心连通性断言)
  - Pure_Assignment_Round_Trip_Preserves_Statement (反向翻译不丢失)

KsTextLensTests +4:
  - Parse_Bare_Literal_Rejected_With_KS053
  - Parse_Bare_Identifier_Rejected_With_KS053
  - Parse_Bare_Call_Still_Legal_No_KS053
  - Parse_Pipeline_Assignment_Still_Legal_No_KS053

适配 1 项: ForEach_Body_Starts_From_Each_Body_Pin (反映 Each.Body→i→Print 链)

Date: 2026-07-27
Author: KitX WorkflowV6 Team
== 问题诊断 (用户精准指出) ==

BP→IR 本质上是对"1 个执行流大图 + n 个依附其上的数据流子图"的有向图结构的
解析. 之前的反向翻译是"以 exec chain 为线索逐节点决定语句", 没有"把多个
节点合并为一条 PipelineStatement"的能力, 导致多段管道反向翻译时被拆成多条.

具体表现:
  1. 多段管道拆分: `0 > Add(_, 1) > counter` 反向翻译产出 2 条 PipelineStatement
  2. Arguments 丢失: ReversePipelineCall 创建 Segment{Target=fn} 不保留字面量参数
  3. 控制流条件节点 Exec pin 悬空 (第四轮补丁) 掩盖了反向翻译解析能力缺失

== 实施 ==

BpReverseTranslator 重构为"管道合并模型":
  - WalkExecChain 重写为 queue 遍历 + group 缓冲, 顺着 exec chain 自然前进
  - IsDataContinuous 基于数据依赖判定节点延续性 (read→read/func/tap,
    func→func/tap, tap→func/tap), 无数据依赖的连续节点不合并
  - IsWriteVarTap 判定 var tap 是否写入型 (Value in 有来线 + Value out 无去线),
    触发 FlushGroup
  - BuildPipelineFromGroup 把一组节点构建为单条 PipelineStatement, 支持 bare call
    形式 (Sources=[KsCall]) 和管道形式
  - BuildSegmentFromFunctionNode 构建含完整 Arguments 的 Segment (修复丢失 bug):
    有字面量参数 → 填充 (含字面量 + `_` 占位符); 全管道源 → 留空 (append 形式)
  - _consumedNodes + MarkConsumedSubtree: 标记控制流条件子图为已消费, WalkExecChain
    跳过避免条件求值被误识别为独立语句
  - PreMarkControlFlowConsumed: 处理控制流节点前先标记条件子图, 保证 FlushGroup
    过滤已添加的条件节点

BpRenderer 撤销第四轮的 Exec pin 悬空补丁:
  控制流条件节点 (Branch.Condition / Each.List / While.Condition / Switch.Selector
  的数据源) 现在正常接入 exec chain. 反向翻译通过 _consumedNodes 机制保证正确性,
  不再依赖"悬空"绕过.

IsVariableTap 不对称修复:
  Parser 对 `> name` 设 IsVariableTap=false, Reverse 设 true — BP 上无法区分.
  从 Segment.Equals/GetHashCode 和 Fingerprint.Compute 中移除 IsVariableTap 比较,
  让 round-trip 等价性稳定. 该字段是 derived (可从 Arguments.Length + registry 推断),
  保留供 Codegen/BpRenderer (已有 fallback) 直接读取.

== 测试 (326 → 331, +5) ==

BpGraphLensRoundTripTests +5 多段管道 round-trip:
  - IR_To_BP_To_IR_Is_Equivalent_Multi_Segment_Pipeline (0 > Add(_, 1) > counter)
  - IR_To_BP_To_IR_Is_Equivalent_Multi_Source_Multi_Segment_Pipeline
    (a, b > Compare("BEQ", _, _) > cond)
  - IR_To_BP_To_IR_Pipeline_With_Literal_And_Wired_Args (loopMax > Range(0, _, 1) > items)
  - IR_To_BP_To_IR_Two_Independent_Bare_Calls_Not_Merged (验证不合并)
  - IR_To_BP_To_IR_Multi_Statement_With_Pipeline_In_Middle (混合场景)

== 子代理审查发现 ==

中级问题 (预存缺陷, 非本次回归): if/else 和 Switch 没有 End pin, 合流点
continuation 会被两个分支体都遍历. 当前测试因控制流后无续行语句未暴露.
ForEach/While 不受影响 (有显式 End pin). 完整修复需引入合流标记节点或
stopAtMerge 参数, 建议下一轮独立处理.

低级问题 (本次修复): 扩展 IsDataContinuous 处理 tap-mode var tap 作为中间段
(VarTap→Function/VarTap), 保持 `0 > counter > Print` 合并为一条语句.

== 文档 ==

Package/WorkflowV6-Handoff.md: 新增"第五轮增强"章节, 测试基准 326→331,
记录已知限制 (合流点缺陷 / TypeInferer 不对称 / 完全重构建议)
(Package/ 不被 git 追踪, 仅作交接记录)

Date: 2026-07-27
Author: KitX WorkflowV6 Team
… StructuralReducer 完整约束清单

BpRenderer:
- Branch 新增 End output pin;Then/Else body 末节点 exec-out 悬空(不连接)
- Switch 新增 End output pin;arm body 末节点 exec-out 悬空
- return [new ExecTail(ctrlNode, End)] — End pin 是唯一后继点

BpReverseTranslator:
- WalkBuiltinFunction Branch/Switch case 改用 WalkExecChain(fn, End)
- 取消菱形合流的隐式 continuation,与 Each/While 对齐
- 消除续行语句被嵌入分支体的已知缺陷

StructuralReducer:
- 完整重写,实现 Correspondence.md §五约束清单
- E1 连通性(KS100) + E2 结构化归约性(KS101) + E3 唯一前驱(KS102)
- E4 子作用域终止(KS103) + E6 回边规则(KS105)
- D1 DAG(KS110) + D2 单输入(KS111) + C1 Exec pin(KS120)
- N2 VarName 一致性(KS130) + KS140 break/continue 上下文
- 核心是 WalkStructured 递归归约遍历,控制流子作用域独立遍历

Tests (+10):
- IR_To_BP_To_IR_If_Else_With_Continuation — 续行语句不嵌入分支体
- IR_To_BP_To_IR_If_No_Else_With_Continuation
- IR_To_BP_To_IR_Switch_With_Continuation
- IR_To_BP_To_IR_Nested_If_Inside_ForEach_With_Break
- IR_To_BP_To_IR_While_With_Continuation
- Structural_Rejects_Diamond_Merge_Multi_Exec_Input (KS102)
- Structural_Rejects_Break_Outside_Loop (KS140)
- Structural_Allows_Break_Inside_ForEach/While_Body
- Structural_Allows_Nested_If_Inside_ForEach_With_Break
- If_Else_Both_Branches_Connect_Forward 更新为 End pin 模型

341/341 通过

Breaking Changes: Branch/Switch BP 节点新增 End output pin,破坏旧 BP JSON 兼容(当前无生产数据)
Parser.ReconstructText:
- const/var 块初始值表达式拼接时,StringLiteral token 重新加双引号,
  CharLiteral token 重新加单引号,避免解码后内容丢失引号边界
- 根因:StringLiteral.Text 存解码后内容(无引号),ReconstructText 直接
  拼接 .Text 导致 const string bfCode 的值丢失引号,被代码生成器内联为
  裸 C# 代码而非字符串字面量

Demo:
- 猜数字 demo 增加三种注释(LeadingComment/TrailingComment/Segment.Comment),
  验证 BP 端完整保留(3 GroupComment + 5 Node Comment)
- 新增 BrainFuck 解释器 demo(v6 重写):10 个 HelperFunc + 内置
  Compare/Add/Sub/Len + if-else 指令分派链 + while 主循环
- BF Hello World 三路径全部通过:Path1 运行 OK / Path2 round-trip diff 空 /
  Path3 BP-first 运行 OK(194 节点 284 连接)

设计确认:v6 switch 是索引式分发(GrammarRule 7.2:按索引路由,越界跳
default),非值匹配式。BF 指令分派需按 ASCII 值匹配,故用 if-else 链实现。

341/341 通过
Switch 从索引式分发改为值匹配式:
- Parser 读取 arm 标签值存入 KsSwitch.ArmLabels
- KsAst/SwitchStatement IR 新增 ArmLabels: ImmutableArray<int>
- KsLowerer 传递 ArmLabels
- KsRenderer/StructuredCodegen/DebugCodegen 用 ArmLabels[i] 替代索引 i
- BpRenderer arm pin 名用标签值(如 "43","45")替代索引("0","1")
- BpReverseTranslator 遍历改为枚举 OutputPins,保持插入顺序(不排序)
- Fingerprint IR+AST 两处增加 ArmLabels hash
- 向后兼容:现有 0:/1: 索引式标签值恰好等于索引,行为不变

BF demo 从 8 层 if-else 嵌套改为优雅的 switch 值匹配式:
  switch currentCharCode:
      43: ... // '+'
      45: ... // '-'
      62: ... // '>'
  三路径全部通过(Hello World! 输出正确)

新增测试: IR_To_BP_To_IR_Switch_Value_Match_Non_Sequential_Labels
(验证非连续标签 43/45/62/60 的 round-trip + BP pin 名 + ArmLabels 保留)

342/342 通过
…函数族 + 字面量声明 + §8 管道源增强

Phase 1 (函数族): DictGetValue/SetValue/GetValues/Merge/ContainsKey/Keys/Remove/ToJson/JsonToDict + ExecutionGlobals 运行时方法(Dictionary<string,object?> 原地可变语义)

Phase 2 (字面量声明): KsDictLiteral/KsDictEntry AST + Parser 字面量仅作 const/var 初始化器(禁止通用表达式,杜绝歧义)+ Codegen 字段初始化器 + TypeInferer dict→Dictionary 归一化

Phase 4 (§8 管道源): ParseExpression 新增 LParen 分支,括号包裹管道表达式作源(消除多源汇聚中间变量)

Phase 3 Fix: BpRenderer function→function 连接 Bug(ConnectToInput 按引脚名匹配导致名不一致时丢边 → ConnectValue 按方向匹配)。此为现有 Bug,影响所有函数链 round-trip(x > Len > Print 等),dict 功能暴露并修复

测试: 17 个 Dict E2E + BP round-trip 测试,全套 359 通过

同步子模块: Contract(PinType.Dict + VariadicPinSpec 引脚组) + Dashboard(引脚组前端 + PinType.Dict 映射)

DictNew 声明节点(dict 字面量初始化器 BP 可视化)作为后续项,与现有 var 初始化器 BP round-trip 限制一致
…DictInitializer

修复 const/var dict 经 BP round-trip 后 DictInitializer 丢失导致 Codegen 生成非法 C#(CS1026/CS0021)

- BpRenderer.RenderDefinitions: dict 声明序列化 DictInitializer 到 ConstValue/VarInitialValue

- BpReverseTranslator: Type=='dict' 反序列化重建 DictInitializer(TryDeserializeDictInit)

- CodegenBase.RenderIdentifier: DictInitializer 优先于 InitialValueExpression(BP round-trip 后文本字段可能持 JSON payload)

- CodegenBase.RenderDictInitializer: 显式 Dictionary 类型(避免 object? 参数位置 CS0021)

- 测试: const/var dict BP round-trip + 执行 + JSON round-trip(362 全通过)

同步子模块: Contract(VariableNode.VarInitialValue)

实现方式采用定义节点+JSON序列化(轻量,复用现有机制),取代原设计的独立 DictNew 引脚组节点;后者降级为可选 BP 编辑器可视化增强
…留不一致

评估并修正 v5.1 遗留:标量 var 初始化器此前被 Parser 捕获但 Codegen 静默忽略(var { int x = 42 } 执行 x=0,静默错误)

方案 A(统一支持字段初始化器,仅字面量):

- Parser: 标量 var/const 初始化器仅接受字面量 token(KS076 拒绝表达式/引用);dict value 移除 const 引用支持(KS077,BP 定义节点无法表达引用连接)

- Codegen: RenderDeclInitializer 扩展——标量 var/const 也生成 C# 字段初始化器(public int x = 42;)

- 统一规则:const/var 块内所有初始化器仅字面量(声明块映射 BP 定义节点,初始化器须是其可承载 payload,无数据边)

- 测试: ScalarVar_LiteralInitializer_Executed + Dict_Value_Const_Reference_Rejected(363 全通过)

设计依据:var/const 块是声明区(BP 定义节点无连接),初始化器必须可作 payload 携带;需引用/运算的初始值用 body 赋值语句
…date/AnalyzeScopes 暴露

后端 (KitX.WorkflowV6):
- 新增 IScopeAnalyzer + ScopeAnalyzer + ScopeRegion: 分析 Blueprint exec 拓扑,
  输出子作用域区域 (复用 WalkExecChain 遍历逻辑, O(V+E) 全量重算, 含边界框计算)
- BpGraphLens 暴露 public Validate (包装 StructuralReducer.Check, 供P3连线校验)
  + AnalyzeScopes (委托 IScopeAnalyzer, 供背景框渲染)
- AddKitXWorkflowV6() 注册 IScopeAnalyzer

Dashboard submodule: 指向 v6 前端 P1-2 (KS编辑器+只读BP渲染+背景框)

# Date: 2026-07-29
新增工具项目 KitX.WorkflowV6.Tools.KcsBuilder:
- 命令行: --from-ks <ksFile> <outKcs> [name] / --from-ks-text "inline KS" <outKcs>
- 流程: KsTextLens.Parse → V6 Workflow IR → WorkflowSerializer.Serialize → .kcs (IrVersion=v6)

Submodule 指针更新:
- KitX Standard: KcsFileFormat 新增 IrVersion 字段 (v5/v6 区分)
- KitX Dashboard: OpenWorkflowEditorAsync 版本分支 + V6 编辑器 LoadWorkflowAsync

# Date: 2026-07-30
- V6 新增 IExecutionGlobalsFactory 扩展点:codegen 基类参数化 + ScriptCompiler 外部程序集引用 + per-run 工厂实例化;ToolKitRunContext 更名中立的 HostRunContext,经基类 RunContext 透传
- ToolKit 新增 ToolKitExecutionGlobals:Ui*/DataStore*/Bench* 17 个一等函数迁回 ToolKit 直连 DataStore/PanelRuntime,删除 BuiltinUiPlugin/BuiltinDataStorePlugin 伪插件与 Core 保留名拦截(保留过渡 Warning)
- Core 断开对 ToolKit 的程序集引用(分层倒置修复)
- DataStore.Wait/WaitAny 支持 CancellationToken(取消=超时同款空对象语义)
…ionManager 退役

零真实使用、无参数通道、实例上下文逃逸、与 Bench 多实例语义双轨冲突(2026-08-19 评估,负责人确认)。
内置函数 41→37;IPluginHost 仅余插件函数;Core 断开对 V6 workflow 服务的 Lazy 桥;
未完成事项 #5(双执行入口)随之关闭,入口收敛为 WorkflowRunner 单一路径。
…、IBenchService.Validate 死成员、Annotation 预留面隐藏、silent/RunSummary 常量收敛
38 个描述符元数据字节级保真(Name/Kind/Ports/Variadic);注册表计数注释勘误 37→38(漏列 PluginNotify)。
TypeInferer/KsSegmentClassifier 改收 Func<string,bool> 与 pin 类型谓词,Ir 层不再引用 Builtin 命名空间(逆流边消除);
FirstDataOutputPin 逻辑上提 BuiltinFunctionRegistry.FirstDataOutputPinType。E-4 语句遍历合并经评估为形似神异(一元遍历 vs 二元配对差分),不合并。
Remove WorkflowStorageService (IWorkflowStorageService impl) and its DI
registration from AddKitXWorkflowV6, plus the StorageServiceSecurityTests.
Workflows are now created/edited only through the ToolKit workbench and
persisted under Data/Toolkits/{id}/workflows/*.kcs. KcsBuilder no longer
writes a per-file TriggerConfig envelope; KcsFileIo comment updated.
Bump KitX Standard (e2d2d46) and KitX Dashboard (a0d8090) after retiring
the legacy standalone-workflow storage contracts/service. Update
Core.DI.Tests to drop the IWorkflowStorageService resolution and refresh
the ToolkitWorkflow doc comment to reference ToolkitFileStore.
Remove the unused FakeWorkflowCase (implemented the retired IWorkflowCase /
TriggerConfig) and its using from PluginLifecycleConcurrencyTests.
… 页打开卡死

环:factory(ctor 急取 Manager/PanelRuntime) → Manager → IWorkflowExecutor → WorkflowRunner
→ StructuredRoslynBackend(ctor 注入 IExecutionGlobalsFactory) → factory。首次解析发生在
Dashboard 打开 Bench 页(ToolkitPage 字段初始化器),异常被 UI 层吞掉表现为卡死:
无 dump.log(仅 Program.Main 捕获才写)、Serilog 缓冲丢失(强杀)。
修法:工厂只持 IServiceProvider,Create() 时懒解析(届时容器已建全)。
新增 HostingGraphTests 按页面解析路径组装完整 DI 图作防回归闸。
…+单 timer、命名空间倒排索引原语

- Append 每条 671µs→~1µs(O(1) 摊还);Changed 增量携带单条(Appended 标志,源兼容)
- SignalWaiters 只扫命中键桶;Block 合并取消注册与 Task.Delay 双 timer
- 新增 KeysByPrefix/RemoveByPrefix(实例命名空间桶,写路径增量维护)
- 附录基准:Append -99.9%;Set K=1k·W=50 组 3-4×(F2 保留判定依据)
… JsonNode.Parse 字符串中转

- 节点完成 310→222 µs/节点(-28%,3k 键背景 100 节点链)
- JsonElement 引用包装按 ValueKind 分派(JsonObject/JsonArray/JsonValue.Create);
  实测 JsonValue.Create(JsonElement) 对 object/array 抛 InvalidOperationException,任务书原始指定不可用
- 等价性测试 20 例:新旧实现 GetRawText 逐字节一致(深嵌套/大整数/小数/科学计数/中文/转义/覆盖)
…onnection O(1)+按插件/触发器扇出)

- N=20 源每消息 108→15 µs(-86%),P99 256→18 µs(-93%);N 倍重复解析彻底消除
- PluginEventTrigger 无 router 时保留回退直订(测试直构兼容);Perf 基准补 Router/直订对照场景
- DI 懒解析 IPluginServer(防 DI 环卡死复发);注册进 ServiceCollectionExtensions 随后笔
…pletedCap 淘汰、绑定路径编译缓存

- Instances getter/Spawn 的 trigger 查找 O(T)→O(1):快照物化 197→130 µs(-34%)
- EndInstance 经 RemoveByPrefix 清 {toolkitId}/{instanceId}/wf/ 键(panel 按设计保留)——此前确证永久累积
- CompletedInstanceCap 默认 200(可配,0=无限):完成回调按 CompletedAt 淘汰最旧,走 EndInstance 全语义
- BindingResolver path→编译段链缓存(config 静态,键空间有界)
…er/Options DI 注册收口

- ScriptCompiler MaxCacheEntries 常量→构造参数(LRU 驱逐/Unload 不变);StructuredRoslynBackend 经 WorkflowV6Options 透传(可选参数,现有直构调用方源兼容)
- Core 新增 Config_Performance section + AppConfig 聚合 + IPerformanceConf(Standard 0796b9e)
- ServiceCollectionExtensions 收口:IPluginEventRouter(F3)与 ToolkitInstanceManagerOptions(CompletedCap)注册
- 两项配置经 Dashboard 性能设置页写入、启动时一次性注入(Dashboard 侧随后笔)
- PluginConnection 解析结果随事件下传(契约 nullable 字段,默认 null 回退源兼容);PluginsServer 优先消费不再二次解析
- PluginEventRouter 优先消费 e.Command(跳过预筛与反序列化),未携带时完整回退(兼容假件/旧实现)
- PluginsManager 启停等待轮询消除 Connections.ToList() 全量物化(每 500/200ms O(n) → FindConnection O(1))
- 删除 Core 本地死重复 PluginEventArgs(Device/Events,零使用;PluginsServer 经别名用契约版)
- 端到端:真实 socket 驱动 RegisterPlugin/普通命令/响应三路验证解析结果与原始串一致
- ActivityManager.ReadActivities(limit, skip) 按 Id 倒序分页(全量 ~73.6ms → 页读 0.57ms,~130×);CountActivities 供分页判定
- TrimToCap() 公共幂等裁剪(5000 上限裁最旧;Record 写路径每 1000 写低频触发)——顺带修复异步时序下终态可能超限的边界
- Config_Performance 加 PanelLogLimit(默认 1000),随性能页第三项配置
…p Dashboard(G2-G6 四笔)

- Host.Perf:消息解析链(G1)/RefreshInstances(G3)/Log 集合(G2)/活动日志(G6)/ConfigValidator(G5)五项,基线与优化后对照见《ToolKit-Dashboard-Core-性能评估-2026-08-19.md》附录
- Dashboard bump dfdc899→0958242(面板/列表/画布/设置四域)
读 {toolkitId}/{instanceId}/panel/{controlId}/log 环形键,字符串条目透传、其余以原始 JSON 文本呈现(与前端实时投影同格式);随 G3 面板重建回填消费
架构整改 #3(Core 侧):
- _activitiesDatabase 静态改实例字段,公共构造自建 Data 目录并打开 LiteDB(ConstantTable 路径),internal 测试构造注入 :memory:
- ReadActivities/CountActivities/Record/Update/TrimToCap 全部实例化;ActivitiesDatabase 静态可写属性删除;实现 IDisposable(DI 容器进程生命周期不释放,无行为回归)
- 过时 NOTE(D1 负责迁移、本轮不动)随收敛删除
- KitX.Core.csproj 增 InternalsVisibleTo KitX.Core.Test.Xunit(测试构造访问)
- 测试改实例化初始化,新增构造建目录开库用例;Core 66 全绿
- Dashboard b94c49c/5106609/8911d09/a6f117d/8d3c483:
  P1 hover 预览校验(RFC C14)|DB 初始化下沉 Core(Dashboard 侧)|更新 TODO 勘误|
  UIStateService 全量收敛 IWindowService+IGlobalDataStore|AppLanguage 静态删除(资源键)
- Standard 403b665:IActivityService 补 ReadActivities/CountActivities 契约成员
- 测试基线:WorkflowV6 655 / ToolKit 142 / Core 66(+1)/ Dashboard 93(+16),全绿
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants