GitNexus Evidence Provenance v2:计划文件证据溯源与防篡改安全写入的字节级契约
【免费下载链接】GitNexusGitNexus: The Zero-Server Code Intelligence Engine - GitNexus is a client-side knowledge graph creator that runs entirely in your browser. Drop in a git repository (Github, Gitlab, Azure, Local) or ZIP file, and get an interactive knowledge graph with a built in Graph RAG Agent. Perfect for code exploration项目地址: https://gitcode.com/GitHub_Trending/gi/GitNexus
导读
evidence_provenance(证据溯源)是 GitNexus 的gitnexus-plan(规划)与gitnexus-work(执行)两个技能之间传递"计划文件证据"的唯一机器可读接口。本文围绕 evidence-provenance.md 展开,系统讲解 schema 2 的规范化字节格式、read-plan/snapshot/write-plan三条命令的用法,以及基于目录描述符锚定(descriptor anchoring)与link(2)原子发布的安全读写契约。读完本文,你将掌握:为什么计划文件必须由唯一写边界写入、global_dirty_digest是如何逐字节计算的、Deepen 模式--replace的撤销保护机制,以及 Linux 与 macOS 两种平台各自如何达成"失败即关闭(fail closed)"的可验证读写。
一份"既是规范又是实现"的字节级契约
evidence-provenance.md的自我定位非常明确:它是evidence_provenanceschema 2 的规范字节契约(normative byte contract),而相邻的 scripts/evidence-provenance.mjs 是其可执行定义。规范文档与可执行脚本成对出现,文档规定字节如何组合,脚本确保只有按该字节规则产生的数据才被接受。
该契约在仓库中被刻意做了多份字节一致的副本:
- gitnexus-claude-plugin/skills/gitnexus-work/scripts/evidence-provenance.mjs(2366 行)
- gitnexus/skills/gitnexus-work/scripts/evidence-provenance.mjs
gitnexus-plan技能下亦携带着同名 references/scripts 副本
这样设计的原因在文档中写得很直白:gitnexus-plan与gitnexus-work需要在不依赖对方技能是否安装的前提下,各自产出同一份快照。也就是说,规划方算出的摘要在执行方必须能够原样重算,任何一方都不能"各写各的"。
由此派生出一条**唯一写边界(only supported write boundary)**规则:
任何生成的计划文件(generated plan)只允许通过该 helper 写入目标路径;严禁用临时拼凑的 shell 管道重新计算摘要,或绕过 helper 直接写计划目标路径。
这条规则在 SKILL.md 的 "Never" 一节中被强化为执行纪律:计划体是"决策产物(decision artifact)"而非脚本,执行者只允许读取,绝不允许改写计划正文。
调用方式:三条命令撑起完整生命周期
脚本从目标仓库根目录运行,命令格式为:
node <skill-dir>/scripts/evidence-provenance.mjs <command> [options]命令共三条(外加 read/write 参数组合),下表汇总了它们的职责:
| 命令 | 用途 | 关键产出 |
|---|---|---|
read-plan | 加载一份既有计划(Deepen 或执行的唯一入口) | JSON receipt:规范化generated_plan_path、bytes_read、精确的plan_bytes_base64、plan_digest(sha256:<hex>) |
snapshot | 生成evidence_provenance的完整 JSON 值 | 需逐条传入被引用路径(--cited),schema 1 会被显式拒绝 |
write-plan | 将完全组装好的文档以其精确 UTF-8 字节发布到目标路径 | 成功时输出含规范化generated_plan_path与bytes_written的 JSON receipt |
read-plan:加载既有计划的唯一入口
node <skill-dir>/scripts/evidence-provenance.mjs read-plan \ --repo "$PWD" \ --generated-plan docs/plans/YYYY-MM-DD-gitnexus-plan-example-change-plan.mdreceipt 中携带的字节与摘要必须被"解码后整体消费":不要重新打开词法路径(lexical path),只能消费 receipt 中那一段 base64 解码出的精确字节。且一份路径的 receipt 不能授权另一份路径——即便两者的字节完全相同也不行。在 Deepen 会话期间,必须把规范路径与摘要配对保留在会话状态中(SKILL.md Phase 1 明确要求generated_plan_path与plan_digest双保留),并校验其与文档内generated_plan_path逐字节相等。
snapshot:产出证据快照
node <skill-dir>/scripts/evidence-provenance.mjs snapshot \ --repo "$PWD" \ --schema-version 2 \ --generated-plan docs/plans/YYYY-MM-DD-gitnexus-plan-example-change-plan.md \ --cited src/one.ts \ --cited test/one.test.ts每一个被引用的路径都要单独传一条--cited。helper 输出完整 JSON 值后,应原样拷贝而不改写任何字段。gitnexus-work执行时会把计划里的schema_version、generated_plan_path以及cited_path_manifest中的每条路径原样传给 snapshot 重算(SKILL.md Phase 1 第 3 步的"两层漂移检查")。schema 1 是 legacy,会被刻意拒绝,执行方必须以 schema 2 对老计划做保守的重新锚定(re-anchor)。
write-plan:原子发布与 Deepen 覆写
# 初始发布:目标路径已存在即为错误 node <skill-dir>/scripts/evidence-provenance.mjs write-plan \ --repo "$PWD" \ --generated-plan docs/plans/YYYY-MM-DD-gitnexus-plan-example-change-plan.md \ < /path/to/outside-repo-scratch-plan.md # Deepen 覆写:必须带 --replace 与 read-plan 的精确摘要 node <skill-dir>/scripts/evidence-provenance.mjs write-plan \ --repo "$PWD" \ --generated-plan docs/plans/YYYY-MM-DD-gitnexus-plan-example-change-plan.md \ --replace \ --expected-plan-path docs/plans/YYYY-MM-DD-gitnexus-plan-example-change-plan.md \ --expected-plan-digest 'sha256:<digest-from-read-plan>' \ < /path/to/outside-repo-scratch-plan.md参数约束在源码 parseCli 中得到校验:--replace必须同时携带--expected-plan-path与--expected-plan-digest,二者缺一即抛错;--expected-plan-path必须与写入目标完全相等(一份计划的字节不能授权另一份计划)。CLI 会拒绝任何与所选命令无关的选项;直接调用 API 同样要求字面量布尔值与精确摘要字符串,不接受 truthy 弱类型转换。标准输入必须是合法 UTF-8 且不超过 16 MiB(源码常量 MAX_PLAN_BYTES)。成功的 Deepen 写入还会返回prior_plan_backup_git_path,指向被替换旧计划的 Git 管理备份路径。
初始发布与 Deepen 的核心区别
| 模式 | 是否传--replace | 目标已存在 | 旧计划处置 |
|---|---|---|---|
| 初始规划(initial planning) | 否 | 直接报错 | 不适用 |
| Deepen 循环 | 是(同时要求 read-plan 同会话的精确 path + digest) | 允许覆写,但先做备份 | 原子移入gitnexus-plan-backups/保险库 |
路径契约:不静默修复,只失败关闭
脚本对每一条 Git 路径和 CLI 路径的校验是"全有或全无"式的:
- 必须是合法 UTF-8,且已归一化到 Unicode NFC;
- 必须是非空的POSIX 仓库相对路径;
- 下列情况一律拒绝(而不是悄悄修复或别名化):NUL 字节、反斜杠、绝对路径 / 盘符路径、空路径段、
.或..路径段; - 下述任意一种状况同样失败关闭:来自 Git 的非法 UTF-8、非 NFC 名称、未合并的 index stage、不支持的 Git mode、socket/设备/FIFO、不可读对象、父路径段中的符号链接穿透、或在快照期间观察到的仓库变异。
schema 2 下generated-plan路径必须为仓库相对路径,快照排除与写入严格匹配该命名模式(源码中的 GENERATED_PLAN_WRITE_PATTERN):
docs/plans/YYYY-MM-DD-gitnexus-plan-<3-5-word-kebab-slug>.md其中日期必须是合法日历日期。写入目标不能是.git、源码、配置或任意仓库文件。
为了兼容文档与既有 legacy 计划,read-plan额外接受匹配docs/plans/*gitnexus-plan*.md的归一化文件(源码 GENERATED_PLAN_READ_PATTERN),但读取的宽松并不扩大写入的严格——writer 仍然只认精确命名。外部输出在 schema 2 下没有表达形式。
快照排除语义同样精确:只做一条精确归一化路径的相等比较,不允许glob、目录、仅按 basename、或整目录docs/plans/范围的排除。若该路径恰好是某次重命名的终点,仅排除那一条终点记录。
安全既有计划读取契约:描述符锚定的"读取"
普通读取最大的隐患是 TOCTOU(检查与使用之间被替换)。read-plan的读取协议因此设计为与写入同等强度的锚定式读取:
- 平台门槛:宿主平台必须能基于持有的目录描述符解析名称——Linux 依赖
/proc/self/fd加O_DIRECTORY与O_NOFOLLOW,macOS 用O_DIRECTORY/O_NOFOLLOW。其余平台一律拒绝——未经验证的读取不是"降级读取",而是另一种充满竞态的读取操作; - 解析与打开:先解析精确的 Git 顶层目录,以持有的 no-follow 目录描述符打开仓库根与每一个计划父目录;拒绝缺失、符号链接、非目录以及逃逸的父目录;最终叶子文件以
O_NOFOLLOW打开; - 读取与证明:从该持有 fd 中最多读取 16 MiB,要求合法 UTF-8,对精确字节做哈希;随后再次证明父链与词法叶子仍然指向同一组持有的对象,之后才返回 receipt;
- 使用纪律:Deepen 与执行流程都不允许解析 receipt 之外、更早得到的字节。
源码侧对应 readPlanSafely 及一组描述符工具(openVerifiedDirectory、verifyPinnedDescriptors、verifyLexicalChain)。SKILL.md 将之固化为执行铁律:先解析词法候选,再调用read-plan,只加载 receipt 中描述符锚定的精确字节,并校验 receipt 规范路径与文档generated_plan_path逐字节相等。
安全写入契约:没有解释器、没有原生模块、只有 link(2)
写入器是整份规范中安全语义最密集的部分,它把"不覆盖、可验证、可恢复"三条底线以操作系统原语固化:
- 平台门槛:宿主平台须提供
O_DIRECTORY、O_NOFOLLOW,Linux 另需/proc/self/fd; - 零解释器、零原生模块:发布动作就是
link(2)。link(2)天然原子;目标名已被占用时以EEXIST失败;目标是指向符号链接时不跟随,直接拒绝。这与renameat2(RENAME_NOREPLACE)、renameatx_np(RENAME_EXCL)提供的 no-replace 保证一致,且fs.linkSync在所有受支持平台上都可用; - 同 inode 发布:临时名在 link 成功后即 unlink;发布出去的文件就是 writer 创建并验证过的同一 inode,因此下游所有身份检查在构造上即为真。link 成功而后续 unlink 失败时,计划已发布,此时如实报告成功——因为事实就是成功;
- 同文件系统约束:计划父目录与仓库的 Git 管理目录(Git-admin directory)必须位于同一文件系统(否则无法在同一卷内做原子 move 备份)。
写入主流程(对应源码 writePlanSafely):
- 解析目标仓库的精确 Git 顶层目录,以持有的 no-follow 目录描述符打开根与每个目标父目录;相对于这些描述符创建缺失的父目录;
- 在写边界再次证明描述符链与词法链仍指向同一批目录;
- 以随机独占方式在最终父描述符下创建临时文件,保持其 no-follow 描述符打开;
- 写入并 flush 字节,将临时名绑定到已打开的 inode,发布前对打开的文件做哈希;
- 发布前一刻重新验证父目录与临时路径的 inode、大小、摘要;
- 以
link(2)相对持有目录描述符发布:目标已被占用则失败而非替换——因此初始模式无法覆盖"缺省检查之后才出现的"目标; - 发布后 flush 目录,以
O_NOFOLLOW打开已提交路径,同时对原临时 fd 与路径绑定 fd 做哈希,再做一次描述符锚定的路径身份校验; - 检测到任何变异或替换即中止,绝不接受"混合时代(mixed-era)"的输出。
Linux 锚定与 macOS 验证:两种不同的证明路径
文档坦诚地把两平台差异摆上台面——它们到达同一目的地,但证明方式完全不同:
- Linux:每个名字都经由
/proc/self/fd/<fd>/<child>解析。这是内核依据描述符已持有的 inode 解析的魔法链接(magic link),其上方的名字不会被重新遍历,因此"检查与使用之间父目录被改名"的攻击在结构上不可能发生——不是被检测到,而是根本不成立; - macOS:没有魔法链接。
/dev/fd/<fd>是 devfs 节点,可以 open,但无法穿过它解析子路径(文档注明这在 macOS 26 上实测验证,而非推测)。Node 不暴露openat、没有dir_fd参数、也没有 FFI,因此 macOS 采用"词法 +O_NOFOLLOW"策略:逐组件 no-follow 解析、全程持有链上每个目录的打开描述符、在每一步前后证明链仍精确指向正在持有的 inode。持有描述符正是 inode 号可信的前提:打开的描述符钉住 inode,被释放的编号不可能在遍历过程中被回收再利用。
Linux 买到的是"不可能发生",macOS 买到的是"必定被检测"。macOS 在检查与使用之间的窗口里被替换的父目录,会被随后的检查捕获并使操作在什么都没写的情况下中止;但两个平台的共同底线是:任何已发布字节都不会逃过验证。
Deepen 的撤销保护与备份保险库
--replace仅接受已存在的常规文件,并且只保留给 Deepen。它的完整保护链如下:
- 只接受同一会话
read-planreceipt 中的精确规范generated_plan_path与plan_digest;期望路径必须与写入目标完全相等; - 保存前一刻,对仍持有的旧计划 fd 做哈希,任何摘要 / inode / 路径不匹配(包括同 inode 编辑、读取与写入之间的变动)都拒绝;
- 以 no-replace 原子方式把当前目标移入 Git 管理目录下的随机
gitnexus-plan-backups/文件,并对照持有的 fd 验证被移动的 inode 与摘要; - 之后才以同样的原子 no-replace 原语发布新计划;
- 两个边界上任何一个目标"重现"都会使其保持原样不动,绝不覆盖。
失败路径的恢复语义同样严格:每个新建的计划或 vault 目录都会被 fsync,并再次 fsync 进其所在目录;每次跨目录的保存移动都会先 fsync 源与目标目录,成功或恢复路径被报告。临时字节一旦存在,失败的发布或验证会先保全Git 管理 vault 中每一个可得的 prior / displaced / unpublished / intended 计划再报告失败。错误信息中提到的每个恢复文件,都要从重新解析的 Git 根重新打开并验证后,才会以git-path:gitnexus-plan-backups/<random-name>的形式命名。
解析备份路径的方式在文档中有明确警告——不要把它当作仓库相对的工作树路径解读,而是:
git rev-parse --git-path gitnexus-plan-backups/<random-name>只读或不支持的 checkout 会产生阻塞性错误。调用方不得绕过 helper、不得重定向到外部路径、不得削弱任何检查项。
规范化字节:global_dirty_digest 是如何逐字节算出来的
schema 2 最核心的可重算保证,是global_dirty_digest.value等于对下列字节流的 lowercase SHA-256(注意:不带sha256:前缀;源码中sha256()helper 为带前缀形式,而 global digest 使用裸 hex)。所有文本值取其精确 UTF-8 字节,下面NUL代表单个0x00:
- 前缀字段,每个后跟 NUL,随后再补一个 NUL:
gitnexus-evidence-provenance、schema_version、2; - 零条或多条记录,按归一化路径 UTF-8 字节的无符号字典序排序(locale 排序与文件系统顺序都被禁止);
- 每条记录为
record+ NUL,后接下列固定顺序的field-name+ NUL +field-value+ NUL 对序列,最后再补一个 NUL:path、state、head_kind、index_kind、worktree_kind、untracked_kind、rename_from、rename_to、head_digest、index_digest、worktree_digest、untracked_digest(字段清单与源码 RECORD_FIELDS 完全对应); - 字面量
absent代表所有不可得的重命名终点、对象种类与层摘要——它永远不是空字符串。
整个 schema 的规范化字面量是:gitnexus-evidence-provenance-v2 NUL-framed UTF-8 records(源码 EVIDENCE_PROVENANCE_CANONICALIZATION)。固定字段数加上前缀/记录后的额外 NUL,使分帧无歧义;值本身不允许包含 NUL;重复的归一化路径会被拒绝。
记录、重命名与状态:从 dirty set 到规范记录
原始 dirty 集来自 Git porcelain v2,其采集参数在文档中明确列明:NUL 结尾、包含全部 untracked 文件、开启 submodule 检查、固定 50% 重命名阈值,并同时设置diff.renameLimit=0与status.renameLimit=0——这样仓库自身配置无法截断重命名候选。
共享同一路径的多个原始 porcelain 事实会被合并为一条规范记录。一次重命名贡献两个端点事实:
- 旧端点:
path=<old>、rename_from=absent、rename_to=<new>; - 新端点:
path=<new>、rename_from=<old>、rename_to=absent>。
两者通常都是renamed状态;决定顺序的是记录排序而非新旧角色。工作树脏的重命名目的地、或同时还有其他事实的端点标记为mixed(保留重命名元数据)。任一端点被引用时,引用清单会同时扩展包含两个端点。
普通XY状态的归类规则(源码 classifyXY):
| 观察到的情形 | 归类 |
|---|---|
| index 与 worktree 两列均脏 | mixed |
| 删除 | deleted |
| 仅 index 改动 | staged |
| 仅 worktree 改动 | unstaged |
?(仅 untracked) | untracked |
| 同一路径存在多条不同事实 | mixed(已暂存删除后又重建的文件,保留 HEAD/index 事实,文件系统对象记录进 untracked 层) |
? child/(Git 内嵌目录标记) | 去掉尾部斜杠再归一化,child物化为一个受界定的目录对象 |
| 被引用路径不在 dirty 集 | clean;仅存在于 Git 层之外为untracked;任何层都不存在为absent |
对象与摘要规则:四层证据与目录流 v1
schema 2 的证据按四层组织:HEAD、index、worktree、untracked。每个存在的层摘要都是sha256:<lowercase-hex>,但每层的"被哈希对象"定义不同:
- HEAD 常规文件 / 符号链接:精确 Git blob 字节的 SHA-256;HEAD 目录为精确原始 Git tree 字节;HEAD gitlink 为 tree 中存放的 ASCII object ID;
- Index 常规文件 / 符号链接:stage-0 Git blob 字节;index gitlink 为其 ASCII object ID;index 没有目录层;任何非 stage-0 条目一律拒绝;
- 被跟踪的 worktree 常规文件:不跟随符号链接地打开后读取的原始文件字节;符号链接取其原始链接目标字节;
- worktree gitlink:仅当
rev-parse --show-toplevel证明该目录本身就是嵌套仓库根、HEAD能在那里解析、且 porcelain v2 报告无任何 staged/unstaged/untracked/ignored 嵌套变更时,才取 checkout 出的嵌套 HEAD 的 ASCII object ID。脏、空、未初始化或父级穿透的 gitlink失败关闭(变异守卫会重复同样的 root/HEAD/clean-status 证明); - 目录:走下面描述的v1 目录流;
- 同时缺席 HEAD 与 index 的路径:文件系统对象放入
untracked层,worktree标记为absent;Git 托管的路径放入worktree层,untracked标记为absent;缺失的层 kind 与 digest 都用字面量absent;空文件是零字节的 SHA-256,绝不等同于缺失。
目录对象 v1 流
文件系统目录字节使用前缀字段gitnexus-evidence-directory、schema_version、1,同样的 NUL 分帧,递归条目按无符号 UTF-8 相对路径字节排序;每个条目含固定字段path、kind、digest。实现上做单次自底向上的文件系统遍历,每个节点只访问一次,同时返回各子摘要与打平的子树(flat subtree),以保全这些规范字节;链接永不跟随。当目录被证明是精确的嵌套 Git 顶层时,只排除其管理性.git条目,其余子项——包括工作文件与嵌套目录——全部保留为证据。
目录对象设有硬边界(源码 DIRECTORY_LIMITS):单目录对象最多10,000个访问条目、深度256、常规文件内容总计256 MiB。越界即失败关闭;这些边界对每条记录物化出的每个顶层目录对象独立生效。
层一致性:拒绝"混合时代"的证据
- HEAD 对象只从快照开始时捕获的完整 object ID读取,符号
HEAD名称对层而言永远不会被重新解析; - index 层只从一次捕获的 stage-0 清单解析;
- helper 对相应的 HEAD/ref/reflog 控制与原始 index 文件设守卫,结束时对比捕获清单,拒绝"A→B→A"式的普通变异而不是接受混合时代层;
- 常规文件经
O_NOFOLLOW描述符读取并做前后身份检查;符号链接用 lstat/readlink/lstat 三重;目录在盘点前后都记录身份; - 末尾还要对比原始 porcelain-v2 status 与 HEAD,再复查文件系统守卫;
- 缺席的被引用路径会为最近存在的父目录持有 no-follow 描述符并记录首个缺失组件或叶子,该锚定缺席在最终 Git status 通过前后各检查一次,使"新建的 ignored 路径"无法绕过 porcelain;任何观察到的竞态都会拒绝整个快照,而不是吐出混合时代的证据。
从字节契约到执行流程:它在技能体系中的位置
evidence-provenance.md不是孤立的理论文档,它直接嵌入gitnexus-work与gitnexus-plan的分工契约(见 gitnexus-work/README.md 的 "Contract with gitnexus-plan"):
- 加载:
gitnexus-work在任何符号编辑前,先用本技能的read-plan描述符锚定读取计划,只消费 receipt 内 base64 的精确字节;schema-2 的generated_plan_path必须与 receipt 规范路径逐字节相等;缺失或 schema-1 证据一律按 schema 2重新锚定; - 两层漂移检查:即便当前 HEAD 与计划钉住的 HEAD 相同,SKILL.md Phase 1 仍要求每次都重算全局 dirty digest 与排序后的被引用路径清单——这正是上文规范化字节规则的用武之地;随后与计划内
evidence_provenance比对,被改动引用的路径重新读取,未引用的新脏路径做作用域评估,不可读的证据会阻塞依赖步骤直到恢复; - 门禁:每次符号编辑前做
impact图查询、每次 commit 前做detect_changes(仓库强制规定,见 AGENTS.md),而任何关系性变更都会使前述程序证明失效,下一图查询前必须执行 Build-current/index-current 程序完成中间刷新; - Deepen:仅当漂移动摇了作用域、需求、关键技术决策(KTD)或计划接缝时才回退
gitnexus-planDeepen 模式,而 Deepen 正是唯一使用--replace+ 备份保险库路径的场景。
简言之:本文档定义的字节契约,是保证"规划方写的证据,执行方无法篡改也无法误读"的信任基座;而两条技能命令(Claude Code 用/gitnexus-work [plan path],见 mcp.json 的gitnexus@1.6.9 mcp启动方式)正是在这一基座上,把计划变成一串经过验证的原子提交。
结语:为什么"写计划文件"值得如此严防死守
单看命令,evidence-provenance.mjs只是"读计划 / 算摘要 / 写计划"的工具;但它的设计反映出一个关键判断:AI 生成的实施计划是决定后续所有符号编辑与提交的依据。如果计划文件在生成与执行之间被静默替换、被并发覆盖、或摘要与实际字节脱钩,那么再严格的impact/detect_changes门禁也建立在流沙之上。schema 2 用三条互锁的防线解决这个问题——NUL 分帧的可重算规范字节(任何人都能独立复算 digest)、目录描述符锚定的读写(TOCTOU 在 Linux 上被消除、在 macOS 上被强制检测)、以及link(2)原子 no-replace 发布加 Git-admin 保险库(Deepen 覆写也无法吞掉历史)。这正是 GitNexus 把"证据可信"从口号落实为逐字节约束的完整样本。
【免费下载链接】GitNexusGitNexus: The Zero-Server Code Intelligence Engine - GitNexus is a client-side knowledge graph creator that runs entirely in your browser. Drop in a git repository (Github, Gitlab, Azure, Local) or ZIP file, and get an interactive knowledge graph with a built in Graph RAG Agent. Perfect for code exploration项目地址: https://gitcode.com/GitHub_Trending/gi/GitNexus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考