Use one delivery asset verification pass
This commit is contained in:
@@ -134,6 +134,50 @@ func TestDeliverPrebuiltArtifactsRemainCallerOwned(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeliverRejectsSameSizePrebuiltTamperingBeforeHostedWrites(t *testing.T) {
|
||||
repository := newTaggedDeliveryConsumer(t)
|
||||
prebuilt, err := releasekit.Build(context.Background(), releasekit.BuildRequest{
|
||||
RepositoryDir: repository,
|
||||
Version: "v1.2.3",
|
||||
Spec: releasekit.BuildSpec{
|
||||
MainPackage: "./cmd/app",
|
||||
BinaryName: "fixture",
|
||||
Targets: []releasekit.Target{{OS: runtime.GOOS, Arch: runtime.GOARCH}},
|
||||
},
|
||||
OutputDir: filepath.Join(t.TempDir(), "caller-owned-assets"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Build() error = %v", err)
|
||||
}
|
||||
contents, err := os.ReadFile(prebuilt.Artifacts[0].Path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(contents) == 0 {
|
||||
t.Fatal("built artifact is empty")
|
||||
}
|
||||
contents[0] ^= 0xff
|
||||
if err := os.WriteFile(prebuilt.Artifacts[0].Path, contents, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
server, remote := newFacadeDeliveryServer(t, repository)
|
||||
defer server.Close()
|
||||
result, err := releasekit.Deliver(context.Background(), releasekit.DeliveryRequest{
|
||||
RepositoryDir: repository,
|
||||
Product: "Fixture",
|
||||
Version: "v1.2.3",
|
||||
Destination: facadeDeliveryDestination(server.URL),
|
||||
Prebuilt: &prebuilt,
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "verify build assets") || result.State != releasekit.DeliveryNotAttempted {
|
||||
t.Fatalf("Deliver() = %#v, %v; want local asset verification failure", result, err)
|
||||
}
|
||||
if remote.requests != 0 {
|
||||
t.Fatalf("hosted requests = %d, want none before local verification", remote.requests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeliverPageOnlySkipsBuildAndHostedAssets(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
|
||||
@@ -39,7 +39,7 @@ reports these implemented packages:
|
||||
| `internal/artifact` | Shared build-target normalization, artifact declarations, filename rules, and checksum-manifest naming. |
|
||||
| `internal/build` | Candidate-build coordination, selected Go target compilation, version injection, native smoke check, and artifact digests. |
|
||||
| `internal/candidate` | Stable-version, note, repository-state, candidate capture, and post-validation checks. |
|
||||
| `internal/delivery` | Tagged-source and release-note checks, managed builds in disposable storage outside the checkout, prebuilt artifact validation, manifest assembly, and hosted delivery coordination. |
|
||||
| `internal/delivery` | Tagged-source and release-note checks, managed builds in disposable storage outside the checkout, structural artifact declaration, manifest assembly, one local digest-and-size preflight before hosted requests, and delivery coordination. |
|
||||
| `internal/git` | Git command boundary, committed-tree reads, lazy local-only batched blob reads, push-destination resolution, exact ref inspection, tagged checkout preparation, and tag operations, including captured-endpoint verification during recovery. |
|
||||
| `internal/gitea` | Gitea API transport, hosted release pages, asset inspection, manifests, and uploads. |
|
||||
| `internal/gotool` | Effective Go configuration discovery, shared native-validation and static-build environments, and network-capable dependency preparation; see [Go command environments](gotool.md). |
|
||||
@@ -87,7 +87,7 @@ not a separate release matrix:
|
||||
| Authoritative endpoint and exact annotated tag publication | `releasekit.Publish`, `internal/publish`, `internal/git` | `TestPublishPushesOnlyTheCapturedAnnotatedTag`, `TestPublishRejectsUnexpectedPushURLBeforeValidation`, `TestPublishComparesExpectedURLWithResolvedInsteadOfDestination`, `TestPublishExpectedPushURLUsesPushURLWhenFetchAndPushDiffer`, `TestCheckIgnoresExpectedPushURL`, `TestResolveUsesPushURLAndQueriesWithoutMutation` |
|
||||
| Publication partial and uncertain recovery | `internal/publish`, `internal/git` | `TestPublishRejectedPushKeepsLocalTagAndReportsNotPublished`, `TestPublishLostAcknowledgmentConfirmsExactRemoteObject`, `TestPublishLostAcknowledgmentWithChangedEndpointRemainsUnknown`, `TestPublishUnavailableReconciliationRemainsUnknownAndPreservesPushError` |
|
||||
| Cross-builds, version smoke checks, empty target sets, and partial outputs | `releasekit.Build`, `internal/build`, `buildinfo` | `TestBuildCreatesSelectedTargetsAndInjectsVersion`, `TestBuildWithNoTargetsDoesNotInvokeCompiler`, `TestBuildReturnsPartialArtifactsAfterTargetFailure` |
|
||||
| Page-only and managed or prebuilt binary delivery | `releasekit.Deliver`, `internal/delivery`, `internal/gitea`, CLI | `TestDeliverPageOnlySkipsBuildAndHostedAssets`, `TestDeliverManagedBuildUploadsArtifactsAndCleansTemporaryOutput`, `TestDeliverBinaryConsumerWithBuildinfoAndSelectedTargets`, `TestDeliverPrebuiltArtifactsRemainCallerOwned`, `TestDeliverManagedBuildFailureCleansTemporaryOutputBeforeHostedWrites`, `TestDeliverManagedBuildCancellationCleansTemporaryOutput`, `TestDeliverBuildsTaggedArtifactsAndReusesAssets`, `TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites` |
|
||||
| Page-only and managed or prebuilt binary delivery | `releasekit.Deliver`, `internal/delivery`, `internal/gitea`, CLI | `TestDeliverPageOnlySkipsBuildAndHostedAssets`, `TestDeliverManagedBuildUploadsArtifactsAndCleansTemporaryOutput`, `TestDeliverBinaryConsumerWithBuildinfoAndSelectedTargets`, `TestDeliverPrebuiltArtifactsRemainCallerOwned`, `TestDeliverRejectsSameSizePrebuiltTamperingBeforeHostedWrites`, `TestDeliverManagedBuildFailureCleansTemporaryOutputBeforeHostedWrites`, `TestDeliverManagedBuildCancellationCleansTemporaryOutput`, `TestDeliverBuildsTaggedArtifactsAndReusesAssets`, `TestDeliverRejectsTamperedAssetsAndStaleReceiptBeforeRemoteWrites` |
|
||||
| Tagged checkout and build dependency preparation | `releasekit.PrepareTaggedCheckout`, `releasekit.PrepareBuildDependencies`, `internal/preparation`, `internal/git`, `internal/gotool` | `TestPrepareTaggedCheckoutFetchesExpectedCommitAndDetaches`, `TestPrepareTaggedCheckoutRejectsWrongCommitBeforeCheckout`, `TestPrepareTaggedCheckoutReportsLocalCheckoutConflict`, `TestPrepareTaggedCheckoutReportsConflictingLocalTag`, `TestPrepareBuildDependenciesPreservesPreparationEnvironment`, `TestPrepareBuildDependenciesPropagatesCommandFailure`, `TestPrepareBuildDependenciesPropagatesCancellation` |
|
||||
| Page, manifest, asset comparison, and additive retry outcomes | `internal/gitea` | `TestEnsureReleaseCreatesMissingPageFromCommittedNote`, `TestInspectAssetsDetectsSameNameAndSizeWithDifferentBytes`, `TestUploadMissingPartialFailureThenRerunOnlyMissingAssets` |
|
||||
| CLI profiles and prepared Woodpecker sequences | `cmd/releasekit`, `cmd/releasekit/internal/cli`, `examples/woodpecker/` | `TestLoadSettingsRejectsInvalidProfiles`, `TestWoodpeckerTagDeliveryTemplate`, `TestWoodpeckerTagDeliveryUsesTaggedModuleVersion`, `TestWoodpeckerSourceDeliveryTemplateSelfBootstraps`, `TestReleaseKitSourceDeliveryWorkflowFromTaggedCheckout` |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Computational Efficiency Implementation Plan
|
||||
|
||||
**Status:** Stages 1–7 complete; Stages 8–11 pending.
|
||||
**Status:** Stages 1–8 complete; Stages 9–11 pending.
|
||||
|
||||
## Purpose And Baseline
|
||||
|
||||
@@ -451,6 +451,23 @@ prebuilt delivery; these are explanatory measurements of the chosen flow.
|
||||
Streaming upload, exact remote comparison, and later upload verification remain
|
||||
intact. Update the inventory's delivery mechanics where needed.
|
||||
|
||||
**Completion:** Complete. `declaredBuildAssets` now performs target/name,
|
||||
filename, regular-file, and stat checks, then uses each recorded SHA-256 to
|
||||
build expected assets and the manifest. The separate local verification in
|
||||
`Deliver` remains after build identity checks and before hosted requests; it
|
||||
checks current bytes and sizes for managed and prebuilt artifacts. A same-size
|
||||
prebuilt mutation now fails at this delivery preflight with zero hosted
|
||||
requests. Delivery, Gitea, facade, and CLI tests passed. On the recorded Go/Git
|
||||
and machine setup, three artifact runs measured declarations at 11.3–15.4 µs
|
||||
(2,296 B/op) and declarations plus one local verification at 121–136 ms
|
||||
(about 69.7 KB/op). The Stage 2 comparison was 94.3–95.4 ms and 186.7–188.4 ms
|
||||
respectively, each including the declaration hash pass; the new second result
|
||||
therefore removes one 32 MiB file-read/hash pass from this workload. Three
|
||||
facade runs measured fresh uploads at 485–520 ms and matching retries at
|
||||
214–311 ms. These complete-workflow timings overlap the recorded baselines
|
||||
within shared-machine variation and remain dominated by the local HTTP fixture,
|
||||
upload, and remote comparison work.
|
||||
|
||||
## Stage 9: Transfer Completed Go Output Without Copies
|
||||
|
||||
**Owners:** [gotool.go](../../internal/gotool/gotool.go), validation/build callers,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -53,22 +50,6 @@ func declaredBuildAssets(result internalbuild.Result) ([]gitea.ExpectedAsset, er
|
||||
if !info.Mode().IsRegular() {
|
||||
return nil, fmt.Errorf("build artifact %q is not a regular file", artifact.Filename)
|
||||
}
|
||||
file, err := os.Open(artifact.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open build artifact %q: %w", artifact.Filename, err)
|
||||
}
|
||||
hash := sha256.New()
|
||||
_, copyErr := io.Copy(hash, file)
|
||||
closeErr := file.Close()
|
||||
if copyErr != nil {
|
||||
return nil, fmt.Errorf("hash build artifact %q: %w", artifact.Filename, copyErr)
|
||||
}
|
||||
if closeErr != nil {
|
||||
return nil, fmt.Errorf("close build artifact %q: %w", artifact.Filename, closeErr)
|
||||
}
|
||||
if artifact.SHA256 != hex.EncodeToString(hash.Sum(nil)) {
|
||||
return nil, fmt.Errorf("build artifact %q does not match its declared SHA-256 digest", artifact.Filename)
|
||||
}
|
||||
assets = append(assets, gitea.ExpectedAsset{
|
||||
Filename: artifact.Filename,
|
||||
SHA256: artifact.SHA256,
|
||||
|
||||
@@ -47,20 +47,6 @@ func TestDeclaredBuildAssetsGenerateManifestFromBuildResult(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeclaredBuildAssetsRejectsTamperedArtifact(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "app-v1.2.3-linux-amd64")
|
||||
if err := os.WriteFile(path, []byte("changed bytes"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err := declaredBuildAssets(internalbuild.Result{
|
||||
Version: "v1.2.3", BinaryName: "app", Targets: []internalbuild.Target{{OS: "linux", Arch: "amd64"}},
|
||||
Artifacts: []internalbuild.Artifact{{Path: path, Filename: filepath.Base(path), Target: internalbuild.Target{OS: "linux", Arch: "amd64"}, SHA256: assetDigest([]byte("original bytes"))}},
|
||||
})
|
||||
if err == nil || !strings.Contains(err.Error(), "SHA-256") {
|
||||
t.Fatalf("declaredBuildAssets() error = %v, want digest mismatch", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeclaredBuildAssetsRejectsArtifactDeclarationMismatch(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "app-v1.2.3-linux-arm64")
|
||||
contents := []byte("built application")
|
||||
|
||||
Reference in New Issue
Block a user