DSH / Atlas
2026-08-28implementedsimplificationarchived 2026-09-04

The absent-probe parent repair runs on Windows only

缺失探测的父目录修复只在 Windows 上执行

`JsonlSessionPersistence.exists` treats ENOENT as absence, and before returning false it stats the path's parent so a session directory blocked by a regular file surfaces as a storage fault rather than as a missing session. Windows needs that: it reports ENOENT, not ENOTDIR, for `regular-file/child`. POSIX does not — `open(2)` specifies ENOTDIR when a component of the path prefix is not a directory, which the repair'

English

Problem

JsonlSessionPersistence.exists treats ENOENT as absence, and before returning false it stats the path's parent so a session directory blocked by a regular file surfaces as a storage fault rather than as a missing session. Windows needs that: it reports ENOENT, not ENOTDIR, for regular-file/child. POSIX does not — open(2) specifies ENOTDIR when a component of the path prefix is not a directory, which the repair's own guard comment already recorded.

The stat ran on every platform and on every absent probe. findLog issues four probes per project directory — two rejecting the legacy flat-file layout, one for the opposite encoding, one for the log itself — and the coordinator resolves an id twice per inspect: once in prepareCore, once in isPreparedSourceCurrent, which runs even when the prepared source is cached. All but one directory answers absent, so nearly every probe paid the extra stat.

Decision

The repair is reached only under process.platform === 'win32', matching the platform dispatch materialize already uses. POSIX keeps the ENOTDIR that open itself reports.

Testing

Counting node:fs/promises calls through the suite's existing module mock, against a store with five project directories — the layout of a real ~/.dsh/sessions:

OperationBeforeAfter
load an existing session40 open + 41 stat40 open + 3 stat
load an absent id20 open + 20 stat20 open + 0 stat

The package's 242 tests pass unchanged and per-file coverage is identical; the new branch carries the same v8 ignore marker as the sibling platform dispatch.

That POSIX never reaches the repair was confirmed directly rather than taken from the comment: opening regular-file/child and regular-file/child/deeper reports ENOTDIR on macOS, while only a genuinely missing directory reports ENOENT.

Alternatives considered

Keep the stat on every platform as defense in depth. Rejected: on POSIX it can only confirm what open already reported, so it detects no fault the caller would otherwise miss — it doubles the syscalls of every absent probe to re-derive a known answer.

Remove the repair outright and let Windows report absence. Rejected: it exists so a session directory blocked by a regular file stays a storage fault instead of reading as a missing session, which is the fail-loud stance the backend takes elsewhere.

Consequences

Path resolution keeps its full cost in open calls: four per project directory per lookup, twice per inspect. This removes the stat half only, and the remaining cost still grows with the number of project directories rather than with the number of sessions.

Collapsing the scan needs an id-to-path index, which first requires deciding what happens to the per-lookup legacy-artifact guard that rejects a compressed obsolete flat-file artifact during targeted lookup pins: that test writes its artifact after list() has memoized the root encoding check, so the guard covers store mutation after memoization, and an index hit would return before reaching it. One readdir per project directory, which would supply both the id-to-path map and that guard without a cache, measured no faster than this change, so any further win requires memoization and its invalidation.

中文

Problem

JsonlSessionPersistence.exists 把 ENOENT 视为「不存在」,并在返回 false 之前 stat 一次路径的父目录,好让「被普通文件挡住的会话目录」暴露成存储故障,而不是一个不存在的会话。Windows 需要这一步:它对 regular-file/child 报的是 ENOENT 而不是 ENOTDIR。POSIX 不需要——open(2) 规定路径前缀中有非目录组件时报 ENOTDIR,这一点该修复自己的守卫注释早已写明。

而这次 stat 在所有平台、每一次缺失探测上都会执行。findLog 对每个 project 目录发四次探测——两次拒绝 legacy 扁平文件布局、一次查相反编码、一次查日志本身——而 coordinator 每次 inspect 要解析两遍 id:一遍在 prepareCore,一遍在 isPreparedSourceCurrent,后者即使 prepared source 已缓存也照跑。除拥有该会话的那个目录外,其余全部回答「不存在」,所以几乎每次探测都付了这次多余的 stat。

Decision

该修复现在只在 process.platform === 'win32' 下可达,与 materialize 既有的平台分派写法一致。POSIX 保留 open 自己报出的 ENOTDIR。

Testing

借用测试套件既有的模块 mock 统计 node:fs/promises 调用,对照一个五 project 目录的存储——即真实 ~/.dsh/sessions 的布局:

操作改动前改动后
load 一个已存在的会话40 open + 41 stat40 open + 3 stat
load 一个不存在的 id20 open + 20 stat20 open + 0 stat

该包的 242 个测试原样通过,单文件覆盖率不变;新增分支带的 v8 ignore 标记与相邻的平台分派一致。

「POSIX 永远到不了这处修复」是直接验证的,而非采信注释:在 macOS 上打开 regular-file/child 与 regular-file/child/deeper 都报 ENOTDIR,只有真正缺失的目录才报 ENOENT。

Alternatives considered

所有平台都保留这次 stat,作为纵深防御。 否决:在 POSIX 上它只能确认 open 已经报出的结论,检测不到任何调用方本会漏掉的故障——代价是把每次缺失探测的系统调用翻倍,只为重新推导一个已知答案。

直接删掉该修复,让 Windows 报「不存在」。 否决:它的存在正是为了让「被普通文件挡住的会话目录」保持为存储故障,而不是读成一个不存在的会话,这与该 backend 别处一贯的 fail-loud 立场一致。

Consequences

路径解析在 open 调用上的开销原样保留:每次查找每个 project 目录四次,每次 inspect 两轮。本次只削掉了 stat 那一半,剩余开销仍随 project 目录数增长,而不随会话数增长。

要压掉这轮扫描需要一份 id→path 索引,而它首先要求决定:rejects a compressed obsolete flat-file artifact during targeted lookup 钉住的那条「每次查找都做的 legacy 产物守卫」该如何安置。该测试是在 list() 已经记忆化 root 编码检查之后才写入产物的,所以这条守卫覆盖的是记忆化之后的存储变动,而索引命中会在到达它之前就返回。「每个 project 目录一次 readdir」本可以同时提供 id→path 映射和这条守卫且无需缓存,但实测并不比本次改动更快,所以更进一步的收益必然要引入记忆化及其失效处理。