docs(write): scope create-only publication visibility by backend - #225
Conversation
|
Codex review: needs maintainer review before merge. Reviewed September 4, 2026, 6:41 AM ET / 10:41 UTC. ClawSweeper reviewWhat this changesThe PR scopes create-only write visibility guarantees by backend, documents the JavaScript fallback’s exclusive-create visibility window, and adds regression coverage for all create-only APIs. Merge readiness✅ Ready for maintainer review Keep open: this accurately corrects a current documentation overstatement and pins the distinct native and JavaScript fallback behaviors. The prior auto-mode qualification has been addressed, and no introduced correctness defect remains. Priority: P2 Review scores
Verification
How this fits togetherRoot is fs-safe’s capability-scoped filesystem writer for untrusted paths. Create-only calls select either native private staging with no-replace publication or the guarded JavaScript fallback, which claims the final name before writing bytes. flowchart LR
A[Caller uses a Root handle] --> B[Create-only write]
B --> C{Native binding loaded?}
C -->|Yes| D[Private staging]
D --> E[No-replace publication]
C -->|No| F[Exclusive final-name claim]
F --> G[Write content in place]
E --> H[Documented visibility contract]
G --> H
Before mergeNone. Agent review detailsSecurityNone. Review metrics
Technical reviewBest possible solution: Land the backend-specific contract wording and its focused regression coverage so callers can select require mode when they need no early destination visibility. Do we have a high-confidence way to reproduce the issue? Yes. The supplied separate-process observation covers all four create-only operations, and current-main source independently shows the fallback creates the final name with O_EXCL before writing content. Is this the best way to solve the issue? Yes. Clarifying the backend boundary is safer than a link-and-unlink fallback implementation because the existing hardlink-rejection invariant remains intact while require mode provides the strong guarantee. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 15d313de56d3. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (3 earlier review cycles)
|
c94e9ad to
06c6167
Compare
The pure-JavaScript fallback has no atomic no-clobber rename: O_EXCL open exposes the final name early, and link+unlink exposes a two-link window that breaks concurrent readers enforcing single-link identity (proven by the sidecar-contention proof). Scope the no-visibility guarantee to backends with atomic no-replace publication, pin both behaviors with regression tests, and document the fallback claim-then-write behavior explicitly.
06c6167 to
84ead44
Compare
|
Addressing the review on head b967e9d (docs precision fix pushed). 1. Fallback staging claim corrected. The docs no longer say create-only writes "share that staging." They now state explicitly that sibling-temp staging with atomic no-replace publication exists only on backends that provide it, and that the pure-JavaScript fallback does not stage: it claims the final name with 2. Real-behavior evidence. Since this PR changes no runtime code, the relevant proof is the documented current behavior, executed on installed packages from actual main ( {
"probe": "separate-process observer at first content write",
"nativeOff": [
{
"operation": "create",
"beforeWriteExists": true,
"beforeWriteBytes": 0,
"finalContentVerified": true
},
{
"operation": "write-no-replace",
"beforeWriteExists": true,
"beforeWriteBytes": 0,
"finalContentVerified": true
},
{
"operation": "createJson",
"beforeWriteExists": true,
"beforeWriteBytes": 0,
"finalContentVerified": true
},
{
"operation": "writeJson-no-replace",
"beforeWriteExists": true,
"beforeWriteBytes": 0,
"finalContentVerified": true
}
],
"nativeRequire": [
{
"operation": "create",
"beforeWriteExists": false,
"finalContentVerified": true
},
{
"operation": "write-no-replace",
"beforeWriteExists": false,
"finalContentVerified": true
},
{
"operation": "createJson",
"beforeWriteExists": false,
"finalContentVerified": true
},
{
"operation": "writeJson-no-replace",
"beforeWriteExists": false,
"finalContentVerified": true
}
]
}In native-off mode the destination existed at size 0 before content (the documented limitation now stated in the docs). In require mode the destination was absent until publication (the guarantee). The regression tests added in this PR execute the same assertions in the repo: fallback name-claim visibility is pinned at The withdrawn hardlink implementation's failure is preserved in the PR description: it passed all local suites but regressed hosted sidecar contention through its transient two-link publication window, which is why the code change was abandoned rather than weakened further. @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
Addressing the re-review finding on head 1502091 (pushed). Default No code changes; the observer receipts in the previous comment remain the behavior evidence. @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Merged as How this resolved. Live testing with a separate-process observer confirmed the original contract violation on The landed change resolves the defect at the contract boundary: the no-visibility guarantee is scoped to backends with atomic no-replace publication ( Verification. Full CI and coverage green on the reviewed head No release or publication was performed. |
Summary
Live testing of
mainconfirmed a real contract violation: in the pure-JavaScript fallback,Root.create(),createJson(), andwrite({ overwrite: false })claim the final destination pathname withO_EXCLbefore content is written, so a concurrent observer can see an empty file at the target. The writing docs promised sibling-temp commit with no half-written replacement visible for these operations.A staging fix was implemented and fully tested locally (private temp file published via hardlink link+unlink). It worked, but the hosted sidecar-contention proof caught a worse regression: between the link and the unlink, the published name transiently has two links, and concurrent readers/creators that enforce single-link identity fail with
hardlink/path-aliaserrors instead of clean contention. POSIX plus Node's fs API offers no atomic operation that is simultaneously no-clobber, content-complete, and single-link:renameclobbers,O_EXCLopen exposes the name early, and link+unlink exposes a two-link window. The native binding'srenameNoReplaceis the correct mechanism and already provides the guarantee.So this PR resolves the defect at the contract boundary rather than by regressing concurrent behavior:
auto/require). The pure-JavaScript fallback's claim-then-write behavior is documented explicitly, matching the package's existing best-effort fallback model.already-existscollision preservation, failure cleanup) are asserted for all four create-only operations.Evidence
mainwith a separate-process observer: all four create-only operations exposed the destination at size 0 before content, in native-off mode; the same probe in require mode saw the destination absent until publication.sidecar-contention-proofroot-async:hardlink/path-aliaserrors, 30/400 acquisitions before the watchdog). That failure is the reason the code change was withdrawn; it is described here rather than hidden.Boundaries
No runtime code changes ship in this PR. No API changes, no new options. The fallback limitation is a Node/POSIX capability gap, not a new relaxation: native (the default) already satisfies the strong contract.