1package authoring23import (4 "bytes"5 "encoding/json"6 "errors"7 "fmt"8 "io"9 "os"10 "path/filepath"11 "sort"12 "strings"13 "time"1415 "github.com/FelineStateMachine/atlas/format/vnext"16)1718const BuildReceiptFormat = "atlas-build-receipt/v3"1920// EvidenceSelection is the executable replay lock embedded in every authored21// Atlas. It identifies the exact immutable captures selected for a build; it22// deliberately contains no adapter or mapping output.23type EvidenceSelection struct {24 Format string `json:"format"`25 Project string `json:"project"`26 ProjectDigest string `json:"projectDigest"`27 Captures []SelectedCapture `json:"captures"`28}2930type SelectedCapture struct {31 Request string `json:"request"`32 Acquisition string `json:"acquisition"`33 Source string `json:"source"`34 Adapter string `json:"adapter"`35 AdapterVersion string `json:"adapterVersion"`36 SHA256 string `json:"sha256"`37 Length int64 `json:"length"`38 MediaType string `json:"mediaType"`39 CapturedAt string `json:"capturedAt"`40 License string `json:"license"`41 Attribution string `json:"attribution"`42 Set string `json:"set,omitempty"`43 SetDigest string `json:"setDigest,omitempty"`44 Termination *CaptureTermination `json:"termination,omitempty"`45 Pages []SelectedPage `json:"pages,omitempty"`46}4748type SelectedPage struct {49 Ordinal int `json:"ordinal"`50 Request string `json:"request"`51 Locator string `json:"locator"`52 SHA256 string `json:"sha256"`53 Length int64 `json:"length"`54 MediaType string `json:"mediaType"`55 CapturedAt string `json:"capturedAt"`56}5758func buildReceipt(plan Plan, captures map[string]Capture) ([]byte, string, error) {59 return buildReceiptSets(plan, captures, nil)60}6162func buildReceiptSets(plan Plan, captures map[string]Capture, sets map[string]CaptureSet) ([]byte, string, error) {63 selection := EvidenceSelection{64 Format: BuildReceiptFormat, Project: plan.Project, ProjectDigest: plan.ProjectDigest,65 }66 createdAt := ""67 for _, request := range plan.Requests {68 capture, ok := captures[request.ID]69 if !ok {70 return nil, "", fmt.Errorf("build selects no capture for request %s", request.ID)71 }72 selected := SelectedCapture{73 Request: request.ID, Acquisition: requestCacheKey(request.Request), Source: request.Source, SHA256: capture.Body.SHA256,74 Adapter: request.Adapter, AdapterVersion: request.AdapterVersion,75 Length: capture.Body.Length, MediaType: capture.Body.MediaType, CapturedAt: capture.CapturedAt,76 License: request.License, Attribution: request.Attribution,77 }78 if set, ok := sets[request.ID]; ok && set.Digest != "" {79 root, found := captureRootForRequest(set, request.Request)80 if !found {81 return nil, "", fmt.Errorf("capture set selects no root for request %s", request.ID)82 }83 termination := root.Termination84 selected.Set, selected.SetDigest, selected.Termination = set.ID, set.Digest, &termination85 selected.Pages = make([]SelectedPage, len(root.Pages))86 for index, page := range root.Pages {87 selected.Pages[index] = SelectedPage{88 Ordinal: index, Request: page.RequestHash, Locator: page.Locator,89 SHA256: page.Body.SHA256, Length: page.Body.Length, MediaType: page.Body.MediaType,90 CapturedAt: page.CapturedAt,91 }92 if page.CapturedAt > createdAt {93 createdAt = page.CapturedAt94 }95 }96 }97 selection.Captures = append(selection.Captures, selected)98 if capture.CapturedAt > createdAt {99 createdAt = capture.CapturedAt100 }101 }102 if createdAt == "" {103 return nil, "", fmt.Errorf("build selects no captured evidence")104 }105 sort.Slice(selection.Captures, func(i, j int) bool {106 if selection.Captures[i].Request != selection.Captures[j].Request {107 return selection.Captures[i].Request < selection.Captures[j].Request108 }109 return selection.Captures[i].Source < selection.Captures[j].Source110 })111 data, err := json.Marshal(selection)112 if err != nil {113 return nil, "", err114 }115 return append(data, '\n'), createdAt, nil116}117118func loadEvidenceSelection(path string) (EvidenceSelection, error) {119 if filepath.Ext(path) == ".atlas" {120 file, err := vnext.OpenFile(path, vnext.StandardSchema())121 if err != nil {122 return EvidenceSelection{}, fmt.Errorf("open replay Atlas: %w", err)123 }124 defer file.Close()125 volume, err := file.Volume()126 if err != nil {127 return EvidenceSelection{}, fmt.Errorf("read replay Atlas: %w", err)128 }129 for _, asset := range volume.Assets {130 if asset.ID == "build-receipt" {131 return decodeEvidenceSelection(asset.Data)132 }133 }134 return EvidenceSelection{}, fmt.Errorf("replay Atlas has no build receipt")135 }136 data, err := os.ReadFile(path)137 if err != nil {138 return EvidenceSelection{}, fmt.Errorf("read replay receipt: %w", err)139 }140 return decodeEvidenceSelection(data)141}142143func decodeEvidenceSelection(data []byte) (EvidenceSelection, error) {144 return ParseEvidenceSelection(data)145}146147// ParseEvidenceSelection strictly validates the executable receipt contract148// used by replay and the compiled-artifact inspector.149func ParseEvidenceSelection(data []byte) (EvidenceSelection, error) {150 var selection EvidenceSelection151 decoder := json.NewDecoder(bytes.NewReader(data))152 decoder.DisallowUnknownFields()153 if err := decoder.Decode(&selection); err != nil {154 return EvidenceSelection{}, fmt.Errorf("decode replay receipt: %w", err)155 }156 if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {157 if err == nil {158 return EvidenceSelection{}, fmt.Errorf("replay receipt carries more than one JSON value")159 }160 return EvidenceSelection{}, fmt.Errorf("decode replay receipt trailer: %w", err)161 }162 if selection.Format != BuildReceiptFormat || selection.Project == "" || len(selection.Captures) == 0 || validateSHA256("project", selection.ProjectDigest) != nil {163 return EvidenceSelection{}, fmt.Errorf("invalid replay receipt")164 }165 seen := make(map[string]bool, len(selection.Captures))166 previousRequest, previousSource := "", ""167 for _, capture := range selection.Captures {168 if validateSHA256("request", capture.Request) != nil || validateSHA256("acquisition", capture.Acquisition) != nil || validateSHA256("body", capture.SHA256) != nil ||169 capture.Source == "" || capture.Adapter == "" || capture.AdapterVersion == "" || capture.Length < 0 || strings.TrimSpace(capture.MediaType) == "" ||170 strings.TrimSpace(capture.License) == "" || strings.TrimSpace(capture.Attribution) == "" || seen[capture.Request] {171 return EvidenceSelection{}, fmt.Errorf("invalid replay capture for request %s", capture.Request)172 }173 if _, err := time.Parse(time.RFC3339Nano, capture.CapturedAt); err != nil {174 return EvidenceSelection{}, fmt.Errorf("invalid replay capture time for request %s", capture.Request)175 }176 if err := validateSelectedSet(capture); err != nil {177 return EvidenceSelection{}, err178 }179 if previousRequest > capture.Request || (previousRequest == capture.Request && previousSource > capture.Source) {180 return EvidenceSelection{}, fmt.Errorf("replay captures are not canonically ordered")181 }182 seen[capture.Request] = true183 previousRequest, previousSource = capture.Request, capture.Source184 }185 return selection, nil186}187188func validateSelectedSet(capture SelectedCapture) error {189 if capture.Set == "" && capture.SetDigest == "" && capture.Termination == nil && len(capture.Pages) == 0 {190 return nil191 }192 if validateSHA256("capture set", capture.Set) != nil || validateSHA256("capture set digest", capture.SetDigest) != nil || capture.Termination == nil || capture.Termination.Kind == "" || len(capture.Pages) == 0 {193 return fmt.Errorf("invalid capture set selection for request %s", capture.Request)194 }195 seen := make(map[string]bool, len(capture.Pages))196 for index, page := range capture.Pages {197 if page.Ordinal != index || validateSHA256("page request", page.Request) != nil || validateSHA256("page body", page.SHA256) != nil || page.Locator == "" || page.Length < 0 || page.MediaType == "" || seen[page.Request] {198 return fmt.Errorf("invalid selected page for request %s", capture.Request)199 }200 if _, err := time.Parse(time.RFC3339Nano, page.CapturedAt); err != nil {201 return fmt.Errorf("invalid selected page time for request %s", capture.Request)202 }203 seen[page.Request] = true204 }205 first := capture.Pages[0]206 if first.Request != capture.Acquisition || first.SHA256 != capture.SHA256 || first.Length != capture.Length || first.MediaType != capture.MediaType || first.CapturedAt != capture.CapturedAt {207 return fmt.Errorf("capture set primary page differs for request %s", capture.Request)208 }209 return nil210}211212func replayPlanEvidence(plan Plan, selection EvidenceSelection, cache *Cache) (buildEvidence, error) {213 legacy, hasSets := true, false214 for _, selected := range selection.Captures {215 if selected.SetDigest != "" {216 hasSets = true217 legacy = false218 }219 }220 if !hasSets && legacy {221 captures, err := replayCaptures(plan, selection, cache)222 if err != nil {223 return buildEvidence{}, err224 }225 return legacyBuildEvidence(plan, captures), nil226 }227 selected := make(map[string]SelectedCapture, len(selection.Captures))228 for _, capture := range selection.Captures {229 selected[capture.Request] = capture230 }231 if selection.Project != plan.Project || len(selected) != len(plan.Requests) {232 return buildEvidence{}, fmt.Errorf("replay receipt does not match project %s requests", plan.Project)233 }234 evidence := buildEvidence{Captures: make(map[string]Capture), Sets: make(map[string]CaptureSet), Cached: make(map[string]bool)}235 usedSets := make(map[string]CaptureSet)236 usedRequests := 0237 var usedBytes int64238 for _, group := range planCaptureGroups(plan) {239 first := selected[group.Requests[0].ID]240 set, ok := usedSets[first.SetDigest]241 if !ok {242 var err error243 set, err = cache.SelectSet(first.Set, first.SetDigest)244 if err != nil {245 return buildEvidence{}, fmt.Errorf("select replay capture set for %s: %w", group.Requests[0].Source, err)246 }247 requests := make([]Request, len(group.Requests))248 for index := range group.Requests {249 requests[index] = group.Requests[index].Request250 }251 if err := validateSetForRequests(set, requests); err != nil {252 return buildEvidence{}, err253 }254 usedSets[first.SetDigest] = set255 usedBytes += set.TotalBytes256 for _, root := range set.Roots {257 usedRequests += len(root.Pages)258 }259 }260 for _, planned := range group.Requests {261 want, ok := selected[planned.ID]262 if !ok || want.Set != set.ID || want.SetDigest != set.Digest || want.Source != planned.Source || want.Acquisition != requestCacheKey(planned.Request) || want.Adapter != planned.Adapter || want.AdapterVersion != planned.AdapterVersion || want.License != planned.License || want.Attribution != planned.Attribution {263 return buildEvidence{}, fmt.Errorf("replay receipt does not select capture set for request %s", planned.ID)264 }265 root, ok := captureRootForRequest(set, planned.Request)266 if !ok || !selectedRootMatches(want, root) {267 return buildEvidence{}, fmt.Errorf("replay capture set differs for request %s", planned.ID)268 }269 evidence.Captures[planned.ID] = root.Pages[0]270 evidence.Sets[planned.ID] = set271 evidence.Cached[planned.ID] = true272 }273 }274 if usedRequests > plan.Budgets.Requests || usedBytes > plan.Budgets.TotalBytes {275 return buildEvidence{}, fmt.Errorf("replay capture sets exceed build budgets")276 }277 return evidence, nil278}279280func selectedRootMatches(selected SelectedCapture, root CaptureRoot) bool {281 if selected.Termination == nil || selected.Termination.Kind != root.Termination.Kind || selected.Termination.Returned != root.Termination.Returned || !equalOptionalInt64(selected.Termination.Matched, root.Termination.Matched) || len(selected.Pages) != len(root.Pages) {282 return false283 }284 for index, page := range root.Pages {285 want := selected.Pages[index]286 if want.Ordinal != index || want.Request != page.RequestHash || want.Locator != page.Locator || want.SHA256 != page.Body.SHA256 || want.Length != page.Body.Length || want.MediaType != page.Body.MediaType || want.CapturedAt != page.CapturedAt {287 return false288 }289 }290 return true291}292293func equalOptionalInt64(left, right *int64) bool {294 if left == nil || right == nil {295 return left == nil && right == nil296 }297 return *left == *right298}299300func legacyBuildEvidence(plan Plan, captures map[string]Capture) buildEvidence {301 evidence := buildEvidence{Captures: captures, Sets: make(map[string]CaptureSet, len(captures)), Cached: make(map[string]bool, len(captures))}302 for _, planned := range plan.Requests {303 capture := captures[planned.ID]304 request := planned.Request305 set := CaptureSet{Format: CaptureSetFormat, ID: captureSetID([]Request{request}), Adapter: request.Adapter, Version: request.AdapterVersion, Kind: request.Kind, CapturedAt: capture.CapturedAt, TotalBytes: capture.Body.Length, Roots: []CaptureRoot{{Acquisition: requestCacheKey(request), Pages: []Capture{capture}, Termination: CaptureTermination{Kind: TerminationSinglePage}}}}306 evidence.Sets[planned.ID] = set307 evidence.Cached[planned.ID] = true308 }309 return evidence310}311312func replayCaptures(plan Plan, selection EvidenceSelection, cache *Cache) (map[string]Capture, error) {313 if selection.Project != plan.Project {314 return nil, fmt.Errorf("replay receipt belongs to project %s, not %s", selection.Project, plan.Project)315 }316 selected := make(map[string]SelectedCapture, len(selection.Captures))317 for _, capture := range selection.Captures {318 selected[capture.Request] = capture319 }320 if len(selected) != len(plan.Requests) {321 return nil, fmt.Errorf("replay receipt selects %d requests, project plans %d", len(selected), len(plan.Requests))322 }323 captures := make(map[string]Capture, len(plan.Requests))324 for _, request := range plan.Requests {325 want, ok := selected[request.ID]326 if !ok || want.Source != request.Source || want.Acquisition != requestCacheKey(request.Request) || want.Adapter != request.Adapter ||327 want.AdapterVersion != request.AdapterVersion || want.License != request.License || want.Attribution != request.Attribution {328 return nil, fmt.Errorf("replay receipt does not select source %s request %s", request.Source, request.ID)329 }330 capture, err := cache.Select(want.Acquisition, want.SHA256)331 if err != nil {332 return nil, fmt.Errorf("select replay capture for %s: %w", request.Source, err)333 }334 if capture.Body.Length != want.Length || capture.Body.MediaType != want.MediaType || capture.CapturedAt != want.CapturedAt {335 return nil, fmt.Errorf("replay capture metadata differs for source %s", request.Source)336 }337 captures[request.ID] = capture338 }339 return captures, nil340}