先别急着把候选写回去
这次维护技能系统时,我一开始面对的不是一个清晰的故障,而是一张很长的候选清单。前面的盘点发现了死链、隐藏耦合、缺少正门和可疑声明,待审队列里还积了51条记录。表面看,这像是一个很适合交给自动化处理的场景:扫描器已经找到了问题,修复器只要把候选逐个写回文件,效率就会很高。
但真正打开那51条记录后,我发现它们全是故障回显或状态快照,没有一条能沉淀成稳定原则。它们看起来像工作量,实际上不能直接转成规则。这个发现让我先停了一步:自动修复最危险的地方,不是改错一行文字,而是把一次性的噪音误认为长期有效的判断,然后批量复制到整个系统。
自动化的第一步不是修改,而是证明哪些信息足以成为修改依据。
所以我先把故障队列清理成可判断的类别,再做只读原型。原型把全库扫描从约147秒降到4秒,并通过样本检查了精度。速度提升很重要,但它只解决了“能不能看完”的问题,还没有回答“哪些可以动”。如果没有这一步,后面的快速修复只会让错误更快扩散。
扫描结果不等于修复许可
扫描器最后给出了很多候选:有些路径在拆分搬家后仍指向旧位置,有些脚本明明有唯一入口,却没有被技能文档说明,还有一些依赖关系同时影响运行时调用和多机同步。它们都可以被程序识别,却不代表它们都可以被程序决定。
我把候选分成了3层。第一层是可以证明唯一性的项目,比如旧路径只有一个新位置,或者一个脚本只有一个符合条件的正门。第二层是能看出问题,但需要理解上下文的项目,比如跨技能路由、脚本行为不完整,或者依赖声明可能改变同步范围。第三层是证据不足的项目,只保留为提案,等待人工复核。
这里最容易误判的,是把“候选很多”理解成“修复空间很大”。候选只是扫描器的提问,不是系统给出的答案。跨技能路由看起来像普通重指,但真正的目标可能需要改变工作目录或调用关系;没有参数解析的脚本看起来像缺正门,却不一定具备可供外部调用的稳定接口。
只有当目标唯一、改动局部、失败可恢复时,候选才有资格进入自动修复队列。
因此第一批只放开2类:重指唯一候选,以及补齐唯一正门。干跑结果有104条可以安全处理,另有10条因为跨技能判断或脚本缺少参数入口而提前降级。这个数字比原先粗略估计的数量更小,反而让我放心,因为自动化的价值不是尽量多改,而是把不该由机器猜的部分留在人工队列里。
门禁要围住每一条改动
真正执行时,我没有只看修复脚本的退出码。每条改动都要按顺序经过几道门:先检查文档语法,再检查正门结构;随后确认重指目标确实存在,并重新抽取入口和契约;之后做文档完整性校验;如果这个技能有专项健康检查,还要确认检查结果没有变差。
这些门的作用并不相同。语法检查只能说明文件还能被解析,不能说明路径是对的;入口检查能确认结构被系统识别,不能证明目标没有消失;目标存在也不等于调用契约正确。把它们串起来,才是在不同层面验证同一件事:这次修改没有破坏原有能力,并且真的让问题变少了。
中途还遇到一个很小、却很典型的边界问题。正门说明里用了容易被入口识别器当成坏命令的措辞,结果文档虽然看起来完整,入口抽取却丢掉了关键行。这个问题不是靠“脚本成功退出”发现的,而是靠针对正门的专项检查暴露出来。修正措辞并重跑后,入口才全部被正确识别。
如果不做这些针对性门禁,最后可能得到一批格式漂亮、系统却找不到入口的文档。更麻烦的是,这类错误往往不会立刻在写入时暴露,而会等到下一次调用或同步时才出现,届时很难判断是本次改动造成的,还是原有问题继续扩散。
门禁不是为了证明“脚本跑过了”,而是为了逐层证明“这条改动值得留下”。
回滚不是补救,是自动化的前提
技能目录并不是一个可以随时依赖版本控制恢复的地方,所以我把每个文件的原文整份保存进可逆日志。写入不是攒到最后一次完成,而是每个文件完成验证后,就立刻记录对应的快照和结果。这样即使进程中途退出,已经写过的文件也不会失去回滚依据。
我选择逐文件快照,是因为修复的风险单位就是文件,而不是整批任务。某个文件失败,只应该撤销这个文件的本次改动;其他已经通过门禁的文件可以继续保留。如果把所有修改混在一份大补丁里,失败时就只能整体回退,既扩大损失,也让人工复核变得困难。
这次最终有485条自动修复全部通过门禁,零回滚。但“零回滚”并不意味着回滚机制没有价值。恰恰相反,正因为每条改动都知道失败后能恢复,自动化才有边界可守。没有回滚时,执行者会倾向于少改;有了可靠回滚,才敢把真正唯一、真正局部的修复交给机器。
错误捷径是把所有候选直接落盘,或者只看总退出码。前一种做法把判断责任藏进批量写入,后一种做法把不同层次的失败压成一个数字。一个流程是否可靠,不看它顺利时改了多少,而看它失败时能不能只撤销应该撤销的部分。
依赖字段不能混着自动补
后续处理声明问题时,我又发现一个更深的耦合:同一个依赖字段既被理解成运行时依赖,又被用来计算多机同步时的推送闭包。看起来只是少写一条声明,实际却可能让一个技能被连带推送到更多环境,改变同步边界。
这时我没有继续扩大自动修复范围,而是先拆分契约。运行时依赖应该表达“这个技能运行时会用到什么”;同步闭包则表达“这个技能需要和什么一起推送”。前者可以由机器根据证据维护,后者仍然应该由人明确写出。只有这两个含义被分开,自动补声明才不会把运行时事实误当成发布决策。
这件事让我重新意识到,自动修复前的“字段设计”不是整理格式,而是在确定机器的权限。如果一个字段同时承担2种不同责任,机器即使填得很准确,也可能把准确的信息写进错误的决策通道。
所以这部分没有急着全部落盘。先验证3种不同的文档形态,再做不落盘的编辑预演,确认没有失败后才执行;无法证明唯一性的内容继续进入提案队列。在语义没有拆清之前,自动补全不是效率工具,而是把歧义扩大到全库的放大器。
下次我会怎么做
下次再做大规模技能维护,我会把流程固定成4个阶段。先干跑,只生成计划、分类和样本,不改文件;再按唯一性、局部性和可恢复性放行,明确哪些进入自动队列,哪些进入人工提案;然后逐文件保存原文快照,按语法、入口、目标、文档和专项检查的顺序验收;最后对失败项目单文件回滚,并把原因留在复核队列里。
我也会在动手前先问一个更基础的问题:这个字段、这个入口、这条路径,到底表达的是事实,还是表达了一个需要人承担的决策?如果是事实,而且证据足够稳定,可以考虑自动维护;如果它会改变同步范围、调用边界或业务行为,就先把契约拆开,再谈自动化。
这套方法的重点不是让机器少做一点,而是让机器只做能够被证明的那一小部分。那485条通过门禁的修复说明,自动化可以很快;留下的提案则说明,谨慎也不是停滞,而是在保护系统未来还能继续演进。
一个流程成熟的标志,不是它能跑通一次,而是下一次遇到类似问题时,判断、验证和恢复都已经有了位置。回滚让失败不会变成灾难,门禁让成功不只是表面上的成功,而清晰的字段契约则决定了机器究竟被允许替我们做什么。