feat(util): extract dsh-atomic-write and migrate settings-local writes
writeFileAtomic: exclusive-create random-suffix temp + rename carrying the caller-stated mode; settings-local persistSection now consumes it. The credentials-local store shares it next.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# dsh-atomic-write
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Zero-dependency atomic file replacement shared by file-backed stores that must never leave partial, symlink-hijacked, or wider-than-intended content on disk — the user-settings document (`dsh-settings-local`) and the credentials store (`dsh-credentials-local`).
|
||||
|
||||
## Surface
|
||||
|
||||
```ts
|
||||
import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write'
|
||||
|
||||
await writeFileAtomic('/home/u/.dsh/settings.yaml', text, { mode: 0o600 })
|
||||
```
|
||||
|
||||
One export. The contract, in the order failures would exploit it:
|
||||
|
||||
- **Exclusive-create temp** (`wx`, random suffix): the open refuses to follow a symlink planted at a guessable temp path.
|
||||
- **The fresh inode carries `mode` through the rename**: replacing a wider-permission file narrows it without a chmod race. `mode` is required so the permission decision stays visible at every call site (subject to the process umask, like every fresh inode).
|
||||
- **`rename` replaces a symlinked target itself**, never writing through to its referent.
|
||||
- **Same-directory sibling** keeps the rename on one filesystem, so the swap stays atomic.
|
||||
- Parent directories are created; on any failure the temp is removed and the failure rethrown; readers observe either the old or the new complete content.
|
||||
|
||||
## Model Experience
|
||||
|
||||
None, as this is a pure filesystem primitive; nothing here reaches a model request.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **Atomic, not durable** — no `fsync` of the file or its directory, so after a crash the rename may be observed unwound. The file-backed stores here re-read and republish on boot, keeping durability the caller's policy.
|
||||
- **String content only** — no `Buffer` or stream form until a consumer needs one.
|
||||
Reference in New Issue
Block a user