diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ccf67..184cb91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,32 @@ # Changelog -## 0.7.1 - Unreleased - -- Keep lock payload, serialization, and parsing failures outside acquisition retry handling, and scope Root failure receipts to each observation so historical errors cannot replay retry authority. -- Retry Root-backed async lock handoffs with descriptor-proven, budgeted observation discard, including Windows unlink resolution failures; verify creator bytes before admission and preserve creator-token cleanup authority on failure. -- Reject native selected ZIP entry reads whose decoded size differs from the declared uncompressed size, preserving byte caps and CRC verification. -- Canonicalize Windows synchronous lock parents consistently with Root, bound lock-file open-denial and missing-snapshot retries, use captured snapshot age for staleness, and retain failed release cleanup for retry without double-decrementing references or re-closing a consumed descriptor. -- Keep create-only Root writes from opening an existing target merely to inherit its mode, while preserving boundary, alias, hardlink, and type checks. -- Clarify that invalid UTF-8 or NUL padding in fixed TAR path fields rejects with `ArchiveSecurityError("entry-path")`. +## 0.7.1 - 2026-09-01 + +### Highlights + +- **Make lock handoffs reliable:** recover from disappearing ownership records within retry budgets, preserve callback errors, and safely retry failed synchronous release cleanup. +- **Reject corrupt native ZIP reads:** verify decoded entry length against the declared size before returning bytes, while retaining byte caps and CRC checks. +- **Preserve long destination filenames:** use independent private staging names for Root writes and copies, ZIP extraction, and synchronous JSON writes instead of lengthening the destination basename. + +### File locks + +- Recover Root-backed asynchronous handoffs only with exact descriptor identity and unlink evidence, including Windows resolver `EPERM`/`EBADF` failures. Recheck canonical ancestors and charge discarded observations to retry/deadline budgets without granting release or reclaim authority. ([#189](https://github.com/openclaw/fs-safe/pull/189)) +- Verify reopened Root-created sidecars against the creator's exact serialized bytes and ownership token before admission. Reject replacements and unlinked descriptors, and retain the original creator receipt for failed-acquisition cleanup. ([#189](https://github.com/openclaw/fs-safe/pull/189)) +- Align Windows synchronous lock-parent canonicalization with Root, including short-name expansion, and bound lock-file create/open denials and missing or changed snapshot retries. Preserve the original `EPERM` when a retry budget is exhausted. ([#189](https://github.com/openclaw/fs-safe/pull/189)) +- Determine synchronous lock staleness from the captured payload timestamp or snapshot mtime, avoiding false stale decisions after unlink or replacement. ([#189](https://github.com/openclaw/fs-safe/pull/189)) +- Retain failed synchronous release cleanup for safe retries without double-decrementing references, re-closing a consumed or reused descriptor, or deleting a replacement sidecar. ([#189](https://github.com/openclaw/fs-safe/pull/189)) +- Propagate payload, JSON serialization, and parsing failures unchanged without retrying the failing callback. Scope Root failure evidence to each observation so historical errors cannot trigger retries in later, nested, or concurrent acquisitions. ([#190](https://github.com/openclaw/fs-safe/pull/190)) + +### File writes and archives + +- Keep private staging names independent of destination basenames across guarded Root writes and copies, native Windows writes, ZIP extraction, and `writeJsonSync()`. Preserve public producer filenames, custom `tempPrefix`, and literal JSON parent-path and drive-relative semantics. ([#191](https://github.com/openclaw/fs-safe/pull/191)) +- Avoid opening an existing target merely to inherit its mode during create-only Root writes, while preserving boundary, alias, hardlink, and type checks. ([#189](https://github.com/openclaw/fs-safe/pull/189)) +- Reject native `readArchiveEntry()` ZIP payloads whose decoded length differs from the declared uncompressed size with `ArchiveFormatError("archive-header-invalid")`, matching JavaScript behavior while preserving `maxBytes` and CRC checks. ([#189](https://github.com/openclaw/fs-safe/pull/189)) + +### Docs and validation + +- Clarify that invalid UTF-8 or nonzero bytes after NUL terminators in fixed TAR name, linkname, and USTAR prefix fields reject with `ArchiveSecurityError("entry-path")`; this documents existing behavior. ([#189](https://github.com/openclaw/fs-safe/pull/189)) +- Add cross-process contention and release-retry proofs, plus focused lock-admission and ZIP-integrity coverage in JavaScript-fallback and required-native CI paths. Refresh the release checklist for eight-package trusted publishing. ## 0.7.0 - 2026-08-31 diff --git a/Cargo.lock b/Cargo.lock index 0be3493..d7429c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,7 +154,7 @@ dependencies = [ [[package]] name = "fs-safe-native" -version = "0.7.0" +version = "0.7.1" dependencies = [ "bzip2", "flate2", diff --git a/RELEASE-PREREQS.md b/RELEASE-PREREQS.md index 3fa475d..54d9bb0 100644 --- a/RELEASE-PREREQS.md +++ b/RELEASE-PREREQS.md @@ -1,12 +1,23 @@ -# 0.5.0 release prerequisites +# Release prerequisites -Complete this checklist in npm before pushing the first `v0.5.0` tag. The release workflow uses npm trusted publishing and has no npm token fallback. +Maintainer checklist for the protected, tag-driven [release workflow](.github/workflows/release.yml). See [the contribution guide](docs/contributing.md) for development and validation details. Publishing uses npm trusted publishing, with no npm token fallback or local publication. ## Package setup -Exactly one package is published: `@openclaw/fs-safe`. It already exists, is public, and is owned by the OpenClaw npm organization. +A release publishes eight packages: -Open its npm package settings and configure this trusted publisher: +- `@openclaw/fs-safe`: JavaScript, declarations, and documentation; no embedded native binaries. +- `@openclaw/fs-safe-darwin-arm64` +- `@openclaw/fs-safe-darwin-x64` +- `@openclaw/fs-safe-linux-arm64-gnu` +- `@openclaw/fs-safe-linux-arm64-musl` +- `@openclaw/fs-safe-linux-x64-gnu` +- `@openclaw/fs-safe-linux-x64-musl` +- `@openclaw/fs-safe-win32-x64-msvc` + +The `native/` npm workspace and Rust crate are private build inputs, not additional published packages. The seven native packages are platform-filtered optional dependencies of the root package. + +Each published package must have this trusted publisher configured in npm: | npm trusted-publisher field | Required value | |---|---| @@ -14,16 +25,76 @@ Open its npm package settings and configure this trusted publisher: | Organization or user | `openclaw` | | Repository | `fs-safe` | | Workflow filename | `release.yml` | -| Environment | Leave empty; this workflow does not use a GitHub environment. | +| Environment | Leave empty; the workflow does not use a GitHub environment. | + +Use the existing package and publisher configuration; do not bootstrap or reconfigure it merely to retry a release. Publishing maintainers must have 2FA enabled. Do not add `NPM_TOKEN` or an automation token to GitHub. + +## Prepare the release candidate + +Update the root package version, all seven native package versions, all seven exact root optional-dependency pins, the private native npm workspace version, and the crate version in `native/Cargo.toml`. The root `Cargo.toml` is an unversioned workspace. + +Regenerate the lockfiles rather than editing them by hand: + +```bash +pnpm install +``` + +```bash +cargo update --workspace --offline +``` + +Inspect both lockfile diffs for unrelated dependency changes. Use the toolchain versions declared in the repository and workflow. + +Finalize a nonempty, dated `## X.Y.Z - YYYY-MM-DD` changelog section, including every user-visible change since the preceding release. Generate and inspect its release notes: + +```bash +pnpm release:notes X.Y.Z +``` + +Run the release checks on the candidate: + +```bash +pnpm check +``` + +```bash +pnpm test:security +``` + +```bash +cargo test --workspace --locked +``` + +```bash +cargo clippy --workspace --locked -- -D warnings +``` + +```bash +pnpm docs:site +``` + +```bash +git diff --check +``` + +Host-native package proof additionally uses `pnpm native:build` followed by `pnpm package:smoke`. Its synthetic foreign-platform filtering fixtures are not foreign runtime proof. Full `pnpm package:collect` requires all seven real bindings assembled by the release workflow and must run through pnpm. + +Do not commit generated `dist/` files, native binaries, or release artifacts. + +## Tag and publish + +Merge the reviewed release preparation into `main` after its exact-head CI is green. Confirm the intended release commit is the requested latest main before creating an annotated, protected `vX.Y.Z` tag on that exact commit. Push only that tag; never move or overwrite an existing release tag. + +The workflow checks tag format, annotation, protection, main ancestry, matching package/crate versions and pins, and dated changelog content. Main ancestry alone does not guarantee the tag points to latest main, and the workflow does not wait for a separate CI run; maintainers must enforce both conditions before tagging. + +The automated order is source validation, seven-target native build, assembly and eight-package smoke validation, draft GitHub Release creation, platform-package publication, root-package publication, cryptographic registry verification, release-note proof generation, and draft promotion. A tag push alone is not a completed release. + +If a version already exists, the publishing helper verifies it instead of republishing it. Preserve the collected manifest and tarballs when investigating a failure. Never rebuild or replace published artifacts to work around a byte, signature, or provenance mismatch. -Confirm the publishing maintainers have 2FA enabled. Do not add `NPM_TOKEN` or an automation token to GitHub. The former native loader and platform package names were never published, so there is nothing to deprecate or unpublish. +## Verify the completed release -## Release commit and tag +For the root and all seven native packages, verify the exact version, expected `latest` dist-tag, canonical registry tarball URL, integrity, publication time, registry signatures, and workflow-bound provenance. Confirm the public GitHub Release and protected annotated tag exist and point to the intended commit. -- Confirm `0.5.0` in the root package, private native build workspace, and Rust crate. -- Run `pnpm install` so `pnpm-lock.yaml` matches the stable package version. -- Change `## 0.5.0 - Unreleased` in `CHANGELOG.md` to the release date. -- Run `pnpm check`, `pnpm test:security`, `cargo test --workspace --locked`, `cargo clippy --workspace --locked -- -D warnings`, `pnpm docs:site`, and `git diff --check` on the release commit. -- Merge the release commit to `main`, then create an annotated, protected `v0.5.0` tag on that exact commit. +The Release body must match the finalized changelog and include npm version links, registry tarball links, integrity, attestation proof, and the successful Actions run. Download the workflow's collected package artifacts promptly; their retention is bounded. -The workflow validates the protected annotated tag, `main` ancestry, package and crate versions, all seven non-empty native binaries, the single tarball's bytes, install/import behavior, native loading and fallback behavior, and changelog-derived release notes before it publishes anything. +Leave the repository clean on synchronized `main`. Do not prefill another release or create the next Unreleased section as part of closeout. diff --git a/native/Cargo.toml b/native/Cargo.toml index 35028ad..4861130 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fs-safe-native" -version = "0.7.0" +version = "0.7.1" edition = "2024" license = "MIT" repository = "https://github.com/openclaw/fs-safe" diff --git a/native/package.json b/native/package.json index c755af7..d93d721 100644 --- a/native/package.json +++ b/native/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/fs-safe-native-build", - "version": "0.7.0", + "version": "0.7.1", "description": "Private build workspace for the native bindings bundled by @openclaw/fs-safe.", "private": true, "license": "MIT", diff --git a/package.json b/package.json index b02ad36..4265f7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/fs-safe", - "version": "0.7.0", + "version": "0.7.1", "description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.", "keywords": [ "filesystem", @@ -149,13 +149,13 @@ "crabbox:warmup": "crabbox warmup" }, "optionalDependencies": { - "@openclaw/fs-safe-darwin-arm64": "0.7.0", - "@openclaw/fs-safe-darwin-x64": "0.7.0", - "@openclaw/fs-safe-linux-arm64-gnu": "0.7.0", - "@openclaw/fs-safe-linux-arm64-musl": "0.7.0", - "@openclaw/fs-safe-linux-x64-gnu": "0.7.0", - "@openclaw/fs-safe-linux-x64-musl": "0.7.0", - "@openclaw/fs-safe-win32-x64-msvc": "0.7.0", + "@openclaw/fs-safe-darwin-arm64": "0.7.1", + "@openclaw/fs-safe-darwin-x64": "0.7.1", + "@openclaw/fs-safe-linux-arm64-gnu": "0.7.1", + "@openclaw/fs-safe-linux-arm64-musl": "0.7.1", + "@openclaw/fs-safe-linux-x64-gnu": "0.7.1", + "@openclaw/fs-safe-linux-x64-musl": "0.7.1", + "@openclaw/fs-safe-win32-x64-msvc": "0.7.1", "jszip": "^3.10.1", "tar": "7.5.22" }, diff --git a/packages/darwin-arm64/package.json b/packages/darwin-arm64/package.json index 381b7c3..e8dfc59 100644 --- a/packages/darwin-arm64/package.json +++ b/packages/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/fs-safe-darwin-arm64", - "version": "0.7.0", + "version": "0.7.1", "description": "macOS arm64 native binding for @openclaw/fs-safe.", "license": "MIT", "author": "OpenClaw Team ", diff --git a/packages/darwin-x64/package.json b/packages/darwin-x64/package.json index 27186ea..981e567 100644 --- a/packages/darwin-x64/package.json +++ b/packages/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/fs-safe-darwin-x64", - "version": "0.7.0", + "version": "0.7.1", "description": "macOS x64 native binding for @openclaw/fs-safe.", "license": "MIT", "author": "OpenClaw Team ", diff --git a/packages/linux-arm64-gnu/package.json b/packages/linux-arm64-gnu/package.json index 42c0319..f44ea0b 100644 --- a/packages/linux-arm64-gnu/package.json +++ b/packages/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/fs-safe-linux-arm64-gnu", - "version": "0.7.0", + "version": "0.7.1", "description": "Linux arm64 glibc native binding for @openclaw/fs-safe.", "license": "MIT", "author": "OpenClaw Team ", diff --git a/packages/linux-arm64-musl/package.json b/packages/linux-arm64-musl/package.json index 542dab6..7398bc6 100644 --- a/packages/linux-arm64-musl/package.json +++ b/packages/linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/fs-safe-linux-arm64-musl", - "version": "0.7.0", + "version": "0.7.1", "description": "Linux arm64 musl native binding for @openclaw/fs-safe.", "license": "MIT", "author": "OpenClaw Team ", diff --git a/packages/linux-x64-gnu/package.json b/packages/linux-x64-gnu/package.json index 99f763e..19b7fcb 100644 --- a/packages/linux-x64-gnu/package.json +++ b/packages/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/fs-safe-linux-x64-gnu", - "version": "0.7.0", + "version": "0.7.1", "description": "Linux x64 glibc native binding for @openclaw/fs-safe.", "license": "MIT", "author": "OpenClaw Team ", diff --git a/packages/linux-x64-musl/package.json b/packages/linux-x64-musl/package.json index 77c3ee6..5c12a9e 100644 --- a/packages/linux-x64-musl/package.json +++ b/packages/linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/fs-safe-linux-x64-musl", - "version": "0.7.0", + "version": "0.7.1", "description": "Linux x64 musl native binding for @openclaw/fs-safe.", "license": "MIT", "author": "OpenClaw Team ", diff --git a/packages/win32-x64-msvc/package.json b/packages/win32-x64-msvc/package.json index 04a7777..47e8ca4 100644 --- a/packages/win32-x64-msvc/package.json +++ b/packages/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@openclaw/fs-safe-win32-x64-msvc", - "version": "0.7.0", + "version": "0.7.1", "description": "Windows x64 MSVC native binding for @openclaw/fs-safe.", "license": "MIT", "author": "OpenClaw Team ", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 73f9cf0..475cf5f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,25 +46,25 @@ importers: version: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.1)) optionalDependencies: '@openclaw/fs-safe-darwin-arm64': - specifier: 0.7.0 + specifier: 0.7.1 version: link:packages/darwin-arm64 '@openclaw/fs-safe-darwin-x64': - specifier: 0.7.0 + specifier: 0.7.1 version: link:packages/darwin-x64 '@openclaw/fs-safe-linux-arm64-gnu': - specifier: 0.7.0 + specifier: 0.7.1 version: link:packages/linux-arm64-gnu '@openclaw/fs-safe-linux-arm64-musl': - specifier: 0.7.0 + specifier: 0.7.1 version: link:packages/linux-arm64-musl '@openclaw/fs-safe-linux-x64-gnu': - specifier: 0.7.0 + specifier: 0.7.1 version: link:packages/linux-x64-gnu '@openclaw/fs-safe-linux-x64-musl': - specifier: 0.7.0 + specifier: 0.7.1 version: link:packages/linux-x64-musl '@openclaw/fs-safe-win32-x64-msvc': - specifier: 0.7.0 + specifier: 0.7.1 version: link:packages/win32-x64-msvc jszip: specifier: ^3.10.1