第一百〇九章 TODO
那行注释在手机屏幕上看起来很短。
`// TODO: verify isolation context before fault propagation`
图片存了,给麦景行回:「能查到这个提交是谁做的吗?」
「正在看,git log 有记录,等我一下。」
他去倒了杯水,回来手机上多了一条消息。
「查到了。不是邓工。」
「是谁?」
麦景行停了几秒,发过来:「用户名 `wl_g1dev`,邮箱是泉华内网的,名字显示魏磊,G1 开发组。」
把这个名字记下来。G1 开发组的人,跑进了 `common/` 里。
「提交时间?」
「5月12号,下午两点四十七分。」
5 月 12 日。合同是 5 月初签的,初次扫描是第二周。5 月 12 日,他们这边刚刚开始第一轮代码摸底。
时间线对上了。
「魏磊现在在哪个组?」他问。
「不清楚,要查的话得走泉华内部。」
「加上去之前,原来的代码是什么样的?」
「空的,」麦景行回,「函数体里只有几行基础的状态写入,其他什么都没有。魏磊加了这一行,推上去,没有其他修改。」
空函数体,加一行 TODO,推了。
这不像在修代码,更像是在给将来某个会来查的人留一个信号,然后走掉了。
他给豆包发了消息,附上截图。

豆包回来很快,发了一段解释。
`verify isolation context before fault propagation`,字面意思:在传播故障信号之前,先确认隔离上下文是否就绪。工控安全里对应的场景是:如果 `set_system_fault` 被调用时,调用方和周围模块之间的隔离没有完成,比如共享状态没清空、内存锁没释放,故障信号传播出去,接收方拿到的就是中间态。
「后果是什么?」
「取决于接收方的处理方式。最坏的情况是接收方基于这个中间态继续运行了一段时间,然后在错误的时间点停止。工控里,停机指令出了顺序,有时比不停更危险。」
把那段话看了两遍。
他们一直在查 ERR_HANDLER 什么情况下会触发。豆包解释的这个,是触发之后可能出什么岔子。两件事,都没有写在任何文档里。
这行注释现在不只是一个未完成事项了。
有人在内部意识到了这个风险,写下来,然后就停在那里了,没有任何后续。
把豆包的分析截图发给乔木,什么都没说。
回复来了,用了大概两分钟。
「这改变了一件事。」
「说。」
「之前我们来这里,是帮泉华把已知的东西写清楚。现在这个情况,至少有一个人在内部已经知道这里有问题,但这件事没有走到任何地方。」
「意思是?」
「内部风险已知,没有对外披露。比注释缺失严重一个等级,法律属性不一样。」
没有立刻回。
「你觉得方总知道吗?」乔木发过来。
这个问题在脑子里转了一会儿。
方晓晨在 5 月初把他们引进来,说是 G1 代码注释有合规缺口。监管检查在十一月。引进审计组之后第二周,G1 开发组有人去碰了 `set_system_fault`,加了一行 TODO 然后消失了。

如果方晓晨知道,引进他们就是为了让外部审计把这个风险记录在案,把责任分出去一部分。如果她不知道,这件事就是内部有人意识到了,但从来没有向上汇报过的坑。
两种情况,处理方式完全不同。
「现在还不知道,」他给乔木回,「先不找她。」
「好,先查清楚这个 TODO 背后的实际意义,再决定怎么谈。」
给麦景行发了新任务:查一下 `set_system_fault` 在整个代码库里还有没有别的调用方,除了 G1 旁路那一处。
「有,我进来之后扫过,两处。」
「旁路是 G1 的那处,另外一处?」
「在 G2 模块里,文件是 `g2/recovery_handler.c`,函数名 `handle_recovery_fail`。」
「G2 那边也在调?」
「路径不同。G2 那边是通过条件宏展开的,`ERR_HANDLER` 这个宏在 G2 里也有,展开出来的函数名一样,也是 `set_system_fault`。」
「`ERR_HANDLER` 在 G2 里的定义在哪个文件?」
他突然想到什么,在麦景行回之前又发了一条:「让我猜一下——如果这个宏定义在 `common/` 里,就不是 G1 的问题,也不是 G2 的问题。」
麦景行停了一下。
「等一下。」
「怎么了?」
「头文件是 `common/err_handle.h`,G1 和 G2 的宏定义在同一个里面。」
「对的。」
这个情况在脑子里过了一遍。
两套独立的合同模块,通过同一个 `common/` 里的宏,调用同一个函数。设计上两边隔离,但入口是一个。
魏磊改的是 `common/err_handle.h`,是 BasePlatform 的地盘,不是 G1 的范围。写这行注释的人清楚:这个函数有不止一个调用方,各自的隔离状态不一样。然后停在这里,没有继续。

「这行 TODO 在头文件里的什么位置?」
「紧跟在 `set_system_fault` 函数声明前面,一行。」
一个 G1 工程师,进了 BasePlatform 的地盘,加了一行 TODO,推上去,然后没有人回应过这个提交。
邓明海做了十八年 BasePlatform,这件事没有被提过。或者发现了,也没有说。
「去查一下这个提交有没有经过 code review。」他给麦景行说。
等了大概三分钟。
「没有,」麦景行的回复很短,「直接推到了主干,没有任何 review 记录,也没有关联工单。」
手机放下来。
在合同签完后的第二周,G1 开发组有人直接推了一个没经过 review 的提交进 `common/`,加了一行 TODO,然后这件事就消失在版本历史里,直到今天才被看见。
这不是巧合。
但背后是什么,现在还不够清楚。
「魏磊在 5 月 12 号之后还有别的提交吗?」他问。
「只有这一次。」麦景行回,「`common/` 里只有这条记录。G1 模块下面他有正常提交,从 2023 年开始,最后一次是 4 月底。」
4 月底是他在 G1 的最后记录,5 月 12 日越界进了 `common/`,然后再没出现过。
「他现在还在泉华吗?」
「没办法查到人事信息。」
消息又来了,应该是乔木也在消化这些信息。
「我理解一下现在的情况:G1 工程师发现了一个跨模块问题,没走内部报告,没申请 review,直接在 BasePlatform 的文件里加了一行 TODO,然后就停了。」
「对。」

「问题是,」乔木停顿了一下,「合同里扩展的三项工作,现在实际上是四项。第四项是确认 G1 和 G2 共享调用路径的隔离状态是否满足安全要求。但这一项不在合同里。」
把这句话看了两遍。
合同里没有这项,但现在不写进报告,报告就是残的。写进去,就要再谈。
「先让麦景行把 G2 那边的调用链拉出来,两条路径一起看清楚,再决定怎么跟方总说。」
他放下手机,椅背上靠了一会儿。
窗外是楼道的灯,快八点了。麦景行应该还在电脑前面。
他想起邓明海那个背景里的旧书和挂件,想起那句「能跑就行」。三十年前设计这套系统的人,仔细的重点在别的地方:机器能跑,故障能止住,链路通。文档、注释、跨模块依赖,当时不是重点。
后来重点变了。
这行 TODO 是那个变化的某个截面。有人开始意识到,能跑和安全,不完全是同一件事。他写下来,然后停在那里,等别人来看。
现在有人来了。
手机亮起来。麦景行发来一张截图,`g2/recovery_handler.c`,有高亮标注。
「陆哥,G2 那条路,`set_system_fault` 是在 recovery 失败的最终路径里调的,调用时 G2 的内存锁已经释放了。」
「也就是说——」
「G1 和 G2 同时触发,进的是同一个函数,但一个有锁,一个没锁。」
用人话说就是:两列火车,同一个道叉,一列踩着刹车进的,一列刹车已经松了。进同一道叉,出来什么没人知道。
屏幕在桌上亮着,那行字停在那里。
他给麦景行发了一条:「魏磊,这个人还在泉华吗?想办法查一下。」
放下手机,靠在椅背上。
见方晓晨之前,得先搞清楚魏磊在哪里,以及他当时看到了什么。