Documentation
¶
Overview ¶
Package manifest is the typed schema, loader, and structural validator for dockyard.app.yaml — Dockyard's control plane (RFC §4.2).
Every Dockyard app has a manifest at its root. It is the single artifact the Wave 7 CLI commands — validate, generate, dev, test, build, install — read; it makes the conventions explicit and is the one place the tool-to-UI wiring is visible. Phase 06 ships the schema + loader + validation only; it deliberately leaves a clean, typed Manifest API for those later commands to consume.
Loading ¶
Load and LoadFile parse YAML into the typed Manifest struct. A malformed document fails with an error that names the source and, where the YAML decoder reports a node position, the line — so an invalid manifest fails in Dockyard's own tooling rather than deep inside a host (RFC §4.2 acceptance).
Validation ¶
Manifest.Validate runs structural validation: required identity fields, well-formed semantic version, known transports and enums, unique tool and app names, well-formed ui:// URIs, and tool->app ui: cross-references. Every rejection is reported as a source-located *Error (or an *ErrorList for several at once). Validation is structural only — it never reads Go source or the filesystem; the quality.* gates (RFC §9.4) are parsed and shape-checked here but enforced by dockyard validate (Phase 18).
Contract resolution ¶
A tool's input and output are Go type references (RFC §6.1, contract-first); the manifest never duplicates schema. ResolveContracts turns each reference into a JSON Schema through the ContractResolver seam. Phase 06 ships a registry-backed resolver over internal/codegen.SchemaForType; the source-scanning resolver dockyard generate needs is Phase 18's, and satisfies the same one-method seam.
Index ¶
- Constants
- Variables
- func MergeDiscoveredApps(m *Manifest, discovered []DiscoveredApp) int
- func WriteDiscoveredApps(path string, discovered []DiscoveredApp) error
- type App
- type BundleStrategy
- type CSP
- type ContractReference
- type ContractResolver
- type DiscoveredApp
- type DisplayMode
- type Error
- type ErrorList
- type Manifest
- type Quality
- type RegistryResolver
- type Runtime
- type RuntimeUI
- type TaskSupport
- type Tasks
- type Tool
- type ToolContracts
- type Transport
- type UIFramework
- type Visibility
Constants ¶
const DefaultFilename = "dockyard.app.yaml"
DefaultFilename is the conventional manifest filename at an app's root.
Variables ¶
var ErrContractUnresolved = errors.New("dockyard/internal/manifest: contract reference unresolved")
ErrContractUnresolved is returned by a ContractResolver when a reference names no type it can resolve. Callers branch on it with errors.Is.
var ErrInvalidManifest = errors.New("dockyard/internal/manifest: invalid manifest")
ErrInvalidManifest is the sentinel every manifest loading or validation failure wraps, so callers can branch with errors.Is.
Functions ¶
func MergeDiscoveredApps ¶
func MergeDiscoveredApps(m *Manifest, discovered []DiscoveredApp) int
MergeDiscoveredApps merges discovered Apps into m in place and returns the number of new apps[] entries added (0 means every discovered App was already wired). It is the pure, side-effect-free core of WriteDiscoveredApps — exported so a caller can merge into an in-memory Manifest without a file.
func WriteDiscoveredApps ¶
func WriteDiscoveredApps(path string, discovered []DiscoveredApp) error
WriteDiscoveredApps merges discovered Apps into the manifest file at path and rewrites it (RFC §7.6 — the discovered tool↔UI wiring is written into dockyard.app.yaml so it stays inspectable).
The merge is conservative and idempotent:
- a discovered App whose id is not yet in apps[] is appended as a new entry;
- a discovered App whose id already exists is left untouched — a developer-authored entry (CSP opt-outs, display modes, visibility) is never overwritten by discovery;
- apps[] is sorted by id afterwards so the file is deterministic.
WriteDiscoveredApps parses the manifest *without* the cross-reference checks (so a manifest carrying a tools[].ui that points at an as-yet-undiscovered App — the natural pre-discovery state — is still mergeable), then runs full structural validation on the *merged* result before writing. It never writes an invalid dockyard.app.yaml: a merge whose result is still invalid (for example a still-orphan apps[] entry) fails here with a source-located error and the file on disk is left untouched.
Re-marshalling through yaml.v3 normalises formatting and does not preserve inline comments (D-058); the manifest is machine-authored after `dockyard new`, so this is acceptable for V1.
Types ¶
type App ¶
type App struct {
// ID is the app's manifest-local identifier; tools[].ui references it.
// Required and unique within the manifest.
ID string `yaml:"id"`
// URI is the ui:// resource URI the App is served under. Required, and must
// be a well-formed ui:// URI (RFC §7.1).
URI string `yaml:"uri"`
// Entry is the path to the App's UI entrypoint, relative to the project
// root (e.g. web/src/apps/customer-health.svelte). Required.
Entry string `yaml:"entry"`
// DisplayModes is the subset of inline|fullscreen|pip the App supports
// (RFC §7.2). At least one is required.
DisplayModes []DisplayMode `yaml:"display_modes"`
// CSP is the App's Content-Security-Policy opt-outs. An empty CSP is the
// secure default — a single-file bundle with no external origins (RFC §7.4).
CSP CSP `yaml:"csp"`
// Visibility is the surfaces the App is exposed to (RFC §7.1 _meta.ui).
Visibility []Visibility `yaml:"visibility"`
}
App is one entry of the manifest apps list — a ui:// resource and its host-facing metadata (RFC §4.2, §7).
type BundleStrategy ¶
type BundleStrategy string
BundleStrategy is how an App's UI is bundled (RFC §7.4).
const ( // BundleSingleFile inlines everything into one HTML file — zero external // origins, so the deny-by-default CSP just works. The default. BundleSingleFile BundleStrategy = "single-file" // BundleMultiFile keeps separate asset files — requires CSP opt-outs. BundleMultiFile BundleStrategy = "multi-file" )
func (*BundleStrategy) UnmarshalYAML ¶
func (b *BundleStrategy) UnmarshalYAML(node *yaml.Node) error
UnmarshalYAML decodes and validates a BundleStrategy.
type CSP ¶
type CSP struct {
// Connect is the set of origins the App may open network connections to
// (CSP connect-src).
Connect []string `yaml:"connect"`
// Resource is the set of origins the App may load passive resources from
// (images, fonts, styles).
Resource []string `yaml:"resource"`
}
CSP is the apps[].csp block: explicit Content-Security-Policy opt-outs. Each list is a set of origins the App's deny-by-default CSP is widened to allow (RFC §7.4). Both lists empty is the secure default.
type ContractReference ¶
type ContractReference struct {
// Package is the import path of the package the type lives in.
Package string
// TypeName is the exported type identifier within that package.
TypeName string
}
ContractReference is a parsed tools[].input / tools[].output value: the Go type a tool's contract resolves to (RFC §6.1, contract-first). Its wire form is "<package path>.<TypeName>", e.g. "internal/contracts.ShowCustomerHealthInput".
func ParseContractReference ¶
func ParseContractReference(ref string) (ContractReference, error)
ParseContractReference parses a tools[].input/output value into its package path and type name. It is the seam Wave 7's dockyard generate uses to locate the Go type to feed the codegen pipeline.
func (ContractReference) String ¶
func (r ContractReference) String() string
String renders the reference back to its "<package>.<TypeName>" wire form.
type ContractResolver ¶
type ContractResolver interface {
// Resolve returns the JSON Schema for the named contract reference. It
// returns an error wrapping ErrContractUnresolved when the reference names
// no known type.
Resolve(ref string) (*jsonschema.Schema, error)
}
ContractResolver turns a tools[].input/output Go type reference into its JSON Schema. It is the seam between the manifest and the codegen pipeline (RFC §6.1): Phase 06 ships RegistryResolver for tests and the example, and Wave 7's dockyard generate supplies a source-scanning resolver that satisfies the same one-method contract.
type DiscoveredApp ¶
type DiscoveredApp struct {
// ID is the manifest-local identifier — the apps[].id a tools[].ui targets.
ID string
// URI is the ui:// resource URI the App is served under.
URI string
// Entry is the .svelte source path relative to the project root.
Entry string
// DisplayModes is the subset of inline|fullscreen|pip the App supports.
// When empty, WriteDiscoveredApps defaults a new entry to [inline] — the
// minimal valid set (a manifest apps[] entry requires at least one mode,
// RFC §7.2); a developer widens it by editing dockyard.app.yaml.
DisplayModes []DisplayMode
}
DiscoveredApp is one App found by the runtime/apps convention discovery (RFC §7.6), in the shape WriteDiscoveredApps merges into a manifest. It is a deliberately small, manifest-package-local value type so internal/manifest never depends on the runtime: the CLI (Wave 7) maps an apps.DiscoveredApp onto this struct, keeping the dependency direction one-way.
type DisplayMode ¶
type DisplayMode string
DisplayMode is an App viewing style (RFC §7.2).
const ( // DisplayModeInline — an inline widget. DisplayModeInline DisplayMode = "inline" // DisplayModeFullscreen — a fullscreen view. DisplayModeFullscreen DisplayMode = "fullscreen" // DisplayModePIP — a picture-in-picture view. DisplayModePIP DisplayMode = "pip" )
func (*DisplayMode) UnmarshalYAML ¶
func (d *DisplayMode) UnmarshalYAML(node *yaml.Node) error
UnmarshalYAML decodes and validates a DisplayMode.
type Error ¶
type Error struct {
// Source is the manifest file path (or another identifier passed to Load).
Source string
// Line is the 1-based YAML line; 0 when no position is available, in which
// case the rendered message degrades to "file" rather than "file:line".
Line int
// Field is the dotted manifest path of the fault, e.g. "tools[0].input".
Field string
// Msg is the human-facing description.
Msg string
}
Error is a single source-located manifest fault. It names the source file and, where the YAML node position is known, the line — so an invalid manifest fails with a "file:line" message (RFC §4.2 acceptance). Error wraps ErrInvalidManifest.
type ErrorList ¶
type ErrorList []*Error
ErrorList aggregates several source-located faults found in a single pass, so Validate can report every problem at once rather than one per run. It wraps ErrInvalidManifest.
type Manifest ¶
type Manifest struct {
// Name is the app's wire identifier — a short, kebab-or-snake-case token.
Name string `yaml:"name"`
// Title is the human-facing display name.
Title string `yaml:"title"`
// Version is the app's semantic version (MAJOR.MINOR.PATCH).
Version string `yaml:"version"`
// Runtime declares which deployment modes and UI framework the app supports.
Runtime Runtime `yaml:"runtime"`
// Tools is the app's MCP tools. At least one is required.
Tools []Tool `yaml:"tools"`
// Apps is the app's ui:// resources. Optional — a plain MCP server has none
// (RFC §4.1: a UI resource is additive, not a requirement).
Apps []App `yaml:"apps"`
// Tasks holds the app's MCP Tasks lifecycle limits (RFC §8.5). Optional —
// an app with no task-supporting tool omits it and the zero value disables
// every limit.
Tasks Tasks `yaml:"tasks"`
// Quality holds the quality.* gates dockyard validate enforces (RFC §9.4).
Quality Quality `yaml:"quality"`
}
Manifest is the typed form of dockyard.app.yaml — Dockyard's control plane (RFC §4.2). It is produced by Load / LoadFile and is an immutable value after loading: its accessor methods (Tool, App, ResolveContracts) only read, so a loaded Manifest is safe for concurrent use.
func Load ¶
Load parses a manifest from r and structurally validates it. source is the human-facing origin (a file path, or any identifier) used to source-locate errors; it never opens the filesystem itself.
Load fails on: an I/O error reading r, a YAML syntax error, an unknown field, a bad enum value, or any structural-validation fault. Every failure wraps ErrInvalidManifest and, where a position is known, carries a "file:line".
func LoadFile ¶
LoadFile reads, parses, and structurally validates the manifest at path. It is the convenience entry point for the Wave 7 CLI commands: a nil error means the returned Manifest is well-formed and ready to consume.
func (*Manifest) ResolveContracts ¶
func (m *Manifest) ResolveContracts(r ContractResolver) (map[string]ToolContracts, error)
ResolveContracts resolves every tool's input and output reference through r, returning a map keyed by tool name. It fails fast on the first unresolved reference, wrapping the failure with the offending tool and field so the caller knows which manifest line to fix.
ResolveContracts only reads the Manifest, so it is safe to call concurrently on a loaded Manifest with a concurrency-safe resolver.
func (*Manifest) Tool ¶
Tool returns the named tool and true, or a zero value and false. The returned pointer aliases the Manifest's slice element — treat it as read-only.
func (*Manifest) Validate ¶
Validate runs structural validation on a Manifest loaded by some other means (Load already validates). It re-checks every rule without YAML positions, so reported faults name the source but not a line.
Validation is structural only: it checks required fields, well-formed values, known enums, uniqueness, and tool->app cross-references. It never reads Go source or the filesystem — resolving the tool contracts is ResolveContracts, and enforcing the quality.* gates is dockyard validate (Phase 18, RFC §9.4).
type Quality ¶
type Quality struct {
RequireLoadingState bool `yaml:"require_loading_state"`
RequireEmptyState bool `yaml:"require_empty_state"`
RequireErrorState bool `yaml:"require_error_state"`
RequirePermissionState bool `yaml:"require_permission_state"`
RequireFixtures bool `yaml:"require_fixtures"`
RequireContractTests bool `yaml:"require_contract_tests"`
RequireSpecCompliance bool `yaml:"require_spec_compliance"`
}
Quality holds the quality.* gates dockyard validate enforces (RFC §9.4). Every field defaults to false when omitted; the manifest opts each gate in.
type RegistryResolver ¶
type RegistryResolver struct {
// contains filtered or unexported fields
}
RegistryResolver is a ContractResolver backed by an explicit map of contract reference to Go type. It is how Phase 06 resolves contracts without scanning Go source: a caller registers each contract type, and Resolve runs it through internal/codegen.SchemaForType — the reflect-based seam Phase 04 shaped for exactly this use. Wave 7's dockyard generate replaces it with a source-scanning resolver behind the same ContractResolver interface.
A RegistryResolver is built once and then read-only, so it is safe for concurrent use; Register must not race with Resolve.
func NewRegistryResolver ¶
func NewRegistryResolver() *RegistryResolver
NewRegistryResolver returns an empty RegistryResolver.
func Register ¶
func Register[T any](r *RegistryResolver, ref string) *RegistryResolver
Register binds a contract reference to the Go type T, so a manifest naming ref resolves to T's schema. It returns the resolver for fluent chaining.
func (*RegistryResolver) Resolve ¶
func (r *RegistryResolver) Resolve(ref string) (*jsonschema.Schema, error)
Resolve implements ContractResolver: it looks the reference up in the registry and runs the bound type through the codegen schema engine.
type Runtime ¶
type Runtime struct {
// Transports is the set of deployment modes the app supports. V1: stdio,
// http (RFC §5.2). At least one is required.
Transports []Transport `yaml:"transports"`
// UI configures the App UI build. Optional — a plain MCP server omits it.
UI *RuntimeUI `yaml:"ui"`
}
Runtime is the manifest runtime block: which transports the app is deployed over and, for an App, how its UI is built (RFC §4.2, §7.4).
type RuntimeUI ¶
type RuntimeUI struct {
// Framework is the UI framework. V1: svelte only.
Framework UIFramework `yaml:"framework"`
// Bundle is the bundle strategy. single-file is the default and yields a
// zero-external-origin App so the deny-by-default CSP just works (RFC §7.4).
Bundle BundleStrategy `yaml:"bundle"`
}
RuntimeUI is the runtime.ui block: the UI framework and bundle strategy.
type TaskSupport ¶
type TaskSupport string
TaskSupport is a tool's declared relationship to the MCP Tasks extension (RFC §8.4). The zero value is TaskSupportForbidden.
const ( // TaskSupportForbidden — the tool never runs as a task. The default. TaskSupportForbidden TaskSupport = "forbidden" // TaskSupportOptional — the tool may run as a task or synchronously. TaskSupportOptional TaskSupport = "optional" // TaskSupportRequired — the tool always runs as a task. TaskSupportRequired TaskSupport = "required" )
func (*TaskSupport) UnmarshalYAML ¶
func (ts *TaskSupport) UnmarshalYAML(node *yaml.Node) error
UnmarshalYAML decodes and validates a TaskSupport.
type Tasks ¶
type Tasks struct {
// MaxTTLMillis is the largest task retention duration the runtime honours,
// in milliseconds. A requestor asking for more is clamped down. Zero means
// unlimited retention (no clamp).
MaxTTLMillis int64 `yaml:"max_ttl_millis"`
// DefaultTTLMillis is the retention applied to a task whose requestor
// expressed no TTL preference, in milliseconds. Zero means unlimited.
DefaultTTLMillis int64 `yaml:"default_ttl_millis"`
// PurgeIntervalMillis is how often the background TTL purge sweep runs, in
// milliseconds. Zero disables the sweep.
PurgeIntervalMillis int64 `yaml:"purge_interval_millis"`
// MaxConcurrentPerRequestor caps the number of non-terminal tasks one
// authorization context may hold at once. Zero means no cap.
MaxConcurrentPerRequestor int `yaml:"max_concurrent_per_requestor"`
}
Tasks is the manifest tasks block: the manifest-tunable MCP Tasks lifecycle limits (RFC §8.5). Every field defaults to zero — "no limit" — when omitted, which is the correct posture for an ephemeral single-user stdio app. A durable HTTP/Portico app sets explicit limits so a task leak cannot grow the store unbounded (brief 02 §4.6). The runtime maps these onto the Tasks engine's lifecycle controls.
type Tool ¶
type Tool struct {
// Name is the tool's MCP wire name. Required and unique within the manifest.
Name string `yaml:"name"`
// Description is the model-facing hint.
Description string `yaml:"description"`
// Input is the Go type reference for the tool input contract — a
// "pkg/path.TypeName" string the codegen pipeline resolves. Required.
Input string `yaml:"input"`
// Output is the Go type reference for the tool output contract. Required.
Output string `yaml:"output"`
// UI links the tool to an apps[] entry by its id (RFC §4.2). Optional — a
// tool without a UI is a plain MCP tool.
UI string `yaml:"ui"`
// TaskSupport declares the tool's relationship to the Tasks extension
// (RFC §8.4). Defaults to forbidden when omitted.
TaskSupport TaskSupport `yaml:"task_support"`
}
Tool is one entry of the manifest tools list (RFC §4.2). A tool's input and output are Go type references resolved by the codegen pipeline (RFC §6.1).
type ToolContracts ¶
type ToolContracts struct {
// Input is the resolved schema for the tool's input contract.
Input *jsonschema.Schema
// Output is the resolved schema for the tool's output contract.
Output *jsonschema.Schema
}
ToolContracts is a tool's resolved input and output schemas, produced by ResolveContracts.
type UIFramework ¶
type UIFramework string
UIFramework is the App UI framework (RFC §4.2). V1: svelte only.
const UIFrameworkSvelte UIFramework = "svelte"
UIFrameworkSvelte is Svelte — the only V1 UI framework.
func (*UIFramework) UnmarshalYAML ¶
func (f *UIFramework) UnmarshalYAML(node *yaml.Node) error
UnmarshalYAML decodes and validates a UIFramework.
type Visibility ¶
type Visibility string
Visibility is a surface an App is exposed to (RFC §7.1, _meta.ui.visibility).
const ( // VisibilityModel — the App is offered to the model. VisibilityModel Visibility = "model" // VisibilityApp — the App is offered to the host application surface. VisibilityApp Visibility = "app" )
func (*Visibility) UnmarshalYAML ¶
func (v *Visibility) UnmarshalYAML(node *yaml.Node) error
UnmarshalYAML decodes and validates a Visibility.