Skip to content

修复自动化删除保护并实现规则配置包导入 - #47

Merged
massif-01 merged 9 commits into
RMinte-AI:mainfrom
massif-01:fix/automation-delete-protection
Oct 12, 2026
Merged

massif-01 merged 9 commits into
RMinte-AI:mainfrom
massif-01:fix/automation-delete-protection

Conversation

@massif-01

@massif-01 massif-01 commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

动作模板仍被规则引用时删除会留下失效引用;规则配置包此前只能验证,无法真正导入。本 PR 补齐删除/保存两端的引用保护,并实现经过签名、目标设备和静态依赖检查的配置包保存。导入后当前运行规则保持,重启后加载新配置。

修改

  • 普通模板删除检查全部运行规则,包括停用规则;返回去重的规则名称/ID。普通保存拒绝新增或增加次数的缺失模板引用,保留历史未增加的引用、草稿与后续保存/触发能力。
  • 配置包导入使用完整保存集合、不可变加密对象与单一 selector;保持普通 NVS/SD JSON 来源和原服务保护。保存、运行及未知提交状态分别表达,支持 CAS、幂等和恢复确认。
  • R1:恢复确认删除后保留旧对象/已有租约,禁止新自动或手动准入、同 ID 写入/重导入;容量按运行与保存 ID 并集计算。已知成功的普通删除仍立即生效;列表与快捷入口一致显示待删除。
  • R2:严格静态依赖只约束包来源;普通规则仍兼容加载。运行版本保存自身来源,首次依赖初始化可以进入,真实包依赖未就绪时不发布部分集合。
  • R3:预检与拟变更保护共用有效动作/覆盖视图解析。模板一级优先,直接 SSH 与命令引用 SSH 按实际内部主机优先级核验本地依赖及回退字段。WebHook 保持未实现,新包和受保护模板变更明确拒绝;不调用执行、连接或 pin 做预检。
  • 整合保留原删除保护的双语说明、忙/加载/恢复区分、首页与 root 入口,以及中英文 README 原有正文和新头图。
  • 普通保存与 checked 模板删除同时保留两边的准入检查;共同绑定锁采用递归取得/释放,edit 准入使用同一锁的非阻塞接口,保持 binding→transaction 顺序。生产锁和真实删除入口的定向测试覆盖嵌套、竞争拒绝、待重启及运行包的删除保护。

当前整合源码验证

  • tests/runtime/run_all.sh:退出 0;全部相关 runtime、codec、store、快照/动作提交/服务与规则包回归通过,包含 A1–A3/B1–B3/C1–C3、1851 个保存/迁移/恢复故障场景。
  • 生产删除/保存 H01–H14 与并发顺序:退出 0;8 份 HTTP 响应字节 fixtures 保持一致,拒绝时无移除/文件清理/提交副作用。
  • 指定静态/单元:33/33;实际双语 Chrome 浏览器:58/58(38 删除保护+20 规则包)。
  • 固定 ESP-IDF 5.5.2、ESP32-S3 隔离完整 app/SPIFFS 构建:退出 0;git diff --check 通过。

旧证据文件分别对应各自分支的历史源码。当前整合证据、命令、日志摘要与工件身份见 整合验证报告;R1–R3 修复前反例与逐项闭合见 规则包修复报告。生产函数测试使用外围夹具,浏览器使用本地模拟设备,尚无真机验收。

发布与兼容边界

完整集合/selector 和加密信任架构保持,不放松证书验证,不实现 Webhook,不自动修复历史规则,不合入独立 SSH 指纹或其他提示语分支。
部署前仍需确认真实签名根、Developer 证书用途、可信时间及 NVS/SD 空间。启动即可发生旧集合到 v3 的迁移,不以点击导入为前提;旧固件不承诺原地降级。

本次在原 PR 提交后追加一个整合 merge commit,保留两个已审查分支的历史。个人仓库 main 和本 PR 来源分支同时推进;上游 PR 保持未合并,等待用户统一合并。未刷机、操作设备、修改发布版本、打标签、手动触发工作流或 Release。

Linux CI 编译适配

首轮远端 runtime 检查暴露规则包原生测试遗漏 -lm;追加测试编译适配提交,显式链接数学库,并补齐递归锁夹具的 POSIX 声明。相关本地 configuration_protocol 和规则包 C3 重跑通过。生产源码未变化,最新 head 的 GitHub CI 正在重新验证;旧 head 的失败不能当作最新检查结果。

@massif-01 massif-01 changed the title 修复自动化动作模板引用保护与删除提示 修复自动化删除保护并更新 README 头图 Oct 9, 2026
Persist complete versioned rule sets with one selector, exact-byte acceptance records, target-local revisions and restart-pending guards. Validate signing authority, recipient identity and dependencies without publishing imported rules at runtime.

Fix datasource toggle routing and add production crypto, migration, fault-recovery, engine/API and bilingual browser coverage.
Classify active/saved union states and block new admission after confirmed
pending deletion while preserving existing runtime objects and leases.
Keep ordinary rule loading compatible, retain published source identity,
and scope strict dependencies to active and saved package versions.
Share effective-action/proposed-object validation for direct/reference SSH,
internal-first host fallbacks and unsupported Webhook actions.

Add the directed A1-A3, B1-B3 and C1-C3 production-path regressions and
bilingual pending-delete/error UI checks. Preserve v3 storage, encrypted
trust and save-only/reboot activation. Record red/green evidence and
unverified device/deployment prerequisites in R1-R3-review.md.
Preserve both reviewed histories, PR README/banner changes and existing
ordinary-reference deletion diagnostics. Keep package dependency guards
in the checked removal path and retain new-reference validation on saves.

Use the same recursive SSH binding gate for nested cold operations and
nonblocking edit admission with binding-before-transaction lock order.
Adapt production-path fixtures without weakening their assertions.

Validated combined runtime suite, exact HTTP fixtures, 33 unit/static
checks, 58 bilingual browser cases and isolated ESP-IDF 5.5.2 build.
Device acceptance remains unexecuted; see INTEGRATION.md for evidence.
@massif-01 massif-01 changed the title 修复自动化删除保护并更新 README 头图 修复自动化删除保护并实现规则配置包导入 Oct 10, 2026
@massif-01
massif-01 merged commit efc83ad into RMinte-AI:main Oct 12, 2026
4 checks passed
massif-01 added a commit to massif-01/TianshanOS that referenced this pull request Oct 12, 2026
…-prompts

RMinte-AI#47、RMinte-AI#48 在上游是以 squash 方式合并的,分支里保留的是它们原来的提交,直接合并会产生大量假冲突。
这里按正确的共同祖先(RMinte-AI#47 的头提交 bdc987d)做三方合并:我方改动与 RMinte-AI#48 改动没有重叠的文件,结果干净无冲突,
合并后相对 origin/main 的差异就只剩本 PR 自己的改动。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
massif-01 added a commit to massif-01/TianshanOS that referenced this pull request Oct 12, 2026
- version.txt 升到 0.6.2;新增 docs/releases/v0.6.2.md(汇总 RMinte-AI#47、RMinte-AI#48、RMinte-AI#49)和 v0.6.2-validation.md
- 合并到上游 main 后由 Actions 自动创建标签 v0.6.2 并发布 Release(check_release_state.py 的 main 路径)
- README 版本说明、静态资源 ?v= 标识同步为 0.6.2
- tests/ws_subscriptions/test_f1_f2.c:按帧的 type 字段判断“输出帧”,不再被原因代号里的 ssh_output 字样误判

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
massif-01 added a commit to massif-01/TianshanOS that referenced this pull request Oct 12, 2026
- RMinte-AI#47 新增的配置包签发根(CONFIG_TS_CONFIG_PACK_SIGNING_ROOT_SHA256,默认空)还没写进已跟踪的 sdkconfig,
  每次构建都会把它补上;这里先提交,保证从提交出来的干净检出构建不会改动已跟踪文件
- tools/make_firmware_manifest.py:发版时把干净构建的 6 个镜像/映射文件复制到 build/ 并生成 firmware-manifest.json,
  并检查构建版本与 version.txt、被构建提交一致(HASH 后带 d 的未提交修改构建会被拒绝);--verify 核对 build/ 与清单

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
massif-01 added a commit that referenced this pull request Oct 12, 2026
* fix: 失败响应保留 data,修复 SSH「测试连接」弹不出主机指纹确认框

SSH 主机页「测试连接」调用 ssh.exec。目标是新主机(1002)或指纹变化(1001)时,
固件把指纹放进 result->data,同时返回非 OK;HTTP 出口的通用失败分支只输出
{code,error} 并释放 data,页面取不到指纹就跳过确认框,用户只看到「连接失败」,
无法在该入口信任新主机,也无法核对并接受指纹变化。

- ts_webui_api.c:通用失败分支同样输出 result.data;没有 data 的失败响应不变。
  同一处顺带加固:code / error 写不进去(内存分配失败)时改发 HTTP 500,
  不再发出缺 code 的残缺 JSON(原有漏洞,由下面的分配失败遍历发现)。
  固件的指纹校验没有改动:确认必须带回当前连接上的指纹,否则继续拒绝,
  校验失败时不发凭据、不执行命令。
- 新增 tests/runtime/test_ssh_fingerprint_wire.py:运行生产的 ssh.exec / ssh.test
  处理函数和 HTTP 响应分支,检查线上字节,并存为契约夹具
  tests/fixtures/ssh-fingerprint;并在构建失败响应的每个分配点注入失败,
  结果只能是完整响应或 HTTP 500。
- 新增 tests/prompts/ssh-fingerprint.browser.test.cjs:把夹具原样回放给真实页面,
  中英文检查确认框内容、取消不信任不重试、确认只发送用户看到的指纹、
  确认时设备换密钥则不信任且不循环。
- test_keystore.py:原先钉着「其他接口的失败响应不带 data」,按新决定改为断言带上
  (只改了这一条断言和模拟字段名,共 2 行);密钥生成原有的失败阶段、清理结果、
  序列化失败返回 500 等测试全部保留,并逐一破坏生产代码确认它们仍会报错。
- docs/repair/ssh-fingerprint/RESULT.md:根因、修复与验证记录。

验证(未接触真机):运行时全套 41 项、静态 122 项、浏览器 249 项、发布检查通过;
ESP-IDF 5.5.2 完整编译通过。还原修复后运行时测试断言失败;夹具换成旧格式后
浏览器测试失败。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat: 指纹变化时的确认框改为明确的安全警告(可选,单独提交)

修复「测试连接」确认框之后,指纹变化(1001)和新主机(1002)弹出的是同一个框:
不说指纹变了,也不显示已登记的旧指纹,用户容易当成普通确认点过去。

- 指纹变化时:标题「安全警告:主机指纹不匹配」,说明可能原因(中间人攻击、服务器重装、
  IP 被分配给了别的服务器),同时显示已登记指纹和当前指纹,确认按钮为「更新主机密钥」,
  默认焦点在「取消」,回车等于取消。
- 新主机的确认框不变。
- 文案沿用已有的 securityPage.* 语言包条目,没有新增翻译。
- index.html 中 app.js 的缓存版本串提升为 0.6.1-lpmu6-keygen2-fp1。
- 浏览器测试补充:标题、按钮文字、两个指纹、默认焦点、回车不接受;去掉本提交后这些断言失败。

验证(未接触真机):静态 122 项、浏览器 249 项、运行时 41 项、发布检查通过。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* refactor: 删除 50 个无入口的旧界面函数及其残留

app.js 删除确认没有任何入口的旧函数 50 个(约 900 行):LED 旧控制与滤镜、
旧 SSH 主机管理、旧自动化变量页、旧动作行等;同时删除它们的 27 条 window
导出、因此失去用途的 selectedFilter / allVariables 变量,以及再也打不开的
「主机指纹不匹配」旧弹窗及其处理函数。app.js 减少约 47 KB(gzip 约 8 KB)。

确认方式:语法树可达性分析加全文搜索(含字符串里的函数名、window.xxx、
模板里的 onclick);删除后重新分析无人调用函数为 0;静态 122 项、浏览器
模拟 232 项与删除前一致。handleModalLogMessage 经 window 在运行时被调用,
不在删除范围内。

input-repair 浏览器测试原先直接调用旧矩阵页的 applySelectedFilter,
改为在现行的弹窗滤镜路径上验证同样的零值保留。macOS 27 对照清单与说明
文档同步。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(firmware): 手动触发被拒和 SSH 连接失败时,如实报告具体原因

样板间 B、C 需要固件多说一点;只加字段、不改任何业务,旧界面照常工作。

快捷操作点击后被拒(automation.rules.trigger):
- 引擎里新增 ts_rule_trigger_ex(),除返回值外还报告拒绝原因:引擎未在接收执行请求、
  规则被禁用、不允许手动执行、配置未加载、需要恢复、配置正在保存、规则正在执行、
  服务状态不允许启动、规则刚被修改。原来这些全被压成同一句 "Failed to trigger rule"。
  ts_rule_trigger() 行为不变(内部调用 _ex)。
- API 把原因代号写进 message(如 engine_not_accepting、execution_busy);
  忙碌类仍用 BUSY 错误码,旧界面还能说"设备正忙"。规则不存在、其他未知原因的返回不变。

SSH 连接失败(ssh.exec / ssh.test):
- 客户端记录连接停在哪一步:域名解析、端口被拒、主机不可达、超时、握手、主机指纹、
  认证(ts_ssh_get_fail_stage);错误文字 "SSH connection failed (code=N)" 与所有返回值不变。
- ssh.exec 的连接失败改用原因代号(ssh_dns_failed、ssh_port_refused、ssh_host_unreachable、
  ssh_connect_timeout、ssh_handshake_failed、ssh_auth_failed、ssh_connect_failed),
  data 里带 stage 和客户端原文 detail;ssh.test 保持原来的成功信封,另加 reason 和 stage。

测试(主机上运行生产代码,未接触真机):
- 引擎:逐一覆盖每种拒绝原因;未知规则不报原因;ts_rule_trigger / _ex 两个入口。
- SSH 客户端:逐一模拟每个失败阶段,并确认原有返回值与错误文字不变。
- test_failure_wire.py:规则保存/启停的回复和触发被拒经生产的 HTTP 响应链,线上字节存为
  tests/fixtures/failure-wire;test_ssh_fingerprint_wire.py 增加每种 SSH 连接失败的线上字节。
  夹具与代码不一致时测试失败,浏览器测试用同一批字节回放。
- 逐一破坏(原因搞错、原因丢失、阶段搞错),这些测试都会报错。
- test_stop_protocol 沿用的是 test_engine 的前半截,桩件原文未动,新开关用宏叠加。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(webui): 失败提示按"动作:原因。下一步。"重写——规则启停保存、快捷操作、SSH 测试连接(样板间)

三个样板间,作为后续铺开到全部页面的范本:
- 规则:启用 / 禁用 / 保存。写出规则名而不是内部 id;原因是人话("它正在执行中"),
  不再先说"提交的信息不符合要求"再跟内部代号;失败了不再写"已启用";成功说"已启用规则“x”",
  而不是 "…: ok";结果未确认单独成句并提示不要重复提交。
- 快捷操作:原来一句"当前不允许启动:请检查启用、手动权限及服务状态"塞了 5 种可能,
  现在卡片知道自己为什么不能启动(规则已禁用、不允许手动执行、引用未解析、详情读取失败、
  服务运行中/启动中/核验中/停止中/未确认已停止),分别说;点击后被固件拒绝的 9 种也各说各的。
  "请等待几秒后再触发其他模型"改为完整句子"刚刚触发过其他快捷操作,请等几秒再试。"
- SSH 测试连接:按连接停在哪一步说(解析不了主机名、对方拒绝连接、无法到达、超时、
  无法建立 SSH 加密连接、主机拒绝了用户登录);取消确认框是中性提示而不是"连接失败";
  确认时对方换了指纹会明确说没有信任它。

公共部分:
- 语言包新增 failure 目录:拼句格式、各操作名称、约 50 个"原因 + 下一步"(中英文)。
- api.js describeFailure():按原因代号取句子;查不到的代号按错误类别诚实地说,
  设备英文原文只放进折叠的"详情";缺参数时降级;绝不出现"说明暂时无法显示"。
- app.js 新的失败提示条:停留 8 秒,鼠标悬停或展开详情时暂停,有"详情"折叠(原因代号、接口、
  设备原文、阶段、时间)和关闭按钮;不会被随后的普通成功提示冲掉。其他页面的提示不受影响。
- runtimeSaveError 唯一的使用者被替换,已删除。

测试:
- failure-catalog:两种语言一一对应;每条文字符合写作规范(不带机器代号、中文里无多余英文、
  原因无句末标点、下一步是完整句子);固件会发出的每个原因代号必须有对应说法(从 C 源码读取)。
- failure-messages:提示条行为;每种语言下的标准句子;规则、快捷操作的真实页面流程,
  回放固件线上字节;SSH 连接失败 7 种 + 老固件无指纹 + 确认时换指纹。
- 受影响的旧测试同步:快捷操作被阻止、SSH 取消、规则保存的 Node 版测试;
  静态检查把 failure 目录整族纳入。

验证(未接触真机):静态 127、浏览器 305、运行时 43 项、发布检查通过;ESP-IDF 5.5.2 完整编译通过。

补充:详情只在有可看的内容时出现(页面自己判断出的拒绝没有接口、设备原文,就没有「详情」链接);
× 与「详情」同一行;新网页配旧固件(只发英文原文)时仍按错误类别诚实地说;
写操作没拿到设备回答一律说"结果未确认"。写法与新增方法见 docs/repair/failure-messages/README.md。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(firmware): 自动化引擎、动作模板、数据源失败时如实报告具体原因

只加字段、不改业务:失败时 message 换成固定的原因代号(网页按语言翻译),
数字错误码按"最近类别"选,旧网页仍能按类别说话。

- 引擎:启动/暂停被拒时按引擎当时状态报 engine_paused / engine_not_running /
  engine_unavailable;停止超时 engine_stop_timeout;重载读配置失败
  config_file_invalid(原始错误名放 data.detail);内存不足 no_memory
- 动作模板:数量满 template_capacity、重复 ID template_exists、LED 滤镜参数
  不合法 led_filter_invalid;服务绑定保护沿用 service_delete_protected
- 动作测试:没跑成功时报 action_failed / action_timeout,动作自己的输出放
  data.detail(折叠详情里看);找不到模板用 NOT_FOUND
- 数据源创建:表满 source_capacity;管理器未就绪 engine_unavailable。
  原先 INVALID_STATE 被说成"该 ID 已存在",其实 ts_source_register 对已有 ID
  是更新而不是拒绝,这句话是错的

结果拼装抽成小函数(fail_with / engine_refusal_token / action_template_write_reply /
action_execute_failure / source_register_reply),主机测试用生产代码 + 生产
HTTP 响应链生成线上字节存进 tests/fixtures/failure-wire/,漂移即失败。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(webui): 自动化页失败提示迁到统一目录(引擎、规则、动作模板、数据源)

按"动作+对象失败:原因。下一步。"重写,成功也说清做了什么、对谁:
- 引擎四个按钮、规则(手动触发/删除/读取)、动作模板(保存/测试/读取/删除)、
  数据源(启用/禁用/删除/创建)及三个列表的读取失败;列表读取失败不再被当成"空列表"
- 保存动作模板的必填项校验改为贴在输入框旁并自动定位(空的是变量名还是值分别提示)
- 规则编辑器里"没有动作模板"和"动作模板列表读不出来"是两回事,分别说
- 页面自己的脚本出错单独说"页面出了问题"并把脚本报错放进详情,不再冒充设备失败;
  写操作的网络中断一律说"结果未确认"(引擎启停等是用 GET 发出的写操作)
- 英文必填提示改成通顺的句子;详情里"设备原文"改称"原始信息"(网络错误文字不是设备说的)

顺带修复:数据源的启用/禁用开关一直先去查"同名规则",查不到就报错,请求从未发给设备
(v0.5.2 起;规则才需要版本检查)。现在直接请求 sources.enable/disable,并有回归测试。

测试:新增 tests/prompts/flows.browser.test.cjs,用真实页面 + 生产固件的线上字节逐场景
回放,中英文句子存进 tests/fixtures/failure-texts/automation.json(评审文案看这份);
pagekit.cjs 抽出公共的页面脚手架;目录测试改为读取每个调用点真正需要的文案,
并检查目录里没有无人使用的条目。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(firmware): 数据源连接测试与配置包导入导出失败时如实报告具体原因

只加字段、不改业务:失败时 message 换成固定的原因代号,数字错误码按"最近类别"。

- 配置包(.tscfg):新增 ts_config_pack_reason(),13 种结果各有自己的代号
  (不是为这台设备生成的 / 签名没通过 / 内容损坏 / 已过期 ……),库里的英文原句放进
  data.detail;automation.{sources,rules,actions}.{export,import} 六个接口改用它。
  导出权限不足 pack_not_allowed、设备证书不可用 device_cert_unavailable;
  写文件失败统一 storage_failed(errno 文字放详情)
- 规则配置包:验证和预览可以,真正导入一直返回 "source_read_only: encrypted rule
  import requires a recoverable writer"——这是功能没做完,不是"只读来源",现在如实报
  rule_import_unavailable(原文放详情)
- 数据源连接测试(REST / WebSocket / Socket.IO 代理):连不上 proxy_connect_failed、
  对方回了 HTTP 错误 proxy_http_error(状态码数字 + 回应开头放 data)、WebSocket
  连接出错/超时/没收到消息 ws_*、Socket.IO 没按协议回应/等不到事件 sio_*;
  原来都是写死的英文句子

主机测试用生产代码 + 生产 HTTP 响应链生成线上字节,存进 tests/fixtures/failure-wire/。
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(webui): 自动化页其余失败提示迁到统一目录(连接测试、配置包、选择列表)

- 数据源「测试」:REST / WebSocket / Socket.IO 的失败说清是哪个地址、为什么、怎么办;
  测试不改任何东西,所以断线说"无法连接到设备"而不是"结果未确认"(write:false)
- 配置包导出 / 验证 / 导入(数据源、规则、动作模板):失败写进对话框结果框,详情放提示;
  对象叫法放进 failure.kinds,英文句中小写;"请先选择文件"说明要选的是 .tscfg 配置包
- 变量 / 图片 / 主机的选择列表读取失败不再被当成"空列表";SD 卡未挂载单独一句
- 设备回的数字(如 HTTP 状态码)可以填进原因文案里的 {status}
- 英文表单提示补冠词、改成通顺的句子

测试:新增 41 个场景,中英文句子存进 tests/fixtures/failure-texts/automation.json。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(firmware): SSH 主机、密钥、指令、执行失败时如实报告具体原因

只加字段、不改业务:失败时 message 换成固定的原因代号,数字错误码按"最近类别",旧网页仍按类别说话。

- 新增公共函数 ts_api_result_reason(result, 类别, 代号, 详情)(原先只在自动化接口里有)和私有头
  ts_api_pack.h(配置包失败的统一回复,自动化与 SSH 两边共用)
- 主机指纹:保存/核对失败 known_host_failed;新主机 ssh_host_key_unknown、指纹变化
  ssh_host_key_changed(数字码 1002/1001 不变)——页面里其他调用 ssh.exec 的地方也能说清
- ssh.exec / ssh.test / ssh.copyid / ssh.revoke:登录信息不全 ssh_config_invalid、会话创建失败
  no_memory、密钥库没有这把密钥 key_not_found、公钥格式错 pubkey_invalid、服务器拒绝部署
  ssh_deploy_failed(服务器的 stderr 放详情)、撤销前检查/撤销失败、命令没执行成功
  ssh_exec_failed;连接失败沿用上一批的阶段代号
- ssh.exec_stream / ssh.cancel:另一条命令在跑 ssh_exec_busy、没能开始 ssh_start_failed、
  没有正在执行的命令 ssh_no_session、取消没送达 ssh_cancel_failed
- 主机 / 指令数量满 host_capacity / command_capacity;密钥重名 key_exists;服务不允许启动
  service_not_admissible
- 配置包导入导出(主机、指令)改用共用回复;写文件失败 storage_failed
- 连接失败的阶段映射挪进 SSH 客户端库(ts_ssh_connect_reason),WebSocket 的命令执行错误也用它:
  ssh_exec_error 消息现在带原因代号 + stage + detail;输出送达不完整 / 结果送达失败改成代号

主机测试按新代号更新(指纹相关的线上字节夹具随之改变)。
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(webui): SSH 主机、密钥、指令页失败提示迁到统一目录

- 安全页:已知主机指纹、主机记录、撤销公钥、删除/导出密钥、部署/撤销密钥的失败都说清
  "对谁做什么失败:原因。下一步。";部署/撤销里缺哪项就把光标放到哪项;主机列表读不出来时
  说明原因,不再只写"加载失败";撤销成功但没能移除本机记录单独说
- 指令页:保存/删除指令、执行(含 WebSocket 推回来的错误)、取消、启动服务、查看日志;
  辅助命令(读日志/查进程)失败不再显示成"无输出";必填项贴在输入框旁;ID 规则一次说全
- 配置包导出(SSH 主机、指令)失败写进对话框;导出句式改成"导出“名称”(种类)失败",
  中文里的英文种类(SSH 主机)前后不再粘连
- 行内失败提示现在也有「详情」链接,和提示条一致("点详情可以看到…"在哪里都成立);
  浏览器拒绝写剪贴板单独说明;列表过期(找不到这台主机/指令)说明是页面过期

测试:新增 53 个场景,中英文句子存进 tests/fixtures/failure-texts/ssh.json 和 ssh-commands.json;
几个旧测试跟着新句子更新。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* docs: 重写中英文 README,更新 0.6.1 项目说明

* fix: 防止动作模板删除产生失效规则引用

* docs: 更新中英文 README 的 TianshanOS 头图

* fix: implement durable save-only rule pack import

Persist complete versioned rule sets with one selector, exact-byte acceptance records, target-local revisions and restart-pending guards. Validate signing authority, recipient identity and dependencies without publishing imported rules at runtime.

Fix datasource toggle routing and add production crypto, migration, fault-recovery, engine/API and bilingual browser coverage.

* test: 声明删除引用主机测试所需的 POSIX 接口

* feat(firmware): 配置包校验、应用、导出失败时如实报告具体原因代号

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* feat(webui): 全站操作失败提示迁到统一目录(登录与改密、服务与重启、OTA、文件、网络、风扇、LED、证书与密钥、配置包)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(automation): close rule-pack deletion and dependency review gaps

Classify active/saved union states and block new admission after confirmed
pending deletion while preserving existing runtime objects and leases.
Keep ordinary rule loading compatible, retain published source identity,
and scope strict dependencies to active and saved package versions.
Share effective-action/proposed-object validation for direct/reference SSH,
internal-first host fallbacks and unsupported Webhook actions.

Add the directed A1-A3, B1-B3 and C1-C3 production-path regressions and
bilingual pending-delete/error UI checks. Preserve v3 storage, encrypted
trust and save-only/reboot activation. Record red/green evidence and
unverified device/deployment prerequisites in R1-R3-review.md.

* test: link rule-pack math and declare POSIX gate APIs on Linux

* feat(webui): 合并后补齐上游新功能的提示语(规则配置包导入、规则待重启/待删除、模板缺失)走统一目录

- 规则配置包验证/导入失败、结果未确认改由 failure 目录出句子(原因 22 条新增;旧的 rulePack.errors 整张表删除)
- 快捷操作:待删除/未生效的规则被拒时说明原因
- 固件:config pack 新结果码(时间未确认、未配置信任、签名者无权限、凭证变化)映射为原因代号;引擎拒绝待删除规则时给出 rule_pending_delete
- describeFailure 读取错误自带的 data(上游让 ApiOperationError 带上 data)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(webui): 验证中发现并修复的问题

- 44 个自动转换留下的“保存失败/设置失败”式提示改成“动作+对象”,并补上“结果未确认”的说法
- 设备原因不再被页面自己抛的 Error 吞掉:OTA 上传/服务器、证书、配置包都带原因代号(pageFailure)
- 登录失败只在表单里显示一次;目录读取失败不再套用“上传结果不受影响”;更新检查不再重复“检查更新失败”
- 密钥生成弹窗保留失败阶段与清理结果;规则保存缺少动作模板时说出模板名
- 折叠详情里的“接口”统一写成 storage.mkdir 这种形式;错误自带 data 时也显示阶段与原始信息
- 删除 198 条已无人使用的语言包旧键和 1 条已不存在的原因(规则导入已实现)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: 跟上合并与提示语迁移——新增页面场景测试、调用点参数检查,修正钉着旧句子的断言

- failure-catalog:新增“每个调用点都给足句子里的占位值”检查;一次列出全部问题
- flows:新增 system 组(登录、重启、服务、文件、网络、OTA、证书、配置包)和阻断删除弹窗、规则包导入场景;黄金句子重生成
- 上游测试适配:rule-pack/delete-reference 用 describeFailure 取句子;runtime 测试补桩与抽取清单
- ws 适配测试:补 ts_ssh_connect_reason 桩;帧类型按 type 字段判断,不再被原因代号里的 ssh_output 误判

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: 补上“待删除规则被拒绝”的引擎、线上字节与页面用例;去掉一处行尾空格

- 引擎:删除待重启的规则,手动触发给出 TS_RULE_REJECT_PENDING_DELETE(优先于“正在保存/正在执行”),自动评估保持静默
- 线上字节:新增 wire-trigger-rule-pending-delete.json,并让“每个拒绝原因都有代号”的循环覆盖到它
- 页面:触发该规则的句子写入黄金清单;快捷操作卡片在“待删除/还没生效”时各自给出原因

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* fix(webui): 文件选择器读取目录失败时保留设备给出的原因

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* 发布 v0.6.2:版本号、中文发布说明与验证记录

- version.txt 升到 0.6.2;新增 docs/releases/v0.6.2.md(汇总 #47、#48、#49)和 v0.6.2-validation.md
- 合并到上游 main 后由 Actions 自动创建标签 v0.6.2 并发布 Release(check_release_state.py 的 main 路径)
- README 版本说明、静态资源 ?v= 标识同步为 0.6.2
- tests/ws_subscriptions/test_f1_f2.c:按帧的 type 字段判断“输出帧”,不再被原因代号里的 ssh_output 字样误判

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* test: 证书页测试跟上新的失败提示方式

CI 的证书回归按名字从页面脚本里取 certError 并断言设备文字原样显示;迁移后该函数已删除,失败提示改由
failure 目录生成。测试改为:直接运行 api.js 里生产的 describeFailure 与 app.js 的 packBox/failureHtml,
断言句子来自目录、设备给的类 HTML 文字只以转义形式出现在折叠详情里、断线按“结果未确认”处理且只提交一次、按钮恢复。

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore: 同步 sdkconfig 新增配置项的默认值;新增 build/ 清单生成脚本

- #47 新增的配置包签发根(CONFIG_TS_CONFIG_PACK_SIGNING_ROOT_SHA256,默认空)还没写进已跟踪的 sdkconfig,
  每次构建都会把它补上;这里先提交,保证从提交出来的干净检出构建不会改动已跟踪文件
- tools/make_firmware_manifest.py:发版时把干净构建的 6 个镜像/映射文件复制到 build/ 并生成 firmware-manifest.json,
  并检查构建版本与 version.txt、被构建提交一致(HASH 后带 d 的未提交修改构建会被拒绝);--verify 核对 build/ 与清单

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* 发布 v0.6.2:更新 build/ 默认刷机镜像到 0.6.2

- 从干净检出的提交 774c3a9 用 ESP-IDF 5.5.2 构建;版本 0.6.2+774c3a97.10130158,主固件 2,226,000 字节
- build/firmware-manifest.json 记录版本、被构建提交、源码输入哈希和 6 个文件的 SHA-256(tools/make_firmware_manifest.py 生成,--verify 通过)
- 验证记录补充:镜像来源与更新方法、证书回归已在本地跑通

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
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.

1 participant