Skip to content

fix: harden Windows process ownership, lock cleanup and root validation - #47

Merged
Sandyzzx merged 2 commits into
Sandyzzx:masterfrom
Han7872:codex/filesystem-root-guards-20261017
Oct 19, 2026
Merged

Sandyzzx merged 2 commits into
Sandyzzx:masterfrom
Han7872:codex/filesystem-root-guards-20261017

Conversation

@Han7872

@Han7872 Han7872 commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Changes

Harden filesystem root guards, lock cleanup and Windows process ownership, including the claimant-scan failures reported against 628984c.

  • Keep task occupancy when an enumerated parent disappears before its handle is acquired, or a reused parent PID still has an older or unprovable child.
  • Require a held, row-consistent identity and a strictly later creation interval for every excluded parent-child edge. Recorded roots follow the same rules; a cached foreign classification cannot override claimant evidence from another recorded root.
  • Persist task-relevant observations from unsuccessful or rejected scans, so later cancellation and Manager recreation cannot forget a previously observed intermediate ancestor. Release normally once cleanup is proven.
  • Apply the termination deadline to members without confirmed handle-based exit evidence; continue bounded closure confirmation for already-exited members.
  • Retain filesystem error classification, shared root guards, late identity handling and owned-fixture cleanup from the original change. Keep tracked plugin bundles synchronized.

Verification

Independent verification of the repaired candidate:

Environment Scope Passed Skipped Failed/cancelled
Windows, Node 22.18.0 Full suite 508 1 0/0
Windows, Node 24.16.0 Full suite 507 2 0/0
Windows, Node 24.19.0 Affected regressions 152 0 0/0
Linux, Node 22.18.0 Full suite 508 3 0/0
Linux, Node 24.20.0 Full suite 507 4 0/0

Typecheck, build, plugin validation, clean-entrypoint checks and diff checks passed. Independent positive/negative scanner probes and real Manager consumption/recreation tests passed on Node 22.18.0 and 24.16.0. Original failing candidates and intermediate failures were retained. Linux ran rootless, non-root, without networking or host mounts, with capabilities dropped and no-new-privileges enabled.

The initial Linux bundle comparison differed only in source-path comments because the evaluator linked its dependency directory. Build-only checks using physical dependencies on both runtimes produced byte-identical candidate bundles; full suites were not repeated.

Boundaries

The maintainer's two process-lock failures were not naturally reproduced locally, including with Node 24.16.0. The budget correction has deterministic regression evidence; this does not establish the exact cause on the maintainer's machine. Unobserved intermediate ancestors remain outside the scan's proof. Platform/version-specific skips are retained rather than reported as passes. ADR-009 numbering remains subject to integration coordination. No dependency, public MCP schema, release or installed-plugin update is included.

See the repair report and ADR-009 for the final behavior and evidence boundaries.

Bind Windows tree termination to verified native handles and preserve
incomplete cleanup evidence across recovery, retries, and worker commits.

Harden filesystem lock transitions, late identity writes, and repository
root guards. Add lifecycle regressions, ADR-009, and synchronized bundles.

Validation: Windows Node 22/24 and isolated Linux Node 24 matrices,
identity and recovery counterexamples, and owned process-tree tests.
The staged sources match the independently accepted source manifest.

Copy link
Copy Markdown
Owner

对当前 head 628984ca625a4d307b859d9f32d49e52e2d21ee6 做了源码审查和独立反例验证。整体方向有价值,但建议先修复下面两处 P1,再合并。

P1:枚举后消失的记录 PID 会漏掉存活子进程

位置:windows-process-cleanup.ts:655–656,及第 667 行。

枚举中存在父 101 和仍存活的子 102;随后 hold(101) 返回 absent。当前代码直接 continue,但 live 集合仍包含 101。扫描 102 时,父既不在 claimant 中,也不满足 parentRecordedDead,最终错误返回 clean:true。

建议在 absent 分支保守失败,或正确更新状态并继续追踪旧父名下的存活子代,不能用过期的 live 集合证明清理完成。

P1:父 PID 换代不代表旧代子进程已经退出

位置:windows-process-cleanup.ts:652–654,及第 665–667 行。

记录的原父 101 指纹为旧代;扫描时 101 已被新进程复用,但旧子 102 仍存活,且创建时间早于新父。当前实现把 101 标为 foreign 后排除整棵树,仍返回 clean:true。只能证明当前 101 是外国换代,不能证明 ParentProcessId=101 的所有行都属于新代。

建议只排除有证据属于新代的后代;旧代或不可归属的候选继续保持占用。现有“新父+新子”的排除测试不能覆盖“新父+存活旧子”。

这两处返回值会进入 Manager 的 cleanup release gate,可能提前清除 cleanup_unverified,使后续同目录任务与残留进程并行写入。

可独立重跑的最小反例

先运行 npm run build:test,再将下面内容保存为仓库根目录的 .mjs 执行。仅使用 fake transaction,不打开或终止任何真实进程。当前源码两个场景都输出 clean:true,预期均应为 false 或明确拒绝。

import { verifyWindowsRecordedTreeClaimants } from './dist-test/src/adapters/windows-process-cleanup.js';

const row = (pid, parentPid, t) => ({
  pid, parentPid, creationFileTime: String(t), startTicks: String(t),
});
for (const scenario of ['absent-after-enumeration', 'reused-parent-old-child']) {
  const absent = scenario === 'absent-after-enumeration';
  const tx = {
    enumerate: async () => absent
      ? [row(101, 4, 100), row(102, 101, 200)]
      : [row(101, 4, 300), row(102, 101, 200)],
    hold: async () => absent
      ? [{ pid: 101, status: 'absent' }]
      : [{ pid: 101, status: 'ok',
           creationFileTime: '300', creationTicks: '300' }],
    close: async () => {},
    kill: async () => { throw Error('read-only probe must not signal'); },
  };
  const result = await verifyWindowsRecordedTreeClaimants(
    [{ pid: 101, fingerprint: '100' }],
    { open: async () => tx },
  );
  console.log(scenario, result);
  if (result.clean) process.exitCode = 1;
}

本次验证与待核实项

  • PR 的 Windows/Ubuntu CI 已通过,这一点已核实。
  • 本机 Node v24.16.0,从该 head 导出独立源码目录;复用现有 node_modules,未执行 npm ci。npm run build:test 通过。
  • 对 windows-process-cleanup、windows-cleanup-review-regressions、windows-transport-bounds、manager-recovery-latch、spawn-generation-identity、process-lock、validate-plugin-root-pattern 共 7 个测试文件运行:101 项,99 pass / 2 fail。
  • 失败为 process-lock.test.ts 的 a crashing or hung prober is reaped before cleanup leaves no residue 和 a failing terminator or a child that never closes makes the reap fail visibly before any cleanup;两个用例单独重跑仍均失败,错误为 process_tree_cleanup_unverified: 1 recorded member(s) not provably exited。完整根因目前不确定,请排查真实进程回收的预算/退出核验路径;不能仅凭这个结果断言是 CI 偶发或运行环境问题。
  • 本次未跑完整测试矩阵、未重建插件、未运行真实模型账号链路。

与维护者本地改动的整合边界

ENOTEMPTY/EPERM 锁竞争处理、worker fixture 退出后再删目录,与维护者本地修复部分重复。锁释放机制是互补的:本 PR 做有限撤回重试,本地以 token 绑定的 release.json 支持撤回持续失败后的逻辑释放恢复,后续整合应组合保留。Ledger READY Gate、整任务委派与 doctor 诊断属于本地独有改动。

双方还使用了不同内容的 ADR-009,需要整合时重编号并同步引用;无需在本 PR 顺带实现维护者的其他本地功能。

请先修改本 PR 的两处 P1、补回归并排查上述本机测试失败,更新后再审。维护者本地改动暂不合并,本 PR 也先保持未合并。

@Sandyzzx
Sandyzzx merged commit 73af9d6 into Sandyzzx:master Oct 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants