scriptexec

package
v1.131.1 Latest Latest
Warning

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

Go to latest
Published: Sep 11, 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

	// 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 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) 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