Summary
Merely reading a standard function property such as .apply/.call/.bind off a materialized function leaf permanently rewrites that leaf's loader record: the leaf flips from kind: "function" to kind: "namespace", and a phantom child leaf (<path>.apply, kind: "function") appears. The property access is treated as an api-tree traversal that materializes a child, rather than returning the function's own Function.prototype.apply. A later leaves(".", { details: true }) — or a second composition/serve of the same instance — then reports the corrupted shape.
Reproduce
Verified against @cldmv/slothlet@3.14.0 (repo v3.13.3-9-g0dd03b7), eager mode. api dir with one file tool.mjs exporting export function ping() { return "pong"; }:
const api = await slothlet({ base: dirUrl, silent: true, mode: "eager" });
await api.slothlet.api.leaves(".", { details: true });
// → [ { path: "tool", kind: "namespace" }, { path: "tool.ping", kind: "function" } ]
const ap = api.tool.ping.apply; // just READ it — no call
typeof ap; // "function"
api.tool.ping(); // still "pong"
await api.slothlet.api.leaves(".", { details: true });
// → [ { path: "tool", kind: "namespace" },
// { path: "tool.ping", kind: "namespace" }, ← was "function"
// { path: "tool.ping.apply", kind: "function" } ] ← phantom leaf
// Reflect.apply(api.tool.ping, thisArg, args) does NOT trigger this.
Expected
Accessing Function.prototype members (apply/call/bind/name/length/…) on a callable leaf should return the function's own properties and leave the record untouched — the leaf is a function; those are its methods, not child endpoints. Only genuine child names should materialize a namespace.
Impact
Any code that invokes a leaf as leaf.apply(thisArg, args) (a common idiom for forwarding this + a spread of args) silently corrupts the api's own record tree, and re-enumerating or re-composing the instance then surfaces Function.prototype.apply bound to the real leaf in place of the leaf. Found while building @cldmv/slothlet-vine: answering a forwarded call with leaf.apply(parent, args) corrupted the served surface; the workaround is Reflect.apply(leaf, parent, args), which routes around the leaf proxy's get trap entirely.
Severity
Correctness — silent record corruption from a read-only, idiomatic access. Likely the lazy/eager leaf proxy's get trap treating every property (including Function.prototype names) as a child traversal.
Summary
Merely reading a standard function property such as
.apply/.call/.bindoff a materialized function leaf permanently rewrites that leaf's loader record: the leaf flips fromkind: "function"tokind: "namespace", and a phantom child leaf (<path>.apply,kind: "function") appears. The property access is treated as an api-tree traversal that materializes a child, rather than returning the function's ownFunction.prototype.apply. A laterleaves(".", { details: true })— or a second composition/serve of the same instance — then reports the corrupted shape.Reproduce
Verified against
@cldmv/slothlet@3.14.0(repov3.13.3-9-g0dd03b7), eager mode. api dir with one filetool.mjsexportingexport function ping() { return "pong"; }:Expected
Accessing
Function.prototypemembers (apply/call/bind/name/length/…) on a callable leaf should return the function's own properties and leave the record untouched — the leaf is a function; those are its methods, not child endpoints. Only genuine child names should materialize a namespace.Impact
Any code that invokes a leaf as
leaf.apply(thisArg, args)(a common idiom for forwardingthis+ a spread of args) silently corrupts the api's own record tree, and re-enumerating or re-composing the instance then surfacesFunction.prototype.applybound to the real leaf in place of the leaf. Found while building@cldmv/slothlet-vine: answering a forwarded call withleaf.apply(parent, args)corrupted the served surface; the workaround isReflect.apply(leaf, parent, args), which routes around the leaf proxy'sgettrap entirely.Severity
Correctness — silent record corruption from a read-only, idiomatic access. Likely the lazy/eager leaf proxy's
gettrap treating every property (includingFunction.prototypenames) as a child traversal.