任务:转换脚本、合成全链路验收和真机运行脚本
依据: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 合并之后
要做的
- 转换脚本(集成者在本机对只读副本跑;只打印计数和摘要;输出写到参数给的暂存目录,从不写进仓库):check-runtime-contract.ts <合约文件> --expect <摘要>,用 case-compile 的合约解析器核形状和身份,不改字节;bed-topology.ts <网关拓扑回答> --services <文件> --reserved <文件>,机械转换成 test-bed-manifest@1,目标类型从 test-bed 包的 manifest 模块导入,源标签表只在脚本里写一处,撞名、不认识的标签、没前缀或掩码是假定的地址拒绝(或按显式参数列出后丢弃),两个包的清单解析都要通过,打印源回答的摘要;runtime-profile.ts 从环境核对的回答、转换后的清单和合约起草配置,所有者要确认的部分留成显式的待填项,用包的配置解析器校验。文件>文件>网关拓扑回答>摘要>合约文件>
- 合成全链路验收(tests/acceptance 新文件):真 Hub 装配、临时 PostgreSQL、回环网关桩、脚本化模型;合约、配置、清单都是编的,地址只用 192.0.2.0/24。发布三份文档、登记执行器、job precheck、预订、计划运行(脚本化模型交槽位,驳回一次再修好)、生成脚本出工作簿、上传、核验运行、job dispatch、结果、释放。另走三条:上传的工作簿改一个字节,核验不支持,派发被拒;网关丢回执,冻结,对账后继续;只有计划运行(缺工作簿)不能派发。
- 真机运行脚本 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 问:指定脑图里
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 问:合约、台架清单和运行环境配置都算接入方式。 三份由转换脚本生成,集成者用
circle-hub doc put发布,不再等所有者确认。文档名固定:台架清单bed-topology(test-bed 包的默认名),运行器合约runner-contract,运行环境配置runtime-profile;计划运行绑定的就是这三个名字。每个脚本最后一行打印要运行的doc put命令(只有这些文档名和暂存目录里的文件名),自己不运行。测试断言这一行的形状。 -
第 3 问:合约身份取网关钉住的那份。
check-runtime-contract.ts --expect的值由集成者从部署的网关只读取得,配置里的runtime.contract_sha256用同一个值;这个值不进代码、文档和测试。只改脚本的用法说明。 - 第 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 问:地址。
- 设备命令行里可以出现本台架其他设备的地址;台架外的地址照旧只能经替换表换进来。这一件已在 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 问:只读探测逐台看版本和接口地址。 只有一处声明:执行器定义(集成者 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、8 问: 见
gateway-request-ids.md。落到这条分支上的只有一件:device-run.ts跑完第一个用例先停,打印它结果回答的引用;集成者据它认证“结果按派发身份归属”(那一页第 12 条),更新执行器定义以后,再跑后四个。没认证时照现在记无判定。 -
第 9 问:运行器在 Hub 之外的动作照常接受,作为观察记进结果。 Hub 侧见
gateway-request-ids.md第 14 条。这条分支上:device-run.ts把结果里这一段原样打印;验收里(要dispatch-gateway先合并)合成运行日志带一项的标记、缺另一项,结果里一项写“本次日志里有这一项的标记”,一项写“没有”。 -
第 10 问:不通知别人。 结论照现在带“未排除本地操作”(
local_ops_not_excluded)。device-run.ts逐用例打印结论时连限定一起打印,测试断言有它。 - 第 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 条。
- 集成者先在现有部署和环境配置里只读找这把密钥(只经
-
第 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,所以上机用另一份主目录,它的钩子放行这一条。 - 验收(
tests/acceptance的那一个新文件)照上面加场景:两个用例先后派发;一个四类以外、参数能定角色的方法用到并通过;本台架另一台设备的地址出现在设备命令行里并通过(要第 5 条 case-compile 那件先合并);逐台探测不符时“未发出”,没上机的拒绝记“目标拒绝”、不冻结,丢回执后按请求 id 核对、接着读结果,副作用逐项列出(这几件要dispatch-gateway先合并)。依赖没合并的场景等它合并再写,不先写成跳过。
完成后在终端里回报:提交号、测试数量、没做完或拿不准的地方。