manifest

package
v0.7.0 Latest Latest
Warning

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

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

Documentation

Overview

Package manifest defines Shipwright's declarative workflow manifest schema (design.md D-H, the workflow-manifest spec): the typed Go structs a YAML workflow document decodes into, plus stages 1-3 of the fixed seven-stage validation pipeline — size-capped read + decode (stage 1), document identity (stage 2), and structure (stage 3).

Stages 4-7 (references, graph, provider resolution, value binding) belong to later packages (internal/workflow/interp, internal/workflow/graph, internal/workflow/providers) and are deliberately NOT implemented here — see Parse's doc comment.

Schema drift enforcement mirrors pkg/shipwright's guaranteed-surface golden (design.md D-E): testdata/schema.golden records the accepted field set, and any change to it forces a deliberate, reviewed `-update` diff and an explicit apiVersion decision in the same PR (design.md D-H).

Index

Constants

View Source
const MaxManifestBytes = 1 << 20

MaxManifestBytes is the hard cap on a manifest's raw byte size before decode (design.md D-H threat matrix item 1, tasks.md 4.4). A manifest read through Parse/ParseFile that exceeds this size is rejected before any YAML decoding happens.

Together with the typed schema (no field capable of holding arbitrary depth other than Step.With/Step.When, both bounded further by this raw byte cap), this is the deliberate defense against alias-amplification ("billion laughs") documents. yaml.v3's own internal alias-ratio protection is a backstop only — design.md is explicit that this package must not rely on it alone.

1 MiB is a test constant proposed in design.md, not a contract element (design.md Open Questions): tune it if a legitimate manifest ever needs more, or if the billion-laughs fixture in security_test.go demands a tighter bound.

Variables

This section is empty.

Functions

func ValidateExecutable

func ValidateExecutable(m *Manifest) error

ValidateExecutable is the execution-only fail-close gate (design.md D1). Deliberately NOT called by Parse/ParseFile, so a read-only command (e.g. --list-steps) still parses and displays these controls; the execution path must call this itself before any Dagger connection.

Fixed, first-offender order: approvals (sorted names) → policies.* → maxParallel > 1. No override.

func ValidateIdentity

func ValidateIdentity(m *Manifest) error

ValidateIdentity implements stage 2 of the fixed seven-stage validation pipeline (design.md D-H, workflow-manifest spec "Versioned Document Identity"): apiVersion and kind must be present and within their allowlists, and metadata.name must be present. Nothing about step structure is inspected here — that is ValidateStructure's job (stage 3).

func ValidateStructure

func ValidateStructure(m *Manifest) error

ValidateStructure implements stage 3 of the fixed seven-stage validation pipeline (design.md D-H, workflow-manifest spec "capability Is The Contract, uses Is The Implementation"): every step id is non-empty and unique, capability is one of the five contract types, uses is present (a provider or a module), and uses.version is non-empty.

needs[]/when/with are parsed by the schema but their CONTENTS are not interpreted here — resolving needs against known step ids belongs to stage 4 (internal/workflow/interp) and stage 5 (internal/workflow/graph, cycle/reference validation), which this package deliberately does not implement.

Types

type ApprovalSpec

type ApprovalSpec struct {
	Required []string `yaml:"required,omitempty"`
}

ApprovalSpec is declared, queryable metadata only — see Environment's doc comment and design.md D-M.

type ArtifactsPolicy

type ArtifactsPolicy struct {
	Immutable *bool `yaml:"immutable,omitempty"`
}

ArtifactsPolicy declares whether published artifacts must be immutable. No layer enforces it; see the Policies comment. Immutable is a pointer: declaring it `false` still asserts a guarantee this engine does not provide, and must fail closed too.

type ConcurrencySpec

type ConcurrencySpec struct {
	MaxParallel int `yaml:"maxParallel,omitempty"`
}

ConcurrencySpec bounds parallel execution within a wave.

type DependenciesPolicy

type DependenciesPolicy struct {
	ForbidCycles *bool `yaml:"forbidCycles,omitempty"`
}

DependenciesPolicy declares whether cycles are forbidden. Acyclicity is already enforced unconditionally at stage 5; see the Policies comment for why the flag is rejected even when declared `false`.

type Environment

type Environment struct {
	Approvals *ApprovalSpec `yaml:"approvals,omitempty"`
}

Environment names a deployment target and its declared approval metadata. The engine never blocks, queues, or waits on approvals (design.md D-M); any DECLARED approvals block — even an empty one — is rejected outright by ValidateExecutable (see the Policies comment). Approvals is a pointer so ValidateExecutable can distinguish "omitted" (nil) from "declared but empty" (non-nil, Required == nil): a manifest author writing `approvals: {}` still asserts a control this engine does not implement, and must fail closed the same as a populated block.

type ExecutionSpec

type ExecutionSpec struct {
	Concurrency ConcurrencySpec `yaml:"concurrency,omitempty"`
	FailFast    bool            `yaml:"failFast,omitempty"`
	Timeout     string          `yaml:"timeout,omitempty"`
}

ExecutionSpec declares scheduling intent. MaxParallel (via Concurrency) is validated and recorded as an upper bound (design.md D-K); the engine (Phase 8, out of scope here) is not required to use it to widen execution — sequential waves are a correct schedule for any maxParallel >= 1.

type Manifest

type Manifest struct {
	APIVersion string   `yaml:"apiVersion"`
	Kind       string   `yaml:"kind"`
	Metadata   Metadata `yaml:"metadata"`
	Spec       Spec     `yaml:"spec"`
}

Manifest is the top-level document shape: APIVersion + Kind identify the schema version (stage 2, ValidateIdentity); Metadata names the workflow; Spec holds everything else.

func Parse

func Parse(r io.Reader) (*Manifest, error)

Parse decodes and validates (stages 1-3 of the fixed seven-stage pipeline, design.md D-H) a manifest read from r. Stage 1 (size-capped read + decode) happens first and unconditionally: r's bytes are never handed to the YAML decoder directly, only a byte slice already proven to be within MaxManifestBytes (see readCapped).

func ParseFile

func ParseFile(path string) (*Manifest, error)

ParseFile opens path, applies the MaxManifestBytes cap, decodes with KnownFields(true), and runs stages 2-3 validation (ValidateIdentity, ValidateStructure). It does not run stages 4-7 (references, graph, provider resolution, value binding) — those belong to internal/workflow/interp, internal/workflow/graph, and internal/workflow/providers.

type Metadata

type Metadata struct {
	Name        string            `yaml:"name"`
	Description string            `yaml:"description,omitempty"`
	Labels      map[string]string `yaml:"labels,omitempty"`
}

Metadata identifies the workflow document itself.

type Policies

type Policies struct {
	Secrets      SecretsPolicy      `yaml:"secrets,omitempty"`
	Providers    ProvidersPolicy    `yaml:"providers,omitempty"`
	Dependencies DependenciesPolicy `yaml:"dependencies,omitempty"`
	Artifacts    ArtifactsPolicy    `yaml:"artifacts,omitempty"`
}

Policies declares intent this schema parses but does not itself enforce (workflow-manifest spec, "Policies Are Declared As Structured, Enforceable Schema Fields"). ANY declaration — even an explicit `false`, and even one whose guarantee already holds unconditionally, like Providers.RequireVersion or Dependencies.ForbidCycles — is rejected by ValidateExecutable, an execution-only stage NOT run by Parse/ParseFile. Each flag below is a pointer so ValidateExecutable can tell "omitted" (nil) apart from "declared" (non-nil, at any value) — see each field's comment.

type ProvidersPolicy

type ProvidersPolicy struct {
	RequireVersion *bool `yaml:"requireVersion,omitempty"`
}

ProvidersPolicy declares provider-version intent. An empty uses.version is already unconditionally rejected by ValidateStructure regardless of this flag; see the Policies doc comment for why the flag is rejected even when declared `false`.

type SecretSpec

type SecretSpec struct {
	FromEnv string `yaml:"fromEnv"`
}

SecretSpec is a NAME REFERENCE to a secret resolved from an external source (an environment variable today) — never an inline plaintext value (workflow-manifest spec, "Secrets Referenced By Name, Never Embedded As Plaintext"). This struct deliberately declares no field capable of holding a literal secret value: a manifest attempting `spec.secrets.<name>.value: "..."` fails at stage 1 decode (yaml.Decoder.KnownFields(true) rejects the unknown `value` field), not at a later validation stage — the schema shape itself is the enforcement mechanism, mirroring D-D's retyping of credentials to *dagger.Secret and the internal/pipelines/shared/docker.go client.SetSecret pattern this mechanism must reuse.

type SecretsPolicy

type SecretsPolicy struct {
	ForbidPlaintext *bool `yaml:"forbidPlaintext,omitempty"`
}

SecretsPolicy declares whether a secret reference is forbidden in a non-secret-typed field. No layer enforces it; see the Policies comment. ForbidPlaintext is a pointer: declaring it `false` still asserts a guarantee this engine does not provide, and must fail closed too.

type SourceSpec

type SourceSpec struct {
	Path          string `yaml:"path,omitempty"`
	Repo          string `yaml:"repo,omitempty"`
	Ref           string `yaml:"ref,omitempty"`
	AuthSecretRef string `yaml:"authSecretRef,omitempty"`
}

SourceSpec selects the workflow's input source: a local Path, or a Git Repo+Ref pinned pair with an optional AuthSecretRef. Which combination a given provider actually requires is a later-stage (references/graph) concern, not a structural one — this package parses the shape only.

type Spec

type Spec struct {
	Source       SourceSpec             `yaml:"source"`
	Variables    map[string]string      `yaml:"variables,omitempty"`
	Secrets      map[string]SecretSpec  `yaml:"secrets,omitempty"`
	Steps        []Step                 `yaml:"steps"`
	Execution    ExecutionSpec          `yaml:"execution,omitempty"`
	Environments map[string]Environment `yaml:"environments,omitempty"`
	Policies     Policies               `yaml:"policies,omitempty"`
}

Spec is the workflow's declarative body.

type Step

type Step struct {
	ID         string              `yaml:"id"`
	Capability string              `yaml:"capability"`
	Uses       UsesSpec            `yaml:"uses"`
	Needs      []string            `yaml:"needs,omitempty"`
	Input      string              `yaml:"input,omitempty"`
	With       map[string]any      `yaml:"with,omitempty"`
	When       map[string][]string `yaml:"when,omitempty"`
	Attempts   *int                `yaml:"attempts,omitempty"`
}

Step is one node in the workflow DAG. Capability is the contract (one of public-module-api's five capability interfaces, named build/test/ artifact/deploy/run); Uses is the implementation. Needs declares explicit DAG edges — a step's position in Spec.Steps never implies an edge (workflow-manifest spec, "Explicit DAG Edges Via needs[]"). Outputs is deliberately NOT a field here: every capability returns exactly one typed result, addressable as `${{ steps.<id>.output }}` (design.md D-H schema simplification — the proposal's field list was explicitly non-normative).

type UnenforceableControlError

type UnenforceableControlError struct {
	Field  string
	Detail string
}

UnenforceableControlError reports a declared control that is parsed but not enforced at runtime (design.md D2).

func (*UnenforceableControlError) Error

func (e *UnenforceableControlError) Error() string

type UsesSpec

type UsesSpec struct {
	Provider string `yaml:"provider,omitempty"`
	Module   string `yaml:"module,omitempty"`
	Version  string `yaml:"version"`
}

UsesSpec names the step's implementation: either an in-repo Provider or an external Module (never both meaningfully at once — which is used is a stage-6 provider-resolution concern), both pinned to a required Version. Version is design.md D-E's provider-version axis — independent of, and not covered by, ContractVersion's compatibility guarantee.

Jump to

Keyboard shortcuts

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