feat: 0.5.0 API surface — archive policy, publication mechanics, sync locks, secret create-only, Root.walk, cleanup receipts#59
feat: 0.5.0 API surface — archive policy, publication mechanics, sync locks, secret create-only, Root.walk, cleanup receipts#59steipete wants to merge 35 commits into
Conversation
|
Codex review: found issues before merge. Reviewed July 26, 2026, 12:04 AM ET / 04:04 UTC. ClawSweeper reviewWhat this changesThis PR replaces the optional Python helper with optional native filesystem packages and adds archive, publication, walking, locking, secret, temporary-workspace, Windows-permission, and release-pipeline APIs. Merge readinessKeep this PR open for an explicit release-direction decision: the documented 0.5 migration makes existing required-helper deployments fail closed when no native package is usable, and the only supplied approval statement is from the contributor. Likely related people: steipete (low-confidence routing candidate for the proposed native-helper migration). Priority: P1 Review scores
Verification
How this fits togetherfs-safe provides capability-style filesystem roots and guarded helper APIs for Node applications handling untrusted paths. Calls select an optional native mechanism or a guarded JavaScript fallback, then use identity and path checks before changing filesystem state. flowchart LR
App[Node application] --> Root[Capability root and policies]
Root --> Mode[Native mode selection]
Mode --> Native[Optional native packages]
Mode --> Fallback[Guarded JavaScript fallback]
Native --> Guards[Path and identity guards]
Fallback --> Guards
Guards --> APIs[Archive, publish, walk, lock and secret APIs]
APIs --> Disk[Filesystem state]
Decision needed
Why: This is an intentional public upgrade-contract and fail-closed-policy choice; the supplied contributor statement does not establish independent maintainer acceptance of the deployment break. Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Technical reviewBest possible solution: Land the native-helper design only after a release owner explicitly accepts the required-helper compatibility break, or retain a compatible transition path with tested default and strict-mode behavior through the 0.5 release. Do we have a high-confidence way to reproduce the issue? Not applicable: this is a feature PR, so the relevant evidence is after-fix behavior and upgrade safety rather than reproduction of a current-main bug. Is this the best way to solve the issue? No: replacing the existing required-helper contract without an independent release decision is not yet the best maintainable path; either explicitly approve that incompatibility or preserve a tested transition. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 8cced2ab6264. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (12 earlier review cycles; latest 8 shown)
|
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
|
Verified as development-only: @SocketSecurity ignore npm/fast-wrap-ansi@0.2.2 |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
Maintainer decision: 0.5 intentionally adopts the native-only helper migration. Legacy Python modes warn and map for 0.5.x, but configured interpreter paths are no longer executed. @clawsweeper re-review |
|
Resolved the final P1: @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
Summary
This is the complete 0.5.0 surface. Nothing in this PR is staged for a later feature release: optional native acceleration, every guarded JavaScript fallback, archive policy, publication semantics, walking, locks, secret/temp APIs, Windows security, migration guidance, CI, and release-graph proof all ship together.
copy_file_range, JavaScript-loop, and native no-replace rename strategies under common identity/content/durability fencing.Final 0.5 additions
Harvest-audit API closeout
movePathWithCopyFallback({ sourceHardlinks: "reject" })now performs a streaming 50,000-entry recursive preflight before mutation. This closes the shipped 0.4.x ordering gap where a successful same-filesystem rename bypassed the policy. Because link count and rename cannot be made atomic portably, this opt-in mode publishes a fresh staged copy even on one filesystem, fencingnlinkafter open and after copying; self-descendant, symlink-aliased, and dot-segment destinations fail before staging.sha256File(input: string | FileHandle)is public from@openclaw/fs-safe/durabilityand returnsPromise<{ bytes: number; digest: string }>. It uses the async native task when available and a positioned 64 KiB JS stream otherwise, preserves caller handle ownership/offset, and opens path inputs no-follow/nonblocking before regular-file and identity validation.readOwnerAndDacl(path)is public from@openclaw/fs-safe/permissions. Windows returns{ status: "supported", ownerSid, daclPresent, isLocal, complete, unsupportedAceTypes, aces }; each ordered allow/deny ACE contains{ sid, mask, aceType, flags }, includinginheritOnlyand raw/decoded inheritance flags. Other platforms return{ status: "unsupported-platform", platform }. The API reports facts only and applies no trusted-SID policy.Publication sync-failure policy
publishFileExclusive()adds:rollbackremains the default. After a thrown parent-directory sync, fs-safe removes only the unchanged target identity created by this call.preservekeeps the complete, fenced target and throws the typed error withdetails: { phase: "directory-sync", targetCreated: true, targetIdentity, cleanup: "preserved", directorySync: { status: "failed", code? } }.rename-noreplacealso always preserves after a successful rename because the target is the source's only remaining name.preserve; protocols where the name means “durably committed” normally preferrollback.OpenClaw adoption closeout
Root.walk()supportsentryFilter: "include" | "skip" | "skip-subtree"andonDirectoryError: "throw" | "skip-and-report"; every examined entry consumes the traversal budget before filtering.maxEntryPathComponentsdefaults to 256 afterstripComponentsand rejects JS/native extraction before implicit directory creation.Architecture and fallback contract
TypeScript is the only policy owner. Rust receives validated relative mechanisms and accepted archive plans; no policy is duplicated. Native absence does not change traversal/link rejection, filters, limits, mode clamping, exclusive creation, identity/content fencing, cleanup receipts, secret policy, or lock policy.
The mechanism delta is explicit:
openat2, component walk,NtCreateFilewith reparse rejection)helper-unavailablecopy_file_range/async native SHA-256rename-noreplaceGetSecurityInfo, ordered basic ACE factsautoprefers native and falls back where equivalent;offdeterministically tests JavaScript;requirefails closed. Import-time loading never spawns a process.Packed behavior proof
The nine-package macOS arm64 scratch install exercises the published package boundary:
{ "native": { "nativeBinding": "loaded", "openBeneath": "function", "publicHash": { "bytes": 3, "digest": "ba7816bf...15ad" } }, "fallback": { "nativeMode": "off", "fallback": "hardlink" }, "finalRound": { "hash": { "bytes": 3, "digest": "ba7816bf...15ad" }, "dacl": { "status": "unsupported-platform", "platform": "darwin" }, "moveHardlinks": { "sameFilesystemRejected": true, "errorCode": "hardlink" }, "archive": { "errorCode": "entry-filtered", "pathLimitCode": "archive-entry-path-components-exceeds-limit" }, "walk": { "prunedSubtree": true, "directoryErrorReported": true, "healthyEntryPreserved": true } }, "missingRequiredBinding": { "errorCode": "helper-unavailable" } }The source-blind behavior contract varied native
require/off, physically removed the binding for the required-native failure, checked hardlink source/target state, and exercised both hash implementations.Documentation and migration
The new 0.5 migration checklist covers install/runtime, Python-to-native configuration, archive clamp-default auditing, filter/limit adoption, publication sync policy, bounded walking, locks/secrets/temp, native-only features, exact Windows ACE facts, public streaming hashes, and consumer CI.
The docs pass also corrected behavior drift: Windows fallback inspection uses path-only
icaclsrather than invalid/sid; lock retry factor defaults to 1; non-test hook registration throws;FsSafeErrorexposesdetails; the main export and install subpath tables now matchpackage.json; sync-lock examples are actually synchronous; strict secret reads include their required label; and the quickstart copies into the temp workspace rather than an unrelated root.ClawSweeper checklist
offfallback, and binding-removedrequirefailure.auto/require/off, native/JS equivalence, Alpine musl, archive adversarial cases, publication receipts, and Windows fallback/native modes.ldd; loader detection is strictly non-executing.fast-wrap-ansi@0.2.2is dev-only through@napi-rs/cli; Socket justification is in this comment.Corrected APFS benchmark
Local macOS arm64, APFS SSD, Node 26.5.0, release native build, real-byte fixtures. End-to-end includes all identity, fsync, and SHA-256 fences; primitive rows isolate clone + normalization/fsync.
Single-host medians only; the public native operation still scales with mandatory hashing.
CI and local proof
Final exact head:
cfbe661c2a590c3997315da6e162a0a416a69f37.pnpm check(585 passed, 7 skipped),pnpm test:security(59 passed), Rust tests (13 passed), clippy with warnings denied, RustSec audit of 70 locked dependencies, cargo fmt, actionlint, docs build, packed release-graph smoke, source-blind behavior validation, and final P1 autoreview clean.Final numstat
Rust dependency health
tarflate2zstdbzip2zipsha2cargo audit --deny warningsreports no advisories across the 70 locked Rust dependencies.Final downstream identity addendum: Windows
readOwnerAndDacl()results now includecurrentUserSid, serialized from the current process token'sTokenUser; it is a policy-free fact alongsideownerSid.