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 ¶
- func Decode(op interfaces.Operation, raw []byte) (any, error)
- func InputSchema(v any) []byte
- func MapSchema(flags []types.FlagSpec, args types.ArgSpec) []byte
- type Builder
- type Config
- type MapRun
- type Registry
- func (r *Registry) Find(name string) (interfaces.Operation, error)
- func (r *Registry) List() []interfaces.Operation
- func (r *Registry) Lookup(name string) (interfaces.Operation, bool)
- func (r *Registry) Reserve(name, holder string) (interfaces.Reservation, error)
- func (r *Registry) Run(ctx context.Context, name string, input any, obs interfaces.ExecutionObserver) error
- type Reservation
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; empty raw leaves the input's defaults. A bad argument is CodeInvalidInput naming the operation.
func InputSchema ¶
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.
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
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 ¶
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 ¶
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.