同一个企业应用里同一张events表两条记录。一条来自你的 AI 助手它调用create_event这个工具在注册表里标着 R3需要你点确认。 另一条来自POST /api/v1/events一个普通 HTTP 接口不经过任何工具也不写 AI 的副作用账。两条记录长得一样。差别不在动作在入口。一句话治理能担保的不是「注册表里有什么」而是「哪条执行路径被验证过、哪条还没有」——该交付的是一张未验证入口清单不是一个覆盖率。如果你只想要结论读到这里就够了。下面是把这句拆开的四件事、一张入口表和一处我们自己的失败现场可以跳读。一、注册表是一个集合入口是一条路径这个运行时启动时注册 61 个工具全部无条件注册存在一个Map里。GET /ai/tools把这批工具连同风险级、门控档位一起列出来。这个集合回答的是「我们打算管什么」。它不回答「动作从哪里进来」。三处具体的地方说明这个差别不是措辞问题第一目录有两份其中一份审计读者看不到。GET /ai/tools只映射本地注册表判read ToolInventory外部 MCP 工具是运行时发现的喂给模型时合并进去但不在那份清单里——它们出现在/admin/mcp/tools那个端点判manage all。审计读者只持read所以「Agent 能调用什么」这个问题审计员只能答一半——另一半要么问管理员要么漏掉。第二门控不把「未注册」当成自己的拒绝条件。assertToolAllowed取不到工具时捕获异常、继续往下走注释写得很直白「让后续 execute 抛 not found这里不拦截」。执行器那一层确实会拒绝toolRegistry.execute先取工具取不到就抛Tool ... not found。**所以不能从这里推出「有绕过」——未注册的名字到不了业务层。**能推出的是另一件事**门控通过不等于注册表登记。**门是「查到了就按声明检查」不是「查不到就拒绝」未注册动作要靠执行器那一层守住两层各管各的。把「门放行了」读成「这个动作在册」正是这篇要拆开的那种合并。第三我们自己的表漂过。工具元数据表曾经有四条标签指向不存在的工具而 12 个真实工具一条标签都没有。修法不是再维护一份手写清单而是让检查对着活注册表跑。这是我们自己的现场不是假想手维护的表会在「新增工具忘了登记」和「工具改名、旧条目留在原地」那一刻静默漂移。同一类错误往上抬一层就是这篇文章的问题我们有一道闸对着注册表查元数据却没有一道闸对着运行时的入口集查注册表。没人会红当一条新的写路径出现的时候。二、四件事必须分开动作目录声明需要治理什么。今天的形态是 61 条注册 一张元数据表 运行时发现的外部工具。执行入口一次动作实际从哪里开始——HTTP 路由、SSE 流、WebSocket 事件、MCP 端点、定时任务、工作流节点、队列消费者、插件路由。治理覆盖哪些入口的写确实经过了同一个门与同一条执行器。验证证据凭什么说上一条成立——源码调用点、单元测试、端到端测试还是「我看了一遍」。把这四件事合成一个百分比最常见的写法是「覆盖率 90%」。问题在分母分母是执行入口的集合而这个集合正是我们不知道的东西。一个分母未知的百分比是对一个你无法枚举的集合下的断言。清单不一样——它逐条列出「这条没验证」可以被反驳也可以被补上。换个说法覆盖率回答「我们做了多少」清单回答「还剩哪些没做」。审计要的是后者。三、怎么盘一遍入口起点不是注册表是执行器。先找到「写」真正发生的那几行再往回走。这个仓里写工具的执行为executeWrite全仓只有三个调用点流式对话里两处本轮信任的直接写、确认通过后的写以及 R4 审批的执行器。从这三处往上追得到的是SSE 流、WebSocket 的ai:chat、管理台流式、离线裁决、治理台回调。然后反过来查其余每一条能承载 AI 署名的入口写是怎么发生的。这一步靠读代码不靠注册表。盘的过程里最容易漏的不是「有没有门」而是门是不是一个可选参数const execute readOnlyExecutor ?? ((tool: string, args: Recordstring, unknown, uid: string) toolRegistry.execute(tool, args, uid));plan 与子代理本该只读注入了只读执行器时写工具会被拒。但执行器是可选参数不传就回落到toolRegistry.execute——没有门、没有确认、没有副作用账。子代理那边是同一形状。我们用一段不注入执行器的调用跑过一次让规划器返回一个写工具步骤工具名直接落到了registry.execute。当前装配里每个调用点都注入了执行器所以这是今天没人走的路不是线上缺口。但它是可复现的单元测试甚至把「不传执行器 ⇒ 直调注册表」钉成了期望行为。一个可选参数把「门」变成了「如果调用方记得」。这个方法有一个它自己治不了的毛病它是一次快照。明天加一个控制器直接调toolRegistry.execute没有任何东西会红。今天最接近「运行期自证」的是/app/provenance但它返回的是计数——总数、读/写、风险级分布。一个计数说不出有哪几条入口。四、结论、证据、可达性分三列写状态只回答一个问题结论是什么。verified在这条路径被声明的范围内已有证据表明它满足指定的治理条件——写经过门与执行器或被明确的拒绝挡住。failed已证明至少存在一条违反该条件的路径。unknown证据不足判不出来。不计入已覆盖。not_applicable已证明这条路径不属于当前声明的治理目标并写清它属于什么范围。还有一个问题不在这四个值里凭什么。把「读过源码」和「跑过端到端」压成同一个verified就是在重犯这篇批评的那种合并。所以另设一列证据等级与状态独立E1读了源码确认调用链在执行点经过门与执行器。E2单元或集成测试跑过该路径。E3端到端测试或真实运行验证过。E4由第三方在独立环境按文中步骤复现。E0 只有陈述不构成证据本文不使用。verified / E1与verified / E3不是同一句话源码能证明「这个调用点调了executeWrite」证明不了运行时装配、依赖注入、配置开关和异常分支测试通过证明的也只是被跑到的那条路径。本篇最高到 E3没有一条到 E4。入口写怎么发生门控 / 账状态证据可达性当前装配POST /ai/chat非流式·/headless/chat·管理台非流式写工具被拒不执行起点判requiresConfirmationverified拒绝成立E2已接线POST /ai/chat/stream·WSai:chat·管理台流式executeWrite执行点复查开关 / 字段域 / 目的地 / 审批verifiedE1 E2执行器层已接线POST /mcpMCP 出口写工具只回「需确认」不执行executeToolForExternal门控verified拒绝成立E2已接线POST /admin/mcp/call外部 MCP 网关直接调第三方 server策略开关 / R5 / 确认判定verified门在账不在E2已接线确认与审批对话内裁决 / 离线裁决 / 治理台回调批准后走executeWrite与流式同一执行点verifiedE2已接线plan / 子代理已注入只读执行器写工具被拒executeAgentRead强制只读verifiedE2已接线plan / 子代理未注入执行器的回落直调toolRegistry.execute无failed代码结构E1 仓外脚本实跑不可达已知调用点均注入FLOWai_task/human_task只生成文本 / 建flow_task与通知不经工具not_applicable不在 AI 工具范围E1—定时 AI每日摘要0 8 * * *LLM → 通知行不经工具not_applicable写通知不写业务E1—队列knowledge / push / reminder转写、推送、提醒不经工具not_applicable不在 AI 工具范围E1—业务 REST 直写如POST /events直接调业务服务无 AI 门控not_applicable不在 AI 入口范围E1—插件路由POST /plugins/*插件可取任意宿主服务并注册路由无unknown未核E1只读到接口未核先解释表里唯一一条failed它必须分成两句读。代码结构失败。不传执行器时写工具名会落到registry.execute真实实现就是tool.execute门、确认、副作用账都不在。这是一条可复现的路径不是推理出来的。当前装配没有证据表明它被走到。全仓的调用点都注入了执行器。这只覆盖「今天被查过的装配点」覆盖不了明天新增的调用点也覆盖不了运行时分支——所以它不构成「安全」。两句话不能合成一句。把「代码允许」写成「线上发生」是失实把「今天没人走」写成「代码没问题」是另一种失实。这一列的可达性就是为这件事单独设的。表里还有两点要说白。not_applicable只说明这条路径不在当前声明的范围内不代表业务写安全也不代表它受 KeelBase 治理。业务 REST 直写和 AI 工具写调用的是同一个服务函数create_event工具与POST /events都落到eventsService.create产物落在同一张表。它不在 AI 入口清单里所以这张表替它做的唯一承诺是「我们不声称管了它」——而且如果哪天有一条 AI 路径去调它门不会替它挡。把not_applicable读成「这项写已经有保障」是把范围外读成了通过。unknown不能被读成verified。这个运行时有一套缺口登记表每条缺口声明它对结论的影响是partial还是unknown而unknown的定义是「连漏了什么都说不出」比partial更强。有一条测试专门钉住这个语义只有 high 级缺口才允许判unknown。问题是登记表里明明有两条正是这篇文章说的东西——risk-level-not-expressible-for-non-discrete-actions不是离散具名工具的动作面挂不上风险级和host-ai-path-not-governed宿主自己有一条不经治理引擎的 AI 路径——但本部署的事实集把这两个条件钉成false所以声明里不会出现它们。我们能说出这条缺口的名字却不能让它出现在声明里。这比「没有这条缺口」更糟一点它看起来像核过了。事实集为什么这样钉文件里对两条事实写了理由剩下两条没有。这就是一个诚实的unknown应该长的样子——不是空格是「这里有东西但我们还没有输入能判它」。但它已经不是一般的unknown是声明机制本身的缺陷。两者要分开记一般的unknown是缺证据——没核到或核了判不出来补一轮核验就可能消掉这一条是缺机制——缺口在登记表里、名字说得出来事实集却把触发条件钉成false于是声明必然不呈现它再核一遍也不会出现。一个只分「已实现 / 未实现」的声明会把这类问题永远留在两者之间的缝里。五、七条可以自己做的检查这条动作从哪个入口进来客户端点出来的、HTTP 路由、WebSocket 事件、定时、队列、工作流节点——写下来不要只写「AI」。这个入口的写经过同一个执行器吗问「有没有门」不是问「有没有审计」。审计只说明发生过门说明被允许过。同一次动作从两个入口进来产物一样吗如果一样表里那行就分不出是谁写的——除非再 join 审计行与副作用行。目录有几份本地注册、元数据表、运行时发现的外部工具各自被谁读。unknown写在哪个字段里如果没有unknown这个取值缺席会被读成通过。新加一条入口时什么会红答案如果是「没有」那这套覆盖是手抄的。这个verified凭的是哪一级证据读源码和跑端到端都会写成「验证过」但能担保的范围差一大截。六、我们有什么、没有什么已经有的61 条无条件注册的工具目录与审计员可读的清单一个执行点复检的写执行器开关 / 字段域 / 目的地 / 审批要求 / 认领MCP 出口与非流式入口对写工具的拒绝plan / 子代理的只读强制一张带unknown语义、结论由事实算出来的缺口登记表一道对着活注册表查元数据的完备性闸。边界目录有两份本地清单不含运行时发现的外部工具外部 MCP 网关的写有门有审计、没有本地副作用账目标在别人那里plan / 子代理的回落把门变成可选参数门不拒绝未注册的名字两条本该说「这里有条看不见的路径」的缺口被钉成false而不出现在声明里业务 REST 直写与 AI 写共用同一服务函数凭行本身分不出来。没有做的没有运行期的入口枚举与断言——「动作面可枚举」在我们这里仍然是押注不是已证明的事实没有一条测试证明「所有 AI 入口都闭了」这张表本身是 2026-10-26 的一次手工盘点不是产品功能插件路由与宿主侧 AI 路径没有评估。先分清三个面才不会拿一个面的完整性替另一个面背书。动作面注册表里的 61 个工具、元数据表、运行时发现的外部 MCP 工具——「我们打算管什么」。入口面能触发执行的 HTTP、SSE、WebSocket、MCP、定时、队列、工作流、插件——「动作从哪儿进来」。业务副作用面真正改变业务状态的那些写入点——「到底改了什么」。三个面可以各自缺一块。入口清单齐了也不代表副作用面齐了一个插件能取到任意宿主服务一个通用业务服务也能被一条没盘到的路径调用。所以结论不该停在「把入口枚举出来」——纳入治理范围的业务写入只能经由已验证的执行路径完成范围外的写入必须明确披露边界。入口清单只是这句话在今天这份代码上的一个近似。结语回到开头那两条记录决定一条写受不受治理的是它从哪个入口进来不是它落在哪张表。这套机制取自开源项目 KeelBase 的实现——一个夹在 AI Agent 与企业业务系统之间的运行时在工具执行前重新检查身份、授权、范围、风险与确认状态并把每次决策写成可离线核验的记录。入口盘点是一套读法不是已交付功能KeelBase 是它的实现与证据载体。仓库境内访问建议用 Gitee 镜像GitHubhttps://github.com/rain6fish/KeelBaseGiteehttps://gitee.com/rain6fish/KeelBase照实交代四处。一是表里六条身上有 E2其中一条只在执行器层有测试、五条只到 E1读源码回落那条是 E1 加一次仓外实跑没有一条到 E4。二是那条回落绕过是我们用一个不注入执行器的脚本跑通的生产装配今天不走它。三是定时任务、队列、工作流三项判not_applicable的依据是「不走工具」不是「测过不能写」。四是「所有入口已覆盖」这句话我们不写因为没有任何东西能证明它。责任不在一个地方。运行时负责证明自己的执行路径确实受治理约束——门、确认、账且能被测试复现宿主应用负责识别并披露那些绕过运行时的业务入口因为只有它知道自己的写从哪儿来集成方负责确认哪些业务写入被纳入了范围因为范围是在集成时定的。而治理系统必须把已验证、失败、未知、范围外四类分开写不能拿一份工具目录替整个应用背书。注册表负责说「我们打算管什么」入口清单负责说「我们实际管到了哪」。两者之间的差才是治理的边界。
阅读完成 · 觉得有帮助?