Skip to content

feat: permissions.global.checkCall(caller, target, args) — call-side twin of event.resolveLevel #508

Description

@Shinrai

Summary

Add a host-only query that answers "may caller path X call target Y with these args", running the full call-gate semantics. It is the call-side counterpart of api.slothlet.event.resolveLevel(subscriberPath, event), which was added for slothlet-vine's event forwarding.

Why

@cldmv/slothlet-vine needs to gate served calls as a channel principal (CLDMV/slothlet-vine#33): a far peer over an untrusted transport (a browser over a WebSocket) must be judged by the serving instance's rules, not by the far side's own stub. Slothlet has no way to dispatch a call as an arbitrary identity (context.run keeps host standing, lockCaller is a passthrough from the host), so the vine has to ask the rules engine and enforce the answer itself.

The only public query today, permissions.global.checkAccess(caller, target), is a silent query that passes callMeta = null. Through it:

  • resource-scoped conditions (condition: (ctx, { args }) => …) receive null and are non-matches;
  • 3.21 principals (requires: [...]) that are stale are non-matches instead of being resolved.

So a rule like { caller: "remote.renderer", target: "project.files.list", condition: (ctx, meta) => meta?.args?.[0] === ctx.actor?.project } stops meaning what it says when it's evaluated through checkAccess. The error goes whichever way defaultPolicy points.

Proposal

api.slothlet.permissions.global.checkCall(callerPath, targetPath, args) → boolean | Promise<boolean>

  • Forwards callMeta = { args, target: targetPath } to function conditions, exactly as the call gate builds it in enforcePermission.
  • Evaluates conditions against the ambient runtime context (tryGetContext()?.context), like checkAccess / resolveLevel.
  • When a requires principal is stale, resolves it and re-evaluates, mirroring the wrapper's deferral. Returns a Promise only in that case; the sync fast path stays sync.
  • Treats the caller as a module with no source file: the self-call bypass never applies, and a module-private target is denied (not judged by the private.host policy).
  • Emits the audit lifecycle events (permission:denied / permission:allowed / permission:default) so a probing peer is visible like any other denial.
  • Returns true when the permission system is disabled, consistent with checkAccess.
  • Host-only: modules can't call it, like event.resolveLevel.

Consumer

slothlet-vine #33 (serve-side principal gate), followed by CLDMV/slothlet-vine#51 (serve around). rummage's WebSocket serve (CLDMV/rummage#69) needs this before it can widen its served surface past read-only host.**.

Activity

  1. added theissue type on Sep 28, 2026
  2. added
    priority: mediumShould be addressed in the normal course of development
    type: feature requestA request to add new functionality that does not currently exist
    area: coreTouches core library / runtime source code
    status: not startedNot implemented yet — no code exists for this
    status: in progressPartially implemented — actively being built
    status: implementedBuilt and deployed, but not yet fully tested/verified
    and removed
    status: not startedNot implemented yet — no code exists for this
    status: in progressPartially implemented — actively being built
    on Sep 28, 2026
  3. Shinrai commented on Sep 29, 2026

    @Shinrai
    ContributorAuthor

    Landed on next in #513 (aba22f89). Ships in the next release (3.22.0); this closes when it reaches master.

  4. cldmv-bot commented on Oct 3, 2026

    @cldmv-bot
    Contributor

    Closed in v3.22.0 — resolved by #513.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreTouches core library / runtime source codepriority: mediumShould be addressed in the normal course of developmentstatus: implementedBuilt and deployed, but not yet fully tested/verifiedtype: feature requestA request to add new functionality that does not currently exist

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions