From 935099a37ee9e5f501d2bfcac90cfd54e0c0830a Mon Sep 17 00:00:00 2001 From: Smilewithoutfalling <91455578+Smilewithoutfalling@users.noreply.github.com> Date: Mon, 26 Oct 2026 07:37:50 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20=E9=98=B6=E6=AE=B5=206=20=E6=A1=8C?= =?UTF-8?q?=E9=9D=A2=E6=89=93=E5=8C=85=E6=96=B9=E6=A1=88=EF=BC=88D1=20?= =?UTF-8?q?=E5=A3=B3=20+=20D2=20=E9=A6=96=E6=AC=A1=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=EF=BC=9BD3~D5=20=E6=8E=A8=E8=BF=9F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/DEVLOG.md | 18 +++++- docs/roadmap/phase5-frontend.md | 4 +- docs/roadmap/phase6-desktop.md | 108 ++++++++++++++++++++++++++++++++ docs/roadmap/plan.md | 2 +- 4 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 docs/roadmap/phase6-desktop.md diff --git a/docs/DEVLOG.md b/docs/DEVLOG.md index a44cb56..d5da18a 100644 --- a/docs/DEVLOG.md +++ b/docs/DEVLOG.md @@ -2225,7 +2225,7 @@ S 10-25 定:§2 上游原文折叠保留;§4 根字号 14。 - 判定变更:旧断言一条没改(零调用 reported=None 的路径以前没有测试断言)。新增 tests/test_v2d.py 20 条、src/views/v2d.test.tsx 5 条、e2e/f4-budget.spec.ts 1 条(fake 项目按文件名 f[34]* 匹配)。提示词、chart_fixtures 不变。视觉 11、25 两屏:token 文字让 `.acard-time` 遮罩框变宽(每张 1119 像素,只在遮罩框里),基线换成首跑 CI run 40260578380 的 x64 actual;我本地报的「基线不用重取」是错的——本地 aarch64 这 4 屏本来就红,看不出这个差异。 -## [67] 2026-10-26 · F5 通过,删 Streamlit · commit <待提交> +## [67] 2026-10-26 · F5 通过,删 Streamlit · commit c668ff4(PR #79) [66] 回填为 2b283b6(PR #78;master tests run 40262438449 绿)。F5 走查记录补在 [55] 末尾:五条路径都过了;10-26 的 4 条发现 S 定为可接受,不处理。 @@ -2239,3 +2239,19 @@ S 10-25 定:§2 上游原文折叠保留;§4 根字号 14。 - 另外改了:app/core/service.py 文件头的入口列表、plan.md 阶段 5 的状态(标成完成)。 pyarrow:它只是 streamlit 的传递依赖([18],DEVLOG 第 771 行),所以删掉 streamlit 后,新装的环境里不会再有 pyarrow。pandas 3.0 在没有 pyarrow 时字符串后端改用 python,开发和打包也因此一致了。这样 [18] 留到阶段 6 的「pyarrow 去留」问题就不用再定了,sidecar 体积预计少约 78 MB,以 CI 的 sidecar 工件为准。验证:我用全新 venv 装 requirements-dev.txt,确认没有 streamlit 和 pyarrow,再跑全量 pytest。 + +## [68] 2026-10-26 · 阶段 6 方案:桌面打包 · commit <待提交> + +[67] 回填为 c668ff4(PR #79;master tests run 40283639951 绿)。 + +新文件 docs/roadmap/phase6-desktop.md。阶段 6 是新阶段,就不再往 phase5(已经 1300 多行)里写了。内容: +- D0:要 S 定的 6 件事——外壳、安装包格式、WebView2、卸载时是否保留数据、发布渠道、仓库许可证(仓库目前没有 LICENSE)。 +- D1:壳的进程管理。sidecar 走 bundle.resources,不用 externalBin(onedir 的 `_internal` 会丢);用 Job Object 防止留下孤儿进程;单实例;导航边界和空 capabilities;代理绕过要实测。CI 自动冒烟覆盖静默安装、`--smoke`、强杀壳后 sidecar 跟着退出、卸载后数据还在。 +- D2:首次启动先进设置页;补上 THREAT_MODEL §4 (a) 欠的「来路不明」提示(现在代码里没有这句)。 +- D3:自动更新,用 minisign 签名,和 Authenticode 无关。 +- D4:许可证审计闸门。 +- D5:签名路线表,D1~D4 按未签名先做完。 + +更正:phase5 F5 末尾和 plan.md 阶段 6 的验收里写着「EV 对 SmartScreen 立刻生效 / SmartScreen 不拦」,这在 2024 年以后已经不成立(微软 2024 年 8 月去掉了 EV OID,OV 和 EV 都要积累信誉)。验收改成「显示已验证的发布者」。Azure Artifact Signing 目前不对中国主体开放。 + +S 10-26 定:只在本机自用,D0 的建议全部采纳;D3~D5 推迟到对外分发之前再做,本阶段只做 D1 + D2(方案「范围」一节)。网页端(`--open` 浏览器模式)保留,壳的功能不能让它坏掉。 diff --git a/docs/roadmap/phase5-frontend.md b/docs/roadmap/phase5-frontend.md index 30b7836..4a90d87 100644 --- a/docs/roadmap/phase5-frontend.md +++ b/docs/roadmap/phase5-frontend.md @@ -754,8 +754,8 @@ F5 不是回归清单,而是一次真 Key 的手动走查加观感判断,预 - 代码签名证书挪到**阶段 6 前置事项**。它需要实名主体、付款和 CA 审核,agent 和评审都办不了,要由有主体的人申请;周期长,而且不依赖 F5 的结论,主体一旦确定就可以启动。目标是「Windows 一键安装不弹 SmartScreen 黄条」,所以大概率要 OV 起步。选型: - DV / 个人验证:最快,但 SmartScreen 信誉要靠下载量慢慢攒。 - OV:需要营业执照和电话回访,签名后信誉建立比 DV 快。 - - EV:私钥放在硬件 token 或云 HSM 上,对 SmartScreen 立刻生效。 - 材料清单在阶段 6 方案里给出。 + - EV:私钥放在硬件 token 或云 HSM 上。~~对 SmartScreen 立刻生效~~ **更正(10-26)**:2024 年起 EV 不再立刻免掉 SmartScreen 提示,和 OV 一样要靠信誉慢慢积累,详见 phase6-desktop.md §6。 + 材料清单写在 docs/roadmap/phase6-desktop.md §6。 **F5 覆盖不到的:#49 矮画布紧凑几何**。在产品里不可达:`.echart` 固定 `height: 360px`(styles.css:110),EChart.tsx 用 `isCompact(el.clientHeight)`,而 COMPACT_BELOW = 300,所以 isCompact(360) 恒为 false,COMPACT_KEYS 在真实用户路径上从来不会下发。窗口变矮只会出现滚动条,图本身不会变矮。f3-themes 里 #49 的用例能绿,是因为 measure() 人为把 style.height 设成了 220px,**测试绿不等于产品生效**。要人工验只能在 devtools 里改 style.height。#49 的状态是:契约、单测、e2e 都已完成,产品行为不变。 diff --git a/docs/roadmap/phase6-desktop.md b/docs/roadmap/phase6-desktop.md new file mode 100644 index 0000000..4a692b0 --- /dev/null +++ b/docs/roadmap/phase6-desktop.md @@ -0,0 +1,108 @@ +# 阶段 6:桌面打包(方案,待 S 评审) + +来源:plan.md 阶段 6 的交付物与验收;M19(签名 / SmartScreen / 干净机器验证)、M22(外壳选型);sidecar 契约 docs/sidecar-contract.md;阶段 4 spike DEVLOG [18];phase5 F5 末尾的「阶段 6 前置事项」。 + +**为什么单独写一份文件,不写进 phase5-frontend.md**:phase5 已经 1300 多行,阶段 6 是新阶段,有自己的步骤和验收。HANDOVER §3 的「方案先行」照旧适用,只是换了文件。 + +## 范围(S 10-26 定) + +S 定:**目前只在自己的电脑上安装使用,不对外分发**,§1 的建议全部采纳。所以: +- **现在做**:D1 壳 + 安装包、D2 首次启动 + (a) 提示。 +- **推迟到对外分发之前**:D3 自动更新(自己用的话,重装新的安装包就能升级,数据保留)、D4 许可证审计、D5 代码签名、§1 第 5 条发布渠道和第 6 条许可证。对外分发的前提条件不变:签名、许可证、THREAT_MODEL (a) 重新确认,三者缺一不可。 +- **网页端保留**(S 10-26 定):`python -m app.server --open` 用浏览器打开,这条路径继续支持。e2e 测试照旧跑在浏览器上。壳专属的功能(重启 sidecar、单实例)不能让浏览器模式坏掉:前端用能力探测决定是否显示这类按钮,不用 UA 判断。D2 的首次启动引导和 (a) 提示两种模式都要有。阶段 8 的 Web 服务器版继续复用同一套前后端。 +- 未签名的安装包在 S 本机第一次运行时,SmartScreen 会拦一次(点「更多信息 → 仍要运行」),之后同一个文件不会再拦。 + +## 0. 已有的地基(不重做) + +- sidecar:PyInstaller onedir,在 windows-latest 上打包,冒烟用 `packaging/smoke_sidecar.py` 覆盖自检、后端、重启。zip 85.7 MB(sidecar run 40282390448,去掉 pyarrow 之后);冷启动约 1.5 s([18])。 +- 启动契约:壳用 `--port 0` 启动 → 读 stdout 第一行 JSON `{port, token, home}` → 轮询 `/health` → 打开 `http://127.0.0.1:/#token=`(sidecar-contract.md)。 +- 前端只通过 HTTP / SSE 和后端通信,不用 Tauri IPC(S 10-12 定,M22 回应)。壳可以换,换壳不用改前端。 +- 后端托管 dist 并带 CSP,前端已经能在 Firefox 里完整走一遍(F5 已通过)。 + +## 1. 要 S 先定的(D0) + +| # | 问题 | 我的建议 | 理由 | +|---|---|---|---| +| 1 | 外壳:Tauri 2 还是 Electron | **Tauri 2** | M22 担心的几处成本,在这里都已经卸掉了:前端不碰 IPC;sidecar 自己管进程;Rust 只写大约 200 行的进程管理;构建全在 CI 里,S 不用装 Rust。Electron 会多带一个 Chromium(约 100 MB),而且 Node 和 Python 两套运行时并存 | +| 2 | 安装包格式 | **NSIS,按当前用户安装**(`installMode: currentUser`,不要管理员权限,装到 `%LOCALAPPDATA%\Programs\DataChat`) | 不弹 UAC;S 的本机就是普通用户场景。MSI 适合企业批量部署,现在用不上 | +| 3 | WebView2 怎么装 | **downloadBootstrapper**(Tauri 默认):缺 WebView2 时联网装 | Win11 自带 WebView2,Win10 一般也已经通过系统更新装上了,离线安装包要多出约 127 MB。离线场景另出一个包,放到以后再做 | +| 4 | 卸载时删不删数据 | **默认不删**。数据目录 `%LOCALAPPDATA%\datachat`(会话、数据集、server.log)和钥匙串里的 Key 都保留;卸载页给一个「同时删除我的数据」的勾选框 | 重装或升级后历史都还在;想彻底删的用户也有出口 | +| 5 | 发布渠道 | **GitHub Releases**(仓库是公开的),自动更新读 Release 里的 `latest.json` | 不用另找服务器 | +| 6 | 仓库许可证 | 要 S 定(MIT / Apache-2.0 / 不开源) | 仓库现在**没有 LICENSE**(GitHub API 返回 license=null),默认就是保留所有权利。这一项影响 §6 免费签名能不能申请、第三方声明怎么写,也影响别人能不能合法使用 | + +## 2. D1 壳:进程管理与窗口(spike + 实现) + +**规则(已定待评审)** +1. sidecar 放在 `bundle.resources`,**不用 `externalBin`**。externalBin 只拷单个 exe,PyInstaller onedir 的 `_internal/` 会丢(Tauri 文档 v2.tauri.app/develop/sidecar;社区里多个项目踩过这个坑,都改成了 resources,比如 openworker、HostWise 的提交)。Rust 用 `std::process::Command` 从 `resource_dir()/sidecar/datachat-sidecar.exe` 启动,不引入 tauri-plugin-shell。 +2. 启动:`--port 0 --home %LOCALAPPDATA%\datachat`,`CREATE_NO_WINDOW`(不弹黑框),stdout 用管道接,stderr 丢弃(日志已经写进 server.log)。读第一行 JSON(超时 20 s)→ 轮询 `/health`(每 100 ms 一次,总超时 20 s)→ 主窗口 `navigate` 到 `http://127.0.0.1:/#token=`。等待期间窗口显示本地的「正在启动…」页(打包进壳的静态 html,不经 sidecar)。 +3. **sidecar 不能变成孤儿进程**:壳一启动就建一个 Job Object,设 `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`,把 sidecar 放进去。壳不管是正常退出、崩溃还是被任务管理器结束,sidecar 都会被系统一起杀掉。正常退出时先 `POST /shutdown`(新接口,要 Bearer),等 3 s 再强杀。理由:Windows 没有「父进程死了子进程跟着死」的默认行为,没有 Job Object 的话,残留的 sidecar 会一直占着 SQLite 和端口。 + - 注意:沙箱 worker 已经在**另一个** Job Object 里(内存上限)。Win8 起 Job 可以嵌套,嵌套是否生效要在 spike 里实测(worker 的内存上限在嵌套后仍然生效、壳被杀后 worker 也会消失)。 +4. sidecar 中途退出:窗口切到本地错误页,显示退出码和 server.log 最后 20 行(先打码,复用 T11 的打码函数),并提供「重新启动」按钮。顺带解决 P1-16(「重新打开应用」在浏览器里做不到,到了壳里就能做)。 +5. 单实例:`tauri-plugin-single-instance`。第二次打开时聚焦已有窗口,不再起第二个 sidecar(两个 sidecar 写同一个 SQLite 会冲突)。 +6. 导航边界:主窗口只允许 `http://127.0.0.1:<本次端口>/` 和本地启动页、错误页。其他地址(结论里的 Markdown 链接、`target=_blank`)一律用系统默认浏览器打开,不在壳里打开(`on_navigation` + `on_new_window`)。capabilities 给远程 URL 的权限为空:127.0.0.1 页面拿不到任何 Tauri API,T9 的防线不因为套了壳而变弱。 +7. 下载:导出 PNG 走 WebView2 自带的下载流程,默认存到「下载」文件夹。在 spike 里确认 WebView2 不会弹出意外的对话框;如果会,再用 `on_download` 处理。 +8. 代理:S 的机器常配系统代理,[18] 里遇到过 127.0.0.1 被代理转发、返回 404。Chromium 默认对 loopback 绕过代理,WebView2 应该一样。spike 在 runner 上设好系统代理再跑一遍,确认这一点;如果不绕过,就给 WebView2 加 `--proxy-bypass-list=`。 +9. 窗口:标题 DataChat,最小 1024×640(25 屏就是按 1024 设计的),记住上次的大小和位置。 + +**验收** +- CI(windows-latest):`tauri build` 出 NSIS 安装包,作为工件上传。 +- CI 自动冒烟:静默安装(`/S`)→ 用 `--smoke` 参数启动已安装的壳(无界面模式:起 sidecar、等 health、请求一次首页、正常退出,退出码 0)→ 检查进程表里没有残留的 sidecar → 再启动一次,`taskkill /F` 结束壳,检查 sidecar 也跟着消失 → 静默卸载,安装目录被删除、数据目录还在。 +- S 本机:下载安装包 → 双击安装(未签名,SmartScreen 点「更多信息 → 仍要运行」)→ 从开始菜单打开 → F5 的五条路径里挑 ① 和 ② 再走一遍 → 关闭窗口后,任务管理器里没有 datachat-sidecar。 + +## 3. D2 首次启动与发布约束 + +1. 首次启动(钥匙串里没有 Key):直接进设置页,顶部加一句引导「先填 API Key,再上传 CSV」。复用现有的设置页和测试连接,不另做向导。 +2. **THREAT_MODEL §4 (a) 欠的账**:(a) 要求「首次加载 CSV 时提示不要加载来路不明的文件」,现在没有实现(`grep -rn 来路不明 ui/web/src app` 为空)。在上传对话框里加一行常驻提示:「只分析你自己的数据。不要加载来路不明的 CSV:表里的内容会被模型读到。」不做成弹窗,也不做「不再提示」开关。 +3. 安装说明(README 加「安装」一节)写明产品定位是「只分析自己的数据」,不写「可安全打开别人给的数据」。 + +**验收**:vitest 断言首次进入时落在设置页,并且上传对话框里有这行提示;e2e 加一条首次启动的用例(fake 项目,文件名按 f[34]* 的规则)。 + +## 4. D3 自动更新 + +1. `tauri-plugin-updater`,endpoint 指向 GitHub Releases 的 `latest.json`。启动 10 s 后检查一次:有新版本就在顶栏提示「有新版本 x.y.z」,用户点了才下载、安装、重启,不静默更新。 +2. 更新包用 Tauri 的更新签名(minisign)校验,**和 Authenticode 代码签名无关**,没有代码签名证书也能做。私钥放进 GitHub Secret `TAURI_SIGNING_PRIVATE_KEY`:密钥对由我在沙箱里生成,私钥由 S 亲手粘贴进 Secrets,私钥本身不进仓库、不进对话。公钥写进 `tauri.conf.json`。 +3. 版本号:`tauri.conf.json` 的 version 是唯一来源,打 tag `v0.x.y` 时触发 release 工作流,构建、签名、发 Release,并生成 latest.json。 +4. 数据兼容:升级不碰数据目录。SQLite 迁移沿用现有做法(启动时补表,参见 v1 → feedback 表的 `test_v1_database_gains_feedback_table_without_touching_old_tables`)。之后每次改表都要带一条「旧库启动」的测试。 + +**验收**:CI 发两个版本(v0.1.0 → v0.1.1 的测试 Release,标为 pre-release),在 runner 上装旧版 → 让它检查更新 → 装上新版 → 版本号变了、旧会话还在。S 本机手动验证一次。 + +## 5. D4 依赖许可证审计 + +1. 三套依赖各用一个工具:Python 用 `pip-licenses`(只扫 requirements.txt 的运行依赖,不扫 dev),前端用 `license-checker`(只扫 production),Rust 用 `cargo-about`。产出 `THIRD_PARTY_NOTICES.txt`,打进安装包,在设置页「关于」里能打开。 +2. CI 闸门:出现 GPL / AGPL / LGPL / SSPL / 未知许可证就红,例外要逐条写进白名单并说明理由。已知的一条:PyInstaller 本体是 GPL,但带 bootloader 例外,打出来的程序不受 GPL 约束,写进白名单。 +3. 我先跑一遍出结果,有问题的依赖在方案评审时一并处理。 + +## 6. D5 代码签名(前置事项,S 或有主体的一方办) + +**先更正 phase5 F5 末尾的一句话**:那里写的是「EV 对 SmartScreen 立刻生效」。这个说法 2024 年起就不成立了。微软 2024 年 8 月从 Trusted Root Program 里去掉了 EV 代码签名 OID,现在 OV 和 EV 一样,SmartScreen 信誉都要靠时间和下载量慢慢积累,没有哪种证书能立刻免掉提示(Microsoft Learn「SmartScreen reputation for Windows app developers」和「Code signing options for Windows app developers」,2026)。所以 plan.md 的验收「SmartScreen 不拦」要改写成:**签名后显示已验证的发布者名称,不再显示「未知发布者」;SmartScreen 提示随信誉积累逐渐消失**。 + +| 路线 | 费用 | 能不能办 | 备注 | +|---|---|---|---| +| Azure Artifact Signing(原 Trusted Signing) | 9.99 美元/月 | **目前不行**:公共信任证书只开放给部分国家的组织和美加的个人,中国不在列表里 | 微软推荐的路线;以后开放了再看 | +| OV 证书(DigiCert、Sectigo 等) | 每年 150~300 美元 | 需要营业执照(组织);私钥必须放在硬件 token 或云 HSM 里(CA/B 论坛 2023 年 6 月起的要求) | 国内有主体时最现实;云 HSM 版本可以在 CI 里签 | +| 个人代码签名证书(例如 Certum 的个人证书或开源证书) | 每年几十欧元起 | 要以个人身份做实名验证,证书上写个人姓名 | 价格和是否接受中国个人,以 CA 官网为准,申请前再核实 | +| SignPath Foundation(开源项目免费) | 免费 | 前提是 OSI 许可证 + 公开仓库 + 可复现的 CI 构建 | 取决于 §1 第 6 条许可证怎么定 | +| 不签名 | 0 | 可以 | 只适合 S 自己用和小范围内测;每个用户都会看到「未知发布者」 | + +**已定待评审**:阶段 6 的 D1~D4 全部按**未签名**做完、验收完,签名不阻塞开发。CI 里预留签名步骤:Secret 存在就签,不存在就跳过并在工件名里标 `unsigned`。对外分发(给 S 以外的人)之前,必须办好签名,并且在 THREAT_MODEL 里重新确认 (a)。 + +材料(以 OV 为例,具体以 CA 要求为准):营业执照、对公电话(CA 会回访)、申请人身份证明、域名或邮箱验证;云 HSM 版本还要开 CA 的签名账号。 + +## 7. 顺序和工作量 + +| 步 | 内容 | PR | S 要做的 | +|---|---|---|---| +| D0 | 本方案 + §1 的 6 个决定 | 本 PR | 评审 | +| D1 | 壳 spike → 实现 + CI 安装冒烟 | 实现 PR 1 | 本机装一次,走 ①② | +| D2 | 首次启动 + (a) 提示 + README 安装一节 | 并进实现 PR 1 | 同上 | +| D3 | 自动更新 + release 工作流 | 实现 PR 2 | 粘贴 Secret;本机验一次升级 | +| D4 | 许可证审计 + 关于页 | 并进实现 PR 2 | 看一眼白名单 | +| D5 | 签名 | 等主体 | 申请证书 | + +工作方式的偏离:沙箱是 aarch64 Linux,**Windows 的 Tauri 打包只能在 CI 上跑**,所以每改一次壳都要等一轮 windows-latest 构建,预计 10~15 分钟(Rust 首次编译慢,之后靠 cache)。Rust 部分我本地只能做 `cargo check` 级别的检查。 + +## 8. 不做 + +- macOS / Linux 安装包;离线 WebView2 包;静默自动更新;托盘常驻;开机自启。 +- 把前端打进壳、绕过 sidecar 托管(会破坏「换壳不改前端」和 CSP 只有一份来源的前提)。 +- THREAT_MODEL (b):OS 级隔离仍然放在阶段 8;选了 (a),就不对外宣传能打开别人给的数据。 diff --git a/docs/roadmap/plan.md b/docs/roadmap/plan.md index cd85484..602f897 100644 --- a/docs/roadmap/plan.md +++ b/docs/roadmap/plan.md @@ -18,7 +18,7 @@ | 3.5 外部验证闸门 | 3 天 | 非自造数据上的 25 题(单表 15:UCI Online Retail 改造的中文 GBK 脏表;多表 10:Olist 8 张表);标准答案由独立脚本在原始数据上算出;最小多表支持(dfs);报告逐题标判对层级、评测集指纹 | 按 docs/roadmap/gate-phase35.md 预登记的阈值与失败分布规则出结论 | M01 M02 M04 M05 M10(部分) M15 | | 4 后端 API | 1–2 周 | 第一步:API Key 进系统钥匙串(keyring);`analyze_stream()` 逐步产出事件,`analyze()` 包一层(M09)。FastAPI:数据集上传(一个数据集可含多个文件,M10)、会话、消息(SSE 流式推送每一步)、设置;SQLite + SQLAlchemy(表结构兼容 PG,预留 user_id);logging + trace id(会话 / 消息 id),runs.jsonl 换成数据库(M08 M14);接口返回 png + 图表数据(M13)。最后:PyInstaller sidecar + 沙箱 worker 在 Actions Windows runner 上的 spike(B35 / M06) | 用 curl 走完"上传 → 提问 → 流式结果 → 历史可查";重启后会话还在;spike 打出的 exe 能在干净 runner 上执行一次沙箱代码 | B18 B35 | | 5 前端 | 2–3 周 | 第一件事(M13,从阶段 4 顺延,含后端三步):图表库定为 ECharts;定 option 格式与白名单、写 builder `chart(kind, df, x=, y=, series=, title=)`;worker 产出 chart_option(与 result 脱钩,B44);接口加 chart_option;改绘图提示词并重跑闸门。之后:React + Vite + TS:数据集面板、对话流(展示计划、代码、执行步骤)、图表、可展开的代码、👍👎 反馈、Key 设置页,分步与验收见 docs/roadmap/phase5-frontend.md(F0–F5) | 不碰命令行就能完整用一遍 | — | -| 6 桌面打包 | 1–2 周 | Tauri 2 外壳(前提见上:前端只走 HTTP)+ PyInstaller 后端 sidecar;Windows 安装包;自动更新;首次启动引导填 Key;依赖许可证审计;pyarrow 已随 Streamlit 一起去掉(DEVLOG [67]) | 在一台干净的 Windows 上一键安装,可用;对外分发前代码签名、SmartScreen 不拦(M19);对外分发前满足 THREAT_MODEL.md 的 (b) 条件 | — | +| 6 桌面打包 | 1–2 周 | Tauri 2 外壳(前提见上:前端只走 HTTP)+ PyInstaller 后端 sidecar;Windows 安装包;自动更新;首次启动引导填 Key;依赖许可证审计;pyarrow 已随 Streamlit 一起去掉(DEVLOG [67]) | 在一台干净的 Windows 上一键安装,可用;对外分发前代码签名,显示已验证的发布者(2024 年起任何证书都不能让 SmartScreen 立刻不拦,见 phase6-desktop.md §6,M19);方案见 docs/roadmap/phase6-desktop.md;对外分发前满足 THREAT_MODEL.md 的 (b) 条件 | — | | 7 准确率 | 2–4 周 | DuckDB 统一 CSV/Excel/SQL;YAML 语义模型(指标、口径、同义词);枚举值召回;结果合理性校验;反馈沉淀为已验证问答对 | 在阶段 3.5 闸门数据上给出具体目标(闸门结论后定,M20);自造集只作回归 | B05 B09 | | 8 Web 上线(后续) | 另定 | PostgreSQL、登录、任务队列、Docker/E2B 沙箱、Langfuse、服务器部署与 ICP 备案 | 另定 | 多租户 |