Files
orca/internal/jobspec/spec.go
T
Jon Chery 667f20a7b3 feat(P0b): Markdown jobspec parser + dispatcher + fuzz harness (REQ-064,067)
P0b — Canonical Markdown+frontmatter jobspec parser (R-013/R-014).

Parser (internal/jobspec/markdown.go, REQ-064):
- WorkloadSpec/RuntimeBlock/PortSpec/VolumeSpec types. ParseMarkdown
  hand-rolled YAML frontmatter (no yaml.v3 dep). Kind validation (Job/
  Service/DaemonSet per R-012). BOM-stripped frontmatter, byte-exact body
  preservation (R-015) via the fuzz harness.

Dispatcher (internal/jobspec/dispatch.go, REQ-064):
- ParseFile/Dispatch routes on extension: .md->Markdown, .yaml/.yml->
  Markdown-with-empty-body, .hcl->ParseHCL adapter. HCL adapter converts
  Spec{Job,Tasks} to *WorkloadSpec (Kind=Job, Runtime.one_of=process).
  Backward compat preserved (REQ-090) — orca job run old-spec.hcl works.
- Legacy Parse renamed ParseHCLLegacy, marked // Deprecated per R-013.

Fuzz harness (internal/jobspec/markdown_fuzz_test.go, REQ-067, R-015):
- FuzzParseMarkdownRoundTrip with 10 seed corpus entries (CRLF, BOM,
  no-frontmatter, only-closing-separator, code-fence ---, trailing
  whitespace, empty body, etc). Asserts byte-exact body round-trip.

Tests: markdown_test.go (19 tests), dispatch_test.go (17 tests), fuzz
(10 seeds). jobspec package 89.2% coverage. cli 81.8% (no regression).

18 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.

---ci---
project: orca
phase: P0b
milestone: v0.9
status: execute
---/ci---
2026-08-05 17:02:33 +00:00

69 lines
2.0 KiB
Go

package jobspec
import (
"fmt"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclsimple"
)
// Spec is the legacy HCL-only jobspec shape. It is retained for the
// v0.9→v0.10 migration window (REQ-090) and is populated by ParseHCLLegacy.
//
// Deprecated: HCL is legacy per R-013; new code should consume the
// unified *WorkloadSpec returned by ParseFile/Dispatch (see
// dispatch.go and markdown.go).
type Spec struct {
Job JobSpec `hcl:"job,block"`
Tasks []TaskSpec `hcl:"task,block"`
}
// JobSpec is the legacy HCL job block.
type JobSpec struct {
Name string `hcl:"name,label"`
Type string `hcl:"type,optional"`
}
// TaskSpec is the legacy HCL task block.
type TaskSpec struct {
Name string `hcl:"name,label"`
Command string `hcl:"command"`
Args []string `hcl:"args,optional"`
Env []string `hcl:"env,optional"`
}
// hclDecode wraps hclsimple.Decode for testability.
func hclDecode(filename string, data []byte, spec *Spec) error {
return hclsimple.Decode(filename, data, nil, spec)
}
// Validate is the legacy HCL Spec validator retained for the migration
// window (REQ-090). New code should use validateWorkload on a
// *WorkloadSpec.
func (s *Spec) Validate() error {
if strings.TrimSpace(s.Job.Name) == "" {
return fmt.Errorf("job name is required")
}
if len(s.Tasks) == 0 {
return fmt.Errorf("at least one task is required")
}
return nil
}
// Parse is the original HCL-only entry point retained for backward
// compatibility with direct HCL callers during the v0.9→v0.10 migration
// window (REQ-090). New code should call the dispatcher ParseFile (which
// returns *WorkloadSpec) or ParseHCL (which adapts HCL into
// *WorkloadSpec).
//
// Deprecated: use ParseFile (dispatcher) or ParseHCL (adapter). HCL is
// legacy per R-013.
func Parse(data []byte, filename string) (*Spec, error) {
return ParseHCLLegacy(data, filename)
}
var _ = hcl.Diagnostics{}
var _ = gohcl.DecodeBody