package installplan import ( "crypto/sha256" "encoding/hex" "encoding/json" "errors" "server-deploy/internal/preflight" ) type Draft struct { Executable bool `json:"executable"` RepositoryAuthenticated bool `json:"repositoryAuthenticated"` LockDigest string `json:"lockDigest"` ObservationDigest string `json:"observationDigest"` Blockers []string `json:"blockers"` RequestedPackages []Package `json:"requestedPackages"` Impacts []string `json:"impacts"` } // Build binds requested package pins to a local observation for review only. // Neither digest is a signature, host identity, freshness token or approval. func Build(report preflight.Report, lock Lock) (Draft, error) { digest, err := Validate(lock, lock.Suite, lock.Architecture) if err != nil { return Draft{}, err } if report.Runtime.OS == "linux" && (report.Runtime.Architecture != lock.Architecture || (report.Distribution.State == "observed" && (report.Distribution.ID != "ubuntu" || report.Distribution.Codename != lock.Suite))) { return Draft{}, errors.New("lock does not match local platform") } proposal := preflight.Plan(report) blockers := []string{} for _, b := range proposal.Blockers { if b != "package_versions_unresolved" { blockers = append(blockers, b) } } blockers = append(blockers, "dependency_transaction_unresolved", "artifact_bytes_unverified", "repository_metadata_freshness_unverified") raw, _ := json.Marshal(report) sum := sha256.Sum256(raw) return Draft{LockDigest: digest, ObservationDigest: "sha256:" + hex.EncodeToString(sum[:]), Blockers: blockers, RequestedPackages: append([]Package{}, lock.Packages...), Impacts: []string{"package_database_and_repository_changes", "services_may_start_during_package_install", "host_network_rules_may_change", "additional_dependencies_not_yet_resolved"}}, nil }