Skip to content

v3.2.1

Choose a tag to compare

@cldmv-bot cldmv-bot released this 22 Apr 13:44
· 16 commits to master since this release
v3.2.1
56df353

release: v3.2.1 (#78)

Slothlet v3.2.1 Changelog

Release Date: April 11, 2026
Release Type: Patch
Branch: release/3.2.1


Overview

Version 3.2.1 is a patch release that adds a missing defineProperty trap to the version dispatcher proxy introduced in v3.2.0. Without the trap, Reflect.defineProperty calls on a dispatcher node fell through to the raw proxy target rather than the resolved versioned wrapper, and non-configurable descriptor writes could produce proxy invariant violations causing V8 to throw TypeError on subsequent property reads.

A minor pre-commit tooling cleanup is also included.

No public API or configuration changes. All v3.2.0 usage is fully compatible.


🐛 Bug Fixes

Dispatcher Proxy: Missing defineProperty Trap

Problem: The version dispatcher proxy introduced in v3.2.0 implemented get, has, ownKeys, and getOwnPropertyDescriptor traps but had no defineProperty trap. Without it, any Reflect.defineProperty(api.someDispatcher, prop, descriptor) call fell through to the proxy's raw target t via the default [[DefineOwnProperty]] behaviour — bypassing version routing entirely and writing directly to the internal target object.

For configurable: false descriptors this created a proxy invariant violation: t would hold a non-configurable property while the getOwnPropertyDescriptor trap returned a descriptor sourced from the versioned wrapper (or undefined if the property didn't exist there). V8's §10.5.8 invariant requires the get trap to return a value consistent with t's non-configurable + non-writable descriptor — a mismatch causes V8 to throw a TypeError on the very next property read on that dispatcher node.

Fix: Added a defineProperty trap that routes to the resolved versioned wrapper using Reflect.defineProperty (which propagates a false return cleanly; Object.defineProperty calls DefinePropertyOrThrow and always throws on failure regardless of strict mode). For configurable: false descriptors the trap:

  1. Shadow-preflights t — clones its current descriptor and extensibility onto a transient object so the engine decides compatibility without mutating t.
  2. Writes to vw first — if the versioned wrapper rejects the definition, t is never touched.
  3. Mirrors onto t only after vw accepts — satisfying V8's §10.5.6 step 28 invariant (the raw proxy target must hold a matching non-configurable descriptor whenever the trap returns true).

For configurable: true descriptors the trap delegates directly to Reflect.defineProperty(vw, prop, descriptor) — §10.5.6 step 28 does not apply, so no mirroring is needed.


🛠️ Tooling

precommit-validation.mjs: Removed Duplicate build:cleanup Step

The explicit "Clean Build Artifacts" step in validationSteps ran npm run build:cleanup at the start of the pre-commit sequence. However, npm run build:dev (the next step) already starts with build:cleanup as its first sub-command. This caused dist/ and types/ to be deleted twice per commit with no benefit.

Fix: Removed the standalone build:cleanup step. The sequence is now:
build:dev → debug → test:node → vitest

The stale "Clean Build Artifacts" entry was also removed from the stepCommands failure-hint map, and the @fileoverview JSDoc was updated to reflect the new sequence.


📦 Files Changed

File Change
src/lib/handlers/version-manager.mjs Added defineProperty trap to the dispatcher proxy; shadow preflight + vw-first + t-mirror-on-success for configurable: false descriptors; Reflect.defineProperty used throughout; updated JSDoc
src/lib/builders/api_builder.mjs Removed stale inline comment and /* v8 ignore next */ annotation from the dead handlersKeys: "undefined" arm in METADATA_NOT_AVAILABLE error construction
src/lib/handlers/metadata.mjs Added /* v8 ignore next */ with explanatory comment on the defensive !apiPath guard in getPathMetadata
src/lib/handlers/unified-wrapper.mjs Added /* v8 ignore next */ annotations with explanatory comments on all fire-and-forget _materialize().catch(() => {}) sites
tests/vitests/suites/versioning/versioning-dispatcher-internals.test.vitest.mjs New tests covering defineProperty trap: configurable: true delegation, configurable: false successful define, incompatible redefine returning false, raw target t not mutated when vw rejects
tests/vitests/suites/versioning/versioning-runtime-api.test.vitest.mjs New test: version.setVersionMetadata with a non-object patch merges nothing; added resolveWrapper import for internal-handler access tests
tests/vitests/suites/builders/api-builder-guards.test.vitest.mjs New tests: metadata.setForVersion throws METADATA_NOT_AVAILABLE with handlers.metadata = null (truthy handlers arm) and with handlers = null (falsy arm)
tests/vitests/suites/lazy/lazy-background-materialization.test.vitest.mjs Increased fixed wait times from shorter durations to 1000 ms to avoid timing-sensitive assertion failures
tools/dev/precommit-validation.mjs Remove duplicate build:cleanup step; reorder to build:dev → debug → test:node → vitest; remove stale entry from stepCommands; update @fileoverview
.github/workflows/ci.yml, publish.yml, release.yml, update-major-version-tags.yml Upgrade reusable workflow refs from @v1 to @v2; rename BOT_APP_ID input to BOT_APP_CLIENT_ID to match upstream breaking change
README.md, AGENT-USAGE.md Updated v3.2.1 release notes and feature reference to reflect shipped implementation
types/src/lib/handlers/version-manager.d.mts.map, metadata.d.mts.map, unified-wrapper.d.mts.map, api_builder.d.mts.map Regenerated type declaration sourcemaps

🔄 Upgrade from v3.2.0

No changes required. Install the update:

npm install @cldmv/slothlet@3.2.1

All v3.2.0 configuration and API usage is fully compatible.


👥 Contributors