From eb61b40f082751460d79f50c7273425058379ddc Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 1 Nov 2026 19:29:50 +0000 Subject: [PATCH] Sort only missing release assets once --- docs/roadmap/remediation.md | 9 +++++++- internal/delivery/remote_assets.go | 33 ++++++++++++------------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md index a6f9ce7..c20158f 100644 --- a/docs/roadmap/remediation.md +++ b/docs/roadmap/remediation.md @@ -300,7 +300,14 @@ avoidable asymptotic cost without claiming measured release-time savings. ## 7. Order the missing upload set once — P3 -Status: open. +Status: complete. + +**Completed:** Declaration validation now receives an unsorted collection. +Classification collects only missing declarations, and that owned worklist is +sorted in place once after the no-upload exits. Scheduling now takes +O(`n + k log k`) metadata work while retaining filename order and the manifest +last. Existing delivery/artifact tests passed, including manifest matching, +upload ordering, conflict/unverifiable stops, and partial recovery. **Evidence:** `orderedExpected`, `orderMissing`, and `uploadMissingAssets` in [shared asset delivery](../../internal/delivery/remote_assets.go). diff --git a/internal/delivery/remote_assets.go b/internal/delivery/remote_assets.go index b03b0ff..a481019 100644 --- a/internal/delivery/remote_assets.go +++ b/internal/delivery/remote_assets.go @@ -114,13 +114,17 @@ func remoteAssetDigest(ctx context.Context, backend forge.Backend, handle any) ( } func uploadMissingAssets(ctx context.Context, backend forge.Backend, releaseHandle any, inspection Inspection) (result UploadResult, err error) { - expected := orderedExpected(inspection.Assets) + expected := make([]artifact.ExpectedAsset, len(inspection.Assets)) + for index, observation := range inspection.Assets { + expected[index] = observation.Expected + } if err := artifact.ValidateExpectedAssets(expected); err != nil { return UploadResult{State: Partial}, &forge.Failure{Kind: forge.FailureInvalid, Err: err} } result = UploadResult{State: Partial} preflightConflict := false preflightUnverifiable := false + var missing []artifact.ExpectedAsset pending := make(map[string]struct{}, len(expected)) // Keep the initial observation order and compact pending identities once, // including on early returns after partial or unresolved uploads. @@ -141,6 +145,7 @@ func uploadMissingAssets(ctx context.Context, backend forge.Backend, releaseHand case AssetMissing: result.Pending = append(result.Pending, item) pending[observation.Expected.Filename] = struct{}{} + missing = append(missing, observation.Expected) case AssetConflicting: preflightConflict = true result.Conflicting = append(result.Conflicting, item) @@ -164,10 +169,8 @@ func uploadMissingAssets(ctx context.Context, backend forge.Backend, releaseHand return result, nil } - for _, item := range orderMissing(expected) { - if _, needsUpload := pending[item.Filename]; !needsUpload { - continue - } + orderMissing(missing) + for _, item := range missing { content, err := assetContent(item) if err != nil { return result, &forge.Failure{Kind: forge.FailureInvalid, Err: fmt.Errorf("open local release asset %q: %w", item.Filename, err)} @@ -226,18 +229,16 @@ func reconcileAssetUpload(ctx context.Context, backend forge.Backend, releaseHan } } -func orderMissing(expected []artifact.ExpectedAsset) []artifact.ExpectedAsset { - result := append([]artifact.ExpectedAsset(nil), expected...) - sort.Slice(result, func(i, j int) bool { - if result[i].Filename == artifact.ManifestFilename { +func orderMissing(missing []artifact.ExpectedAsset) { + sort.Slice(missing, func(i, j int) bool { + if missing[i].Filename == artifact.ManifestFilename { return false } - if result[j].Filename == artifact.ManifestFilename { + if missing[j].Filename == artifact.ManifestFilename { return true } - return result[i].Filename < result[j].Filename + return missing[i].Filename < missing[j].Filename }) - return result } func assetContent(asset artifact.ExpectedAsset) (io.ReadCloser, error) { @@ -247,14 +248,6 @@ func assetContent(asset artifact.ExpectedAsset) (io.ReadCloser, error) { return os.Open(asset.Path) } -func orderedExpected(observations []AssetInspection) []artifact.ExpectedAsset { - result := make([]artifact.ExpectedAsset, 0, len(observations)) - for _, observation := range observations { - result = append(result, observation.Expected) - } - return orderMissing(result) -} - func identity(asset artifact.ExpectedAsset) AssetIdentity { return AssetIdentity{Filename: asset.Filename, SHA256: asset.SHA256} }