任务:转换脚本、合成全链路验收和真机运行脚本

依据:docs/hub-r4-device-run.md(上真实台架的设计,第 1–5 节和第 8 节的分工),决定 26、27(docs/hub-r4-progress.md),理论文档第 6、7 节;模型用 node tools/show-element.ts D02 D03 D04 D07 B05 B06 B07 V03 W05 P05 读。

  • 分支和工作树:hub-device-run,目录 circle-hub-hub-device-run(依赖已装好)
  • 可以改的路径:scripts/real/convert/**(新);scripts/real/accept/device-run.ts(新);tests/acceptance/ 的一个新文件。不改 hub/、packs/、contracts/。转换脚本部分在 hub-case-runtime 合并后即可开始,验收部分等 hub-case-workbook 和 hub-job-dispatch 合并。
  • 规则:同前(不推送、不建 PR、不发布、不部署;不碰 7420、钥匙串、环境配置文件和任何真实系统;测试只用合成数据,地址只用 192.0.2.0/24 和 2001:db8::/32;git commit -s)。所有者的规矩:未知只能是过渡状态,写明缺的事实和下一步动作,流程自动走这一步;Hub 只核验,不生成内容、不调用模型;枚举只来自 contracts/generated(运行时用按包生成的快照)。
  • 依赖:hub-case-runtime、hub-case-workbook、hub-job-dispatch 合并之后

要做的

  1. 转换脚本(集成者在本机对只读副本跑;只打印计数和摘要;输出写到参数给的暂存目录,从不写进仓库):check-runtime-contract.ts <合约文件> --expect <摘要>,用 case-compile 的合约解析器核形状和身份,不改字节;bed-topology.ts <网关拓扑回答> --services <文件> --reserved <文件>,机械转换成 test-bed-manifest@1,目标类型从 test-bed 包的 manifest 模块导入,源标签表只在脚本里写一处,撞名、不认识的标签、没前缀或掩码是假定的地址拒绝(或按显式参数列出后丢弃),两个包的清单解析都要通过,打印源回答的摘要;runtime-profile.ts 从环境核对的回答、转换后的清单和合约起草配置,所有者要确认的部分留成显式的待填项,用包的配置解析器校验。
  2. 合成全链路验收(tests/acceptance 新文件):真 Hub 装配、临时 PostgreSQL、回环网关桩、脚本化模型;合约、配置、清单都是编的,地址只用 192.0.2.0/24。发布三份文档、登记执行器、job precheck、预订、计划运行(脚本化模型交槽位,驳回一次再修好)、生成脚本出工作簿、上传、核验运行、job dispatch、结果、释放。另走三条:上传的工作簿改一个字节,核验不支持,派发被拒;网关丢回执,冻结,对账后继续;只有计划运行(缺工作簿)不能派发。
  3. 真机运行脚本 scripts/real/accept/device-run.ts(集成者在所有者答复以后才跑):按设计第 1 节的次序调用已部署 Hub 的命令,每一步先打印要做什么,派发前要求输入确认;只打印阶段、码、摘要和逐用例结论;不进 CI。

必须有的测试

  • 转换脚本:合成拓扑的好坏各一组(撞名、不认识的标签、没前缀、假定掩码、重复地址);身份不符的合成合约和旧副本被拒;允许集合不是子集的配置被拒;标准输出只有计数和摘要(断言),输出文件只在给定目录。
  • 合成全链路验收全部通过;没有 sandbox-exec 时记跳过,不算通过。
  • 真机脚本的参数解析和确认提示有单元测试,不联网。
  • 全仓检查:词表扫描零命中、不加例外;仓库卫生检查(没有真实地址、凭据、绝对路径)通过。

完成后在终端里回报:提交号、测试数量、没做完或拿不准的地方。真实台架上的任何动作都由集成者做,并且要等所有者答复设计第 6 节的问题。

决定 35 的答复落到这里

依据:

  • 决定 35 的“上真实台架”一组(docs/hub-r4-progress.md)答了设计第 6 节的 12 问,上面最后一句要等的答复已经有了。真实台架上的动作仍然只由集成者做。
  • 模型:node tools/show-element.ts D02 D03 D04 D07 B05 P07 V03 T01。模型不用改。设计 docs/hub-r4-device-run.md 由集成者照下面改写:第 1 节(五个用例、谁做哪一步)、第 2 节(服务和保留段取自拓扑回答)、第 3 节第 6 条(地址)、第 4 节第 1、3、9 步(租约只在运行期间持有、逐台探测)和第 5 节(本轮开放的方法)。
  • 集成者 10-23 晚按审读改过这一节:占台架没有时长上限;探测只由执行器定义声明;同台架设备的地址已在 hub-case-runtime 做完。

  • 分支和工作树:不变,hub-device-run,目录 circle-hub-hub-device-run,依赖照上面。
  • 这一节新增的路径:scripts/real/convert/auto-cases.ts、scripts/real/convert/probes.ts 和它们的测试,都在上面的 scripts/real/convert/** 里。落到 packs/workflow/case-compile/** 和 hub/dispatch/** 的几件不在这条分支上改,下面每条写明去哪、排在谁后面。
  • 规则:同上。另外:autoid、文档号、合约摘要、探测用的命令和回答都是部署数据,只写进参数给的暂存目录,不进 Git,也不进测试;测试里的这些值全是编的。
  1. 第 1 问:指定脑图里 auto 是 YES 的 5 个用例全编全上。
    • auto-cases.ts <脑图原件> --out <暂存目录>:先用核心的 KityMinder 解析器(hub/refs/parsers/kityminder.ts)解析,再用 case-compile 的 extractCases(functions/cases.ts,case_rule 和计划运行用的一样)抽用例,留下 auto 正好是 YES 的,按文档里的次序把 autoid 写进暂存目录。标准输出只有个数和摘要。个数不是 5 就停,写明找到几个,不猜也不补。
    • 绑定:脑图在敏捷平台上有文档号就绑 agile:mindmap:<文档号>,没有才照第 2 节规整后上传。有没有文档号由集成者先只读查。
    • 分工:每个用例的第 4 到 6 步(模型在 Circle 里填计划槽位、修复,客户端脚本生成工作簿、上传,开核验运行)由 Circle 会话做;device-run.ts 只跑 Hub 的管理命令:打印 doc put 行、执行器核对、job precheck、派发、状态和逐用例结论。派发前它停下,等集成者输入这个用例的核验运行号;它不写任何槽位内容,也不调 MCP 工具。
    • 占台架:每个用例自己一条谱系。先在 Circle 会话里经 test-bed 占住台架,计划运行以占台架那次运行为前驱,核验运行、派发都在这条谱系上;这个用例的作业有终态以后释放,下一个用例再占。租约只在运行持有期间有效,没有时长限额(决定 35 第 15 项),device-run.ts 里不写任何时长上限。
    • 一次一个,前一个有了终态才开下一个;前一个留下未知(目标冻结)就停,打印对账的下一步,后面的不派发。
    • 测试:合成脑图里 5 个 YES,另有 yes、空串和没有这个字段的,只取那 5 个;4 个、6 个都停;验收里两个用例先后派发,各有自己的 S;第一个丢回执冻结时,第二个不派发。
  2. 第 2 问:合约、台架清单和运行环境配置都算接入方式。 三份由转换脚本生成,集成者用 circle-hub doc put 发布,不再等所有者确认。文档名固定:台架清单 bed-topology(test-bed 包的默认名),运行器合约 runner-contract,运行环境配置 runtime-profile;计划运行绑定的就是这三个名字。每个脚本最后一行打印要运行的 doc put 命令(只有这些文档名和暂存目录里的文件名),自己不运行。测试断言这一行的形状。

  3. 第 3 问:合约身份取网关钉住的那份。 check-runtime-contract.ts --expect 的值由集成者从部署的网关只读取得,配置里的 runtime.contract_sha256 用同一个值;这个值不进代码、文档和测试。只改脚本的用法说明。

  4. 第 4 问:合约启用的(对象,方法)全开,拒绝表照旧。
    • runtime-profile.ts 起草 allowed 时取合约里每个启用的(对象,方法),不再只取四类。每个参数要落到 case-compile 格式表里的一种角色(设备命令行、主机 shell、字面断言、等待);合约里看不出的,草稿留成显式的待填项,由集成者填,--check 再用 case-compile 的配置解析器核。
    • 有参数落不到这四种角色的组合不开:核验对这个参数什么都查不了,开了就绕过拒绝表,碰底线(不重启设备)。草稿把它们单列成“未开”,每项写缺的事实(哪个参数、为什么落不进)和下一步(case-compile 为它加角色和检查,另写一页)。
    • 拒绝表照旧:设备上拒绝重启、清配置、升级、HA 这类整机命令,主机上拒绝重启、关机、格式化这类命令,写在配置的 denied.device、denied.host 里;命令文字是部署数据。
    • --check 另核:allowed 是启用组合的子集;每个启用的组合要么在 allowed 里,要么在“未开”里,不重不漏。
    • 测试:合成合约 6 个启用组合(四类各一、一个参数能定角色的别的方法、一个有落不进角色的参数)加一个禁用对象:开 5 个、未开 1 个,禁用的两边都没有;待填项没填时 --check 不过;漏列、多列都不过。
  5. 第 5 问:地址。
    • 设备命令行里可以出现本台架其他设备的地址;台架外的地址照旧只能经替换表换进来。这一件已在 hub-case-runtime 的 b9da849a 做了,随 hub-case-runtime 和 hub-case-workbook 合进主线,不再转交,也不再升版;验收里“本台架另一台设备的地址出现在设备命令行里并通过”现在就写。
    • 在用和保留的地址段从网关拓扑只读取得:bed-topology.ts 去掉 --services 和 --reserved 两个参数。服务取拓扑回答里的 services;在用的就是拓扑里每台主机和设备的接口地址,清单本来就记;保留段取拓扑观察里的未登记邻居(网关仓库 gateway/bed.py 记在 observation.unregistered_neighbours:在二层上回应、又不是本台架登记的地址,别人的虚拟地址、HA、网关都在这里),每个写成一个单地址的保留段。拓扑里没有的不猜、不编。
    • observation.unregistered_neighbours 的形状是 {<跳板机接口>: [<IPv4 文字>…]},只来自 ARP:只有 IPv4,是采集那一刻的快照。不在本台架任何设备接口网段里的邻居不是台架地址(case-compile 本来就把这类地址读成台外),不进 reserved,屏幕打印个数,地址只写进暂存目录。
    • 服务的协议照一张对照表换:http、https 记 tcp,dns 记 udp;表外的协议整份停下,写明是哪个服务、哪个字段。屏幕打印各类个数。
    • 执行器的只读核对比的是声明的 JSON 指针下内容的规范摘要,指针要同时覆盖拓扑、观察和服务,这样保留段和服务变了也能发现。
    • 测试:合成拓扑回答有两个网段内的未登记邻居和一个网段外的,清单的 reserved 正好是网段内那两个单地址段,两个包的清单解析都过;没有邻居时 reserved 是空列表;回答里没有 services 就整份拒绝,写明缺哪个字段;http、dns 服务照对照表换,表外的协议停下。
  6. 第 6 问:只读探测逐台看版本和接口地址。 只有一处声明:执行器定义(集成者 10-23 晚按审读改正:真实合约里没有只读命令的标记;网关的探测工具只收一条命令和设备序号,不经过运行器,所以不放进运行环境配置)。
    • 由 hub/dispatch 发探测;座位经绑定的运行环境配置和台架清单对到网关的设备序号。
    • 每个座位两项,分开写成 version 和 interfaces 两个键(不用另造“用途”取值),每项是命令原文加一条只有一个命名分组的正则。网关收的命令形状(例如只收 show 或 get 开头的一行)也写在执行器定义里,是部署数据,包和核心代码里都不硬写。
    • 期望:version 取出的值要等于锁定构建的某个名字(基础构建名或知识库里记的别名),不做手工规整;interfaces 取出的地址集合等于清单里这个座位那台设备的地址集合。不符就“未发出”,写明哪台哪一项;取到的原文只进受限的结果。
    • 执行器定义里探测的形状由 dispatch-gateway(gateway-request-ids.md 第二步,第 13 条)定;probes.ts 等那个形状定下以后,只起草这一份。缺某个座位的某一项时,草稿写明缺哪一项,下一步是集成者从台架只读核对后补进执行器定义。
    • 这条分支上 6a125df0 往运行环境配置加的 probes(case-compile 的 functions/profile.ts、functions/probes.ts、tables/probe-format.json、README、pack.json)撤掉,case-compile 回到和 hub-case-workbook 一样,免得两份不同的包用同一个版本号。
    • 测试:合成执行器定义里两个座位、各两项;一项命令不合声明的形状被拒;缺一项时写明缺的事实;回显取不到值;版本用别名时照样相符;地址集合和清单逐项相等。真实命令由集成者写进部署的执行器定义,不进仓库。
  7. 第 7、8 问: 见 gateway-request-ids.md。落到这条分支上的只有一件:device-run.ts 跑完第一个用例先停,打印它结果回答的引用;集成者据它认证“结果按派发身份归属”(那一页第 12 条),更新执行器定义以后,再跑后四个。没认证时照现在记无判定。

  8. 第 9 问:运行器在 Hub 之外的动作照常接受,作为观察记进结果。 Hub 侧见 gateway-request-ids.md 第 14 条。这条分支上:device-run.ts 把结果里这一段原样打印;验收里(要 dispatch-gateway 先合并)合成运行日志带一项的标记、缺另一项,结果里一项写“本次日志里有这一项的标记”,一项写“没有”。

  9. 第 10 问:不通知别人。 结论照现在带“未排除本地操作”(local_ops_not_excluded)。device-run.ts 逐用例打印结论时连限定一起打印,测试断言有它。

  10. 第 11 问:TLS 加固定的实验室 CA,只用运行权限的那把密钥。
    • 集成者先在现有部署和环境配置里只读找这把密钥(只经 scripts/real/ 的脚本,只打印名字、有无和长度),找到就经标准输入用 circle-hub executor add 登记;找不到才请所有者经标准输入给。端点要 https、带固定 CA,hub/dispatch/definition.ts 已经这样要求。
    • hub-variables 第一步部署以后,钥匙经标准输入交给 circle-hub secret set --name GATEWAY_RUN_KEY:先登记执行器,定义里写 "credential": "${secret:GATEWAY_RUN_KEY}",secret set 才收;executor add 不再读标准输入。在那之前照旧经 executor add 交,部署以后 executor update 加上 credential 时迁移过去,不用重交。
    • device-run.ts 第 0 步核对:带参数 --executor <exe_…>;暂存目录里的执行器定义是 https、带 CA;把 {...暂存的定义, id: <那个 id>} 按 hub/dispatch 的 ExecutorDefinition 解析(默认值照样补上),规范 JSON 后的 SHA-256 等于 circle-hub executor list 给这个 id 打印的摘要;不等就停。每次 executor update 以后重做一次第 0 步(第 7 条在第一个用例和后四个之间会更新定义)。
    • hub-variables 合并以后,第 0 步按 hub/dispatch 的源定义模式(ExecutorSource)解析暂存的定义(里面可以有 ${var:…}),照旧比 executor list 打印的“定义摘要”(源定义的),另核同一行后面加的解析后摘要;只改变量时不用 executor update,源定义的摘要也不变。device-run.ts 的这一处和 28-device-run.test.ts 由 hub-variables 改。
    • “只有运行权限”的机械核对见 gateway-request-ids.md 第 15 条。
  11. 第 12 问: 见 script-distribution.md。这一轮集成者照 case-compile 的 SKILL.md 手工放进所有者 Circle 的技能目录,按 pack.json 的摘要逐个核对;那一页的 circle-hub local-tools verify 合并以后改用它核。上机用的 Circle 主目录由集成者另备:评审用的专用主目录的钩子只放行 Hub 的六个工具和 write_todos,第 5 步要 execute 跑 node <技能目录>/tools/*.ts,所以上机用另一份主目录,它的钩子放行这一条。

  12. 验收(tests/acceptance 的那一个新文件)照上面加场景:两个用例先后派发;一个四类以外、参数能定角色的方法用到并通过;本台架另一台设备的地址出现在设备命令行里并通过(要第 5 条 case-compile 那件先合并);逐台探测不符时“未发出”,没上机的拒绝记“目标拒绝”、不冻结,丢回执后按请求 id 核对、接着读结果,副作用逐项列出(这几件要 dispatch-gateway 先合并)。依赖没合并的场景等它合并再写,不先写成跳过。

完成后在终端里回报:提交号、测试数量、没做完或拿不准的地方。