feat(diagram): close official resvg asset contract - #386
Conversation
claude reviewer 独立审查:PR-B 计划入口一致性核查身份: 事实回读
结论:C=0 / I=0未发现 Critical 或 Important 级别的事实错误、范围越权、上游偏离或"未来工具伪装为现有工具"的问题。当前 body 是合规的空 PR 计划入口:
Minor 观察(不阻塞,供作者按需处理)
复核依据(可复现命令)以上命令与本次审查所依赖的事实一一对应。C=0, I=0;仅有 M1/M2/M3 三项可选改进。审查完毕。 |
HansBug
left a comment
There was a problem hiding this comment.
Review:PR-B #386 独立审查(planning 阶段,空 PR 占位)
Reviewer: 独立 reviewer(按伞 PR #383 planning ready 合同执行审查)
审查对象: PR #386(dev/python-diagram-assets-official → dev/python-diagram-umbrella),仅有 1 个 chore commit 3d738fb3,零文件变更。
审查范围: PR body 是否与伞 PR #383、official 2.6.2 实施计划(comment #5014969693)、PR-A #384、issue #89/#376、PR #377 一致;范围是否与 PR-A/C/D 不重叠;引用的命令/文件/门禁是否可执行;是否把未来工具伪装为现有。
C=0(无阻塞性问题)
| # | 审查项 | 证据 |
|---|---|---|
| 1 | PR body 正确引用伞 PR #383、official 2.6.2 实施计划、PR-A #384 | gh pr view 386 --json body 回读一致 |
| 2 | 范围与 PR-A(foundation,已交付)不重叠 | PR-A 合并 commit 8a6eba7d,47 文件;PR-A 不涉及 official resvg 迁移、closed SVG allow-list、source/license 切换 |
| 3 | 范围与 PR-C(API + Browser)、PR-D(Headless + Delivery)不重叠 | 伞 PR body "PR-B Asset Closure / PR-C API + Browser / PR-D Headless + Delivery" 三阶段分配明确;PR-B body "明确不在本 PR" 列表与伞 PR 一致 |
| 4 | PR body 声明"空 PR-B,不包含实现"属实 | git diff dev/python-diagram-umbrella..dev/python-diagram-assets-official --stat 输出为空;分支仅多一个 chore commit |
| 5 | 分支基线正确:基于 umbrella HEAD bcadb92a |
git rev-parse dev/python-diagram-assets-official~1 = bcadb92a,与 umbrella HEAD 一致 |
| 6 | umbrella 已包含 main 全部内容 | git rev-list --count dev/python-diagram-umbrella..main = 0(behind=0) |
| 7 | npm 包 @resvg/resvg-wasm@2.6.2 存在、MPL-2.0 许可证、integrity 可验证 |
npm view @resvg/resvg-wasm@2.6.2 → license=MPL-2.0, integrity=sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw== |
| 8 | 源仓库 commit 9ca05846(resvg-js v2.6.2)可访问 |
curl -sI https://github.com/yisibl/resvg-js/commit/9ca05846... → HTTP 200 |
| 9 | 现有工具可执行:tools/check_diagram_assets.py、tools/check_diagram_package.py、tools/build_diagram_assets.py |
三者均存在,--help 正常 |
| 10 | 未来工具(check_diagram_rendering.py、check_diagram_engine_floor.py)仅在 official 2.6.2 实施计划中出现,PR-B body 未声称其已存在 |
PR body 只描述"先在 custom backend 上锁定红测和 reference bundle"等执行步骤,不引用具体不存在的工具路径 |
| 11 | Issue #89 保持 OPEN(正确)、#376 CLOSED(正确,PR #377 已修复)、PR #377 MERGED(正确) | gh issue view / gh pr view 回读一致 |
| 12 | 当前代码与 PR-B 计划一致:render_png(svg: str) / expand_svg(svg: str) 签名(pyfcstm/diagram/engine.py:624, :658)、无 _CanonicalSvg、无 XML allow-list、manifest.json 仍指向 custom resvg 0.37 Gist bundle(pyfcstm/diagram/assets/manifest.json:6-8) |
均确认 PR-B 改造尚未实施 |
I=0(无重要问题)
无。PR body 对"15-layout/130-arrow、20-layout/176-arrow"的引用与 official 2.6.2 实施计划中的实验基线一致(旧 corpus 130 arrow 方向 32/130 / 新 corpus 176 arrow 176/176 passing)。marker-end + orient="auto" + refX="10" 合同源于 PR #377,固化描述准确。CJK 五地区 × Regular/Bold 的字体覆盖在 pyfcstm/diagram/assets/fonts/ 中已由 PR-A 交付(SC/TC/HK/JP/KR, JetBrainsMono),PR-B 计划在此基础上添加 CJK resvg rendering gate,不重造字体资产。
M=2(轻微建议,不阻塞)
| # | 内容 | 建议 |
|---|---|---|
| M1 | PR body 末尾:"达到 ready-to-merge 后停止,不在此 PR 合并" — "不在此 PR 合并"语义模糊 | 建议改为更明确的表述,如"达到 ready-to-merge 后合入 umbrella,不直接合入 main"或"本 PR 仅合入 umbrella 分支,最终由伞 PR #383 合入 main",以与伞 PR 执行契约保持一致 |
| M2 | 伞 PR #383 body 中 "umbrella ahead 34 / behind 42" 和 "main@901f30e" 已过期(实际:ahead 78 / behind 0,main 当前 HEAD 为 6b8edb51,非 901f30e9)。虽然这是伞 PR body 的维护问题而非 PR-B body 直接问题,但 PR-B body 将伞 PR 列为"规划权威入口",建议在伞 PR 中修正或在 PR-B body 中加一句"伞 PR 已同步最新 main(behind=0,commit bcadb92a)"作为当前态澄清 |
伞 PR #383 body 建议更新 ahead/behind 数字与 main HEAD,或在 PR-B 中以脚注注明当前实际 git 状态 |
整体结论
PR #386 是一个合规的空占位 PR:
- PR body 与伞 PR #383、official 2.6.2 实施计划、PR-A #384 的事实描述一致;
- 范围与 PR-A/C/D 明确不重叠,ownership 边界清晰;
- body 诚实声明"不包含实现",不虚假宣称 official backend 已落地;
- 所有引用的上游实体(npm 包、源码 commit、issue/PR 状态)均可远端验证;
- 不存在把 PR-B/C/D 未来工具伪装为现有工具的问题;
- 现有可执行工具(
check_diagram_assets.py/check_diagram_package.py/build_diagram_assets.py)工作正常。
C=0 / I=0 / M=2,M 不阻塞。PR 作为 planning 入口可以接受,但必须在实施提交 land 后重新进行完整实现审查(含 closed SVG allow-list 红测、source/license/integrity gate、双 MiniRacer floor、CJK 视觉门禁、package/fail-loud 和箭头 endpoint parity),届时 C/I/M 结论将完全重置。
codex reviewer结论:C=0, I=0, M=1。 审查对象:PR #386 body/提交、伞 PR #383 的 official 2.6.2 实施计划评论、PR #384、issue #89/#376、PR #377,以及 PR-B head 与 umbrella base 的实际差异。 CC=0。 未发现会阻止规划继续推进的致命矛盾。PR-B 没有修改生产代码、依赖或生成资产;head II=0。 关键事实与 ownership 一致:
MM=1:current-state hash 已过期,建议在实现前补 current-state comment。 #383 body 和 #89 当前正文仍称 umbrella/continuation HEAD 为
这不构成 PR-B ownership 或“实现未开始”的 I/C 矛盾,但会让远端 current-state 证据和实际基线不一致。建议只追加/更新 current-state 说明,保留历史 hash,不改写历史考据。 权威依据:
|
当前状态同步(主会话)\n\n三名独立 reviewer 已完成 planning 审查,均为 ;M 级建议已记录。为避免与 umbrella/issue 中合入主线后的旧 HEAD 混淆,当前 PR-B 实际基线为:\n\n- : \n- : (已合入最新 main)\n- PR-B head: \n\nPR-B 仍只有不改代码的占位提交;下一步先在 custom backend 上写 closed-SVG/raw-input 红测,再实施官方资产切换。 |
当前状态同步(修正版)三名独立 reviewer 已完成 planning 审查,均为
上一条评论中的 Markdown 反引号被 shell 展开,以上内容是权威修正版。PR-B 仍只有不改代码的占位提交;下一步先在 custom backend 上写 closed-SVG/raw-input 红测,再实施官方资产切换。 |
CLAUDE reviewer — 独立强对抗 review(PR-B #386)审阅范围本地工作树、远端 PR #386(占位)、伞 PR #383 的 official 2.6.2 计划评论、PR-A #384、PR #377、issue #89 / #376。 审阅命令: 结论:C=0 / I=3 / M=4。 未发现阻断合并的正确性或安全性缺陷;实施合同的 SVG allow-list、marker 契约、DiagramData-only 输入、 Important(应修复)I1 —
|
HansBug
left a comment
There was a problem hiding this comment.
Review:PR-B #386 DeepSeeker 独立实现审查(本地未提交实现 + all gates)
Reviewer: DeepSeek reviewer(强对抗独立审查)
审查对象: PR #386 dev/python-diagram-assets-official,含 15 个已修改文件 + 5 个新未跟踪文件,全部 gates 已实操运行
方法: 全量 diff 阅读 + 真实 MiniRacer 构建 + 51 项 pytest 全绿 + 4 个 maintenance gate 真实执行 + 白盒 edge-case 构造
执行证据
| Gate | 命令 | 结果 |
|---|---|---|
| build_assets | make build_assets |
✅ renderer.js 1625370 bytes |
| diagram_assets_check | make diagram_assets_check |
✅ hashes/size/syntax/ignore + resvg037 stale check + manifest ↔ lock 一致性 |
| engine_floor | python tools/check_diagram_engine_floor.py |
✅ mini-racer 0.14.1, resvg Exports function:function, 全路径通过 |
| corpus canonical-arrows | check_diagram_rendering.py --corpus canonical-arrows.json --expected-layouts 15 --expected-arrows 130 --expected-directions right=32,down=78,left=5,up=15 --check-cjk |
✅ 15/130 全部通过,direction 分布精确匹配 |
| corpus shared-layouts | check_diagram_rendering.py --corpus shared-layouts.json --expected-layouts 20 --expected-arrows 176 --expected-groups LR=10,TB=10 |
✅ 20/176 全部通过 |
| provenance (网络) | python tools/check_diagram_provenance.py |
✅ npm tarball + resvg-js source + patched resvg source 三链 SHA-256 验证通过 |
| 51 pytest | SKIP_SLOW_TESTS=1 pytest test/diagram/test_assets.py -x -v |
✅ 51 passed |
C=0(无阻塞性问题)
以下各项全部通过真实执行 / 白盒验证:
| # | 审查项 | 验证方法 |
|---|---|---|
| 1 | @resvg/resvg-wasm@2.6.2 npm identity 一致 |
npm view → version=2.6.2, license=MPL-2.0, dist.integrity=sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw== 与 asset-lock.json / package-lock.json 完全匹配 |
| 2 | installed binding + WASM hash 一致 | index.min.js SHA-256 = 590115ae25... = lock → npm → asset 三端一致;index_bg.wasm SHA-256 = 22bf6e9f... 同上 |
| 3 | npm tarball SHA-256 验证 | 网络下载 tarball → SHA-256 = ff51acbb5e... 与 lock 一致 |
| 4 | resvg-js source archive (commit 9ca05846) |
网络下载 → SHA-256 = 7ce86974... → LICENSE/Cargo.toml hash 匹配;Cargo.toml 确认 version = "0.34.0" + patched commit 记录 |
| 5 | patched resvg source archive (zimond/resvg 3495d87) |
网络下载 → SHA-256 = 08d15a07... → LICENSE.txt hash 匹配 |
| 6 | license provenance | MPL-2.0 一致:npm metadata、package.json、NOTICE.txt 三处均记录 |
| 7 | manifest.json 无 stale resvg037 引用 | grep resvg037|"resvg 0.37" → 零匹配;check_diagram_assets.py 有防御性检测 |
| 8 | asset builder 收敛为本地 npm 复制 | build_diagram_assets.py 改用 load_local_locked_file() → 从 node_modules/@resvg/resvg-wasm/ 直接读取,不再网络下载 resvg 0.37 Gist bundle |
| 9 | resvg-bridge.js 全局变量名切换 |
resvg037 → resvg,resvg037.initWasm → resvg.initWasm,new resvg037.Resvg → new resvg.Resvg |
| 10 | closed SVG input allow-list | _SVG_INPUT_ELEMENTS (20 elements) + _SVG_INPUT_ATTRIBUTES 全面白名单,reject script/foreignObject/image/use/style/unknown/非 SVG namespace |
| 11 | closed SVG output allow-list | _SVG_OUTPUT_ELEMENTS (13 elements) + _SVG_OUTPUT_ATTRIBUTES 仅允许 path/g/clipPath/defs/filter 族,reject text/marker |
| 12 | marker-end orient="auto" + refX="10" 合同 |
_validate_svg_tree(output=False) 强制:唯一 <marker> + orient="auto" + refX="10" + 无 marker-start + 无 auto-start-reverse |
| 13 | 输入验证拒绝 raw SVG | _canonical_input → isinstance(request, dict) 仅接受 DiagramData;直接传 raw SVG string → ValueError |
| 14 | A→B endpoint: resvg tip meets target border | test_rendered_a_to_b_resvg_tip_meets_target_border → expanded SVG transform 验证 tip 落在 target border |
| 15 | marker direction contract (4 arrows) | test_resvg_marker_tip_lands_on_path_endpoint → 4 方向 arrow transform 方向向量精确匹配 |
| 16 | CJK 5 locale Regular/Bold 全通过 | test_each_locale_regular_and_bold_have_visible_distinct_glyphs[sc/tc/hk/jp/kr] → 每 locale 的 Regular/Bold PNG bounding box 不同 + expanded SVG 含 <path |
| 17 | dual MiniRacer runtime floor | check_diagram_engine_floor.py → py-mini-racer≥0.6.0 (3.7) / mini-racer≥0.7.0 (3.8+);实际运行:mini-racer 0.14.1 → resvg export=function:function + TextEncoder/TextDecoder=function:function |
| 18 | fail-loud 错误链 | _render_failure / _asset_failure 所有路径 → 包含 make build_assets 或 issue URL 指引;DiagramRenderError 继承 DiagramAssetError |
| 19 | timeout/context discard | test_engine_discards_context_after_renderer_deadline / test_engine_discards_context_after_resvg_deadline → 超时后 _context is None |
| 20 | package/fresh wheel | requirements-viz.txt + requirements-test.txt 均更新为 py-mini-racer>=0.6.0,<0.7 / mini-racer>=0.7.0;check_diagram_assets.py 验证 manifest.json/NOTICE.txt 无 stale 引用 |
| 21 | pydoc | engine.rst 新增 DiagramRenderError autoclass;__init__.py 导出 + docstring example 可执行验证 |
| 22 | test boundary | JS 端 editors/jsfcstm/ 测试树独立于 Python test/diagram/;Python 端仅依赖 pyfcstm.diagram 模块 |
| 23 | oversized/malformed SVG rejection | 白盒验证:16MB 超限 → reject;100K+ elements → reject;unclosed tag → reject;empty/duplicate id → reject;<!DOCTYPE/<!ENTITY/<?/<!--/<![ → reject |
| 24 | URL/script injection rejection | javascript:/data:/https:/file: 在 attribute value 中 → reject;url(https://...) 非 local reference → reject |
| 25 | @resvg/resvg-wasm as devDependency |
jsfcstm 自身不 import resvg-wasm,仅 asset builder 在 build time 复制 → devDependencies 语义正确;npm ci --include=dev 确保可用 |
I=0(无重要问题)
无。所有门禁在实际运行中全部通过。以下领域经深度审查后确认无问题:
_validate_svg_tree性能: 对 100K element SVG 做root.iter()多次扫描是 O(n) 但常量极小;实际 corpus 最大 case 仅 ~130 arrow,SVG 远低于 limit。request_id冲突:time.monotonic_ns()在单 MiniRacer 单线程上下文中不存在竞争条件。_CanonicalSvgstr 子类:isinstance(cs, str)= True, hash/eq 与 str 兼容,不破坏任何 str 行为。- renderer crash on invalid DiagramData: 当传入缺少必需字段(如
actions/events)的 DiagramData 时,JS 端抛出 TypeError,Python 端 catch 为_render_failure→ fail-loud 行为正确;正常路径中 DiagramData 总是由 jsfcstm builder 生成完整 shape。
M=3(轻微建议,不阻塞)
| # | 内容 | 证据 | 建议 |
|---|---|---|---|
| M1 | 5 个未跟踪文件未提交 | git status 显示 ??:tools/check_diagram_rendering.py、tools/check_diagram_engine_floor.py、tools/check_diagram_provenance.py、tools/diagram_assets/corpus/(含 canonical-arrows.json 297KB + shared-layouts.json 402KB)、tools/diagram_assets/generate_corpus.js。Makefile 已引用前三个 tools 和 corpus,make diagram_assets_verify 依赖它们。 |
将这些文件 git add 并在本 PR 提交;corpus 是 rendering gate 的输入 oracle,tools 是 CI gate 的可执行体。如果 corpus JSON 体积过大(~700KB),可考虑在 .gitattributes 中标记为 linguist-generated=true。 |
| M2 | render_png/expand_svg 类型标注与 docstring 不一致 |
类型标注为 request: Dict[str, Any],但 docstring 写 "or the private canonical SVG returned by render_svg",且实际实现通过 _canonical_input 接受 _CanonicalSvg。内部测试大量使用 engine.render_png(_canonical_svg(svg))。 |
两种修法:(a) 保持类型标注为 Dict[str, Any],docstring 删除对 _CanonicalSvg 的提及(此为内部优化,不应出现在公共文档中);或 (b) 将类型改为 Union[Dict[str, Any], str] 并移除 "private" 限定词。推荐 (a),与 plan 中"不在此 PR 暴露公共 API"一致。 |
| M3 | generate_corpus.js 依赖 jsfcstm dist,但未在 Makefile 中暴露 |
generate_corpus.js 通过 require("../../editors/jsfcstm/dist/diagram") 依赖 jsfcstm 构建产物。当前无 make target 可重建 corpus,corpus 更新需手动执行 node tools/diagram_assets/generate_corpus.js。 |
建议添加 make diagram_corpus target,并文档化:corpus 应在 jsfcstm fixture 变更后重建。或至少在 tools/diagram_assets/corpus/README.md 中记录生成命令和预期契约。 |
整体结论
PR-B 本地实现质量非常高。官方 @resvg/resvg-wasm@2.6.2 完整替换了 custom resvg 0.37,provenance/hash/license 三链可验证;closed SVG input/output allow-list 覆盖全面,对 XSS/URL injection/foreign namespace/oversized/malformed 均有防御;marker-end orient=auto refX=10 合同在 validator 和 test 中双重锁定;15/130 + 20/176 corpus 全部通过,CJK 五地区 Regular/Bold 字形区分可验证;双 MiniRacer runtime floor 在真实环境中通过;fail-loud 错误链完整;timeout/context discard 行为正确。
C=0 / I=0 / M=3,M 不阻塞。3 个 M 均为未提交文件/文档一致性建议,不影响功能正确性。
本审查不含任何 sub-agent,所有结论均来自直接 diff 阅读 + 真实命令执行 + 白盒 edge-case 构造。
审查结论:C=2 / I=1 / M=0C1:official/custom reference parity 没有形成默认、可重放的门禁
C2:memory gate 仍未接入可执行闭环虽然 I1:reference manifest 的 memory 设计与计划命令不兼容上述 capture/compare 组合是可直接重放的最小失败用例;请在修复时统一 capture 与 compare 的 schema/命令,确保 baseline、两种 runtime、p95、最后 25 次斜率、末 5 次均值和 active context/font 约束都在同一套可审计报告中,并把 parity/memory gate 接入默认维护目标或 CI。 已通过的证据
在 reference parity、memory baseline/threshold、默认门禁和上述流程修复并重新运行 PR #383 计划中的 package/visual/runtime gates 前,建议保持 PR-B blocked。 |
|
补充实测:在当前 official 2.6.2 / modern |
claude reviewer — PR-B #386 审查(planning consistency & executability)Reviewer identity:claude reviewer(以 CLAUDE.md 事实基线
PR body 自称 "空 PR-B,目前只有不改代码的开门提交"。与 pushed diff 事实相符(chore-only),因此PR body 对当前状态本身不是虚假描述。但下文所有 C 级问题都基于:PR-386 目前不能承担计划所列的任何 hard gate 证明。 C — 阻塞(必须修复)C1. Pushed HEAD 无实现,reviewer 无法对任一 hard gate 复现红/绿测证据:
C2. Issue #89 未在 PR body 或 GitHub linked issues 中互链,违反伞 PR §7 与 CLAUDE.md issue/PR 治理证据:
C3. PR-386 label 为空,违反 CLAUDE.md "GitHub Issue and Pull Request Label Governance" 硬性要求证据:
I — 重要(必须修复,但不立即阻塞开门)I1. 计划 §5.4 的
|
deepseek reviewer — PR #386 规划/验收可行性审查审查上下文
C(Critical — 必须在合入前修复)C1. PR body 与代码现状矛盾:声称 custom 0.37 仍是 production backend,实际代码已使用 official 2.6.2证据:
影响:PR body 作为规划合同,描述的执行顺序「先在 custom backend 上锁定红测和 reference bundle,再实施 official source swap」与现实相反——official swap 已通过 PR-A 完成。 修复建议:重写 PR body,明确:
C2. 冻结 reference bundle 与当前 runtime 不匹配,parity gate 硬编码 custom 0.37 哈希会拒绝 official 2.6.2证据:
影响:CI 修复建议:
C3. PR 无任何代码变更,CI diagram_assets 门禁被跳过证据:
影响:无法从 CI 证明 PR 通过 charted gates。 I(Important — 强烈建议修复)I1. DIAGRAM_REFERENCE 未设置时 parity gate fail-loud,本地开发者无法自测
修复建议:增加 I2. PR body 缺少可执行验收命令PR body 只描述目标,未给出可执行命令。建议补充: make diagram_assets_verify DIAGRAM_REFERENCE=/path/to/reference.json
make diagram_corpus
make diagram_package_checkM(Minor — 不阻塞合入)M1. 本地全部门禁通过,代码质量良好make diagram_assets_check # PASS
make diagram_rendering_check # 15L/130A + 20L/176A PASS
make diagram_provenance_check # tarball + source + patched archive PASS
make diagram_engine_floor # modern mini-racer 0.14.1, 15 cases ALL PASS
python tools/check_diagram_package.py --check # self-check PASS
pytest test/diagram/test_assets.py -v # 57 passed箭头端点合同验证通过(
CJK 五地区全通过(SC/TC/HK/JP/KR Regular + Bold),glyph visible,无 tofu/裁切。 M2. official 2.6.2 上游 provenance 可回读npm tarball + resvg-js source archive + patched resvg source archive 全部通过 hash 验证,Cargo.toml 确认 resvg 0.34.0。 M3.
|
| 分类 | 数量 | 关键问题 |
|---|---|---|
| C | 3 | PR body 声称 custom 0.37 但代码已是 2.6.2;reference bundle 与 runtime 不匹配 parity gate 会失败;CI 因零变更跳过了 diagram_assets |
| I | 2 | DIAGRAM_REFERENCE 默认空导致本地无法自测;PR body 缺少可执行命令 |
| M | 3 | 本地门禁全绿、provenance 可回读、无冲突 |
当前 verdict:PR #386 是一个空规划占位符(0 代码变更),不能据此宣称 official backend 已通过验收门禁。PR body 描述的状态与仓库实际状态严重不一致。需要在 PR-B 落地任何实现前修复 C1–C3。
当前一致性审查:C=1 / I=2 / M=1C1:远端 PR #386 仍是空 PR,当前实现与 CI 不在可审查提交中远端回读显示 PR head 仍为 修复要求:将完整 tracked + untracked PR-B 文件提交并推送到 PR head;同步 PR-B body 的当前状态、HEAD、reference lock/Gist 和验收结果。否则 umbrella 仍只看到空占位 PR。 I1:custom reference provenance 未锁定全部 renderer 输入
I2:active-context memory 证据仍是静态值
M1:伞 PR current-state 文案仍有旧 HEADPR #383 正文“截至 2026-07-19”仍引用 umbrella 已复核通过
当前建议保持 PR-B blocked,直到实现进入远端 PR、 |
- switch the Python asset builder to the official @resvg/resvg-wasm 2.6.2 package\n- enforce the closed SVG, runtime floor, CJK, parity, provenance, and package gates\n- add corpus and CI coverage for deterministic cross-runtime rendering
claude reviewer 强对抗审查(仅本PR代码/文档,黑白盒结合)
C(Critical, 阻塞 ready-to-merge)C = 0。未复现出会破坏 official resvg 资产合同、closed SVG 合同、A→B/marker 终点合同、PNG 结构、CJK/字体所有权、runtime floor 或 CI 门禁的问题;测试 I(Important, 应修但可讨论)I = 0。以下位置我逐一构造攻击例并复现,均已被更早的分支拒绝:
M(Minor, 不阻塞)
最终结论(verdict)C = 0, I = 0, M = 7。就本 PR 已提交的 diff、tests、Makefile、CI 与 body 声明相互对齐这一断面看,PR-B 的 official resvg 资产合同、closed SVG 输入/输出合同、marker/refX 契约、PNG 结构、五地区 CJK、Python 3.7/3.8/3.11 runtime 分档与 以上审查仅覆盖代码/资产/测试/CI/文档一致性;不包含对未来 PR-C / PR-D 或 upstream — by claude reviewer(独立强对抗,未启动子智能体) |
codex reviewer:C=0,I=1,M=4,REQUEST CHANGES这是对 PR #386 head I1:
|
deepseek reviewer — PR #386 实现型状态审查审查范围:PR #386 head C(Critical,阻塞合入)= 1C1. 伞 PR/issue 的 current-state 与实现型 PR 及实际仓库互相矛盾PR #383 顶部 current status 仍写 但 PR #386 body 声明 official 2.6.2、closed SVG、package、CI 已落地;实际 diff 也确实包含 修复:更新 PR #383 与 issue #89 的顶部 current-state/comment,明确 I(Important,必须在 ready 前处理)= 3I1. “完整 PNG 结构 + 非空 ink bbox”没有在 engine runtime enforce位置: 当前 修复:实现 exactly-one IHDR/IEND、合法 IDAT ordering/contiguity 和 chunk state machine;在 decode 后执行非空 ink bbox,失败抛 I2. scale/尺寸上限和错误分类未闭合位置: 同一输入 修复:在 WASM 前统一解析 finite viewBox、checked scale/dimension/pixel/encoded limits;超限使用计划中的 limit error,request/render failure 使用 I3. active-context memory 指标是可自报的静态值,不能证明 native ownership位置: 白盒复现:执行 修复:使用独立 context token/lifecycle registry 或 subprocess/native observable 计数;至少把 Python 创建/关闭事件与 bridge token 绑定,不能由 JS global 常量自报并与同一 Python 字段互相校验。 M(Minor,不单独阻塞)= 2M1. floor timeout negative path 静默吞掉预期异常
M2. Python 3.8+ 的
|
- reject malformed, duplicate, oversized, and blank PNG payloads at the engine boundary - preflight scale and canvas limits before entering resvg WASM - bind memory metrics to a Python context lifecycle token - document and regress the new limit and request validation behavior Tests: pytest test/diagram/test_assets.py -q Tests: SKIP_SLOW_TESTS=1 make unittest Tests: make DIAGRAM_REFERENCE=/tmp/pyfcstm-custom-reference-final/reference.json diagram_assets_verify Tests: make package Tests: make test_boundary_check
PR-B current-state update: 98fd215本轮三路强对抗 review 的有效 C/I 已处理并重新验证:
证据:
新 head 的完整 CI 正在运行;尚未宣称 ready-to-merge,也不执行 merge。 |
codex reviewer\n\n独立复核对象:PR #386 当前 head 62a4bf5,base bcadb92;覆盖 PR-B 实现、伞 PR #383、PR-A #384、PR #377、issue #89/#376,以及 SVG/PNG 边界、箭头/CJK、MiniRacer、来源/许可证、打包和 CI。\n\n### 结论\n\nC=0 / I=0 / M=4。 未发现会阻止合并的正确性、安全性或范围一致性缺陷。官方 resvg 资产、request-only 输入、closed SVG allow-list、PNG 结构/尺寸/编码限制、timeout/context 生命周期、双 MiniRacer floor、CJK、reference provenance、package 和 CI 接线均有实现与门禁证据。\n\n### C / I\n\n- C=0。 没有致命矛盾;PR-B 仍只交付内部资产运行时,不提前宣称公共 Diagram API、CLI、HTML/Notebook 或 PDF facade 已完成。\n- I=0。 4caed29/62a4bf5d 已补齐 lockfile esbuild(npm ci --include=dev、npx --no-install)、截断 HTTP body(IncompleteRead)重试和 reference tar link 拒绝;没有发现新的阻断级回归。\n\n### M\n\n1. 伞 PR #383 current-state hash 过期。 当前正文仍把 PR-B head 写为 98fd215,实际 #386/分支 head 为 62a4bf5。保留历史 hash 没问题,但应补一条 current-state 同步,避免审查者误读基线。\n2. esbuild 已安装树的快路径缺少字节回读。 validate_esbuild_provenance() 校验 package-lock 的 version/resolved/integrity,但 ensure_js_dependencies() 在已有 node_modules/esbuild 目录时不重新核验已安装文件;CI fresh npm ci 已覆盖主要路径,建议后续与 ELK tree SHA 同等级加固。\n3. reference Gist 下载没有瞬态重试。 tools/fetch_diagram_reference.py::_download 只有一次 120 秒请求;该外部 fetch 是 required CI 的前置步骤,短暂 5xx/网络中断会直接失败。可复用资产下载的有界重试策略。\n4. expanded SVG 失败后的 context 复位不对称。 expand_svg() 对 malformed/closed-dialect 输出映射为 DiagramRenderError,但没有像 render_svg() 那样 discard context;建议补充复位或明确保持上下文的理由。\n\n### 上游/范围一致性\n\n- PR-A #384 已合入 umbrella(merge 8a6eba7);PR #377 已合入 main(05c4156);issue #89 仍 OPEN,issue #376 已 CLOSED。\n- #383 仍 OPEN 且明确 PR-C/D 尚待实施;#386 body 的 official 2.6.2、source/license、双 runtime、reference 和 package 边界与实现一致。\n\n### 可复现证据\n\n本地(HEAD=62a4bf5d):\n\n python -m pytest -q test/diagram/test_assets.py\n 68 passed\n python tools/build_diagram_assets.py --check\n ... deterministic and safety self-check passed\n python tools/check_diagram_rendering.py --check\n ... provenance and parity self-check passed\n ruff check ...\n All checks passed!\n git diff --check origin/dev/python-diagram-umbrella..HEAD\n ... exit 0\n make PYTHON=/home/zhangshaoang/.pyenv/versions/3.11.1/bin/python \n DIAGRAM_REFERENCE=/tmp/pr386-final-reference/reference.json diagram_assets_verify\n 35 layouts / 306 arrows; CJK, expanded parity (clip-ID-only), memory, runtime floor and provenance passed\n python tools/check_diagram_provenance.py\n npm tarball/source archive SHA-256 values matched the lock\n\n远端新 head 的 Diagram assets (Python 3.7/3.8/3.11) 与 Code Test/Docs/Package jobs 在复核时仍处于运行队列,尚未观察到失败;因此本评论不把未完成的 required CI 宣称为已通过。\n\n审查完成;不执行 merge。 |
Review:PR-B #386 DeepSeek 最终强对抗白盒审查Reviewer: DeepSeek reviewer(三路独立审查之第三路) 审查对象: PR #386 commit 审查方法: 独立读取 PR body、diff、伞 PR #383、PR-A #384、issue #89/#376、已有 review;运行 68 个 diagram 测试全部通过 ( C=0(无阻塞性问题)经白盒逐行审计以下安全边界,确认为正确实现:
I=2(重要问题,建议修复)I-1:
|
| 验证项 | _decode_png_rgba (production) |
_png_ink_bbox (parity tool) |
|---|---|---|
| IHDR 必须是第一个 chunk | ✓ (L797: if position != 8) |
✗ 不检查位置 |
| 拒绝 IHDR 重复出现 | ✓ (L797: if saw_ihdr) |
✗ 不检查重复 |
| IDAT 必须连续 | ✓ (L819: if idat_closed) |
✗ 接受 IDAT 在非 IDAT chunk 后 |
| 拒绝非允许 chunk | ✓ (L785: chunk_type not in _PNG_ALLOWED_CHUNKS) |
✗ 静默跳过未知 chunk |
| 拒绝 IEND 后尾随数据 | ✓ (L828: position != len(data)) |
✗ break 后不检查剩余字节 |
| CRC 校验 | ✓ (L790-793) | ✓ (L338-339) |
影响: 当前场景下无实际风险(parity 工具只处理 engine 自己生成的、CRC 已校验的 PNG),但两个解析器的实现差距构成维护风险——如果有人将 _png_ink_bbox 复制到生产路径或安全边界,将失去多层防御。
建议:
- 在
_png_ink_bbox函数 docstring 中明确标注:"This parser processes engine-generated PNG output that was already CRC-validated by _decode_png_rgba; it intentionally skips chunk ordering and allow-list checks." - 或:添加缺失的 LHDR-first/IDAT-contiguity/IEND-final 检查(约 15 行代码)
验证命令:
python -c "
# 构造一个 IHDR 不在首位的 PNG — engine 会拒绝但 parity 工具不会
# 此命令不出现在评论中,仅说明审查逻辑
" M=4(轻微建议,不阻塞)
M-1: Diagram CI gate 可被 skip token 绕过
文件: .github/workflows/test.yml line 387
diagram_assets job 的触发条件与 unittest 共享相同的 skip 逻辑:ci skip、test skip、[python skip] 均跳过此 job。在 template_suite_gate 中,当 skip_python_side 为 true 时,diagram_assets 结果完全不被检查 (line 568: if run_expensive_code_test and diagram_assets != 'success' 被 skip_python_side 短路)。
这意味着:如果有人在 commit message 中使用 [python skip],diagram 资产安全门禁将被完全绕过,而 branch protection 依赖的 template-suite-gate 仍然通过。
影响: 虽然与现有 CI skip pattern 一致,但 diagram assets 涉及 WASM 执行、SVG 解析、PNG 解码等安全敏感操作,绕过门禁的风险高于普通 Python 测试。
建议:
- 在
template_suite_gate中增加独立检查:即使skip_python_side为 true,如果DIAGRAM_ASSETS_RESULT存在且非success/skipped,仍报告失败;或 - 至少在此 PR body 或 CI workflow 中添加注释,说明 diagram assets gate 与现有 skip 行为一致但未来可能需要独立加固
验证命令:
# 检查 template_suite_gate 中的 diagram_assets 检查是否受 skip_python_side 保护
git show 62a4bf5d:.github/workflows/test.yml | grep -A5 'diagram_assets != .success'M-2: _DANGEROUS_URI_SCHEME_RE 中 https? 缺少说明注释
文件: pyfcstm/diagram/engine.py line 335
_DANGEROUS_URI_SCHEME_RE = re.compile(
r"(?:javascript|vbscript|data|blob|about|file|https?|ftp|ws|wss|"
r"chrome|chrome-extension):",
re.IGNORECASE,
)https? 匹配所有 HTTP/HTTPS URL,但当前被属性 allowlist 间接保护(xmlns 不在 allowlist 中,其他属性值含 http:// 也会命中此 regex 被拒绝)。当前实现正确,但 regex 视觉上会误伤合法 SVG 属性如 xmlns="http://www.w3.org/2000/svg",让人疑惑为什么 SVG 还能工作。
建议: 在 regex 定义上方添加注释:
# https? is safe here because the xmlns attribute (the only SVG attribute
# that embeds http://www.w3.org/2000/svg) is excluded from every per-element
# attribute allowlist. This scheme check guards against future allowlist
# additions that could accidentally admit executable or exfiltrating URIs.M-3: 字体路径在 engine.py 与 check_diagram_assets.py 中重复维护
文件:
pyfcstm/diagram/engine.pylines 114-129(_LATIN_FONT_ASSET_PATHS、_CJK_FONT_ASSET_PATHS)tools/check_diagram_assets.pylines 33-54(EXPECTED_FONT_PATHS)
两组 13 个字体路径完全重复。如果新增字体(如未来加入 NotoSansMonoCJK),需同步修改两处,容易遗漏。
建议: 从 asset-lock.json 的 fonts.faces[].path 动态推导,或抽取 pyfcstm/diagram/_font_paths.py 共享常量。
M-4: _valid_opentype 全字体 checksum 计算缺少行内注释
文件: pyfcstm/diagram/engine.py lines 796-808
total_checksum = (
sum(word[0] for word in struct.iter_unpack(">I", data)) & 0xFFFFFFFF
)
return total_checksum == 0xB1B0AFBA此处的 total_checksum 使用 data 的完整字节(包括 head.checkSumAdjustment 字段的原始值,因为在 table-level loop 中 zero-fill 的是 table_data 的副本)。OpenType 规范要求全字体校验和 == 0xB1B0AFBA 的前提正是 checkSumAdjustment 字段已被字体构建工具设为能令总和为此魔数的值。
当前实现正确,但注释仅说明 "Table-level checksums alone cannot detect a damaged adjustment field",未解释为什么此处可以直接使用原始 data 而不需要手动 zero-fill head.checkSumAdjustment。
建议: 在 line 807 前添加注释:
# The font builder sets head.checkSumAdjustment so that the total
# checksum (including the adjustment itself) equals this magic value.
# Table-level zero-fill (above) was for per-table verification only.未发现问题(已验证安全)
以下区域经过白盒逐行审计,确认为正确实现:
| 区域 | 验证方法 |
|---|---|
base64 padding 边界攻击(_MAX_RENDER_PNG_BASE64_BYTES) |
手动验证 %3==0/1/2 三种场景 + test 覆盖 |
| PNG chunk CRC + IHDR/IDAT/IEND 合同 | 代码审查 + test_render_png_rejects_* × 6 |
| closed SVG input/output element/attribute allowlist | 白盒审计 + 构造 <script>/<use>/onload/href/style/javascript: 均被拒绝 |
| Reference tar symlink/hardlink/path traversal | 白盒审计 _safe_extract + 验证 ./../绝对路径 |
| 下载重试(IncompleteRead + transient HTTP + 退避) | 代码审查 + _check_download_retry self-check |
esbuild provenance(lockfile + --no-install) |
代码审查 + validate_esbuild_provenance |
| CJK locale 切换 context 重建 | test_locale_switch_rebuilds_context_for_each_font_pair |
| MiniRacer timeout/context 生命周期 | test_engine_restarts_context_after_native_timeout 等 5 个 timeout 测试 |
marker-end + orient="auto" + refX="10" + target-border endpoint |
test_canonical_svg_validator_accepts_renderer_output_and_marker_contract |
broad except Exception 合规 |
逐个审计:仅 _eval 使用 isinstance guard |
总评
代码质量高。PNG 链路(CRC、chunk 合同、padding 窗口)、SVG 方言(三层 allowlist + URI scheme + local reference)、字体 OpenType 校验、CJK locale 切换、timeout/context 生命周期、下载重试、esbuild provenance 均实现扎实。68 个测试 + 3 个 maintenance check 全部通过。
C=0 / I=2 / M=4。2 个 Important 是维护一致性问题(死代码 + 两个 PNG parser 验证深度不对称),不影响当前正确性。4 个 Minor 是文档/注释/维护性建议。
结论: 满足 ready-to-merge 条件中的 C/I 要求(C=0,I 无阻塞性安全问题)。建议 I-1(删除 _valid_png 死代码)在后续清理 PR 中处理。
审查于 commit 62a4bf5d739fca4932fb691815ba530c48fd3fc6;68 个 diagram 测试通过、provenance/engine floor/rendering self-check 通过。
- remove the unused boolean PNG validator and test the typed decoder contract\n- reject malformed parity PNG structure before ink decoding\n- add maintenance self-checks for ordering, chunk, and trailing-byte failures
Claude reviewer 最终审查 — head 3ea1a34审查范围与方法
3ea1a34 是最后一个提交,两处关键改动:
C(Critical, 阻塞 merge)无。 I(Important, 建议处理但不阻塞)无。 本轮审查未发现新的功能/安全问题。上一轮 reviewer 指出的两个 Important( M(Minor, 建议)M-1
|
| 场景 | 结果 |
|---|---|
| valid RGBA8 1×1 | OK |
| bad signature | ValueError: lacks PNG signature |
| SIG only | ValueError: missing IHDR/IDAT/IEND |
| missing IEND | ValueError: missing IHDR/IDAT/IEND |
| trailing bytes after IEND | ValueError: trailing bytes after IEND |
| duplicate IHDR | ValueError: duplicate/misplaced IHDR |
| IDAT before IHDR | ValueError: non-IHDR first chunk |
| tEXt ancillary chunk | ValueError: unsupported ancillary chunk |
| bad CRC | ValueError: invalid chunk checksum |
| non-contiguous IDAT | ValueError: trailing bytes after IEND(因为 _PNG_ALLOWED_CHUNKS 仅 3 个,间隔只能是 IEND) |
| IDAT + IEND + IEND | ValueError: trailing bytes after IEND |
| zero dimensions | ValueError: non-positive dimensions |
| interlaced | ValueError: violates RGBA8 size contract |
3. _png_ink_bbox(parity checker)对抗输入(6 个)
python -c "sys.path.insert(0,'tools'); from check_diagram_rendering import _png_ink_bbox; ..."全 6 项按预期抛:valid → OK;trailing / dup_ihdr / tEXt / idat_before_ihdr / non_contig_idat 全部 raise ValueError。
对比修改前 parity checker 只有 CRC + IEND + IHDR-长度 检查,本次补齐后与运行时 _decode_png_rgba 结构合同对齐。maintenance self-check _self_check_png_parser 也覆盖 valid + 4 个反例。
4. tools/check_diagram_rendering.py --check 通过
diagram rendering checker: provenance, parity, and PNG self-check passed
5. pytest 全绿
pytest test/diagram/test_assets.py -q → 68 passed in 38.88s
make test_boundary_check → test boundary check passed
6. 其它必查项(继承上轮 review、快速回读)
| 区域 | 结果 |
|---|---|
| resvg 2.6.2 lock(tarball/binding/wasm SHA-256 + MPL-2.0 + upstream 双 commit) | tools/diagram_assets/asset-lock.json 完整;builder 会校验 |
esbuild 0.28.1 lock + --no-install |
editors/jsfcstm/package.json 已锁定为 devDependency;build 从 jsfcstm 工作目录解析本地 binary |
| reference archive path-traversal / symlink | _safe_extract 逐 member 拒绝 sym/hard/非普通 + 绝对路径 resolve 校验;13 项对抗 tar 由测试覆盖 |
| marker-end / refX=10 / orient=auto | engine.py:463-467 硬编码断言 |
| PNG base64 padding 边界 | engine.py:1404-1416 %3 padding 检查完整 |
| 5 CJK locale (SC/TC/HK/JP/KR) Regular+Bold | asset-lock.json fonts.faces 10 face 覆盖 |
| py-mini-racer / mini-racer split | requirements-viz.txt python_version < "3.8" / >= "3.8" |
是否可以 merge
可以合入 umbrella dev/python-diagram-umbrella。
理由:
- C=0, I=0, M=4;M 全部是可延后处理的建议
- 3ea1a34 是等价重构 + parity checker 收紧,对抗输入实测 13+6 项全部按预期拒绝,无静默回归
- 上一轮 reviewer 指出的 I-1/I-2 在本 commit 全部关闭
- 68 diagram + boundary check + rendering self-check 全绿
注意事项(不阻塞):
- 本 PR 仅合入 umbrella,最终并入
main前仍需等 umbrella 三路 review 与全 required checks - CI 目前尚未在此 head 全跑(若 required checks 未 green,仍要按 PR body 的 ready-to-merge 停止条件走)
- M-2(fetch_diagram_reference 无 retry)建议在后续清理 PR 处理
审查基准:head 3ea1a343;本地 venv Python 3.11;未修改任何被审代码。
DeepSeek 强对抗白盒审查 — PR #386 (head 3ea1a34)审查人: DeepSeek reviewer 总体判断:通过,无阻塞性问题本次 PR 的核心变更——删除 [I-1]
|
| 缺失测试用例 | _decode_png_rgba 拒绝 |
_png_ink_bbox 拒绝 |
|---|---|---|
| non-zero-length IEND | ✔ PNG output has an invalid IEND |
✔ PNG output has an invalid IEND |
| non-contiguous IDAT(IDAT → 非 IDAT → IDAT) | ✔ 被 chunk-type 拒绝 | ✔ 被 chunk-type 拒绝 |
| IDAT after IEND | ✔ trailing bytes after IEND |
✔ trailing bytes after IEND |
验证命令:
import struct, zlib
from pyfcstm.diagram.engine import _decode_png_rgba
from tools.check_diagram_rendering import _png_ink_bbox
def chunk(kind, payload):
crc = zlib.crc32(kind + payload) & 0xFFFFFFFF
return struct.pack(">I", len(payload)) + kind + payload + struct.pack(">I", crc)
sig = b"\x89PNG\r\n\x1a\n"
hdr = struct.pack(">IIBBBBB", 1, 1, 8, 6, 0, 0, 0)
img = chunk(b"IDAT", zlib.compress(b"\x00\x00\x00\x00\xff"))
# IEND with non-zero payload
nonzero_iend = sig + chunk(b"IHDR", hdr) + img + chunk(b"IEND", b"x")
_decode_png_rgba(nonzero_iend) # raises ValueError
_png_ink_bbox(nonzero_iend) # raises ValueError
# But _self_check_png_parser does NOT test this case.建议: 在 _self_check_png_parser 的 invalid_cases 元组中添加这些用例,使其与 PR 声明 "keep parity PNG structure checks aligned" 一致。
[M-1] _decode_png_rgba IHDR 错误消息将三个不同失败合并为一
文件: pyfcstm/diagram/engine.py:940(行号可能因代码前后偏移 ±5 行)
位置: if saw_ihdr or position != 8 or length != 13: raise ValueError("PNG output has duplicate or misplaced IHDR")
问题: 条件 length != 13 触发时,错误的实际原因是 "IHDR 长度不是 13 字节",但消息说 "duplicate or misplaced IHDR",误导诊断。对比 parity parser(_png_ink_bbox)正确地将 IHDR 长度检查分离出来:
# Parity parser — 正确分离:
if saw_ihdr or chunk_start != 8:
raise ValueError("PNG output has duplicate or misplaced IHDR")
if len(payload) != 13:
raise ValueError("PNG output has an invalid IHDR")
# Runtime decoder — 三合一:
if saw_ihdr or position != 8 or length != 13:
raise ValueError("PNG output has duplicate or misplaced IHDR")建议: 将 length != 13 分离为独立检查,以匹配 parity parser 的粒度。
[M-2] 缺少 _png_ink_bbox ↔ _decode_png_rgba 的跨验证
文件: tools/check_diagram_rendering.py:437
影响: _self_check_png_parser 仅验证 _png_ink_bbox 的自洽性(接受已知 valid、拒绝 known-malformed),不验证它与 _decode_png_rgba 对任意输入的输出一致。如果其中一个无意中偏离,自检不会捕获。
验证命令:
cd /home/zhangshaoang/oo-projects/pyfcstm && python -c "
from pyfcstm.diagram.engine import _decode_png_rgba
from tools.check_diagram_rendering import _png_ink_bbox
# 无现成跨验证 — 两个解析器独立演化
"建议: 添加一个跨验证测试,至少在 valid 1×1 RGBA PNG 上确保两个解析器输出一致。不需要覆盖所有 malformed 路径(因为两者是独立维护面),但 valid 路径的一致性应被 enforce。
[I-2] make build_assets fast-path 仅检查文件存在而非完整性
文件: tools/build_diagram_assets.py:132-148
位置: _install_lockfile_packages 的 early-return 条件
if (
ELK_API_PATH.is_file()
and ELK_WORKER_PATH.is_file()
and (RESVG_PACKAGE_DIR / "index.min.js").is_file()
...
and (JSFCSTM_DIR / "node_modules" / "esbuild").is_dir()
):
return # <-- fast path: 仅检查文件存在如果 node_modules 包含错误版本的文件(例如旧版 esbuild、损坏的 WASM),fast-path 会跳过 npm ci,然后 build_renderer 会用错误工具链构建。最终 SHA-256 检查会在 make diagram_assets_check 中捕获,但错误消息可能让人困惑("hash mismatch" 而非 "stale node_modules")。
建议: 在 fast-path 条件中添加至少一个关键文件的 SHA-256 检查(如 resvg.wasm),或者添加文档说明需要先 make tpl_clean 后再重建。
已验证通过的安全/正确性检查(全部 PASS)
以下所有用户指定的审查面均通过对抗验证,无发现问题:
| 审查面 | 验证方式 | 结果 |
|---|---|---|
| IHDR 首位/唯一性 | 手工构造 duplicate IHDR / non-first IHDR,执行 engine + parity 解析器 | ✔ 正确拒绝 |
| IDAT 连续性 | non-contiguous IDAT(tEXt 间隙)/ IDAT-after-IEND | ✔ 正确拒绝 |
| IEND 尾随字节 | valid PNG + trailing bytes | ✔ 正确拒绝 |
| 允许 chunk | tEXt (ancillary), sRGB (ancillary), TEXT (invalid ASCII), PLTE, gAMA, pHYs | ✔ 全部正确拒绝 |
| chunk type 字节校验 | 非 ASCII chunk type (\x01\x02\x03\x04), \x80\x00\x00\x00 | ✔ 正确拒绝 |
| chunk checksum | 故意错误 CRC | ✔ 正确拒绝 |
| PNG 尺寸/维度限制 | 1×1 valid, zero-dimension IHDR, huge length overflow | ✔ 边界正确 |
| _valid_png 删除 | grep -rn _valid_png → 零引用 |
✔ 完全删除 |
| Self-check 通过 | python tools/check_diagram_rendering.py --check |
✔ PASS |
| Reference tar link/path traversal | 代码审查 _safe_extract: 拒绝 symlink/硬链/路径穿越/非文件成员 |
✔ 安全 |
| Reference 自引用 | reference-lock.json 使用 immutable Gist SHA |
✔ 不可变 |
| IncompleteRead/5xx 重试 | download_locked 三次有界重试,永久 4xx 立即失败 |
✔ 正确 |
| esbuild lockfile 安装 | npm ci + npx --no-install 从 jsfcstm 目录解析 |
✔ 锁定 |
| esbuild provenance 检查 | validate_esbuild_provenance 交叉检查 asset-lock ↔ package-lock |
✔ 锁定 |
| CI skip/gate 绕过 | diagram_assets 在 template-suite-gate 的 needs 中,仅 ci skip/[python skip] 显式跳过 |
✔ 无绕过 |
| CI gate 漏检 | run_expensive_code_test=false 时跳过 gate — 但 pyfcstm/diagram/** 匹配 _CODE_PATTERNS 触发 true |
✔ 正确 |
| CJK 字体 | 13 字体 SC/TC/HK/JP/KR Regular/Bold + SHA-256 锁 + 大小预算 | ✔ 完整 |
| Arrow marker-end | inspect_arrows tipError/tolerance, terminalLength < 18.0, rect/pseudo-exit 双容差 |
✔ 验证 |
| MiniRacer timeout/OOM | JSTimeoutException/JSOOMException → _discard_context → context close + _DiagramEvaluationInterruptedError |
✔ 正确 |
| MiniRacer 双安装检测 | DiagramEngineConflictError 拒绝 mini-racer + py-mini-racer 共存 |
✔ 正确 |
| Package old-path 拒绝 | check_diagram_package.py LEGACY_PATH_PREFIXES 拒绝 pyfcstm/assets / pyfcstm/diagram_runtime |
✔ 正确 |
| Package fresh install | check_diagram_package.py wheel/sdist + SHA-256 全量验证 |
✔ 通过 |
| SVG allow-list | DOCTYPE/ENTITY/PI/script 拒绝 + element/attribute allow-list + depth/element/byte 限制 | ✔ 安全 |
| Test suite | pytest test/diagram/test_assets.py -q → 68 passed in 39.53s |
✔ 通过 |
| Boundary check | make test_boundary_check → passed |
✔ 通过 |
结论
PR #386 的 PNG 校验对齐(3ea1a343)是正确且安全的改进。_valid_png 删除无遗留引用,_decode_png_rgba / _png_ink_bbox 均正确拒绝所有 20+ 种 malformed PNG 结构。发现的所有问题是自检覆盖缺口(I-1)、错误消息精度(M-1)、跨验证缺失(M-2)和 fast-path 深度不足(I-2),均为非阻塞性质。建议在后续 PR 中补充 I-1 的 self-check 用例,其余可在维护周期内修复。
最终判断: ✅ APPROVED — 无阻塞性问题,可 merge。
codex reviewer\n\n这是针对 PR #386 精确 head 3ea1a34 的最终独立强对抗复核;base 为 bcadb92。范围覆盖 official resvg 2.6.2 provenance/license/lock、esbuild、reference archive/retry、closed SVG/raw input、箭头/CJK、PNG/limits、MiniRacer timeout/context、package/CI/pydoc 与测试边界。\n\n### 结论\n\nC=0 / I=0 / M=6。 本次删除 _valid_png 并将 parity PNG 检查改为严格结构解析后,没有发现阻断级正确性、安全性或范围问题。官方资产锁、source/license 回读、request-only/closed SVG、PNG 生产解码、双 MiniRacer floor、箭头/CJK、reference/package/CI 门禁均有可复现证据。\n\n### C / I\n\n- C=0。 没有致命矛盾;PR-B 仍限定为内部资产运行时,不提前宣称公共 Diagram API、CLI、HTML/Notebook 或 PDF facade 完成。\n- I=0。 3ea 的 parity parser 能拒绝错误 chunk、IHDR 顺序、非连续 IDAT、缺失/错误 IEND、尾随数据和坏 CRC;生产引擎继续使用 _decode_png_rgba 做完整 RGBA PNG 校验。未发现新的阻断回归。\n\n### M\n\n1. Parity PNG limit drift。 tools/check_diagram_rendering.py::_png_ink_bbox() 未复用生产的最大 PNG 字节数、最大维度、最大像素数和最大 RGBA 字节数。真实负例:构造合法 16385x1 RGBA PNG 时,pyfcstm.diagram.engine._decode_png_rgba 抛 DiagramRenderLimitError,而 parity parser 返回 (16385, 1)。当前 renderer 输出受生产入口限制,故不阻塞,但 parser 声称与生产结构合同对齐并不完整。\n2. 伞 PR #383 current-state 过期。 正文仍把 PR-B head 写为 98fd215,实际 head 为 3ea1a34;历史 hash 可保留,但需追加 current-state 同步。\n3. PR #386 body current-state 过期。 body 的当前 head/最新修复仍写 62a4bf5,未同步 3ea1a34。\n4. esbuild 已安装树快路径无字节回读。 validate_esbuild_provenance() 校验 package-lock 的 version/resolved/integrity,但 ensure_js_dependencies() 发现 node_modules/esbuild 目录后不核验已安装文件;fresh CI npm ci 覆盖主要路径,后续可与 ELK tree SHA 同等级加固。\n5. reference Gist 下载无瞬态重试。 tools/fetch_diagram_reference.py::_download 只有一次 120 秒请求;Gist 的短暂 5xx/网络中断会直接失败 required CI,建议复用资产下载的有界重试。\n6. expanded SVG 错误后的 context 复位不对称。 expand_svg() 对 malformed/closed-dialect 输出映射为 DiagramRenderError,但没有像 render_svg() 一样 discard context;建议补充复位或明确保持上下文的理由。\n\n### 上游/范围一致性\n\n- PR-A #384 已合入 umbrella,merge commit 8a6eba7;PR #377 已合入 main,merge commit 05c4156。\n- issue #89 仍 OPEN,issue #376 已 CLOSED;#383 仍 OPEN 且明确 PR-C/D 尚待实施。\n- #386 body 的 official 2.6.2、source/license、双 runtime、reference/package 所有权与当前实现一致,只有上述 current-state hash 需要同步。\n\n### 可复现证据\n\n本地 HEAD=3ea1a343:\n\n python -m pytest -q test/diagram/test_assets.py\n 68 passed\n python tools/check_diagram_rendering.py --check\n diagram rendering checker: provenance, parity, and PNG self-check passed\n python tools/build_diagram_assets.py --check\n diagram asset builder: deterministic and safety self-check passed\n ruff check ... / ruff format --check ...\n passed\n make test_boundary_check\n passed\n make PYTHON=/home/zhangshaoang/.pyenv/versions/3.11.1/bin/python \n DIAGRAM_REFERENCE=/tmp/pr386-final-reference/reference.json diagram_assets_verify\n 35 layouts, 306 arrows; CJK, expanded parity, memory, runtime floor and provenance passed\n python tools/check_diagram_provenance.py\n npm tarball/source archive SHA-256 values matched the lock\n python tools/check_diagram_package.py --check\n adversarial self-check passed\n\n真实 PNG limit probe:\n\n _decode_png_rgba(16385x1 PNG) -> DiagramRenderLimitError\n _png_ink_bbox(16385x1 PNG) -> (16385, 1)\n\n远端在评论时 Diagram assets Python 3.11 已通过,Python 3.7/3.8 与其余 Code Test/Docs/Package required jobs 仍在运行队列;因此不把未完成 CI 宣称为通过。审查完成,不执行 merge。 |
- validate an existing esbuild tree before reusing it\n- cover additional malformed PNG structures in the maintenance self-check\n- share runtime PNG size limits with parity validation
Claude reviewer 最终强对抗白盒审查(head 710ee26)审查基线: 验证方式(全部亲自执行):
C(critical / 阻断):无I(important / 需处理):无M(minor / 建议 follow-up,不阻断 merge)
与 PR body 已声明范围一致性
结论
以上审查为 Claude reviewer 独立完成,基于 710ee26,未修改代码,未执行 merge。 |
codex reviewer\n\n这是针对 PR #386 精确 head 710ee26 的最终独立强对抗复核;base 为 bcadb92。范围覆盖 official resvg 2.6.2 provenance/license/lock、esbuild、reference archive/retry、closed SVG/raw input、箭头/CJK、PNG/limits、MiniRacer timeout/context、package/CI/pydoc 与测试边界。\n\n### 结论\n\nC=0 / I=0 / M=5。 本次新增的 esbuild 快路径校验、PNG 限制复用和 parity self-check 均通过;未发现阻断级正确性、安全性或范围问题。官方资产/source/license 回读、request-only/closed SVG、PNG 生产解码、双 MiniRacer floor、箭头/CJK、reference/package/CI 门禁均有证据。\n\n### C / I\n\n- C=0。 没有致命矛盾;PR-B 仍限定为内部资产运行时,不提前宣称公共 Diagram API、CLI、HTML/Notebook 或 PDF facade 完成。\n- I=0。 esbuild 已安装树会校验 package.json/name/version、wrapper 存在且 npx --no-install esbuild --version 与锁定版本一致;parity parser 复用了生产 PNG byte/dimension/pixel/RGBA limits,并覆盖非法 IEND、尾随 IDAT、未知/非连续 chunk 和 runtime/parity valid cross-check。未发现新的阻断回归。\n\n### M\n\n1. Parity PNG 解压后扫描线限制仍有轻微漂移。 tools/check_diagram_rendering.py::_png_ink_bbox() 已同步压缩文件字节、IHDR 维度/像素/RGBA limits,但没有同步生产 _decode_png_rgba() 在 zlib.decompress 后对 decoded scanlines 执行的 _MAX_RENDER_RGBA_BYTES 上限。恶意压缩流仍可能先展开较大内存再因 scanline 长度失败;当前 reference/renderer 受可信来源与生产入口限制,故不阻塞。\n2. esbuild 已安装树仍未做 integrity/字节回读,也没有版本探测 timeout。 快路径验证 package-lock metadata 与可执行版本,但没有把已安装 wrapper/binary 与 integrity 摘要逐字节核对;被篡改 wrapper 也可能长时间阻塞 --version。fresh CI npm ci 覆盖主要路径,建议后续继续加固。\n3. 伞 PR #383 current-state 过期。 正文仍把 PR-B head 写为 98fd215,实际 head 为 710ee26;历史 hash 可保留,但需追加 current-state 同步。\n4. PR #386 body current-state 过期。 body 的当前 head/最新修复仍写 62a4bf5,未同步 710ee26。\n5. reference Gist 下载与 expanded SVG 失败复位仍可加固。 fetch_diagram_reference.py::_download 只有一次 120 秒请求,短暂 5xx/网络中断会直接失败 required CI;同时 expand_svg() 对 malformed/closed-dialect 输出映射为 DiagramRenderError,但没有像 render_svg() 一样 discard context。两者均不阻塞当前可信输入路径。\n\n### 上游/范围一致性\n\n- PR-A #384 已合入 umbrella,merge commit 8a6eba7;PR #377 已合入 main,merge commit 05c4156。\n- issue #89 仍 OPEN,issue #376 已 CLOSED;#383 仍 OPEN 且明确 PR-C/D 尚待实施。\n- #386 body 的 official 2.6.2、source/license、双 runtime、reference/package 所有权与当前实现一致,只有 current-state hash 需要同步。\n\n### 可复现证据\n\n本地 HEAD=710ee261:\n\n python -m pytest -q test/diagram/test_assets.py\n 68 passed\n python tools/check_diagram_rendering.py --check\n diagram rendering checker: provenance, parity, and PNG self-check passed\n python tools/build_diagram_assets.py --check\n diagram asset builder: deterministic and safety self-check passed\n ruff check ... / ruff format --check ...\n passed\n make test_boundary_check\n passed\n make PYTHON=/home/zhangshaoang/.pyenv/versions/3.11.1/bin/python \n DIAGRAM_REFERENCE=/tmp/pr386-final-reference/reference.json diagram_assets_verify\n 35 layouts, 306 arrows; CJK, expanded parity, memory, runtime floor and provenance passed\n python tools/check_diagram_provenance.py\n npm tarball/source archive SHA-256 values matched the lock\n python tools/check_diagram_package.py --check\n adversarial self-check passed\n\n真实负例:\n\n installed_esbuild_usable=True\n npx --no-install esbuild --version -> 0.28.1\n tampered package.json version -> _installed_esbuild_is_usable() False\n nonzero IEND -> ValueError\n IDAT after IEND -> ValueError\n unknown/non-contiguous separator -> ValueError\n 16385x1 PNG -> ValueError: PNG output exceeds the bounded dimension limit\n\n远端在评论时没有观察到 FAILURE/CANCELLED;Diagram assets Python 3.11 已通过,其余新 head required jobs 仍在运行或排队,因此不把未完成 CI 宣称为通过。审查完成,不执行 merge。 |
DeepSeek Reviewer — PR #386 最终强对抗白盒审查审查 head: 审查方法论对以下每个领域进行了白盒路径追踪 + 手动对抗构造:
C (Critical) — 0 项无关键问题。 I (Important) — 3 项I1. Self-check 未覆盖 CRC 校验码验证分支位置: 验证: 手动构造 CRC 损坏的 PNG 确认运行时 parser 确实拒绝: python3 -c "
from pyfcstm.diagram.engine import _decode_png_rgba
import struct, zlib
def bad_crc_chunk(kind, payload):
checksum = (zlib.crc32(kind + payload) ^ 0xFF) & 0xFFFFFFFF
return struct.pack('>I', len(payload)) + kind + payload + struct.pack('>I', checksum)
signature = b'\x89PNG\r\n\x1a\n'
header = struct.pack('>IIBBBBB', 1, 1, 8, 6, 0, 0, 0)
image = bad_crc_chunk(b'IDAT', zlib.compress(b'\x00\x00\x00\x00\xff'))
# CRC mismatch on IHDR
bad = signature + bad_crc_chunk(b'IHDR', header) + image + bad_crc_chunk(b'IEND', b'')
_decode_png_rgba(bad) # ValueError: PNG output contains an invalid chunk checksum
"
# Output: ValueError: PNG output contains an invalid chunk checksum运行时拒绝正确。但 self-check 缺此覆盖。 建议: 在 I2. fetch_diagram_reference.py 缺少瞬断重试逻辑位置: 对比: # build_diagram_assets.py — 有重试
_DOWNLOAD_ATTEMPTS = 3
_DOWNLOAD_BACKOFF_SECONDS = 1.0
_TRANSIENT_HTTP_CODES = frozenset({408, 429, 500, 502, 503, 504})
# fetch_diagram_reference.py — 无重试
def _download(url: str) -> bytes:
request = Request(url, headers={"User-Agent": "pyfcstm-diagram-reference/1"})
try:
with urlopen(request, timeout=120) as response:
return response.read()
except (HTTPError, URLError, OSError) as err:
raise RuntimeError("unable to download diagram reference archive") from err影响: 低。CI job 本身会重试整个 step,本地维护者可以手动重试。但一致性不佳。 建议: 对齐 I3. reference-lock.json 依赖 GitHub Gist 而非 immutable release artifact位置: 当前双重锁定: {
"3.8": {
"encoding": "base64",
"sha256": "67fd4394ade6c01f94379e6d2c0bd2d864833ed8bb60bbdf2a7b3a5e5fb850e2",
"url": "https://gist.githubusercontent.com/HansBug/b43d202ccc9436cada8bc57fa3763450/raw/dbe7c73645bb30f4650a65b3556023c8c6982693/reference-3.8.tar.gz.b64"
}
}影响: 低。SHA256 校验确保内容完整性,且该参考仅用于 CI parity gate,不进入生产路径。即使 Gist 不可用,CI job 会失败而非静默通过。 建议: 长期可考虑将参考归档到 M (Minor) — 4 项M1. Self-check 仅测试最小 PNG (1×1, RGBA, filter 0)位置: 验证: 手动确认运行时拒绝 unsupported filter: python3 -c "
from pyfcstm.diagram.engine import _decode_png_rgba
import struct, zlib
def chunk(kind, payload):
checksum = zlib.crc32(kind + payload) & 0xFFFFFFFF
return struct.pack('>I', len(payload)) + kind + payload + struct.pack('>I', checksum)
header = struct.pack('>IIBBBBB', 1, 1, 8, 6, 0, 0, 0)
signature = b'\x89PNG\r\n\x1a\n'
# filter type 5 is unsupported
image = chunk(b'IDAT', zlib.compress(b'\x05\x00\x00\x00\xff'))
bad = signature + chunk(b'IHDR', header) + image + chunk(b'IEND', b'')
_decode_png_rgba(bad) # ValueError: PNG output uses an unsupported filter
"
# Output: ValueError: PNG output uses an unsupported filter ✓建议: 可以在 self-check 中添加一个 filter-4 (Paeth) 的 case 或一个 zlib-corrupted IDAT case,但不阻塞。 M2. pydoc 示例引用未实现的公开 API位置: M3.
|
| 领域 | 验证方法 | 结果 |
|---|---|---|
| esbuild 快路径 | 路径追踪所有 corrupt/stale 场景 | ✅ 正确 fallback 到 npm ci |
| PNG chunk 白名单 (IEND/IDAT) | 手动构造恶意 PNG + 运行时验证 | ✅ 正确拒绝所有非法 chunk |
| Closed SVG 合同 | 代码审查 allow-list + marker 合同 | ✅ 严密 |
| CJK font 注册 & locale 切换 | 68 个 pytest 全部通过 | ✅ 5 个 locale 正确渲染 |
| Arrow endpoint (marker-end) | test_rendered_a_to_b_resvg_tip_meets_target_border |
✅ 通过 |
| MiniRacer timeout/context 生命周期 | test_engine_restarts_context_after_native_timeout |
✅ 通过 |
| Package checker 旧路径拒绝 | check_diagram_package.py LEGACY_PATH_PREFIXES |
✅ 正确拒绝 pyfcstm/assets 和 pyfcstm/diagram_runtime |
| Official resvg 2.6.2 provenance | tools/check_diagram_provenance.py --skip-tarball |
✅ source archive SHA256 匹配 |
| Reference archive 提取安全 | _safe_extract 拒绝 symlink/hardlink/path traversal |
✅ 严密 |
| Base64 padding 边界 | 数学分析 + 手动测试 | ✅ 无溢出窗口 |
| Self-check 运行 | python3 tools/build_diagram_assets.py --check + tools/check_diagram_rendering.py --check |
✅ 通过 |
| Diagram rendering parity | tools/check_diagram_rendering.py --check |
✅ 通过 |
| Engine floor (timeout reset) | tools/check_diagram_engine_floor.py --all-cases --check-timeout-reset |
✅ 通过 (15 cases, SVG+PNG+expanded SVG) |
总结
| 等级 | 数量 | 说明 |
|---|---|---|
| C (Critical) | 0 | — |
| I (Important) | 3 | Self-check CRC 覆盖缺口、fetch retry 不一致、Gist 依赖 |
| M (Minor) | 4 | Self-check 最小 PNG 限制、串行检查、数学恒真式、pydoc (无实际缺陷) |
结论: PR #386 head 710ee261 在审查范围内无关键缺陷。3 个 Important 项均为防御性改进建议(self-check 覆盖率、retry 一致性、长期 immutable storage),不阻塞合入。所有核心安全边界(PNG/SVG 合同、esbuild 锁定、字体打包、MiniRacer 生命周期、provenance 溯源)均已正确实现并通过验证。
推荐: Ready-to-merge 到 dev/python-diagram-umbrella。
DeepSeek Reviewer 通过 codex-deepseek exec 执行,审查时长 ~12 分钟
claude reviewer — 最终强对抗审查(head
|
| 项 | 结果 |
|---|---|
@resvg/resvg-wasm@2.6.2 provenance/license/hash |
pyfcstm/diagram/assets/resvg-binding.js SHA256 = 590115ae25dead0d688da192f2d31586cdf1f8c70fe294919419c168e03e5c42,resvg.wasm SHA256 = 22bf6e9f9a100d972da0411a69c5ba504367fc1fa87b3b64e3f35e53926d2d70,与 tools/diagram_assets/asset-lock.json 的 bindingSha256/wasmSha256 精确一致;MPL-2.0/EPL-2.0/OFL-1.1 三份 LICENSE 与 NOTICE 齐备并覆盖 elkjs、resvg-wasm、JetBrains Mono、Noto Sans SC/TC/HK/JP/KR |
| Closed SVG allow-list | 用 11 组 hostile 输入(<?xml, <!DOCTYPE, <!ENTITY, <script>, onclick, <a href="javascript:">, style, <image href="data:">, xlink:href, url(https://…), url(#missing))全部被 DiagramAssetError 拒绝,marker orient!="auto" 与 refX!="10" 各自命中显式契约错误 |
| Raw SVG 拒绝 | render_png("<svg …/>") / expand_svg("<svg …/>") 命中 ValueError: render_png/expand_svg require a DiagramData request; raw SVG input is not supported |
| PNG 边界 | 手工构造覆盖 duplicate/misplaced IHDR、无 IDAT、IEND 长度非 0、trailing bytes、CRC 破坏、unknown critical/ancillary chunk、scanline mismatch、纯白无 ink、bit_depth/color_type 非 RGBA8、base64 上限;_decode_png_rgba 全部拒绝,有 ink 的 1×1 通过 |
| Base64 encoded PNG 上限 | 在 base64.b64decode 之前即命中 _MAX_RENDER_PNG_BASE64_BYTES 及 padding-窗口检查,多一字节窗口路径被覆盖 |
| Scale/canvas/pixel/RGBA 上限 | scale=0/-1/NaN/inf/True/4.1 均被 ValueError/DiagramRenderLimitError 拒绝;_checked_render_dimensions 计算与实际 PNG 尺寸精确一致 |
| Marker geometry | _validate_svg_tree 强制单 marker、orient="auto"、refX="10"、拒绝 marker-start,与 arrow 端点检查(tools/check_diagram_rendering.py tipError/normalCosine)匹配;make diagram_parity_check 使用 35/306 corpus |
| CJK 五地区 | _CJK_FONT_ASSET_PATHS/_CJK_FONT_FAMILIES 覆盖 sc/tc/hk/jp/kr Regular+Bold;每次切换 locale 会 _discard_context(),避免 WASM heap 中残留其他 face |
| Dual MiniRacer runtime | mini-racer 与 py-mini-racer 同装时 DiagramEngineConflictError;timeout_sec vs 毫秒的模式切换用 inspect.signature(MiniRacer.eval).parameters 检测;Python 3.8 CI 强制回落到 mini-racer==0.7.0 |
| Timeout/OOM/discard/fail-loud | _DiagramEvaluationInterruptedError 触发 _discard_context() 并按 request 分流为 DiagramRenderError 或 DiagramAssetError;render_svg deadline 到达后同样 discard;本地 python tools/check_diagram_engine_floor.py --all-cases --check-timeout-reset 通过(15 case × {svg,png,expanded-svg},timeout 复位路径正常) |
| Provenance 网络门禁 | tools/check_diagram_provenance.py 校验 npm tarball、resvg-js commit 归档、patched resvg 归档、LICENSE/Cargo.toml/patched-resvg/LICENSE.txt 三份 SHA256;下载器 60s 超时;_download_archive 支持 codeload 与 archive 双端点 |
| Reference fetch 重试 | tools/fetch_diagram_reference.py --check 通过 IncompleteRead → OSError → Response 三阶段 flaky、单次 503 → success、404 立即失败三条自检;_safe_extract 拒绝 symlink/hardlink/非普通成员及 path traversal |
| Make/CI 硬门禁 | make unittest/make template_unittest/make package 均依赖 build_assets diagram_assets_check tpl;Diagram assets (Python 3.7/3.8/3.11) 是 template-suite-gate 的 needs 项之一,diagram_assets_verify = diagram_assets_check + diagram_rendering_check + diagram_parity_check + diagram_engine_floor + diagram_provenance_check + diagram_reference_check;Fresh wheel/sdist self-check 全平台通过 |
| 测试边界 | test/diagram/test_assets.py 仅使用 pytest + 公共 pyfcstm.diagram API 与 pyfcstm.diagram.engine 私有常量;未触碰 jsfcstm test tree;make test_boundary_check pass |
| Pydoc / reST | 6 个公共类与 3 个渲染方法均含 :param/:type/:return/:rtype/:raises/Example::;__init__.py 使用 .. list-table:: 表格 roadmap,符合 CLAUDE.md 规范 |
| CI 状态 | 57 项 required checks 全 pass(Diagram assets 3.7/3.8/3.11、Code test × 33、Template full × 5、Fresh CLI/wheel/sdist、jsfcstm、Docs HTML/PDF、Bilingual Docs PDF、CLI Build 及 Release、template-suite-gate 全绿) |
| 干净树 | git status --porcelain 只有 untracked .agents/(预置外部目录),无 tracked 改动 |
本地实测:SKIP_SLOW_TESTS=1 pytest test/diagram/test_assets.py -q → 68 passed / 46s;python tools/check_diagram_rendering.py --check / tools/fetch_diagram_reference.py --check / tools/check_diagram_assets.py --check / tools/check_diagram_engine_floor.py --all-cases --check-timeout-reset 全部通过。
C — 0
未发现阻塞性缺陷。所有闭包(allow-list、PNG 契约、raw SVG 拒绝、marker 几何、CJK、runtime floor、provenance、reference parity、fail-loud、Make/CI 硬门禁)均可复现并对抗性构造验证过。
I — 0
未发现应在 merge 前解决的重要问题。上一轮 codex reviewer 指出的 "encoded base64 PNG 上限需前置" 已在 pyfcstm/diagram/engine.py:1407-1416 显式实现,独立复现验证正确(_MAX_RENDER_PNG_BASE64_BYTES + 1 及 padding-窗口测试均直接命中 DiagramRenderLimitError)。上一轮 "reference 网络下载 mid-body 断流 + 连接断开 + 第三次成功" 与 "esbuild 从 editors/jsfcstm 本地锁定解析" 均可复现且被 --check 固化。
M — 3(观察,均非阻塞)
M1:pyfcstm/diagram/engine.py:1383-1391 的 render_png(scale=...) 允许任何 float() 可解析的类型(例如 " 2.0 "、"2e10")。对空格串我可复现被接受、对上限外的字符串仍走 DiagramRenderLimitError 拒绝,因此没有安全影响;但 docstring :type scale: float 与 max_memory 里显式拒绝 bool 的输入卫生风格不一致,未来 PR-C/D 增加公共 API 时可考虑显式 isinstance(scale, (int, float)) 拒绝 str。
M2:tools/fetch_diagram_reference.py:52 的 raise RuntimeError("unable to download diagram reference archive") 是死代码——最后一次 attempt 的 except 分支永远会 raise。无功能影响,属可读性小瑕疵。
M3:pyfcstm.diagram.engine._CanonicalSvg 是内部标记类型;_canonical_input(_CanonicalSvg) 会直接返回而不再走 SVG allow-list。虽然它 leading-underscore、未导出 __all__、无用户文档,但仍可通过 from pyfcstm.diagram.engine import _CanonicalSvg 触及。当前风险为 0(Python 私有约定),值得在未来公共 API 上台前把这条 fast-path 收敛为"只接受内部产出的 sentinel"(例如加 context_token 绑定或改成集合成员判断)。
独立复现命令
git rev-parse HEAD # 09ce4b9b
sha256sum pyfcstm/diagram/assets/{renderer.js,resvg-binding.js,resvg.wasm}
python tools/check_diagram_rendering.py --check
python tools/fetch_diagram_reference.py --check
python tools/check_diagram_assets.py --check
python tools/check_diagram_engine_floor.py --all-cases --check-timeout-reset
SKIP_SLOW_TESTS=1 pytest test/diagram/test_assets.py -q
make test_boundary_checkhostile 构造脚本(本审查已运行):
_validate_canonical_svg输入:<!DOCTYPE、<!ENTITY、<?xml、<script>、onclick、javascript:、style、<image href="data:…">、xlink:href、url(https://…)、brokenurl(#…);markerorient="90"与refX="5"— 全部按契约拒绝。_decode_png_rgba输入:pre-IHDR IDAT / duplicate IHDR / IEND non-empty / trailing bytes / bad-CRC IDAT /tEXtancillary / unknown criticalXYZW/ 全白 no-ink / scanline size mismatch — 全部按契约拒绝,1×1 red RGBA PNG 通过。render_png(scale=…):0 / -1 / NaN / inf / True / 4.1 / "abc" / "1e1000" / "2e10"全部拒绝;" 2.0 "与"2.0"走float()通过(对应 M1)。
Ready-to-merge 建议
- 可标记 ready-to-merge:C=0、I=0;M1/M2/M3 均非阻塞,可作为 PR-C 或后续 hardening 提案。
- 本 PR 定位为 official resvg 资产收敛底座,不含公共
StateMachine.diagramAPI 与 CLI;请维护者按伞 PR 规划:建立 Python Diagram API 与共享离线渲染链路 #383 计划节奏合入 umbrella,不合入main。
结论:LGTM。C=0 / I=0 / M=3(非阻塞)。
— claude reviewer
codex reviewer审查对象:PR #386,当前精确 HEAD 结论
Minor
本地可复现实据
远端检查在当前 SHA 上执行 以上三项均为可后续修复的 Minor,不构成阻止合入 umbrella 的 Critical/Important 风险。按 PR 正文约定,本评论只完成审查,不执行 merge。 |
deepseek reviewer — 最终独立强对抗审查报告审查范围: PR #386 head 总评代码质量扎实。official 结论C = 0 / I = 0 / M = 2,审查通过,M 不阻塞 merge。 Minor (M)M-1:
|
| 区域 | 验证方法 |
|---|---|
| 12 向量 SVG 安全合同(XSS/entity/外部URL/foreignObject/script/event handler/style/data URI/超大SVG) | 全部 12 攻击被 _validate_canonical_svg 正确拒绝,见下方 |
| raw SVG 入口拒绝 | _canonical_input("raw string") → ValueError ✓ |
| PNG CRC + IHDR/IDAT/IEND 合同 | 代码审计 + parity 工具 _self_check_png_parser pass |
| PNG scanline filter 0-4 全覆盖 | _decode_png_rgba 代码审计 + coverage |
| PNG base64 padding 边界攻击 | 手动验证 _MAX_RENDER_PNG_BASE64_BYTES 边界公式(%3==0/1/2) |
| marker-end orient=auto refX=10 合同 | _validate_svg_tree marker 强制检查 |
| CJK SC/TC/HK/JP/KR Regular/Bold 13 字体 | _CJK_LOCALE_PATTERN 全部 10 测试通过 |
| dual MiniRacer 拒绝 | _create_context 检测到两者同时安装时 raise ✓ |
| timeout → context discard | _eval 捕获 interrupt → _discard_context ✓ |
| 错误分类(asset vs render vs limit) | _asset_failure / _render_failure / DiagramRenderLimitError 三分法 ✓ |
| 开发/发布恢复提示 | _is_development_checkout() → make build_assets vs issue URL ✓ |
| reference 防伪造 | _CUSTOM_REFERENCE_FILES + backend=custom-resvg-0.37 + _self_check_reference_provenance pass |
broad except Exception 合规 |
仅 _eval 中一处,带 isinstance(err, self._interrupt_errors) guard + re-raise ✓ |
| 无 TODO/FIXME/HACK | grep 结果为空 ✓ |
实际运行证据
# 68 diagram tests
$ pytest test/diagram/test_assets.py -q
68 passed in 39.73s
# Full slow-skip test suite
$ SKIP_SLOW_TESTS=1 make unittest
46948 passed, 752 skipped
# Engine floor (15 cases × 3 formats)
$ python tools/check_diagram_engine_floor.py --all-cases --check-timeout-reset
{"cases": 15, "counts": {"expanded-svg": 15, "png": 15, "svg": 15}, ...}
# Canonical arrows (15 layouts, 130 arrows)
$ python tools/check_diagram_rendering.py --corpus canonical-arrows.json ...
{"arrows": 130, "directions": {"down": 78, "left": 5, "right": 32, "up": 15}, "layouts": 15}
# Shared layouts (20 layouts, 176 arrows)
$ python tools/check_diagram_rendering.py --corpus shared-layouts.json ...
{"arrows": 176, "directions": {"down": 75, "left": 13, "right": 75, "up": 13}, "layouts": 20}
# Provenance
$ python tools/check_diagram_provenance.py
{"patchedSourceArchiveSha256": "08d15a07...", "sourceArchiveSha256": "7ce86974...", "tarballSha256": "ff51acbb..."}
# Package + diagram checker
$ make package
diagram package archives: 1 wheel(s), 1 sdist(s) passed
# Boundaries
$ make test_boundary_check
test boundary check passed: no violations found
# reST
$ make rst_auto
Generated: ./docs/source/api_doc_en.rst
Generated: ./docs/source/api_doc_zh.rst
# Self-checks
PNG parser self-check: PASSED
Reference provenance self-check: PASSED
Reference comparison self-check: PASSED
12 向量 SVG 攻击全部拦截
OK: XSS onclick → DiagramAssetError: rejected attribute onclick
OK: <script> → DiagramAssetError: rejected element: script
OK: <a href=javascript:> → DiagramAssetError: rejected element: a
OK: url(http://...) → DiagramAssetError: rejected an external URL
OK: <!DOCTYPE/ENTITY → DiagramAssetError: rejected XML declarations or entities
OK: <!-- comment --> → DiagramAssetError: rejected XML declarations or entities
OK: <?xml ...?> → DiagramAssetError: rejected XML declarations or entities
OK: <foreignObject> → DiagramAssetError: rejected element: foreignObject
OK: style="..." → DiagramAssetError: rejected attribute style
OK: xlink:href → DiagramAssetError: rejected malformed XML (unbound prefix)
OK: data: URI → DiagramAssetError: rejected malformed XML (invalid token)
OK: >16MB SVG → DiagramAssetError: rejected an oversized SVG
与上游 PR/issue 计划的对照
| 计划项 | 状态 |
|---|---|
official @resvg/resvg-wasm@2.6.2 |
✅ asset-lock.json verified |
| closed SVG dialect | ✅ 12-attack pass |
| PNG bounded contract | ✅ CRC/IHDR/IDAT/IEND/scanline/ink |
| raw SVG entry rejected | ✅ _canonical_input gate |
| CJK 五 locale Regular/Bold | ✅ 13 fonts, 10 pattern tests |
| dual V8 + timeout/OOM/context | ✅ _discard_context + error taxonomy |
| reference custom-baseline 防伪造 | ✅ mechanism correct (M-1 note) |
| parity strict/clip-ID-only | ✅ self-check passed |
| Makefile/CI hard gate | ✅ diagram_assets → template_suite_gate |
| wheel/sdist package | ✅ make package passed |
| pydoc/reST | ✅ make rst_auto passed |
| PR #377 arrow endpoint 继承 | ✅ marker orient=auto refX=10 contract |
deepseek reviewer, 2026-07-20
PR-B 当前改动、三运行时实测与可视证据(
|
| Python | MiniRacer | 路径 | engine-floor | 完整视觉语料 |
|---|---|---|---|---|
| 3.7.17 | py-mini-racer==0.6.0 |
legacy | 15 cases × 3 formats | 35 layouts / 306 arrows |
| 3.8.1 | mini-racer==0.7.0 |
modern 最低版本 | 15 cases × 3 formats | 35 layouts / 306 arrows |
| 3.11.1 | mini-racer==0.14.1 |
modern 当前版本 | 15 cases × 3 formats | 35 layouts / 306 arrows |
每个环境都独立生成 35 个 canonical SVG、35 个 PNG、35 个 expanded SVG 和 SC/TC/HK/JP/KR 五地区 CJK 输出。不是只做 import smoke。
3. 我如何实测
先构建和检查当前 head 的运行资源:
make build_assets
make diagram_assets_check三个环境分别执行 runtime floor、三格式渲染和 timeout context-reset:
PYTHONPATH=. python3.7 tools/check_diagram_engine_floor.py --all-cases --check-timeout-reset
PYTHONPATH=. python3.8 tools/check_diagram_engine_floor.py --all-cases --check-timeout-reset
PYTHONPATH=. python3.11 tools/check_diagram_engine_floor.py --all-cases --check-timeout-reset三个环境再分别生成完整视觉目录:
PYTHONPATH=. pythonX.Y tools/check_diagram_rendering.py \
--corpus tools/diagram_assets/corpus/canonical-arrows.json \
--corpus tools/diagram_assets/corpus/shared-layouts.json \
--expected-layouts 35 --expected-arrows 306 \
--expected-directions right=107,down=153,left=18,up=28 \
--check-cjk --report-dir /tmp/prb-visual-pyXY逐案例 SHA-256 比较结果:
canonical SVG mismatch: 0
PNG mismatch: 0
expanded SVG mismatch: 0
CJK PNG mismatch: 0
CJK expanded SVG mismatch: 0
方向覆盖为 right=107, down=153, left=18, up=28。三个 engine-floor 的 timeout 路径都实际触发中断,并验证旧 context 被丢弃、下一次调用在新 context 中恢复。
此外还完成:
pytest test/diagram/test_assets.py -q
68 passed
SKIP_SLOW_TESTS=1 make unittest
46838 passed, 756 skipped, 106 deselected
make DIAGRAM_REFERENCE=/tmp/pyfcstm-custom-reference-final/reference.json diagram_assets_verify
35 layouts / 306 arrows、CJK、parity、memory、runtime floor、provenance 全部通过
make package
wheel、sdist、fresh-install SVG/PNG/expanded SVG 与 package inventory 通过
4. 当前实际生成图
全部 35 个布局:
五地区 CJK:
12 个未缩放的原始 expanded SVG,可逐个放大检查端点:
- simple leaf / 普通 A→B
- nested HVAC
- deep nesting
- many transitions / 四方向
- forced expansion / LR dark
- guard and effect
- absolute event
- rich state details
- traffic light stubs / LR dark
- nested crossing / LR dark
- CJK labels
- long labels
5. 视觉验收和 Gist 证据可靠性
- 最初联系表使用 SVG 内嵌
data:image/png;本地 Chrome 可见,但 Gist raw 的严格 CSP 会阻止。最终版本改为直接组合各运行时实际生成的 expanded SVG path,并隔离内部 ID;不含data:或外部资源。 - 三个 12-case 图页和三个 35-case 图页均重新栅格化;去掉仅含版本号的标题区后,Python 3.7 对 3.8、3.7 对 3.11 的主体像素差都为 0。
- 人工检查普通 A→B、自循环、LR/TB、forced、nested crossing、四方向、dark/light、长标签和深层组合状态,未见端点贴边、裁切、异常重排或 backend 切换副作用。
- SC/TC/HK/JP/KR 均显示实际字形,无 tofu 方框;中文长标签没有越出 block。联系表的说明文字已改为中文,fixture ID 和状态名保留原始被测字面量。
- Gist 最终为 54 个文件;全新 clone 的 head 与 API 最新版本均为
2abd897…,SHA256SUMS的 53 个内容文件全部通过,25 个 SVG 均通过 XML 解析和精确外部资源扫描。 - Chrome 已直接打开远端 raw 的中文汇总图和 Python 3.7 图;Python 3.8、3.11 与 CJK 图先从 raw URL 下载并逐字节核对 SHA-256,再由同一 Chrome 打开。这样不会把大 SVG 的网络等待超时误判成渲染失败。
当前 PR 仍保持 OPEN / CLEAN,本次只补证据和评论,没有执行 merge。
|
已按意见把资产元数据文档收敛到原有英文基线,提交为
本地验证: 这是文档最小化提交,没有修改运行时代码、语料、package inventory 或生成资产;当前 head 的 GitHub checks 正在重跑,PR 未执行 merge。 |
- detect real document type declarations without rejecting comment or CDATA literals - reject non-mapping and non-finite DiagramData requests at the Python boundary - add legacy-compatible regression coverage for both contracts
Keep the first MiniRacer error headline while dropping evaluated source lines and stack text. Add a real JavaScript failure regression to preserve actionable causes without exposing the evaluated request.
Evaluate packaged JavaScript through a temporary in-context loader so legacy py-mini-racer can report syntax corruption instead of aborting when a timeout is configured. Remove the loader after startup and keep eval/Function blocked for renderer requests.
PR-B final current-state review (
|
|
PR-B 已完成 merge。
本 PR 只合入 umbrella,没有直接进入 |
Merged State
2026-07-20T12:45:18Z.b78ae178b7649aea48b8a7f313642e1caf863f08.dev/python-diagram-umbrella.aa745161980799a4ba9e56e3654040339f7faa98.main; the umbrella remains blocked pending PR-C, PR-D, and final integration gates.This section supersedes the historical pre-merge state below.
Pre-Merge State (historical)
b78ae178b7649aea48b8a7f313642e1caf863f08dev/python-diagram-umbrella@bcadb92a9cc510de419b72f1a8935c377e9e6978dev/python-diagram-umbrella; this PR must not merge directly intomain.Docs generated resourcesskip;template-suite-gatepassed. Codecov is complete at 94.54% (25 sessions).This PR closes the Python-side official resvg asset/runtime stage. The latest
maintainer decisions in this section supersede conflicting request-only, output-limit,
and production-validator text preserved in older planning comments.
Production Runtime
@resvg/resvg-wasm@2.6.2binding and WASM assets.py-mini-racer>=0.6.0,<0.7on Python 3.7 andmini-racer>=0.7.0on Python 3.8+.and packaged resources.
DiagramAssetEngine(timeout=None)applies no default artificial timeout. Callersmay opt into a finite positive timeout; timeout/OOM still discards the context.
scalemust be finite and positive. Production imposes no canvas, pixel, encodedoutput, or scale upper limit.
declarations, and accepts legal comments and CDATA.
and final IEND placement without imposing byte/dimension/pixel caps.
render_png()andexpand_svg()accept DiagramData dictionaries and canonical SVGcompatibility input. The string path is not a general-purpose SVG sanitizer.
JSON boundary.
DiagramAssetError; it is deleted after bundle load, while globalevalandFunctionremain blocked for renderer requests.Maintenance And CI
Strict checks remain in
tools/check_diagram_rendering.py, outside production andoutside pytest imports of
tools.*:corpus evidence.
The checked-in corpus covers 35 layouts and 306 arrows: canonical 15/130 plus shared
20/176, including LR/TB, four directions, block-border endpoints, and five CJK locales.
Verification
py-mini-racer 0.6.0: 61 diagram tests passed.mini-racer 0.14.1: 61 diagram tests passed.SVG/PNG/expanded SVG and timeout reset passed.
and expanded-SVG clip-ID-only comparison passed.
ruff check,ruff format --check,compileall,git diff --check, and test-boundary checks passed.comment/CDATA declaration literals on the production
render_svg()path.Merge Boundary
This PR does not add
StateMachine.diagram(),model.show(), a public Diagram facade,HTML viewer/cache, Notebook display, a standalone diagram CLI, PDF APIs, backend
selectors, or ELK/VSCode geometry changes. Those remain PR-C/PR-D work.
PR-B is merged into the umbrella branch at
aa745161. The umbrella remains blockedfrom
mainuntil PR-C, PR-D, and final integration gates are complete.