scriptdraft

package
v1.131.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 10, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package scriptdraft executes a managed script's draft under the identity of the person asking for it.

It introduces no authority. The run opens an in-memory MCP session carrying the caller's own identity, so every platform call it makes is authenticated, authorized, rate limited, and audited exactly as the same call typed by that person directly would be: there is nothing reachable through a draft run that its caller could not already reach by calling the tools themselves. What it adds is the loop — real interpreter errors, real rows, real shapes — so a script is finished before it is saved as the version that runs.

It is deliberately NOT a way around the execution gate. It runs under tighter limits than a platform run will, and by default it persists nothing: the three named helpers preview, and every other write a script makes goes through platform.call and is stopped by the engine's write barrier (#1664). A caller who wants the writes asks for them, and the outcome then lists what the run persisted.

The package exists because there are two surfaces that ask for a draft run — the manage_script tool an agent calls and the editor its owner works in (#1364) — and exactly one of them may decide what a draft run is. The identity is the caller's in both cases; the difference between them is only where that identity was read from.

Index

Constants

This section is empty.

Variables

View Source
var ErrBusy = errors.New("too many draft runs are already executing; try again in a moment")

ErrBusy marks a draft refused because this replica is already running as many as it will. It is a sentinel so an HTTP surface can answer 503 with a retry rather than reporting the platform as broken.

View Source
var ErrNoIdentity = errors.New("a draft run needs an authenticated caller to run as")

ErrNoIdentity marks a draft request carrying nobody to run as. A draft has no identity of its own, so there is nothing to fall back to.

Functions

func ClassifierOver added in v1.131.0

func ClassifierOver(toolkits ToolkitLister) toolwrite.Classifier

ClassifierOver builds the classifier a draft's write barrier decides with, over the deployment's live toolkits (#1664).

It exists here, rather than at each composition root, because both surfaces that reach a draft need it and exactly one of them may decide what it is: the manage_script tool an agent calls and the editor its owner works in.

A deployment with no api gateway resolves nothing through it, and a call addressed by operation id is then classified as a write like anything else the table cannot read.

Types

type Identity

type Identity struct {
	UserID   string
	Email    string
	Roles    []string
	AuthType string
	Claims   map[string]any
}

Identity is the person a draft executes as. It is copied from a caller the platform has already authenticated — never synthesized here — which is what makes the no-new-authority property structural rather than a promise.

type Outcome

type Outcome struct {
	// RunID identifies the run, and is also the session id every audit row the
	// run produced carries.
	RunID string
	// Result is the engine's record of the execution, present even when the
	// script failed.
	Result *scriptrun.Result
	// Err is the script's own failure — a Starlark error, a refused host call,
	// or a limit — and nil when it succeeded.
	Err error
}

Outcome is what one draft execution did. Failure is a normal outcome and is carried here rather than returned as an error: a failed draft's log is the whole reason to have run it.

func (*Outcome) Failed

func (o *Outcome) Failed() bool

Failed reports whether the script itself failed.

type Request

type Request struct {
	// Source is the Starlark to execute. It is passed explicitly rather than
	// read from a record because the editor's whole purpose is running an edit
	// that has not been saved.
	Source string
	// Name labels the script in tracebacks.
	Name string
	// Params is the already-bound parameter set. Binding is the domain's
	// (script.BindParams) and happens before a Runner is involved, so a draft
	// and a platform run bind by one rule.
	Params map[string]any
	// State is the script's live state, read by the caller, handed to the
	// draft as run.state (#1537). A draft reads what a platform run created
	// now would read, and writes nothing back: what it would have saved is on
	// the outcome's result.
	State    map[string]any
	Identity Identity
	// AllowWrites lifts the engine's write barrier, letting the draft persist
	// through platform.call for real (#1664).
	//
	// It is off by default because a draft is a rehearsal: an author drafting
	// an ingestion script wants to exercise the landing pipeline without
	// landing, and a draft that forgot to ask for it left resources,
	// registrations and assets behind with no run record. It exists because the
	// rehearsal has a limit — a pipeline whose next step reads what the last
	// one created cannot be exercised without the create — and the person
	// making that call is the one who owns what it writes.
	AllowWrites bool
}

Request is one draft execution: the code, what to call it, and the values it binds.

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner executes drafts against an assembled MCP server.

func New

func New(server *mcp.Server, destinations []script.Destination) *Runner

New builds a Runner over the assembled server. destinations is the deployment's configured bucket destination set, resolved by a draft exactly as a platform run resolves it, so a destination a real run would refuse fails while the author is iterating. A nil server yields a Runner that refuses every request, which is the honest shape for a deployment with no server to run against.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context, req Request) (*Outcome, error)

Run executes one draft and returns its outcome.

The returned error is the platform's — no server, no identity, a session that could not be opened. The script's own failure is in the outcome.

func (*Runner) WithToolkits added in v1.131.0

func (r *Runner) WithToolkits(toolkits ToolkitLister) *Runner

WithToolkits returns the Runner with the write barrier's live lookups installed, and is how a composition root that holds the toolkit registry hands them over: the api gateway's operation-id to HTTP method resolution, and what an MCP gateway's upstream declares about a proxied tool.

Without them the barrier classifies from the declared table alone, which refuses both of those forms. With them, a draft pulls through the address api_discover names and calls a proxied tool its upstream says reads.

type ToolkitLister added in v1.131.0

type ToolkitLister interface {
	All() []registry.Toolkit
}

ToolkitLister is the live toolkit registry. The resolver below asks it at the moment it classifies a call rather than holding a toolkit resolved at assembly: connections are added, reloaded and removed while the platform runs, and a barrier reading a snapshot would classify against a catalog that has moved on. It is the same reason a provenance capture takes the registry.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL