产物校验
校验用同一条命令回答两个不同的问题。针对源码,它问的是项目是否描述了一个合法的捆绑包;针对 产物,它问的是输出的字节是否就是清单所承诺的字节。
validate --artifact、mcp 与 hooks 都能在项目源码已被删除的情况下针对已构建产物工作。这是刻意
如此:只有在源码树存在时才能通过校验的产物,其实并不算自包含;测试中的
packed-deleted-source 证明级别存在的理由与此相同。
内容寻址,而不是路径存在性
agent-bundle.manifest.json 为每个输出文件记录一份 SHA-256 摘要,包括被复制的脚本与资源。产物校验
把真实字节与这些摘要比对,因此被手工改过的生成文件会失败,而不会因为路径还在就通过。被引用的文件同样
会被检查,清单声明的 logo 若在产物中缺失或逃逸出部署树,会报告 AB6025。已列入清单的编译证据记录若
与清单文件表不一致,会报告 AB6039。
自包含性由编译过程的 externals 证据证明(在编译期报告 AB6005)。编译器服务会降低每个宿主包表面、
每个包构建入口(dist/bin/*.js、渲染式路由的 Flight worker .mjs 与 lib 入口),以及每个 MCP App
视图。框架自有的
ArtifactDependencyAuditPlugin 会接入 thisCompilation,记录 Rspack 保持外部化的每个模块,服务则在
信任任何产物之前读取这份证据。只有 Node 内建模块(node:fs、fs)、pnpapi 以及同一产物已输出的同级
文件可以保持外部化;其他请求无论被 Rspack 输出成 import、require 还是其 createRequire shim,都会
失败。表达式请求(import(expr)、require(expr))不在编译器视野之内:Rslib 的配置不解析它,而是原样保留,
因此它既不会被打包,也不算外部模块。
已输出模块的遍历仍留在这份编译器证据之后,只负责编译器看不见的部分。它把每个已输出的 .js/.mjs
模块按 ES 模块读取,并在其中任何一个,包括编译产物,里出现表达式 import() 时失败
(AB6005 has a non-literal dynamic import)。被编译证据记录证明的模块,记录以相同字节覆盖的清单
bundle 文件,且构建未使用 tools 逃生舱,只做词法
分析而不完整解析,但它仍带有的每个字面量导入都要对照记录核对:Node 内建模块或该文件记录在案的
外部依赖通过;其他任何请求都是构建被要求忽略的导入(rspackIgnore/webpackIgnore,Rspack
原样保留它,没有模块、外部依赖或警告),并以 AB6005 loads "<request>", which the compiler neither bundled nor recorded as an external 失败。摘要匹配只证明字节是编译器输出的,不证明其中每个导入都
已被解析。其他所有模块都会被完整解析,导入也会对照清单解析,生成的 install.mjs、被复制的消费者脚本、
没有记录的产物中的每个模块,以及逃生舱可能改写了输出资源的构建(记录中的 coverage.rewritable)
里的每个模块。这些发现就是 AB6005 的残余形态:不受支持或无效的说明符、缺失于或逃逸出输出树的目标、
无效语法;dist 的发现以 dist/<path> 命名文件。预构建负载(kind: 'prebuilt')保持不透明、仅做哈希
锁定,声明文件(.d.ts)不会被遍历。
编译时,AB4837 拒绝导入携带编译器的框架入口:agent-bundle、
agent-bundle/api、agent-bundle/config、agent-bundle/eval、agent-bundle/rstest、
agent-bundle/test 和 agent-bundle/test/browser。检查使用转换后的依赖请求和解析后的
包导出身份(包括别名),并指出导入模块。被配置的转换器擦除的类型导入仍然合法。
仅分析源码的 inspect 和 validate 不证明此依赖边界。agent-bundle/app 是浏览器安全的叶入口。
已安装产物通过 <plugin> web 提供 App。
编译证据记录
agent-bundle build 在产物根目录写出 agent-bundle.compile-evidence.json,并在
agent-bundle.manifest.json 中把它列为 generated 文件。这份记录就是编译器服务对它输出的每个文件
所报告的内容,并与那些字节绑定。assets[] 中的每一项对应一个已编译文件(bundle kind:
bin/*.mjs、scripts/*.mjs、hooks/*.mjs、mcp/*.mjs、Flight worker、mcp-apps/*.html),
并保存 path、sha256、被保留的 externals(kind 为 artifact-relative 或 builtin,以及
externalType、issuers、request、userRequest,同级文件还有 target)和被内联的
packages。记录级字段是策略 closed-world-externals@1、producer
{ name: 'agent-bundle', rspack, version }、coverage.rewritable(当 tools.rspack 或
tools.rsbuild 逃生口参与了这次构建时为 true,此时输出字节可能与模块图不一致),以及
coverage.unobserved。
agent-bundle validate --artifact 把已列入清单的记录对照清单文件表复核,且不读取 JavaScript
(AB6039)。包构建在进程内返回同一份记录(PackageBuildResult.evidence,路径为 dist/bin/…),不写到磁盘。
coverage.unobserved 列出 Rslib 在捆绑中原样保留的加载形式,因此编译器既不会打包它们,也不会记录
它们:import(<expression>)、require(<expression>)、require.resolve(…)、
createRequire(…)(…)、import.meta.resolve(…)。因此「没有记录任何 externals」并不能证明不存在
这类加载。
AB6039 是 error。恢复方式:重新构建产物,使其编译证据记录描述已输出的文件。消息形式:
Compile evidence record <reason>.(严格解析器自身的消息,例如is not valid JSON)Compile evidence record cannot be read.Compile evidence was judged under policy <name>@<revision>; this validator applies closed-world-externals@1.Compile evidence does not cover compiled file "<path>".Compile evidence for "<path>" describes different bytes.Compile evidence names "<path>", which the manifest does not list as a compiled file.Compile evidence for "<path>" records "<request>" as a built-in; it is not one.Compile evidence for "<path>" records sibling "<request>", which the artifact does not contain.Compile evidence for "<path>" records "<request>" as an external; a view inlines every module it loads.
每条诊断都是一份结构化记录:稳定的 AB 代码、一个严重级别、一条消息,通常还有 sourcePath 与一条
recovery 提示。由诊断把关的命令,build、prepack、validate、doctor、install 与 dev,只有
存在 error 级诊断时才以非零退出;warning 与 info 绝不会为构建、校验或 dev 重建把关。eval 与 inspect
在试验失败或无定论、或模型无效时也会以 1 退出,这与错误诊断无关,见
命令行退出码。
Claude Code 严格校验
宿主校验只为所选投影运行,且作用于组合根目录。当 Claude Code 位于 PATH 上、且所选投影中包含 claude
时,产物校验会对根目录运行已安装的 claude plugin validate。agent-bundle build 会对刚写出的根目录运行
同样的检查(--no-host-validation 跳过;--strict 把宿主 warning 提升为构建 error),而
agent-bundle doctor --host claude --from <root> 会对该根目录以及 Claude 列出的每一份已安装副本运行
这些检查。Claude Code 根据被指向的清单决定检查什么:一个同时包含
.claude-plugin/plugin.json 与 .claude-plugin/marketplace.json 的根目录会被当作 marketplace,
而 marketplace 校验从不打开插件的 hook、skill、agent 或 command 文件。因此 Agent Bundle 运行两条命令:
- 宿主的 error 会变成 Agent Bundle 的 error(
AB6021)。 - 宿主的 warning 保持为 warning(
AB6020),除非设置了agent-bundle validate --strict。 - 每条发现都会点名 Claude Code 检查的文件,例如
(hooks hooks/hooks.json): hooks: hooks.postToolUse: unknown hook event。Agent Bundle 会先请求 CLI 的--json报告(Claude Code 2.1.259 及更高版本;固定的 2.1.260 会输出它),只有当 CLI 拒绝该标志或探测到的 版本早于 2.1.259 时才解析文本报告。marketplace 那次 运行中对插件运行已报告过的清单发现的plugins[0] plugin.json →副本会被丢弃。 - 二进制文件缺失会被报告为一次显式的信息性跳过(
AB6019),绝不会伪造成功;探测或运行无法完成 时为AB6022。PATH上没有claude时,build只会启动一次进程并只报告一次跳过。
加载检查
校验不是加载结论。Claude Code 2.1.250 到 2.1.260 在 --strict 下会接受一些随后会被会话拒绝加载的清单与
组件文件,例如无效的 monitors/monitors.json,或指向自动加载的 hooks/hooks.json 的 hooks 字段。拒绝
唯一显现的地方是 claude --plugin-dir <root> plugin list --json("Plugins reference" → "plugin list")
中该插件所在的行:被拒绝的行带有 errors 数组,内含宿主的消息;健康的行没有 errors 键。因此 build 与
validate --artifact 在两次校验运行之后正好执行这条列表命令,并读取 bundle 的 <name>@inline 行:
这条列表命令是只读的,也不需要登录。Doctor 不会重复它:其 --plugin-dir 注册证明与清单行中的 errors
已经承载了同样的结论。
CI 应当使用严格校验:
当需要确定性的、只看 schema 的检查时使用 --no-host-validation,没有安装这些宿主的机器也必须能运行
同一道门禁,并得到同样的 schema 结论。
在开发期,先在不安装的情况下为单个会话加载已构建的根目录,然后迭代:
在 agent-bundle build 重写了 hooks/、.mcp.json 或 agents/ 之后,在该会话中运行
/reload-plugins;SKILL.md 的修改无需它即可生效。--plugin-dir 插件对不带参数的
claude plugin list 不可见;把该标志放在子命令之前即可查看:
这份列表就是上文描述的加载结论,build 与 validate --artifact 会替你运行它。agent-bundle doctor --host claude --from <root> 会在 --plugin-dir 注册证明与已安装副本两处读取同一个 errors 数组,并报告为
AB7325(load-failed);当已安装副本带有该数组时,agent-bundle install claude 以 AB7006 失败。被固定的
Claude plugin schema 也会预先拒绝 hooks: "./hooks/hooks.json"(AB6012),因为清单中的该字段只用于额外的
hook 文件。
Doctor 还会读取每一行的 enabled 标志。被 claude plugin disable 关闭的副本("Plugins reference" →
"plugin disable")仍然处于已安装状态,甚至可能与 bundle 字节一致,但它的任何内容都不会进入会话;Doctor 把它列为
disabled,保留其内容结论,并追加一条 AB7327 warning,其修复建议就是精确的
claude plugin enable <name>@<marketplace> [--scope <scope>] 命令,重新安装不会启用一个被禁用的插件。
Codex 与 Cursor:被固定的 schema
Codex 与 Cursor 目前都没有发布插件校验的开发者动词,因此 Agent Bundle 用自己 vendored 的、被固定的 schema 校验它们输出的 JSON 文档,并把宿主的 schema 生成器视为一个漂移信号,绝不当作替代的插件契约。
AB6031 是其中最值得注意的一条:当 Codex 生成的 schema 与被固定的版本不一致时,只有在 Codex 发布了
匹配的契约之后才会更新这个固定版本。自动跟随宿主的实时输出,会把一次上游变更变成这里一次无声的契约变更。
Agent Skills 规范 lint
AB6034 是针对输出字节的 error:某份 SKILL.md 拥有合法的 YAML frontmatter,但其后没有 Markdown
指令正文。被固定的 Agent Skills 规范要求 frontmatter 之后必须跟随 Markdown 内容,而这项检查针对的是
产物实际包含的内容,而不是编写时的源码。
哈希固定与仓库自有的表格
哈希固定覆盖那些真实来源位于本仓库之外、且可能漂移的 vendored 外部内容:src/adapters/schemas/* 下的
宿主文档 schema(其上游 URL、commit 与 SHA-256 记录在 PROVENANCE.json 中)、清单 agentSkills 块中
由 Agent Skills 规范派生的 schema 固定值,以及为完整性而记录的输出产物文件与源输入。
仓库自有的能力表格与证据不做哈希。能力证据记录被观察到的宿主版本(observedVersion)与 target,
适配器则携带一个单调递增的 adapterRevision。Git 已经为仓库自有内容做了版本管理;在仓库内部再哈希一遍
是自我指涉的,并且会让每次编辑表格都产生变动噪声。
npm prepack 门禁
prepack 运行发布构建,在 dist/ 生成规范 npm 根目录,再通过当前 Node 可执行文件
(process.execPath)启动 npm 的 npm-cli.js,从该根目录运行
npm pack --dry-run --json --ignore-scripts,因此在 npm 不是 PATH 上可直接
spawn 的命令时(pnpm 管理的 Node,或 Windows 上 PATH 没有 npm.cmd)门禁仍可用。
当 npm_execpath 指向 pnpm 时,它不会把该变量当作 npm。生成的包会原样复制已校验的组合产物,让路由式
package.json bin 指向清单声明的 bin/<name>.mjs,并且只额外加入单独编写的 bin 与库。
门禁会检查这份精确清单、清单哈希、包 bin 目标、发布版本一致性、包专属编译证据以及
package.json 中的安装期依赖字段。作者写入的 package.json.files 与 .npmignore 规则不会缩窄
生成根目录:每个所选隐藏宿主目录与每个清单文件都必须进入包,否则 prepack 会在发布前失败。构建会把每个依赖内联进手写包入口与各宿主包,因此已发布的插件不应安装任何东西:请把框架、@agent-bundle/runtime、react、
zod 以及其余技术栈都声明在 devDependencies 下。已编译的 bundle,无论是宿主包模块还是 dist
bundle,都不能把裸包保持外部化:编译器会在 prepack 触及清单之前报告 AB6005。因此
仅仅把包内联进 bundle 并不能证明消费者需要安装它。--output 是相对
--root 的产物路径,会覆盖配置中的 output.distPath,默认值为 artifact。请直接运行门禁(生成项目提供
pack:check),再用 npm publish ./dist --ignore-scripts 发布生成根目录。任何 npm 生命周期都绝不会执行宿主安装。
消费者的 preinstall、install 与 postinstall 脚本仅在不直接调用 Node,或恰好是一个
node <generated-package-path> 命令时保留。源 dist/ 下已知的手写输出路径会改写到生成根目录,并对照
已暂存文件校验。不支持的 Node 命令或缺失的源码资源会报告 AB4768;包构建器绝不会为了满足生命周期路径
而复制整棵源码树。
这种内联是编译器的契约,而不是一次工具链升级就可能翻转的打包器默认值。生成的可执行文件,产物脚本、路由式
CLI、MCP 入口、钩子包装层以及包构建的 JavaScript bundle,都以禁用 Rslib 的 autoExternal、bundle: true 与
splitChunks: false 的配置编译,框架自身也不添加任何 externals;Rslib 的 node target 只把 Node
内建模块(node:fs、path,以及 Yarn PnP 的 pnpapi)保持外部化。MCP App 视图则把每个脚本与样式都
内联进同一个 HTML 文件,同一个审计插件也位于它们的 Rsbuild 编译器中:浏览器文档不允许任何 external,
因此视图编译保持外部化的任何模块,比如通过逃生舱映射到全局变量的 react,都会让该视图以 AB6005
失败(Compiled MCP App view "mcp-apps/<name>.html" keeps … external …)。AB4725 会拒绝不为 false
的 tools.rsbuild.output.autoExternal,以及静态可见、
点名某个包的字符串或对象 externals 条目。RegExp、函数形式、相对路径与 mutator 添加的 externals 则由编译过程
自身的证据判断:若 Rspack 在宿主包或 dist 的生成可执行文件中保持了内建模块、pnpapi 或已输出同级文件
之外的任何 external,AB6005 就会失败。通过
tools 逃生舱保持外部化并不能绕过契约,也永远到不了 prepack
的清单检查。
AB7014 只接受三类消费者运行时证据:
- 通过
definePrebuilt声明的预构建 payload 上的runtimeDependencies; - TypeScript 的
preProcessFile从打包后声明文件读出的引用:import、export、import-equalsrequire、import("…")类型、外部模块文件中的模块扩充,以及/// <reference types>(连同其@types对应包);没有 import 的文件中的环境式declare module不算证据,而#子路径会经由包的imports映射解析; - 消费者侧
preinstall、install或postinstall脚本(包括其委托的脚本)点名该依赖,或运行其bin命令。
框架自身的运行时模块在运行时不加载任何包,因此不存在可作为第四类证据读取的框架进程依赖记录。
仅仅内联进已编译 bundle 的包不算已使用。当这是唯一证据时,AB7014 会点名内联它的 dist bundle,
例如:The build inlined "a" into dist/bin/x.js; every consumer installs them for nothing.
请把仅构建期使用的包移到 devDependencies;编译后的 bundle 会内联其 import(AB6005)。仅当打包后的
声明文件引用、消费者安装脚本点名或运行,或预构建 payload 在 runtimeDependencies
(definePrebuilt)中声明某个包时,才把它保留为运行时依赖。
发布构建同样会拒绝 package.json 中没有有效发布版本的项目(AB4013),因此已发布的产物绝不会携带
0.0.0-dev.<short-revision> 这个开发期回退值。