对象:SharpTutor
docs/Vitro后端-C#扩展期需求清单.md(2026-09-12,对端锚定10591ad; 对端仓库本地镜像D:\code\SharpTutor,非本仓库内容,故不提供仓库内链接) 处置日期:2026-09-12 | 处置基线:本仓库master后续裁定注记(2026-09-27):本文 §3(C1 第二批 capi 窗口)与 §8 遗留 1 的"capi 第二批落地时" 表态已被 U2 拍板(2026-09-19)取代——第二批 capi 裁不做、19 声明冻结现状、对应能力以 serve 协议为终态载体(见 wasm多实例并发模型与U2拍板 §3);正文按当日回执留痕不改写。 关联文档:schema v0.1、 C# 扩展计划、CLI 手册、变更记录
本文档是对端需求清单的逐项回执:每项给出处置状态、落地载体与可复现证据。
第一批(A1/A2/B1/D1)的处置细节落在 CHANGELOG.md,本文档只引用不重复;
本批新增处置 B2 / C1 / C2 / D2 / D3。
0. 处置总表
| 项 | 内容 | 状态 | 落地载体 |
|---|---|---|---|
| A1 | EOF 终结语义 | ✅ 已修复(第一批) | crates/vitro_vm/src/host/io.rs;回归 baseline/scanf_eof_loop.c、scanf_eof_after_exhaust.c |
| A2 | 交互式输入喂入 | ✅ 已实现(第一批) | serve input.feed(语义单源 session_api::input_feed) |
| A3 | C# 子集规范 + 顶层语句入口合成 | ⏳ CS0/CS1 交付面 | CSharp前端引入计划.md §11(入口合成为 CS1 硬需求) |
| B1 | C# 码段 + error_catalog 导出 | ✅ 已落地(第一批) | 码段 E5xxx;vitro_get_error_catalog_json + serve error_catalog |
| B2 | schema v0.2 激活流程 | ✅ 本次落地 | schema §9 + unified/contracts.rs + 冻结测试(见 §1) |
| B3 | C# 已知差异清单 | ⏳ CS1 前由我方出草稿 | 对标 native/tests/*_FAILURES.md 格式 |
| B4 | 排期确认(透视 preview / G9) | ✅ 本次表态(§2) | — |
| C1 | 第二批 capi 窗口 | ✅ 本次表态(§3) | — |
| C2 | memory.regions 三段式标注 | ✅ 本次落地(serve 过渡形态) | session_api::memory_regions + 测试 memory_map_segments_test(§4) |
| D1 | 成员函数类型重载 trap | ✅ 已修复(第一批) | mangled 名带参数类型编码 + E4026 |
| D2 | 多会话语义澄清 | ✅ 本次落地 | serve session.create/reset/destroy 响应带 session 拓扑字段(§5) |
| D3 | target_name 跨帧解析 | ✅ 本次落地 | VitroVM::find_variable_name_at_addr + collector 回退链(§6) |
1. B2:schema v0.2 激活轨道(预留位 → 激活)
请求:① 预留位激活 = v0.2 只增事件且必须重跑 C1–C3+S1–S5;② UNWINDING 不得合并单步
写进字段冻结测试;③ semantic_label 受控词汇表落 schema 附录;④ code_file 与
ApiFrameInfo.return_line 排入 v0.2。
处置:四项全部落地,且不是文档承诺而是机器防线。
- v0.1 正式冻结:schema 头部状态改为"v0.1 已冻结(2026-09-12,S1–S5 签字回放 61/61)",
冻结锚点
10591ad。 - 激活轨道(schema §9,机器可读单源
native/src/unified/contracts.rs):- 预留位字段名集合
RESERVED_FIELDS_V0_2(四字段)冻结为常量; V0_2_ACTIVATION_CHECKLIST(五条:只增事件 / 更新 §7 校验表 / 重跑 C1–C3 / 重跑 S1–S5 + S4 激活契约 / 解除预留位断言并知会下游)落代码;- tripwire:
test_v0_1_reserved_fields_absent扫描嵌套全量键集合,一旦出现任一 预留位字段即失败,并把清单原样打印——"悄悄激活"在结构上不可能; - 出口:serve
contracts方法 +capabilities.schema(消费方可直读轨道与台账)。
- 预留位字段名集合
UNWINDING 不得合并单步有可执行判据:contracts::check_unwinding_granularity—— 相邻展开步的unwind_frames_left下降幅度 ≤ 1(一次弹多帧 = 合并单步); 允许为 0(finally步不弹帧,S4 §5 A5);回增为违规;进入展开须 ≥ 1、离开必须归零。 合成序列单测 + 集成断言双覆盖,CS3b 回放驱动将直接复用该函数。semantic_label受控词汇表(schema 附录 B,单源native/src/unified/vocabulary.rs):C 域 10 条(active)+ 异常域 4 条(reserved, 取自 S4 §6:throw/unwind/catch_enter/finally),出口 servesemantic_labels。 防线test_semantic_label_vocabulary_closed断言引擎实际产出的每个非空 label 都能归类 ——新增标签不登记词汇表即失败。code_file/call_stack[].return_line/func_display_name / func_mangled_name已进 v0.2 字段台账(§9.2,状态pending),并在 §8 #1/#2/#9 的计划列指向台账。
防线首日即抓到实缺陷(诚实记录):词汇闭合测试上线后立刻发现
释放内存、调用 printf、返回三条词汇在真实程序里几乎不可达——infer_semantic_label把"循环上下文"判定排在具体语句模式之前,而循环变量在循环结束后仍在作用域内, 于是循环之后的printf/free(p)/return 0全被标成循环 i=3。 已修订判定顺序(具体语句模式优先,循环上下文降为兜底前的最后一档),实测 循环体内的普通语句仍保留"循环 i=k"标注(教学价值最高的用法不变), 循环之后的语句恢复正确标签。
2. B4:排期确认
- 透视模式 preview(CS2):确认 CS2 交付点包含可运行 build/分支,由我方在 CS2 完成后提供"内容组试用报告",作为 CS3 投入的最后一道确认。
- G9 算法验证落点:确认落点 CS5 之后、LeetCode 批次之前。
关于"41 个 infer 模板元数据提前可见"——无需等待:算法标注器与模板元数据
已在仓库公开可读:
- 标注器:
native/crates/vitro_algorithm_steps/(7 个模块:sorting / search / graph / tree / dp / math / structures,41 个infer_*函数); - 模板元数据:
templates/*/meta.yaml(88 个模板,含source.c/source.cpp与meta.yaml)。 对端可据此立即起草 CS6 的对拍用例集("同题 C/C# 解法的算法名/phase/置信度一致")。
- 标注器:
3. C1:第二批 capi(memory / breakpoints)窗口表态
结论:第二批 capi 走"CS 批次之间插入"——落在 CS1 验收面稳定之后、CS2 交付点之前。
理由(按依赖强度排序):
- CS1 不需要它:CS1 验收面是 ch01–04 语料的"可编译可运行 + 入口合成", 只依赖第一批(compile / run / diagnostics)与 A3;
- CS2 反而需要它:CS2 是"预览版交付点"(白箱跑算法 + ARC 别名可视化), ARC 别名可视化要 region 高亮、透视模式要内存/断点数值形态——第二批若排在 CS2 之后, CS2 只能做半张画面;
- M3(调试面板)依赖第二批的三段式
kind,而 C2 已把 serve 过渡形态先行落地 (见 §4),第二批的增量被压缩为"语言中立化 + capi 导出",量级可控。
可退让的降级线(对端可直接采用其降级方案):若 CS0/CS1 出现进度压力, 退让顺序为 把第二批整体后移到 CS2 之后(M3 顺延,先做满 M1/M2)。 该退让不需要对端重新谈判——文中"降级方案"即为我方接受的口径。
4. C2:memory.regions 三段式 + 栈/全局标注
请求:kind(global/stack/heap)定型时,为栈/全局区域补 alloc_line / 名字。
处置:已在 serve 出口落地(过渡形态),并附可执行测试。
kind |
来源 | name |
alloc_line |
alloc_by |
备注 |
|---|---|---|---|---|---|
heap |
内部堆区清单 | heap_N / FILE:<path> |
分配点行号 | malloc/calloc/realloc/strdup/fopen/vfs |
保留 is_freed(三色堆图) |
global |
VM 全局符号合成 | 变量名 | 声明行 | static |
ty 为 C 风格类型名 |
stack |
活跃调用帧合成 | 函数名 | 进入该帧的调用行(main 为 0) |
call |
size = 帧跨度 |
- 数组按地址升序;新增
region_counts{global,stack,heap}(判据字段); - 堆统计口径零影响:栈/全局区域只在导出层合成,不写回
session.memory.regions——否则total_allocated/ 碎片率会把栈帧算成"已分配堆内存"。 该不变量有独立测试(test_segments_do_not_pollute_heap_region_list); - 实测(S3 的 swap 载体):
main帧alloc_line=0、swap帧alloc_line=14(调用点行)、 全局GLOBAL_Nalloc_line=3(声明行)。
对 S3 断言的影响:
regions现在同时含栈/全局条目,S3 A10–A12 按alloc_by == "malloc"过滤,不受影响;A4 的"指针 target_addr 落在此前无 region 覆盖的 栈空间"现在变成有kind:"stack"区域覆盖,是契约增强而非冲突。
5. D2:多会话语义澄清
结论(一句话版):单 serve 进程 = 单活跃会话;session.create = 清空重建,
session.reset = 清空运行状态但保留会话级配置,session.destroy = 清空重建(进程存活)。
方法表不携带并发句柄参数,也不支持并发逻辑会话——并发拓扑(长寿命诊断 +
瞬态运行)请起两个 serve 进程。
不只是文档:三个方法的响应都带上机器可读的拓扑字段,消费方不必靠文档猜:
{"id":1,"method":"session.create"}
{"id":1,"ok":true,"result":{"created":true,
"session":{"model":"single-active-session","active_sessions":1,"concurrent_sessions":false,
"handle_parameter":false,
"create_semantics":"clear-and-rebuild(清空重建同一实例)",
"reset_semantics":"清空编译/运行状态,保留会话级配置(隔离预算/deterministic/argv)",
"destroy_semantics":"清空重建(进程存活;如需回收进程请用 shutdown)",
"concurrency_recommendation":"并发场景起多个 serve 进程"},
"config":{…}}}
于是"M1 的双进程拓扑能否收敛为单进程"的答案是:不能收敛,也无需收敛 (两进程各自独立会话,互不干扰;单进程方案会引入会话句柄与互斥,收益为负)。
6. D3:target_name 跨帧解析
请求:被 pointed-to 变量在其他帧快照中可匹配时给出名字。
处置:已落地。VitroVM::find_variable_name_at_addr(addr) 搜索范围 =
当前帧 → 全局符号 → 其余活跃帧(由内向外):
- 命中判据:地址等于变量起始地址,或落在数组类型变量的元素区间内(
&arr[2]→arr); - 作用域判定与
get_variable_snapshot同源(函数归属 + 声明行;非顶层帧的"当前行" 取被调帧记录的caller_line,语义自洽); - collector 侧保留两级顺序:当前帧快照优先,未命中才问 VM(同名变量时内层更贴近学生视角)。
实测(S3 P1 swap 载体):
{"name":"a","target_addr":1048568,"target_name":"x","status":"Valid"} // 此前 target_name==""
{"name":"b","target_addr":1048572,"target_name":"y","status":"Valid"}
Schema 影响(已在 §2.5 写明):target_name 的空串语义收窄为"地址不属于任何
可见变量槽"(指向堆块内部、struct 字段中间、已出作用域变量),不再等同于"跨帧解不出来";
消费方不得把空串当作悬空信号(悬空与否由 status 表达)。无字段增删,属值语义增强。
7. 证据与防线(本次全部实跑)
| 防线 | 命令 | 结果 |
|---|---|---|
| Rust 全量 | cargo test --workspace |
✅ 全绿(含 3 个新增测试文件/用例组) |
| 静态检查 | cargo clippy --workspace --all-targets --all-features -- -D warnings |
✅ 零警告 |
| 字段冻结(含新增) | cargo test --test step_payload_schema_v0_1_test |
✅ 10 passed(预留位缺省 / 词汇闭合 / 展开粒度 / 文档↔代码) |
| 内存地图与跨帧 | cargo test --test memory_map_segments_test |
✅ 3 passed |
| C 影子验证 | go run ./scripts/shadow_verify(时点命令为 Python 版,已随 D5 退役) |
✅ 662 用例(见 CHANGELOG 记录) |
| C++ 影子验证 | go run ./scripts/shadow_verify_cpp(时点命令为 Python 版,已随 D5 退役) |
✅(见 CHANGELOG 记录) |
| serve 出口一致性 | go run ./scripts/serve_smoke(当时为 Python 版) |
✅ 40 项断言通过(新增三段式内存地图 / schema 轨道 / 词汇表 / 会话语义) |
| 签字回放 S1–S5 | go run ./scripts/replay/replay_s1_s5.go(时点命令为 Python 版,已随 D5 退役) |
✅ 61/61 PASS(前置门禁要求产物含当前 HEAD) |
8. 遗留与诚实记录
- C2 属过渡形态:
kind三段式目前在 serve 出口;capi 第二批落地时做语言中立化 (kind+status+alloc_line),届时memory.regions可能增字段(status), 现有字段语义不变。 - 全局区域
size口径:优先取符号表槽位跨度(与 codegen 实际分配一致); 末位符号退化为compute_type_size(标量/数组精确,struct/union/class 因 VM 侧无布局表 返回 0 时再退化为 1 字节)。这是可视化口径,不是sizeof语义,勿用于判分。 target_name已知限制:非数组复合类型不参与区间匹配(struct 字段取址解不出名字)。- B2 的 CS3b 部分仍是"已登记、待激活":四预留位 + 异常域词汇 +
unwinding_step_granularity契约均为reserved,激活顺序与验收按 schema §9.1 清单与 S4 §5 A1–A8 执行。 - A3 / B3 属 CS0/CS1 交付面(子集规范、差异清单草稿),不在本批处置范围。