@cldmv/slothlet 3.16.3 → 3.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -43,18 +43,19 @@ Every feature has been hardened with a comprehensive test suite - over **5,300 t
43
43
 
44
44
  ## ✨ What's New
45
45
 
46
- ### Latest: v3.16.3 (September 2026)
46
+ ### Latest: v3.17.0 (September 2026)
47
47
 
48
- - **`impl:created` / `impl:changed` stop leaking the raw callable** — the public lifecycle events carried the module's raw, unwrapped implementation (`data.impl`) and fired per contribution before collision resolution, so they announced leaves a later merge discarded. They now fire **post-placement, once, only for the contribution that owns the path**, and carry the wrapped leaf on `data.wrapper.__impl` — never the enforcement-bypassing raw callable. Read `data.wrapper.__impl` instead of `data.impl` (#398).
49
- - **Routine cascade lives only at `api.<name>`** — a routine's root cascade was also mirrored onto slothlet's own control namespace as `api.slothlet.<name>()`. That duplicate is gone; call `api.<name>()` (e.g. `api.initialize()`). The `shutdown` / `destroy` dispose builtins still respond at `api.slothlet.shutdown` / `destroy` as before (#399).
50
- - [View full v3.16.3 Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v3/v3.16.3.md)
48
+ - **Instance-wide event system** — a new `event` family at `api.slothlet.event` (`on` / `once` / `off` / `emit`) gives one composed api instance named, permission-gated publish/subscribe, alongside `hook` and `lifecycle`. Delivery is a three-level effect (`deny` / `notify` / `allow`) resolved from an **event-rule pool** whose layered, most-specific-wins precedence (built-in < manifest < instance < runtime) mirrors the permission system; emitting is never gated. The permission and hook rule systems were refactored onto the same layered tiebreak, so all three resolve rules identically (#407).
49
+ - **Per-entity lifecycle routines** — `cascade: false` plus `api.<path>.<name>.for(key)` and `.contributors` let a host invoke exactly one co-owner of a shared routine path (in that contributor's own extent + identity) instead of the run-all cascade, for namespaces co-owned by several first-class packages (#400). Also a scoped-exclusion glob `!(a|b)` in the shared pattern engine (#360), and `api.add()`'s init-time collision options (`collisionMode` / `mutateExisting` / `recordHistory`) now **warn-and-ignore** instead of silently stripping — opt in with `api.mutations.allowCollisionOverride` (#380).
50
+ - **Fixes & docs** — a function carried on a `run()` / `scope()` context now survives the clone (shared by reference) instead of being flattened to `{}` (#408), and three api-generation edge cases are corrected: object default exports honor `collisionMode` for named-export conflicts (#421), all-caps hyphenated names sanitize to a valid identifier (#422), and the browser manifest loader honors `hidden` / `apiDepth` / `scanHiddenFolders` to match the filesystem scan (#423). The API-RULES generation-rules catalog was also substantially expanded and reconciled against the code (#424).
51
+ - [View full v3.17.0 Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v3/v3.17.0.md)
51
52
 
52
53
  ### Recent Releases
53
54
 
55
+ - **v3.16.3** (September 2026) — `impl:created` / `impl:changed` stop leaking the module's raw unwrapped callable and stop firing for merge-discarded leaves — they now fire post-placement, once, for the owning contribution, carrying the wrapped leaf on `data.wrapper.__impl`; and a routine's root cascade is no longer mirrored onto `api.slothlet.<name>()` (call `api.<name>()`) (#398, #399) ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v3/v3.16.3.md))
54
56
  - **v3.16.2** (September 2026) — Routine root cascades now run each contributor inside the instance extent, so ambient `self.*` resolves under `api.<name>()` and the auto-fired `startup` / `shutdown` / `destroy` routines exactly as a per-path call does; plus a post-release docs-build repair (unparseable JSDoc `@param` → `@callback` typedef, no public type change) and a contributor-guide refresh for the v4 flow (#394, #385, #386) ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v3/v3.16.2.md))
55
57
  - **v3.16.1** (September 2026) — Widened the optional `typescript` peer to `^6.0.3 || ^7.0.0` so consumers can adopt TypeScript 7 without an `npm ci` `ERESOLVE` conflict against `@cldmv/slothlet`; shipped `.d.ts` verified clean under TypeScript 7.0.2 (#382) ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v3/v3.16.1.md))
56
- - **v3.16.0** (September 2026) — Stackable lifecycle routines (`routines` config + root cascade) replacing the deprecated `collectLifecycleHooks`, a `stackRoutines` flag decoupled from `collisionMode`, a large collision/ownership/routine-stacking correctness pass with reactive per-path stack self-heal, and the remaining live-runtime deferred-callback boundaries pinned; minimum Node raised to `>=22.12.0` (#341, #365, #366, #362, #369) ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v3/v3.16.0.md))
57
- - **v3.15.3** (September 2026) — Wrap-on-set / `api.add()` live Proxy delegation, plus hook-lifecycle default fixes: `self.X = obj` and `api.slothlet.api.add()` now give a genuinely live, two-way view onto the underlying object instead of a frozen snapshot, including `EventEmitter`-derived values (#340, #342); hook-subset default `||` bug fixed to `??` (#343, #344) ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v3/v3.15.3.md)) 📚 **For complete version history and detailed release notes, see [docs/changelog/](https://github.com/CLDMV/slothlet/tree/master/docs/changelog/) folder.**
58
+ - **v3.16.0** (September 2026) — Stackable lifecycle routines (`routines` config + root cascade) replacing the deprecated `collectLifecycleHooks`, a `stackRoutines` flag decoupled from `collisionMode`, a large collision/ownership/routine-stacking correctness pass with reactive per-path stack self-heal, and the remaining live-runtime deferred-callback boundaries pinned; minimum Node raised to `>=22.12.0` (#341, #365, #366, #362, #369) ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v3/v3.16.0.md)) 📚 **For complete version history and detailed release notes, see [docs/changelog/](https://github.com/CLDMV/slothlet/tree/master/docs/changelog/) folder.**
58
59
 
59
60
  ---
60
61
 
package/REFERENCE.md CHANGED
@@ -10,17 +10,24 @@ Full documentation lives in the GitHub repository.
10
10
  | All docs | [docs/](https://github.com/CLDMV/slothlet/tree/master/docs) |
11
11
  | Configuration | [docs/CONFIGURATION.md](https://github.com/CLDMV/slothlet/blob/master/docs/CONFIGURATION.md) |
12
12
  | API transformation rules | [docs/API-RULES.md](https://github.com/CLDMV/slothlet/blob/master/docs/API-RULES.md) |
13
+ | Sanitization | [docs/SANITIZATION.md](https://github.com/CLDMV/slothlet/blob/master/docs/SANITIZATION.md) |
13
14
  | Hooks | [docs/HOOKS.md](https://github.com/CLDMV/slothlet/blob/master/docs/HOOKS.md) |
15
+ | Events | [docs/EVENTS.md](https://github.com/CLDMV/slothlet/blob/master/docs/EVENTS.md) |
14
16
  | Context propagation | [docs/CONTEXT-PROPAGATION.md](https://github.com/CLDMV/slothlet/blob/master/docs/CONTEXT-PROPAGATION.md) |
15
17
  | Metadata | [docs/METADATA.md](https://github.com/CLDMV/slothlet/blob/master/docs/METADATA.md) |
16
18
  | Module discovery & mount | [docs/MODULE-DISCOVERY.md](https://github.com/CLDMV/slothlet/blob/master/docs/MODULE-DISCOVERY.md) |
19
+ | Module structure | [docs/MODULE-STRUCTURE.md](https://github.com/CLDMV/slothlet/blob/master/docs/MODULE-STRUCTURE.md) |
17
20
  | Module manifest schema | [schemas/slothlet.module.schema.json](https://github.com/CLDMV/slothlet/blob/master/schemas/slothlet.module.schema.json) |
18
21
  | Permissions | [docs/PERMISSIONS.md](https://github.com/CLDMV/slothlet/blob/master/docs/PERMISSIONS.md) |
22
+ | Permission conditions | [docs/PERMISSIONS-CONDITIONS.md](https://github.com/CLDMV/slothlet/blob/master/docs/PERMISSIONS-CONDITIONS.md) |
19
23
  | API versioning | [docs/VERSIONING.md](https://github.com/CLDMV/slothlet/blob/master/docs/VERSIONING.md) |
20
24
  | TypeScript | [docs/TYPESCRIPT.md](https://github.com/CLDMV/slothlet/blob/master/docs/TYPESCRIPT.md) |
21
25
  | Lifecycle | [docs/LIFECYCLE.md](https://github.com/CLDMV/slothlet/blob/master/docs/LIFECYCLE.md) |
22
26
  | Hot reload | [docs/RELOAD.md](https://github.com/CLDMV/slothlet/blob/master/docs/RELOAD.md) |
27
+ | Internationalization | [docs/I18N.md](https://github.com/CLDMV/slothlet/blob/master/docs/I18N.md) |
28
+ | Browser usage | [docs/BROWSER.md](https://github.com/CLDMV/slothlet/blob/master/docs/BROWSER.md) |
23
29
  | Performance | [docs/PERFORMANCE.md](https://github.com/CLDMV/slothlet/blob/master/docs/PERFORMANCE.md) |
24
30
  | Testing & coverage | [docs/TESTING.md](https://github.com/CLDMV/slothlet/blob/master/docs/TESTING.md) |
31
+ | Migration (v2 → v3) | [docs/MIGRATION.md](https://github.com/CLDMV/slothlet/blob/master/docs/MIGRATION.md) |
25
32
  | Changelog | [docs/changelog/](https://github.com/CLDMV/slothlet/tree/master/docs/changelog) |
26
33
  | Agent/AI usage guide | [AGENT-USAGE.md](https://github.com/CLDMV/slothlet/blob/master/AGENT-USAGE.md) |
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{isNode,AsyncResource,loadJson}from"@cldmv/slothlet/helpers/platform";import{ComponentBase}from"#factories/component-base";import{TYPE_STATES,resolveWrapper}from"#handlers/unified-wrapper";import{TRUSTED_ROOT,PROTECT_SENTINEL}from"#handlers/trusted-root";import{getLanguage,initI18n,setLanguage,setLanguageAsync,t,translate}from"@cldmv/slothlet/i18n";function _resolvePathOrModuleId(slothlet,pathOrModuleId){const history=slothlet.handlers?.apiManager?.state?.addHistory;if(history){let match=null;for(let i=history.length-1;i>=0;i--){const entry=history[i];if(entry?.moduleID===pathOrModuleId){match=entry;break}}if(match)return match.apiPath}return pathOrModuleId}function describeScopeReceived(value){if(Array.isArray(value)){const badIndex=value.findIndex(entry=>typeof entry!=="string");if(badIndex!==-1){return`array with non-string entry (${typeof value[badIndex]} at index ${badIndex})`}return"array"}return typeof value}function makeCopyOnWriteSelf(parentSelf){const overlay=new Map;const childViews=new Map;return new Proxy({},{get(_t,prop){if(overlay.has(prop))return overlay.get(prop);const real=parentSelf[prop];if(real!==null&&typeof real==="object"){let view=childViews.get(prop);if(view===void 0){view=makeCopyOnWriteSelf(real);childViews.set(prop,view)}return view}return real},set(_t,prop,value){overlay.set(prop,value);childViews.delete(prop);return true},has(_t,prop){return overlay.has(prop)||prop in parentSelf},deleteProperty(_t,prop){overlay.delete(prop);childViews.delete(prop);return true},ownKeys(){const keys=new Set(Reflect.ownKeys(parentSelf));for(const k of overlay.keys())keys.add(k);return[...keys]},getOwnPropertyDescriptor(_t,prop){if(overlay.has(prop)){return{value:overlay.get(prop),writable:true,enumerable:true,configurable:true}}const desc=Reflect.getOwnPropertyDescriptor(parentSelf,prop);return desc?{...desc,configurable:true}:void 0}})}class ApiBuilder extends ComponentBase{static slothletProperty="apiBuilder";constructor(slothlet){super(slothlet)}async buildFinalAPI(userApi){this.slothlet.debug("api",{key:"DEBUG_MODE_BUILD_FINAL_API_CALLED",diagnostics:this.____config.diagnostics,userApiKeys:Object.keys(userApi)});if(this.slothlet._ownBuiltins){for(const[key,ref]of Object.entries(this.slothlet._ownBuiltins)){if(ref&&Object.prototype.hasOwnProperty.call(userApi,key)&&userApi[key]===ref){try{delete userApi[key]}catch(_){}}}}this.slothlet.userHooks={shutdown:typeof userApi.shutdown==="function"?userApi.shutdown:null,destroy:typeof userApi.destroy==="function"?userApi.destroy:null};if(userApi.slothlet){if(!this.____config?.silent){new this.SlothletWarning("WARNING_RESERVED_PROPERTY_CONFLICT",{properties:"slothlet"})}await this.emitImplDiagnostic("warning",{apiPath:"slothlet",code:"WARNING_RESERVED_PROPERTY_CONFLICT",context:{properties:"slothlet"},source:"buildAPI"})}const slothletNamespace=await this.createSlothletNamespace(userApi);this.slothlet.debug("api",{key:"DEBUG_MODE_SLOTHLET_NAMESPACE_CREATED",namespaceKeys:Object.keys(slothletNamespace),hasDiag:!!slothletNamespace.diag});const shutdownFn=this.createShutdownFunction();this.attachBuiltins(userApi,{slothlet:slothletNamespace,shutdown:shutdownFn,destroy:null});this.slothlet.debug("api",{key:"DEBUG_MODE_BUILT_INS_ATTACHED",userApiKeys:Object.keys(userApi),hasSlothlet:!!userApi.slothlet,hasDiag:!!userApi.slothlet?.diag});const destroyWithApi=this.createDestroyFunction(userApi);Object.defineProperty(userApi,"destroy",{value:destroyWithApi,enumerable:true,writable:false,configurable:true});this.slothlet._ownBuiltins={shutdown:shutdownFn,slothlet:slothletNamespace,destroy:destroyWithApi};return userApi}async createSlothletNamespace(userApi){const slothlet=this.slothlet;const config=this.____config;const enforceInternalPermission=targetPath=>{const ctx=slothlet.contextManager?.tryGetContext?.(slothlet.instanceID);const identity=slothlet.contextManager?.getCallerIdentity?.(slothlet.instanceID);if(identity?.unresolved){throw new slothlet.SlothletError("PERMISSION_DENIED",{caller:null,target:targetPath})}const callerWrapper=identity?.currentWrapper;if(!callerWrapper)return;const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.enforceAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const callerPath=callerWrapper.____slothletInternal?.apiPath??"";const callerFilePath=callerWrapper.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;if(!permissionManager.enforceAccess(callerPath,targetPath,callerFilePath,null,runtimeContext)){throw new slothlet.SlothletError("PERMISSION_DENIED",{caller:callerPath,target:targetPath})}};const canTraverseInternalNamespace=targetPath=>{const ctx=slothlet.contextManager?.tryGetContext?.(slothlet.instanceID);const identity=slothlet.contextManager?.getCallerIdentity?.(slothlet.instanceID);if(identity?.unresolved)return false;const callerWrapper=identity?.currentWrapper;if(!callerWrapper)return false;const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForCaller||!permissionManager?.checkAccess){return false}const callerPath=callerWrapper.____slothletInternal?.apiPath??"";const callerFilePath=callerWrapper.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;const callerRules=permissionManager.getRulesForCaller(callerPath);const conditionMatches=condition=>typeof permissionManager.matchesCondition==="function"?permissionManager.matchesCondition(condition,runtimeContext):false;const prefix=`${targetPath}.`;for(const rule of callerRules){if(!rule||rule.effect!=="allow"||typeof rule.target!=="string")continue;const couldMatchDescendant=rule.target.startsWith(prefix)||rule.target==="**"||rule.target==="*";if(!couldMatchDescendant)continue;if(rule.target.startsWith(prefix)&&conditionMatches(rule.condition)){return true}const probePaths=[`${targetPath}.__probe__`];if(rule.target.startsWith(prefix)){const suffix=rule.target.slice(prefix.length);const firstSegment=suffix.split(".")[0];if(firstSegment&&!/[*!?{}]/u.test(firstSegment)){probePaths.unshift(`${targetPath}.${firstSegment}`);probePaths.push(`${targetPath}.${firstSegment}.__probe__`)}}for(const probePath of probePaths){if(permissionManager.checkAccess(callerPath,probePath,callerFilePath,null,runtimeContext,{useCache:false})){return true}}}return false};const createInternalRouteProxy=(value,routePath,seen=new WeakMap)=>{if(!value||typeof value!=="object"&&typeof value!=="function")return value;const isMetaProperty=prop=>prop==="__proto__"||prop==="prototype"||prop==="constructor"||prop==="caller"||prop==="arguments";let routeCache=seen.get(value);if(!routeCache){routeCache=new Map;seen.set(value,routeCache)}if(routeCache.has(routePath)){return routeCache.get(routePath)}const proxy=new Proxy(value,{get(target,prop,receiver){if(typeof prop!=="string"){const result2=Reflect.get(target,prop,receiver);return createInternalRouteProxy(result2,routePath,seen)}const childRoutePath=`${routePath}.${prop}`;let deniedError=null;try{enforceInternalPermission(childRoutePath)}catch(error){deniedError=error}if(deniedError){if(canTraverseInternalNamespace(childRoutePath)){const result2=Reflect.get(target,prop,receiver);if(result2&&(typeof result2==="object"||typeof result2==="function")){return createInternalRouteProxy(result2,childRoutePath,seen)}}throw deniedError}const result=Reflect.get(target,prop,receiver);if(isMetaProperty(prop)){return result}const descriptor=Object.getOwnPropertyDescriptor(target,prop);if(descriptor&&"value"in descriptor&&descriptor.configurable===false&&descriptor.writable===false){return descriptor.value}return createInternalRouteProxy(result,childRoutePath,seen)},getOwnPropertyDescriptor(target,prop){const descriptor=Reflect.getOwnPropertyDescriptor(target,prop);if(!descriptor)return void 0;if(typeof prop!=="string"){return descriptor}const childRoutePath=`${routePath}.${prop}`;if(isMetaProperty(prop)){enforceInternalPermission(childRoutePath);return descriptor}if("get"in descriptor||"set"in descriptor){enforceInternalPermission(childRoutePath);if(descriptor.configurable===true){return{...descriptor,get:typeof descriptor.get==="function"?function slothlet_internal_descriptor_getter(...args){enforceInternalPermission(childRoutePath);return Reflect.apply(descriptor.get,this,args)}:descriptor.get,set:typeof descriptor.set==="function"?function slothlet_internal_descriptor_setter(...args){enforceInternalPermission(childRoutePath);return Reflect.apply(descriptor.set,this,args)}:descriptor.set}}return descriptor}if(!("value"in descriptor)){return descriptor}const descriptorValue=descriptor.value;if(descriptor.configurable===false&&descriptor.writable===false){enforceInternalPermission(childRoutePath);return descriptor}if(!descriptorValue||typeof descriptorValue!=="object"&&typeof descriptorValue!=="function"||typeof descriptorValue==="function"){enforceInternalPermission(childRoutePath);if(typeof descriptorValue==="function"&&descriptor.configurable===true){return{...descriptor,value:createInternalRouteProxy(descriptorValue,childRoutePath,seen)}}return descriptor}return{...descriptor,value:createInternalRouteProxy(descriptorValue,childRoutePath,seen)}},set(target,prop,newValue,receiver){if(typeof prop!=="string"){return Reflect.set(target,prop,newValue,receiver)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.set(target,prop,newValue,receiver)},defineProperty(target,prop,descriptor){if(typeof prop!=="string"){return Reflect.defineProperty(target,prop,descriptor)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.defineProperty(target,prop,descriptor)},deleteProperty(target,prop){if(typeof prop!=="string"){return Reflect.deleteProperty(target,prop)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.deleteProperty(target,prop)},ownKeys(target){return Reflect.ownKeys(target)},apply(target,thisArg,argArray){enforceInternalPermission(routePath);return Reflect.apply(target,thisArg,argArray)},construct(target,argArray,newTarget){enforceInternalPermission(routePath);return Reflect.construct(target,argArray,newTarget)}});routeCache.set(routePath,proxy);return proxy};let version="unknown";if(isNode){const pkg=loadJson(new URL("../../../package.json",import.meta.url));if(pkg?.version)version=pkg?.version}const namespace={i18n:{setLanguage,setLanguageAsync,getLanguage,translate,t,initI18n},version,instanceID:slothlet.instanceID,types:TYPE_STATES,api:{add:async function slothlet_api_add(apiPath,folderPath,options={},versionConfig=null){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.add",validationError:true})}const{recordHistory:____recordHistory,collisionMode:____collisionMode,mutateExisting:____mutateExisting,...filteredOptions}=options;const addResult=await slothlet.handlers.apiManager.addApiComponent({apiPath,folderPath,options:filteredOptions,versionConfig:versionConfig||null});if(slothlet.handlers.routineManager){await slothlet.handlers.routineManager.rebuildStacks(userApi)}return addResult},remove:async function slothlet_api_remove(pathOrModuleId,apiPath){if(!config.api?.mutations?.remove){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.remove",validationError:true})}if(typeof pathOrModuleId!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"string",received:typeof pathOrModuleId,validationError:true})}if(apiPath!==void 0&&typeof apiPath!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"apiPath",expected:"string",received:typeof apiPath,validationError:true})}const removeResult=await slothlet.handlers.apiManager.removeApiComponent(pathOrModuleId,{scopedApiPath:apiPath});if(slothlet.handlers.routineManager){await slothlet.handlers.routineManager.rebuildStacks(userApi)}return removeResult},leaves:async function slothlet_api_leaves(key,options={}){enforceInternalPermission("slothlet.api.leaves");if(typeof key!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"key",expected:"string",received:typeof key,validationError:true})}const normalizedOptions=options==null?{}:options;if(typeof normalizedOptions!=="object"||Array.isArray(normalizedOptions)){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"options",expected:"object with optional boolean details/includePrivate",received:Array.isArray(normalizedOptions)?"array":typeof normalizedOptions,validationError:true})}const{details=false,includePrivate=false}=normalizedOptions;for(const[name,value]of[["details",details],["includePrivate",includePrivate]]){if(typeof value!=="boolean"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:`options.${name}`,expected:"boolean",received:typeof value,validationError:true})}}const ownership=slothlet.handlers.ownership;const leavesIdentity=slothlet.contextManager?.getCallerIdentity?.();const leavesCaller=leavesIdentity?.currentWrapper??null;if(includePrivate&&leavesCaller){throw new slothlet.SlothletError("PERMISSION_DENIED",{caller:leavesCaller.____slothletInternal?.apiPath??"",target:"slothlet.api.leaves:includePrivate"})}let moduleID=null;if(key==="."||key===""){moduleID=[...ownership.moduleEndpoints.entries()].find(([,endpoint2])=>endpoint2===".")?.[0]}else if(ownership.moduleToPath.has(key)){moduleID=key}else{moduleID=ownership.getCurrentOwner(key)?.moduleID??null;if(!moduleID){moduleID=[...ownership.moduleEndpoints.entries()].find(([,endpoint2])=>endpoint2===key)?.[0]??null}}if(!moduleID||!ownership.moduleToPath.has(moduleID)){throw new slothlet.SlothletError("API_LEAVES_UNKNOWN_MODULE",{key},null,{validationError:true})}const endpoint=ownership.moduleEndpoints.get(moduleID);const boundApi=slothlet.boundApi;if(config.mode==="lazy"&&boundApi){const seen=new WeakSet;let root=boundApi;if(endpoint!=="."){for(const segment of endpoint.split("."))root=root[segment]}const pending=[{node:root,path:endpoint==="."?"":endpoint}];while(pending.length>0){const{node,path}=pending.pop();if(node===null||typeof node!=="object"&&typeof node!=="function")continue;if(seen.has(node))continue;seen.add(node);if(typeof node._materialize==="function"&&node.__materialized===false){await node._materialize()}const childKeys=new Set(Object.keys(node));const records=ownership.moduleToPath.get(moduleID);if(path!==null&&records){const prefix=path===""?"":`${path}.`;for(const ownedPath of records){if(ownedPath===""||prefix!==""&&!ownedPath.startsWith(prefix))continue;const rest=ownedPath.slice(prefix.length);const segmentEnd=rest.indexOf(".");childKeys.add(segmentEnd===-1?rest:rest.slice(0,segmentEnd))}}for(const childKey of childKeys){if(path===""&&(childKey==="slothlet"||childKey==="shutdown"||childKey==="destroy"))continue;let child;try{child=node[childKey]}catch{continue}const childWrapper=child?resolveWrapper(child):null;if(childWrapper?.____slothletInternal?.userAssigned)continue;const childPath=path===null?null:path===""?childKey:`${path}.${childKey}`;pending.push({node:child,path:records?.has(childPath)?childPath:null})}}}if(!ownership.moduleToPath.has(moduleID)){throw new slothlet.SlothletError("API_LEAVES_UNKNOWN_MODULE",{key},null,{validationError:true})}const ownedPaths=[...ownership.moduleToPath.get(moduleID)].filter(path=>{if(path==="")return false;if(endpoint==="."&&(path==="slothlet"||path.startsWith("slothlet.")||path==="shutdown"||path==="destroy"))return false;const currentOwner=ownership.getCurrentOwner(path);return!currentOwner||currentOwner.moduleID===moduleID});ownedPaths.sort();const namespaces=new Set;for(const owned of ownedPaths){let cut=owned.lastIndexOf(".");while(cut>0){namespaces.add(owned.slice(0,cut));cut=owned.lastIndexOf(".",cut-1)}}const kindOf=path=>{if(namespaces.has(path))return"namespace";const entry=ownership.pathToModule.get(path)?.find(candidate=>candidate.moduleID===moduleID);return typeof entry?.value==="function"?"function":"data"};const permissionManager=slothlet.handlers?.permissionManager;let visiblePaths=ownedPaths;if(!includePrivate&&permissionManager?.isEnabled?.()){const callerPath=leavesCaller?.____slothletInternal?.apiPath??"";const callerFilePath=leavesCaller?.____slothletInternal?.filePath??null;const runtimeContext=slothlet.contextManager?.tryGetContext?.()?.context??null;visiblePaths=ownedPaths.filter(path=>{if(!permissionManager.isPrivateTarget(path))return true;const entry=ownership.pathToModule.get(path)?.find(candidate=>candidate.moduleID===moduleID);return permissionManager.checkAccess(callerPath,path,callerFilePath,entry?.filePath,runtimeContext)})}if(details){return visiblePaths.map(path=>({path,kind:kindOf(path)}))}return visiblePaths.filter(path=>kindOf(path)==="function")},reload:async function slothlet_api_reload(pathOrModuleId,options){if(!config.api?.mutations?.reload){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.reload",validationError:true})}if(pathOrModuleId==null||pathOrModuleId===""||pathOrModuleId==="."){return slothlet.handlers.apiManager.reloadApiComponent({apiPath:".",options})}if(typeof pathOrModuleId!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"string, null, undefined, or '.'",received:typeof pathOrModuleId,validationError:true})}const isModuleId=slothlet.handlers.apiManager.state.addHistory.some(entry=>entry.moduleID===pathOrModuleId);if(isModuleId){return slothlet.handlers.apiManager.reloadApiComponent({moduleID:pathOrModuleId,options})}const normalizedPath=slothlet.handlers.apiManager.normalizeApiPath(pathOrModuleId).apiPath;const pathParts=normalizedPath.split(".");let current=slothlet.api;for(const part of pathParts){if(!current||typeof current!=="object"&&typeof current!=="function"){throw new slothlet.SlothletError("INVALID_API_PATH",{apiPath:pathOrModuleId,validationError:true})}current=current[part]}if(current===void 0){throw new slothlet.SlothletError("INVALID_API_PATH",{apiPath:pathOrModuleId,validationError:true})}return slothlet.handlers.apiManager.reloadApiComponent({apiPath:normalizedPath,options})},modules:{discover:function slothlet_modules_discover(options){return slothlet.handlers.moduleManager.discover(options)},sort:function slothlet_modules_sort(results,comparator){return slothlet.handlers.moduleManager.sort(results,comparator)},addModule:async function slothlet_modules_addModule(nameOrResult,options){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.addModule",validationError:true})}return slothlet.handlers.moduleManager.addModule(nameOrResult,options)},addModules:async function slothlet_modules_addModules(items,options){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.addModules",validationError:true})}return slothlet.handlers.moduleManager.addModules(items,options)},removeModule:async function slothlet_modules_removeModule(name,opts){if(!config.api?.mutations?.remove){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.removeModule",validationError:true})}return slothlet.handlers.moduleManager.removeModule(name,opts)},addDiscovered:async function slothlet_modules_addDiscovered(options={}){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.addDiscovered",validationError:true})}const{sort:comparator,collisionMode,onFailure,concurrency,...discoverOptions}=options;const found=await slothlet.handlers.moduleManager.discover(discoverOptions);const ordered=slothlet.handlers.moduleManager.sort(found,comparator);return slothlet.handlers.moduleManager.addModules(ordered,{collisionMode,onFailure,concurrency})},getDiscoveryCache:function slothlet_modules_getDiscoveryCache(){return slothlet.handlers.moduleManager.getDiscoveryCache()},clearDiscoveryCache:function slothlet_modules_clearDiscoveryCache(){return slothlet.handlers.moduleManager.clearDiscoveryCache()},getStaleMounts:function slothlet_modules_getStaleMounts(){return slothlet.handlers.moduleManager.getStaleMounts()}}},sanitize:function slothlet_sanitize(str){if(typeof str!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"str",expected:"string",received:typeof str,validationError:true})}return slothlet.helpers.sanitize.sanitizePropertyName(str,slothlet.config.sanitize||{})},lockCaller:function slothlet_lockCaller(fn){if(typeof fn!=="function"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"fn",expected:"function",received:typeof fn,validationError:true})}const capturedWrapper=slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper??null;if(!capturedWrapper)return fn;const locked=function slothlet_lockedCaller(...args){return slothlet.contextManager.runInContext(slothlet.instanceID,fn,this,args,capturedWrapper,true)};locked._slothletOriginal=fn;return locked},bind:function slothlet_bind(fn){if(typeof fn!=="function"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"fn",expected:"function",received:typeof fn,validationError:true})}return AsyncResource?AsyncResource.bind(fn,"slothlet-bound"):fn},context:{get:key=>{if(slothlet.contextManager.constructor.name==="LiveContextManager"){const currentID=slothlet.contextManager.currentInstanceID;if(currentID){const activeStore=slothlet.contextManager.instances.get(currentID);const isOurInstance=currentID===slothlet.instanceID||currentID?.startsWith(slothlet.instanceID+"__run_")||activeStore?.parentInstanceID===slothlet.instanceID;if(isOurInstance&&activeStore){return key?activeStore.context[key]:{...activeStore.context}}}const store=slothlet.contextManager.instances.get(slothlet.instanceID);if(!store){const baseContext2=slothlet.context||{};return key?baseContext2[key]:{...baseContext2}}return key?store.context[key]:{...store.context}}if(slothlet.contextManager.constructor.name==="AsyncContextManager"){let currentStore=slothlet.contextManager.tryGetContext();if(!currentStore){const baseStore2=slothlet.contextManager.instances.get(slothlet.instanceID);const baseContext3=baseStore2?.context||{};return key?baseContext3[key]:{...baseContext3}}const isOurInstance=currentStore.instanceID===slothlet.instanceID||currentStore.parentInstanceID===slothlet.instanceID||currentStore.instanceID.startsWith(slothlet.instanceID+"__run_");if(isOurInstance){return key?currentStore.context[key]:{...currentStore.context}}const baseStore=slothlet.contextManager.instances.get(slothlet.instanceID);const baseContext2=baseStore?.context||{};return key?baseContext2[key]:{...baseContext2}}const baseContext=slothlet.context||{};return key?baseContext[key]:{...baseContext}},diagnostics:()=>{if(!slothlet.config?.diagnostics)return void 0;const managerType=slothlet.contextManager.constructor.name;const result={instanceID:slothlet.instanceID,managerType,instancesMapSize:slothlet.contextManager.instances.size,instancesMapKeys:Array.from(slothlet.contextManager.instances.keys()),baseContext:slothlet.context};const store=slothlet.contextManager.instances.get(slothlet.instanceID);result.storeFromInstancesMap=store?{instanceID:store.instanceID,context:store.context,createdAt:store.createdAt}:null;if(managerType==="AsyncContextManager"){try{const currentCtx=slothlet.contextManager.tryGetContext();result.currentALSContext=currentCtx?{instanceID:currentCtx.instanceID,context:currentCtx.context,hasParent:!!currentCtx.parentContext,parentInstanceID:currentCtx.parentInstanceID}:null}catch(____error){result.currentALSContext=null}}if(managerType==="LiveContextManager"){result.currentInstanceID=slothlet.contextManager.currentInstanceID}return result},run:this.createRunFunction(),scope:this.createScopeFunction()},hook:{on:function slothlet_hook_on(typePattern,handler,options={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.on(typePattern,handler,options)},remove:function slothlet_hook_remove(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.remove(filter)},clear:function slothlet_hook_clear(filter={}){return this.remove(filter)},off:function slothlet_hook_off(idOrFilter){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}const filter=typeof idOrFilter==="string"?{id:idOrFilter}:idOrFilter;return slothlet.handlers.hookManager.remove(filter)},enable:function slothlet_hook_enable(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.enable(filter)},disable:function slothlet_hook_disable(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.disable(filter)},enablePattern:function slothlet_hook_enablePattern(pattern){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.enablePattern(pattern)},disablePattern:function slothlet_hook_disablePattern(pattern){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.disablePattern(pattern)},resetPatternFilter:function slothlet_hook_resetPatternFilter(){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.resetPatternFilter()},list:function slothlet_hook_list(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.list(filter)},pin:{get enabled(){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.pinEnforced},enable:function slothlet_hook_pin_enable(){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.setPinEnforced(true)},disable:function slothlet_hook_pin_disable(){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.setPinEnforced(false)}}},metadata:{setGlobal:function slothlet_metadata_setGlobal(keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const validateGlobalMetadataKeyPath=keyPath=>{const blocked=new Set(["__proto__","prototype","constructor"]);if(typeof keyPath!=="string"||keyPath.length===0){throw new slothlet.SlothletError("INVALID_METADATA_KEY",{key:keyPath,type:typeof keyPath,expected:"non-empty string"})}const segments=keyPath.split(".");for(const segment of segments){if(!segment||blocked.has(segment)){throw new slothlet.SlothletError("INVALID_METADATA_KEY",{key:keyPath,type:typeof keyPath,expected:"safe dot-notation key without reserved segments"})}}};const normalizeGlobalMetadataObject=(source,prefix="",ancestors=new WeakSet)=>{if(ancestors.has(source)){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"keyOrObj",expected:"acyclic object",received:"circular reference",validationError:true})}ancestors.add(source);try{const normalized={};for(const[key,nestedValue]of Object.entries(source)){const fullKey=prefix?`${prefix}.${key}`:key;validateGlobalMetadataKeyPath(fullKey);const nestedProto=nestedValue&&typeof nestedValue==="object"?Object.getPrototypeOf(nestedValue):null;const isPlainNested=nestedProto===Object.prototype||nestedProto===null;if(nestedValue&&typeof nestedValue==="object"&&!Array.isArray(nestedValue)&&isPlainNested){normalized[key]=normalizeGlobalMetadataObject(nestedValue,fullKey,ancestors);continue}normalized[key]=nestedValue}return normalized}finally{ancestors.delete(source)}};if(keyOrObj&&typeof keyOrObj==="object"&&!Array.isArray(keyOrObj)){const normalizedMetadata=normalizeGlobalMetadataObject(keyOrObj);for(const[key,nestedValue]of Object.entries(normalizedMetadata)){slothlet.handlers.metadata.setGlobalMetadata(key,nestedValue)}return}if(typeof keyOrObj!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"keyOrObj",expected:"string or object",received:typeof keyOrObj,validationError:true})}validateGlobalMetadataKeyPath(keyOrObj);return slothlet.handlers.metadata.setGlobalMetadata(keyOrObj,value)},set:function slothlet_metadata_set(fn,key,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}return slothlet.handlers.metadata.setUserMetadata(fn,key,value)},remove:function slothlet_metadata_remove(fn,key){return slothlet.handlers.metadata.removeUserMetadata(fn,key)},setFor:function slothlet_metadata_setFor(pathOrModuleId,keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.setPathMetadata(resolvedPath,keyOrObj,value)},removeFor:function slothlet_metadata_removeFor(pathOrModuleId,key){if(!slothlet.handlers?.metadata)return;const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.removePathMetadata(resolvedPath,key)},getFor:function slothlet_metadata_getFor(pathOrModuleId){if(!slothlet.handlers?.metadata)return{};const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.getPathMetadata(resolvedPath)},setForVersion:function slothlet_metadata_setForVersion(logicalPath,versionTag,keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const info=slothlet.handlers?.versionManager?.list(logicalPath);if(!info||!info.versions?.[versionTag]){throw new slothlet.SlothletError("VERSION_NOT_FOUND",{version:versionTag,apiPath:logicalPath})}const{moduleID}=info.versions[versionTag];const resolvedPath=_resolvePathOrModuleId(slothlet,moduleID);return slothlet.handlers.metadata.setPathMetadata(resolvedPath,keyOrObj,value)},getForVersion:function slothlet_metadata_getForVersion(logicalPath,versionTag){if(!slothlet.handlers?.metadata)return{};const info=slothlet.handlers?.versionManager?.list(logicalPath);if(!info||!info.versions?.[versionTag])return{};const{moduleID}=info.versions[versionTag];const resolvedPath=_resolvePathOrModuleId(slothlet,moduleID);return slothlet.handlers.metadata.getPathMetadata(resolvedPath)}},scope:this.createScopeFunction(),run:this.createRunFunction(),reload:async(options={})=>{if(!config.api?.mutations?.reload){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"reload",validationError:true})}return slothlet.reload(options)},shutdown:async()=>{let routineError=null;try{await slothlet.handlers.routineManager?.runShutdownModeRoutines()}catch(error){routineError=error}const result=await slothlet.shutdown();if(routineError)throw routineError;return result},owner:{get:apiPath=>{if(slothlet.handlers?.ownership){return slothlet.handlers.ownership.getPathOwnership(apiPath)}return null}},materialize:(()=>{const mgr=slothlet.handlers?.materialize;const getMaterializedState=()=>{enforceInternalPermission("slothlet.materialize.materialized");return mgr?.materialized??false};const getMaterializeStats=()=>{enforceInternalPermission("slothlet.materialize.get");return mgr?mgr.get():{total:0,materialized:0,remaining:0,percentage:100}};const waitForMaterialization=async()=>{enforceInternalPermission("slothlet.materialize.wait");if(!mgr)return;return mgr.wait()};if(!mgr){return Object.freeze({get materialized(){return getMaterializedState()},get:getMaterializeStats,wait:waitForMaterialization})}return Object.freeze({get materialized(){return getMaterializedState()},get:getMaterializeStats,wait:waitForMaterialization})})(),lifecycle:(()=>{const handler=slothlet.handlers?.lifecycle;const noop=()=>{};if(!handler)return{on:noop,off:noop};return{on:handler.on.bind(handler),off:handler.off.bind(handler)}})(),env:slothlet.envSnapshot,versioning:{list:function slothlet_version_list(logicalPath){if(!slothlet.handlers?.versionManager)return void 0;return slothlet.handlers.versionManager.list(logicalPath)},setDefault:function slothlet_version_setDefault(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return;return slothlet.handlers.versionManager.setDefault(logicalPath,versionTag)},unregister:async function slothlet_version_unregister(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return false;const info=slothlet.handlers.versionManager.list(logicalPath);if(!info||!info.versions?.[versionTag])return false;const{moduleID:versionedModuleID}=info.versions[versionTag];await slothlet.handlers.apiManager.removeApiComponent(versionedModuleID);return true},getVersionMetadata:function slothlet_version_getVersionMetadata(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return void 0;return slothlet.handlers.versionManager.getVersionMetadataByPath(logicalPath,versionTag)},setVersionMetadata:function slothlet_version_setVersionMetadata(logicalPath,versionTag,patch){if(!slothlet.handlers?.versionManager)return;return slothlet.handlers.versionManager.setVersionMetadataByPath(logicalPath,versionTag,patch)}},permissions:{addRule:function slothlet_permissions_addRule(rule){if(!config.api?.mutations?.permissions){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.slothlet.permissions.addRule",validationError:true})}const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.addRule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ruleId=permissionManager.addRule(rule,null);if(slothlet.handlers?.apiManager?.state?.operationHistory){slothlet.handlers.apiManager.state.operationHistory.push({type:"addPermissionRule",rule,ownerModuleID:null,ruleId,timestamp:Date.now()})}return ruleId},removeRule:function slothlet_permissions_removeRule(ruleId){if(!config.api?.mutations?.permissions){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.slothlet.permissions.removeRule",validationError:true})}const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.removeRule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const currentWrapper=slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper;const callerModuleID=currentWrapper?.____slothletInternal?.moduleID??null;const result=slothlet.handlers.permissionManager.removeRule(ruleId,callerModuleID);if(result&&slothlet.handlers?.apiManager?.state?.operationHistory){slothlet.handlers.apiManager.state.operationHistory.push({type:"removePermissionRule",ruleId,callerModuleID,timestamp:Date.now()})}return result},self:{access:function slothlet_permissions_self_access(target){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.checkAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ctx=slothlet.contextManager?.tryGetContext?.();const currentWrapper=slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper;const callerPath=currentWrapper?.____slothletInternal?.apiPath??"";const callerFilePath=currentWrapper?.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;return slothlet.handlers.permissionManager.checkAccess(callerPath,target,callerFilePath,null,runtimeContext)},rules:function slothlet_permissions_self_rules(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForCaller){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const currentWrapper=slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper;const callerPath=currentWrapper?.____slothletInternal?.apiPath??"";return slothlet.handlers.permissionManager.getRulesForCaller(callerPath)}},global:{checkAccess:function slothlet_permissions_global_checkAccess(caller,target){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.checkAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const runtimeContext=slothlet.contextManager?.tryGetContext?.()?.context??null;return slothlet.handlers.permissionManager.checkAccess(caller,target,null,null,runtimeContext)},rulesForPath:function slothlet_permissions_global_rulesForPath(path){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForPath){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return slothlet.handlers.permissionManager.getRulesForPath(path)},rulesByModule:function slothlet_permissions_global_rulesByModule(moduleID){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesByModule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return slothlet.handlers.permissionManager.getRulesByModule(moduleID)}},control:{get enabled(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.isEnabled){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return permissionManager.isEnabled()},enable:function slothlet_permissions_control_enable(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.enable){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.enable()},disable:function slothlet_permissions_control_disable(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.disable){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.disable()},get readGatingEnabled(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.isReadGatingEnabled){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return permissionManager.isReadGatingEnabled()},readGating:function slothlet_permissions_control_readGating(value){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.setReadGating){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.setReadGating(value)},seal:function slothlet_permissions_control_seal(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.seal){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.seal()},get sealed(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.isSealed){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return permissionManager.isSealed()}}}};if(!config.hook?.enabled&&config.diagnostics!==true){delete namespace.hooks}if(config.diagnostics===true){namespace.diag={describe:(showAll=false)=>{if(showAll){return{...userApi}}return Reflect.ownKeys(userApi)},reference:slothlet.reference||null,context:slothlet.context||{},inspect:()=>{return slothlet.getDiagnostics()},getAPI:()=>{return slothlet.getAPI()},getOwnership:()=>{return slothlet.getOwnership()},owner:{get:apiPath=>{if(slothlet.handlers?.ownership){return slothlet.handlers.ownership.getPathOwnership(apiPath)}return null}},caches:{get:()=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.getCacheDiagnostics()}return{totalCaches:0,caches:[]}},getAllModuleIDs:()=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.getAllModuleIDs()}return[]},has:moduleID=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.has(moduleID)}return false}},SlothletWarning:slothlet.SlothletWarning,hook:slothlet.handlers?.hookManager?{get enabled(){return slothlet.handlers.hookManager.enabled},compilePattern:pattern=>{return slothlet.handlers.hookManager.getCompilePatternForDiagnostics()(pattern)}}:void 0}}return createInternalRouteProxy(namespace,"slothlet")}createShutdownFunction(){const slothlet=this.slothlet;const shutdownFunction={shutdown:async()=>{let routineError=null;try{await slothlet.handlers.routineManager?.runShutdownModeRoutines()}catch(error){routineError=error}if(slothlet.userHooks?.shutdown&&typeof slothlet.userHooks.shutdown==="function"){await slothlet.userHooks.shutdown()}const result=await slothlet.shutdown();if(routineError)throw routineError;return result}}.shutdown;return shutdownFunction}createRunFunction(){const slothlet=this.slothlet;const scopeFunc=this.createScopeFunction();const runFunction={run:async(contextData,callback,...args)=>{if(slothlet.config.scope===false){throw new slothlet.SlothletError("SCOPE_DISABLED",{},null,{validationError:true})}if(!contextData||typeof contextData!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_CONTEXT",{received:typeof contextData},null,{validationError:true})}if(typeof callback!=="function"){throw new slothlet.SlothletError("SCOPE_INVALID_CALLBACK",{received:typeof callback},null,{validationError:true})}return scopeFunc({context:contextData,fn:callback,args,merge:slothlet.config.scope?.merge||"shallow",isolation:slothlet.config.scope?.isolation||"partial"})}}.run;return runFunction}createScopeFunction(){const slothlet=this.slothlet;const scopeFunction={scope:async options=>{if(slothlet.config.scope===false){throw new slothlet.SlothletError("SCOPE_DISABLED",{},null,{validationError:true})}if(!options||typeof options!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_OPTIONS",{received:typeof options},null,{validationError:true})}if(!options.fn||typeof options.fn!=="function"){throw new slothlet.SlothletError("SCOPE_INVALID_FN",{received:typeof options?.fn},null,{validationError:true})}if(!options.context||typeof options.context!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_CONTEXT_OBJECT",{received:typeof options?.context},null,{validationError:true})}const{context:contextData,fn,args=[],merge="shallow",isolation,protect,owners}=options;if(merge!=="shallow"&&merge!=="deep"){throw new slothlet.SlothletError("SCOPE_INVALID_MERGE_STRATEGY",{merge},null,{validationError:true})}const isolationMode=isolation||slothlet.config.scope?.isolation||"partial";if(isolationMode!=="partial"&&isolationMode!=="full"){throw new slothlet.SlothletError("SCOPE_INVALID_ISOLATION_MODE",{isolationMode},null,{validationError:true})}if(protect!==void 0&&(!Array.isArray(protect)||protect.some(k=>typeof k!=="string"))){throw new slothlet.SlothletError("SCOPE_INVALID_PROTECT",{received:describeScopeReceived(protect)},null,{validationError:true})}if(owners!==void 0){if(typeof owners!=="object"||owners===null||Array.isArray(owners)){throw new slothlet.SlothletError("SCOPE_INVALID_OWNERS",{received:describeScopeReceived(owners)},null,{validationError:true})}for(const[key,owner]of Object.entries(owners)){if(typeof owner!=="string"||owner.length===0){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:`owners.${key}`,expected:"non-empty string",received:typeof owner==="string"?"empty string":typeof owner,validationError:true})}}}const buildContextOwners=parentOwners=>{const base=parentOwners??null;let child=base;const claim=(key,owner)=>{const existing=child&&Object.prototype.hasOwnProperty.call(child,key)?child[key]:void 0;if(existing!==void 0&&existing!==owner){throw new slothlet.SlothletError("CONTEXT_KEY_OWNED",{key:String(key)},null,{validationError:true})}if(child===base)child=base?Object.assign(Object.create(null),base):Object.create(null);child[key]=owner};if(Array.isArray(protect))for(const k of protect)claim(k,PROTECT_SENTINEL);if(owners&&typeof owners==="object")for(const[k,o]of Object.entries(owners))claim(k,o);return child};const contextManager=slothlet.contextManager;if(!contextManager){throw new slothlet.SlothletError("NO_CONTEXT_MANAGER",{validationError:true})}const{utilities}=slothlet.helpers;if(contextManager.constructor.name==="LiveContextManager"){let currentStore=null;const currentID=contextManager.currentInstanceID;if(currentID){const activeStore=contextManager.instances.get(currentID);const isOurContext=currentID===slothlet.instanceID||activeStore?.parentInstanceID===slothlet.instanceID||currentID.startsWith(slothlet.instanceID+"__run_");if(isOurContext){currentStore=activeStore}}if(!currentStore){currentStore=contextManager.instances.get(slothlet.instanceID)}if(!currentStore){throw new slothlet.SlothletError("CONTEXT_NOT_FOUND",{instanceID:slothlet.instanceID,availableInstances:[...contextManager.instances.keys()].join(", ")||"none",validationError:true})}let mergedContext;if(merge==="deep"){mergedContext=utilities.deepMerge(currentStore.context,contextData);mergedContext=structuredClone(mergedContext)}else{const clonedParent=structuredClone(currentStore.context);mergedContext={...clonedParent,...contextData}}const childInstanceID=`${slothlet.instanceID}__run_${Date.now()}_${Math.random().toString(36).slice(2,9)}`;const childStore={instanceID:childInstanceID,context:mergedContext,self:isolationMode==="full"?makeCopyOnWriteSelf(currentStore.self):currentStore.self,config:currentStore.config,createdAt:currentStore.createdAt,parentInstanceID:slothlet.instanceID,currentWrapper:currentStore.currentWrapper,callerWrapper:currentStore.callerWrapper,slothlet:currentStore.slothlet,__contextOwners:buildContextOwners(currentStore.__contextOwners)};if(currentStore[TRUSTED_ROOT]===true){Object.defineProperty(childStore,TRUSTED_ROOT,{value:true,configurable:true})}contextManager.instances.set(childInstanceID,childStore);const previousInstanceID=contextManager.currentInstanceID;try{contextManager.currentInstanceID=childInstanceID;return await fn(...args)}finally{contextManager.currentInstanceID=previousInstanceID;contextManager.instances.delete(childInstanceID)}}if(contextManager.constructor.name==="AsyncContextManager"){let currentStore=null;const activeStore=contextManager.tryGetContext();if(activeStore){const isOurContext=activeStore.instanceID===slothlet.instanceID||activeStore.parentInstanceID===slothlet.instanceID||activeStore.instanceID.startsWith(slothlet.instanceID+"__run_");if(isOurContext){currentStore=activeStore}}if(!currentStore){const baseStore=contextManager.instances.get(slothlet.instanceID);if(!baseStore){throw new slothlet.SlothletError("CONTEXT_NOT_FOUND",{instanceID:slothlet.instanceID,availableInstances:[...contextManager.instances.keys()].join(", ")||"none",validationError:true})}currentStore=baseStore}let mergedContext;if(merge==="deep"){mergedContext=utilities.deepMerge(currentStore.context,contextData);mergedContext=structuredClone(mergedContext)}else{const clonedParent=structuredClone(currentStore.context);mergedContext={...clonedParent,...contextData}}const childInstanceID=`${slothlet.instanceID}__run_${Date.now()}_${Math.random().toString(36).slice(2,9)}`;const childStore={instanceID:childInstanceID,context:mergedContext,self:isolationMode==="full"?makeCopyOnWriteSelf(currentStore.self):currentStore.self,config:currentStore.config,createdAt:currentStore.createdAt,parentInstanceID:slothlet.instanceID,currentWrapper:currentStore.currentWrapper,callerWrapper:currentStore.callerWrapper,slothlet:currentStore.slothlet,__contextOwners:buildContextOwners(currentStore.__contextOwners)};if(currentStore[TRUSTED_ROOT]===true){Object.defineProperty(childStore,TRUSTED_ROOT,{value:true,configurable:true})}contextManager.instances.set(childInstanceID,childStore);try{return await contextManager.als.run(childStore,async()=>{return await fn(...args)})}finally{contextManager.instances.delete(childInstanceID)}}throw new slothlet.SlothletError("UNSUPPORTED_CONTEXT_MANAGER",{manager:contextManager.constructor.name,validationError:true})}}.scope;return scopeFunction}createDestroyFunction(api){const slothlet=this.slothlet;const destroyFunction={destroy:async()=>{let routineError=null;try{await slothlet.handlers.routineManager?.runDestroyModeRoutines()}catch(error){routineError=error}if(slothlet.userHooks?.destroy&&typeof slothlet.userHooks.destroy==="function"){await slothlet.userHooks.destroy()}try{if(api&&typeof api.shutdown==="function"){await api.shutdown()}else{await slothlet.shutdown()}}catch(error){if(!routineError)routineError=error}slothlet.isDestroyed=true;const objectsToClear=[api,slothlet.api].filter(obj=>obj&&typeof obj==="object");for(const obj of objectsToClear){const keys=Object.keys(obj);for(const key of keys){try{delete obj[key]}catch(_){}}}slothlet.api=null;if(routineError)throw routineError}}.destroy;return destroyFunction}attachBuiltins(userApi,builtins){Object.defineProperty(userApi,"slothlet",{value:builtins.slothlet,enumerable:true,writable:false,configurable:true});Object.defineProperty(userApi,"shutdown",{value:builtins.shutdown,enumerable:true,writable:false,configurable:true});if(builtins.destroy!==null){Object.defineProperty(userApi,"destroy",{value:builtins.destroy,enumerable:true,writable:false,configurable:true})}}}export{ApiBuilder};
17
+ import{isNode,AsyncResource,loadJson}from"@cldmv/slothlet/helpers/platform";import{ComponentBase}from"#factories/component-base";import{TYPE_STATES,resolveWrapper}from"#handlers/unified-wrapper";import{TRUSTED_ROOT,PROTECT_SENTINEL}from"#handlers/trusted-root";import{getLanguage,initI18n,setLanguage,setLanguageAsync,t,translate}from"@cldmv/slothlet/i18n";function _resolvePathOrModuleId(slothlet,pathOrModuleId){const history=slothlet.handlers?.apiManager?.state?.addHistory;if(history){let match=null;for(let i=history.length-1;i>=0;i--){const entry=history[i];if(entry?.moduleID===pathOrModuleId){match=entry;break}}if(match)return match.apiPath}return pathOrModuleId}function describeScopeReceived(value){if(Array.isArray(value)){const badIndex=value.findIndex(entry=>typeof entry!=="string");if(badIndex!==-1){return`array with non-string entry (${typeof value[badIndex]} at index ${badIndex})`}return"array"}return typeof value}function makeCopyOnWriteSelf(parentSelf){const overlay=new Map;const childViews=new Map;return new Proxy({},{get(_t,prop){if(overlay.has(prop))return overlay.get(prop);const real=parentSelf[prop];if(real!==null&&typeof real==="object"){let view=childViews.get(prop);if(view===void 0){view=makeCopyOnWriteSelf(real);childViews.set(prop,view)}return view}return real},set(_t,prop,value){overlay.set(prop,value);childViews.delete(prop);return true},has(_t,prop){return overlay.has(prop)||prop in parentSelf},deleteProperty(_t,prop){overlay.delete(prop);childViews.delete(prop);return true},ownKeys(){const keys=new Set(Reflect.ownKeys(parentSelf));for(const k of overlay.keys())keys.add(k);return[...keys]},getOwnPropertyDescriptor(_t,prop){if(overlay.has(prop)){return{value:overlay.get(prop),writable:true,enumerable:true,configurable:true}}const desc=Reflect.getOwnPropertyDescriptor(parentSelf,prop);return desc?{...desc,configurable:true}:void 0}})}class ApiBuilder extends ComponentBase{static slothletProperty="apiBuilder";constructor(slothlet){super(slothlet)}async buildFinalAPI(userApi){this.slothlet.debug("api",{key:"DEBUG_MODE_BUILD_FINAL_API_CALLED",diagnostics:this.____config.diagnostics,userApiKeys:Object.keys(userApi)});if(this.slothlet._ownBuiltins){for(const[key,ref]of Object.entries(this.slothlet._ownBuiltins)){if(ref&&Object.prototype.hasOwnProperty.call(userApi,key)&&userApi[key]===ref){try{delete userApi[key]}catch(_){}}}}this.slothlet.userHooks={shutdown:typeof userApi.shutdown==="function"?userApi.shutdown:null,destroy:typeof userApi.destroy==="function"?userApi.destroy:null};if(userApi.slothlet){if(!this.____config?.silent){new this.SlothletWarning("WARNING_RESERVED_PROPERTY_CONFLICT",{properties:"slothlet"})}await this.emitImplDiagnostic("warning",{apiPath:"slothlet",code:"WARNING_RESERVED_PROPERTY_CONFLICT",context:{properties:"slothlet"},source:"buildAPI"})}const slothletNamespace=await this.createSlothletNamespace(userApi);this.slothlet.debug("api",{key:"DEBUG_MODE_SLOTHLET_NAMESPACE_CREATED",namespaceKeys:Object.keys(slothletNamespace),hasDiag:!!slothletNamespace.diag});const shutdownFn=this.createShutdownFunction();this.attachBuiltins(userApi,{slothlet:slothletNamespace,shutdown:shutdownFn,destroy:null});this.slothlet.debug("api",{key:"DEBUG_MODE_BUILT_INS_ATTACHED",userApiKeys:Object.keys(userApi),hasSlothlet:!!userApi.slothlet,hasDiag:!!userApi.slothlet?.diag});const destroyWithApi=this.createDestroyFunction(userApi);Object.defineProperty(userApi,"destroy",{value:destroyWithApi,enumerable:true,writable:false,configurable:true});this.slothlet._ownBuiltins={shutdown:shutdownFn,slothlet:slothletNamespace,destroy:destroyWithApi};return userApi}async createSlothletNamespace(userApi){const slothlet=this.slothlet;const config=this.____config;const enforceInternalPermission=targetPath=>{const ctx=slothlet.contextManager?.tryGetContext?.(slothlet.instanceID);const identity=slothlet.contextManager?.getCallerIdentity?.(slothlet.instanceID);if(identity?.unresolved){throw new slothlet.SlothletError("PERMISSION_DENIED",{caller:null,target:targetPath})}const callerWrapper=identity?.currentWrapper;if(!callerWrapper)return;const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.enforceAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const callerPath=callerWrapper.____slothletInternal?.apiPath??"";const callerFilePath=callerWrapper.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;if(!permissionManager.enforceAccess(callerPath,targetPath,callerFilePath,null,runtimeContext)){throw new slothlet.SlothletError("PERMISSION_DENIED",{caller:callerPath,target:targetPath})}};const canTraverseInternalNamespace=targetPath=>{const ctx=slothlet.contextManager?.tryGetContext?.(slothlet.instanceID);const identity=slothlet.contextManager?.getCallerIdentity?.(slothlet.instanceID);if(identity?.unresolved)return false;const callerWrapper=identity?.currentWrapper;if(!callerWrapper)return false;const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForCaller||!permissionManager?.checkAccess){return false}const callerPath=callerWrapper.____slothletInternal?.apiPath??"";const callerFilePath=callerWrapper.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;const callerRules=permissionManager.getRulesForCaller(callerPath);const conditionMatches=condition=>typeof permissionManager.matchesCondition==="function"?permissionManager.matchesCondition(condition,runtimeContext):false;const prefix=`${targetPath}.`;for(const rule of callerRules){if(!rule||rule.effect!=="allow"||typeof rule.target!=="string")continue;const couldMatchDescendant=rule.target.startsWith(prefix)||rule.target==="**"||rule.target==="*";if(!couldMatchDescendant)continue;if(rule.target.startsWith(prefix)&&conditionMatches(rule.condition)){return true}const probePaths=[`${targetPath}.__probe__`];if(rule.target.startsWith(prefix)){const suffix=rule.target.slice(prefix.length);const firstSegment=suffix.split(".")[0];if(firstSegment&&!/[*!?{}]/u.test(firstSegment)){probePaths.unshift(`${targetPath}.${firstSegment}`);probePaths.push(`${targetPath}.${firstSegment}.__probe__`)}}for(const probePath of probePaths){if(permissionManager.checkAccess(callerPath,probePath,callerFilePath,null,runtimeContext,{useCache:false})){return true}}}return false};const createInternalRouteProxy=(value,routePath,seen=new WeakMap)=>{if(!value||typeof value!=="object"&&typeof value!=="function")return value;const isMetaProperty=prop=>prop==="__proto__"||prop==="prototype"||prop==="constructor"||prop==="caller"||prop==="arguments";let routeCache=seen.get(value);if(!routeCache){routeCache=new Map;seen.set(value,routeCache)}if(routeCache.has(routePath)){return routeCache.get(routePath)}const proxy=new Proxy(value,{get(target,prop,receiver){if(typeof prop!=="string"){const result2=Reflect.get(target,prop,receiver);return createInternalRouteProxy(result2,routePath,seen)}const childRoutePath=`${routePath}.${prop}`;let deniedError=null;try{enforceInternalPermission(childRoutePath)}catch(error){deniedError=error}if(deniedError){if(canTraverseInternalNamespace(childRoutePath)){const result2=Reflect.get(target,prop,receiver);if(result2&&(typeof result2==="object"||typeof result2==="function")){return createInternalRouteProxy(result2,childRoutePath,seen)}}throw deniedError}const result=Reflect.get(target,prop,receiver);if(isMetaProperty(prop)){return result}const descriptor=Object.getOwnPropertyDescriptor(target,prop);if(descriptor&&"value"in descriptor&&descriptor.configurable===false&&descriptor.writable===false){return descriptor.value}return createInternalRouteProxy(result,childRoutePath,seen)},getOwnPropertyDescriptor(target,prop){const descriptor=Reflect.getOwnPropertyDescriptor(target,prop);if(!descriptor)return void 0;if(typeof prop!=="string"){return descriptor}const childRoutePath=`${routePath}.${prop}`;if(isMetaProperty(prop)){enforceInternalPermission(childRoutePath);return descriptor}if("get"in descriptor||"set"in descriptor){enforceInternalPermission(childRoutePath);if(descriptor.configurable===true){return{...descriptor,get:typeof descriptor.get==="function"?function slothlet_internal_descriptor_getter(...args){enforceInternalPermission(childRoutePath);return Reflect.apply(descriptor.get,this,args)}:descriptor.get,set:typeof descriptor.set==="function"?function slothlet_internal_descriptor_setter(...args){enforceInternalPermission(childRoutePath);return Reflect.apply(descriptor.set,this,args)}:descriptor.set}}return descriptor}if(!("value"in descriptor)){return descriptor}const descriptorValue=descriptor.value;if(descriptor.configurable===false&&descriptor.writable===false){enforceInternalPermission(childRoutePath);return descriptor}if(!descriptorValue||typeof descriptorValue!=="object"&&typeof descriptorValue!=="function"||typeof descriptorValue==="function"){enforceInternalPermission(childRoutePath);if(typeof descriptorValue==="function"&&descriptor.configurable===true){return{...descriptor,value:createInternalRouteProxy(descriptorValue,childRoutePath,seen)}}return descriptor}return{...descriptor,value:createInternalRouteProxy(descriptorValue,childRoutePath,seen)}},set(target,prop,newValue,receiver){if(typeof prop!=="string"){return Reflect.set(target,prop,newValue,receiver)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.set(target,prop,newValue,receiver)},defineProperty(target,prop,descriptor){if(typeof prop!=="string"){return Reflect.defineProperty(target,prop,descriptor)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.defineProperty(target,prop,descriptor)},deleteProperty(target,prop){if(typeof prop!=="string"){return Reflect.deleteProperty(target,prop)}const childRoutePath=`${routePath}.${prop}`;enforceInternalPermission(childRoutePath);return Reflect.deleteProperty(target,prop)},ownKeys(target){return Reflect.ownKeys(target)},apply(target,thisArg,argArray){enforceInternalPermission(routePath);return Reflect.apply(target,thisArg,argArray)},construct(target,argArray,newTarget){enforceInternalPermission(routePath);return Reflect.construct(target,argArray,newTarget)}});routeCache.set(routePath,proxy);return proxy};let version="unknown";if(isNode){const pkg=loadJson(new URL("../../../package.json",import.meta.url));if(pkg?.version)version=pkg?.version}const namespace={i18n:{setLanguage,setLanguageAsync,getLanguage,translate,t,initI18n},version,instanceID:slothlet.instanceID,types:TYPE_STATES,api:{add:async function slothlet_api_add(apiPath,folderPath,options={},versionConfig=null){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.add",validationError:true})}const LOCKED_ADD_OPTIONS=["collisionMode","mutateExisting","recordHistory"];let effectiveOptions=options;if(!config.api?.mutations?.allowCollisionOverride){const passed=LOCKED_ADD_OPTIONS.filter(key=>key in options);if(passed.length>0){if(!config?.silent){new slothlet.SlothletWarning("WARNING_API_ADD_OPTION_LOCKED",{option:passed.join(", ")})}effectiveOptions={};for(const key of Object.keys(options)){if(!LOCKED_ADD_OPTIONS.includes(key))effectiveOptions[key]=options[key]}}}const addResult=await slothlet.handlers.apiManager.addApiComponent({apiPath,folderPath,options:effectiveOptions,versionConfig:versionConfig||null});if(slothlet.handlers.routineManager){await slothlet.handlers.routineManager.rebuildStacks(userApi)}return addResult},remove:async function slothlet_api_remove(pathOrModuleId,apiPath){if(!config.api?.mutations?.remove){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.remove",validationError:true})}if(typeof pathOrModuleId!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"string",received:typeof pathOrModuleId,validationError:true})}if(apiPath!==void 0&&typeof apiPath!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"apiPath",expected:"string",received:typeof apiPath,validationError:true})}const removeResult=await slothlet.handlers.apiManager.removeApiComponent(pathOrModuleId,{scopedApiPath:apiPath});if(slothlet.handlers.routineManager){await slothlet.handlers.routineManager.rebuildStacks(userApi)}return removeResult},leaves:async function slothlet_api_leaves(key,options={}){enforceInternalPermission("slothlet.api.leaves");if(typeof key!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"key",expected:"string",received:typeof key,validationError:true})}const normalizedOptions=options==null?{}:options;if(typeof normalizedOptions!=="object"||Array.isArray(normalizedOptions)){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"options",expected:"object with optional boolean details/includePrivate",received:Array.isArray(normalizedOptions)?"array":typeof normalizedOptions,validationError:true})}const{details=false,includePrivate=false}=normalizedOptions;for(const[name,value]of[["details",details],["includePrivate",includePrivate]]){if(typeof value!=="boolean"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:`options.${name}`,expected:"boolean",received:typeof value,validationError:true})}}const ownership=slothlet.handlers.ownership;const leavesIdentity=slothlet.contextManager?.getCallerIdentity?.();const leavesCaller=leavesIdentity?.currentWrapper??null;if(includePrivate&&leavesCaller){throw new slothlet.SlothletError("PERMISSION_DENIED",{caller:leavesCaller.____slothletInternal?.apiPath??"",target:"slothlet.api.leaves:includePrivate"})}let moduleID=null;if(key==="."||key===""){moduleID=[...ownership.moduleEndpoints.entries()].find(([,endpoint2])=>endpoint2===".")?.[0]}else if(ownership.moduleToPath.has(key)){moduleID=key}else{moduleID=ownership.getCurrentOwner(key)?.moduleID??null;if(!moduleID){moduleID=[...ownership.moduleEndpoints.entries()].find(([,endpoint2])=>endpoint2===key)?.[0]??null}}if(!moduleID||!ownership.moduleToPath.has(moduleID)){throw new slothlet.SlothletError("API_LEAVES_UNKNOWN_MODULE",{key},null,{validationError:true})}const endpoint=ownership.moduleEndpoints.get(moduleID);const boundApi=slothlet.boundApi;if(config.mode==="lazy"&&boundApi){const seen=new WeakSet;let root=boundApi;if(endpoint!=="."){for(const segment of endpoint.split("."))root=root[segment]}const pending=[{node:root,path:endpoint==="."?"":endpoint}];while(pending.length>0){const{node,path}=pending.pop();if(node===null||typeof node!=="object"&&typeof node!=="function")continue;if(seen.has(node))continue;seen.add(node);if(typeof node._materialize==="function"&&node.__materialized===false){await node._materialize()}const childKeys=new Set(Object.keys(node));const records=ownership.moduleToPath.get(moduleID);if(path!==null&&records){const prefix=path===""?"":`${path}.`;for(const ownedPath of records){if(ownedPath===""||prefix!==""&&!ownedPath.startsWith(prefix))continue;const rest=ownedPath.slice(prefix.length);const segmentEnd=rest.indexOf(".");childKeys.add(segmentEnd===-1?rest:rest.slice(0,segmentEnd))}}for(const childKey of childKeys){if(path===""&&(childKey==="slothlet"||childKey==="shutdown"||childKey==="destroy"))continue;let child;try{child=node[childKey]}catch{continue}const childWrapper=child?resolveWrapper(child):null;if(childWrapper?.____slothletInternal?.userAssigned)continue;const childPath=path===null?null:path===""?childKey:`${path}.${childKey}`;pending.push({node:child,path:records?.has(childPath)?childPath:null})}}}if(!ownership.moduleToPath.has(moduleID)){throw new slothlet.SlothletError("API_LEAVES_UNKNOWN_MODULE",{key},null,{validationError:true})}const ownedPaths=[...ownership.moduleToPath.get(moduleID)].filter(path=>{if(path==="")return false;if(endpoint==="."&&(path==="slothlet"||path.startsWith("slothlet.")||path==="shutdown"||path==="destroy"))return false;const currentOwner=ownership.getCurrentOwner(path);return!currentOwner||currentOwner.moduleID===moduleID});ownedPaths.sort();const namespaces=new Set;for(const owned of ownedPaths){let cut=owned.lastIndexOf(".");while(cut>0){namespaces.add(owned.slice(0,cut));cut=owned.lastIndexOf(".",cut-1)}}const kindOf=path=>{if(namespaces.has(path))return"namespace";const entry=ownership.pathToModule.get(path)?.find(candidate=>candidate.moduleID===moduleID);return typeof entry?.value==="function"?"function":"data"};const permissionManager=slothlet.handlers?.permissionManager;let visiblePaths=ownedPaths;if(!includePrivate&&permissionManager?.isEnabled?.()){const callerPath=leavesCaller?.____slothletInternal?.apiPath??"";const callerFilePath=leavesCaller?.____slothletInternal?.filePath??null;const runtimeContext=slothlet.contextManager?.tryGetContext?.()?.context??null;visiblePaths=ownedPaths.filter(path=>{if(!permissionManager.isPrivateTarget(path))return true;const entry=ownership.pathToModule.get(path)?.find(candidate=>candidate.moduleID===moduleID);return permissionManager.checkAccess(callerPath,path,callerFilePath,entry?.filePath,runtimeContext)})}if(details){return visiblePaths.map(path=>({path,kind:kindOf(path)}))}return visiblePaths.filter(path=>kindOf(path)==="function")},reload:async function slothlet_api_reload(pathOrModuleId,options){if(!config.api?.mutations?.reload){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.reload",validationError:true})}if(pathOrModuleId==null||pathOrModuleId===""||pathOrModuleId==="."){return slothlet.handlers.apiManager.reloadApiComponent({apiPath:".",options})}if(typeof pathOrModuleId!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"string, null, undefined, or '.'",received:typeof pathOrModuleId,validationError:true})}const isModuleId=slothlet.handlers.apiManager.state.addHistory.some(entry=>entry.moduleID===pathOrModuleId);if(isModuleId){return slothlet.handlers.apiManager.reloadApiComponent({moduleID:pathOrModuleId,options})}const normalizedPath=slothlet.handlers.apiManager.normalizeApiPath(pathOrModuleId).apiPath;const pathParts=normalizedPath.split(".");let current=slothlet.api;for(const part of pathParts){if(!current||typeof current!=="object"&&typeof current!=="function"){throw new slothlet.SlothletError("INVALID_API_PATH",{apiPath:pathOrModuleId,validationError:true})}current=current[part]}if(current===void 0){throw new slothlet.SlothletError("INVALID_API_PATH",{apiPath:pathOrModuleId,validationError:true})}return slothlet.handlers.apiManager.reloadApiComponent({apiPath:normalizedPath,options})},modules:{discover:function slothlet_modules_discover(options){return slothlet.handlers.moduleManager.discover(options)},sort:function slothlet_modules_sort(results,comparator){return slothlet.handlers.moduleManager.sort(results,comparator)},addModule:async function slothlet_modules_addModule(nameOrResult,options){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.addModule",validationError:true})}return slothlet.handlers.moduleManager.addModule(nameOrResult,options)},addModules:async function slothlet_modules_addModules(items,options){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.addModules",validationError:true})}return slothlet.handlers.moduleManager.addModules(items,options)},removeModule:async function slothlet_modules_removeModule(name,opts){if(!config.api?.mutations?.remove){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.removeModule",validationError:true})}return slothlet.handlers.moduleManager.removeModule(name,opts)},addDiscovered:async function slothlet_modules_addDiscovered(options={}){if(!config.api?.mutations?.add){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.modules.addDiscovered",validationError:true})}const{sort:comparator,collisionMode,onFailure,concurrency,...discoverOptions}=options;const found=await slothlet.handlers.moduleManager.discover(discoverOptions);const ordered=slothlet.handlers.moduleManager.sort(found,comparator);return slothlet.handlers.moduleManager.addModules(ordered,{collisionMode,onFailure,concurrency})},getDiscoveryCache:function slothlet_modules_getDiscoveryCache(){return slothlet.handlers.moduleManager.getDiscoveryCache()},clearDiscoveryCache:function slothlet_modules_clearDiscoveryCache(){return slothlet.handlers.moduleManager.clearDiscoveryCache()},getStaleMounts:function slothlet_modules_getStaleMounts(){return slothlet.handlers.moduleManager.getStaleMounts()}}},sanitize:function slothlet_sanitize(str){if(typeof str!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"str",expected:"string",received:typeof str,validationError:true})}return slothlet.helpers.sanitize.sanitizePropertyName(str,slothlet.config.sanitize||{})},lockCaller:function slothlet_lockCaller(fn){if(typeof fn!=="function"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"fn",expected:"function",received:typeof fn,validationError:true})}const capturedWrapper=slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper??null;if(!capturedWrapper)return fn;const locked=function slothlet_lockedCaller(...args){return slothlet.contextManager.runInContext(slothlet.instanceID,fn,this,args,capturedWrapper,true)};locked._slothletOriginal=fn;return locked},bind:function slothlet_bind(fn){if(typeof fn!=="function"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"fn",expected:"function",received:typeof fn,validationError:true})}return AsyncResource?AsyncResource.bind(fn,"slothlet-bound"):fn},context:{get:key=>{if(slothlet.contextManager.constructor.name==="LiveContextManager"){const currentID=slothlet.contextManager.currentInstanceID;if(currentID){const activeStore=slothlet.contextManager.instances.get(currentID);const isOurInstance=currentID===slothlet.instanceID||currentID?.startsWith(slothlet.instanceID+"__run_")||activeStore?.parentInstanceID===slothlet.instanceID;if(isOurInstance&&activeStore){return key?activeStore.context[key]:{...activeStore.context}}}const store=slothlet.contextManager.instances.get(slothlet.instanceID);if(!store){const baseContext2=slothlet.context||{};return key?baseContext2[key]:{...baseContext2}}return key?store.context[key]:{...store.context}}if(slothlet.contextManager.constructor.name==="AsyncContextManager"){let currentStore=slothlet.contextManager.tryGetContext();if(!currentStore){const baseStore2=slothlet.contextManager.instances.get(slothlet.instanceID);const baseContext3=baseStore2?.context||{};return key?baseContext3[key]:{...baseContext3}}const isOurInstance=currentStore.instanceID===slothlet.instanceID||currentStore.parentInstanceID===slothlet.instanceID||currentStore.instanceID.startsWith(slothlet.instanceID+"__run_");if(isOurInstance){return key?currentStore.context[key]:{...currentStore.context}}const baseStore=slothlet.contextManager.instances.get(slothlet.instanceID);const baseContext2=baseStore?.context||{};return key?baseContext2[key]:{...baseContext2}}const baseContext=slothlet.context||{};return key?baseContext[key]:{...baseContext}},diagnostics:()=>{if(!slothlet.config?.diagnostics)return void 0;const managerType=slothlet.contextManager.constructor.name;const result={instanceID:slothlet.instanceID,managerType,instancesMapSize:slothlet.contextManager.instances.size,instancesMapKeys:Array.from(slothlet.contextManager.instances.keys()),baseContext:slothlet.context};const store=slothlet.contextManager.instances.get(slothlet.instanceID);result.storeFromInstancesMap=store?{instanceID:store.instanceID,context:store.context,createdAt:store.createdAt}:null;if(managerType==="AsyncContextManager"){try{const currentCtx=slothlet.contextManager.tryGetContext();result.currentALSContext=currentCtx?{instanceID:currentCtx.instanceID,context:currentCtx.context,hasParent:!!currentCtx.parentContext,parentInstanceID:currentCtx.parentInstanceID}:null}catch(____error){result.currentALSContext=null}}if(managerType==="LiveContextManager"){result.currentInstanceID=slothlet.contextManager.currentInstanceID}return result},run:this.createRunFunction(),scope:this.createScopeFunction()},hook:{on:function slothlet_hook_on(typePattern,handler,options={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.on(typePattern,handler,options)},remove:function slothlet_hook_remove(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.remove(filter)},clear:function slothlet_hook_clear(filter={}){return this.remove(filter)},off:function slothlet_hook_off(idOrFilter){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}const filter=typeof idOrFilter==="string"?{id:idOrFilter}:idOrFilter;return slothlet.handlers.hookManager.remove(filter)},enable:function slothlet_hook_enable(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.enable(filter)},disable:function slothlet_hook_disable(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.disable(filter)},enablePattern:function slothlet_hook_enablePattern(pattern){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.enablePattern(pattern)},disablePattern:function slothlet_hook_disablePattern(pattern){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.disablePattern(pattern)},resetPatternFilter:function slothlet_hook_resetPatternFilter(){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.resetPatternFilter()},list:function slothlet_hook_list(filter={}){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.list(filter)},pin:{get enabled(){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.pinEnforced},enable:function slothlet_hook_pin_enable(){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.setPinEnforced(true)},disable:function slothlet_hook_pin_disable(){if(!slothlet.handlers?.hookManager){throw new slothlet.SlothletError("HOOKS_NOT_INITIALIZED",{validationError:true})}return slothlet.handlers.hookManager.setPinEnforced(false)}}},metadata:{setGlobal:function slothlet_metadata_setGlobal(keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const validateGlobalMetadataKeyPath=keyPath=>{const blocked=new Set(["__proto__","prototype","constructor"]);if(typeof keyPath!=="string"||keyPath.length===0){throw new slothlet.SlothletError("INVALID_METADATA_KEY",{key:keyPath,type:typeof keyPath,expected:"non-empty string"})}const segments=keyPath.split(".");for(const segment of segments){if(!segment||blocked.has(segment)){throw new slothlet.SlothletError("INVALID_METADATA_KEY",{key:keyPath,type:typeof keyPath,expected:"safe dot-notation key without reserved segments"})}}};const normalizeGlobalMetadataObject=(source,prefix="",ancestors=new WeakSet)=>{if(ancestors.has(source)){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"keyOrObj",expected:"acyclic object",received:"circular reference",validationError:true})}ancestors.add(source);try{const normalized={};for(const[key,nestedValue]of Object.entries(source)){const fullKey=prefix?`${prefix}.${key}`:key;validateGlobalMetadataKeyPath(fullKey);const nestedProto=nestedValue&&typeof nestedValue==="object"?Object.getPrototypeOf(nestedValue):null;const isPlainNested=nestedProto===Object.prototype||nestedProto===null;if(nestedValue&&typeof nestedValue==="object"&&!Array.isArray(nestedValue)&&isPlainNested){normalized[key]=normalizeGlobalMetadataObject(nestedValue,fullKey,ancestors);continue}normalized[key]=nestedValue}return normalized}finally{ancestors.delete(source)}};if(keyOrObj&&typeof keyOrObj==="object"&&!Array.isArray(keyOrObj)){const normalizedMetadata=normalizeGlobalMetadataObject(keyOrObj);for(const[key,nestedValue]of Object.entries(normalizedMetadata)){slothlet.handlers.metadata.setGlobalMetadata(key,nestedValue)}return}if(typeof keyOrObj!=="string"){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:"keyOrObj",expected:"string or object",received:typeof keyOrObj,validationError:true})}validateGlobalMetadataKeyPath(keyOrObj);return slothlet.handlers.metadata.setGlobalMetadata(keyOrObj,value)},set:function slothlet_metadata_set(fn,key,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}return slothlet.handlers.metadata.setUserMetadata(fn,key,value)},remove:function slothlet_metadata_remove(fn,key){return slothlet.handlers.metadata.removeUserMetadata(fn,key)},setFor:function slothlet_metadata_setFor(pathOrModuleId,keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.setPathMetadata(resolvedPath,keyOrObj,value)},removeFor:function slothlet_metadata_removeFor(pathOrModuleId,key){if(!slothlet.handlers?.metadata)return;const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.removePathMetadata(resolvedPath,key)},getFor:function slothlet_metadata_getFor(pathOrModuleId){if(!slothlet.handlers?.metadata)return{};const resolvedPath=_resolvePathOrModuleId(slothlet,pathOrModuleId);return slothlet.handlers.metadata.getPathMetadata(resolvedPath)},setForVersion:function slothlet_metadata_setForVersion(logicalPath,versionTag,keyOrObj,value){if(!slothlet.handlers?.metadata){throw new slothlet.SlothletError("METADATA_NOT_AVAILABLE",{handlersKeys:slothlet.handlers?Object.keys(slothlet.handlers).join(", "):"undefined",validationError:true})}const info=slothlet.handlers?.versionManager?.list(logicalPath);if(!info||!info.versions?.[versionTag]){throw new slothlet.SlothletError("VERSION_NOT_FOUND",{version:versionTag,apiPath:logicalPath})}const{moduleID}=info.versions[versionTag];const resolvedPath=_resolvePathOrModuleId(slothlet,moduleID);return slothlet.handlers.metadata.setPathMetadata(resolvedPath,keyOrObj,value)},getForVersion:function slothlet_metadata_getForVersion(logicalPath,versionTag){if(!slothlet.handlers?.metadata)return{};const info=slothlet.handlers?.versionManager?.list(logicalPath);if(!info||!info.versions?.[versionTag])return{};const{moduleID}=info.versions[versionTag];const resolvedPath=_resolvePathOrModuleId(slothlet,moduleID);return slothlet.handlers.metadata.getPathMetadata(resolvedPath)}},scope:this.createScopeFunction(),run:this.createRunFunction(),reload:async(options={})=>{if(!config.api?.mutations?.reload){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"reload",validationError:true})}return slothlet.reload(options)},shutdown:async()=>{let routineError=null;try{await slothlet.handlers.routineManager?.runShutdownModeRoutines()}catch(error){routineError=error}const result=await slothlet.shutdown();if(routineError)throw routineError;return result},owner:{get:apiPath=>{if(slothlet.handlers?.ownership){return slothlet.handlers.ownership.getPathOwnership(apiPath)}return null}},materialize:(()=>{const mgr=slothlet.handlers?.materialize;const getMaterializedState=()=>{enforceInternalPermission("slothlet.materialize.materialized");return mgr?.materialized??false};const getMaterializeStats=()=>{enforceInternalPermission("slothlet.materialize.get");return mgr?mgr.get():{total:0,materialized:0,remaining:0,percentage:100}};const waitForMaterialization=async()=>{enforceInternalPermission("slothlet.materialize.wait");if(!mgr)return;return mgr.wait()};if(!mgr){return Object.freeze({get materialized(){return getMaterializedState()},get:getMaterializeStats,wait:waitForMaterialization})}return Object.freeze({get materialized(){return getMaterializedState()},get:getMaterializeStats,wait:waitForMaterialization})})(),lifecycle:(()=>{const handler=slothlet.handlers?.lifecycle;const noop=()=>{};if(!handler)return{on:noop,off:noop};return{on:handler.on.bind(handler),off:handler.off.bind(handler)}})(),event:(()=>{const handler=slothlet.handlers?.eventManager;if(!handler){const noop=()=>{};return{on:()=>({level:"deny",off:noop}),once:()=>({level:"deny",off:noop}),off:()=>false,emit:async()=>{},rules:{add:noop,remove:noop}}}return{on:handler.on.bind(handler),once:handler.once.bind(handler),off:handler.off.bind(handler),emit:handler.emit.bind(handler),rules:{add:function slothlet_event_rules_add(rule){if(!config.api?.mutations?.events){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.slothlet.event.rules.add",validationError:true})}const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.addEventRule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ruleId=permissionManager.addEventRule(rule,null,null,"runtime");if(slothlet.handlers?.apiManager?.state?.operationHistory){slothlet.handlers.apiManager.state.operationHistory.push({type:"addEventRule",rule,ownerModuleID:null,ruleId,timestamp:Date.now()})}return ruleId},remove:function slothlet_event_rules_remove(ruleId){if(!config.api?.mutations?.events){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.slothlet.event.rules.remove",validationError:true})}const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.removeEventRule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const currentWrapper=slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper;const callerModuleID=currentWrapper?.____slothletInternal?.moduleID??null;const result=slothlet.handlers.permissionManager.removeEventRule(ruleId,callerModuleID);if(result&&slothlet.handlers?.apiManager?.state?.operationHistory){slothlet.handlers.apiManager.state.operationHistory.push({type:"removeEventRule",ruleId,callerModuleID,timestamp:Date.now()})}return result}}}})(),env:slothlet.envSnapshot,versioning:{list:function slothlet_version_list(logicalPath){if(!slothlet.handlers?.versionManager)return void 0;return slothlet.handlers.versionManager.list(logicalPath)},setDefault:function slothlet_version_setDefault(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return;return slothlet.handlers.versionManager.setDefault(logicalPath,versionTag)},unregister:async function slothlet_version_unregister(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return false;const info=slothlet.handlers.versionManager.list(logicalPath);if(!info||!info.versions?.[versionTag])return false;const{moduleID:versionedModuleID}=info.versions[versionTag];await slothlet.handlers.apiManager.removeApiComponent(versionedModuleID);return true},getVersionMetadata:function slothlet_version_getVersionMetadata(logicalPath,versionTag){if(!slothlet.handlers?.versionManager)return void 0;return slothlet.handlers.versionManager.getVersionMetadataByPath(logicalPath,versionTag)},setVersionMetadata:function slothlet_version_setVersionMetadata(logicalPath,versionTag,patch){if(!slothlet.handlers?.versionManager)return;return slothlet.handlers.versionManager.setVersionMetadataByPath(logicalPath,versionTag,patch)}},permissions:{addRule:function slothlet_permissions_addRule(rule){if(!config.api?.mutations?.permissions){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.slothlet.permissions.addRule",validationError:true})}const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.addRule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ruleId=permissionManager.addRule(rule,null);if(slothlet.handlers?.apiManager?.state?.operationHistory){slothlet.handlers.apiManager.state.operationHistory.push({type:"addPermissionRule",rule,ownerModuleID:null,ruleId,timestamp:Date.now()})}return ruleId},removeRule:function slothlet_permissions_removeRule(ruleId){if(!config.api?.mutations?.permissions){throw new slothlet.SlothletError("INVALID_CONFIG_MUTATIONS_DISABLED",{operation:"api.slothlet.permissions.removeRule",validationError:true})}const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.removeRule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const currentWrapper=slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper;const callerModuleID=currentWrapper?.____slothletInternal?.moduleID??null;const result=slothlet.handlers.permissionManager.removeRule(ruleId,callerModuleID);if(result&&slothlet.handlers?.apiManager?.state?.operationHistory){slothlet.handlers.apiManager.state.operationHistory.push({type:"removePermissionRule",ruleId,callerModuleID,timestamp:Date.now()})}return result},self:{access:function slothlet_permissions_self_access(target){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.checkAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const ctx=slothlet.contextManager?.tryGetContext?.();const currentWrapper=slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper;const callerPath=currentWrapper?.____slothletInternal?.apiPath??"";const callerFilePath=currentWrapper?.____slothletInternal?.filePath??null;const runtimeContext=ctx?.context??null;return slothlet.handlers.permissionManager.checkAccess(callerPath,target,callerFilePath,null,runtimeContext)},rules:function slothlet_permissions_self_rules(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForCaller){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const currentWrapper=slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper;const callerPath=currentWrapper?.____slothletInternal?.apiPath??"";return slothlet.handlers.permissionManager.getRulesForCaller(callerPath)}},global:{checkAccess:function slothlet_permissions_global_checkAccess(caller,target){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.checkAccess){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}const runtimeContext=slothlet.contextManager?.tryGetContext?.()?.context??null;return slothlet.handlers.permissionManager.checkAccess(caller,target,null,null,runtimeContext)},rulesForPath:function slothlet_permissions_global_rulesForPath(path){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesForPath){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return slothlet.handlers.permissionManager.getRulesForPath(path)},rulesByModule:function slothlet_permissions_global_rulesByModule(moduleID){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.getRulesByModule){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return slothlet.handlers.permissionManager.getRulesByModule(moduleID)}},control:{get enabled(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.isEnabled){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return permissionManager.isEnabled()},enable:function slothlet_permissions_control_enable(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.enable){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.enable()},disable:function slothlet_permissions_control_disable(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.disable){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.disable()},get readGatingEnabled(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.isReadGatingEnabled){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return permissionManager.isReadGatingEnabled()},readGating:function slothlet_permissions_control_readGating(value){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.setReadGating){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.setReadGating(value)},seal:function slothlet_permissions_control_seal(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.seal){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}slothlet.handlers.permissionManager.seal()},get sealed(){const permissionManager=slothlet.handlers?.permissionManager;if(!permissionManager?.isSealed){throw new slothlet.SlothletError("PERMISSION_MANAGER_NOT_AVAILABLE",{validationError:true})}return permissionManager.isSealed()}}}};if(!config.hook?.enabled&&config.diagnostics!==true){delete namespace.hooks}if(config.diagnostics===true){namespace.diag={describe:(showAll=false)=>{if(showAll){return{...userApi}}return Reflect.ownKeys(userApi)},reference:slothlet.reference||null,context:slothlet.context||{},inspect:()=>{return slothlet.getDiagnostics()},getAPI:()=>{return slothlet.getAPI()},getOwnership:()=>{return slothlet.getOwnership()},owner:{get:apiPath=>{if(slothlet.handlers?.ownership){return slothlet.handlers.ownership.getPathOwnership(apiPath)}return null}},caches:{get:()=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.getCacheDiagnostics()}return{totalCaches:0,caches:[]}},getAllModuleIDs:()=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.getAllModuleIDs()}return[]},has:moduleID=>{if(slothlet.handlers?.apiCacheManager){return slothlet.handlers.apiCacheManager.has(moduleID)}return false}},SlothletWarning:slothlet.SlothletWarning,hook:slothlet.handlers?.hookManager?{get enabled(){return slothlet.handlers.hookManager.enabled},compilePattern:pattern=>{return slothlet.handlers.hookManager.getCompilePatternForDiagnostics()(pattern)}}:void 0}}return createInternalRouteProxy(namespace,"slothlet")}createShutdownFunction(){const slothlet=this.slothlet;const shutdownFunction={shutdown:async()=>{let routineError=null;try{await slothlet.handlers.routineManager?.runShutdownModeRoutines()}catch(error){routineError=error}if(slothlet.userHooks?.shutdown&&typeof slothlet.userHooks.shutdown==="function"){await slothlet.userHooks.shutdown()}const result=await slothlet.shutdown();if(routineError)throw routineError;return result}}.shutdown;return shutdownFunction}createRunFunction(){const slothlet=this.slothlet;const scopeFunc=this.createScopeFunction();const runFunction={run:async(contextData,callback,...args)=>{if(slothlet.config.scope===false){throw new slothlet.SlothletError("SCOPE_DISABLED",{},null,{validationError:true})}if(!contextData||typeof contextData!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_CONTEXT",{received:typeof contextData},null,{validationError:true})}if(typeof callback!=="function"){throw new slothlet.SlothletError("SCOPE_INVALID_CALLBACK",{received:typeof callback},null,{validationError:true})}return scopeFunc({context:contextData,fn:callback,args,merge:slothlet.config.scope?.merge||"shallow",isolation:slothlet.config.scope?.isolation||"partial"})}}.run;return runFunction}createScopeFunction(){const slothlet=this.slothlet;const scopeFunction={scope:async options=>{if(slothlet.config.scope===false){throw new slothlet.SlothletError("SCOPE_DISABLED",{},null,{validationError:true})}if(!options||typeof options!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_OPTIONS",{received:typeof options},null,{validationError:true})}if(!options.fn||typeof options.fn!=="function"){throw new slothlet.SlothletError("SCOPE_INVALID_FN",{received:typeof options?.fn},null,{validationError:true})}if(!options.context||typeof options.context!=="object"){throw new slothlet.SlothletError("SCOPE_INVALID_CONTEXT_OBJECT",{received:typeof options?.context},null,{validationError:true})}const{context:contextData,fn,args=[],merge="shallow",isolation,protect,owners}=options;if(merge!=="shallow"&&merge!=="deep"){throw new slothlet.SlothletError("SCOPE_INVALID_MERGE_STRATEGY",{merge},null,{validationError:true})}const isolationMode=isolation||slothlet.config.scope?.isolation||"partial";if(isolationMode!=="partial"&&isolationMode!=="full"){throw new slothlet.SlothletError("SCOPE_INVALID_ISOLATION_MODE",{isolationMode},null,{validationError:true})}if(protect!==void 0&&(!Array.isArray(protect)||protect.some(k=>typeof k!=="string"))){throw new slothlet.SlothletError("SCOPE_INVALID_PROTECT",{received:describeScopeReceived(protect)},null,{validationError:true})}if(owners!==void 0){if(typeof owners!=="object"||owners===null||Array.isArray(owners)){throw new slothlet.SlothletError("SCOPE_INVALID_OWNERS",{received:describeScopeReceived(owners)},null,{validationError:true})}for(const[key,owner]of Object.entries(owners)){if(typeof owner!=="string"||owner.length===0){throw new slothlet.SlothletError("INVALID_ARGUMENT",{argument:`owners.${key}`,expected:"non-empty string",received:typeof owner==="string"?"empty string":typeof owner,validationError:true})}}}const buildContextOwners=parentOwners=>{const base=parentOwners??null;let child=base;const claim=(key,owner)=>{const existing=child&&Object.prototype.hasOwnProperty.call(child,key)?child[key]:void 0;if(existing!==void 0&&existing!==owner){throw new slothlet.SlothletError("CONTEXT_KEY_OWNED",{key:String(key)},null,{validationError:true})}if(child===base)child=base?Object.assign(Object.create(null),base):Object.create(null);child[key]=owner};if(Array.isArray(protect))for(const k of protect)claim(k,PROTECT_SENTINEL);if(owners&&typeof owners==="object")for(const[k,o]of Object.entries(owners))claim(k,o);return child};const contextManager=slothlet.contextManager;if(!contextManager){throw new slothlet.SlothletError("NO_CONTEXT_MANAGER",{validationError:true})}const{utilities}=slothlet.helpers;if(contextManager.constructor.name==="LiveContextManager"){let currentStore=null;const currentID=contextManager.currentInstanceID;if(currentID){const activeStore=contextManager.instances.get(currentID);const isOurContext=currentID===slothlet.instanceID||activeStore?.parentInstanceID===slothlet.instanceID||currentID.startsWith(slothlet.instanceID+"__run_");if(isOurContext){currentStore=activeStore}}if(!currentStore){currentStore=contextManager.instances.get(slothlet.instanceID)}if(!currentStore){throw new slothlet.SlothletError("CONTEXT_NOT_FOUND",{instanceID:slothlet.instanceID,availableInstances:[...contextManager.instances.keys()].join(", ")||"none",validationError:true})}let mergedContext;if(merge==="deep"){mergedContext=utilities.deepMerge(currentStore.context,contextData);mergedContext=utilities.deepClone(mergedContext)}else{const clonedParent=utilities.deepClone(currentStore.context);mergedContext={...clonedParent,...contextData}}const childInstanceID=`${slothlet.instanceID}__run_${Date.now()}_${Math.random().toString(36).slice(2,9)}`;const childStore={instanceID:childInstanceID,context:mergedContext,self:isolationMode==="full"?makeCopyOnWriteSelf(currentStore.self):currentStore.self,config:currentStore.config,createdAt:currentStore.createdAt,parentInstanceID:slothlet.instanceID,currentWrapper:currentStore.currentWrapper,callerWrapper:currentStore.callerWrapper,slothlet:currentStore.slothlet,__contextOwners:buildContextOwners(currentStore.__contextOwners)};if(currentStore[TRUSTED_ROOT]===true){Object.defineProperty(childStore,TRUSTED_ROOT,{value:true,configurable:true})}contextManager.instances.set(childInstanceID,childStore);const previousInstanceID=contextManager.currentInstanceID;try{contextManager.currentInstanceID=childInstanceID;return await fn(...args)}finally{contextManager.currentInstanceID=previousInstanceID;contextManager.instances.delete(childInstanceID)}}if(contextManager.constructor.name==="AsyncContextManager"){let currentStore=null;const activeStore=contextManager.tryGetContext();if(activeStore){const isOurContext=activeStore.instanceID===slothlet.instanceID||activeStore.parentInstanceID===slothlet.instanceID||activeStore.instanceID.startsWith(slothlet.instanceID+"__run_");if(isOurContext){currentStore=activeStore}}if(!currentStore){const baseStore=contextManager.instances.get(slothlet.instanceID);if(!baseStore){throw new slothlet.SlothletError("CONTEXT_NOT_FOUND",{instanceID:slothlet.instanceID,availableInstances:[...contextManager.instances.keys()].join(", ")||"none",validationError:true})}currentStore=baseStore}let mergedContext;if(merge==="deep"){mergedContext=utilities.deepMerge(currentStore.context,contextData);mergedContext=utilities.deepClone(mergedContext)}else{const clonedParent=utilities.deepClone(currentStore.context);mergedContext={...clonedParent,...contextData}}const childInstanceID=`${slothlet.instanceID}__run_${Date.now()}_${Math.random().toString(36).slice(2,9)}`;const childStore={instanceID:childInstanceID,context:mergedContext,self:isolationMode==="full"?makeCopyOnWriteSelf(currentStore.self):currentStore.self,config:currentStore.config,createdAt:currentStore.createdAt,parentInstanceID:slothlet.instanceID,currentWrapper:currentStore.currentWrapper,callerWrapper:currentStore.callerWrapper,slothlet:currentStore.slothlet,__contextOwners:buildContextOwners(currentStore.__contextOwners)};if(currentStore[TRUSTED_ROOT]===true){Object.defineProperty(childStore,TRUSTED_ROOT,{value:true,configurable:true})}contextManager.instances.set(childInstanceID,childStore);try{return await contextManager.als.run(childStore,async()=>{return await fn(...args)})}finally{contextManager.instances.delete(childInstanceID)}}throw new slothlet.SlothletError("UNSUPPORTED_CONTEXT_MANAGER",{manager:contextManager.constructor.name,validationError:true})}}.scope;return scopeFunction}createDestroyFunction(api){const slothlet=this.slothlet;const destroyFunction={destroy:async()=>{let routineError=null;try{await slothlet.handlers.routineManager?.runDestroyModeRoutines()}catch(error){routineError=error}if(slothlet.userHooks?.destroy&&typeof slothlet.userHooks.destroy==="function"){await slothlet.userHooks.destroy()}try{if(api&&typeof api.shutdown==="function"){await api.shutdown()}else{await slothlet.shutdown()}}catch(error){if(!routineError)routineError=error}slothlet.isDestroyed=true;const objectsToClear=[api,slothlet.api].filter(obj=>obj&&typeof obj==="object");for(const obj of objectsToClear){const keys=Object.keys(obj);for(const key of keys){try{delete obj[key]}catch(_){}}}slothlet.api=null;if(routineError)throw routineError}}.destroy;return destroyFunction}attachBuiltins(userApi,builtins){Object.defineProperty(userApi,"slothlet",{value:builtins.slothlet,enumerable:true,writable:false,configurable:true});Object.defineProperty(userApi,"shutdown",{value:builtins.shutdown,enumerable:true,writable:false,configurable:true});if(builtins.destroy!==null){Object.defineProperty(userApi,"destroy",{value:builtins.destroy,enumerable:true,writable:false,configurable:true})}}}export{ApiBuilder};
@@ -0,0 +1,17 @@
1
+ /*
2
+ Copyright 2026 CLDMV/Shinrai
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import{ComponentBase}from"#factories/component-base";class EventManager extends ComponentBase{static slothletProperty="eventManager";#subscribers=new Map;constructor(slothlet){super(slothlet);this.#subscribers=new Map}get#permissions(){return this.slothlet.handlers?.permissionManager??null}on(event,listener,options={}){if(typeof event!=="string"||!event){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"event",expected:"a non-empty string",received:typeof event})}if(typeof listener!=="function"){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"listener",expected:"a function",received:typeof listener})}const ownerWrapper=this.slothlet.contextManager?.getCallerIdentity?.()?.currentWrapper??null;const subscriberPath=ownerWrapper?.____slothletInternal?.apiPath??null;const sub={listener,once:options.once===true,subscriberPath,ownerWrapper,level:null,levelEpoch:-1};const level=this.#levelFor(sub,event);if(level==="deny"){return{level,off:()=>{}}}if(!this.#subscribers.has(event))this.#subscribers.set(event,new Set);this.#subscribers.get(event).add(sub);return{level,off:()=>this.#removeSub(event,sub)}}once(event,listener,options={}){return this.on(event,listener,{...options,once:true})}off(event,listener){const set=this.#subscribers.get(event);if(!set)return false;for(const sub of set){if(sub.listener===listener){this.#removeSub(event,sub);return true}}return false}async emit(event,payload){if(typeof event!=="string"||!event){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"event",expected:"a non-empty string",received:typeof event})}const set=this.#subscribers.get(event);if(!set||set.size===0)return;const meta={event,at:Date.now(),instanceID:this.instanceID};const cm=this.slothlet.contextManager;const promises=[];for(const sub of[...set]){const level=this.#levelFor(sub,event);if(level==="deny"){this.#removeSub(event,sub);continue}const callArgs=level==="allow"?[payload,meta]:[void 0,meta];if(sub.once)this.#removeSub(event,sub);try{const result=sub.ownerWrapper&&cm?.runInContext?cm.runInContext(this.instanceID,sub.listener,void 0,callArgs,sub.ownerWrapper,true):sub.listener(...callArgs);if(result&&typeof result.then==="function"){promises.push(result.catch(error=>this.#warnListener(event,error)))}}catch(error){this.#warnListener(event,error)}}if(promises.length>0)await Promise.all(promises)}#levelFor(sub,event){const pm=this.#permissions;if(!pm)return"allow";const conditional=pm.hasConditionalEventRules;const epoch=pm.eventRulesEpoch;if(!conditional&&sub.level!=null&&sub.levelEpoch===epoch){return sub.level}const runtimeContext=this.slothlet.contextManager?.tryGetContext?.()??null;const level=pm.resolveEventLevel(sub.subscriberPath,event,runtimeContext);sub.level=conditional?null:level;sub.levelEpoch=epoch;return level}#removeSub(event,sub){const set=this.#subscribers.get(event);if(!set)return;set.delete(sub);if(set.size===0)this.#subscribers.delete(event)}#warnListener(event,error){if(this.____config?.silent)return;new this.SlothletWarning("WARNING_EVENT_HANDLER_ERROR",{event},error)}shutdown(){this.#subscribers.clear()}}export{EventManager};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{ComponentBase}from"#factories/component-base";import{SlothletError}from"@cldmv/slothlet/errors";import{sortModules}from"@cldmv/slothlet/helpers/module-sort";const DEFAULT_MODULE_COLLISION_MODE="merge";class ModuleManager extends ComponentBase{static slothletProperty="moduleManager";#cache=new Map;#mounted=new Map;constructor(slothlet){super(slothlet)}async discover(options={}){await this.#emit("modules:discover-start",{scanRoot:options.scanRoot,options});const{discoverModules}=await import("@cldmv/slothlet/helpers/module-discovery");const found=await discoverModules(options);this.#cache.clear();for(const result of found){const key=`${result.packageName}@${result.manifest.version}`;this.#cache.set(key,result)}await this.#emit("modules:discover-complete",{found,stale:this.getStaleMounts()});return found}sort(results,comparator){return sortModules(results,comparator)}getDiscoveryCache(){return[...this.#cache.values()]}clearDiscoveryCache(){this.#cache.clear()}getStaleMounts(){const stale=[];for(const[key,mountResult]of this.#mounted){if(!this.#cache.has(key)){stale.push(mountResult)}}return stale}async addModule(nameOrResult,options={}){const collisionMode=options.collisionMode??DEFAULT_MODULE_COLLISION_MODE;const discoverResult=await this.#resolveToDiscoverResult(nameOrResult,options);await this.#emit("modules:mount-start",{items:[nameOrResult],options});const mountResult=await this.#mountSingle(discoverResult,collisionMode,null);await this.slothlet.handlers.routineManager?.rebuildStacks(this.slothlet.api);await this.#emit("modules:loaded",{mounted:[mountResult]});return mountResult}async addModules(items,options={}){if(!Array.isArray(items)){throw new SlothletError("INVALID_ARGUMENT",{argument:"items",expected:"array of string|DiscoverResult",received:typeof items},null,{validationError:true})}const onFailure=options.onFailure??"throw";const concurrency=Math.max(1,Number(options.concurrency)||1);const collisionMode=options.collisionMode??DEFAULT_MODULE_COLLISION_MODE;if(!["throw","rollback","best-effort"].includes(onFailure)){throw new SlothletError("INVALID_ARGUMENT",{argument:"onFailure",expected:"throw|rollback|best-effort",received:String(onFailure)},null,{validationError:true})}const resolved=[];for(const item of items){resolved.push(await this.#resolveToDiscoverResult(item,options))}const versionConfigs=this.#buildVersionConfigs(resolved);await this.#emit("modules:mount-start",{items,options});let outcome;if(concurrency===1){outcome=await this.#mountSerial(resolved,collisionMode,onFailure,versionConfigs)}else{outcome=await this.#mountParallel(resolved,collisionMode,onFailure,concurrency,versionConfigs)}await this.slothlet.handlers.routineManager?.rebuildStacks(this.slothlet.api);const loadedPayload=Array.isArray(outcome)?{mounted:outcome}:{mounted:outcome.mounted,failed:outcome.failed};await this.#emit("modules:loaded",loadedPayload);return outcome}async removeModule(name,opts={}){const matches=[];for(const[key,mountResult]of this.#mounted){if(mountResult.packageName!==name)continue;if(opts.version!==void 0&&mountResult.discoverResult.manifest.version!==opts.version)continue;matches.push({key,mountResult})}if(matches.length===0)return false;for(const{key,mountResult}of matches){await this.slothlet.handlers.apiManager.removeApiComponent(mountResult.moduleID);this.#mounted.delete(key)}return true}async#resolveToDiscoverResult(arg,options){if(arg&&typeof arg==="object"&&typeof arg.packageName==="string"){return arg}if(typeof arg!=="string"){throw new SlothletError("INVALID_ARGUMENT",{argument:"module name or DiscoverResult",expected:"string or DiscoverResult object",received:typeof arg},null,{validationError:true})}if(this.#cache.size===0){await this.discover(options.discover??{})}const candidates=[];for(const result of this.#cache.values()){if(result.packageName!==arg)continue;if(options.version!==void 0&&result.manifest.version!==options.version)continue;candidates.push(result)}if(candidates.length===0){throw new SlothletError("MODULE_PACKAGE_NOT_FOUND",{packageName:arg,hint:"addModule(name) requires the package to be installed under one of the scanned roots. Run discover() first, or pass a DiscoverResult object directly."},null,{validationError:true})}if(candidates.length>1){throw new SlothletError("INVALID_ARGUMENT",{argument:"version",expected:"version disambiguator (multi-version cache hit)",received:"undefined"},null,{validationError:true})}return candidates[0]}async#mountSingle(discoverResult,collisionMode,versionConfig){const mountPathDotted=discoverResult.mountPath.join(".");const effectiveMountPath=versionConfig?.version?`${versionConfig.version}.${mountPathDotted}`:mountPathDotted;if(collisionMode==="error"){const existing=this.#findExactMountAt(effectiveMountPath);if(existing){throw new SlothletError("MODULE_MOUNT_COLLISION",{packageName:discoverResult.packageName,mountPath:effectiveMountPath,existingModuleID:existing.moduleID,collisionMode},null,{validationError:true})}}const version=discoverResult.manifest.version;const underlyingCollisionMode=collisionMode==="error"?"merge":collisionMode;const moduleID=await this.slothlet.handlers.apiManager.addApiComponent({apiPath:mountPathDotted,folderPath:discoverResult.apiDir,options:{collisionMode:underlyingCollisionMode,metadata:{_module:{manifest:discoverResult.manifest}}},versionConfig:versionConfig??null});const result={packageName:discoverResult.packageName,mountPath:effectiveMountPath,moduleID,discoverResult,versionConfig:versionConfig??null};this.#mounted.set(`${discoverResult.packageName}@${version}`,result);await this.#emit("modules:mount-complete",{name:discoverResult.packageName,version,mountPath:effectiveMountPath,moduleID});return result}#buildVersionConfigs(resolved){const byName=new Map;for(const r of resolved){const list=byName.get(r.packageName)??[];list.push(r);byName.set(r.packageName,list)}const highestByName=new Map;for(const[name,group]of byName){if(group.length<2)continue;const versions=group.map(r=>r.manifest.version);highestByName.set(name,pickHighestSemver(versions))}const configs=new Map;for(let i=0;i<resolved.length;i++){const r=resolved[i];const group=byName.get(r.packageName);if(!group||group.length<2){configs.set(i,null);continue}const versionTag=semverToTag(r.manifest.version);const isDefault=r.manifest.version===highestByName.get(r.packageName);configs.set(i,{version:versionTag,default:isDefault})}return configs}async#emit(event,payload){const lifecycle=this.slothlet?.handlers?.lifecycle;if(lifecycle&&typeof lifecycle.emit==="function"){await lifecycle.emit(event,payload)}}#findExactMountAt(dottedPath){const history=this.slothlet?.handlers?.apiManager?.state?.addHistory;if(!Array.isArray(history))return null;for(let i=history.length-1;i>=0;i--){const entry=history[i];if(entry?.apiPath===dottedPath)return entry}return null}async#mountSerial(resolved,collisionMode,onFailure,versionConfigs){const mounted=[];const failed=[];for(let i=0;i<resolved.length;i++){const item=resolved[i];try{const mountResult=await this.#mountSingle(item,collisionMode,versionConfigs?.get(i)??null);mounted.push(mountResult)}catch(err){if(onFailure==="throw"){throw err}if(onFailure==="rollback"){await this.#rollback(mounted);throw err}failed.push({item,error:err})}}if(onFailure==="best-effort"){return{mounted,failed}}return mounted}async#mountParallel(resolved,collisionMode,onFailure,concurrency,versionConfigs){const mounted=[];const failed=[];let firstError=null;let nextIndex=0;const worker=async()=>{while(true){if(firstError&&onFailure!=="best-effort")return;const i=nextIndex++;if(i>=resolved.length)return;const item=resolved[i];try{const mountResult=await this.#mountSingle(item,collisionMode,versionConfigs?.get(i)??null);mounted.push(mountResult)}catch(err){if(onFailure==="best-effort"){failed.push({item,error:err})}else if(!firstError){firstError=err}}}};const workers=Array.from({length:Math.min(concurrency,resolved.length)},()=>worker());await Promise.all(workers);if(firstError){if(onFailure==="rollback"){await this.#rollback(mounted)}throw firstError}if(onFailure==="best-effort"){return{mounted,failed}}return mounted}async#rollback(mounted){for(const m of mounted){try{await this.slothlet.handlers.apiManager.removeApiComponent(m.moduleID);this.#mounted.delete(`${m.packageName}@${m.discoverResult.manifest.version}`)}catch{}}}}function semverToTag(version){const m=/^(\d+)/.exec(String(version));return m?`v${m[1]}`:`v${version}`}function pickHighestSemver(versions){if(versions.length===1)return versions[0];const segs=v=>String(v).split(/[.\-+]/).map(p=>{const n=Number.parseInt(p,10);return Number.isFinite(n)?n:0});let best=versions[0];let bestSegs=segs(best);for(let i=1;i<versions.length;i++){const candidate=versions[i];const candidateSegs=segs(candidate);const len=Math.max(candidateSegs.length,bestSegs.length);let candidateWins=false;for(let j=0;j<len;j++){const a=candidateSegs[j]??0;const b=bestSegs[j]??0;if(a>b){candidateWins=true;break}if(a<b)break}if(candidateWins){best=candidate;bestSegs=candidateSegs}}return best}export{ModuleManager};
17
+ import{ComponentBase}from"#factories/component-base";import{SlothletError}from"@cldmv/slothlet/errors";import{sortModules}from"@cldmv/slothlet/helpers/module-sort";const DEFAULT_MODULE_COLLISION_MODE="merge";class ModuleManager extends ComponentBase{static slothletProperty="moduleManager";#cache=new Map;#mounted=new Map;constructor(slothlet){super(slothlet)}async discover(options={}){await this.#emit("modules:discover-start",{scanRoot:options.scanRoot,options});const{discoverModules}=await import("@cldmv/slothlet/helpers/module-discovery");const found=await discoverModules(options);this.#cache.clear();for(const result of found){const key=`${result.packageName}@${result.manifest.version}`;this.#cache.set(key,result)}await this.#emit("modules:discover-complete",{found,stale:this.getStaleMounts()});return found}sort(results,comparator){return sortModules(results,comparator)}getDiscoveryCache(){return[...this.#cache.values()]}clearDiscoveryCache(){this.#cache.clear()}getStaleMounts(){const stale=[];for(const[key,mountResult]of this.#mounted){if(!this.#cache.has(key)){stale.push(mountResult)}}return stale}async addModule(nameOrResult,options={}){const collisionMode=options.collisionMode??DEFAULT_MODULE_COLLISION_MODE;const discoverResult=await this.#resolveToDiscoverResult(nameOrResult,options);await this.#emit("modules:mount-start",{items:[nameOrResult],options});const mountResult=await this.#mountSingle(discoverResult,collisionMode,null);await this.slothlet.handlers.routineManager?.rebuildStacks(this.slothlet.api);await this.#emit("modules:loaded",{mounted:[mountResult]});return mountResult}async addModules(items,options={}){if(!Array.isArray(items)){throw new SlothletError("INVALID_ARGUMENT",{argument:"items",expected:"array of string|DiscoverResult",received:typeof items},null,{validationError:true})}const onFailure=options.onFailure??"throw";const concurrency=Math.max(1,Number(options.concurrency)||1);const collisionMode=options.collisionMode??DEFAULT_MODULE_COLLISION_MODE;if(!["throw","rollback","best-effort"].includes(onFailure)){throw new SlothletError("INVALID_ARGUMENT",{argument:"onFailure",expected:"throw|rollback|best-effort",received:String(onFailure)},null,{validationError:true})}const resolved=[];for(const item of items){resolved.push(await this.#resolveToDiscoverResult(item,options))}const versionConfigs=this.#buildVersionConfigs(resolved);await this.#emit("modules:mount-start",{items,options});let outcome;if(concurrency===1){outcome=await this.#mountSerial(resolved,collisionMode,onFailure,versionConfigs)}else{outcome=await this.#mountParallel(resolved,collisionMode,onFailure,concurrency,versionConfigs)}await this.slothlet.handlers.routineManager?.rebuildStacks(this.slothlet.api);const loadedPayload=Array.isArray(outcome)?{mounted:outcome}:{mounted:outcome.mounted,failed:outcome.failed};await this.#emit("modules:loaded",loadedPayload);return outcome}async removeModule(name,opts={}){const matches=[];for(const[key,mountResult]of this.#mounted){if(mountResult.packageName!==name)continue;if(opts.version!==void 0&&mountResult.discoverResult.manifest.version!==opts.version)continue;matches.push({key,mountResult})}if(matches.length===0)return false;for(const{key,mountResult}of matches){await this.slothlet.handlers.apiManager.removeApiComponent(mountResult.moduleID);this.#mounted.delete(key)}return true}async#resolveToDiscoverResult(arg,options){if(arg&&typeof arg==="object"&&typeof arg.packageName==="string"){return arg}if(typeof arg!=="string"){throw new SlothletError("INVALID_ARGUMENT",{argument:"module name or DiscoverResult",expected:"string or DiscoverResult object",received:typeof arg},null,{validationError:true})}if(this.#cache.size===0){await this.discover(options.discover??{})}const candidates=[];for(const result of this.#cache.values()){if(result.packageName!==arg)continue;if(options.version!==void 0&&result.manifest.version!==options.version)continue;candidates.push(result)}if(candidates.length===0){throw new SlothletError("MODULE_PACKAGE_NOT_FOUND",{packageName:arg,hint:"addModule(name) requires the package to be installed under one of the scanned roots. Run discover() first, or pass a DiscoverResult object directly."},null,{validationError:true})}if(candidates.length>1){throw new SlothletError("INVALID_ARGUMENT",{argument:"version",expected:"version disambiguator (multi-version cache hit)",received:"undefined"},null,{validationError:true})}return candidates[0]}async#mountSingle(discoverResult,collisionMode,versionConfig){const mountPathDotted=discoverResult.mountPath.join(".");const effectiveMountPath=versionConfig?.version?`${versionConfig.version}.${mountPathDotted}`:mountPathDotted;if(collisionMode==="error"){const existing=this.#findExactMountAt(effectiveMountPath);if(existing){throw new SlothletError("MODULE_MOUNT_COLLISION",{packageName:discoverResult.packageName,mountPath:effectiveMountPath,existingModuleID:existing.moduleID,collisionMode},null,{validationError:true})}}const version=discoverResult.manifest.version;const underlyingCollisionMode=collisionMode==="error"?"merge":collisionMode;const moduleID=await this.slothlet.handlers.apiManager.addApiComponent({apiPath:mountPathDotted,folderPath:discoverResult.apiDir,options:{collisionMode:underlyingCollisionMode,metadata:{_module:{manifest:discoverResult.manifest}}},versionConfig:versionConfig??null});const permissionManager=this.slothlet.handlers?.permissionManager;if(permissionManager){const manifest=discoverResult.manifest;if(Array.isArray(manifest.permissions)){for(const rule of manifest.permissions){permissionManager.addRule(rule,moduleID,null,"manifest")}}if(Array.isArray(manifest.events)){for(const rule of manifest.events){permissionManager.addEventRule(rule,moduleID,null,"manifest")}}}const result={packageName:discoverResult.packageName,mountPath:effectiveMountPath,moduleID,discoverResult,versionConfig:versionConfig??null};this.#mounted.set(`${discoverResult.packageName}@${version}`,result);await this.#emit("modules:mount-complete",{name:discoverResult.packageName,version,mountPath:effectiveMountPath,moduleID});return result}#buildVersionConfigs(resolved){const byName=new Map;for(const r of resolved){const list=byName.get(r.packageName)??[];list.push(r);byName.set(r.packageName,list)}const highestByName=new Map;for(const[name,group]of byName){if(group.length<2)continue;const versions=group.map(r=>r.manifest.version);highestByName.set(name,pickHighestSemver(versions))}const configs=new Map;for(let i=0;i<resolved.length;i++){const r=resolved[i];const group=byName.get(r.packageName);if(!group||group.length<2){configs.set(i,null);continue}const versionTag=semverToTag(r.manifest.version);const isDefault=r.manifest.version===highestByName.get(r.packageName);configs.set(i,{version:versionTag,default:isDefault})}return configs}async#emit(event,payload){const lifecycle=this.slothlet?.handlers?.lifecycle;if(lifecycle&&typeof lifecycle.emit==="function"){await lifecycle.emit(event,payload)}}#findExactMountAt(dottedPath){const history=this.slothlet?.handlers?.apiManager?.state?.addHistory;if(!Array.isArray(history))return null;for(let i=history.length-1;i>=0;i--){const entry=history[i];if(entry?.apiPath===dottedPath)return entry}return null}async#mountSerial(resolved,collisionMode,onFailure,versionConfigs){const mounted=[];const failed=[];for(let i=0;i<resolved.length;i++){const item=resolved[i];try{const mountResult=await this.#mountSingle(item,collisionMode,versionConfigs?.get(i)??null);mounted.push(mountResult)}catch(err){if(onFailure==="throw"){throw err}if(onFailure==="rollback"){await this.#rollback(mounted);throw err}failed.push({item,error:err})}}if(onFailure==="best-effort"){return{mounted,failed}}return mounted}async#mountParallel(resolved,collisionMode,onFailure,concurrency,versionConfigs){const mounted=[];const failed=[];let firstError=null;let nextIndex=0;const worker=async()=>{while(true){if(firstError&&onFailure!=="best-effort")return;const i=nextIndex++;if(i>=resolved.length)return;const item=resolved[i];try{const mountResult=await this.#mountSingle(item,collisionMode,versionConfigs?.get(i)??null);mounted.push(mountResult)}catch(err){if(onFailure==="best-effort"){failed.push({item,error:err})}else if(!firstError){firstError=err}}}};const workers=Array.from({length:Math.min(concurrency,resolved.length)},()=>worker());await Promise.all(workers);if(firstError){if(onFailure==="rollback"){await this.#rollback(mounted)}throw firstError}if(onFailure==="best-effort"){return{mounted,failed}}return mounted}async#rollback(mounted){for(const m of mounted){try{await this.slothlet.handlers.apiManager.removeApiComponent(m.moduleID);this.#mounted.delete(`${m.packageName}@${m.discoverResult.manifest.version}`)}catch{}}}}function semverToTag(version){const m=/^(\d+)/.exec(String(version));return m?`v${m[1]}`:`v${version}`}function pickHighestSemver(versions){if(versions.length===1)return versions[0];const segs=v=>String(v).split(/[.\-+]/).map(p=>{const n=Number.parseInt(p,10);return Number.isFinite(n)?n:0});let best=versions[0];let bestSegs=segs(best);for(let i=1;i<versions.length;i++){const candidate=versions[i];const candidateSegs=segs(candidate);const len=Math.max(candidateSegs.length,bestSegs.length);let candidateWins=false;for(let j=0;j<len;j++){const a=candidateSegs[j]??0;const b=bestSegs[j]??0;if(a>b){candidateWins=true;break}if(a<b)break}if(candidateWins){best=candidate;bestSegs=candidateSegs}}return best}export{ModuleManager};
@@ -14,4 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import{path}from"@cldmv/slothlet/helpers/platform";import{ComponentBase}from"#factories/component-base";import{compilePattern}from"@cldmv/slothlet/helpers/pattern-matcher";import{translate}from"@cldmv/slothlet/i18n";let ruleIdCounter=0;const HOOK_TARGET_TYPES=new Set(["before","after","always","error","hook"]);function runtime_isPrivateName(targetPath){if(typeof targetPath!=="string"||targetPath.length===0)return false;const cut=targetPath.lastIndexOf(".");if(cut<0)return false;return targetPath.charCodeAt(cut+1)===95}function runtime_sameModuleDir(callerFilePath,targetFilePath){return path.dirname(callerFilePath)===path.dirname(targetFilePath)}class PermissionManager extends ComponentBase{static slothletProperty="permissionManager";#rules=new Map;#defaultPolicy="allow";#enabled=false;#audit="default";#readGating=false;#capture=true;#privateHost="deny";#sealed=false;#resolvedCache=new Map;#compiledCache=new Map;constructor(slothlet){super(slothlet);const permConfig=slothlet.config?.permissions;if(permConfig){this.#defaultPolicy=permConfig.defaultPolicy||"allow";this.#enabled=permConfig.enabled!==false;this.#audit=permConfig.audit||"default";this.#readGating=permConfig.readGating!==false;this.#capture=permConfig.references?.capture!==false;this.#privateHost=permConfig.private?.host==="allow"?"allow":"deny";if(Array.isArray(permConfig.rules)){for(const rule of permConfig.rules){this.addRule(rule,null)}}}this.addRule({caller:"**",target:"slothlet.permissions.control.**",effect:"deny"},"__builtin__");this.addRule({caller:"**",target:"slothlet.lockCaller",effect:"allow"},"__builtin__");this.addRule({caller:"**",target:"slothlet.bind",effect:"allow"},"__builtin__");this.addRule({caller:"**",target:"slothlet.hook.**",effect:"deny"},"__builtin__");this.addRule({caller:"**",target:"slothlet.hook.list",effect:"allow"},"__builtin__");this.addRule({caller:"**",target:"slothlet.hook.on",effect:"allow"},"__builtin__")}addRule(rule,ownerModuleID=null,ruleId=null){this.#assertNotSealed();this.#validateRule(rule);const id=ruleId||`perm-${++ruleIdCounter}`;const hookTarget=this.#parseHookTarget(rule.target);const entry={id,caller:rule.caller,target:rule.target,effect:rule.effect,condition:rule.condition??null,hookType:hookTarget?hookTarget.hookType:null,hookPathPattern:hookTarget?hookTarget.pathPattern:null,ownerModuleID,registeredAt:Date.now()};this.#rules.set(id,entry);this.#clearCache();this.debug("permissions",{key:"DEBUG_PERMISSION_RULE_ADDED",ruleId:id,caller:rule.caller,target:rule.target,effect:rule.effect,ownerModuleID});return id}removeRule(ruleId,callerModuleID=null){this.#assertNotSealed();const entry=this.#rules.get(ruleId);if(!entry)return false;if(callerModuleID&&entry.ownerModuleID&&callerModuleID===entry.ownerModuleID){throw new this.SlothletError("PERMISSION_SELF_MODIFY",{ruleId,moduleID:callerModuleID})}this.#rules.delete(ruleId);this.#clearCache();this.debug("permissions",{key:"DEBUG_PERMISSION_RULE_REMOVED",ruleId,caller:entry.caller,target:entry.target,effect:entry.effect});return true}checkAccess(callerPath,targetPath,callerFilePath=null,targetFilePath=null,runtimeContext=null,options=null){const normalizedOptions=options==null?{}:options;if(typeof normalizedOptions!=="object"||Array.isArray(normalizedOptions)){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"options",expected:"object with optional boolean useCache",received:Array.isArray(normalizedOptions)?"array":typeof normalizedOptions,validationError:true})}const{useCache=true}=normalizedOptions;if(typeof useCache!=="boolean"){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"options.useCache",expected:"boolean",received:typeof useCache,validationError:true})}return this.#resolveAccess(callerPath,targetPath,callerFilePath,targetFilePath,runtimeContext,useCache).allowed}matchesCondition(condition,runtimeContext=null){if(condition==null)return true;this.#assertValidConditionPayload(condition,"INVALID_ARGUMENT");return this.#matchesConditionUnchecked(condition,runtimeContext)}#matchesConditionUnchecked(condition,runtimeContext=null){if(condition==null)return true;const ctx=runtimeContext??{};if(Array.isArray(condition)){return condition.some(entry=>this.#singleConditionMatches(entry,ctx))}return this.#singleConditionMatches(condition,ctx)}enforceAccess(callerPath,targetPath,callerFilePath=null,targetFilePath=null,runtimeContext=null){const result=this.#resolveAccess(callerPath,targetPath,callerFilePath,targetFilePath,runtimeContext,true);if(result.event){this.#emitAuditEvent(result.event,result.payload)}return result.allowed}enforceHookAccess(callerPath,hookPath,hookType,callerFilePath=null,targetFilePath=null,runtimeContext=null){const result=this.#resolveHookAccess(callerPath,hookPath,hookType,callerFilePath,targetFilePath,runtimeContext);if(result.event){this.#emitAuditEvent(result.event,result.payload)}return result.allowed}checkHookAccess(callerPath,hookPath,hookType,callerFilePath=null,targetFilePath=null,runtimeContext=null){return this.#resolveHookAccess(callerPath,hookPath,hookType,callerFilePath,targetFilePath,runtimeContext).allowed}getRulesForPath(targetPath){const matching=[];for(const entry of this.#rules.values()){const targetMatcher=this.#getCompiledPattern(entry.target);if(targetMatcher(targetPath)){matching.push(this.#serializeRule(entry))}}return matching}getRulesByModule(moduleID){const matching=[];for(const entry of this.#rules.values()){if(entry.ownerModuleID===moduleID){matching.push(this.#serializeRule(entry))}}return matching}getRulesForCaller(callerPath){const matching=[];for(const entry of this.#rules.values()){const callerMatcher=this.#getCompiledPattern(entry.caller);if(callerMatcher(callerPath)){matching.push(this.#serializeRule(entry))}}return matching}enable(){this.#assertNotSealed();this.#enabled=true;this.#clearCache()}#assertNotSealed(){if(this.#sealed){throw new this.SlothletError("PERMISSION_SEALED",{},null,{validationError:true})}}seal(){this.#sealed=true}isSealed(){return this.#sealed}disable(){this.#assertNotSealed();this.#enabled=false;this.#clearCache()}isEnabled(){return this.#enabled}isReadGatingEnabled(){return this.#readGating}isCaptureEnabled(){return this.#capture}isPrivateTarget(targetPath){return runtime_isPrivateName(targetPath)}setReadGating(value){this.#assertNotSealed();if(typeof value!=="boolean"){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"value",expected:"boolean",received:typeof value,validationError:true})}this.#readGating=value}exportRules(){const rules=[];for(const entry of this.#rules.values()){rules.push({rule:{caller:entry.caller,target:entry.target,effect:entry.effect},ownerModuleID:entry.ownerModuleID})}return rules}importRules(registrations){if(!Array.isArray(registrations))return;for(const reg of registrations){this.addRule(reg.rule,reg.ownerModuleID)}}async shutdown(){this.#rules.clear();this.#resolvedCache.clear();this.#compiledCache.clear();this.#enabled=false;this.#defaultPolicy="allow";this.#audit="default";this.#readGating=false}#validateRule(rule){if(!rule||typeof rule!=="object"){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_NOT_OBJECT"),received:typeof rule})}if(typeof rule.caller!=="string"||!rule.caller){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_CALLER_REQUIRED"),received:typeof rule.caller})}if(typeof rule.target!=="string"||!rule.target){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_TARGET_REQUIRED"),received:typeof rule.target})}if(rule.effect!=="allow"&&rule.effect!=="deny"){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_EFFECT_INVALID"),received:rule.effect})}if(rule.condition!==void 0&&rule.condition!==null){this.#assertValidConditionPayload(rule.condition,"INVALID_PERMISSION_RULE")}}#assertValidConditionPayload(condition,errorCode){const getValueType=value=>{if(value===null)return"null";if(Array.isArray(value))return"array";return typeof value};const isPlainObject=value=>{if(value===null||typeof value!=="object")return false;const proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null};const isValidConditionEntry=value=>typeof value==="function"||isPlainObject(value);const entries=Array.isArray(condition)?condition:[condition];const invalidEntry=entries.length===0?condition:entries.find(entry=>!isValidConditionEntry(entry));if(entries.length>0&&invalidEntry===void 0)return;const received=getValueType(invalidEntry);if(errorCode==="INVALID_PERMISSION_RULE"){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_CONDITION_INVALID"),received})}throw new this.SlothletError("INVALID_ARGUMENT",{argument:"condition",expected:translate("PERM_RULE_CONDITION_INVALID"),received,validationError:true})}#deepObjectMatches(pattern,ctx){if(ctx==null||typeof ctx!=="object")return false;for(const[key,val]of Object.entries(pattern)){const proto=val!==null&&typeof val==="object"?Object.getPrototypeOf(val):null;const isPlainNested=proto===Object.prototype||proto===null;if(val!==null&&typeof val==="object"&&isPlainNested){if(!this.#deepObjectMatches(val,ctx[key]))return false}else{if(ctx[key]!==val)return false}}return true}#singleConditionMatches(conditionEntry,ctx){if(typeof conditionEntry==="function"){try{return!!conditionEntry(ctx)}catch{return false}}return this.#deepObjectMatches(conditionEntry,ctx)}#conditionMatches(entry,runtimeContext){return this.#matchesConditionUnchecked(entry.condition,runtimeContext)}#resolveAccess(callerPath,targetPath,callerFilePath,targetFilePath,runtimeContext,useCache){const isControlTarget=targetPath?.startsWith("slothlet.permissions.control.");if(!this.#enabled&&!isControlTarget)return{allowed:true,event:null,payload:null};if(callerFilePath&&targetFilePath&&callerFilePath===targetFilePath){return{allowed:true,event:"permission:self-bypass",payload:{caller:callerPath,target:targetPath,filePath:callerFilePath}}}if(runtime_isPrivateName(targetPath)&&targetPath!=="slothlet"&&!targetPath.startsWith("slothlet.")){if(callerFilePath&&targetFilePath&&runtime_sameModuleDir(callerFilePath,targetFilePath)){return{allowed:true,event:"permission:private-module-bypass",payload:{caller:callerPath,target:targetPath,filePath:callerFilePath}}}if(!callerFilePath&&this.#privateHost==="allow"){return{allowed:true,event:"permission:private-host-allow",payload:{caller:callerPath??null,target:targetPath}}}return{allowed:false,event:"permission:denied",payload:{caller:callerPath??null,target:targetPath}}}const cacheKey=`${callerPath}::${targetPath}`;if(useCache&&this.#resolvedCache.has(cacheKey)){return this.#resolvedCache.get(cacheKey)}const entry=this.#evaluate(callerPath,targetPath,runtimeContext);if(useCache&&!entry.hasConditionalRules){this.#resolvedCache.set(cacheKey,entry)}return entry}#evaluate(callerPath,targetPath,runtimeContext=null){const matches=[];for(const entry of this.#rules.values()){if(entry.hookType!=null)continue;const callerMatcher=this.#getCompiledPattern(entry.caller);const targetMatcher=this.#getCompiledPattern(entry.target);if(callerMatcher(callerPath)&&targetMatcher(targetPath)){matches.push(entry)}}const hasConditionalRules=matches.some(m=>m.condition!=null);const conditioned=matches.filter(entry=>this.#conditionMatches(entry,runtimeContext));if(conditioned.length===0){const allowed2=this.#defaultPolicy==="allow";return{allowed:allowed2,event:"permission:default",payload:{caller:callerPath,target:targetPath,policy:this.#defaultPolicy},hasConditionalRules}}conditioned.sort((a,b)=>{const specA=this.#computeSpecificity(a,callerPath,targetPath);const specB=this.#computeSpecificity(b,callerPath,targetPath);if(specA!==specB)return specB-specA;return a.registeredAt-b.registeredAt});const highestSpec=this.#computeSpecificity(conditioned[0],callerPath,targetPath);const topTier=conditioned.filter(m=>this.#computeSpecificity(m,callerPath,targetPath)===highestSpec);const winner=topTier[topTier.length-1];const allowed=winner.effect==="allow";return{allowed,event:allowed?"permission:allowed":"permission:denied",payload:{caller:callerPath,target:targetPath,rule:this.#serializeRule(winner),conditionMatched:winner.condition!=null},hasConditionalRules}}#resolveHookAccess(callerPath,hookPath,hookType,callerFilePath,targetFilePath,runtimeContext){if(callerPath==null)return{allowed:true,event:null,payload:null};const hookDecision=this.#evaluateHook(callerPath,hookPath,hookType,runtimeContext);if(hookDecision.matched){return{allowed:hookDecision.allowed,event:hookDecision.event,payload:hookDecision.payload}}const callDecision=this.#resolveAccess(callerPath,hookPath,callerFilePath,targetFilePath,runtimeContext,true);return{allowed:callDecision.allowed,event:callDecision.event,payload:callDecision.payload}}#evaluateHook(callerPath,hookPath,hookType,runtimeContext){const matches=[];for(const entry of this.#rules.values()){if(entry.hookType==null)continue;if(entry.hookType!=="hook"&&entry.hookType!==hookType)continue;const callerMatcher=this.#getCompiledPattern(entry.caller);const pathMatcher=this.#getCompiledPattern(entry.hookPathPattern);if(callerMatcher(callerPath)&&pathMatcher(hookPath)){matches.push(entry)}}const conditioned=matches.filter(entry=>this.#conditionMatches(entry,runtimeContext));if(conditioned.length===0){return{matched:false,allowed:false,event:null,payload:null}}const spec=e=>this.#patternSpecificity(e.caller,callerPath)+this.#patternSpecificity(e.hookPathPattern,hookPath)+(e.hookType==="hook"?0:1);conditioned.sort((a,b)=>{const specA=spec(a);const specB=spec(b);if(specA!==specB)return specB-specA;return a.registeredAt-b.registeredAt});const highest=spec(conditioned[0]);const topTier=conditioned.filter(m=>spec(m)===highest);const winner=topTier[topTier.length-1];const allowed=winner.effect==="allow";return{matched:true,allowed,event:allowed?"permission:allowed":"permission:denied",payload:{caller:callerPath,target:`${hookPath}:${hookType}`,rule:this.#serializeRule(winner),conditionMatched:winner.condition!=null}}}#parseHookTarget(target){const lastColon=target.lastIndexOf(":");if(lastColon===-1)return null;const type=target.substring(lastColon+1);if(!HOOK_TARGET_TYPES.has(type))return null;const pathPattern=target.substring(0,lastColon);if(!pathPattern)return null;return{pathPattern,hookType:type}}#computeSpecificity(entry,callerPath,targetPath){return this.#patternSpecificity(entry.caller,callerPath)+this.#patternSpecificity(entry.target,targetPath)}#patternSpecificity(pattern,___path){if(!pattern.includes("*")&&!pattern.includes("?")&&!pattern.includes("{")){return 3}if(pattern.includes("**")){return 1}return 2}#getCompiledPattern(pattern){let matcher=this.#compiledCache.get(pattern);if(!matcher){matcher=compilePattern(pattern);this.#compiledCache.set(pattern,matcher)}return matcher}#clearCache(){this.#resolvedCache.clear()}#emitAuditEvent(event,payload){this.debug("permissions",{key:event==="permission:denied"?"DEBUG_PERMISSION_DENIED":event==="permission:allowed"?"DEBUG_PERMISSION_ALLOWED":event==="permission:self-bypass"?"DEBUG_PERMISSION_SELF_BYPASS":"DEBUG_PERMISSION_DEFAULT",...payload});const alwaysEmit=event==="permission:denied"||event==="permission:self-bypass";if(!alwaysEmit&&this.#audit!=="verbose")return;const lifecycle=this.slothlet.handlers?.lifecycle;if(lifecycle){lifecycle.emit(event,{...payload,timestamp:Date.now()})}}#serializeRule(entry){return{id:entry.id,caller:entry.caller,target:entry.target,effect:entry.effect,condition:entry.condition??null,ownerModuleID:entry.ownerModuleID,registeredAt:entry.registeredAt}}debug(category,data){this.slothlet.debug(category,data)}}export{PermissionManager};
17
+ import{path}from"@cldmv/slothlet/helpers/platform";import{ComponentBase}from"#factories/component-base";import{compilePattern}from"@cldmv/slothlet/helpers/pattern-matcher";import{translate}from"@cldmv/slothlet/i18n";let ruleIdCounter=0;let ruleRegistrationSeq=0;const HOOK_TARGET_TYPES=new Set(["before","after","always","error","hook"]);const RULE_LAYER_RANK=Object.freeze({builtin:0,manifest:1,instance:2,runtime:3});function resolveRuleLayer(layer,ownerModuleID){if(layer&&Object.prototype.hasOwnProperty.call(RULE_LAYER_RANK,layer))return layer;if(ownerModuleID==="__builtin__")return"builtin";return"instance"}function runtime_isPrivateName(targetPath){if(typeof targetPath!=="string"||targetPath.length===0)return false;const cut=targetPath.lastIndexOf(".");if(cut<0)return false;return targetPath.charCodeAt(cut+1)===95}function runtime_sameModuleDir(callerFilePath,targetFilePath){return path.dirname(callerFilePath)===path.dirname(targetFilePath)}class PermissionManager extends ComponentBase{static slothletProperty="permissionManager";#rules=new Map;#defaultPolicy="allow";#enabled=false;#audit="default";#readGating=false;#capture=true;#privateHost="deny";#eventRules=new Map;#eventDefault="notify";#eventRulesEpoch=0;#sealed=false;#resolvedCache=new Map;#compiledCache=new Map;constructor(slothlet){super(slothlet);const permConfig=slothlet.config?.permissions;if(permConfig){this.#defaultPolicy=permConfig.defaultPolicy||"allow";this.#enabled=permConfig.enabled!==false;this.#audit=permConfig.audit||"default";this.#readGating=permConfig.readGating!==false;this.#capture=permConfig.references?.capture!==false;this.#privateHost=permConfig.private?.host==="allow"?"allow":"deny";if(Array.isArray(permConfig.rules)){for(const rule of permConfig.rules){this.addRule(rule,null)}}if(permConfig.events){if(permConfig.events.default)this.#eventDefault=permConfig.events.default;if(Array.isArray(permConfig.events.rules)){for(const rule of permConfig.events.rules){this.addEventRule(rule,null)}}}}this.addRule({caller:"**",target:"slothlet.permissions.control.**",effect:"deny"},"__builtin__");this.addRule({caller:"**",target:"slothlet.lockCaller",effect:"allow"},"__builtin__");this.addRule({caller:"**",target:"slothlet.bind",effect:"allow"},"__builtin__");this.addRule({caller:"**",target:"slothlet.hook.**",effect:"deny"},"__builtin__");this.addRule({caller:"**",target:"slothlet.hook.list",effect:"allow"},"__builtin__");this.addRule({caller:"**",target:"slothlet.hook.on",effect:"allow"},"__builtin__");this.addRule({caller:"**",target:"slothlet.event.**",effect:"deny"},"__builtin__");this.addRule({caller:"**",target:"slothlet.event.on",effect:"allow"},"__builtin__");this.addRule({caller:"**",target:"slothlet.event.once",effect:"allow"},"__builtin__");this.addRule({caller:"**",target:"slothlet.event.off",effect:"allow"},"__builtin__");this.addRule({caller:"**",target:"slothlet.event.emit",effect:"allow"},"__builtin__")}addRule(rule,ownerModuleID=null,ruleId=null,layer=null){this.#assertNotSealed();this.#validateRule(rule);const id=ruleId||`perm-${++ruleIdCounter}`;const hookTarget=this.#parseHookTarget(rule.target);const ruleLayer=resolveRuleLayer(layer,ownerModuleID);const entry={id,caller:rule.caller,target:rule.target,effect:rule.effect,condition:rule.condition??null,hookType:hookTarget?hookTarget.hookType:null,hookPathPattern:hookTarget?hookTarget.pathPattern:null,ownerModuleID,layer:ruleLayer,layerRank:RULE_LAYER_RANK[ruleLayer],registeredAt:Date.now(),registrationSeq:++ruleRegistrationSeq};this.#rules.set(id,entry);this.#clearCache();this.debug("permissions",{key:"DEBUG_PERMISSION_RULE_ADDED",ruleId:id,caller:rule.caller,target:rule.target,effect:rule.effect,ownerModuleID});return id}removeRule(ruleId,callerModuleID=null){this.#assertNotSealed();const entry=this.#rules.get(ruleId);if(!entry)return false;if(callerModuleID&&entry.ownerModuleID&&callerModuleID===entry.ownerModuleID){throw new this.SlothletError("PERMISSION_SELF_MODIFY",{ruleId,moduleID:callerModuleID})}this.#rules.delete(ruleId);this.#clearCache();this.debug("permissions",{key:"DEBUG_PERMISSION_RULE_REMOVED",ruleId,caller:entry.caller,target:entry.target,effect:entry.effect});return true}addEventRule(rule,ownerModuleID=null,ruleId=null,layer=null){this.#assertNotSealed();this.#validateEventRule(rule);const id=ruleId||`evt-${++ruleIdCounter}`;const ruleLayer=resolveRuleLayer(layer,ownerModuleID);const entry={id,caller:rule.caller,event:rule.event,effect:rule.effect,condition:rule.condition??null,ownerModuleID,layer:ruleLayer,layerRank:RULE_LAYER_RANK[ruleLayer],registeredAt:Date.now(),registrationSeq:++ruleRegistrationSeq};this.#eventRules.set(id,entry);this.#eventRulesEpoch++;this.debug("permissions",{key:"DEBUG_PERMISSION_RULE_ADDED",ruleId:id,caller:rule.caller,target:`${rule.event}:event`,effect:rule.effect,ownerModuleID});return id}removeEventRule(ruleId,callerModuleID=null){this.#assertNotSealed();const entry=this.#eventRules.get(ruleId);if(!entry)return false;if(callerModuleID&&entry.ownerModuleID&&callerModuleID===entry.ownerModuleID){throw new this.SlothletError("PERMISSION_SELF_MODIFY",{ruleId,moduleID:callerModuleID})}this.#eventRules.delete(ruleId);this.#eventRulesEpoch++;return true}resolveEventLevel(subscriberPath,eventName,runtimeContext=null){if(subscriberPath==null)return"allow";const matches=[];for(const entry of this.#eventRules.values()){const callerMatcher=this.#getCompiledPattern(entry.caller);const eventMatcher=this.#getCompiledPattern(entry.event);if(callerMatcher(subscriberPath)&&eventMatcher(eventName))matches.push(entry)}const conditioned=matches.filter(entry=>this.#conditionMatches(entry,runtimeContext));if(conditioned.length===0)return this.#eventDefault;conditioned.sort((a,b)=>{const specA=this.#patternSpecificity(a.caller,subscriberPath)+this.#patternSpecificity(a.event,eventName);const specB=this.#patternSpecificity(b.caller,subscriberPath)+this.#patternSpecificity(b.event,eventName);if(specA!==specB)return specB-specA;if(a.layerRank!==b.layerRank)return b.layerRank-a.layerRank;return b.registrationSeq-a.registrationSeq});return conditioned[0].effect}get eventRulesEpoch(){return this.#eventRulesEpoch}get hasConditionalEventRules(){for(const entry of this.#eventRules.values()){if(entry.condition!=null)return true}return false}#validateEventRule(rule){if(!rule||typeof rule!=="object"){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_NOT_OBJECT"),received:typeof rule})}if(typeof rule.caller!=="string"||!rule.caller){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_CALLER_REQUIRED"),received:typeof rule.caller})}if(typeof rule.event!=="string"||!rule.event){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_EVENT_RULE_EVENT_REQUIRED"),received:typeof rule.event})}if(rule.effect!=="deny"&&rule.effect!=="notify"&&rule.effect!=="allow"){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_EVENT_RULE_EFFECT_INVALID"),received:rule.effect})}if(rule.condition!==void 0&&rule.condition!==null){this.#assertValidConditionPayload(rule.condition,"INVALID_PERMISSION_RULE")}}checkAccess(callerPath,targetPath,callerFilePath=null,targetFilePath=null,runtimeContext=null,options=null){const normalizedOptions=options==null?{}:options;if(typeof normalizedOptions!=="object"||Array.isArray(normalizedOptions)){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"options",expected:"object with optional boolean useCache",received:Array.isArray(normalizedOptions)?"array":typeof normalizedOptions,validationError:true})}const{useCache=true}=normalizedOptions;if(typeof useCache!=="boolean"){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"options.useCache",expected:"boolean",received:typeof useCache,validationError:true})}return this.#resolveAccess(callerPath,targetPath,callerFilePath,targetFilePath,runtimeContext,useCache).allowed}matchesCondition(condition,runtimeContext=null){if(condition==null)return true;this.#assertValidConditionPayload(condition,"INVALID_ARGUMENT");return this.#matchesConditionUnchecked(condition,runtimeContext)}#matchesConditionUnchecked(condition,runtimeContext=null){if(condition==null)return true;const ctx=runtimeContext??{};if(Array.isArray(condition)){return condition.some(entry=>this.#singleConditionMatches(entry,ctx))}return this.#singleConditionMatches(condition,ctx)}enforceAccess(callerPath,targetPath,callerFilePath=null,targetFilePath=null,runtimeContext=null){const result=this.#resolveAccess(callerPath,targetPath,callerFilePath,targetFilePath,runtimeContext,true);if(result.event){this.#emitAuditEvent(result.event,result.payload)}return result.allowed}enforceHookAccess(callerPath,hookPath,hookType,callerFilePath=null,targetFilePath=null,runtimeContext=null){const result=this.#resolveHookAccess(callerPath,hookPath,hookType,callerFilePath,targetFilePath,runtimeContext);if(result.event){this.#emitAuditEvent(result.event,result.payload)}return result.allowed}checkHookAccess(callerPath,hookPath,hookType,callerFilePath=null,targetFilePath=null,runtimeContext=null){return this.#resolveHookAccess(callerPath,hookPath,hookType,callerFilePath,targetFilePath,runtimeContext).allowed}getRulesForPath(targetPath){const matching=[];for(const entry of this.#rules.values()){const targetMatcher=this.#getCompiledPattern(entry.target);if(targetMatcher(targetPath)){matching.push(this.#serializeRule(entry))}}return matching}getRulesByModule(moduleID){const matching=[];for(const entry of this.#rules.values()){if(entry.ownerModuleID===moduleID){matching.push(this.#serializeRule(entry))}}return matching}getRulesForCaller(callerPath){const matching=[];for(const entry of this.#rules.values()){const callerMatcher=this.#getCompiledPattern(entry.caller);if(callerMatcher(callerPath)){matching.push(this.#serializeRule(entry))}}return matching}enable(){this.#assertNotSealed();this.#enabled=true;this.#clearCache()}#assertNotSealed(){if(this.#sealed){throw new this.SlothletError("PERMISSION_SEALED",{},null,{validationError:true})}}seal(){this.#sealed=true}isSealed(){return this.#sealed}disable(){this.#assertNotSealed();this.#enabled=false;this.#clearCache()}isEnabled(){return this.#enabled}isReadGatingEnabled(){return this.#readGating}isCaptureEnabled(){return this.#capture}isPrivateTarget(targetPath){return runtime_isPrivateName(targetPath)}setReadGating(value){this.#assertNotSealed();if(typeof value!=="boolean"){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"value",expected:"boolean",received:typeof value,validationError:true})}this.#readGating=value}exportRules(){const rules=[];for(const entry of this.#rules.values()){rules.push({rule:{caller:entry.caller,target:entry.target,effect:entry.effect},ownerModuleID:entry.ownerModuleID})}return rules}importRules(registrations){if(!Array.isArray(registrations))return;for(const reg of registrations){this.addRule(reg.rule,reg.ownerModuleID)}}async shutdown(){this.#rules.clear();this.#resolvedCache.clear();this.#compiledCache.clear();this.#enabled=false;this.#defaultPolicy="allow";this.#audit="default";this.#readGating=false}#validateRule(rule){if(!rule||typeof rule!=="object"){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_NOT_OBJECT"),received:typeof rule})}if(typeof rule.caller!=="string"||!rule.caller){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_CALLER_REQUIRED"),received:typeof rule.caller})}if(typeof rule.target!=="string"||!rule.target){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_TARGET_REQUIRED"),received:typeof rule.target})}if(rule.effect!=="allow"&&rule.effect!=="deny"){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_EFFECT_INVALID"),received:rule.effect})}if(rule.condition!==void 0&&rule.condition!==null){this.#assertValidConditionPayload(rule.condition,"INVALID_PERMISSION_RULE")}}#assertValidConditionPayload(condition,errorCode){const getValueType=value=>{if(value===null)return"null";if(Array.isArray(value))return"array";return typeof value};const isPlainObject=value=>{if(value===null||typeof value!=="object")return false;const proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null};const isValidConditionEntry=value=>typeof value==="function"||isPlainObject(value);const entries=Array.isArray(condition)?condition:[condition];const invalidEntry=entries.length===0?condition:entries.find(entry=>!isValidConditionEntry(entry));if(entries.length>0&&invalidEntry===void 0)return;const received=getValueType(invalidEntry);if(errorCode==="INVALID_PERMISSION_RULE"){throw new this.SlothletError("INVALID_PERMISSION_RULE",{reason:translate("PERM_RULE_CONDITION_INVALID"),received})}throw new this.SlothletError("INVALID_ARGUMENT",{argument:"condition",expected:translate("PERM_RULE_CONDITION_INVALID"),received,validationError:true})}#deepObjectMatches(pattern,ctx){if(ctx==null||typeof ctx!=="object")return false;for(const[key,val]of Object.entries(pattern)){const proto=val!==null&&typeof val==="object"?Object.getPrototypeOf(val):null;const isPlainNested=proto===Object.prototype||proto===null;if(val!==null&&typeof val==="object"&&isPlainNested){if(!this.#deepObjectMatches(val,ctx[key]))return false}else{if(ctx[key]!==val)return false}}return true}#singleConditionMatches(conditionEntry,ctx){if(typeof conditionEntry==="function"){try{return!!conditionEntry(ctx)}catch{return false}}return this.#deepObjectMatches(conditionEntry,ctx)}#conditionMatches(entry,runtimeContext){return this.#matchesConditionUnchecked(entry.condition,runtimeContext)}#resolveAccess(callerPath,targetPath,callerFilePath,targetFilePath,runtimeContext,useCache){const isControlTarget=targetPath?.startsWith("slothlet.permissions.control.");if(!this.#enabled&&!isControlTarget)return{allowed:true,event:null,payload:null};if(callerFilePath&&targetFilePath&&callerFilePath===targetFilePath){return{allowed:true,event:"permission:self-bypass",payload:{caller:callerPath,target:targetPath,filePath:callerFilePath}}}if(runtime_isPrivateName(targetPath)&&targetPath!=="slothlet"&&!targetPath.startsWith("slothlet.")){if(callerFilePath&&targetFilePath&&runtime_sameModuleDir(callerFilePath,targetFilePath)){return{allowed:true,event:"permission:private-module-bypass",payload:{caller:callerPath,target:targetPath,filePath:callerFilePath}}}if(!callerFilePath&&this.#privateHost==="allow"){return{allowed:true,event:"permission:private-host-allow",payload:{caller:callerPath??null,target:targetPath}}}return{allowed:false,event:"permission:denied",payload:{caller:callerPath??null,target:targetPath}}}const cacheKey=`${callerPath}::${targetPath}`;if(useCache&&this.#resolvedCache.has(cacheKey)){return this.#resolvedCache.get(cacheKey)}const entry=this.#evaluate(callerPath,targetPath,runtimeContext);if(useCache&&!entry.hasConditionalRules){this.#resolvedCache.set(cacheKey,entry)}return entry}#evaluate(callerPath,targetPath,runtimeContext=null){const matches=[];for(const entry of this.#rules.values()){if(entry.hookType!=null)continue;const callerMatcher=this.#getCompiledPattern(entry.caller);const targetMatcher=this.#getCompiledPattern(entry.target);if(callerMatcher(callerPath)&&targetMatcher(targetPath)){matches.push(entry)}}const hasConditionalRules=matches.some(m=>m.condition!=null);const conditioned=matches.filter(entry=>this.#conditionMatches(entry,runtimeContext));if(conditioned.length===0){const allowed2=this.#defaultPolicy==="allow";return{allowed:allowed2,event:"permission:default",payload:{caller:callerPath,target:targetPath,policy:this.#defaultPolicy},hasConditionalRules}}conditioned.sort((a,b)=>{const specA=this.#computeSpecificity(a,callerPath,targetPath);const specB=this.#computeSpecificity(b,callerPath,targetPath);if(specA!==specB)return specB-specA;if(a.layerRank!==b.layerRank)return b.layerRank-a.layerRank;return b.registrationSeq-a.registrationSeq});const winner=conditioned[0];const allowed=winner.effect==="allow";return{allowed,event:allowed?"permission:allowed":"permission:denied",payload:{caller:callerPath,target:targetPath,rule:this.#serializeRule(winner),conditionMatched:winner.condition!=null},hasConditionalRules}}#resolveHookAccess(callerPath,hookPath,hookType,callerFilePath,targetFilePath,runtimeContext){if(callerPath==null)return{allowed:true,event:null,payload:null};const hookDecision=this.#evaluateHook(callerPath,hookPath,hookType,runtimeContext);if(hookDecision.matched){return{allowed:hookDecision.allowed,event:hookDecision.event,payload:hookDecision.payload}}const callDecision=this.#resolveAccess(callerPath,hookPath,callerFilePath,targetFilePath,runtimeContext,true);return{allowed:callDecision.allowed,event:callDecision.event,payload:callDecision.payload}}#evaluateHook(callerPath,hookPath,hookType,runtimeContext){const matches=[];for(const entry of this.#rules.values()){if(entry.hookType==null)continue;if(entry.hookType!=="hook"&&entry.hookType!==hookType)continue;const callerMatcher=this.#getCompiledPattern(entry.caller);const pathMatcher=this.#getCompiledPattern(entry.hookPathPattern);if(callerMatcher(callerPath)&&pathMatcher(hookPath)){matches.push(entry)}}const conditioned=matches.filter(entry=>this.#conditionMatches(entry,runtimeContext));if(conditioned.length===0){return{matched:false,allowed:false,event:null,payload:null}}const spec=e=>this.#patternSpecificity(e.caller,callerPath)+this.#patternSpecificity(e.hookPathPattern,hookPath)+(e.hookType==="hook"?0:1);conditioned.sort((a,b)=>{const specA=spec(a);const specB=spec(b);if(specA!==specB)return specB-specA;if(a.layerRank!==b.layerRank)return b.layerRank-a.layerRank;return b.registrationSeq-a.registrationSeq});const winner=conditioned[0];const allowed=winner.effect==="allow";return{matched:true,allowed,event:allowed?"permission:allowed":"permission:denied",payload:{caller:callerPath,target:`${hookPath}:${hookType}`,rule:this.#serializeRule(winner),conditionMatched:winner.condition!=null}}}#parseHookTarget(target){const lastColon=target.lastIndexOf(":");if(lastColon===-1)return null;const type=target.substring(lastColon+1);if(!HOOK_TARGET_TYPES.has(type))return null;const pathPattern=target.substring(0,lastColon);if(!pathPattern)return null;return{pathPattern,hookType:type}}#computeSpecificity(entry,callerPath,targetPath){return this.#patternSpecificity(entry.caller,callerPath)+this.#patternSpecificity(entry.target,targetPath)}#patternSpecificity(pattern,___path){if(!pattern.includes("*")&&!pattern.includes("?")&&!pattern.includes("{")){return 3}if(pattern.includes("**")){return 1}return 2}#getCompiledPattern(pattern){let matcher=this.#compiledCache.get(pattern);if(!matcher){matcher=compilePattern(pattern);this.#compiledCache.set(pattern,matcher)}return matcher}#clearCache(){this.#resolvedCache.clear()}#emitAuditEvent(event,payload){this.debug("permissions",{key:event==="permission:denied"?"DEBUG_PERMISSION_DENIED":event==="permission:allowed"?"DEBUG_PERMISSION_ALLOWED":event==="permission:self-bypass"?"DEBUG_PERMISSION_SELF_BYPASS":"DEBUG_PERMISSION_DEFAULT",...payload});const alwaysEmit=event==="permission:denied"||event==="permission:self-bypass";if(!alwaysEmit&&this.#audit!=="verbose")return;const lifecycle=this.slothlet.handlers?.lifecycle;if(lifecycle){lifecycle.emit(event,{...payload,timestamp:Date.now()})}}#serializeRule(entry){return{id:entry.id,caller:entry.caller,target:entry.target,effect:entry.effect,condition:entry.condition??null,ownerModuleID:entry.ownerModuleID,registeredAt:entry.registeredAt}}debug(category,data){this.slothlet.debug(category,data)}}export{PermissionManager};