scriptexec

package
v1.134.1 Latest Latest
Warning

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

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

Documentation

Overview

Package scriptexec executes managed scripts: the run worker that claims due runs off the queue, the per-run script principal it executes them under, and the writer that turns a script's output into a portal asset.

It is the half of the managed-script feature that runs with nobody present. The authoring half (internal/platform/scriptlayer) is interactive and runs as whoever is typing; everything here runs later, unattended, which is why the run gate is re-read on every run rather than trusted from the queue row that got here:

  • code comes from the version the run was queued against, the latest saved version at the moment of the request, so a run always executes an immutable snapshot a person saved;
  • authority is the roles that version's AUTHOR held when they saved it, so an unattended run can never exceed what the person who wrote the code could do;
  • every capability call goes through the assembled MCP server over an in-memory session, so persona and connection authorization, rate limiting, and audit apply to a script exactly as they apply to an agent.

The package must not import pkg/platform. The composition root passes in the stores, the assembled server, and the portal dependencies it already holds.

Index

Constants

View Source
const DefaultRunRetention = 365 * 24 * time.Hour

DefaultRunRetention is how long a terminal run row is kept when the deployment names no retention.

It is a year, an order of magnitude beyond the notification queue's thirty days, because these rows are not queue bookkeeping. A scheduled script's run history is its refresh history — the record of what a dashboard was showing and when it last succeeded — which is product surface a person reads, not residue a sweep should be eager to remove.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	// DB backs the run, script, and version stores. A nil DB with no stores
	// supplied disables the feature: New returns nil and every method on the
	// nil handle is a no-op.
	DB *sql.DB

	// Runs, Scripts, Versions and Schedules, when non-nil, are used directly
	// instead of building PostgreSQL stores from DB. Production passes DB and
	// leaves them nil; they exist so the execution side can be assembled over
	// in-memory stores, which is the only way to exercise the worker, the
	// engine, and the middleware chain together without a database. The same
	// shape as scriptlayer.Config.Store.
	Runs      script.RunStore
	Scripts   ScriptReader
	Versions  VersionReader
	Schedules script.ScheduleStore

	// DSN is the raw database DSN for the LISTEN connection that wakes the
	// worker the moment a run is enqueued. Empty degrades to poll-only.
	DSN string

	// Server is the assembled MCP server a run drives over an in-memory
	// session. A nil server leaves the worker running but every run fails with
	// "script execution is unavailable", which is the honest report.
	Server *mcp.Server

	// Export carries the portal dependencies platform.export writes through. An
	// incomplete set leaves scripts able to query and unable to persist, which
	// each affected run reports.
	Export ExportDeps

	// PortalURL is the deployment's public portal address, from which a run's
	// own page is built: the link a platform.notify post carries when the
	// script names none (#1723). Empty omits the link.
	PortalURL string
	// Destinations is the deployment's configured bucket destinations, which a
	// run resolves platform.export names against at run time.
	Destinations []script.Destination

	// Metrics records what the run queue is doing: runs by script, trigger and
	// status, their duration, how many are executing on this replica, and the
	// fires the misfire policy stepped over (#1307). Optional — every method on
	// it is nil-safe — but without it the automations are invisible to an
	// operator watching the platform rather than reading the run table.
	Metrics *observability.Metrics

	// Audit records the script_run lifecycle event. Optional.
	Audit middleware.AuditLogger

	// Subjects tells a run the subject its author's own session authenticates
	// as, so the run files managed resources in the library that session files
	// in rather than one keyed by the author's address (#1677). Nil leaves a
	// run keyed by address.
	Subjects SubjectResolver

	// Notifier queues the alert a failed SCHEDULED run raises. When nil and DB
	// is set, one is built over the notification queue unless
	// NotificationsDisabled says the deployment turned notifications off.
	Notifier Notifier

	// NotificationsDisabled mirrors notifications.enabled: false in YAML.
	NotificationsDisabled bool

	// DigestHourUTC is the hour daily digests are scheduled for, for a
	// recipient whose delivery mode is daily.
	DigestHourUTC int

	// RunRetention overrides DefaultRunRetention.
	RunRetention time.Duration

	// WorkerDisabled leaves this replica serving without ever claiming from the
	// run queue. run_script still enqueues and still waits on the result, which
	// a separate deployment of the same binary with the worker on produces, so
	// script execution scales apart from serving and a pathological script
	// reaches no pod an agent is talking to. The zero value runs the worker,
	// which is the single-binary default.
	WorkerDisabled bool
}

Config carries everything the execution side needs. No platform types.

type Draft added in v1.134.0

type Draft struct {
	// Script is the record the draft runs as. A draft of a script that is not
	// saved yet carries a record with a name and no id.
	Script *script.Script
	// RunID is the draft's own id, the session every call it makes records.
	RunID string
	// Email and Subject are the person running the draft, and Roles the roles
	// their session holds. A library output lands in that person's library,
	// which is where a platform run of the same script, acting for them as its
	// author, would land it.
	Email   string
	Subject string
	Roles   []string
	// Caller is the draft's own session, which a bucket delivery is made over.
	Caller scriptrun.Caller
}

Draft is one draft run whose author asked for its writes (#1822): the script it drafts, the run it is, and the person running it.

A draft with allow_writes promises to write for real, and the next step of a staging pipeline reads what platform.export wrote -- the file it registers, the table it queries. A draft that previewed its exports while writing everything else could not exercise that pipeline at all.

type ExportDeps

type ExportDeps struct {
	Assets   portal.AssetStore
	Versions portal.VersionStore
	S3       portal.S3Client
	Bucket   string
	Prefix   string
	// FollowTables, when set, is called after every version an output writes,
	// so the tables registered over the output's file follow it (#1536). It
	// returns what happened to each table, which the run records on the
	// output and prints into its log. Nil on a deployment that cannot
	// register tables.
	FollowTables func(ctx context.Context, assetID string, version int) []string
	// Lander writes an output into the managed resource at a path, creating the
	// file the first time and recording a new version of it after (#1663). It is
	// what the built-in "resources" destination writes through. Nil leaves that
	// destination reporting that this deployment has no library, which is what a
	// deployment without one should say.
	Lander ResourceLander
}

ExportDeps is what turning a script's rows into a portal asset needs.

type Handle

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

Handle owns the running execution side.

A handle with no worker is the split deployment's serving half: it still owns the queue, so run_script enqueues and waits exactly as it does on a single-binary replica, and nothing here ever claims. The listener goes with the worker, because waking a replica that will not claim buys nothing but a database connection.

func New

func New(cfg Config) *Handle

New composes the execution side. It returns nil when there is nowhere to keep runs, because a run queue with no storage is not a degraded feature, it is no feature; every method here is nil-safe so the caller wires it unconditionally.

func (*Handle) DraftExporter added in v1.134.0

func (h *Handle) DraftExporter(d Draft) scriptrun.Exporter

DraftExporter builds the writer a draft that was allowed to write persists its outputs through: the same writer a platform run uses, with nothing to record on, because a draft has no run row. A nil Handle -- a deployment with nowhere to keep runs -- returns nil, and the draft previews.

func (*Handle) Notify

func (h *Handle) Notify()

Notify wakes the run worker without waiting for its poll tick. The queue's LISTEN adapter calls it on every pg_notify the store fires, which is how an enqueue on any replica reaches a worker on any other; a producer holding this handle can call it directly and skip the round trip. Nil-safe.

func (*Handle) Runs

func (h *Handle) Runs() script.RunStore

Runs exposes the queue so the tool surface can enqueue and follow runs without building a second store over the same table. Nil-safe.

func (*Handle) Start

func (h *Handle) Start(ctx context.Context) error

Start launches the run worker and, when configured, the LISTEN adapter that wakes it on enqueue. A LISTEN failure degrades to the worker's poll interval rather than failing startup: the wakeup is a latency optimization, and the poll is what makes the queue correct. Nil-safe.

func (*Handle) Stop

func (h *Handle) Stop(ctx context.Context) error

Stop closes the listener and the materializer, then drains the worker inside whatever budget ctx carries: the run in flight finishes if it can and is released if it cannot.

Both producers stop before the consumer. The listener goes first so nothing wakes a worker that is already draining, and the materializer goes with it so the shutdown budget is not spent executing a fire that arrived during it — the fire is not lost, because the schedule is not advanced until its run exists, so the replica that takes over materializes it. Nil-safe.

type Notifier

type Notifier interface {
	Notify(ctx context.Context, recipient, category string, p notification.Payload) (bool, error)
}

Notifier queues one notification. It is the enqueue half of the email substrate, narrowed to the one call this package makes.

type ResourceLander added in v1.131.0

type ResourceLander interface {
	Land(
		ctx context.Context, dest toolkit.ResourceDestination,
		content io.Reader, contentType string, claims resource.Claims,
	) (*toolkit.ResourceLanding, error)
}

ResourceLander lands one output in the managed resource at a path. It is the platform's managed-resource writer, narrowed to the one call this package makes, and it takes the acting identity explicitly: a run's writes go through the platform's own funnels rather than across the MCP middleware that would have put a principal on the context.

type ScriptReader

type ScriptReader interface {
	GetByID(ctx context.Context, id string) (*script.Script, error)
}

ScriptReader is the script lookup the execution side needs, narrowed to the one method so nothing here depends on the whole store contract.

type SubjectResolver added in v1.131.1

type SubjectResolver interface {
	// ForRun returns the subject the address most recently authenticated as,
	// with what that address filed folded into the subject's library, or ""
	// when the platform has not seen the person authenticate.
	ForRun(ctx context.Context, address string) string
}

SubjectResolver answers the subject a run acting for an address presents.

type VersionReader

type VersionReader interface {
	GetVersionByID(ctx context.Context, id string) (*script.Version, error)
	GetVersion(ctx context.Context, scriptID string, version int) (*script.Version, error)
}

VersionReader is the version lookup the execution side needs. The worker reads by id because a run is queued against one — only an id names one immutable snapshot for the life of a script — and the scheduler reads the script's current version by number to build the run it materializes.

Jump to

Keyboard shortcuts

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