Changeset 0.29.1 (#196)
This commit is contained in:
@@ -3,6 +3,12 @@
|
||||
// v0.29.0 CS3: Runner loads a package's Starlark script, assembles
|
||||
// the module set based on granted permissions, and executes it.
|
||||
//
|
||||
// v0.29.1 CS0: Wires api.http permission to BuildHTTPModule with
|
||||
// network_access config from package manifest.
|
||||
//
|
||||
// v0.29.1 CS2: Adds RunContext for per-invocation state (user_id),
|
||||
// vault for provider key decryption, and provider.complete module.
|
||||
//
|
||||
// The runner is the bridge between the package/permission system
|
||||
// and the sandboxed Starlark interpreter.
|
||||
package sandbox
|
||||
@@ -18,11 +24,24 @@ import (
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
)
|
||||
|
||||
// RunContext carries per-invocation state that modules need but which
|
||||
// varies per caller (API route vs filter vs task). Nil is safe — modules
|
||||
// that need RunContext fields gracefully degrade.
|
||||
type RunContext struct {
|
||||
// UserID is the acting user for provider resolution (BYOK chain).
|
||||
UserID string
|
||||
|
||||
// ChannelID is the channel context, if any. Used for provider
|
||||
// resolution when a channel has a pinned provider config.
|
||||
ChannelID string
|
||||
}
|
||||
|
||||
// Runner executes Starlark package scripts with permission-gated modules.
|
||||
type Runner struct {
|
||||
sandbox *Sandbox
|
||||
stores store.Stores
|
||||
notifier NotificationSender // nil = notifications module unavailable
|
||||
notifier NotificationSender // nil = notifications module unavailable
|
||||
resolver ProviderResolver // nil = provider module unavailable
|
||||
}
|
||||
|
||||
// NewRunner creates a runner with the given sandbox and dependencies.
|
||||
@@ -38,12 +57,20 @@ func (r *Runner) SetNotifier(n NotificationSender) {
|
||||
r.notifier = n
|
||||
}
|
||||
|
||||
// SetProviderResolver attaches the provider resolver for the provider.complete module.
|
||||
func (r *Runner) SetProviderResolver(pr ProviderResolver) {
|
||||
r.resolver = pr
|
||||
}
|
||||
|
||||
// ExecPackage loads a package's script from its manifest and executes it
|
||||
// with modules gated by the package's granted permissions.
|
||||
//
|
||||
// The RunContext carries per-invocation state (user_id for provider
|
||||
// resolution). Pass nil if no per-invocation context is needed.
|
||||
//
|
||||
// Returns the script's globals (which may contain callable entry points
|
||||
// like on_pre_completion, on_run, etc.) and any captured print output.
|
||||
func (r *Runner) ExecPackage(ctx context.Context, pkg *store.PackageRegistration) (*Result, error) {
|
||||
func (r *Runner) ExecPackage(ctx context.Context, pkg *store.PackageRegistration, rc *RunContext) (*Result, error) {
|
||||
// Only active starlark packages can execute
|
||||
if pkg.Status != models.PackageStatusActive {
|
||||
return nil, fmt.Errorf("package %q is %s, not active", pkg.ID, pkg.Status)
|
||||
@@ -59,7 +86,7 @@ func (r *Runner) ExecPackage(ctx context.Context, pkg *store.PackageRegistration
|
||||
}
|
||||
|
||||
// Build modules based on granted permissions
|
||||
modules, err := r.buildModules(ctx, pkg.ID)
|
||||
modules, err := r.buildModules(ctx, pkg.ID, pkg.Manifest, rc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build modules for %q: %w", pkg.ID, err)
|
||||
}
|
||||
@@ -75,9 +102,11 @@ func (r *Runner) ExecPackage(ctx context.Context, pkg *store.PackageRegistration
|
||||
// 2. Find the named entry point in globals
|
||||
// 3. Call it with the provided arguments
|
||||
//
|
||||
// The RunContext carries per-invocation state. Pass nil if not needed.
|
||||
//
|
||||
// Returns the function's return value and captured output.
|
||||
func (r *Runner) CallEntryPoint(ctx context.Context, pkg *store.PackageRegistration, entryPoint string, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, string, error) {
|
||||
result, err := r.ExecPackage(ctx, pkg)
|
||||
func (r *Runner) CallEntryPoint(ctx context.Context, pkg *store.PackageRegistration, entryPoint string, args starlark.Tuple, kwargs []starlark.Tuple, rc *RunContext) (starlark.Value, string, error) {
|
||||
result, err := r.ExecPackage(ctx, pkg, rc)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -97,7 +126,10 @@ func (r *Runner) CallEntryPoint(ctx context.Context, pkg *store.PackageRegistrat
|
||||
}
|
||||
|
||||
// buildModules assembles the module map based on granted permissions.
|
||||
func (r *Runner) buildModules(ctx context.Context, packageID string) (map[string]starlark.Value, error) {
|
||||
// The manifest is passed through so modules can read their config
|
||||
// (e.g., http module reads network_access, provider reads requires_provider).
|
||||
// The RunContext carries per-invocation state for user-scoped modules.
|
||||
func (r *Runner) buildModules(ctx context.Context, packageID string, manifest map[string]any, rc *RunContext) (map[string]starlark.Value, error) {
|
||||
if r.stores.ExtPermissions == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -119,11 +151,21 @@ func (r *Runner) buildModules(ctx context.Context, packageID string) (map[string
|
||||
modules["notifications"] = BuildNotificationsModule(ctx, r.notifier, packageID)
|
||||
}
|
||||
|
||||
// Future permissions (CS4+):
|
||||
case models.ExtPermAPIHTTP:
|
||||
httpCfg := ParseNetworkAccess(manifest)
|
||||
modules["http"] = BuildHTTPModule(ctx, httpCfg)
|
||||
|
||||
case models.ExtPermProviderComplete:
|
||||
if r.resolver != nil && rc != nil && rc.UserID != "" {
|
||||
provCfg, ok := ParseRequiresProvider(manifest)
|
||||
if ok {
|
||||
modules["provider"] = BuildProviderModule(ctx, r.resolver, rc.UserID, provCfg)
|
||||
}
|
||||
}
|
||||
|
||||
// Future permissions:
|
||||
// case models.ExtPermDBRead, models.ExtPermDBWrite:
|
||||
// modules["db"] = BuildDBModule(...)
|
||||
// case models.ExtPermAPIHTTP:
|
||||
// modules["http"] = BuildHTTPModule(...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user