diff --git a/go/seats/containerimageregistry/contract.go b/go/seats/containerimageregistry/contract.go index ac06122..6eb1f99 100644 --- a/go/seats/containerimageregistry/contract.go +++ b/go/seats/containerimageregistry/contract.go @@ -15,6 +15,7 @@ const ( VerbTags = "tags" VerbHold = "hold" VerbRelease = "release" + VerbList = "list" ) const ( @@ -28,7 +29,7 @@ const ( // Verbs is every verb the seat serves, in its definition's order: a new copy each call. func Verbs() []string { - return []string{VerbPublish, VerbPublishPart, VerbVersions, VerbRemove, VerbMirror, VerbTags, VerbHold, VerbRelease} + return []string{VerbPublish, VerbPublishPart, VerbVersions, VerbRemove, VerbMirror, VerbTags, VerbHold, VerbRelease, VerbList} } // Events is every event published on the seat's subject: a new copy each call. @@ -103,6 +104,24 @@ type Hold struct { Reason string `json:"reason,omitempty"` } +// Listed is a type of the contract. +type Listed struct { + // for a manifest keeping one bundle or archive, the digest of that blob: what a reference names it by + Artifact string `json:"artifact,omitempty"` + // the manifest's digest, sha256: + Digest string `json:"digest"` + // a hold keeps it from retention + Held bool `json:"held"` + // it is an index, naming a manifest per platform + Index bool `json:"index"` + // for an index, the digests of the manifests it names + Platforms []string `json:"platforms,omitempty"` + // its bytes: its own content and every blob it names + Size int64 `json:"size"` + // the tags naming it, none for an untagged one + Tags []string `json:"tags,omitempty"` +} + // Outcome is the body of published, refused and removed. type Outcome struct { // the source commit it was built from @@ -150,6 +169,14 @@ type Part struct { Size int64 `json:"size"` } +// Repository is a type of the contract. +type Repository struct { + // each manifest it holds, by digest + Manifests []Listed `json:"manifests"` + // the repository + Name string `json:"name"` +} + // PublishInput is what publish takes. type PublishInput struct { // the source commit it was built from @@ -345,6 +372,18 @@ type ReleaseAnswer struct { Released bool `json:"released"` } +// ListInput is what list takes. +type ListInput struct { + // the repository, /; every repository of the seat when not given + Name string `json:"name,omitempty"` +} + +// ListAnswer is what list answers. +type ListAnswer struct { + // each repository named, in order of its name + Repositories []Repository `json:"repositories"` +} + // PublishedEvent is the body of the event published. type PublishedEvent = Outcome @@ -362,9 +401,3 @@ type HeldEvent = Hold // ReleasedEvent is the body of the event released. type ReleasedEvent = Hold - -// ArtifactStoreForm is the form of the provision artifact-store. -type ArtifactStoreForm struct { - // the store's address - URL string `json:"url"` -} diff --git a/go/seats/containerimageregistry/contract_test.go b/go/seats/containerimageregistry/contract_test.go index 3ad30c7..8f4d543 100644 --- a/go/seats/containerimageregistry/contract_test.go +++ b/go/seats/containerimageregistry/contract_test.go @@ -43,6 +43,8 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v ReleaseAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "a tagged version sharing its digest with another tag is kept, not removed", subject: "", given: "{\"kept\":true,\"reason\":\"shared-digest\",\"removed\":false,\"tags\":[\"0.1.19\"]}", wire: "{\"kept\":true,\"reason\":\"shared-digest\",\"removed\":false,\"tags\":[\"0.1.19\"]}", decode: func(b []byte) (any, error) { var v RemoveAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "list answers an index with its platforms, its tag and its hold", subject: "", given: "{\"repositories\":[{\"manifests\":[{\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"held\":false,\"index\":true,\"platforms\":[\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"size\":2048,\"tags\":[\"3\"]},{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"held\":false,\"index\":false,\"size\":1024}],\"name\":\"mail/server\"}]}", wire: "{\"repositories\":[{\"manifests\":[{\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"held\":false,\"index\":true,\"platforms\":[\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"size\":2048,\"tags\":[\"3\"]},{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"held\":false,\"index\":false,\"size\":1024}],\"name\":\"mail/server\"}]}", + decode: func(b []byte) (any, error) { var v ListAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, } for _, c := range cases { v, err := c.decode([]byte(c.given)) diff --git a/go/seats/ociartifactregistry/contract.go b/go/seats/ociartifactregistry/contract.go index 39bba8b..edade5c 100644 --- a/go/seats/ociartifactregistry/contract.go +++ b/go/seats/ociartifactregistry/contract.go @@ -13,6 +13,7 @@ const ( VerbRemove = "remove" VerbHold = "hold" VerbRelease = "release" + VerbList = "list" ) const ( @@ -26,7 +27,7 @@ const ( // Verbs is every verb the seat serves, in its definition's order: a new copy each call. func Verbs() []string { - return []string{VerbPublish, VerbPublishPart, VerbVersions, VerbRemove, VerbHold, VerbRelease} + return []string{VerbPublish, VerbPublishPart, VerbVersions, VerbRemove, VerbHold, VerbRelease, VerbList} } // Events is every event published on the seat's subject: a new copy each call. @@ -101,6 +102,24 @@ type Hold struct { Reason string `json:"reason,omitempty"` } +// Listed is a type of the contract. +type Listed struct { + // for a manifest keeping one bundle or archive, the digest of that blob: what a reference names it by + Artifact string `json:"artifact,omitempty"` + // the manifest's digest, sha256: + Digest string `json:"digest"` + // a hold keeps it from retention + Held bool `json:"held"` + // it is an index, naming a manifest per platform + Index bool `json:"index"` + // for an index, the digests of the manifests it names + Platforms []string `json:"platforms,omitempty"` + // its bytes: its own content and every blob it names + Size int64 `json:"size"` + // the tags naming it, none for an untagged one + Tags []string `json:"tags,omitempty"` +} + // Outcome is the body of published, refused and removed. type Outcome struct { // the source commit it was built from @@ -148,6 +167,14 @@ type Part struct { Size int64 `json:"size"` } +// Repository is a type of the contract. +type Repository struct { + // each manifest it holds, by digest + Manifests []Listed `json:"manifests"` + // the repository + Name string `json:"name"` +} + // PublishInput is what publish takes. type PublishInput struct { // the source commit it was built from @@ -303,6 +330,18 @@ type ReleaseAnswer struct { Released bool `json:"released"` } +// ListInput is what list takes. +type ListInput struct { + // the repository, /; every repository of the seat when not given + Name string `json:"name,omitempty"` +} + +// ListAnswer is what list answers. +type ListAnswer struct { + // each repository named, in order of its name + Repositories []Repository `json:"repositories"` +} + // PublishedEvent is the body of the event published. type PublishedEvent = Outcome @@ -320,9 +359,3 @@ type HeldEvent = Hold // ReleasedEvent is the body of the event released. type ReleasedEvent = Hold - -// ArtifactStoreForm is the form of the provision artifact-store. -type ArtifactStoreForm struct { - // the store's address - URL string `json:"url"` -} diff --git a/go/seats/ociartifactregistry/contract_test.go b/go/seats/ociartifactregistry/contract_test.go index 9a2f3a2..f6d90b3 100644 --- a/go/seats/ociartifactregistry/contract_test.go +++ b/go/seats/ociartifactregistry/contract_test.go @@ -43,6 +43,8 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v ReleaseAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "a tagged version sharing its digest with another tag is kept, not removed", subject: "", given: "{\"kept\":true,\"reason\":\"shared-digest\",\"removed\":false,\"tags\":[\"0.1.19\"]}", wire: "{\"kept\":true,\"reason\":\"shared-digest\",\"removed\":false,\"tags\":[\"0.1.19\"]}", decode: func(b []byte) (any, error) { var v RemoveAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "list answers an untagged archive by the blob it keeps", subject: "", given: "{\"repositories\":[{\"manifests\":[{\"artifact\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"held\":true,\"index\":false,\"size\":4096}],\"name\":\"mail/files\"}]}", wire: "{\"repositories\":[{\"manifests\":[{\"artifact\":\"sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"held\":true,\"index\":false,\"size\":4096}],\"name\":\"mail/files\"}]}", + decode: func(b []byte) (any, error) { var v ListAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, } for _, c := range cases { v, err := c.decode([]byte(c.given)) diff --git a/go/seats/seats.go b/go/seats/seats.go index 125f4fe..a545cdd 100644 --- a/go/seats/seats.go +++ b/go/seats/seats.go @@ -231,7 +231,7 @@ func List() []Seat { {Name: "provisioning", Description: "Read what the provisioner keeps: the registry (which domain answers which kind of placeholder), the needs each module on each node recorded, what is applied where, or the items of every collection, narrowed by node and module. Reads only.", Optional: true, Input: map[string]any{"properties": map[string]any{"collection": map[string]any{"description": "for collect: a collection", "type": "string"}, "module": map[string]any{"description": "a module: for collect, a contributor", "type": "string"}, "node": map[string]any{"description": "a node", "type": "string"}, "what": map[string]any{"description": "registry (when not given), needs, applied or collect", "type": "string"}}, "type": "object"}}, }}, - {Name: MeshArtifactStore, Scope: "mesh", Grain: "seat-list", Decision: "novox/hq ADR 0075", Delivers: "artifact-store"}, + {Name: MeshArtifactStore, Scope: "mesh", Grain: "seat-list", Decision: "novox/hq ADR 0075"}, {Name: MeshCatalog, Scope: "mesh", Grain: "seat-list", Decision: "novox/hq ADR 0604", Serves: []Verb{ {Name: "modules", Description: "Every module the trunk holds, each with its repository, its directory there, the merge commit it was last changed at and its fingerprint (or why it has none): the catalog of the mesh, read from its holder's state. Reads only.", Optional: true, @@ -411,7 +411,7 @@ func List() []Seat { Input: map[string]any{"properties": map[string]any{"digest": map[string]any{"description": "the digest (for a bundle, archive or copied base)", "type": "string"}, "name": map[string]any{"description": "the name", "type": "string"}, "reason": map[string]any{"description": "why it goes", "enum": []string{"tag-gone", "retention"}, "type": "string"}, "tag": map[string]any{"description": "the tag (for a tagged version)", "type": "string"}, "version": map[string]any{"description": "the version", "type": "string"}}, "required": []string{"name", "reason"}, "type": "object"}, Output: map[string]any{"properties": map[string]any{"artifact": map[string]any{"description": "one published thing, as every registry event and answer names it", "properties": map[string]any{"commit": map[string]any{"description": "the source commit it was built from", "type": "string"}, "digest": map[string]any{"description": "its digest, sha256: (a Go package's h1: hash beside it in the version's record)", "type": "string"}, "fingerprint": map[string]any{"description": "the build's fingerprint", "type": "string"}, "kind": map[string]any{"description": "what it is", "enum": []string{"package", "image", "bundle", "archive", "upstream"}, "type": "string"}, "name": map[string]any{"description": "the package's, image's, bundle's or archive's name; a package's is its name in its own language (design 32): a Go package's the go.mod module path (git.novox.be/novox/mesh-sdk/go), an npm package's package.json's name (@novox/mesh-sdk)", "type": "string"}, "tag": map[string]any{"description": "the tag it carries, for a tagged version", "type": "string"}, "version": map[string]any{"description": "the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone", "type": "string"}}, "required": []string{"name", "kind", "digest"}, "type": "object"}, "kept": map[string]any{"description": "it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags)", "type": "boolean"}, "reason": map[string]any{"description": "why it was kept: shared-digest, another tag names its digest", "enum": []string{"shared-digest"}, "type": "string"}, "removed": map[string]any{"description": "it is gone", "type": "boolean"}, "tags": map[string]any{"description": "the other tags still naming its digest, when kept", "items": map[string]any{"type": "string"}, "type": "array"}}, "required": []string{"removed"}, "type": "object"}}, }}, - {Name: ContainerImageRegistry, Scope: "mesh", Grain: "whole", Decision: "novox/hq ADR 0605", Delivers: "artifact-store", Emits: []string{"published", "refused", "removed", "kept", "held", "released"}, + {Name: ContainerImageRegistry, Scope: "mesh", Grain: "whole", Decision: "novox/hq ADR 0605", Emits: []string{"published", "refused", "removed", "kept", "held", "released"}, Serves: []Verb{ {Name: "publish", Description: "Open a transfer, naming the artifact and the digest of every part; answers the digests the holder lacks, which the caller then sends by publish-part. A transfer already open answers the parts held, so a cut transfer goes on from there. With no parts and a version, the held digest is published under that version and nothing is stored (the trunk's claim).", Optional: true, Input: map[string]any{"properties": map[string]any{"commit": map[string]any{"description": "the source commit it was built from", "type": "string"}, "digest": map[string]any{"description": "its digest, sha256: (a Go package's h1: hash beside it in the version's record)", "type": "string"}, "fingerprint": map[string]any{"description": "the build's fingerprint", "type": "string"}, "kind": map[string]any{"description": "what it is", "enum": []string{"package", "image", "bundle", "archive", "upstream"}, "type": "string"}, "name": map[string]any{"description": "the package's, image's, bundle's or archive's name; a package's is its name in its own language (design 32): a Go package's the go.mod module path (git.novox.be/novox/mesh-sdk/go), an npm package's package.json's name (@novox/mesh-sdk)", "type": "string"}, "parts": map[string]any{"description": "every part's digest and size; none when the digest is already held", "items": map[string]any{"description": "one part of a transfer", "properties": map[string]any{"digest": map[string]any{"description": "the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob", "type": "string"}, "size": map[string]any{"description": "its size in bytes", "type": "integer"}}, "required": []string{"digest", "size"}, "type": "object"}, "type": "array"}, "tag": map[string]any{"description": "the tag it carries, for a tagged version", "type": "string"}, "transfer": map[string]any{"description": "the transfer's name, the caller's own; the same name opens the same transfer again", "type": "string"}, "version": map[string]any{"description": "the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone", "type": "string"}}, "required": []string{"transfer", "name", "kind", "digest"}, "type": "object"}, @@ -437,8 +437,11 @@ func List() []Seat { {Name: "release", Description: "Let a hold go; emits released. A release of what is not held answers released false and emits nothing, never an error: the outcome asked holds already. By hand only through the holder's own tool, the operator's session as the proved caller, recorded as a hand act.", Optional: true, Input: map[string]any{"properties": map[string]any{"digest": map[string]any{"description": "the digest", "type": "string"}, "name": map[string]any{"description": "the name", "type": "string"}, "reason": map[string]any{"description": "why it is let go", "type": "string"}}, "required": []string{"name", "digest", "reason"}, "type": "object"}, Output: map[string]any{"properties": map[string]any{"released": map[string]any{"description": "a hold was let go; false when there was none", "type": "boolean"}}, "required": []string{"released"}, "type": "object"}}, + {Name: "list", Description: "What the store holds of one repository, or of every repository the seat holds when none is named: every image manifest and index, each with its digest, whether it is an index (and the manifests an index names), the tags naming it, its size and whether it is held. Read from the store itself, so a manifest no tag names is listed too; it reads only (ADR 0632 §3).", Optional: true, + Input: map[string]any{"properties": map[string]any{"name": map[string]any{"description": "the repository, /; every repository of the seat when not given", "type": "string"}}, "type": "object"}, + Output: map[string]any{"properties": map[string]any{"repositories": map[string]any{"description": "each repository named, in order of its name", "items": map[string]any{"properties": map[string]any{"manifests": map[string]any{"description": "each manifest it holds, by digest", "items": map[string]any{"properties": map[string]any{"artifact": map[string]any{"description": "for a manifest keeping one bundle or archive, the digest of that blob: what a reference names it by", "type": "string"}, "digest": map[string]any{"description": "the manifest's digest, sha256:", "type": "string"}, "held": map[string]any{"description": "a hold keeps it from retention", "type": "boolean"}, "index": map[string]any{"description": "it is an index, naming a manifest per platform", "type": "boolean"}, "platforms": map[string]any{"description": "for an index, the digests of the manifests it names", "items": map[string]any{"type": "string"}, "type": "array"}, "size": map[string]any{"description": "its bytes: its own content and every blob it names", "type": "integer"}, "tags": map[string]any{"description": "the tags naming it, none for an untagged one", "items": map[string]any{"type": "string"}, "type": "array"}}, "required": []string{"digest", "index", "size", "held"}, "type": "object"}, "type": "array"}, "name": map[string]any{"description": "the repository", "type": "string"}}, "required": []string{"name", "manifests"}, "type": "object"}, "type": "array"}}, "required": []string{"repositories"}, "type": "object"}}, }}, - {Name: OciArtifactRegistry, Scope: "mesh", Grain: "whole", Decision: "novox/hq ADR 0605", Delivers: "artifact-store", Emits: []string{"published", "refused", "removed", "kept", "held", "released"}, + {Name: OciArtifactRegistry, Scope: "mesh", Grain: "whole", Decision: "novox/hq ADR 0605", Emits: []string{"published", "refused", "removed", "kept", "held", "released"}, Serves: []Verb{ {Name: "publish", Description: "Open a transfer, naming the artifact and the digest of every part; answers the digests the holder lacks, which the caller then sends by publish-part. A transfer already open answers the parts held, so a cut transfer goes on from there. With no parts and a version, the held digest is published under that version and nothing is stored (the trunk's claim).", Optional: true, Input: map[string]any{"properties": map[string]any{"commit": map[string]any{"description": "the source commit it was built from", "type": "string"}, "digest": map[string]any{"description": "its digest, sha256: (a Go package's h1: hash beside it in the version's record)", "type": "string"}, "fingerprint": map[string]any{"description": "the build's fingerprint", "type": "string"}, "kind": map[string]any{"description": "what it is", "enum": []string{"package", "image", "bundle", "archive", "upstream"}, "type": "string"}, "name": map[string]any{"description": "the package's, image's, bundle's or archive's name; a package's is its name in its own language (design 32): a Go package's the go.mod module path (git.novox.be/novox/mesh-sdk/go), an npm package's package.json's name (@novox/mesh-sdk)", "type": "string"}, "parts": map[string]any{"description": "every part's digest and size; none when the digest is already held", "items": map[string]any{"description": "one part of a transfer", "properties": map[string]any{"digest": map[string]any{"description": "the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob", "type": "string"}, "size": map[string]any{"description": "its size in bytes", "type": "integer"}}, "required": []string{"digest", "size"}, "type": "object"}, "type": "array"}, "tag": map[string]any{"description": "the tag it carries, for a tagged version", "type": "string"}, "transfer": map[string]any{"description": "the transfer's name, the caller's own; the same name opens the same transfer again", "type": "string"}, "version": map[string]any{"description": "the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone", "type": "string"}}, "required": []string{"transfer", "name", "kind", "digest"}, "type": "object"}, @@ -458,6 +461,9 @@ func List() []Seat { {Name: "release", Description: "Let a hold go; emits released. A release of what is not held answers released false and emits nothing, never an error: the outcome asked holds already. By hand only through the holder's own tool, the operator's session as the proved caller, recorded as a hand act.", Optional: true, Input: map[string]any{"properties": map[string]any{"digest": map[string]any{"description": "the digest", "type": "string"}, "name": map[string]any{"description": "the name", "type": "string"}, "reason": map[string]any{"description": "why it is let go", "type": "string"}}, "required": []string{"name", "digest", "reason"}, "type": "object"}, Output: map[string]any{"properties": map[string]any{"released": map[string]any{"description": "a hold was let go; false when there was none", "type": "boolean"}}, "required": []string{"released"}, "type": "object"}}, + {Name: "list", Description: "What the store holds of one repository, or of every repository the seat holds when none is named: every manifest keeping one bundle or archive, each with its digest, whether it is an index (and the manifests an index names), the tags naming it, its size and whether it is held. Read from the store itself, so a manifest no tag names is listed too; it reads only (ADR 0632 §3).", Optional: true, + Input: map[string]any{"properties": map[string]any{"name": map[string]any{"description": "the repository, /; every repository of the seat when not given", "type": "string"}}, "type": "object"}, + Output: map[string]any{"properties": map[string]any{"repositories": map[string]any{"description": "each repository named, in order of its name", "items": map[string]any{"properties": map[string]any{"manifests": map[string]any{"description": "each manifest it holds, by digest", "items": map[string]any{"properties": map[string]any{"artifact": map[string]any{"description": "for a manifest keeping one bundle or archive, the digest of that blob: what a reference names it by", "type": "string"}, "digest": map[string]any{"description": "the manifest's digest, sha256:", "type": "string"}, "held": map[string]any{"description": "a hold keeps it from retention", "type": "boolean"}, "index": map[string]any{"description": "it is an index, naming a manifest per platform", "type": "boolean"}, "platforms": map[string]any{"description": "for an index, the digests of the manifests it names", "items": map[string]any{"type": "string"}, "type": "array"}, "size": map[string]any{"description": "its bytes: its own content and every blob it names", "type": "integer"}, "tags": map[string]any{"description": "the tags naming it, none for an untagged one", "items": map[string]any{"type": "string"}, "type": "array"}}, "required": []string{"digest", "index", "size", "held"}, "type": "object"}, "type": "array"}, "name": map[string]any{"description": "the repository", "type": "string"}}, "required": []string{"name", "manifests"}, "type": "object"}, "type": "array"}}, "required": []string{"repositories"}, "type": "object"}}, }}, {Name: Git, Scope: "mesh", Grain: "seat-list", Decision: "novox/hq ADR 0111", Delivers: "git"}, {Name: NodeBuildGo, Scope: "node", Grain: "seat-list", Decision: "novox/hq ADR 0525, ADR 0536, ADR 0553", Accepts: []string{"delivery", "check", "recheck"}, PulledByKind: true, Yields: true, diff --git a/seats/container-image-registry/fixtures/verbs.json b/seats/container-image-registry/fixtures/verbs.json index 6e50d36..e024a81 100644 --- a/seats/container-image-registry/fixtures/verbs.json +++ b/seats/container-image-registry/fixtures/verbs.json @@ -114,5 +114,36 @@ "0.1.19" ] } + }, + { + "name": "list answers an index with its platforms, its tag and its hold", + "of": "verb:list.answer", + "given": { + "repositories": [ + { + "name": "mail/server", + "manifests": [ + { + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "index": true, + "platforms": [ + "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + ], + "tags": [ + "3" + ], + "size": 2048, + "held": false + }, + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "index": false, + "size": 1024, + "held": false + } + ] + } + ] + } } ] diff --git a/seats/container-image-registry/seat.json b/seats/container-image-registry/seat.json index 8877a5b..b1a595f 100644 --- a/seats/container-image-registry/seat.json +++ b/seats/container-image-registry/seat.json @@ -5,7 +5,6 @@ "grain": "whole", "decision": "novox/hq ADR 0605", "description": "The mesh's container images, in the one artifact store: the OCI distribution protocol's read side, an image's tags and mirroring an outside image.", - "delivers": "artifact-store", "includes": [ "registry" ], @@ -190,6 +189,36 @@ ] }, "optional": true + }, + { + "name": "list", + "description": "What the store holds of one repository, or of every repository the seat holds when none is named: every image manifest and index, each with its digest, whether it is an index (and the manifests an index names), the tags naming it, its size and whether it is held. Read from the store itself, so a manifest no tag names is listed too; it reads only (ADR 0632 §3).", + "callers": "any caller whose role grants it", + "input": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the repository, /; every repository of the seat when not given" + } + } + }, + "answer": { + "type": "object", + "properties": { + "repositories": { + "type": "array", + "items": { + "$ref": "#/defs/Repository" + }, + "description": "each repository named, in order of its name" + } + }, + "required": [ + "repositories" + ] + }, + "optional": true } ], "events": [ @@ -210,24 +239,6 @@ "name": "container-image-registry.events", "max-age-days": 7 }, - "provisions": [ - { - "name": "artifact-store", - "form": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "the store's address" - } - }, - "required": [ - "url" - ], - "description": "the artifact store's read side, the OCI distribution protocol's GET and HEAD, open on the mesh network (design 51)" - } - } - ], "defs": { "Hold": { "type": "object", @@ -263,6 +274,71 @@ "holder" ], "description": "the body of held and released" + }, + "Repository": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the repository" + }, + "manifests": { + "type": "array", + "items": { + "$ref": "#/defs/Listed" + }, + "description": "each manifest it holds, by digest" + } + }, + "required": [ + "name", + "manifests" + ] + }, + "Listed": { + "type": "object", + "properties": { + "digest": { + "type": "string", + "description": "the manifest's digest, sha256:" + }, + "index": { + "type": "boolean", + "description": "it is an index, naming a manifest per platform" + }, + "platforms": { + "type": "array", + "items": { + "type": "string" + }, + "description": "for an index, the digests of the manifests it names" + }, + "artifact": { + "type": "string", + "description": "for a manifest keeping one bundle or archive, the digest of that blob: what a reference names it by" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "the tags naming it, none for an untagged one" + }, + "size": { + "type": "integer", + "description": "its bytes: its own content and every blob it names" + }, + "held": { + "type": "boolean", + "description": "a hold keeps it from retention" + } + }, + "required": [ + "digest", + "index", + "size", + "held" + ] } } } diff --git a/seats/mesh-artifact-store/seat.json b/seats/mesh-artifact-store/seat.json index b1efa95..84c8a57 100644 --- a/seats/mesh-artifact-store/seat.json +++ b/seats/mesh-artifact-store/seat.json @@ -3,6 +3,5 @@ "seat": "mesh-artifact-store", "scope": "mesh", "grain": "seat-list", - "decision": "novox/hq ADR 0075", - "delivers": "artifact-store" + "decision": "novox/hq ADR 0075" } diff --git a/seats/oci-artifact-registry/fixtures/verbs.json b/seats/oci-artifact-registry/fixtures/verbs.json index e0121b7..039c38b 100644 --- a/seats/oci-artifact-registry/fixtures/verbs.json +++ b/seats/oci-artifact-registry/fixtures/verbs.json @@ -114,5 +114,25 @@ "0.1.19" ] } + }, + { + "name": "list answers an untagged archive by the blob it keeps", + "of": "verb:list.answer", + "given": { + "repositories": [ + { + "name": "mail/files", + "manifests": [ + { + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "index": false, + "artifact": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "size": 4096, + "held": true + } + ] + } + ] + } } ] diff --git a/seats/oci-artifact-registry/seat.json b/seats/oci-artifact-registry/seat.json index df5f58c..9d37fce 100644 --- a/seats/oci-artifact-registry/seat.json +++ b/seats/oci-artifact-registry/seat.json @@ -5,7 +5,6 @@ "grain": "whole", "decision": "novox/hq ADR 0605", "description": "The mesh's bundles and archives, by digest, in the one artifact store: the OCI distribution protocol's read side.", - "delivers": "artifact-store", "includes": [ "registry" ], @@ -93,6 +92,36 @@ ] }, "optional": true + }, + { + "name": "list", + "description": "What the store holds of one repository, or of every repository the seat holds when none is named: every manifest keeping one bundle or archive, each with its digest, whether it is an index (and the manifests an index names), the tags naming it, its size and whether it is held. Read from the store itself, so a manifest no tag names is listed too; it reads only (ADR 0632 §3).", + "callers": "any caller whose role grants it", + "input": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the repository, /; every repository of the seat when not given" + } + } + }, + "answer": { + "type": "object", + "properties": { + "repositories": { + "type": "array", + "items": { + "$ref": "#/defs/Repository" + }, + "description": "each repository named, in order of its name" + } + }, + "required": [ + "repositories" + ] + }, + "optional": true } ], "events": [ @@ -113,24 +142,6 @@ "name": "oci-artifact-registry.events", "max-age-days": 7 }, - "provisions": [ - { - "name": "artifact-store", - "form": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "the store's address" - } - }, - "required": [ - "url" - ], - "description": "the artifact store's read side, the OCI distribution protocol's GET and HEAD, open on the mesh network (design 51)" - } - } - ], "defs": { "Hold": { "type": "object", @@ -166,6 +177,71 @@ "holder" ], "description": "the body of held and released" + }, + "Repository": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the repository" + }, + "manifests": { + "type": "array", + "items": { + "$ref": "#/defs/Listed" + }, + "description": "each manifest it holds, by digest" + } + }, + "required": [ + "name", + "manifests" + ] + }, + "Listed": { + "type": "object", + "properties": { + "digest": { + "type": "string", + "description": "the manifest's digest, sha256:" + }, + "index": { + "type": "boolean", + "description": "it is an index, naming a manifest per platform" + }, + "platforms": { + "type": "array", + "items": { + "type": "string" + }, + "description": "for an index, the digests of the manifests it names" + }, + "artifact": { + "type": "string", + "description": "for a manifest keeping one bundle or archive, the digest of that blob: what a reference names it by" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "the tags naming it, none for an untagged one" + }, + "size": { + "type": "integer", + "description": "its bytes: its own content and every blob it names" + }, + "held": { + "type": "boolean", + "description": "a hold keeps it from retention" + } + }, + "required": [ + "digest", + "index", + "size", + "held" + ] } } } diff --git a/ts/seats/containerimageregistry/src/index.ts b/ts/seats/containerimageregistry/src/index.ts index 9baf704..941ed44 100644 --- a/ts/seats/containerimageregistry/src/index.ts +++ b/ts/seats/containerimageregistry/src/index.ts @@ -15,6 +15,7 @@ export const verbs = { tags: "tags", hold: "hold", release: "release", + list: "list", } as const; export const events = { @@ -72,6 +73,30 @@ export interface Hold { "reason"?: string; } +export interface Listed { + /** for a manifest keeping one bundle or archive, the digest of that blob: what a reference names it by */ + "artifact"?: string; + /** the manifest's digest, sha256: */ + "digest": string; + /** a hold keeps it from retention */ + "held": boolean; + /** it is an index, naming a manifest per platform */ + "index": boolean; + /** for an index, the digests of the manifests it names */ + "platforms"?: string[]; + /** its bytes: its own content and every blob it names */ + "size": number; + /** the tags naming it, none for an untagged one */ + "tags"?: string[]; +} + +export interface Repository { + /** each manifest it holds, by digest */ + "manifests": Listed[]; + /** the repository */ + "name": string; +} + /** is what mirror takes. */ export interface MirrorInput { /** its pinned digest, sha256: */ @@ -145,15 +170,21 @@ export interface ReleaseAnswer { "released": boolean; } +/** is what list takes. */ +export interface ListInput { + /** the repository, /; every repository of the seat when not given */ + "name"?: string; +} + +/** is what list answers. */ +export interface ListAnswer { + /** each repository named, in order of its name */ + "repositories": Repository[]; +} + /** is the body of the event held. */ export type HeldEvent = Hold; /** is the body of the event released. */ export type ReleasedEvent = Hold; -/** is the form of the provision artifact-store. */ -export interface ArtifactStoreForm { - /** the store's address */ - "url": string; -} - diff --git a/ts/seats/ociartifactregistry/src/index.ts b/ts/seats/ociartifactregistry/src/index.ts index c1fa55e..ebf6242 100644 --- a/ts/seats/ociartifactregistry/src/index.ts +++ b/ts/seats/ociartifactregistry/src/index.ts @@ -13,6 +13,7 @@ export const verbs = { remove: "remove", hold: "hold", release: "release", + list: "list", } as const; export const events = { @@ -70,6 +71,30 @@ export interface Hold { "reason"?: string; } +export interface Listed { + /** for a manifest keeping one bundle or archive, the digest of that blob: what a reference names it by */ + "artifact"?: string; + /** the manifest's digest, sha256: */ + "digest": string; + /** a hold keeps it from retention */ + "held": boolean; + /** it is an index, naming a manifest per platform */ + "index": boolean; + /** for an index, the digests of the manifests it names */ + "platforms"?: string[]; + /** its bytes: its own content and every blob it names */ + "size": number; + /** the tags naming it, none for an untagged one */ + "tags"?: string[]; +} + +export interface Repository { + /** each manifest it holds, by digest */ + "manifests": Listed[]; + /** the repository */ + "name": string; +} + /** is what hold takes. */ export interface HoldInput { /** the digest */ @@ -104,15 +129,21 @@ export interface ReleaseAnswer { "released": boolean; } +/** is what list takes. */ +export interface ListInput { + /** the repository, /; every repository of the seat when not given */ + "name"?: string; +} + +/** is what list answers. */ +export interface ListAnswer { + /** each repository named, in order of its name */ + "repositories": Repository[]; +} + /** is the body of the event held. */ export type HeldEvent = Hold; /** is the body of the event released. */ export type ReleasedEvent = Hold; -/** is the form of the provision artifact-store. */ -export interface ArtifactStoreForm { - /** the store's address */ - "url": string; -} - diff --git a/ts/src/seats.ts b/ts/src/seats.ts index efe9c39..6c51e36 100644 --- a/ts/src/seats.ts +++ b/ts/src/seats.ts @@ -1810,7 +1810,6 @@ export const list: readonly Seat[] = [ }, { "decision": "novox/hq ADR 0075", - "delivers": "artifact-store", "grain": "seat-list", "name": "mesh-artifact-store", "scope": "mesh" @@ -3809,7 +3808,6 @@ export const list: readonly Seat[] = [ }, { "decision": "novox/hq ADR 0605", - "delivers": "artifact-store", "emits": [ "published", "refused", @@ -4358,12 +4356,99 @@ export const list: readonly Seat[] = [ ], "type": "object" } + }, + { + "description": "What the store holds of one repository, or of every repository the seat holds when none is named: every image manifest and index, each with its digest, whether it is an index (and the manifests an index names), the tags naming it, its size and whether it is held. Read from the store itself, so a manifest no tag names is listed too; it reads only (ADR 0632 §3).", + "input": { + "properties": { + "name": { + "description": "the repository, /; every repository of the seat when not given", + "type": "string" + } + }, + "type": "object" + }, + "name": "list", + "optional": true, + "output": { + "properties": { + "repositories": { + "description": "each repository named, in order of its name", + "items": { + "properties": { + "manifests": { + "description": "each manifest it holds, by digest", + "items": { + "properties": { + "artifact": { + "description": "for a manifest keeping one bundle or archive, the digest of that blob: what a reference names it by", + "type": "string" + }, + "digest": { + "description": "the manifest's digest, sha256:", + "type": "string" + }, + "held": { + "description": "a hold keeps it from retention", + "type": "boolean" + }, + "index": { + "description": "it is an index, naming a manifest per platform", + "type": "boolean" + }, + "platforms": { + "description": "for an index, the digests of the manifests it names", + "items": { + "type": "string" + }, + "type": "array" + }, + "size": { + "description": "its bytes: its own content and every blob it names", + "type": "integer" + }, + "tags": { + "description": "the tags naming it, none for an untagged one", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "digest", + "index", + "size", + "held" + ], + "type": "object" + }, + "type": "array" + }, + "name": { + "description": "the repository", + "type": "string" + } + }, + "required": [ + "name", + "manifests" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "repositories" + ], + "type": "object" + } } ] }, { "decision": "novox/hq ADR 0605", - "delivers": "artifact-store", "emits": [ "published", "refused", @@ -4817,6 +4902,94 @@ export const list: readonly Seat[] = [ ], "type": "object" } + }, + { + "description": "What the store holds of one repository, or of every repository the seat holds when none is named: every manifest keeping one bundle or archive, each with its digest, whether it is an index (and the manifests an index names), the tags naming it, its size and whether it is held. Read from the store itself, so a manifest no tag names is listed too; it reads only (ADR 0632 §3).", + "input": { + "properties": { + "name": { + "description": "the repository, /; every repository of the seat when not given", + "type": "string" + } + }, + "type": "object" + }, + "name": "list", + "optional": true, + "output": { + "properties": { + "repositories": { + "description": "each repository named, in order of its name", + "items": { + "properties": { + "manifests": { + "description": "each manifest it holds, by digest", + "items": { + "properties": { + "artifact": { + "description": "for a manifest keeping one bundle or archive, the digest of that blob: what a reference names it by", + "type": "string" + }, + "digest": { + "description": "the manifest's digest, sha256:", + "type": "string" + }, + "held": { + "description": "a hold keeps it from retention", + "type": "boolean" + }, + "index": { + "description": "it is an index, naming a manifest per platform", + "type": "boolean" + }, + "platforms": { + "description": "for an index, the digests of the manifests it names", + "items": { + "type": "string" + }, + "type": "array" + }, + "size": { + "description": "its bytes: its own content and every blob it names", + "type": "integer" + }, + "tags": { + "description": "the tags naming it, none for an untagged one", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "digest", + "index", + "size", + "held" + ], + "type": "object" + }, + "type": "array" + }, + "name": { + "description": "the repository", + "type": "string" + } + }, + "required": [ + "name", + "manifests" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "repositories" + ], + "type": "object" + } } ] },