v3.13.2
Slothlet v3.13.2 Changelog
Release Date: August 2026
Release Type: Patch
Branch: release/3.13.2
Overview
Version 3.13.2 is a maintenance patch that fixes a composition regression introduced in 3.13.0 (#283, merged via PR #284). The module-private export rule shipped in 3.13.0 (#269) makes an _/__-prefixed member private to its own module and denies it to an unidentified (host) caller by default. A version dispatcher stamps slothlet's own reserved marker keys — __isVersionDispatcher and __logicalPath — on the object it mounts, and those keys are __-prefixed. When a version-dispatched field collides with an existing module, composition merges the dispatcher's markers onto that module's wrapper and the framework then reads one back as the host; under any permissions configuration the new rule denied that read, so composition of every version-dispatched field failed. A permissions-enabled consumer upgrading from 3.12.3 to 3.13.0/3.13.1 could no longer compose such a tree.
The fix distinguishes slothlet's own marker carriers from a consumer's private members by object identity — a module-private brand — rather than by name, so the framework's reads of its own markers are exempt while a consumer's identically-named private member stays denied exactly as 3.13.0's rule intends. No public API changed, and there are no breaking changes.
🐛 Bug Fixes
Version-dispatcher markers were denied by the module-private host gate (#283)
3.13.0's module-private rule (#269) denies an unidentified (host) caller any _/__-prefixed member by default — the mechanism that keeps a module's __-private exports out of reach of the host and of other modules. A version dispatcher carries two reserved marker keys of its own, __isVersionDispatcher and __logicalPath, stamped on the object it mounts so the framework can recognize a dispatched field. Both are __-prefixed, so the module-private rule applied to them as well.
The failure surfaced on a collision. When a version-dispatched field is registered at a path an existing module already occupies, composition merges the dispatcher into that module's wrapper — which copies the dispatcher's marker keys onto it — and the framework then reads __isVersionDispatcher back to confirm the field is a dispatcher. That read carries no caller identity (it is the framework itself, mid-composition), so it is treated as a host read, and under any permissions block the secure-default private.host policy is deny. The read was refused with PERMISSION_DENIED, aborting the registration and, in a permissions-enabled application, the composition of every version-dispatched field. 3.12.3, which predates #269, composed the same tree without issue.
The fix keys the exemption on object identity, never on the key name. A module-private brand — a WeakSet not reachable from consumer code — marks the objects slothlet itself creates and stamps with a marker: a version dispatcher, and any wrapper a dispatcher is merged into. The permission read gate exempts a read of one of the reserved marker keys only when the object carrying it is branded. slothlet's own marker therefore stays readable to the framework — and reads back as true, exactly as it did before 3.13.0 — while a consumer's member that merely shares the name lives on a non-branded object and is still denied, so #269's host-privacy guarantee is preserved intact. A name-based exemption was rejected for precisely this reason: it would have handed the host any consumer private member that happened to share one of these names, reopening what #269 closed.
The reserved marker set that participates in this decision is kept module-private and exposed only through an immutable predicate, so no importer can widen the read-gate exemption by adding to it.
🧪 Tests
Version-dispatcher marker regression + brand-registry unit tests (#283)
Two new suites accompany the fix:
tests/vitests/suites/permissions/version-dispatcher-private-marker.test.vitest.mjscomposes a version-dispatched field that collides with an existing module under apermissionsblock and asserts, across eager and lazy modes, that composition no longer throwsPERMISSION_DENIED, that slothlet's own__isVersionDispatchermarker stays readable to the host, and — as a scope guard — that a genuine consumer__-private member is still denied to the host (#269 intact). A small fixture leaf (api_tests/api_test_versioned/field-base/label.mjs) supplies a public terminal member so the exemption is verified to be scoped to the marker keys rather than to the whole branded wrapper.tests/vitests/suites/handlers/framework-internals.test.vitest.mjsunit-tests the brand registry directly — branding objects and functions, the no-op onnull/primitives, non-branded objects reporting as not framework-internal, and the immutable marker-key predicate.
The full suite passes and the change holds the coverage gate at 100%.
📚 Documentation
- NEW: docs/changelog/v3/v3.13.2.md — this changelog.
- README — refreshed What's New.
Upgrade notes
- No breaking changes. The fix restores composition that 3.12.3 supported and narrows an over-broad denial introduced in 3.13.0; #269's rule — the host and foreign modules cannot read a module's
_/__-private members — is unchanged. - Recommended for any permissions-enabled application that version-dispatches fields. If you deferred the 3.13.0/3.13.1 upgrade because permission-gated composition of a version-dispatched (or manifest) field failed with
PERMISSION_DENIED, 3.13.2 resolves it with no consumer-side change required.