v3.18.1
release: v3.18.1 - scope caller() and event.on identity to the querying…
Slothlet v3.18.1 Changelog
Release Date: September 2026
Release Type: Patch
Branch: release/3.18.1
Overview
Version 3.18.1 fixes a cross-instance context-isolation bug that made the 3.18.0 event-forwarding enablers unreliable whenever two instances share a process. The enablers themselves are unchanged; this release makes them behave as documented across every transport — in particular the same-process case (a grow/serve pair inside one process, such as @cldmv/slothlet-vine's loopback transport) that 3.18.0 silently broke.
api.slothlet.caller() (#431) and the event manager's subscribe-time owner-wrapper capture (#429 / #407) read the caller identity straight from the active AsyncLocalStorage store without scoping to the querying instance. The async context manager is a process-level singleton and ALS propagates its store across await / queueMicrotask, so when one instance's module flow was active and code touched a different instance's api, that other instance saw the foreign module as its own caller. Concretely: a forwarding layer's serving side subscribes to re-emit an event on behalf of a far subscriber; over a same-process transport the far subscriber's flow propagated into the serving instance's handler, so its listener was pinned to the far subscriber's identity and the host-only event.resolveLevel was denied at emit — same-process deliveries were dropped. A real cross-process boundary (worker, process, socket) cannot propagate that context and was unaffected.
Both accessors now pass the querying instance's id to getCallerIdentity(instanceID) — the same instance-scoping the permission read-gate already applied for the nested-boot case (#290). A foreign instance's flow resolves to the host (no caller); the instance's own flow still reports the real module, so caller() and event delivery are now consistent with what the gate already enforced. This is a drop-in patch with no api change.
🐛 Bug Fixes
caller() and event delivery no longer leak a foreign instance's caller across the shared async context (#436)
api.slothlet.caller() on instance A could return instance B's module path, and an event listener registered on A while B's flow was active was pinned to B's identity, when the two instances shared a process and B's flow reached A across an await / queueMicrotask (the async context manager is a process-level singleton whose store ALS propagates). For a cross-boundary event-forwarding layer this denied the host-only re-resolution at emit under the leaked identity, so a same-process forwarded subscription received nothing. Both caller() and the event manager's owner-wrapper capture now scope their identity read to the querying instance (getCallerIdentity(instanceID)), exactly as the permission read-gate already did — a foreign instance's active flow reports the host, while the instance's own flow still reports the real module. Same-process event forwarding (e.g. @cldmv/slothlet-vine over loopback) now delivers correctly; cross-process forwarding, already unaffected, is unchanged.
📚 Documentation
- NEW: docs/changelog/v3/v3.18.1.md — this changelog.
🔧 Dependencies
No dependency updates.
Upgrade notes
A drop-in for v3.18.0. The two event-forwarding enablers (api.slothlet.event.resolveLevel, api.slothlet.caller()) are unchanged; nothing about their signatures or return values moves.
If you forward events across a same-process boundary — two slothlet instances in one process, including @cldmv/slothlet-vine's loopback transport — upgrade to 3.18.1: on 3.18.0 those deliveries are silently dropped. Cross-process forwarding (worker / process / socket) already worked on 3.18.0 and is unaffected. Everything else is unchanged.
| Metric | Coverage |
|---|---|
| Statements | 100.0% |
| Branches | 100.0% |
| Functions | 100.0% |
| Lines | 100.0% |
Avg: 100.0% · fe594e8 · Node lts/*