name: cue description: CUE schema patterns for *.cue files, schema sync and behavioral sync tests, Go struct JSON tag alignment, parser/decode helpers, and error formatting. Use when editing invowkfile_schema.cue, invowkmod_schema.cue, config_schema.cue, pkg/cueutil, CUE-backed Go types, or CUE parsing/validation behavior.
CUE Schema Patterns
Always read .agents/rules/cue-patterns.md first; repository rules override this skill.
Schema Locations
pkg/invowkfile/invowkfile_schema.cuedefines invowkfile structure.pkg/invowkmod/invowkmod_schema.cuedefines invowkmod structure.internal/config/config_schema.cuedefines config.
Schema Compilation Pattern
For new parsing paths, prefer pkg/cueutil helpers instead of reimplementing
the CUE boilerplate. Use cueutil.ParseAndDecode*, cueutil.WithFilename, and
cueutil.FormatError when they fit. Direct CUE parsing is reserved for cases
that need custom lookup/merge behavior.
The helper-backed flow is still conceptually:
// Step 1: Compile the schema (embedded via //go:embed)
ctx := cuecontext.New()
schemaValue := ctx.CompileString(embeddedSchema)
if schemaValue.Err() != nil {
return nil, fmt.Errorf("internal error: failed to compile schema: %w", schemaValue.Err())
}
// Step 2: Compile user data and unify with schema
userValue := ctx.CompileBytes(data, cue.Filename(path))
if userValue.Err() != nil {
return nil, formatCUEError(userValue.Err(), path)
}
schema := schemaValue.LookupPath(cue.ParsePath("#DefinitionName"))
unified := schema.Unify(userValue)
if err := unified.Validate(cue.Concrete(true)); err != nil {
return nil, formatCUEError(err, path)
}
// Step 3: Decode to Go struct
var result GoStructType
if err := unified.Decode(&result); err != nil {
return nil, formatCUEError(err, path)
}
Key Points:
- Schema is embedded via
//go:embedfor single-binary distribution - Use
LookupPath()to get the root definition (e.g.,#Invowkfile,#Config) - Use
Validate(cue.Concrete(true))to ensure all values are concrete - Config parsing also uses concrete validation; defaults and optional fields belong in the CUE schema/default layer before decode.
- Always use
Decode()for type-safe extraction (see Decode Usage Rules below)
Reference Implementations:
pkg/invowkfile/parse.go:ParseBytes()- Invowkfile parsingpkg/invowkmod/parse.go:ParseInvowkmodBytes()- Invowkmod parsinginternal/config/config.go:decodeCUEConfigSource()- Config loading
Validation Responsibility Matrix
Validation is split between CUE and Go based on what each can handle. CUE is the schema source of truth, but Go may deliberately mirror constraints for direct construction, defense-in-depth, richer runtime checks, and drift-detection tests.
CUE Handles (Declarative, Schema-Level)
| Validation Type | CUE Construct | Example |
|---|---|---|
| Type checking | Native | name: string |
| Field format (regex) | =~ |
name: =~"^[a-zA-Z][a-zA-Z0-9_-]*$" |
| Enum values | Disjunction | "native" | "virtual-sh" | "virtual-lua" | "container" |
| Length limits | strings.MaxRunes() |
& strings.MaxRunes(256) |
| Range constraints | Expressions | >=0 & <=65535 |
| Required fields | No ? suffix |
name: string (required) |
| Optional fields | ? suffix |
description?: string (optional) |
| Closed structs | close({}) |
Rejects unknown fields |
| Mutual exclusivity | XOR constraints | See runtime config patterns |
| Non-empty lists | Pattern | [_, ...] (at least one) |
Go Handles (Dynamic, Runtime-Level)
| Validation Type | Why Go-Only | Example |
|---|---|---|
| ReDoS prevention | CUE cannot analyze regex complexity | ValidateRegexPattern() |
| File existence | Requires filesystem access | os.Stat() |
| Path traversal | Cross-platform normalization | filepath.Clean() checks |
| Cross-field logic | Conditional requirements | Runtime-specific fields |
| Command hierarchy | Requires tree analysis | Leaf-only args constraint |
| Length limits (defense-in-depth) | Some limits enforced in Go too | MaxNameLength checks |
Justification Comments
All Go-only validations MUST include a [GO-ONLY] comment explaining why:
// ValidateRegexPattern validates a user-provided regex pattern for safety.
// [GO-ONLY] ReDoS (Regular Expression Denial of Service) prevention MUST be in Go.
// CUE cannot analyze regex complexity or detect catastrophic backtracking patterns.
func ValidateRegexPattern(pattern string) error { ... }
// validateEnvFilePath validates an env file path for security.
// [GO-ONLY] Path traversal prevention and cross-platform path handling require Go.
// CUE cannot perform filesystem operations or cross-platform path normalization.
func validateEnvFilePath(path string) error { ... }
Decode Usage Rules
ALWAYS use value.Decode(&goStruct) for type-safe extraction.
Correct Pattern
var invowkfile Invowkfile
if err := unified.Decode(&invowkfile); err != nil {
return nil, formatCUEError(err, path)
}
Why Not Manual Extraction?
Manual extraction methods (String(), Int64(), Bool()) are error-prone:
- No compile-time type checking
- Requires manual error handling for each field
- Easy to forget optional field handling
- Doesn't leverage CUE's type system
Exception: Manual extraction is acceptable only for truly dynamic scenarios (e.g., extracting arbitrary user-defined keys). Document such cases.
Field Naming Convention
CUE uses snake_case, Go uses PascalCase. The JSON tag bridges them:
// CUE Schema (snake_case)
#Config: close({
container_engine: #ContainerEngine
includes: [...#IncludeEntry]
default_runtime: #RuntimeType
})
// Go Struct (PascalCase with JSON tags)
type Config struct {
ContainerEngine ContainerEngine `json:"container_engine"`
Includes []IncludeEntry `json:"includes"`
DefaultRuntime RuntimeMode `json:"default_runtime"`
}
Rule: Every non-bottom CUE field name that decodes into Go must have a matching
JSON tag in the corresponding Go struct. Bottom-field tombstones (_|_) are
intentionally skipped by sync helpers.
Verification: Schema sync tests (in *_sync_test.go files) catch mismatches at CI time.
Error Formatting Requirements
All CUE errors MUST include JSON path prefixes for clear error messages:
Error Format
<file-path>: <json-path>: <message>
Examples:
invowkfile.cue: cmds[0].implementations[2].script: value exceeds maximum length
config.cue: container.auto_provision.enabled: expected bool, got string
Implementation Pattern
Use pkg/cueutil for new parsing paths. Prefer cueutil.ParseAndDecode* helpers when they fit; use cueutil.FormatError for custom CUE flows that still need repo-standard file/path-prefixed errors.
Only import cuelang.org/go/cue/errors in low-level error-formatting utilities. Most parser code should not reimplement CUE error walking.
CUE Library Version Pinning
Read the current CUE version from go.mod or go list -m cuelang.org/go; do not
hard-code the version in this skill.
Upgrade Process
When upgrading the CUE library version:
- Review Changelog: Check for breaking changes in the CUE release notes
- Run Full Test Suite:
make testincluding all schema sync tests - Check API Deprecations: Search for deprecated function usage
- Verify Error Formats: Manually test that error messages still include paths
- Update Documentation: If CUE behavior changes, update this rules file
- Test Cross-Platform: Run CI on all platforms (Linux, macOS, Windows)
Known CUE Limitations
- No Encode API: CUE can decode Go structs but has no production-ready encoder
- No Code Generation:
gengotypesis experimental; we use sync tests instead - Context is Stateful: Create a new
cuecontext.New()for each parse operation
Rules
- All CUE structs must be closed (use
close({ ... })) so unknown fields cause validation errors. - When adding new CUE struct fields or definitions, always include appropriate validation constraints (e.g.,
strings.MaxRunes(), regex patterns with=~, range constraints like>=0 & <=255) - not just type declarations. This ensures defense-in-depth validation. - Schema sync tests MUST exist for every Go struct that corresponds to a CUE definition.
- Go-only validations MUST have
[GO-ONLY]comments explaining why CUE cannot handle them.
Schema Sync Tests
Sync tests verify Go struct JSON tags match CUE schema field names at CI time. They catch misalignments before they cause silent parsing failures.
Test Files:
pkg/invowkfile/sync_test.go- Invowkfile, Command, Implementation, etc.pkg/invowkfile/sync_runtime_test.goandpkg/invowkfile/sync_runtime_behavioral_test.go- runtime config shape and behaviorpkg/invowkmod/sync_test.go- Invowkmod, ModuleRequirementinternal/config/sync_test.go- Config, VirtualConfig, UIConfig, etc.
Pattern:
func TestStructNameSchemaSync(t *testing.T) {
schema, _ := getCUESchema(t)
cueFields := schematest.ExtractCUEFields(t, schematest.LookupDefinition(t, schema, "#DefinitionName"))
goFields := schematest.ExtractGoJSONTags(t, reflect.TypeFor[GoStructType]())
schematest.AssertFieldsSync(t, "StructName", cueFields, goFields)
}
When to Add Sync Tests:
- Adding a new CUE definition with a corresponding Go struct
- Adding new fields to existing CUE/Go types
- Renaming fields (test will fail until both are updated)
Common Pitfalls
Unclosed CUE Structs
Problem: Open structs allow arbitrary unknown fields, bypassing validation.
// WRONG: Open struct - unknown fields silently accepted
#Config: {
name: string
}
// CORRECT: Closed struct - unknown fields rejected
#Config: close({
name: string
})
Redundant Validation
Problem: Same validation in both CUE and Go creates maintenance burden.
Rule: Validation lives in ONE place. CUE handles format/type validation. Go handles security/filesystem/cross-field logic.
Anti-Pattern:
// WRONG: Duplicates CUE regex validation
if !regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]*$`).MatchString(name) {
return fmt.Errorf("invalid name format")
}
Correct Pattern:
// CORRECT: CUE handles format; Go handles length (defense-in-depth)
// [GO-ONLY] Length limit not in CUE for simplicity; checked here for defense-in-depth.
if len(name) > MaxNameLength {
return fmt.Errorf("name too long (%d chars, max %d)", len(name), MaxNameLength)
}
Missing JSON Tags
Problem: Go struct field without JSON tag won't be populated by Decode().
Symptom: Field is always zero value despite being in CUE file.
Fix: Add JSON tag matching the CUE field name:
type Config struct {
Includes []IncludeEntry `json:"includes"` // Matches CUE field
CachePath string // No JSON tag - will be empty!
}
Wrong Error Import
Problem: Using standard library errors instead of CUE's error package.
// WRONG: Standard library - no Path() function
import "errors"
// CORRECT: CUE error package with path extraction
import "cuelang.org/go/cue/errors"
Stale Schema Sync Tests
Problem: Sync test exclusions become outdated after refactoring.
Fix: After any CUE/Go struct changes, run make test and verify sync tests pass. Remove obsolete exclusions.