Repository navigation
v3.16.3
release: v3.16.3 - install routine cascade only at root api., not… (#401)
Slothlet v3.16.3 Changelog
Release Date: September 2026
Release Type: Patch
Branch: release/3.16.3
Overview
Version 3.16.3 corrects two never-intended surfaces in the lifecycle and
routine systems shipped in v3.16.0. The public impl:created /
impl:changed events stop handing subscribers the module's raw,
unwrapped callable and stop announcing leaves that collision resolution
then discards; and a configured routine's root cascade is no longer
mirrored onto slothlet's own api.slothlet.* control namespace. Both
are corrections to behavior that was never meant to exist, so they are
treated as fixes rather than features — but each removes a surface a
consumer could have come to rely on, so the upgrade notes below call out
the one-line migration for each.
🐛 Bug Fixes
impl:created / impl:changed no longer leak the raw callable or
fire for merge-discarded leaves (#398)
The public lifecycle events carried data.impl — the module's raw,
unwrapped implementation. A subscriber holding it could invoke a leaf
outside slothlet's enforced boundary: no active self extent, no
permission gate, none of the wrapping every real call goes through. The
events also fired per contribution at construction time, before
collision resolution decided placement, so when two modules
contributed the same leaf path they fired for both — including the one
merge then dropped from the tree. A registry built off the event (the
exact pattern docs/LIFECYCLE.md documents) could therefore record an
enforcement-bypassing raw callable, and map a path to a contributor
whose value never actually lands on the composed api.
The lifecycle emitter now has an internal contribution tier. The
framework's own metadata, routine-manager and ownership systems ride
that internal stream, so they still observe every contribution
(merge-losers included) exactly as before. The public impl:created
/ impl:changed now fire post-placement, once, only for the
contribution that actually owns the path, and carry the wrapped
leaf on data.wrapper.__impl (a frozen, minimal { __impl } shape)
instead of the raw data.impl. impl:removed is unchanged. See
LIFECYCLE.md for the updated payload
and the Module Registry pattern.
Routine cascade is installed only at the root api.<name>, not
mirrored onto api.slothlet.<name> (#399)
A configured routine's root cascade was installed at two places: the
canonical root api.<name>() and a duplicate api.slothlet.<name>().
api.slothlet.* is slothlet's own control namespace (run, api.add,
versioning, the shutdown / destroy dispose builtins); a consumer
routine's cascade does not belong there, and because both resolved,
consumer code could end up written against the wrong surface
(api.slothlet.initialize() instead of api.initialize()).
The cascade now installs only at the root api.<name> (both the
initial build and the reactive per-path self-heal). The shutdown /
destroy dispose builtins keep their own api.slothlet.shutdown /
api.slothlet.destroy — those are a separate framework surface,
integrated into the existing dispose functions, and are unaffected. See
LIFECYCLE.md.
🔧 CI & tooling
Opt-in, approval-triggered release-merge caller (#397)
Adds an opt-in caller workflow that lets an approval trigger the next → master release merge, without changing the default release flow for
repos that don't enable it.
📚 Documentation
- NEW: docs/changelog/v3/v3.16.3.md — this
changelog.
Upgrade notes
- No change for the vast majority of consumers — normal api calls,
module authoring, routines, hooks, and permissions are all unaffected.
The two changes below only matter if you subscribe to lifecycle events
or invoke a routine cascade through the control namespace. - Lifecycle subscribers reading
data.impl(#398): switch to
data.wrapper.__impl. The rawimplfield is gone fromimpl:created
/impl:changed;data.wrapper.__implis the leaf's implementation,
and the event now fires post-placement for the placed owner only (so a
registry keyed off it no longer records a merge-discarded contribution).
This removes an enforcement-bypassing callable that was never meant to
be exposed. - Routine cascades invoked as
api.slothlet.<name>()(#399): call
api.<name>()instead (e.g.api.initialize()rather than
api.slothlet.initialize()). Theapi.slothlet.<name>mirror no longer
exists for consumer routines. Theshutdown/destroydispose
builtins still respond at bothapi.shutdown()/
api.slothlet.shutdown()(anddestroy) as before — only the mirrored
routine cascade was removed.
| Metric | Coverage |
|---|---|
| Statements | 99.9% |
| Branches | 99.9% |
| Functions | 99.9% |
| Lines | 99.9% |
Avg: 99.9% · f1ef7e1 · Node lts/*