Repository: distribution/distribution
Pull request #3887 (2023-04-30 16:18:17)

deprecate Versioned in favor of oci.Versioned
Update the Manifest types to use the oci implementation of the Versioned struct.

codecov-commenter: ## [Codecov](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
Patch coverage: **`100.00`**% and project coverage change: **`-0.03`** :warning:
> Comparison is base [(`29b5e79`)](https://codecov.io/gh/distribution/distribution/commit/29b5e79f8254fd606828c84cda2a2da4d6ae3a11?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 56.63% compared to head [(`0fdf1b4`)](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 56.60%.

:mega: This organization is not using Codecov’s [GitHub App Integration](https://github.com/apps/codecov). We recommend you install it so Codecov can continue to function properly for your repositories. [Learn more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #3887      +/-   ##
==========================================
- Coverage   56.63%   56.60%   -0.03%     
==========================================
  Files         106      106              
  Lines       10674    10667       -7     
==========================================
- Hits         6045     6038       -7     
  Misses       3955     3955              
  Partials      674      674              
```


| [Impacted Files](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [manifest/schema1/manifest.go](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bWFuaWZlc3Qvc2NoZW1hMS9tYW5pZmVzdC5nbw==) | `33.82% <ø> (ø)` | |
| [manifest/manifestlist/manifestlist.go](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bWFuaWZlc3QvbWFuaWZlc3RsaXN0L21hbmlmZXN0bGlzdC5nbw==) | `70.90% <100.00%> (-0.52%)` | :arrow_down: |
| [manifest/ocischema/builder.go](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bWFuaWZlc3Qvb2Npc2NoZW1hL2J1aWxkZXIuZ28=) | `64.70% <100.00%> (-1.34%)` | :arrow_down: |
| [manifest/ocischema/manifest.go](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bWFuaWZlc3Qvb2Npc2NoZW1hL21hbmlmZXN0Lmdv) | `74.19% <100.00%> (ø)` | |
| [manifest/schema1/config\_builder.go](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bWFuaWZlc3Qvc2NoZW1hMS9jb25maWdfYnVpbGRlci5nbw==) | `71.35% <100.00%> (-0.31%)` | :arrow_down: |
| [manifest/schema1/reference\_builder.go](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bWFuaWZlc3Qvc2NoZW1hMS9yZWZlcmVuY2VfYnVpbGRlci5nbw==) | `93.75% <100.00%> (-0.25%)` | :arrow_down: |
| [manifest/schema2/builder.go](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bWFuaWZlc3Qvc2NoZW1hMi9idWlsZGVyLmdv) | `72.09% <100.00%> (+0.66%)` | :arrow_up: |
| [manifest/schema2/manifest.go](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-bWFuaWZlc3Qvc2NoZW1hMi9tYW5pZmVzdC5nbw==) | `80.00% <100.00%> (ø)` | |


</details>

[:umbrella: View full report in Codecov by Sentry](https://codecov.io/gh/distribution/distribution/pull/3887?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).

milosgajdos: If we are going to do this, we must look into https://github.com/distribution/distribution/pull/3869 as well
DavidSpek: @thaJeztah @milosgajdos What's the state of this PR now that https://github.com/distribution/distribution/pull/3869 has been merged? Is it still relevant? If so, it needs a rebase.
thaJeztah: Did a quick rebase, but I kept the previous version in a backup branch, as I need to double-check I did the rebase right
milosgajdos: This needs a rebase @thaJeztah 
thaJeztah: > should there be a space here after `//` ?

No; there's a difference between machine-parseable comments and "actual" comments. Machine-parseable comments must follow a specific pattern;

- don't start with a space
- must start with a namespace (`a-z`)
- followed by a colon (`:`)
- and a comma-separated list of options

And (I think), must be their own paragraph when combined with regular comments (so a empty line before them); also see https://github.com/moby/moby/pull/43804 which has some links.

Now, if only the gosec would follow the standards (instead of `// #nosec`) 😞 
milosgajdos: I see yeah, ignore me

https://golangci-lint.run/usage/false-positives/#nolint-directive
milosgajdos: PTAL @Jamstah @squizzi @wy65701436 
milosgajdos: This needs a rebase now @thaJeztah 

Patch:
diff --git a/manifest/manifestlist/manifestlist.go b/manifest/manifestlist/manifestlist.go
index 10474e67..1fcd7bf3 100644
--- a/manifest/manifestlist/manifestlist.go
+++ b/manifest/manifestlist/manifestlist.go
@@ -8,6 +8,7 @@ import (
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/manifest"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
@@ -18,6 +19,11 @@ const (
 
 // SchemaVersion provides a pre-initialized version structure for this
 // packages version of the manifest.
+//
+// Deprecated: use [specs.Versioned] and set MediaType on the manifest
+// to [MediaTypeManifestList].
+//
+//nolint:staticcheck // ignore SA1019: manifest.Versioned is deprecated:
 var SchemaVersion = manifest.Versioned{
 	SchemaVersion: 2,
 	MediaType:     MediaTypeManifestList,
@@ -84,7 +90,10 @@ type ManifestDescriptor struct {
 
 // ManifestList references manifests for various platforms.
 type ManifestList struct {
-	manifest.Versioned
+	specs.Versioned
+
+	// MediaType is the media type of this schema.
+	MediaType string `json:"mediaType,omitempty"`
 
 	// Manifests references a list of manifests
 	Manifests []ManifestDescriptor `json:"manifests"`
@@ -127,10 +136,8 @@ func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestLis
 // fromDescriptorsWithMediaType is for testing purposes, it's useful to be able to specify the media type explicitly
 func fromDescriptorsWithMediaType(descriptors []ManifestDescriptor, mediaType string) (*DeserializedManifestList, error) {
 	m := ManifestList{
-		Versioned: manifest.Versioned{
-			SchemaVersion: SchemaVersion.SchemaVersion,
-			MediaType:     mediaType,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: mediaType,
 	}
 
 	m.Manifests = make([]ManifestDescriptor, len(descriptors))
@@ -175,7 +182,14 @@ func (m *DeserializedManifestList) MarshalJSON() ([]byte, error) {
 // Payload returns the raw content of the manifest list. The contents can be
 // used to calculate the content identifier.
 func (m DeserializedManifestList) Payload() (string, []byte, error) {
-	return m.MediaType, m.canonical, nil
+	var mediaType string
+	if m.MediaType == "" {
+		mediaType = v1.MediaTypeImageIndex
+	} else {
+		mediaType = m.MediaType
+	}
+
+	return mediaType, m.canonical, nil
 }
 
 // validateManifestList returns an error if the byte slice is invalid JSON or if it
diff --git a/manifest/ocischema/builder.go b/manifest/ocischema/builder.go
index 204f2ee1..b0e8bf11 100644
--- a/manifest/ocischema/builder.go
+++ b/manifest/ocischema/builder.go
@@ -5,8 +5,8 @@ import (
 	"errors"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/manifest"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
@@ -58,10 +58,8 @@ func (mb *Builder) SetMediaType(mediaType string) error {
 // Build produces a final manifest from the given references.
 func (mb *Builder) Build(ctx context.Context) (distribution.Manifest, error) {
 	m := Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     mb.mediaType,
-		},
+		Versioned:   specs.Versioned{SchemaVersion: 2},
+		MediaType:   mb.mediaType,
 		Layers:      make([]distribution.Descriptor, len(mb.layers)),
 		Annotations: mb.annotations,
 	}
diff --git a/manifest/ocischema/index.go b/manifest/ocischema/index.go
index d4ec3145..6dffdcd3 100644
--- a/manifest/ocischema/index.go
+++ b/manifest/ocischema/index.go
@@ -8,11 +8,17 @@ import (
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/manifest"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 // IndexSchemaVersion provides a pre-initialized version structure for OCI Image
 // Indices.
+//
+// Deprecated: use [specs.Versioned] and set MediaType on the manifest
+// to [v1.MediaTypeImageIndex].
+//
+//nolint:staticcheck // ignore SA1019: manifest.Versioned is deprecated:
 var IndexSchemaVersion = manifest.Versioned{
 	SchemaVersion: 2,
 	MediaType:     v1.MediaTypeImageIndex,
@@ -48,7 +54,10 @@ func unmarshalImageIndex(b []byte) (distribution.Manifest, distribution.Descript
 
 // ImageIndex references manifests for various platforms.
 type ImageIndex struct {
-	manifest.Versioned
+	specs.Versioned
+
+	// MediaType is the media type of this schema.
+	MediaType string `json:"mediaType,omitempty"`
 
 	// Manifests references a list of manifests
 	Manifests []distribution.Descriptor `json:"manifests"`
@@ -84,10 +93,8 @@ func FromDescriptors(descriptors []distribution.Descriptor, annotations map[stri
 // fromDescriptorsWithMediaType is for testing purposes, it's useful to be able to specify the media type explicitly
 func fromDescriptorsWithMediaType(descriptors []distribution.Descriptor, annotations map[string]string, mediaType string) (_ *DeserializedImageIndex, err error) {
 	m := ImageIndex{
-		Versioned: manifest.Versioned{
-			SchemaVersion: IndexSchemaVersion.SchemaVersion,
-			MediaType:     mediaType,
-		},
+		Versioned:   specs.Versioned{SchemaVersion: 2},
+		MediaType:   mediaType,
 		Annotations: annotations,
 	}
 
diff --git a/manifest/ocischema/manifest.go b/manifest/ocischema/manifest.go
index e409a90f..db0896cd 100644
--- a/manifest/ocischema/manifest.go
+++ b/manifest/ocischema/manifest.go
@@ -8,11 +8,17 @@ import (
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/manifest"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 // SchemaVersion provides a pre-initialized version structure for OCI Image
-// Manifests
+// Manifests.
+//
+// Deprecated: use [specs.Versioned] and set MediaType on the manifest
+// to [v1.MediaTypeImageManifest].
+//
+//nolint:staticcheck // ignore SA1019: manifest.Versioned is deprecated:
 var SchemaVersion = manifest.Versioned{
 	SchemaVersion: 2,
 	MediaType:     v1.MediaTypeImageManifest,
@@ -44,7 +50,10 @@ func unmarshalOCISchema(b []byte) (distribution.Manifest, distribution.Descripto
 
 // Manifest defines a ocischema manifest.
 type Manifest struct {
-	manifest.Versioned
+	specs.Versioned
+
+	// MediaType is the media type of this schema.
+	MediaType string `json:"mediaType,omitempty"`
 
 	// Config references the image configuration as a blob.
 	Config distribution.Descriptor `json:"config"`
@@ -124,7 +133,7 @@ func (m *DeserializedManifest) MarshalJSON() ([]byte, error) {
 
 // Payload returns the raw content of the manifest. The contents can be used to
 // calculate the content identifier.
-func (m DeserializedManifest) Payload() (string, []byte, error) {
+func (m *DeserializedManifest) Payload() (string, []byte, error) {
 	return v1.MediaTypeImageManifest, m.canonical, nil
 }
 
diff --git a/manifest/schema2/builder.go b/manifest/schema2/builder.go
index d432ad60..771014e2 100644
--- a/manifest/schema2/builder.go
+++ b/manifest/schema2/builder.go
@@ -4,6 +4,7 @@ import (
 	"context"
 
 	"github.com/distribution/distribution/v3"
+	"github.com/opencontainers/image-spec/specs-go"
 )
 
 // Builder is a type for constructing manifests.
@@ -35,7 +36,8 @@ func NewManifestBuilder(configDescriptor distribution.Descriptor, configJSON []b
 // Build produces a final manifest from the given references.
 func (mb *Builder) Build(ctx context.Context) (distribution.Manifest, error) {
 	m := Manifest{
-		Versioned: SchemaVersion,
+		Versioned: specs.Versioned{SchemaVersion: defaultSchemaVersion},
+		MediaType: defaultMediaType,
 		Layers:    make([]distribution.Descriptor, len(mb.dependencies)),
 	}
 	copy(m.Layers, mb.dependencies)
diff --git a/manifest/schema2/manifest.go b/manifest/schema2/manifest.go
index f918c637..2b8e4413 100644
--- a/manifest/schema2/manifest.go
+++ b/manifest/schema2/manifest.go
@@ -8,6 +8,7 @@ import (
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/manifest"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 )
 
 const (
@@ -33,15 +34,25 @@ const (
 	MediaTypeUncompressedLayer = "application/vnd.docker.image.rootfs.diff.tar"
 )
 
+const (
+	defaultSchemaVersion = 2
+	defaultMediaType     = MediaTypeManifest
+)
+
 // SchemaVersion provides a pre-initialized version structure for this
 // packages version of the manifest.
+//
+// Deprecated: use [specs.Versioned] and set MediaType on the manifest
+// to [MediaTypeManifest].
+//
+//nolint:staticcheck // ignore SA1019: manifest.Versioned is deprecated:
 var SchemaVersion = manifest.Versioned{
-	SchemaVersion: 2,
-	MediaType:     MediaTypeManifest,
+	SchemaVersion: defaultSchemaVersion,
+	MediaType:     defaultMediaType,
 }
 
 func init() {
-	if err := distribution.RegisterManifestSchema(MediaTypeManifest, unmarshalSchema2); err != nil {
+	if err := distribution.RegisterManifestSchema(defaultMediaType, unmarshalSchema2); err != nil {
 		panic(fmt.Sprintf("Unable to register manifest: %s", err))
 	}
 }
@@ -55,13 +66,16 @@ func unmarshalSchema2(b []byte) (distribution.Manifest, distribution.Descriptor,
 	return m, distribution.Descriptor{
 		Digest:    digest.FromBytes(b),
 		Size:      int64(len(b)),
-		MediaType: MediaTypeManifest,
+		MediaType: defaultMediaType,
 	}, nil
 }
 
 // Manifest defines a schema2 manifest.
 type Manifest struct {
-	manifest.Versioned
+	specs.Versioned
+
+	// MediaType is the media type of this schema.
+	MediaType string `json:"mediaType,omitempty"`
 
 	// Config references the image configuration as a blob.
 	Config distribution.Descriptor `json:"config"`
@@ -116,9 +130,8 @@ func (m *DeserializedManifest) UnmarshalJSON(b []byte) error {
 		return err
 	}
 
-	if mfst.MediaType != MediaTypeManifest {
-		return fmt.Errorf("mediaType in manifest should be '%s' not '%s'",
-			MediaTypeManifest, mfst.MediaType)
+	if mfst.MediaType != defaultMediaType {
+		return fmt.Errorf("mediaType in manifest should be '%s' not '%s'", defaultMediaType, mfst.MediaType)
 	}
 
 	m.Manifest = mfst
diff --git a/manifest/versioned.go b/manifest/versioned.go
index caa6b14e..ec8ebc55 100644
--- a/manifest/versioned.go
+++ b/manifest/versioned.go
@@ -3,6 +3,8 @@ package manifest
 // Versioned provides a struct with the manifest schemaVersion and mediaType.
 // Incoming content with unknown schema version can be decoded against this
 // struct to check the version.
+//
+// Deprecated: use [specs.Versioned] and set MediaType on the Manifest itself.
 type Versioned struct {
 	// SchemaVersion is the image manifest schema that this image follows
 	SchemaVersion int `json:"schemaVersion"`
diff --git a/registry/storage/manifestlisthandler.go b/registry/storage/manifestlisthandler.go
index a1da4632..f7ded433 100644
--- a/registry/storage/manifestlisthandler.go
+++ b/registry/storage/manifestlisthandler.go
@@ -35,17 +35,17 @@ func (ms *manifestListHandler) Unmarshal(ctx context.Context, dgst digest.Digest
 func (ms *manifestListHandler) Put(ctx context.Context, manifestList distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) {
 	dcontext.GetLogger(ms.ctx).Debug("(*manifestListHandler).Put")
 
-	var schemaVersion, expectedSchemaVersion int
+	var schemaVersion int
 	switch m := manifestList.(type) {
 	case *manifestlist.DeserializedManifestList:
-		expectedSchemaVersion = manifestlist.SchemaVersion.SchemaVersion
 		schemaVersion = m.SchemaVersion
 	case *ocischema.DeserializedImageIndex:
-		expectedSchemaVersion = ocischema.IndexSchemaVersion.SchemaVersion
 		schemaVersion = m.SchemaVersion
 	default:
 		return "", fmt.Errorf("wrong type put to manifestListHandler: %T", manifestList)
 	}
+
+	const expectedSchemaVersion = 2
 	if schemaVersion != expectedSchemaVersion {
 		return "", fmt.Errorf("unrecognized manifest list schema version %d, expected %d", schemaVersion, expectedSchemaVersion)
 	}
diff --git a/registry/storage/manifeststore.go b/registry/storage/manifeststore.go
index 027ab65c..9528f74f 100644
--- a/registry/storage/manifeststore.go
+++ b/registry/storage/manifeststore.go
@@ -7,11 +7,11 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/internal/dcontext"
-	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/manifestlist"
 	"github.com/distribution/distribution/v3/manifest/ocischema"
 	"github.com/distribution/distribution/v3/manifest/schema2"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
@@ -88,7 +88,13 @@ func (ms *manifestStore) Get(ctx context.Context, dgst digest.Digest, options ..
 		return nil, err
 	}
 
-	var versioned manifest.Versioned
+	// versioned is a minimal representation of a manifest with version and mediatype.
+	var versioned struct {
+		specs.Versioned
+
+		// MediaType is the media type of this schema.
+		MediaType string `json:"mediaType,omitempty"`
+	}
 	if err = json.Unmarshal(content, &versioned); err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/internal/client/repository_test.go b/internal/client/repository_test.go
index a98fe289..98ff22dc 100644
--- a/internal/client/repository_test.go
+++ b/internal/client/repository_test.go
@@ -18,13 +18,13 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/internal/dcontext"
-	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/ocischema"
 	"github.com/distribution/distribution/v3/registry/api/errcode"
 	"github.com/distribution/distribution/v3/testutil"
 	"github.com/distribution/reference"
 	"github.com/google/uuid"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
@@ -928,10 +928,8 @@ func newRandomOCIManifest(t *testing.T, blobCount int) (*ocischema.Manifest, dig
 	}
 
 	m := ocischema.Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     v1.MediaTypeImageManifest,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: v1.MediaTypeImageManifest,
 		Config: distribution.Descriptor{
 			Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
 			Size:      123,
diff --git a/manifest/ocischema/manifest_test.go b/manifest/ocischema/manifest_test.go
index ca6d45db..da1fb032 100644
--- a/manifest/ocischema/manifest_test.go
+++ b/manifest/ocischema/manifest_test.go
@@ -7,8 +7,8 @@ import (
 	"testing"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/manifestlist"
+	"github.com/opencontainers/image-spec/specs-go"
 
 	"github.com/opencontainers/go-digest"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
@@ -42,10 +42,8 @@ const expectedManifestSerialization = `{
 
 func makeTestManifest(mediaType string) Manifest {
 	return Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     mediaType,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: mediaType,
 		Config: distribution.Descriptor{
 			MediaType:   v1.MediaTypeImageConfig,
 			Digest:      "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
diff --git a/manifest/schema2/manifest_test.go b/manifest/schema2/manifest_test.go
index 23d69f03..7e2c18c9 100644
--- a/manifest/schema2/manifest_test.go
+++ b/manifest/schema2/manifest_test.go
@@ -7,7 +7,7 @@ import (
 	"testing"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/manifest"
+	"github.com/opencontainers/image-spec/specs-go"
 )
 
 const expectedManifestSerialization = `{
@@ -29,10 +29,8 @@ const expectedManifestSerialization = `{
 
 func makeTestManifest(mediaType string) Manifest {
 	return Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     mediaType,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: mediaType,
 		Config: distribution.Descriptor{
 			MediaType: MediaTypeImageConfig,
 			Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
diff --git a/notifications/bridge_test.go b/notifications/bridge_test.go
index dba10f27..5fc2e4c4 100644
--- a/notifications/bridge_test.go
+++ b/notifications/bridge_test.go
@@ -4,13 +4,13 @@ import (
 	"testing"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/schema2"
 	v2 "github.com/distribution/distribution/v3/registry/api/v2"
 	"github.com/distribution/reference"
 	events "github.com/docker/go-events"
 	"github.com/google/uuid"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
@@ -29,7 +29,6 @@ var (
 	}
 	request      = RequestRecord{}
 	tag          = "latest"
-	ociMediaType = v1.MediaTypeImageManifest
 	artifactType = "application/vnd.example.sbom.v1"
 	cfg          = distribution.Descriptor{
 		MediaType: artifactType,
@@ -143,14 +142,13 @@ func TestEventBridgeRepoDeleted(t *testing.T) {
 }
 
 func createTestEnv(t *testing.T, fn testSinkFn) Listener {
-	manifest := schema2.Manifest{
-		Versioned: manifest.Versioned{
-			MediaType: ociMediaType,
-		},
-		Config: cfg,
+	mfst := schema2.Manifest{
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: v1.MediaTypeImageManifest,
+		Config:    cfg,
 	}
 
-	deserializedManifest, err := schema2.FromStruct(manifest)
+	deserializedManifest, err := schema2.FromStruct(mfst)
 	if err != nil {
 		t.Fatalf("creating OCI manifest: %v", err)
 	}
diff --git a/notifications/listener_test.go b/notifications/listener_test.go
index 5781d453..f2ee3343 100644
--- a/notifications/listener_test.go
+++ b/notifications/listener_test.go
@@ -14,6 +14,7 @@ import (
 	"github.com/distribution/distribution/v3/testutil"
 	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 )
 
 func TestListener(t *testing.T) {
@@ -143,7 +144,8 @@ func checkTestRepository(t *testing.T, repository distribution.Repository, remov
 	}
 
 	m := schema2.Manifest{
-		Versioned: schema2.SchemaVersion,
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: schema2.MediaTypeManifest,
 		Config: distribution.Descriptor{
 			MediaType: "foo/bar",
 			Digest:    configDgst,
diff --git a/registry/handlers/api_test.go b/registry/handlers/api_test.go
index 43d3ae14..8fabe02d 100644
--- a/registry/handlers/api_test.go
+++ b/registry/handlers/api_test.go
@@ -21,7 +21,6 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/configuration"
-	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/manifestlist"
 	"github.com/distribution/distribution/v3/manifest/schema2"
 	"github.com/distribution/distribution/v3/registry/api/errcode"
@@ -33,6 +32,7 @@ import (
 	"github.com/distribution/reference"
 	"github.com/gorilla/handlers"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 )
 
 var headerConfig = http.Header{
@@ -1577,10 +1577,8 @@ func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Name
 	// --------------------------------
 	// Attempt to push manifest with missing config and missing layers
 	manifest := &schema2.Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     schema2.MediaTypeManifest,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: schema2.MediaTypeManifest,
 		Config: distribution.Descriptor{
 			Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
 			Size:      3253,
@@ -1900,10 +1898,8 @@ func testManifestAPIManifestList(t *testing.T, env *testEnv, args manifestArgs)
 	// --------------------------------
 	// Attempt to push manifest list that refers to an unknown manifest
 	manifestList := &manifestlist.ManifestList{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     manifestlist.MediaTypeManifestList,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: manifestlist.MediaTypeManifestList,
 		Manifests: []manifestlist.ManifestDescriptor{
 			{
 				Descriptor: distribution.Descriptor{
@@ -2639,10 +2635,8 @@ func createRepository(env *testEnv, t *testing.T, imageName string, tag string)
 	}
 
 	manifest := &schema2.Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     schema2.MediaTypeManifest,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: schema2.MediaTypeManifest,
 		Config: distribution.Descriptor{
 			Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
 			Size:      3253,
@@ -2736,10 +2730,8 @@ func TestRegistryAsCacheMutationAPIs(t *testing.T) {
 
 	// Manifest upload
 	manifest := &schema2.Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     schema2.MediaTypeManifest,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: schema2.MediaTypeManifest,
 		Config: distribution.Descriptor{
 			Digest:    "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
 			Size:      3253,
diff --git a/registry/proxy/proxymanifeststore_test.go b/registry/proxy/proxymanifeststore_test.go
index 67313f66..713dcf74 100644
--- a/registry/proxy/proxymanifeststore_test.go
+++ b/registry/proxy/proxymanifeststore_test.go
@@ -17,6 +17,7 @@ import (
 	"github.com/distribution/distribution/v3/testutil"
 	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 )
 
 type statsManifest struct {
@@ -153,7 +154,8 @@ func populateRepo(ctx context.Context, t *testing.T, repository distribution.Rep
 	}
 
 	m := schema2.Manifest{
-		Versioned: schema2.SchemaVersion,
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: schema2.MediaTypeManifest,
 		Config: distribution.Descriptor{
 			MediaType: "foo/bar",
 			Digest:    configDigest,
diff --git a/registry/storage/manifeststore_test.go b/registry/storage/manifeststore_test.go
index 2594e2cb..4ed013af 100644
--- a/registry/storage/manifeststore_test.go
+++ b/registry/storage/manifeststore_test.go
@@ -9,7 +9,6 @@ import (
 	"testing"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/manifestlist"
 	"github.com/distribution/distribution/v3/manifest/ocischema"
 	"github.com/distribution/distribution/v3/manifest/schema2"
@@ -19,6 +18,7 @@ import (
 	"github.com/distribution/distribution/v3/testutil"
 	"github.com/distribution/reference"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
@@ -94,10 +94,8 @@ func testManifestStorage(t *testing.T, options ...RegistryOption) {
 	builder := schema2.NewManifestBuilder(d, sampleConfig)
 
 	m := &schema2.Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     schema2.MediaTypeManifest,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: schema2.MediaTypeManifest,
 		Config: distribution.Descriptor{
 			Digest:    digest.FromBytes(sampleConfig),
 			Size:      int64(len(sampleConfig)),
@@ -393,7 +391,7 @@ func testOCIManifestStorage(t *testing.T, testname string, includeMediaTypes boo
 		t.Fatalf("%s: unexpected MediaType for result, %s", testname, fetchedManifest.MediaType)
 	}
 
-	if fetchedManifest.SchemaVersion != ocischema.SchemaVersion.SchemaVersion {
+	if fetchedManifest.SchemaVersion != 2 {
 		t.Fatalf("%s: unexpected schema version for result, %d", testname, fetchedManifest.SchemaVersion)
 	}
 
diff --git a/registry/storage/ocimanifesthandler_test.go b/registry/storage/ocimanifesthandler_test.go
index 26557824..c70f4b33 100644
--- a/registry/storage/ocimanifesthandler_test.go
+++ b/registry/storage/ocimanifesthandler_test.go
@@ -7,10 +7,10 @@ import (
 	"testing"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/ocischema"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
@@ -49,11 +49,9 @@ func TestVerifyOCIManifestNonDistributableLayer(t *testing.T) {
 	}
 
 	template := ocischema.Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     v1.MediaTypeImageManifest,
-		},
-		Config: config,
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: v1.MediaTypeImageManifest,
+		Config:    config,
 	}
 
 	type testcase struct {
@@ -189,10 +187,8 @@ func TestVerifyOCIManifestBlobLayerAndConfig(t *testing.T) {
 	}
 
 	template := ocischema.Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     v1.MediaTypeImageManifest,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: v1.MediaTypeImageManifest,
 	}
 
 	checkFn := func(m ocischema.Manifest, rerr error) {
diff --git a/registry/storage/schema2manifesthandler_test.go b/registry/storage/schema2manifesthandler_test.go
index 6b918a80..4f92ef7a 100644
--- a/registry/storage/schema2manifesthandler_test.go
+++ b/registry/storage/schema2manifesthandler_test.go
@@ -7,10 +7,10 @@ import (
 
 	"github.com/distribution/distribution/v3"
 	"github.com/distribution/distribution/v3/internal/dcontext"
-	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/schema2"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 )
 
 func TestVerifyManifestForeignLayer(t *testing.T) {
@@ -45,11 +45,9 @@ func TestVerifyManifestForeignLayer(t *testing.T) {
 	}
 
 	template := schema2.Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     schema2.MediaTypeManifest,
-		},
-		Config: config,
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: schema2.MediaTypeManifest,
+		Config:    config,
 	}
 
 	type testcase struct {
@@ -176,10 +174,8 @@ func TestVerifyManifestBlobLayerAndConfig(t *testing.T) {
 	}
 
 	template := schema2.Manifest{
-		Versioned: manifest.Versioned{
-			SchemaVersion: 2,
-			MediaType:     schema2.MediaTypeManifest,
-		},
+		Versioned: specs.Versioned{SchemaVersion: 2},
+		MediaType: schema2.MediaTypeManifest,
 	}
 
 	checkFn := func(m schema2.Manifest, rerr error) {
diff --git a/registry/storage/tagstore_test.go b/registry/storage/tagstore_test.go
index 2659cfac..548482bd 100644
--- a/registry/storage/tagstore_test.go
+++ b/registry/storage/tagstore_test.go
@@ -6,11 +6,11 @@ import (
 	"testing"
 
 	"github.com/distribution/distribution/v3"
-	"github.com/distribution/distribution/v3/manifest"
 	"github.com/distribution/distribution/v3/manifest/schema2"
 	"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
 	"github.com/distribution/reference"
 	digest "github.com/opencontainers/go-digest"
+	"github.com/opencontainers/image-spec/specs-go"
 )
 
 type tagsTestEnv struct {
@@ -243,10 +243,8 @@ func TestTagIndexes(t *testing.T) {
 			t.Fatal(err)
 		}
 		m := schema2.Manifest{
-			Versioned: manifest.Versioned{
-				SchemaVersion: 2,
-				MediaType:     schema2.MediaTypeManifest,
-			},
+			Versioned: specs.Versioned{SchemaVersion: 2},
+			MediaType: schema2.MediaTypeManifest,
 			Config: distribution.Descriptor{
 				Digest:    conf.Digest,
 				Size:      1,