ops

package
v0.0.42 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package ops is an operations registry: one set of typed operations that several front-ends run the same way, such as a CLI, an MCP server (transport/mcp) and a scheduled job (transport/cron).

An operation is a descriptor (name, summary, safety class, lifecycle, timeout), usually loaded as data, paired with a Builder that binds it to the front-end's environment; Func adapts a plain function over a typed input, whose JSON Schema is reflected from the input's json tags. NewFromConfig builds a Registry from the builders, the descriptors and one ExposurePolicy: it admits only the operations the policy allows (safety-class ceiling, lifecycles, allow and deny lists) and wraps each run in recovery → timeout → logging → code mapping. The registry is a single-slot bulkhead: one operation runs at a time, and Reserve claims the slot before a run starts.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Decode

func Decode(op interfaces.Operation, raw []byte) (any, error)

Decode decodes raw JSON arguments into op's fresh input, rejecting a field the input does not declare — for a map input, a key its schema does not name, so a secret flag (types.FlagSpec.Secret) is refused too; empty or null raw leaves the input's defaults. A bad argument is CodeInvalidInput naming the operation. It is the decode for a caller that is not a human at the terminal (an MCP tool call, a CronJob's arguments).

func DecodeLocal added in v0.0.22

func DecodeLocal(op interfaces.Operation, raw []byte) (any, error)

DecodeLocal is Decode for a front-end where a human typed the arguments (the CLI): it also admits the operation's secret flags.

func FillInput added in v0.0.20

func FillInput(flags []types.FlagSpec, args types.ArgSpec, in map[string]any)

FillInput puts every key the caller omitted into in — a flag's default, else its type's zero; "" for an argument and no items for the rest — and coerces a sent flag to its type (JSON decodes every number as a float64, every list as []any), so a run reads every input the command declares, typed the same whichever front-end sent it. A composite that runs an operation's procedure directly, outside its MapInput operation, fills the same way so the procedure sees every declared input.

func InputSchema

func InputSchema(v any) []byte

InputSchema reflects v's json-tagged fields into an inlined, closed object schema with no $schema version (an MCP tool requires a top-level "type": "object"). Input types are static structs, so a marshal failure is a programming error and panics.

func MapSchema added in v0.0.19

func MapSchema(flags []types.FlagSpec, args types.ArgSpec) []byte

MapSchema returns the closed object schema of a map input: one property per flag (by its field) and per argument, required flags required. A secret flag has no property, so a schema-driven caller (an MCP tool) never asks for it.

Types

type Builder

type Builder[E any] func(desc types.OperationDescriptor, env E) interfaces.Operation

Builder builds one operation from its descriptor over the front-end's environment.

func Func

func Func[T, E any](
	run func(ctx context.Context, in *T, env E, obs interfaces.ExecutionObserver) error,
) Builder[E]

Func returns the Builder of an operation whose input is a T: front-ends decode into a fresh *T, its schema is reflected from T's json tags (a field type may supply its own schema with a JSONSchema method), and run receives a copy of the decoded input.

func MapInput added in v0.0.19

func MapInput[E any](
	flags []types.FlagSpec,
	args types.ArgSpec,
	run MapRun[E],
) Builder[E]

MapInput returns the Builder of an operation whose input is a map[string]any keyed by the flags' fields and the arguments' names, so a command defined as data (a CommandSpec) needs no Go input type; its schema is derived from the same specs, so the MCP tool of the operation takes exactly the command's flags.

func WithDefaults

func WithDefaults[T, E any](b Builder[E], seed func(in *T)) Builder[E]

WithDefaults wraps b so every input it hands out starts from seed's defaults, which a front-end's decode leaves unless the caller sets them.

type Config

type Config[E any] struct {
	// Env is the injected collaborators every admitted operation captures; a front-end
	// resolves the policy's Prompter kind into it before building.
	Env E

	// Log receives the operations' run logs and recovered panics; nil logs nothing.
	Log interfaces.Logger

	// Builders maps each operation name to its implementation.
	Builders map[string]Builder[E]

	// Descriptors are the operation descriptors, one per builder.
	Descriptors []types.OperationDescriptor

	// Policy decides which operations the registry admits.
	Policy types.ExposurePolicy
}

Config is what a front-end's composition root supplies to build its registry. E is the front-end's environment: the collaborators every operation captures.

type MapRun added in v0.0.19

type MapRun[E any] func(
	ctx context.Context,
	in map[string]any,
	env E,
	obs interfaces.ExecutionObserver,
) error

MapRun runs an operation over its map input.

type Registry

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

Registry holds the operations one front-end's exposure policy admits. Build it with NewFromConfig. Operations share the working tree, so the registry runs one at a time: a single slot, held by a named holder, that a second run fails fast against.

func NewFromConfig

func NewFromConfig[E any](cfg Config[E]) (*Registry, error)

NewFromConfig builds the registry of the operations cfg.Policy admits. It fails with CodeInvalidInput when the policy or a descriptor is invalid, when descriptors and builders don't pair one-to-one, when a builder is nil, when an admitted operation's builder returns no operation or seeds defaults for another input type, or when the policy's allow/deny names an unknown operation. Building does no I/O.

func (*Registry) Find

func (r *Registry) Find(name string) (interfaces.Operation, error)

Find returns the admitted operation named name, failing like Run when it is unknown (CodeNotFound) or excluded by the policy (CodeForbidden).

func (*Registry) List

func (r *Registry) List() []interfaces.Operation

List returns the admitted operations sorted by name.

func (*Registry) Lookup

func (r *Registry) Lookup(name string) (interfaces.Operation, bool)

Lookup returns the admitted operation named name; ok is false when the name is unknown or the policy excludes it.

func (*Registry) Reserve

func (r *Registry) Reserve(name, holder string) (interfaces.Reservation, error)

Reserve takes the working-tree slot for the admitted operation named name on behalf of holder (the operation's name, or a background run's id). It fails like Run for an unknown or excluded name, with CodeInvalidInput for an empty holder, and with CodeUnavailable "busy: <holder> is running" when the slot is held.

func (*Registry) Run

func (r *Registry) Run(
	ctx context.Context, name string, input any, obs interfaces.ExecutionObserver,
) error

Run reserves the working-tree slot and runs the admitted operation named name with a decoded input under its decorator chain. An undescribed name fails with CodeNotFound, a described name the policy excludes with CodeForbidden, and a held slot with CodeUnavailable naming its holder.

type Reservation

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

Reservation holds the registry's working-tree slot for one run of one operation. It is one-shot: Run releases the slot when the operation returns, and a Reservation that has run or been released cannot run again.

func (*Reservation) Release

func (s *Reservation) Release()

Release frees the slot without running, so a later Run fails; calling it again, or after Run, is a no-op.

func (*Reservation) Run

func (s *Reservation) Run(
	ctx context.Context,
	input any,
	obs interfaces.ExecutionObserver,
) error

Run runs the reserved operation under its decorator chain and then releases the slot. A nil ctx runs under context.Background(). A second call, or a call after Release, fails with CodeInternal and runs nothing.

Jump to

Keyboard shortcuts

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