package atlas import ( "encoding/json" "fmt" ) // Host is a substrate machine/entry (koala, iguana, …). type Host struct { Name string `json:"n"` Spec string `json:"k"` } // Node is a card within a stage. Plain is the jargon-free default text; Desc is // the technical detail shown on demand. type Node struct { Title string `json:"t"` PlainTitle string `json:"plain_t,omitempty"` Plain string `json:"plain,omitempty"` Desc string `json:"d,omitempty"` Pill string `json:"pill,omitempty"` Cls string `json:"cls,omitempty"` Tags []string `json:"tags,omitempty"` Risk bool `json:"risk,omitempty"` Gate bool `json:"gate,omitempty"` URL string `json:"url,omitempty"` } // Stage is one column of the pipeline. PlainTitle/Plain are the plain-language // default layer; Title/Path/Nodes[].Desc are the technical layer. TransLabel/ // Trans annotate the outgoing transition (the arrow to the next stage): what // moves work forward and what must be true to advance. When Generate is set, // Nodes are derived from a live source at Build time. type Stage struct { No string `json:"no"` Short string `json:"short,omitempty"` Title string `json:"title"` PlainTitle string `json:"plain_title,omitempty"` Plain string `json:"plain,omitempty"` Path string `json:"path,omitempty"` Cls string `json:"cls,omitempty"` Generate string `json:"generate,omitempty"` TransLabel string `json:"trans_label,omitempty"` Trans string `json:"trans,omitempty"` Nodes []Node `json:"nodes"` } // Atlas is the full data model the frontend renders. type Atlas struct { Version string `json:"version,omitempty"` Substrate []Host `json:"substrate"` NS string `json:"ns,omitempty"` Timeline []RunDot `json:"timeline,omitempty"` Stages []Stage `json:"stages"` } // Build unmarshals the authored atlas JSON and overlays generated facts from // real sources, so sourced parts can't drift. Currently: any stage marked // `"generate":"ci-jobs"` gets its Nodes replaced by the workflow's job list. func Build(atlasJSON, workflow []byte) (Atlas, error) { var a Atlas if err := json.Unmarshal(atlasJSON, &a); err != nil { return Atlas{}, fmt.Errorf("parse atlas data: %w", err) } for i := range a.Stages { if a.Stages[i].Generate != "ci-jobs" { continue } jobs, err := JobsFromWorkflow(workflow) if err != nil || len(jobs) == 0 { continue // no CI job list available — keep the authored plain placeholder } nodes := make([]Node, 0, len(jobs)) for _, j := range jobs { nodes = append(nodes, Node{Title: j, Plain: "An automated check that must pass."}) } a.Stages[i].Nodes = nodes } return a, nil }