@deepseek-ai/dsh-atomic-write 0.2.0-rc.2 → 0.2.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -30,11 +30,11 @@
30
30
  en: 93b537308f7c88c7
31
31
  zh: 282e0d2042aeb1be
32
32
  /deepseek-ai-dsh-atomic-write/understand-the-implementation/source-map:
33
- en: 725509a8b621c668
34
- zh: c11e3ffdf73af844
33
+ en: aee5320dc8746377
34
+ zh: f35dbbf350f7b5cf
35
35
  /deepseek-ai-dsh-atomic-write/understand-the-implementation/write-path:
36
- en: 2e6561c9282f0c0d
37
- zh: 7f0e76f6dbdea501
36
+ en: 9b84481fd4a97a49
37
+ zh: 09e25c77eb5e53fb
38
38
  /deepseek-ai-dsh-atomic-write/understand-the-implementation/why-the-swap-stays-safe:
39
39
  en: 784e985c1fb3a25e
40
40
  zh: 4afda1b9bf5f29cc
package/README.md CHANGED
@@ -77,13 +77,12 @@ The package is built on one separation: the atomic commit owns the swap, and the
77
77
  | File | Role |
78
78
  |---|---|
79
79
  | [`src/index.ts`](src/index.ts) | `writeFileAtomic` and `withFileLock`, the package's whole surface |
80
- | — | No runtime invariant companion is published; this pure filesystem primitive owns no event stream or mutable runtime data; its replacement contract is enforced by unit tests. |
81
80
 
82
81
  ### Write path
83
82
 
84
83
  `writeFileAtomic` writes a random-suffix sibling opened with exclusive create (`wx`), then renames it over the target. The exclusive open refuses to follow a symlink planted at a guessable temp path; the same-directory sibling keeps the rename on one filesystem; and the rename replaces a symlinked target itself instead of writing through to its referent. A Windows retry keeps the same complete sibling and uses bounded exponential backoff, so temporary use of the target by software outside the cooperative writer lock cannot turn a safe replacement into an immediate failure; the archived [retry decision record](../../../.agents/notes/archived/bug-fix/2026-08-29-windows-atomic-replace-retry.md) documents the original rationale and rejected alternatives.
85
84
 
86
- `withFileLock` creates a `<filename>.lock` sibling with `wx`. `EEXIST` identifies contention directly; `EPERM` does so only when a fresh `lstat` confirms the lock path exists, covering Windows exclusive-create behavior without hiding an unrelated permission failure. The lock records its creator's PID as `<pid>\n` and is removed by the holder in a `finally`. A contender that reads a record whose PID a signal probe reports as absent (`ESRCH`) creates a `<filename>.lock.takeover-<record hash>` claim with `wx`, re-reads the lock and probes its PID again, removes it only if it still holds the same record and that PID is still absent, removes the claim, and retries at once. A holder that exists under another user (`EPERM`) and a record naming the contender's own process are waited for. Contenders that read the same record contend for one claim, and the second probe rejects a holder that reused the exited PID, so a takeover never removes a lock that another contender acquired after the exited holder's. Takeover proves only that the recorded process exited; an operation that starts other writers leaves its successor a way to find them, as the [Plugin Manager](../../boot/plugin-manager/README.md) does for its pnpm runs. Contention backs off exponentially and fails when the per-call `waitMs` deadline (default two seconds) passes; the [takeover decision record](../../../.agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.md) owns the rationale.
85
+ `withFileLock` creates a `<filename>.lock` sibling with `wx`. `EEXIST` identifies contention directly; `EPERM` does so only when a fresh `lstat` confirms the lock path exists, covering Windows exclusive-create behavior without hiding an unrelated permission failure. The lock records its creator's PID as `<pid>\n` and is removed by the holder in a `finally`. A contender that reads a record whose PID a signal probe reports as absent (`ESRCH`) creates a `<filename>.lock.takeover-<record hash>` claim with `wx`, re-reads the lock and probes its PID again, removes it only if it still holds the same record and that PID is still absent, removes the claim, and retries at once. A holder that exists under another user (`EPERM`) and a record naming the contender's own process are waited for. Contenders that read the same record contend for one claim, and the second probe rejects a holder that reused the exited PID, so a takeover never removes a lock that another contender acquired after the exited holder's. Takeover proves only that the recorded process exited; an operation that starts other writers leaves its successor a way to find them, as the [Plugin Manager](../../boot/plugin-manager/README.md) does for its pnpm runs. Contention backs off exponentially and fails when the per-call `waitMs` deadline (default two seconds) passes; the [historical takeover decision record](../../../.agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.md) records the rationale.
87
86
 
88
87
  ### Why the swap stays safe
89
88
 
package/README.zh.md CHANGED
@@ -77,13 +77,12 @@ Windows 在无法观察到锁时会对 `EPERM` 重试一次,因为持锁方可
77
77
  | 文件 | 职责 |
78
78
  |---|---|
79
79
  | [`src/index.ts`](src/index.ts) | `writeFileAtomic` 与 `withFileLock`,即本包的全部接口 |
80
- | — | 不发布运行时不变式伴生入口;这个纯文件系统原语不维护事件流或可变运行时数据;其替换约定由单元测试覆盖。 |
81
80
 
82
81
  ### 写入路径
83
82
 
84
83
  `writeFileAtomic` 先以独占创建(`wx`)打开一个随机后缀的同级文件并写入内容,然后 rename 到目标上。独占打开拒绝跟随预先埋在可猜测临时路径上的符号链接;同目录兄弟文件保证 rename 落在同一文件系统上;rename 替换的是目标位置的符号链接本身,绝不写穿到该链接指向的文件。Windows 重试会保留同一份完整的兄弟文件,并采用有界指数退避,因此协作式写锁之外的软件瞬时占用目标时,不会让安全替换立即失败;已归档的[重试决策记录](../../../.agents/notes/archived/bug-fix/2026-08-29-windows-atomic-replace-retry.md)记录了最初的理由与被拒绝的替代方案。
85
84
 
86
- `withFileLock` 以 `wx` 创建 `<filename>.lock` 同级文件。`EEXIST` 直接表示竞争;只有一次新的 `lstat` 确认锁路径存在时,`EPERM` 才表示竞争,从而兼容 Windows 的独占创建行为,又不掩盖无关的权限故障。锁以 `<pid>\n` 记录创建者的 PID,由持有者在 `finally` 中移除。竞争者读到的记录若经信号探测报告该 PID 不存在(`ESRCH`),就以 `wx` 创建 `<filename>.lock.takeover-<记录哈希>` 认领文件,重新读取锁并再次探测其 PID,仅当锁仍是同一条记录且该 PID 仍不存在时才移除它,随后移除认领文件并立即重试。以其他用户身份存在的持有者(`EPERM`)和指向竞争者自身进程的记录会继续等待。读到同一条记录的竞争者争用同一个认领文件,第二次探测会排除复用了已退出 PID 的持有者,因此接管绝不会移除另一个竞争者在已退出持有者之后获得的锁。接管只能证明记录中的进程已退出;启动了其他写入者的操作要为后继者留下找到它们的途径,[Plugin Manager](../../boot/plugin-manager/README.zh.md) 对其 pnpm 运行就是这样做的。竞争按指数退避,在每次调用声明的 `waitMs` 期限(默认两秒)过后失败;[接管决策记录](../../../.agents/notes/implemented/bug-fix/2026-09-24-exited-holder-lock-takeover.zh.md)负责说明理由。
85
+ `withFileLock` 以 `wx` 创建 `<filename>.lock` 同级文件。`EEXIST` 直接表示竞争;只有一次新的 `lstat` 确认锁路径存在时,`EPERM` 才表示竞争,从而兼容 Windows 的独占创建行为,又不掩盖无关的权限故障。锁以 `<pid>\n` 记录创建者的 PID,由持有者在 `finally` 中移除。竞争者读到的记录若经信号探测报告该 PID 不存在(`ESRCH`),就以 `wx` 创建 `<filename>.lock.takeover-<记录哈希>` 认领文件,重新读取锁并再次探测其 PID,仅当锁仍是同一条记录且该 PID 仍不存在时才移除它,随后移除认领文件并立即重试。以其他用户身份存在的持有者(`EPERM`)和指向竞争者自身进程的记录会继续等待。读到同一条记录的竞争者争用同一个认领文件,第二次探测会排除复用了已退出 PID 的持有者,因此接管绝不会移除另一个竞争者在已退出持有者之后获得的锁。接管只能证明记录中的进程已退出;启动了其他写入者的操作要为后继者留下找到它们的途径,[Plugin Manager](../../boot/plugin-manager/README.zh.md) 对其 pnpm 运行就是这样做的。竞争按指数退避,在每次调用声明的 `waitMs` 期限(默认两秒)过后失败;[历史接管决策记录](../../../.agents/notes/archived/bug-fix/2026-09-24-exited-holder-lock-takeover.md)负责说明理由。
87
86
 
88
87
  ### 交换为何安全
89
88
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-atomic-write",
3
3
  "description": "Zero-dependency atomic file replacement: exclusive-create random-suffix temp + rename carrying the caller-stated permissions (writeFileAtomic)",
4
- "version": "0.2.0-rc.2",
4
+ "version": "0.2.1-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,9 +27,9 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/cordis": "~4.0.4"
30
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1"
31
31
  },
32
32
  "devDependencies": {
33
- "@deepseek-ai/cordis": "~4.0.4"
33
+ "@deepseek-ai/cordis": "~4.0.5-alpha.1"
34
34
  }
35
35
  }