aifvalidate

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 11 Imported by: 0

README

go-aigentflow-validator

Static, offline validator for AIgentFlow workflow YAML — in Go.

It catches the structural and semantic problems in a flow definition before it reaches a server: missing required fields, dangling step references, malformed executor URIs, broken Go-template syntax, invalid input_schema fields, unbalanced query/response schemas, and more. It reproduces the static checks of the AIgentFlow Go engine, so a flow that passes here passes the server's structural validation.

This is the Go counterpart of aigentflow-flow-validator-js. The two share one vendored enum surface and one conformance corpus, and agree on error code — see PARITY.md.

  • Zero-config, offline, deterministic. No network, no filesystem, no credentials, no server.
  • One dependency. gopkg.in/yaml.v3 and nothing else.
  • WASM-safe. Builds for GOOS=js GOARCH=wasm, so the same rules run in a server, a CLI, and a browser.
  • Positioned findings. Every finding resolves to a line and column in the source.
  • Parity-tracked. Mirrors a pinned AIgentFlow flow-schema version, with a test that diffs against the JS implementation.

Scope: static checks only. It does not check credentials, the model-compliance catalogue, or runtime template field-resolution — those need a live server. See What it does not check.


Install

go get github.com/itsatony/go-aigentflow-validator

Requires Go 1.25+.

Usage

import aifvalidate "github.com/itsatony/go-aigentflow-validator"

result := aifvalidate.ValidateFlow(yamlSource, aifvalidate.Options{})
if !result.Valid {
    for _, e := range result.Errors {
        fmt.Printf("%s [%s] line %d: %s\n", e.Code, e.Field, e.Line, e.Message)
    }
}
for _, w := range result.Warnings {
    fmt.Printf("warning: %s [%s]: %s\n", w.Code, w.Field, w.Message)
}

Already have a decoded document? Use ValidateFlowObject(map[string]any, Options). It gives the same verdict, but cannot set Line/Column — there is no source to locate findings in.

Errors vs warnings

Valid is false if and only if Errors is non-empty. Warnings never lower the verdict, and a consumer that treats them as blocking will reject legitimate flows: the vendored allow-lists (executor schemes, template functions, orchestrator tools) can lag the live AIgentFlow registries, so an unrecognised-but-real name is reported as a warning on purpose.

Set Options{StrictRegistries: true} to promote unrecognised-name findings to errors. That is right for an authoring-time lint and wrong for admission control.

Errors and Warnings are always non-nil, so they encode as [] and never null.

Reporting which schema version judged a flow

Result.SpecVersion (and SpecVersion()) names the AIgentFlow flow-schema version whose rules produced the verdict. Surface it wherever you block on an error. A pinned validator can legitimately reject a flow written against a newer schema, and an author who cannot see which version rejected them has no way to understand it.

What it checks

Area Examples
Basic structure required top-level fields, step-map shape, per-step executor, reserved . in step IDs
Executors scheme://path well-formedness (error), unknown scheme (warning)
Connectivity next.default / next.conditions[].goto_step existence (error), unreachable steps + cycles (warning)
next.parallel rendezvous + member existence, non-empty fan-out, next: orchestrator needs an orchestrator
error_strategy action enum, goto_step existence, Go durations, backoff_multiplier > 0, retry_on categories
query schema param types, nested properties, array items types, min_items/max_items
response_expectation field data types, array items, required as bool-or-template
for_each / loop / throttle mutual exclusions, max_iterations bounds, sub-step ids, throttle ceilings
Credential bindings credential vs credentials exclusivity, stored/{provider}/{name} form, inject_as
input_schema / output_schema version, field names, types, constraint/type compatibility, visible_when, RE2 patterns
quality_gate rubric, threshold range, on_fail enum, self-goto, composite/parallel-member scope
Orchestrator / campaign exons presence, mode enum + owner-needs-yield, triggers, tools, campaign handoff
Templates Go text/template syntax across query, pre_processing, post_processing, conditions[].if
expression_functions exactly one of package / function, non-empty

What it does not check

  • Credentials. Only the reference form is validated. Nothing is read, resolved, or transported.
  • The model-compliance catalogue. Whether a named model is permitted is a server question.
  • Runtime template field resolution. Whether .data.fetch.title will exist at run time needs the live state graph. The companion unresolvable_data_path rule is therefore out of scope.
  • The orchestrator.exons body. Its presence is required; its contents belong to the go-exons engine.

Development

make ci             # fmt + vet + lint + test + wasm-check
make parity-check   # diff verdicts against the sibling JS implementation

make parity-check needs the JS checkout (JS_VALIDATOR_REPO, default ~/code/aigentflow-flow-validator-js) and rebuilds it first — the harness compares against the built CLI and skips loudly if that build trails this library's pinned schema version, so a stale build can never masquerade as parity drift.

Licence

MIT — see LICENSE.

Documentation

Overview

Package aifvalidate is a static, offline validator for AIgentFlow workflow YAML.

It reproduces the *static* verdicts of the AIgentFlow Go reference validator (Validator.ValidateFlowWithDetails + FlowParser.ValidateFlow) and is the Go counterpart of github.com/itsatony/aigentflow-flow-validator-js. Error Code strings are the cross-implementation parity contract; Message wording may differ between the two. See PARITY.md.

Scope: static checks only. Credentials, the model-compliance catalogue, and runtime template field-resolution require a live server and are out of scope.

The library performs no network or filesystem access and builds for GOOS=js GOARCH=wasm, so it can run in a server, a CLI, and a browser.

Index

Constants

View Source
const (
	// MaxDocumentBytes is the largest flow document accepted. A flow is a
	// human-authored declaration; anything past this is not one.
	MaxDocumentBytes = 4 << 20 // 4 MiB
	// MaxAliasTokens bounds YAML alias (`*ref`) usage, the "billion laughs"
	// amplification vector. The JS port caps alias EXPANSION at 100 via the yaml
	// package's maxAliasCount; yaml.v3 has its own internal budget but exposes no
	// knob, so this is an explicit pre-parse guard on the same class of input.
	MaxAliasTokens = 100
)

Document limits. These bound the work an untrusted document can cause, which matters because this validator is designed to sit on a publish gate: a caller should be able to hand it arbitrary bytes.

Variables

This section is empty.

Functions

func ExecutorSchemes

func ExecutorSchemes() []string

ExecutorSchemes returns the known executor URI schemes. Unknown schemes are WARNED, never rejected — the vendored list can lag the live registry.

func InputSchemaVersion

func InputSchemaVersion() int

InputSchemaVersion is the only supported `input_schema.version` value.

func SpecVersion

func SpecVersion() string

SpecVersion is the AIgentFlow flow-schema version whose static rules this validator tracks. Report it alongside any verdict: a consumer that BLOCKS on an error needs to tell an author which schema version judged their document, because a newer valid flow can legitimately fail an older pinned validator.

func TemplateFunctionNames

func TemplateFunctionNames() []string

TemplateFunctionNames returns the recognised Go-template function names (Go builtins plus the AIgentFlow registry). Exported because a consumer may want to offer them as editor completions.

Types

type Issue

type Issue struct {
	// Field is the dotted path to the offending value, e.g.
	// "steps.fetch.query.url". It is the primary way an author locates the
	// problem when Line is unavailable.
	Field string `json:"field"`
	// Message is the human-readable description. NOT part of the parity
	// contract — wording may differ from the JS implementation.
	Message string `json:"message"`
	// Code is the stable, machine-readable identifier. This IS the
	// cross-implementation parity contract; compare on Code, never Message.
	Code string `json:"code"`
	// Severity is "error" or "warning" ("info" reserved).
	Severity Severity `json:"severity"`
	// StepID names the step when the issue is step-specific.
	StepID string `json:"step_id,omitempty"`
	// Line and Column are 1-based positions in the source YAML, set only when
	// the finding could be located (parse errors always; structural findings
	// when the document was parsed with position tracking).
	Line   int `json:"line,omitempty"`
	Column int `json:"column,omitempty"`
	// Context carries extra orientation, e.g. the available step names.
	Context string `json:"context,omitempty"`
	// Suggestion is a concrete proposed fix.
	Suggestion string `json:"suggestion,omitempty"`
}

Issue is a single validation finding. Errors and warnings share this shape; a warning simply carries SeverityWarning and never lowers Result.Valid.

The JSON tags mirror the JS validator's ValidationIssue so a consumer can serialise either implementation's findings and render them with one type.

func ParseFlow

func ParseFlow(yamlText string) (flow map[string]any, parseErrors, parseWarnings []Issue)

ParseFlow decodes flow YAML into a plain mapping plus structured diagnostics. It never panics: malformed input is reported as parseErrors, and a non-empty parseErrors means the returned document is nil.

The returned slices are always non-nil.

type Options

type Options struct {
	// StrictRegistries promotes "unrecognised name" findings — an unknown
	// orchestrator tool, an unknown template function — from warning to error.
	//
	// OFF by default, deliberately: the vendored allow-lists can lag the live
	// AIgentFlow registries, and on a publish gate a false-positive error is
	// strictly worse than a missed lint. Turn it on for authoring-time linting,
	// not for admission control.
	StrictRegistries bool
}

Options tunes validation.

type Result

type Result struct {
	// Valid is true when there are zero error-severity issues. Warnings never
	// affect it.
	Valid bool `json:"valid"`
	// Errors are the blocking findings.
	Errors []Issue `json:"errors"`
	// Warnings are advisory findings. A consumer that treats these as blocking
	// will reject legitimate flows, because the vendored allow-lists (executor
	// schemes, template functions, orchestrator tools) can lag the live
	// AIgentFlow registries.
	Warnings []Issue `json:"warnings"`
	// Summary is the roll-up.
	Summary Summary `json:"summary"`
	// SpecVersion records which AIgentFlow flow-schema version produced this
	// verdict. Carried on the result (not just available via SpecVersion()) so a
	// verdict remains self-describing after it is serialised and stored.
	SpecVersion string `json:"spec_version"`
}

Result is the complete verdict for one flow.

Errors and Warnings are ALWAYS non-nil slices, even when empty, so the JSON encoding is `[]` and never `null`. A consumer's client code that calls an array method on a `null` is a real and expensive failure mode; a library is the right place to make it impossible.

func ValidateFlow

func ValidateFlow(yamlText string, opts Options) Result

ValidateFlow parses flow YAML and validates it.

Parse failures (syntax, duplicate keys, non-mapping root) come back as error-severity findings and the structural validators are skipped, because there is no usable document to inspect.

It never panics on malformed input, and never performs I/O. There is deliberately NO recover() at this boundary: a panic here would be a port bug, and swallowing it would report a broken document as valid — on a publish gate that is the worst possible failure. Every validator narrows `any` explicitly instead.

func ValidateFlowObject

func ValidateFlowObject(flow map[string]any, opts Options) Result

ValidateFlowObject validates an already-decoded flow mapping. Use it when the document came from a caller's own loader; use ValidateFlow to parse and validate in one step and to get source positions on findings.

type Severity

type Severity string

Severity of a validation issue. Info is reserved for future lints.

const (
	SeverityError   Severity = "error"
	SeverityWarning Severity = "warning"
	SeverityInfo    Severity = "info"
)

type Summary

type Summary struct {
	TotalSteps     int `json:"total_steps"`
	ValidSteps     int `json:"valid_steps"`
	ErrorCount     int `json:"error_count"`
	WarningCount   int `json:"warning_count"`
	TemplatesFound int `json:"templates_found"`
	TemplatesValid int `json:"templates_valid"`
}

Summary is the roll-up for one validation run.

Jump to

Keyboard shortcuts

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