From a6f29d42be7763b78ae52c29a367d093b46ac4bb Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 14:07:09 +0100 Subject: [PATCH] =?UTF-8?q?mesh-delivery-contracts:=20the=20four=20registr?= =?UTF-8?q?y=20seats,=20their=20parts=20and=20clients=20move=20in=20from?= =?UTF-8?q?=20mesh-contracts=20(D-1,=20ADR=200634=20=C2=A77;=20issue=20196?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../containerimageregistryclient/client.go | 63 +++ .../client_test.go | 57 +++ .../containerimageregistryclient/go.mod | 17 + .../containerimageregistryclient/go.sum | 18 + .../clients/gopackageregistryclient/client.go | 63 +++ .../gopackageregistryclient/client_test.go | 57 +++ .../go/clients/gopackageregistryclient/go.mod | 17 + .../go/clients/gopackageregistryclient/go.sum | 18 + .../npmpackageregistryclient/client.go | 63 +++ .../npmpackageregistryclient/client_test.go | 57 +++ .../clients/npmpackageregistryclient/go.mod | 17 + .../clients/npmpackageregistryclient/go.sum | 18 + .../ociartifactregistryclient/client.go | 63 +++ .../ociartifactregistryclient/client_test.go | 57 +++ .../clients/ociartifactregistryclient/go.mod | 17 + .../clients/ociartifactregistryclient/go.sum | 18 + .../seats/containerimageregistry/contract.go | 403 ++++++++++++++++ .../containerimageregistry/contract_test.go | 74 +++ .../go/seats/containerimageregistry/go.mod | 3 + .../go/seats/gopackageregistry/contract.go | 262 +++++++++++ .../seats/gopackageregistry/contract_test.go | 66 +++ contracts/go/seats/gopackageregistry/go.mod | 3 + .../go/seats/npmpackageregistry/contract.go | 262 +++++++++++ .../seats/npmpackageregistry/contract_test.go | 66 +++ contracts/go/seats/npmpackageregistry/go.mod | 3 + .../go/seats/ociartifactregistry/contract.go | 361 +++++++++++++++ .../ociartifactregistry/contract_test.go | 74 +++ contracts/go/seats/ociartifactregistry/go.mod | 3 + contracts/module.json | 125 +++++ contracts/parts/registry.json | 436 ++++++++++++++++++ .../fixtures/events.json | 98 ++++ .../fixtures/verbs.json | 149 ++++++ .../seats/container-image-registry/seat.json | 344 ++++++++++++++ .../go-package-registry/fixtures/events.json | 73 +++ .../go-package-registry/fixtures/verbs.json | 110 +++++ contracts/seats/go-package-registry/seat.json | 34 ++ .../npm-package-registry/fixtures/events.json | 72 +++ .../npm-package-registry/fixtures/verbs.json | 110 +++++ .../seats/npm-package-registry/seat.json | 34 ++ .../fixtures/events.json | 98 ++++ .../oci-artifact-registry/fixtures/verbs.json | 138 ++++++ .../seats/oci-artifact-registry/seat.json | 247 ++++++++++ .../containerimageregistry/package-lock.json | 12 + .../seats/containerimageregistry/package.json | 13 + .../seats/containerimageregistry/src/index.ts | 190 ++++++++ .../src/parts/registry.ts | 187 ++++++++ .../containerimageregistry/tsconfig.json | 12 + .../seats/gopackageregistry/package-lock.json | 12 + .../ts/seats/gopackageregistry/package.json | 13 + .../ts/seats/gopackageregistry/src/index.ts | 54 +++ .../gopackageregistry/src/parts/registry.ts | 187 ++++++++ .../ts/seats/gopackageregistry/tsconfig.json | 12 + .../npmpackageregistry/package-lock.json | 12 + .../ts/seats/npmpackageregistry/package.json | 13 + .../ts/seats/npmpackageregistry/src/index.ts | 54 +++ .../npmpackageregistry/src/parts/registry.ts | 187 ++++++++ .../ts/seats/npmpackageregistry/tsconfig.json | 12 + .../ociartifactregistry/package-lock.json | 12 + .../ts/seats/ociartifactregistry/package.json | 13 + .../ts/seats/ociartifactregistry/src/index.ts | 149 ++++++ .../ociartifactregistry/src/parts/registry.ts | 187 ++++++++ .../seats/ociartifactregistry/tsconfig.json | 12 + 62 files changed, 5611 insertions(+) create mode 100644 contracts/go/clients/containerimageregistryclient/client.go create mode 100644 contracts/go/clients/containerimageregistryclient/client_test.go create mode 100644 contracts/go/clients/containerimageregistryclient/go.mod create mode 100644 contracts/go/clients/containerimageregistryclient/go.sum create mode 100644 contracts/go/clients/gopackageregistryclient/client.go create mode 100644 contracts/go/clients/gopackageregistryclient/client_test.go create mode 100644 contracts/go/clients/gopackageregistryclient/go.mod create mode 100644 contracts/go/clients/gopackageregistryclient/go.sum create mode 100644 contracts/go/clients/npmpackageregistryclient/client.go create mode 100644 contracts/go/clients/npmpackageregistryclient/client_test.go create mode 100644 contracts/go/clients/npmpackageregistryclient/go.mod create mode 100644 contracts/go/clients/npmpackageregistryclient/go.sum create mode 100644 contracts/go/clients/ociartifactregistryclient/client.go create mode 100644 contracts/go/clients/ociartifactregistryclient/client_test.go create mode 100644 contracts/go/clients/ociartifactregistryclient/go.mod create mode 100644 contracts/go/clients/ociartifactregistryclient/go.sum create mode 100644 contracts/go/seats/containerimageregistry/contract.go create mode 100644 contracts/go/seats/containerimageregistry/contract_test.go create mode 100644 contracts/go/seats/containerimageregistry/go.mod create mode 100644 contracts/go/seats/gopackageregistry/contract.go create mode 100644 contracts/go/seats/gopackageregistry/contract_test.go create mode 100644 contracts/go/seats/gopackageregistry/go.mod create mode 100644 contracts/go/seats/npmpackageregistry/contract.go create mode 100644 contracts/go/seats/npmpackageregistry/contract_test.go create mode 100644 contracts/go/seats/npmpackageregistry/go.mod create mode 100644 contracts/go/seats/ociartifactregistry/contract.go create mode 100644 contracts/go/seats/ociartifactregistry/contract_test.go create mode 100644 contracts/go/seats/ociartifactregistry/go.mod create mode 100644 contracts/module.json create mode 100644 contracts/parts/registry.json create mode 100644 contracts/seats/container-image-registry/fixtures/events.json create mode 100644 contracts/seats/container-image-registry/fixtures/verbs.json create mode 100644 contracts/seats/container-image-registry/seat.json create mode 100644 contracts/seats/go-package-registry/fixtures/events.json create mode 100644 contracts/seats/go-package-registry/fixtures/verbs.json create mode 100644 contracts/seats/go-package-registry/seat.json create mode 100644 contracts/seats/npm-package-registry/fixtures/events.json create mode 100644 contracts/seats/npm-package-registry/fixtures/verbs.json create mode 100644 contracts/seats/npm-package-registry/seat.json create mode 100644 contracts/seats/oci-artifact-registry/fixtures/events.json create mode 100644 contracts/seats/oci-artifact-registry/fixtures/verbs.json create mode 100644 contracts/seats/oci-artifact-registry/seat.json create mode 100644 contracts/ts/seats/containerimageregistry/package-lock.json create mode 100644 contracts/ts/seats/containerimageregistry/package.json create mode 100644 contracts/ts/seats/containerimageregistry/src/index.ts create mode 100644 contracts/ts/seats/containerimageregistry/src/parts/registry.ts create mode 100644 contracts/ts/seats/containerimageregistry/tsconfig.json create mode 100644 contracts/ts/seats/gopackageregistry/package-lock.json create mode 100644 contracts/ts/seats/gopackageregistry/package.json create mode 100644 contracts/ts/seats/gopackageregistry/src/index.ts create mode 100644 contracts/ts/seats/gopackageregistry/src/parts/registry.ts create mode 100644 contracts/ts/seats/gopackageregistry/tsconfig.json create mode 100644 contracts/ts/seats/npmpackageregistry/package-lock.json create mode 100644 contracts/ts/seats/npmpackageregistry/package.json create mode 100644 contracts/ts/seats/npmpackageregistry/src/index.ts create mode 100644 contracts/ts/seats/npmpackageregistry/src/parts/registry.ts create mode 100644 contracts/ts/seats/npmpackageregistry/tsconfig.json create mode 100644 contracts/ts/seats/ociartifactregistry/package-lock.json create mode 100644 contracts/ts/seats/ociartifactregistry/package.json create mode 100644 contracts/ts/seats/ociartifactregistry/src/index.ts create mode 100644 contracts/ts/seats/ociartifactregistry/src/parts/registry.ts create mode 100644 contracts/ts/seats/ociartifactregistry/tsconfig.json diff --git a/contracts/go/clients/containerimageregistryclient/client.go b/contracts/go/clients/containerimageregistryclient/client.go new file mode 100644 index 00000000..f1468350 --- /dev/null +++ b/contracts/go/clients/containerimageregistryclient/client.go @@ -0,0 +1,63 @@ +// Package containerimageregistryclient is the container-image-registry seat's client (novox/hq ADR 0627 §1, §2; ADR 0611, design 51 +// *Publishing*): it publishes a file in parts to the seat's holder. `publish` opens the transfer naming every part, +// the holder answers the parts it lacks, and only those travel by `publish-part`, in chunks a bus message carries; a +// cut transfer goes on from what the holder answers when the same transfer is opened again. The sending in parts is +// mesh-sdk's seat-neutral runtime (parts); this client binds it to this seat's verbs and types alone. The call is the +// caller's own (a bundle's stdio.Ask, a process's call of a seat verb), so it holds no transport. +package containerimageregistryclient + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry" + "git.novox.be/novox/mesh-sdk/go/parts" +) + +// Call asks one verb of the seat, `seat:.`, with a body, and answers its JSON. +type Call func(key string, body any) (json.RawMessage, error) + +// Sent is what a Publish did: the transfer, whether the holder held its digest already, the parts it lacked and were +// sent, and the chunks they took. +type Sent struct { + Transfer string + parts.Sent +} + +// Publish opens (or opens again) the transfer in on the seat and sends the parts the holder answers it lacks, each in +// chunks of at most parts.MaxChunk, in sequence, the last marked. in.Parts is written from parts. A part whose bytes +// do not hash to its digest is refused before anything of it is sent; a part the holder does not name is never read. +// Its outcome, published or refused, is the holder's event, which the caller waits for. +func Publish(ctx context.Context, call Call, in seat.PublishInput, ps []parts.Part) (Sent, error) { + open := func(_ context.Context, ps []parts.Part) (parts.Opened, error) { + in.Parts = make([]seat.Part, 0, len(ps)) + for _, p := range ps { + in.Parts = append(in.Parts, seat.Part{Digest: p.Digest, Size: p.Size}) + } + raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublish, in) + if err != nil { + return parts.Opened{}, fmt.Errorf("%s's publish of %s: %w", seat.Seat, in.Name, err) + } + var a seat.PublishAnswer + if err := json.Unmarshal(raw, &a); err != nil { + return parts.Opened{}, fmt.Errorf("%s's answer to publish: %w", seat.Seat, err) + } + return parts.Opened{Held: a.Held, Lacks: a.Lacks}, nil + } + chunk := func(_ context.Context, part string, sequence int, last bool, data []byte) error { + raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublishPart, seat.PublishPartInput{Transfer: in.Transfer, Part: part, + Sequence: int64(sequence), Last: last, Data: base64.StdEncoding.EncodeToString(data)}) + if err != nil { + return fmt.Errorf("%s's publish-part: %w", seat.Seat, err) + } + var a seat.PublishPartAnswer + if err := json.Unmarshal(raw, &a); err != nil { + return fmt.Errorf("%s's answer to publish-part: %w", seat.Seat, err) + } + return nil + } + sent, err := parts.Send(ctx, open, chunk, ps) + return Sent{Transfer: in.Transfer, Sent: sent}, err +} diff --git a/contracts/go/clients/containerimageregistryclient/client_test.go b/contracts/go/clients/containerimageregistryclient/client_test.go new file mode 100644 index 00000000..b803d61f --- /dev/null +++ b/contracts/go/clients/containerimageregistryclient/client_test.go @@ -0,0 +1,57 @@ +package containerimageregistryclient + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "io" + "testing" + + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry" + "git.novox.be/novox/mesh-sdk/go/parts" +) + +// The client publishes over its own seat's verbs alone (ADR 0627 §1): publish names every part, and only the part the +// holder lacks travels by publish-part, its bytes whole, the last chunk marked. +func TestPublishTalksToItsOwnSeat(t *testing.T) { + held, lacked := []byte("held"), []byte("lacked") + part := func(b []byte) parts.Part { + sum := sha256.Sum256(b) + return parts.Part{Digest: "sha256:" + hex.EncodeToString(sum[:]), Size: int64(len(b)), + Open: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }} + } + ps := []parts.Part{part(held), part(lacked)} + var asked []string + var got []byte + call := func(key string, body any) (json.RawMessage, error) { + asked = append(asked, key) + raw, _ := json.Marshal(body) + switch key { + case "seat:container-image-registry.publish": + var in seat.PublishInput + _ = json.Unmarshal(raw, &in) + if len(in.Parts) != 2 { + t.Errorf("publish names %d parts, want 2", len(in.Parts)) + } + return json.Marshal(seat.PublishAnswer{Transfer: in.Transfer, Lacks: []string{ps[1].Digest}}) + case "seat:container-image-registry.publish-part": + var in seat.PublishPartInput + _ = json.Unmarshal(raw, &in) + data, _ := base64.StdEncoding.DecodeString(in.Data) + if in.Part != ps[1].Digest || !in.Last || in.Transfer != "t1" { + t.Errorf("publish-part %+v", in) + } + got = append(got, data...) + return json.Marshal(seat.PublishPartAnswer{Transfer: in.Transfer, Part: in.Part}) + } + t.Fatalf("asked %s", key) + return nil, nil + } + sent, err := Publish(context.Background(), call, seat.PublishInput{Transfer: "t1", Name: "x"}, ps) + if err != nil || sent.Transfer != "t1" || sent.Chunks != 1 || len(sent.Parts) != 1 || !bytes.Equal(got, lacked) || len(asked) != 2 { + t.Errorf("sent %+v, %q arrived, asked %v, %v", sent, got, asked, err) + } +} diff --git a/contracts/go/clients/containerimageregistryclient/go.mod b/contracts/go/clients/containerimageregistryclient/go.mod new file mode 100644 index 00000000..25e437c9 --- /dev/null +++ b/contracts/go/clients/containerimageregistryclient/go.mod @@ -0,0 +1,17 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient + +go 1.26.0 + +require ( + git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef +) + +require ( + github.com/klauspost/compress v1.20.1 // indirect + github.com/nats-io/nats.go v1.54.0 // indirect + github.com/nats-io/nkeys v0.4.16 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + golang.org/x/crypto v0.57.0 // indirect + golang.org/x/sys v0.48.0 // indirect +) diff --git a/contracts/go/clients/containerimageregistryclient/go.sum b/contracts/go/clients/containerimageregistryclient/go.sum new file mode 100644 index 00000000..c811fda7 --- /dev/null +++ b/contracts/go/clients/containerimageregistryclient/go.sum @@ -0,0 +1,18 @@ +git.novox.be/novox/mesh-contracts/go v0.0.0-20261104172218-347a3053580a h1:E9ERPqR4uZXypudHGxv2/wNIOYcder9torVqJwJq/DI= +git.novox.be/novox/mesh-contracts/go v0.0.0-20261104172218-347a3053580a/go.mod h1:0dD1XEJBJ0G5UYDQxSZI84nuujb6m3tOpJl4C8rQVvg= +git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-20261105000000-000000000000 h1:BTqpa7dCCTQH9m08Jle8KVFwyIHiHPSUbwWxuqSg2yA= +git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-20261105000000-000000000000/go.mod h1:pPxwhTMb3hIk7UUrBicJnGQCqfTEoBGJvNFspEK2U18= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef h1:/Jic5ho0xz32Z9DH2NSUqVxCF1KyfIOWuRTrX1aLjus= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef/go.mod h1:2wHG7ntLCG8Tpm/SCfgp524a5emUJm1gAC4KrRYsU9s= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA= +github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k= +github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= +github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= diff --git a/contracts/go/clients/gopackageregistryclient/client.go b/contracts/go/clients/gopackageregistryclient/client.go new file mode 100644 index 00000000..7629ccc4 --- /dev/null +++ b/contracts/go/clients/gopackageregistryclient/client.go @@ -0,0 +1,63 @@ +// Package gopackageregistryclient is the go-package-registry seat's client (novox/hq ADR 0627 §1, §2; ADR 0611, design 51 +// *Publishing*): it publishes a file in parts to the seat's holder. `publish` opens the transfer naming every part, +// the holder answers the parts it lacks, and only those travel by `publish-part`, in chunks a bus message carries; a +// cut transfer goes on from what the holder answers when the same transfer is opened again. The sending in parts is +// mesh-sdk's seat-neutral runtime (parts); this client binds it to this seat's verbs and types alone. The call is the +// caller's own (a bundle's stdio.Ask, a process's call of a seat verb), so it holds no transport. +package gopackageregistryclient + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry" + "git.novox.be/novox/mesh-sdk/go/parts" +) + +// Call asks one verb of the seat, `seat:.`, with a body, and answers its JSON. +type Call func(key string, body any) (json.RawMessage, error) + +// Sent is what a Publish did: the transfer, whether the holder held its digest already, the parts it lacked and were +// sent, and the chunks they took. +type Sent struct { + Transfer string + parts.Sent +} + +// Publish opens (or opens again) the transfer in on the seat and sends the parts the holder answers it lacks, each in +// chunks of at most parts.MaxChunk, in sequence, the last marked. in.Parts is written from parts. A part whose bytes +// do not hash to its digest is refused before anything of it is sent; a part the holder does not name is never read. +// Its outcome, published or refused, is the holder's event, which the caller waits for. +func Publish(ctx context.Context, call Call, in seat.PublishInput, ps []parts.Part) (Sent, error) { + open := func(_ context.Context, ps []parts.Part) (parts.Opened, error) { + in.Parts = make([]seat.Part, 0, len(ps)) + for _, p := range ps { + in.Parts = append(in.Parts, seat.Part{Digest: p.Digest, Size: p.Size}) + } + raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublish, in) + if err != nil { + return parts.Opened{}, fmt.Errorf("%s's publish of %s: %w", seat.Seat, in.Name, err) + } + var a seat.PublishAnswer + if err := json.Unmarshal(raw, &a); err != nil { + return parts.Opened{}, fmt.Errorf("%s's answer to publish: %w", seat.Seat, err) + } + return parts.Opened{Held: a.Held, Lacks: a.Lacks}, nil + } + chunk := func(_ context.Context, part string, sequence int, last bool, data []byte) error { + raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublishPart, seat.PublishPartInput{Transfer: in.Transfer, Part: part, + Sequence: int64(sequence), Last: last, Data: base64.StdEncoding.EncodeToString(data)}) + if err != nil { + return fmt.Errorf("%s's publish-part: %w", seat.Seat, err) + } + var a seat.PublishPartAnswer + if err := json.Unmarshal(raw, &a); err != nil { + return fmt.Errorf("%s's answer to publish-part: %w", seat.Seat, err) + } + return nil + } + sent, err := parts.Send(ctx, open, chunk, ps) + return Sent{Transfer: in.Transfer, Sent: sent}, err +} diff --git a/contracts/go/clients/gopackageregistryclient/client_test.go b/contracts/go/clients/gopackageregistryclient/client_test.go new file mode 100644 index 00000000..6294e2a1 --- /dev/null +++ b/contracts/go/clients/gopackageregistryclient/client_test.go @@ -0,0 +1,57 @@ +package gopackageregistryclient + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "io" + "testing" + + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry" + "git.novox.be/novox/mesh-sdk/go/parts" +) + +// The client publishes over its own seat's verbs alone (ADR 0627 §1): publish names every part, and only the part the +// holder lacks travels by publish-part, its bytes whole, the last chunk marked. +func TestPublishTalksToItsOwnSeat(t *testing.T) { + held, lacked := []byte("held"), []byte("lacked") + part := func(b []byte) parts.Part { + sum := sha256.Sum256(b) + return parts.Part{Digest: "sha256:" + hex.EncodeToString(sum[:]), Size: int64(len(b)), + Open: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }} + } + ps := []parts.Part{part(held), part(lacked)} + var asked []string + var got []byte + call := func(key string, body any) (json.RawMessage, error) { + asked = append(asked, key) + raw, _ := json.Marshal(body) + switch key { + case "seat:go-package-registry.publish": + var in seat.PublishInput + _ = json.Unmarshal(raw, &in) + if len(in.Parts) != 2 { + t.Errorf("publish names %d parts, want 2", len(in.Parts)) + } + return json.Marshal(seat.PublishAnswer{Transfer: in.Transfer, Lacks: []string{ps[1].Digest}}) + case "seat:go-package-registry.publish-part": + var in seat.PublishPartInput + _ = json.Unmarshal(raw, &in) + data, _ := base64.StdEncoding.DecodeString(in.Data) + if in.Part != ps[1].Digest || !in.Last || in.Transfer != "t1" { + t.Errorf("publish-part %+v", in) + } + got = append(got, data...) + return json.Marshal(seat.PublishPartAnswer{Transfer: in.Transfer, Part: in.Part}) + } + t.Fatalf("asked %s", key) + return nil, nil + } + sent, err := Publish(context.Background(), call, seat.PublishInput{Transfer: "t1", Name: "x"}, ps) + if err != nil || sent.Transfer != "t1" || sent.Chunks != 1 || len(sent.Parts) != 1 || !bytes.Equal(got, lacked) || len(asked) != 2 { + t.Errorf("sent %+v, %q arrived, asked %v, %v", sent, got, asked, err) + } +} diff --git a/contracts/go/clients/gopackageregistryclient/go.mod b/contracts/go/clients/gopackageregistryclient/go.mod new file mode 100644 index 00000000..cfbfd6db --- /dev/null +++ b/contracts/go/clients/gopackageregistryclient/go.mod @@ -0,0 +1,17 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/clients/gopackageregistryclient + +go 1.26.0 + +require ( + git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry v0.0.0 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef +) + +require ( + github.com/klauspost/compress v1.20.1 // indirect + github.com/nats-io/nats.go v1.54.0 // indirect + github.com/nats-io/nkeys v0.4.16 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + golang.org/x/crypto v0.57.0 // indirect + golang.org/x/sys v0.48.0 // indirect +) diff --git a/contracts/go/clients/gopackageregistryclient/go.sum b/contracts/go/clients/gopackageregistryclient/go.sum new file mode 100644 index 00000000..1ab0e3c0 --- /dev/null +++ b/contracts/go/clients/gopackageregistryclient/go.sum @@ -0,0 +1,18 @@ +git.novox.be/novox/mesh-contracts/go v0.0.0-20261104172218-347a3053580a h1:E9ERPqR4uZXypudHGxv2/wNIOYcder9torVqJwJq/DI= +git.novox.be/novox/mesh-contracts/go v0.0.0-20261104172218-347a3053580a/go.mod h1:0dD1XEJBJ0G5UYDQxSZI84nuujb6m3tOpJl4C8rQVvg= +git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry v0.0.0-20261105000000-000000000000 h1:Zl6CzMg9AZtk7RU/DSJxSGGoUg5fSW3t7Yf8zyfXbVY= +git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry v0.0.0-20261105000000-000000000000/go.mod h1:Y5kgBh0hBbL/hgc+abCF9Vbhov+vCsB0ckpRzUw1D+0= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef h1:/Jic5ho0xz32Z9DH2NSUqVxCF1KyfIOWuRTrX1aLjus= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef/go.mod h1:2wHG7ntLCG8Tpm/SCfgp524a5emUJm1gAC4KrRYsU9s= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA= +github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k= +github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= +github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= diff --git a/contracts/go/clients/npmpackageregistryclient/client.go b/contracts/go/clients/npmpackageregistryclient/client.go new file mode 100644 index 00000000..c2a2e403 --- /dev/null +++ b/contracts/go/clients/npmpackageregistryclient/client.go @@ -0,0 +1,63 @@ +// Package npmpackageregistryclient is the npm-package-registry seat's client (novox/hq ADR 0627 §1, §2; ADR 0611, design 51 +// *Publishing*): it publishes a file in parts to the seat's holder. `publish` opens the transfer naming every part, +// the holder answers the parts it lacks, and only those travel by `publish-part`, in chunks a bus message carries; a +// cut transfer goes on from what the holder answers when the same transfer is opened again. The sending in parts is +// mesh-sdk's seat-neutral runtime (parts); this client binds it to this seat's verbs and types alone. The call is the +// caller's own (a bundle's stdio.Ask, a process's call of a seat verb), so it holds no transport. +package npmpackageregistryclient + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry" + "git.novox.be/novox/mesh-sdk/go/parts" +) + +// Call asks one verb of the seat, `seat:.`, with a body, and answers its JSON. +type Call func(key string, body any) (json.RawMessage, error) + +// Sent is what a Publish did: the transfer, whether the holder held its digest already, the parts it lacked and were +// sent, and the chunks they took. +type Sent struct { + Transfer string + parts.Sent +} + +// Publish opens (or opens again) the transfer in on the seat and sends the parts the holder answers it lacks, each in +// chunks of at most parts.MaxChunk, in sequence, the last marked. in.Parts is written from parts. A part whose bytes +// do not hash to its digest is refused before anything of it is sent; a part the holder does not name is never read. +// Its outcome, published or refused, is the holder's event, which the caller waits for. +func Publish(ctx context.Context, call Call, in seat.PublishInput, ps []parts.Part) (Sent, error) { + open := func(_ context.Context, ps []parts.Part) (parts.Opened, error) { + in.Parts = make([]seat.Part, 0, len(ps)) + for _, p := range ps { + in.Parts = append(in.Parts, seat.Part{Digest: p.Digest, Size: p.Size}) + } + raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublish, in) + if err != nil { + return parts.Opened{}, fmt.Errorf("%s's publish of %s: %w", seat.Seat, in.Name, err) + } + var a seat.PublishAnswer + if err := json.Unmarshal(raw, &a); err != nil { + return parts.Opened{}, fmt.Errorf("%s's answer to publish: %w", seat.Seat, err) + } + return parts.Opened{Held: a.Held, Lacks: a.Lacks}, nil + } + chunk := func(_ context.Context, part string, sequence int, last bool, data []byte) error { + raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublishPart, seat.PublishPartInput{Transfer: in.Transfer, Part: part, + Sequence: int64(sequence), Last: last, Data: base64.StdEncoding.EncodeToString(data)}) + if err != nil { + return fmt.Errorf("%s's publish-part: %w", seat.Seat, err) + } + var a seat.PublishPartAnswer + if err := json.Unmarshal(raw, &a); err != nil { + return fmt.Errorf("%s's answer to publish-part: %w", seat.Seat, err) + } + return nil + } + sent, err := parts.Send(ctx, open, chunk, ps) + return Sent{Transfer: in.Transfer, Sent: sent}, err +} diff --git a/contracts/go/clients/npmpackageregistryclient/client_test.go b/contracts/go/clients/npmpackageregistryclient/client_test.go new file mode 100644 index 00000000..051d8d47 --- /dev/null +++ b/contracts/go/clients/npmpackageregistryclient/client_test.go @@ -0,0 +1,57 @@ +package npmpackageregistryclient + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "io" + "testing" + + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry" + "git.novox.be/novox/mesh-sdk/go/parts" +) + +// The client publishes over its own seat's verbs alone (ADR 0627 §1): publish names every part, and only the part the +// holder lacks travels by publish-part, its bytes whole, the last chunk marked. +func TestPublishTalksToItsOwnSeat(t *testing.T) { + held, lacked := []byte("held"), []byte("lacked") + part := func(b []byte) parts.Part { + sum := sha256.Sum256(b) + return parts.Part{Digest: "sha256:" + hex.EncodeToString(sum[:]), Size: int64(len(b)), + Open: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }} + } + ps := []parts.Part{part(held), part(lacked)} + var asked []string + var got []byte + call := func(key string, body any) (json.RawMessage, error) { + asked = append(asked, key) + raw, _ := json.Marshal(body) + switch key { + case "seat:npm-package-registry.publish": + var in seat.PublishInput + _ = json.Unmarshal(raw, &in) + if len(in.Parts) != 2 { + t.Errorf("publish names %d parts, want 2", len(in.Parts)) + } + return json.Marshal(seat.PublishAnswer{Transfer: in.Transfer, Lacks: []string{ps[1].Digest}}) + case "seat:npm-package-registry.publish-part": + var in seat.PublishPartInput + _ = json.Unmarshal(raw, &in) + data, _ := base64.StdEncoding.DecodeString(in.Data) + if in.Part != ps[1].Digest || !in.Last || in.Transfer != "t1" { + t.Errorf("publish-part %+v", in) + } + got = append(got, data...) + return json.Marshal(seat.PublishPartAnswer{Transfer: in.Transfer, Part: in.Part}) + } + t.Fatalf("asked %s", key) + return nil, nil + } + sent, err := Publish(context.Background(), call, seat.PublishInput{Transfer: "t1", Name: "x"}, ps) + if err != nil || sent.Transfer != "t1" || sent.Chunks != 1 || len(sent.Parts) != 1 || !bytes.Equal(got, lacked) || len(asked) != 2 { + t.Errorf("sent %+v, %q arrived, asked %v, %v", sent, got, asked, err) + } +} diff --git a/contracts/go/clients/npmpackageregistryclient/go.mod b/contracts/go/clients/npmpackageregistryclient/go.mod new file mode 100644 index 00000000..4ff30866 --- /dev/null +++ b/contracts/go/clients/npmpackageregistryclient/go.mod @@ -0,0 +1,17 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/clients/npmpackageregistryclient + +go 1.26.0 + +require ( + git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef +) + +require ( + github.com/klauspost/compress v1.20.1 // indirect + github.com/nats-io/nats.go v1.54.0 // indirect + github.com/nats-io/nkeys v0.4.16 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + golang.org/x/crypto v0.57.0 // indirect + golang.org/x/sys v0.48.0 // indirect +) diff --git a/contracts/go/clients/npmpackageregistryclient/go.sum b/contracts/go/clients/npmpackageregistryclient/go.sum new file mode 100644 index 00000000..1858987c --- /dev/null +++ b/contracts/go/clients/npmpackageregistryclient/go.sum @@ -0,0 +1,18 @@ +git.novox.be/novox/mesh-contracts/go v0.0.0-20261104172218-347a3053580a h1:E9ERPqR4uZXypudHGxv2/wNIOYcder9torVqJwJq/DI= +git.novox.be/novox/mesh-contracts/go v0.0.0-20261104172218-347a3053580a/go.mod h1:0dD1XEJBJ0G5UYDQxSZI84nuujb6m3tOpJl4C8rQVvg= +git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0-20261105000000-000000000000 h1:JdEqNAv+s0ut6JEcCtXN5RIoGpuYg0jIVteFZSJJ36s= +git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0-20261105000000-000000000000/go.mod h1:JRtTjIjMTkzn59IN4vCYei9ZwJGFnN1Puwame4V/sD0= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef h1:/Jic5ho0xz32Z9DH2NSUqVxCF1KyfIOWuRTrX1aLjus= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef/go.mod h1:2wHG7ntLCG8Tpm/SCfgp524a5emUJm1gAC4KrRYsU9s= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA= +github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k= +github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= +github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= diff --git a/contracts/go/clients/ociartifactregistryclient/client.go b/contracts/go/clients/ociartifactregistryclient/client.go new file mode 100644 index 00000000..a6831a2e --- /dev/null +++ b/contracts/go/clients/ociartifactregistryclient/client.go @@ -0,0 +1,63 @@ +// Package ociartifactregistryclient is the oci-artifact-registry seat's client (novox/hq ADR 0627 §1, §2; ADR 0611, design 51 +// *Publishing*): it publishes a file in parts to the seat's holder. `publish` opens the transfer naming every part, +// the holder answers the parts it lacks, and only those travel by `publish-part`, in chunks a bus message carries; a +// cut transfer goes on from what the holder answers when the same transfer is opened again. The sending in parts is +// mesh-sdk's seat-neutral runtime (parts); this client binds it to this seat's verbs and types alone. The call is the +// caller's own (a bundle's stdio.Ask, a process's call of a seat verb), so it holds no transport. +package ociartifactregistryclient + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry" + "git.novox.be/novox/mesh-sdk/go/parts" +) + +// Call asks one verb of the seat, `seat:.`, with a body, and answers its JSON. +type Call func(key string, body any) (json.RawMessage, error) + +// Sent is what a Publish did: the transfer, whether the holder held its digest already, the parts it lacked and were +// sent, and the chunks they took. +type Sent struct { + Transfer string + parts.Sent +} + +// Publish opens (or opens again) the transfer in on the seat and sends the parts the holder answers it lacks, each in +// chunks of at most parts.MaxChunk, in sequence, the last marked. in.Parts is written from parts. A part whose bytes +// do not hash to its digest is refused before anything of it is sent; a part the holder does not name is never read. +// Its outcome, published or refused, is the holder's event, which the caller waits for. +func Publish(ctx context.Context, call Call, in seat.PublishInput, ps []parts.Part) (Sent, error) { + open := func(_ context.Context, ps []parts.Part) (parts.Opened, error) { + in.Parts = make([]seat.Part, 0, len(ps)) + for _, p := range ps { + in.Parts = append(in.Parts, seat.Part{Digest: p.Digest, Size: p.Size}) + } + raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublish, in) + if err != nil { + return parts.Opened{}, fmt.Errorf("%s's publish of %s: %w", seat.Seat, in.Name, err) + } + var a seat.PublishAnswer + if err := json.Unmarshal(raw, &a); err != nil { + return parts.Opened{}, fmt.Errorf("%s's answer to publish: %w", seat.Seat, err) + } + return parts.Opened{Held: a.Held, Lacks: a.Lacks}, nil + } + chunk := func(_ context.Context, part string, sequence int, last bool, data []byte) error { + raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublishPart, seat.PublishPartInput{Transfer: in.Transfer, Part: part, + Sequence: int64(sequence), Last: last, Data: base64.StdEncoding.EncodeToString(data)}) + if err != nil { + return fmt.Errorf("%s's publish-part: %w", seat.Seat, err) + } + var a seat.PublishPartAnswer + if err := json.Unmarshal(raw, &a); err != nil { + return fmt.Errorf("%s's answer to publish-part: %w", seat.Seat, err) + } + return nil + } + sent, err := parts.Send(ctx, open, chunk, ps) + return Sent{Transfer: in.Transfer, Sent: sent}, err +} diff --git a/contracts/go/clients/ociartifactregistryclient/client_test.go b/contracts/go/clients/ociartifactregistryclient/client_test.go new file mode 100644 index 00000000..18129f0d --- /dev/null +++ b/contracts/go/clients/ociartifactregistryclient/client_test.go @@ -0,0 +1,57 @@ +package ociartifactregistryclient + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "io" + "testing" + + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry" + "git.novox.be/novox/mesh-sdk/go/parts" +) + +// The client publishes over its own seat's verbs alone (ADR 0627 §1): publish names every part, and only the part the +// holder lacks travels by publish-part, its bytes whole, the last chunk marked. +func TestPublishTalksToItsOwnSeat(t *testing.T) { + held, lacked := []byte("held"), []byte("lacked") + part := func(b []byte) parts.Part { + sum := sha256.Sum256(b) + return parts.Part{Digest: "sha256:" + hex.EncodeToString(sum[:]), Size: int64(len(b)), + Open: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }} + } + ps := []parts.Part{part(held), part(lacked)} + var asked []string + var got []byte + call := func(key string, body any) (json.RawMessage, error) { + asked = append(asked, key) + raw, _ := json.Marshal(body) + switch key { + case "seat:oci-artifact-registry.publish": + var in seat.PublishInput + _ = json.Unmarshal(raw, &in) + if len(in.Parts) != 2 { + t.Errorf("publish names %d parts, want 2", len(in.Parts)) + } + return json.Marshal(seat.PublishAnswer{Transfer: in.Transfer, Lacks: []string{ps[1].Digest}}) + case "seat:oci-artifact-registry.publish-part": + var in seat.PublishPartInput + _ = json.Unmarshal(raw, &in) + data, _ := base64.StdEncoding.DecodeString(in.Data) + if in.Part != ps[1].Digest || !in.Last || in.Transfer != "t1" { + t.Errorf("publish-part %+v", in) + } + got = append(got, data...) + return json.Marshal(seat.PublishPartAnswer{Transfer: in.Transfer, Part: in.Part}) + } + t.Fatalf("asked %s", key) + return nil, nil + } + sent, err := Publish(context.Background(), call, seat.PublishInput{Transfer: "t1", Name: "x"}, ps) + if err != nil || sent.Transfer != "t1" || sent.Chunks != 1 || len(sent.Parts) != 1 || !bytes.Equal(got, lacked) || len(asked) != 2 { + t.Errorf("sent %+v, %q arrived, asked %v, %v", sent, got, asked, err) + } +} diff --git a/contracts/go/clients/ociartifactregistryclient/go.mod b/contracts/go/clients/ociartifactregistryclient/go.mod new file mode 100644 index 00000000..dc07b42f --- /dev/null +++ b/contracts/go/clients/ociartifactregistryclient/go.mod @@ -0,0 +1,17 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/clients/ociartifactregistryclient + +go 1.26.0 + +require ( + git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef +) + +require ( + github.com/klauspost/compress v1.20.1 // indirect + github.com/nats-io/nats.go v1.54.0 // indirect + github.com/nats-io/nkeys v0.4.16 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + golang.org/x/crypto v0.57.0 // indirect + golang.org/x/sys v0.48.0 // indirect +) diff --git a/contracts/go/clients/ociartifactregistryclient/go.sum b/contracts/go/clients/ociartifactregistryclient/go.sum new file mode 100644 index 00000000..c9a0ca56 --- /dev/null +++ b/contracts/go/clients/ociartifactregistryclient/go.sum @@ -0,0 +1,18 @@ +git.novox.be/novox/mesh-contracts/go v0.0.0-20261104172218-347a3053580a h1:E9ERPqR4uZXypudHGxv2/wNIOYcder9torVqJwJq/DI= +git.novox.be/novox/mesh-contracts/go v0.0.0-20261104172218-347a3053580a/go.mod h1:0dD1XEJBJ0G5UYDQxSZI84nuujb6m3tOpJl4C8rQVvg= +git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-20261105000000-000000000000 h1:fhQ+q40fnPwuBBU+7Bpv13ktzoPxHtQiO6mpHR9oVoo= +git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-20261105000000-000000000000/go.mod h1:ooQa2CiP8JOvhP0bdQvwurV5wskRE7Cm68ecURBbCwU= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef h1:/Jic5ho0xz32Z9DH2NSUqVxCF1KyfIOWuRTrX1aLjus= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef/go.mod h1:2wHG7ntLCG8Tpm/SCfgp524a5emUJm1gAC4KrRYsU9s= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA= +github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k= +github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= +github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= diff --git a/contracts/go/seats/containerimageregistry/contract.go b/contracts/go/seats/containerimageregistry/contract.go new file mode 100644 index 00000000..6eb1f99e --- /dev/null +++ b/contracts/go/seats/containerimageregistry/contract.go @@ -0,0 +1,403 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// Package containerimageregistry is the contract of the seat "container-image-registry", defined whole (seats/container-image-registry/seat.json): 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. +package containerimageregistry + +// Seat is the seat's name. +const Seat = "container-image-registry" + +const ( + VerbPublish = "publish" + VerbPublishPart = "publish-part" + VerbVersions = "versions" + VerbRemove = "remove" + VerbMirror = "mirror" + VerbTags = "tags" + VerbHold = "hold" + VerbRelease = "release" + VerbList = "list" +) + +const ( + EventPublished = "published" + EventRefused = "refused" + EventRemoved = "removed" + EventKept = "kept" + EventHeld = "held" + EventReleased = "released" +) + +// 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, VerbList} +} + +// Events is every event published on the seat's subject: a new copy each call. +func Events() []string { + return []string{EventPublished, EventRefused, EventRemoved, EventKept, EventHeld, EventReleased} +} + +// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. +const ( + Family = "container-image-registry.events" + FamilyMaxAgeDays = 7 +) + +// SubjectPublished is where published is published. +const SubjectPublished = "mesh.seat.container-image-registry.event.published" + +// SubjectRefused is where refused is published. +const SubjectRefused = "mesh.seat.container-image-registry.event.refused" + +// SubjectRemoved is where removed is published. +const SubjectRemoved = "mesh.seat.container-image-registry.event.removed" + +// SubjectKept is where kept is published. +const SubjectKept = "mesh.seat.container-image-registry.event.kept" + +// SubjectHeld is where held is published. +const SubjectHeld = "mesh.seat.container-image-registry.event.held" + +// SubjectReleased is where released is published. +const SubjectReleased = "mesh.seat.container-image-registry.event.released" + +// Artifact is one published thing, as every registry event and answer names it. +type Artifact struct { + // the source commit it was built from + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // what it is + Kind string `json:"kind"` + // 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) + Name string `json:"name"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of Artifact's kind. +const ( + ArtifactKindPackage = "package" + ArtifactKindImage = "image" + ArtifactKindBundle = "bundle" + ArtifactKindArchive = "archive" + ArtifactKindUpstream = "upstream" +) + +// Hold is the body of held and released. +type Hold struct { + // when, RFC 3339 + At string `json:"at,omitempty"` + // the caller the holder proved + By string `json:"by,omitempty"` + // the digest + Digest string `json:"digest"` + // the module holding the seat + Holder string `json:"holder"` + // the name + Name string `json:"name"` + // why + 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 + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // the module holding the seat that said it + Holder string `json:"holder"` + // what it is + Kind string `json:"kind"` + // the Go module path a Go package is fetched under, equal to name for a Go package (go-package-registry's published); empty for every other kind of artifact + Module string `json:"module,omitempty"` + // 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) + Name string `json:"name"` + // why: a refusal's cause, a removal's (retention, tag-gone, by-hand), or why a removal was kept (shared-digest) + Reason string `json:"reason,omitempty"` + // the reference the seat answers: //@sha256:, or @ for a package (ADR 0605 §9) + Reference string `json:"reference,omitempty"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // for kept: the other tags still naming its digest + Tags []string `json:"tags,omitempty"` + // the transfer it ends, when it ended one + Transfer string `json:"transfer,omitempty"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of Outcome's kind. +const ( + OutcomeKindPackage = "package" + OutcomeKindImage = "image" + OutcomeKindBundle = "bundle" + OutcomeKindArchive = "archive" + OutcomeKindUpstream = "upstream" +) + +// Part is one part of a transfer. +type Part struct { + // the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob + Digest string `json:"digest"` + // its size in bytes + 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 + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // what it is + Kind string `json:"kind"` + // 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) + Name string `json:"name"` + // every part's digest and size; none when the digest is already held + Parts []Part `json:"parts,omitempty"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // the transfer's name, the caller's own; the same name opens the same transfer again + Transfer string `json:"transfer"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of PublishInput's kind. +const ( + PublishInputKindPackage = "package" + PublishInputKindImage = "image" + PublishInputKindBundle = "bundle" + PublishInputKindArchive = "archive" + PublishInputKindUpstream = "upstream" +) + +// PublishAnswer is what publish answers. +type PublishAnswer struct { + // the digest is held already: nothing to send; the outcome follows as an event + Held bool `json:"held,omitempty"` + // the digests of the parts the holder lacks, to send by publish-part + Lacks []string `json:"lacks,omitempty"` + // the transfer + Transfer string `json:"transfer"` +} + +// PublishPartInput is what publish-part takes. +type PublishPartInput struct { + // the chunk's bytes, base64 (at most 760 KiB before encoding) + Data string `json:"data"` + // this chunk is the part's last + Last bool `json:"last"` + // the part's digest + Part string `json:"part"` + // the chunk's place in its part, from 0 + Sequence int64 `json:"sequence"` + // the transfer + Transfer string `json:"transfer"` +} + +// PublishPartAnswer is what publish-part answers. +type PublishPartAnswer struct { + // the part is whole and its bytes match its digest + Complete bool `json:"complete,omitempty"` + // the part + Part string `json:"part"` + // the chunks of the part held so far + Received int64 `json:"received,omitempty"` + // the transfer + Transfer string `json:"transfer"` +} + +// VersionsInput is what versions takes. +type VersionsInput struct { + // narrowed to one kind (optional) + Kind string `json:"kind,omitempty"` + // the name + Name string `json:"name"` +} + +// VersionsAnswer is what versions answers. +type VersionsAnswer struct { + // the name + Name string `json:"name"` + // newest first + Versions []Artifact `json:"versions"` +} + +// RemoveInput is what remove takes. +type RemoveInput struct { + // the digest (for a bundle, archive or copied base) + Digest string `json:"digest,omitempty"` + // the name + Name string `json:"name"` + // why it goes + Reason string `json:"reason"` + // the tag (for a tagged version) + Tag string `json:"tag,omitempty"` + // the version + Version string `json:"version,omitempty"` +} + +// The values of RemoveInput's reason. +const ( + RemoveInputReasonTagGone = "tag-gone" + RemoveInputReasonRetention = "retention" +) + +// RemoveAnswer is what remove answers. +type RemoveAnswer struct { + Artifact *Artifact `json:"artifact,omitempty"` + // it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags) + Kept bool `json:"kept,omitempty"` + // why it was kept: shared-digest, another tag names its digest + Reason string `json:"reason,omitempty"` + // it is gone + Removed bool `json:"removed"` + // the other tags still naming its digest, when kept + Tags []string `json:"tags,omitempty"` +} + +// The values of RemoveAnswer's reason. +const ( + RemoveAnswerReasonSharedDigest = "shared-digest" +) + +// MirrorInput is what mirror takes. +type MirrorInput struct { + // its pinned digest, sha256: + Digest string `json:"digest"` + // the name it is kept under + Name string `json:"name"` + // the outside image's reference + Source string `json:"source"` +} + +// MirrorAnswer is what mirror answers. +type MirrorAnswer struct { + // the digest stored + Digest string `json:"digest"` + // the reference the seat answers + Reference string `json:"reference"` +} + +// TagsInput is what tags takes. +type TagsInput struct { + // the image + Name string `json:"name"` +} + +// TagsAnswerTagsItem is a type of the contract. +type TagsAnswerTagsItem struct { + // its digest + Digest string `json:"digest"` + // the tag + Tag string `json:"tag"` +} + +// TagsAnswer is what tags answers. +type TagsAnswer struct { + // the image + Name string `json:"name"` + // every tag + Tags []TagsAnswerTagsItem `json:"tags"` +} + +// HoldInput is what hold takes. +type HoldInput struct { + // the digest + Digest string `json:"digest"` + // the name + Name string `json:"name"` + // which of the sweep's rules keeps it + Reason string `json:"reason"` +} + +// HoldAnswer is what hold answers. +type HoldAnswer struct { + // it is held + Held bool `json:"held"` + // when the hold began, RFC 3339 + Since string `json:"since,omitempty"` +} + +// ReleaseInput is what release takes. +type ReleaseInput struct { + // the digest + Digest string `json:"digest"` + // the name + Name string `json:"name"` + // why it is let go + Reason string `json:"reason"` +} + +// ReleaseAnswer is what release answers. +type ReleaseAnswer struct { + // a hold was let go; false when there was none + 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 + +// RefusedEvent is the body of the event refused. +type RefusedEvent = Outcome + +// RemovedEvent is the body of the event removed. +type RemovedEvent = Outcome + +// KeptEvent is the body of the event kept. +type KeptEvent = Outcome + +// HeldEvent is the body of the event held. +type HeldEvent = Hold + +// ReleasedEvent is the body of the event released. +type ReleasedEvent = Hold diff --git a/contracts/go/seats/containerimageregistry/contract_test.go b/contracts/go/seats/containerimageregistry/contract_test.go new file mode 100644 index 00000000..8f4d543e --- /dev/null +++ b/contracts/go/seats/containerimageregistry/contract_test.go @@ -0,0 +1,74 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +package containerimageregistry + +import ( + "encoding/json" + "reflect" + "regexp" + "testing" +) + +// TestFixtures reads every fixture of seats/container-image-registry/fixtures: each decodes into its type, a field the type does not +// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's. +func TestFixtures(t *testing.T) { + cases := []struct { + name, subject, given, wire string + decode func([]byte) (any, error) + subjectIs *regexp.Regexp + }{ + {name: "published on the seat's subject", subject: "mesh.seat.container-image-registry.event.published", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"reference\":\"container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"reference\":\"container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.container-image-registry\\.event\\.published$")}, + {name: "refused on the seat's subject", subject: "mesh.seat.container-image-registry.event.refused", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"reason\":\"retention\",\"reference\":\"container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"reason\":\"retention\",\"reference\":\"container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v RefusedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.container-image-registry\\.event\\.refused$")}, + {name: "removed on the seat's subject", subject: "mesh.seat.container-image-registry.event.removed", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"reason\":\"retention\",\"reference\":\"container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"reason\":\"retention\",\"reference\":\"container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v RemovedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.container-image-registry\\.event\\.removed$")}, + {name: "kept on the seat's subject: a tagged version sharing its digest stays, naming the tags on it", subject: "mesh.seat.container-image-registry.event.kept", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"reason\":\"shared-digest\",\"reference\":\"container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"tag\":\"0.1.19-feat-x.2\",\"tags\":[\"0.1.19\"],\"version\":\"0.1.19-feat-x.2\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"reason\":\"shared-digest\",\"reference\":\"container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"tag\":\"0.1.19-feat-x.2\",\"tags\":[\"0.1.19\"],\"version\":\"0.1.19-feat-x.2\"}", + decode: func(b []byte) (any, error) { var v KeptEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.container-image-registry\\.event\\.kept$")}, + {name: "held on the seat's subject", subject: "mesh.seat.container-image-registry.event.held", given: "{\"at\":\"2026-11-04T12:00:00Z\",\"by\":\"mesh-artifact-sweep\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"holder\":\"distribution\",\"name\":\"mesh-sdk\",\"reason\":\"kept by the sweep's rule\"}", wire: "{\"at\":\"2026-11-04T12:00:00Z\",\"by\":\"mesh-artifact-sweep\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"holder\":\"distribution\",\"name\":\"mesh-sdk\",\"reason\":\"kept by the sweep's rule\"}", + decode: func(b []byte) (any, error) { var v HeldEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.container-image-registry\\.event\\.held$")}, + {name: "released on the seat's subject", subject: "mesh.seat.container-image-registry.event.released", given: "{\"at\":\"2026-11-04T12:00:00Z\",\"by\":\"mesh-artifact-sweep\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"holder\":\"distribution\",\"name\":\"mesh-sdk\",\"reason\":\"kept by the sweep's rule\"}", wire: "{\"at\":\"2026-11-04T12:00:00Z\",\"by\":\"mesh-artifact-sweep\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"holder\":\"distribution\",\"name\":\"mesh-sdk\",\"reason\":\"kept by the sweep's rule\"}", + decode: func(b []byte) (any, error) { var v ReleasedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.container-image-registry\\.event\\.released$")}, + {name: "publish opens a transfer", subject: "", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a field the reader does not know is dropped", subject: "", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"image\",\"later\":{\"x\":1},\"name\":\"mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"image\",\"name\":\"mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "publish answers what it lacks", subject: "", given: "{\"lacks\":[\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"transfer\":\"t1\"}", wire: "{\"lacks\":[\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"transfer\":\"t1\"}", + decode: func(b []byte) (any, error) { var v PublishAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a chunk of a part", subject: "", given: "{\"data\":\"aGVsbG8=\",\"last\":true,\"part\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"sequence\":0,\"transfer\":\"t1\"}", wire: "{\"data\":\"aGVsbG8=\",\"last\":true,\"part\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"sequence\":0,\"transfer\":\"t1\"}", + decode: func(b []byte) (any, error) { var v PublishPartInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "the versions held", subject: "", given: "{\"name\":\"mesh-sdk\",\"versions\":[{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"mesh-sdk\",\"version\":\"0.1.19\"}]}", wire: "{\"name\":\"mesh-sdk\",\"versions\":[{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"mesh-sdk\",\"version\":\"0.1.19\"}]}", + decode: func(b []byte) (any, error) { var v VersionsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a release of what is not held answers released false", subject: "", given: "{\"released\":false}", wire: "{\"released\":false}", + 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)) + if err != nil { + t.Errorf("%s: does not decode: %v", c.name, err) + continue + } + back, err := json.Marshal(v) + if err != nil { + t.Errorf("%s: does not encode: %v", c.name, err) + continue + } + var got, want any + if err := json.Unmarshal(back, &got); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal([]byte(c.wire), &want); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire) + } + if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) { + t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs) + } + } +} diff --git a/contracts/go/seats/containerimageregistry/go.mod b/contracts/go/seats/containerimageregistry/go.mod new file mode 100644 index 00000000..26ccf02f --- /dev/null +++ b/contracts/go/seats/containerimageregistry/go.mod @@ -0,0 +1,3 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry + +go 1.26.0 diff --git a/contracts/go/seats/gopackageregistry/contract.go b/contracts/go/seats/gopackageregistry/contract.go new file mode 100644 index 00000000..eb534618 --- /dev/null +++ b/contracts/go/seats/gopackageregistry/contract.go @@ -0,0 +1,262 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// Package gopackageregistry is the contract of the seat "go-package-registry", defined whole (seats/go-package-registry/seat.json): The mesh's Go modules: Go's module proxy protocol's read side, and publishing over the bus. +package gopackageregistry + +// Seat is the seat's name. +const Seat = "go-package-registry" + +const ( + VerbPublish = "publish" + VerbPublishPart = "publish-part" + VerbVersions = "versions" + VerbRemove = "remove" +) + +const ( + EventPublished = "published" + EventRefused = "refused" + EventRemoved = "removed" + EventKept = "kept" +) + +// 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} +} + +// Events is every event published on the seat's subject: a new copy each call. +func Events() []string { + return []string{EventPublished, EventRefused, EventRemoved, EventKept} +} + +// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. +const ( + Family = "go-package-registry.events" + FamilyMaxAgeDays = 7 +) + +// SubjectPublished is where published is published. +const SubjectPublished = "mesh.seat.go-package-registry.event.published" + +// SubjectRefused is where refused is published. +const SubjectRefused = "mesh.seat.go-package-registry.event.refused" + +// SubjectRemoved is where removed is published. +const SubjectRemoved = "mesh.seat.go-package-registry.event.removed" + +// SubjectKept is where kept is published. +const SubjectKept = "mesh.seat.go-package-registry.event.kept" + +// Artifact is one published thing, as every registry event and answer names it. +type Artifact struct { + // the source commit it was built from + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // what it is + Kind string `json:"kind"` + // 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) + Name string `json:"name"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of Artifact's kind. +const ( + ArtifactKindPackage = "package" + ArtifactKindImage = "image" + ArtifactKindBundle = "bundle" + ArtifactKindArchive = "archive" + ArtifactKindUpstream = "upstream" +) + +// Outcome is the body of published, refused and removed. +type Outcome struct { + // the source commit it was built from + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // the module holding the seat that said it + Holder string `json:"holder"` + // what it is + Kind string `json:"kind"` + // the Go module path a Go package is fetched under, equal to name for a Go package (go-package-registry's published); empty for every other kind of artifact + Module string `json:"module,omitempty"` + // 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) + Name string `json:"name"` + // why: a refusal's cause, a removal's (retention, tag-gone, by-hand), or why a removal was kept (shared-digest) + Reason string `json:"reason,omitempty"` + // the reference the seat answers: //@sha256:, or @ for a package (ADR 0605 §9) + Reference string `json:"reference,omitempty"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // for kept: the other tags still naming its digest + Tags []string `json:"tags,omitempty"` + // the transfer it ends, when it ended one + Transfer string `json:"transfer,omitempty"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of Outcome's kind. +const ( + OutcomeKindPackage = "package" + OutcomeKindImage = "image" + OutcomeKindBundle = "bundle" + OutcomeKindArchive = "archive" + OutcomeKindUpstream = "upstream" +) + +// Part is one part of a transfer. +type Part struct { + // the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob + Digest string `json:"digest"` + // its size in bytes + Size int64 `json:"size"` +} + +// PublishInput is what publish takes. +type PublishInput struct { + // the source commit it was built from + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // what it is + Kind string `json:"kind"` + // 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) + Name string `json:"name"` + // every part's digest and size; none when the digest is already held + Parts []Part `json:"parts,omitempty"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // the transfer's name, the caller's own; the same name opens the same transfer again + Transfer string `json:"transfer"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of PublishInput's kind. +const ( + PublishInputKindPackage = "package" + PublishInputKindImage = "image" + PublishInputKindBundle = "bundle" + PublishInputKindArchive = "archive" + PublishInputKindUpstream = "upstream" +) + +// PublishAnswer is what publish answers. +type PublishAnswer struct { + // the digest is held already: nothing to send; the outcome follows as an event + Held bool `json:"held,omitempty"` + // the digests of the parts the holder lacks, to send by publish-part + Lacks []string `json:"lacks,omitempty"` + // the transfer + Transfer string `json:"transfer"` +} + +// PublishPartInput is what publish-part takes. +type PublishPartInput struct { + // the chunk's bytes, base64 (at most 760 KiB before encoding) + Data string `json:"data"` + // this chunk is the part's last + Last bool `json:"last"` + // the part's digest + Part string `json:"part"` + // the chunk's place in its part, from 0 + Sequence int64 `json:"sequence"` + // the transfer + Transfer string `json:"transfer"` +} + +// PublishPartAnswer is what publish-part answers. +type PublishPartAnswer struct { + // the part is whole and its bytes match its digest + Complete bool `json:"complete,omitempty"` + // the part + Part string `json:"part"` + // the chunks of the part held so far + Received int64 `json:"received,omitempty"` + // the transfer + Transfer string `json:"transfer"` +} + +// VersionsInput is what versions takes. +type VersionsInput struct { + // narrowed to one kind (optional) + Kind string `json:"kind,omitempty"` + // the name + Name string `json:"name"` +} + +// VersionsAnswer is what versions answers. +type VersionsAnswer struct { + // the name + Name string `json:"name"` + // newest first + Versions []Artifact `json:"versions"` +} + +// RemoveInput is what remove takes. +type RemoveInput struct { + // the digest (for a bundle, archive or copied base) + Digest string `json:"digest,omitempty"` + // the name + Name string `json:"name"` + // why it goes + Reason string `json:"reason"` + // the tag (for a tagged version) + Tag string `json:"tag,omitempty"` + // the version + Version string `json:"version,omitempty"` +} + +// The values of RemoveInput's reason. +const ( + RemoveInputReasonTagGone = "tag-gone" + RemoveInputReasonRetention = "retention" +) + +// RemoveAnswer is what remove answers. +type RemoveAnswer struct { + Artifact *Artifact `json:"artifact,omitempty"` + // it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags) + Kept bool `json:"kept,omitempty"` + // why it was kept: shared-digest, another tag names its digest + Reason string `json:"reason,omitempty"` + // it is gone + Removed bool `json:"removed"` + // the other tags still naming its digest, when kept + Tags []string `json:"tags,omitempty"` +} + +// The values of RemoveAnswer's reason. +const ( + RemoveAnswerReasonSharedDigest = "shared-digest" +) + +// PublishedEvent is the body of the event published. +type PublishedEvent = Outcome + +// RefusedEvent is the body of the event refused. +type RefusedEvent = Outcome + +// RemovedEvent is the body of the event removed. +type RemovedEvent = Outcome + +// KeptEvent is the body of the event kept. +type KeptEvent = Outcome + +// GoPackageRegistryForm is the form of the provision go-package-registry. +type GoPackageRegistryForm struct { + // the read side's address + URL string `json:"url"` +} diff --git a/contracts/go/seats/gopackageregistry/contract_test.go b/contracts/go/seats/gopackageregistry/contract_test.go new file mode 100644 index 00000000..e5f69ec1 --- /dev/null +++ b/contracts/go/seats/gopackageregistry/contract_test.go @@ -0,0 +1,66 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +package gopackageregistry + +import ( + "encoding/json" + "reflect" + "regexp" + "testing" +) + +// TestFixtures reads every fixture of seats/go-package-registry/fixtures: each decodes into its type, a field the type does not +// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's. +func TestFixtures(t *testing.T) { + cases := []struct { + name, subject, given, wire string + decode func([]byte) (any, error) + subjectIs *regexp.Regexp + }{ + {name: "published on the seat's subject", subject: "mesh.seat.go-package-registry.event.published", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"module\":\"git.novox.be/novox/mesh-sdk/go\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"reference\":\"git.novox.be/novox/mesh-sdk/go@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"module\":\"git.novox.be/novox/mesh-sdk/go\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"reference\":\"git.novox.be/novox/mesh-sdk/go@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.go-package-registry\\.event\\.published$")}, + {name: "refused on the seat's subject", subject: "mesh.seat.go-package-registry.event.refused", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"reason\":\"retention\",\"reference\":\"git.novox.be/novox/mesh-sdk/go@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"reason\":\"retention\",\"reference\":\"git.novox.be/novox/mesh-sdk/go@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v RefusedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.go-package-registry\\.event\\.refused$")}, + {name: "removed on the seat's subject", subject: "mesh.seat.go-package-registry.event.removed", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"reason\":\"retention\",\"reference\":\"git.novox.be/novox/mesh-sdk/go@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"reason\":\"retention\",\"reference\":\"git.novox.be/novox/mesh-sdk/go@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v RemovedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.go-package-registry\\.event\\.removed$")}, + {name: "kept on the seat's subject: a tagged version sharing its digest stays, naming the tags on it", subject: "mesh.seat.go-package-registry.event.kept", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"reason\":\"shared-digest\",\"reference\":\"git.novox.be/novox/mesh-sdk/go@0.1.19\",\"tag\":\"0.1.19-feat-x.2\",\"tags\":[\"0.1.19\"],\"version\":\"0.1.19-feat-x.2\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"reason\":\"shared-digest\",\"reference\":\"git.novox.be/novox/mesh-sdk/go@0.1.19\",\"tag\":\"0.1.19-feat-x.2\",\"tags\":[\"0.1.19\"],\"version\":\"0.1.19-feat-x.2\"}", + decode: func(b []byte) (any, error) { var v KeptEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.go-package-registry\\.event\\.kept$")}, + {name: "publish opens a transfer", subject: "", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a field the reader does not know is dropped", subject: "", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"later\":{\"x\":1},\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "publish answers what it lacks", subject: "", given: "{\"lacks\":[\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"transfer\":\"t1\"}", wire: "{\"lacks\":[\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"transfer\":\"t1\"}", + decode: func(b []byte) (any, error) { var v PublishAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a chunk of a part", subject: "", given: "{\"data\":\"aGVsbG8=\",\"last\":true,\"part\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"sequence\":0,\"transfer\":\"t1\"}", wire: "{\"data\":\"aGVsbG8=\",\"last\":true,\"part\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"sequence\":0,\"transfer\":\"t1\"}", + decode: func(b []byte) (any, error) { var v PublishPartInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "the versions held", subject: "", given: "{\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"versions\":[{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"version\":\"0.1.19\"}]}", wire: "{\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"versions\":[{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"version\":\"0.1.19\"}]}", + decode: func(b []byte) (any, error) { var v VersionsAnswer; 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}, + } + for _, c := range cases { + v, err := c.decode([]byte(c.given)) + if err != nil { + t.Errorf("%s: does not decode: %v", c.name, err) + continue + } + back, err := json.Marshal(v) + if err != nil { + t.Errorf("%s: does not encode: %v", c.name, err) + continue + } + var got, want any + if err := json.Unmarshal(back, &got); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal([]byte(c.wire), &want); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire) + } + if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) { + t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs) + } + } +} diff --git a/contracts/go/seats/gopackageregistry/go.mod b/contracts/go/seats/gopackageregistry/go.mod new file mode 100644 index 00000000..065f758b --- /dev/null +++ b/contracts/go/seats/gopackageregistry/go.mod @@ -0,0 +1,3 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry + +go 1.26.0 diff --git a/contracts/go/seats/npmpackageregistry/contract.go b/contracts/go/seats/npmpackageregistry/contract.go new file mode 100644 index 00000000..5f39cb8d --- /dev/null +++ b/contracts/go/seats/npmpackageregistry/contract.go @@ -0,0 +1,262 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// Package npmpackageregistry is the contract of the seat "npm-package-registry", defined whole (seats/npm-package-registry/seat.json): The mesh's npm packages: the npm registry protocol's read side, and publishing over the bus. +package npmpackageregistry + +// Seat is the seat's name. +const Seat = "npm-package-registry" + +const ( + VerbPublish = "publish" + VerbPublishPart = "publish-part" + VerbVersions = "versions" + VerbRemove = "remove" +) + +const ( + EventPublished = "published" + EventRefused = "refused" + EventRemoved = "removed" + EventKept = "kept" +) + +// 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} +} + +// Events is every event published on the seat's subject: a new copy each call. +func Events() []string { + return []string{EventPublished, EventRefused, EventRemoved, EventKept} +} + +// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. +const ( + Family = "npm-package-registry.events" + FamilyMaxAgeDays = 7 +) + +// SubjectPublished is where published is published. +const SubjectPublished = "mesh.seat.npm-package-registry.event.published" + +// SubjectRefused is where refused is published. +const SubjectRefused = "mesh.seat.npm-package-registry.event.refused" + +// SubjectRemoved is where removed is published. +const SubjectRemoved = "mesh.seat.npm-package-registry.event.removed" + +// SubjectKept is where kept is published. +const SubjectKept = "mesh.seat.npm-package-registry.event.kept" + +// Artifact is one published thing, as every registry event and answer names it. +type Artifact struct { + // the source commit it was built from + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // what it is + Kind string `json:"kind"` + // 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) + Name string `json:"name"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of Artifact's kind. +const ( + ArtifactKindPackage = "package" + ArtifactKindImage = "image" + ArtifactKindBundle = "bundle" + ArtifactKindArchive = "archive" + ArtifactKindUpstream = "upstream" +) + +// Outcome is the body of published, refused and removed. +type Outcome struct { + // the source commit it was built from + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // the module holding the seat that said it + Holder string `json:"holder"` + // what it is + Kind string `json:"kind"` + // the Go module path a Go package is fetched under, equal to name for a Go package (go-package-registry's published); empty for every other kind of artifact + Module string `json:"module,omitempty"` + // 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) + Name string `json:"name"` + // why: a refusal's cause, a removal's (retention, tag-gone, by-hand), or why a removal was kept (shared-digest) + Reason string `json:"reason,omitempty"` + // the reference the seat answers: //@sha256:, or @ for a package (ADR 0605 §9) + Reference string `json:"reference,omitempty"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // for kept: the other tags still naming its digest + Tags []string `json:"tags,omitempty"` + // the transfer it ends, when it ended one + Transfer string `json:"transfer,omitempty"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of Outcome's kind. +const ( + OutcomeKindPackage = "package" + OutcomeKindImage = "image" + OutcomeKindBundle = "bundle" + OutcomeKindArchive = "archive" + OutcomeKindUpstream = "upstream" +) + +// Part is one part of a transfer. +type Part struct { + // the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob + Digest string `json:"digest"` + // its size in bytes + Size int64 `json:"size"` +} + +// PublishInput is what publish takes. +type PublishInput struct { + // the source commit it was built from + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // what it is + Kind string `json:"kind"` + // 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) + Name string `json:"name"` + // every part's digest and size; none when the digest is already held + Parts []Part `json:"parts,omitempty"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // the transfer's name, the caller's own; the same name opens the same transfer again + Transfer string `json:"transfer"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of PublishInput's kind. +const ( + PublishInputKindPackage = "package" + PublishInputKindImage = "image" + PublishInputKindBundle = "bundle" + PublishInputKindArchive = "archive" + PublishInputKindUpstream = "upstream" +) + +// PublishAnswer is what publish answers. +type PublishAnswer struct { + // the digest is held already: nothing to send; the outcome follows as an event + Held bool `json:"held,omitempty"` + // the digests of the parts the holder lacks, to send by publish-part + Lacks []string `json:"lacks,omitempty"` + // the transfer + Transfer string `json:"transfer"` +} + +// PublishPartInput is what publish-part takes. +type PublishPartInput struct { + // the chunk's bytes, base64 (at most 760 KiB before encoding) + Data string `json:"data"` + // this chunk is the part's last + Last bool `json:"last"` + // the part's digest + Part string `json:"part"` + // the chunk's place in its part, from 0 + Sequence int64 `json:"sequence"` + // the transfer + Transfer string `json:"transfer"` +} + +// PublishPartAnswer is what publish-part answers. +type PublishPartAnswer struct { + // the part is whole and its bytes match its digest + Complete bool `json:"complete,omitempty"` + // the part + Part string `json:"part"` + // the chunks of the part held so far + Received int64 `json:"received,omitempty"` + // the transfer + Transfer string `json:"transfer"` +} + +// VersionsInput is what versions takes. +type VersionsInput struct { + // narrowed to one kind (optional) + Kind string `json:"kind,omitempty"` + // the name + Name string `json:"name"` +} + +// VersionsAnswer is what versions answers. +type VersionsAnswer struct { + // the name + Name string `json:"name"` + // newest first + Versions []Artifact `json:"versions"` +} + +// RemoveInput is what remove takes. +type RemoveInput struct { + // the digest (for a bundle, archive or copied base) + Digest string `json:"digest,omitempty"` + // the name + Name string `json:"name"` + // why it goes + Reason string `json:"reason"` + // the tag (for a tagged version) + Tag string `json:"tag,omitempty"` + // the version + Version string `json:"version,omitempty"` +} + +// The values of RemoveInput's reason. +const ( + RemoveInputReasonTagGone = "tag-gone" + RemoveInputReasonRetention = "retention" +) + +// RemoveAnswer is what remove answers. +type RemoveAnswer struct { + Artifact *Artifact `json:"artifact,omitempty"` + // it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags) + Kept bool `json:"kept,omitempty"` + // why it was kept: shared-digest, another tag names its digest + Reason string `json:"reason,omitempty"` + // it is gone + Removed bool `json:"removed"` + // the other tags still naming its digest, when kept + Tags []string `json:"tags,omitempty"` +} + +// The values of RemoveAnswer's reason. +const ( + RemoveAnswerReasonSharedDigest = "shared-digest" +) + +// PublishedEvent is the body of the event published. +type PublishedEvent = Outcome + +// RefusedEvent is the body of the event refused. +type RefusedEvent = Outcome + +// RemovedEvent is the body of the event removed. +type RemovedEvent = Outcome + +// KeptEvent is the body of the event kept. +type KeptEvent = Outcome + +// NpmPackageRegistryForm is the form of the provision npm-package-registry. +type NpmPackageRegistryForm struct { + // the read side's address + URL string `json:"url"` +} diff --git a/contracts/go/seats/npmpackageregistry/contract_test.go b/contracts/go/seats/npmpackageregistry/contract_test.go new file mode 100644 index 00000000..3092b370 --- /dev/null +++ b/contracts/go/seats/npmpackageregistry/contract_test.go @@ -0,0 +1,66 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +package npmpackageregistry + +import ( + "encoding/json" + "reflect" + "regexp" + "testing" +) + +// TestFixtures reads every fixture of seats/npm-package-registry/fixtures: each decodes into its type, a field the type does not +// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's. +func TestFixtures(t *testing.T) { + cases := []struct { + name, subject, given, wire string + decode func([]byte) (any, error) + subjectIs *regexp.Regexp + }{ + {name: "published on the seat's subject", subject: "mesh.seat.npm-package-registry.event.published", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"reference\":\"@novox/mesh-sdk@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"reference\":\"@novox/mesh-sdk@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.npm-package-registry\\.event\\.published$")}, + {name: "refused on the seat's subject", subject: "mesh.seat.npm-package-registry.event.refused", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"reason\":\"retention\",\"reference\":\"@novox/mesh-sdk@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"reason\":\"retention\",\"reference\":\"@novox/mesh-sdk@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v RefusedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.npm-package-registry\\.event\\.refused$")}, + {name: "removed on the seat's subject", subject: "mesh.seat.npm-package-registry.event.removed", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"reason\":\"retention\",\"reference\":\"@novox/mesh-sdk@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"reason\":\"retention\",\"reference\":\"@novox/mesh-sdk@0.1.19\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v RemovedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.npm-package-registry\\.event\\.removed$")}, + {name: "kept on the seat's subject: a tagged version sharing its digest stays, naming the tags on it", subject: "mesh.seat.npm-package-registry.event.kept", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"reason\":\"shared-digest\",\"reference\":\"@novox/mesh-sdk@0.1.19\",\"tag\":\"0.1.19-feat-x.2\",\"tags\":[\"0.1.19\"],\"version\":\"0.1.19-feat-x.2\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"gitea\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"reason\":\"shared-digest\",\"reference\":\"@novox/mesh-sdk@0.1.19\",\"tag\":\"0.1.19-feat-x.2\",\"tags\":[\"0.1.19\"],\"version\":\"0.1.19-feat-x.2\"}", + decode: func(b []byte) (any, error) { var v KeptEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.npm-package-registry\\.event\\.kept$")}, + {name: "publish opens a transfer", subject: "", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a field the reader does not know is dropped", subject: "", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"later\":{\"x\":1},\"name\":\"@novox/mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "publish answers what it lacks", subject: "", given: "{\"lacks\":[\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"transfer\":\"t1\"}", wire: "{\"lacks\":[\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"transfer\":\"t1\"}", + decode: func(b []byte) (any, error) { var v PublishAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a chunk of a part", subject: "", given: "{\"data\":\"aGVsbG8=\",\"last\":true,\"part\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"sequence\":0,\"transfer\":\"t1\"}", wire: "{\"data\":\"aGVsbG8=\",\"last\":true,\"part\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"sequence\":0,\"transfer\":\"t1\"}", + decode: func(b []byte) (any, error) { var v PublishPartInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "the versions held", subject: "", given: "{\"name\":\"@novox/mesh-sdk\",\"versions\":[{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"version\":\"0.1.19\"}]}", wire: "{\"name\":\"@novox/mesh-sdk\",\"versions\":[{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"@novox/mesh-sdk\",\"version\":\"0.1.19\"}]}", + decode: func(b []byte) (any, error) { var v VersionsAnswer; 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}, + } + for _, c := range cases { + v, err := c.decode([]byte(c.given)) + if err != nil { + t.Errorf("%s: does not decode: %v", c.name, err) + continue + } + back, err := json.Marshal(v) + if err != nil { + t.Errorf("%s: does not encode: %v", c.name, err) + continue + } + var got, want any + if err := json.Unmarshal(back, &got); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal([]byte(c.wire), &want); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire) + } + if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) { + t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs) + } + } +} diff --git a/contracts/go/seats/npmpackageregistry/go.mod b/contracts/go/seats/npmpackageregistry/go.mod new file mode 100644 index 00000000..acddbbfb --- /dev/null +++ b/contracts/go/seats/npmpackageregistry/go.mod @@ -0,0 +1,3 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry + +go 1.26.0 diff --git a/contracts/go/seats/ociartifactregistry/contract.go b/contracts/go/seats/ociartifactregistry/contract.go new file mode 100644 index 00000000..edade5c2 --- /dev/null +++ b/contracts/go/seats/ociartifactregistry/contract.go @@ -0,0 +1,361 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// Package ociartifactregistry is the contract of the seat "oci-artifact-registry", defined whole (seats/oci-artifact-registry/seat.json): The mesh's bundles and archives, by digest, in the one artifact store: the OCI distribution protocol's read side. +package ociartifactregistry + +// Seat is the seat's name. +const Seat = "oci-artifact-registry" + +const ( + VerbPublish = "publish" + VerbPublishPart = "publish-part" + VerbVersions = "versions" + VerbRemove = "remove" + VerbHold = "hold" + VerbRelease = "release" + VerbList = "list" +) + +const ( + EventPublished = "published" + EventRefused = "refused" + EventRemoved = "removed" + EventKept = "kept" + EventHeld = "held" + EventReleased = "released" +) + +// 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, VerbList} +} + +// Events is every event published on the seat's subject: a new copy each call. +func Events() []string { + return []string{EventPublished, EventRefused, EventRemoved, EventKept, EventHeld, EventReleased} +} + +// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. +const ( + Family = "oci-artifact-registry.events" + FamilyMaxAgeDays = 7 +) + +// SubjectPublished is where published is published. +const SubjectPublished = "mesh.seat.oci-artifact-registry.event.published" + +// SubjectRefused is where refused is published. +const SubjectRefused = "mesh.seat.oci-artifact-registry.event.refused" + +// SubjectRemoved is where removed is published. +const SubjectRemoved = "mesh.seat.oci-artifact-registry.event.removed" + +// SubjectKept is where kept is published. +const SubjectKept = "mesh.seat.oci-artifact-registry.event.kept" + +// SubjectHeld is where held is published. +const SubjectHeld = "mesh.seat.oci-artifact-registry.event.held" + +// SubjectReleased is where released is published. +const SubjectReleased = "mesh.seat.oci-artifact-registry.event.released" + +// Artifact is one published thing, as every registry event and answer names it. +type Artifact struct { + // the source commit it was built from + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // what it is + Kind string `json:"kind"` + // 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) + Name string `json:"name"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of Artifact's kind. +const ( + ArtifactKindPackage = "package" + ArtifactKindImage = "image" + ArtifactKindBundle = "bundle" + ArtifactKindArchive = "archive" + ArtifactKindUpstream = "upstream" +) + +// Hold is the body of held and released. +type Hold struct { + // when, RFC 3339 + At string `json:"at,omitempty"` + // the caller the holder proved + By string `json:"by,omitempty"` + // the digest + Digest string `json:"digest"` + // the module holding the seat + Holder string `json:"holder"` + // the name + Name string `json:"name"` + // why + 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 + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // the module holding the seat that said it + Holder string `json:"holder"` + // what it is + Kind string `json:"kind"` + // the Go module path a Go package is fetched under, equal to name for a Go package (go-package-registry's published); empty for every other kind of artifact + Module string `json:"module,omitempty"` + // 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) + Name string `json:"name"` + // why: a refusal's cause, a removal's (retention, tag-gone, by-hand), or why a removal was kept (shared-digest) + Reason string `json:"reason,omitempty"` + // the reference the seat answers: //@sha256:, or @ for a package (ADR 0605 §9) + Reference string `json:"reference,omitempty"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // for kept: the other tags still naming its digest + Tags []string `json:"tags,omitempty"` + // the transfer it ends, when it ended one + Transfer string `json:"transfer,omitempty"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of Outcome's kind. +const ( + OutcomeKindPackage = "package" + OutcomeKindImage = "image" + OutcomeKindBundle = "bundle" + OutcomeKindArchive = "archive" + OutcomeKindUpstream = "upstream" +) + +// Part is one part of a transfer. +type Part struct { + // the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob + Digest string `json:"digest"` + // its size in bytes + 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 + Commit string `json:"commit,omitempty"` + // its digest, sha256: (a Go package's h1: hash beside it in the version's record) + Digest string `json:"digest"` + // the build's fingerprint + Fingerprint string `json:"fingerprint,omitempty"` + // what it is + Kind string `json:"kind"` + // 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) + Name string `json:"name"` + // every part's digest and size; none when the digest is already held + Parts []Part `json:"parts,omitempty"` + // the tag it carries, for a tagged version + Tag string `json:"tag,omitempty"` + // the transfer's name, the caller's own; the same name opens the same transfer again + Transfer string `json:"transfer"` + // the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone + Version string `json:"version,omitempty"` +} + +// The values of PublishInput's kind. +const ( + PublishInputKindPackage = "package" + PublishInputKindImage = "image" + PublishInputKindBundle = "bundle" + PublishInputKindArchive = "archive" + PublishInputKindUpstream = "upstream" +) + +// PublishAnswer is what publish answers. +type PublishAnswer struct { + // the digest is held already: nothing to send; the outcome follows as an event + Held bool `json:"held,omitempty"` + // the digests of the parts the holder lacks, to send by publish-part + Lacks []string `json:"lacks,omitempty"` + // the transfer + Transfer string `json:"transfer"` +} + +// PublishPartInput is what publish-part takes. +type PublishPartInput struct { + // the chunk's bytes, base64 (at most 760 KiB before encoding) + Data string `json:"data"` + // this chunk is the part's last + Last bool `json:"last"` + // the part's digest + Part string `json:"part"` + // the chunk's place in its part, from 0 + Sequence int64 `json:"sequence"` + // the transfer + Transfer string `json:"transfer"` +} + +// PublishPartAnswer is what publish-part answers. +type PublishPartAnswer struct { + // the part is whole and its bytes match its digest + Complete bool `json:"complete,omitempty"` + // the part + Part string `json:"part"` + // the chunks of the part held so far + Received int64 `json:"received,omitempty"` + // the transfer + Transfer string `json:"transfer"` +} + +// VersionsInput is what versions takes. +type VersionsInput struct { + // narrowed to one kind (optional) + Kind string `json:"kind,omitempty"` + // the name + Name string `json:"name"` +} + +// VersionsAnswer is what versions answers. +type VersionsAnswer struct { + // the name + Name string `json:"name"` + // newest first + Versions []Artifact `json:"versions"` +} + +// RemoveInput is what remove takes. +type RemoveInput struct { + // the digest (for a bundle, archive or copied base) + Digest string `json:"digest,omitempty"` + // the name + Name string `json:"name"` + // why it goes + Reason string `json:"reason"` + // the tag (for a tagged version) + Tag string `json:"tag,omitempty"` + // the version + Version string `json:"version,omitempty"` +} + +// The values of RemoveInput's reason. +const ( + RemoveInputReasonTagGone = "tag-gone" + RemoveInputReasonRetention = "retention" +) + +// RemoveAnswer is what remove answers. +type RemoveAnswer struct { + Artifact *Artifact `json:"artifact,omitempty"` + // it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags) + Kept bool `json:"kept,omitempty"` + // why it was kept: shared-digest, another tag names its digest + Reason string `json:"reason,omitempty"` + // it is gone + Removed bool `json:"removed"` + // the other tags still naming its digest, when kept + Tags []string `json:"tags,omitempty"` +} + +// The values of RemoveAnswer's reason. +const ( + RemoveAnswerReasonSharedDigest = "shared-digest" +) + +// HoldInput is what hold takes. +type HoldInput struct { + // the digest + Digest string `json:"digest"` + // the name + Name string `json:"name"` + // which of the sweep's rules keeps it + Reason string `json:"reason"` +} + +// HoldAnswer is what hold answers. +type HoldAnswer struct { + // it is held + Held bool `json:"held"` + // when the hold began, RFC 3339 + Since string `json:"since,omitempty"` +} + +// ReleaseInput is what release takes. +type ReleaseInput struct { + // the digest + Digest string `json:"digest"` + // the name + Name string `json:"name"` + // why it is let go + Reason string `json:"reason"` +} + +// ReleaseAnswer is what release answers. +type ReleaseAnswer struct { + // a hold was let go; false when there was none + 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 + +// RefusedEvent is the body of the event refused. +type RefusedEvent = Outcome + +// RemovedEvent is the body of the event removed. +type RemovedEvent = Outcome + +// KeptEvent is the body of the event kept. +type KeptEvent = Outcome + +// HeldEvent is the body of the event held. +type HeldEvent = Hold + +// ReleasedEvent is the body of the event released. +type ReleasedEvent = Hold diff --git a/contracts/go/seats/ociartifactregistry/contract_test.go b/contracts/go/seats/ociartifactregistry/contract_test.go new file mode 100644 index 00000000..f6d90b31 --- /dev/null +++ b/contracts/go/seats/ociartifactregistry/contract_test.go @@ -0,0 +1,74 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +package ociartifactregistry + +import ( + "encoding/json" + "reflect" + "regexp" + "testing" +) + +// TestFixtures reads every fixture of seats/oci-artifact-registry/fixtures: each decodes into its type, a field the type does not +// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's. +func TestFixtures(t *testing.T) { + cases := []struct { + name, subject, given, wire string + decode func([]byte) (any, error) + subjectIs *regexp.Regexp + }{ + {name: "published on the seat's subject", subject: "mesh.seat.oci-artifact-registry.event.published", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"reference\":\"oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"reference\":\"oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.oci-artifact-registry\\.event\\.published$")}, + {name: "refused on the seat's subject", subject: "mesh.seat.oci-artifact-registry.event.refused", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"reason\":\"retention\",\"reference\":\"oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"reason\":\"retention\",\"reference\":\"oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v RefusedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.oci-artifact-registry\\.event\\.refused$")}, + {name: "removed on the seat's subject", subject: "mesh.seat.oci-artifact-registry.event.removed", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"reason\":\"retention\",\"reference\":\"oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"reason\":\"retention\",\"reference\":\"oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v RemovedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.oci-artifact-registry\\.event\\.removed$")}, + {name: "kept on the seat's subject: a tagged version sharing its digest stays, naming the tags on it", subject: "mesh.seat.oci-artifact-registry.event.kept", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"reason\":\"shared-digest\",\"reference\":\"oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"tag\":\"0.1.19-feat-x.2\",\"tags\":[\"0.1.19\"],\"version\":\"0.1.19-feat-x.2\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"holder\":\"distribution\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"reason\":\"shared-digest\",\"reference\":\"oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"tag\":\"0.1.19-feat-x.2\",\"tags\":[\"0.1.19\"],\"version\":\"0.1.19-feat-x.2\"}", + decode: func(b []byte) (any, error) { var v KeptEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.oci-artifact-registry\\.event\\.kept$")}, + {name: "held on the seat's subject", subject: "mesh.seat.oci-artifact-registry.event.held", given: "{\"at\":\"2026-11-04T12:00:00Z\",\"by\":\"mesh-artifact-sweep\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"holder\":\"distribution\",\"name\":\"mesh-sdk\",\"reason\":\"kept by the sweep's rule\"}", wire: "{\"at\":\"2026-11-04T12:00:00Z\",\"by\":\"mesh-artifact-sweep\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"holder\":\"distribution\",\"name\":\"mesh-sdk\",\"reason\":\"kept by the sweep's rule\"}", + decode: func(b []byte) (any, error) { var v HeldEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.oci-artifact-registry\\.event\\.held$")}, + {name: "released on the seat's subject", subject: "mesh.seat.oci-artifact-registry.event.released", given: "{\"at\":\"2026-11-04T12:00:00Z\",\"by\":\"mesh-artifact-sweep\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"holder\":\"distribution\",\"name\":\"mesh-sdk\",\"reason\":\"kept by the sweep's rule\"}", wire: "{\"at\":\"2026-11-04T12:00:00Z\",\"by\":\"mesh-artifact-sweep\",\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"holder\":\"distribution\",\"name\":\"mesh-sdk\",\"reason\":\"kept by the sweep's rule\"}", + decode: func(b []byte) (any, error) { var v ReleasedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.oci-artifact-registry\\.event\\.released$")}, + {name: "publish opens a transfer", subject: "", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a field the reader does not know is dropped", subject: "", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"bundle\",\"later\":{\"x\":1},\"name\":\"mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"bundle\",\"name\":\"mesh-sdk\",\"parts\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"size\":1048576}],\"transfer\":\"t1\",\"version\":\"0.1.19\"}", + decode: func(b []byte) (any, error) { var v PublishInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "publish answers what it lacks", subject: "", given: "{\"lacks\":[\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"transfer\":\"t1\"}", wire: "{\"lacks\":[\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"transfer\":\"t1\"}", + decode: func(b []byte) (any, error) { var v PublishAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a chunk of a part", subject: "", given: "{\"data\":\"aGVsbG8=\",\"last\":true,\"part\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"sequence\":0,\"transfer\":\"t1\"}", wire: "{\"data\":\"aGVsbG8=\",\"last\":true,\"part\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"sequence\":0,\"transfer\":\"t1\"}", + decode: func(b []byte) (any, error) { var v PublishPartInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "the versions held", subject: "", given: "{\"name\":\"mesh-sdk\",\"versions\":[{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"mesh-sdk\",\"version\":\"0.1.19\"}]}", wire: "{\"name\":\"mesh-sdk\",\"versions\":[{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"fingerprint\":\"f1\",\"kind\":\"package\",\"name\":\"mesh-sdk\",\"version\":\"0.1.19\"}]}", + decode: func(b []byte) (any, error) { var v VersionsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a release of what is not held answers released false", subject: "", given: "{\"released\":false}", wire: "{\"released\":false}", + 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)) + if err != nil { + t.Errorf("%s: does not decode: %v", c.name, err) + continue + } + back, err := json.Marshal(v) + if err != nil { + t.Errorf("%s: does not encode: %v", c.name, err) + continue + } + var got, want any + if err := json.Unmarshal(back, &got); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal([]byte(c.wire), &want); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire) + } + if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) { + t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs) + } + } +} diff --git a/contracts/go/seats/ociartifactregistry/go.mod b/contracts/go/seats/ociartifactregistry/go.mod new file mode 100644 index 00000000..f145223c --- /dev/null +++ b/contracts/go/seats/ociartifactregistry/go.mod @@ -0,0 +1,3 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry + +go 1.26.0 diff --git a/contracts/module.json b/contracts/module.json new file mode 100644 index 00000000..1e06b985 --- /dev/null +++ b/contracts/module.json @@ -0,0 +1,125 @@ +{ + "module": "mesh-delivery-contracts", + "version": "1", + "build": { + "artifacts": [ + { + "name": "go-containerimageregistry", + "kind": "package", + "language": "go", + "paths": [ + "go/seats/containerimageregistry" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "go-gopackageregistry", + "kind": "package", + "language": "go", + "paths": [ + "go/seats/gopackageregistry" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "go-npmpackageregistry", + "kind": "package", + "language": "go", + "paths": [ + "go/seats/npmpackageregistry" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "go-ociartifactregistry", + "kind": "package", + "language": "go", + "paths": [ + "go/seats/ociartifactregistry" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "go-containerimageregistryclient", + "kind": "package", + "language": "go", + "paths": [ + "go/clients/containerimageregistryclient" + ], + "series": "0.1" + }, + { + "name": "go-gopackageregistryclient", + "kind": "package", + "language": "go", + "paths": [ + "go/clients/gopackageregistryclient" + ], + "series": "0.1" + }, + { + "name": "go-npmpackageregistryclient", + "kind": "package", + "language": "go", + "paths": [ + "go/clients/npmpackageregistryclient" + ], + "series": "0.1" + }, + { + "name": "go-ociartifactregistryclient", + "kind": "package", + "language": "go", + "paths": [ + "go/clients/ociartifactregistryclient" + ], + "series": "0.1" + }, + { + "name": "ts-containerimageregistry", + "kind": "package", + "language": "typescript", + "paths": [ + "ts/seats/containerimageregistry" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "ts-gopackageregistry", + "kind": "package", + "language": "typescript", + "paths": [ + "ts/seats/gopackageregistry" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "ts-npmpackageregistry", + "kind": "package", + "language": "typescript", + "paths": [ + "ts/seats/npmpackageregistry" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "ts-ociartifactregistry", + "kind": "package", + "language": "typescript", + "paths": [ + "ts/seats/ociartifactregistry" + ], + "series": "0.1", + "generated-from": "." + } + ] + }, + "resources": [] +} diff --git a/contracts/parts/registry.json b/contracts/parts/registry.json new file mode 100644 index 00000000..ca0022f8 --- /dev/null +++ b/contracts/parts/registry.json @@ -0,0 +1,436 @@ +{ + "format": 1, + "part": "registry", + "description": "The verbs and events every registry seat shares, written once so the four cannot drift (design 51, design 26). Who may call each verb is the holder's trusted config value listing its callers (ADR 0363's form), checked against the caller the tool runner proved.", + "verbs": [ + { + "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).", + "callers": "the build modules", + "input": { + "type": "object", + "properties": { + "name": { + "type": "string", + "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)" + }, + "version": { + "type": "string", + "description": "the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone" + }, + "kind": { + "type": "string", + "enum": [ + "package", + "image", + "bundle", + "archive", + "upstream" + ], + "description": "what it is" + }, + "tag": { + "type": "string", + "description": "the tag it carries, for a tagged version" + }, + "digest": { + "type": "string", + "description": "its digest, sha256: (a Go package's h1: hash beside it in the version's record)" + }, + "fingerprint": { + "type": "string", + "description": "the build's fingerprint" + }, + "commit": { + "type": "string", + "description": "the source commit it was built from" + }, + "transfer": { + "type": "string", + "description": "the transfer's name, the caller's own; the same name opens the same transfer again" + }, + "parts": { + "type": "array", + "items": { + "$ref": "#/defs/Part" + }, + "description": "every part's digest and size; none when the digest is already held" + } + }, + "required": [ + "transfer", + "name", + "kind", + "digest" + ] + }, + "answer": { + "type": "object", + "properties": { + "transfer": { + "type": "string", + "description": "the transfer" + }, + "lacks": { + "type": "array", + "items": { + "type": "string" + }, + "description": "the digests of the parts the holder lacks, to send by publish-part" + }, + "held": { + "type": "boolean", + "description": "the digest is held already: nothing to send; the outcome follows as an event" + } + }, + "required": [ + "transfer" + ] + }, + "optional": true + }, + { + "name": "publish-part", + "description": "Send one chunk of one part of an open transfer; the last chunk of the last part ends the transfer, and the holder hashes what it received, asks builder_versions, stores it and says published or refused. A chunk's message stays under the bus's largest message (1 MiB), so at most 760 KiB of data, base64.", + "callers": "the build modules, and only the proved caller that opened the transfer", + "input": { + "type": "object", + "properties": { + "transfer": { + "type": "string", + "description": "the transfer" + }, + "part": { + "type": "string", + "description": "the part's digest" + }, + "sequence": { + "type": "integer", + "description": "the chunk's place in its part, from 0" + }, + "last": { + "type": "boolean", + "description": "this chunk is the part's last" + }, + "data": { + "type": "string", + "description": "the chunk's bytes, base64 (at most 760 KiB before encoding)" + } + }, + "required": [ + "transfer", + "part", + "sequence", + "last", + "data" + ] + }, + "answer": { + "type": "object", + "properties": { + "transfer": { + "type": "string", + "description": "the transfer" + }, + "part": { + "type": "string", + "description": "the part" + }, + "received": { + "type": "integer", + "description": "the chunks of the part held so far" + }, + "complete": { + "type": "boolean", + "description": "the part is whole and its bytes match its digest" + } + }, + "required": [ + "transfer", + "part" + ] + }, + "optional": true + }, + { + "name": "versions", + "description": "The versions the seat holds of one name, newest first: each one's version, tag, digest, kind and when it was published.", + "callers": "any caller whose role grants it", + "input": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the name" + }, + "kind": { + "type": "string", + "description": "narrowed to one kind (optional)" + } + }, + "required": [ + "name" + ] + }, + "answer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the name" + }, + "versions": { + "type": "array", + "items": { + "$ref": "#/defs/Artifact" + }, + "description": "newest first" + } + }, + "required": [ + "name", + "versions" + ] + }, + "optional": true + }, + { + "name": "remove", + "description": "Remove one tagged version; the holder refuses an untagged one but for the retention removal of a bundle, archive or copied base by the artifact sweep's holder. Every removal emits removed with its reason. A tagged version the store cannot remove alone, because another tag names its digest, stays whole, its tag and bytes kept: the answer says kept with the reason shared-digest and the tags still naming the digest, removed false, and kept is emitted, never removed; the caller ends there without a retry.", + "callers": "the builder, for a tagged version; the artifact sweep's holder, for retention", + "input": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the name" + }, + "version": { + "type": "string", + "description": "the version" + }, + "tag": { + "type": "string", + "description": "the tag (for a tagged version)" + }, + "digest": { + "type": "string", + "description": "the digest (for a bundle, archive or copied base)" + }, + "reason": { + "type": "string", + "enum": [ + "tag-gone", + "retention" + ], + "description": "why it goes" + } + }, + "required": [ + "name", + "reason" + ] + }, + "answer": { + "type": "object", + "properties": { + "removed": { + "type": "boolean", + "description": "it is gone" + }, + "kept": { + "type": "boolean", + "description": "it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags)" + }, + "reason": { + "type": "string", + "enum": [ + "shared-digest" + ], + "description": "why it was kept: shared-digest, another tag names its digest" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "the other tags still naming its digest, when kept" + }, + "artifact": { + "$ref": "#/defs/Artifact" + } + }, + "required": [ + "removed" + ] + }, + "optional": true + } + ], + "events": [ + { + "name": "published", + "body": { + "$ref": "#/defs/Outcome" + } + }, + { + "name": "refused", + "body": { + "$ref": "#/defs/Outcome" + } + }, + { + "name": "removed", + "body": { + "$ref": "#/defs/Outcome" + } + }, + { + "name": "kept", + "body": { + "$ref": "#/defs/Outcome" + } + } + ], + "defs": { + "Artifact": { + "type": "object", + "properties": { + "name": { + "type": "string", + "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)" + }, + "version": { + "type": "string", + "description": "the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone" + }, + "kind": { + "type": "string", + "enum": [ + "package", + "image", + "bundle", + "archive", + "upstream" + ], + "description": "what it is" + }, + "tag": { + "type": "string", + "description": "the tag it carries, for a tagged version" + }, + "digest": { + "type": "string", + "description": "its digest, sha256: (a Go package's h1: hash beside it in the version's record)" + }, + "fingerprint": { + "type": "string", + "description": "the build's fingerprint" + }, + "commit": { + "type": "string", + "description": "the source commit it was built from" + } + }, + "required": [ + "name", + "kind", + "digest" + ], + "description": "one published thing, as every registry event and answer names it" + }, + "Part": { + "type": "object", + "properties": { + "digest": { + "type": "string", + "description": "the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob" + }, + "size": { + "type": "integer", + "description": "its size in bytes" + } + }, + "required": [ + "digest", + "size" + ], + "description": "one part of a transfer" + }, + "Outcome": { + "type": "object", + "properties": { + "name": { + "type": "string", + "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)" + }, + "module": { + "type": "string", + "description": "the Go module path a Go package is fetched under, equal to name for a Go package (go-package-registry's published); empty for every other kind of artifact" + }, + "version": { + "type": "string", + "description": "the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone" + }, + "kind": { + "type": "string", + "enum": [ + "package", + "image", + "bundle", + "archive", + "upstream" + ], + "description": "what it is" + }, + "tag": { + "type": "string", + "description": "the tag it carries, for a tagged version" + }, + "digest": { + "type": "string", + "description": "its digest, sha256: (a Go package's h1: hash beside it in the version's record)" + }, + "fingerprint": { + "type": "string", + "description": "the build's fingerprint" + }, + "commit": { + "type": "string", + "description": "the source commit it was built from" + }, + "holder": { + "type": "string", + "description": "the module holding the seat that said it" + }, + "transfer": { + "type": "string", + "description": "the transfer it ends, when it ended one" + }, + "reference": { + "type": "string", + "description": "the reference the seat answers: //@sha256:, or @ for a package (ADR 0605 §9)" + }, + "reason": { + "type": "string", + "description": "why: a refusal's cause, a removal's (retention, tag-gone, by-hand), or why a removal was kept (shared-digest)" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "for kept: the other tags still naming its digest" + } + }, + "required": [ + "name", + "kind", + "digest", + "holder" + ], + "description": "the body of published, refused and removed" + } + } +} diff --git a/contracts/seats/container-image-registry/fixtures/events.json b/contracts/seats/container-image-registry/fixtures/events.json new file mode 100644 index 00000000..65e98e58 --- /dev/null +++ b/contracts/seats/container-image-registry/fixtures/events.json @@ -0,0 +1,98 @@ +[ + { + "name": "published on the seat's subject", + "of": "event:published", + "subject": "mesh.seat.container-image-registry.event.published", + "given": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "image", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "distribution", + "transfer": "t1", + "reference": "container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + }, + { + "name": "refused on the seat's subject", + "of": "event:refused", + "subject": "mesh.seat.container-image-registry.event.refused", + "given": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "image", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "distribution", + "transfer": "t1", + "reference": "container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "reason": "retention" + } + }, + { + "name": "removed on the seat's subject", + "of": "event:removed", + "subject": "mesh.seat.container-image-registry.event.removed", + "given": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "image", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "distribution", + "transfer": "t1", + "reference": "container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "reason": "retention" + } + }, + { + "name": "kept on the seat's subject: a tagged version sharing its digest stays, naming the tags on it", + "of": "event:kept", + "subject": "mesh.seat.container-image-registry.event.kept", + "given": { + "name": "mesh-sdk", + "version": "0.1.19-feat-x.2", + "kind": "image", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "distribution", + "reference": "container-image-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "reason": "shared-digest", + "tag": "0.1.19-feat-x.2", + "tags": [ + "0.1.19" + ] + } + }, + { + "name": "held on the seat's subject", + "of": "event:held", + "subject": "mesh.seat.container-image-registry.event.held", + "given": { + "name": "mesh-sdk", + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "reason": "kept by the sweep's rule", + "holder": "distribution", + "by": "mesh-artifact-sweep", + "at": "2026-11-04T12:00:00Z" + } + }, + { + "name": "released on the seat's subject", + "of": "event:released", + "subject": "mesh.seat.container-image-registry.event.released", + "given": { + "name": "mesh-sdk", + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "reason": "kept by the sweep's rule", + "holder": "distribution", + "by": "mesh-artifact-sweep", + "at": "2026-11-04T12:00:00Z" + } + } +] diff --git a/contracts/seats/container-image-registry/fixtures/verbs.json b/contracts/seats/container-image-registry/fixtures/verbs.json new file mode 100644 index 00000000..e024a813 --- /dev/null +++ b/contracts/seats/container-image-registry/fixtures/verbs.json @@ -0,0 +1,149 @@ +[ + { + "name": "publish opens a transfer", + "of": "verb:publish.input", + "given": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "image", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ] + } + }, + { + "name": "a field the reader does not know is dropped", + "of": "verb:publish.input", + "why": "ADR 0608 §6: a reader ignores a field it does not know", + "given": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "image", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ], + "later": { + "x": 1 + } + }, + "wire": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "image", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ] + } + }, + { + "name": "publish answers what it lacks", + "of": "verb:publish.answer", + "given": { + "transfer": "t1", + "lacks": [ + "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + ] + } + }, + { + "name": "a chunk of a part", + "of": "verb:publish-part.input", + "given": { + "transfer": "t1", + "part": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "sequence": 0, + "last": true, + "data": "aGVsbG8=" + } + }, + { + "name": "the versions held", + "of": "verb:versions.answer", + "given": { + "name": "mesh-sdk", + "versions": [ + { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc" + } + ] + } + }, + { + "name": "a release of what is not held answers released false", + "of": "verb:release.answer", + "why": "A3t: the outcome asked holds already, so it is no error", + "given": { + "released": false + } + }, + { + "name": "a tagged version sharing its digest with another tag is kept, not removed", + "of": "verb:remove.answer", + "why": "G-sdk-contracts 3: the store cannot remove the tag alone, so the answer says what holds", + "given": { + "removed": false, + "kept": true, + "reason": "shared-digest", + "tags": [ + "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/contracts/seats/container-image-registry/seat.json b/contracts/seats/container-image-registry/seat.json new file mode 100644 index 00000000..b1a595f3 --- /dev/null +++ b/contracts/seats/container-image-registry/seat.json @@ -0,0 +1,344 @@ +{ + "format": 1, + "seat": "container-image-registry", + "scope": "mesh", + "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.", + "includes": [ + "registry" + ], + "verbs": [ + { + "name": "mirror", + "description": "Copy an outside image into the store by its pinned digest; emits published or refused.", + "callers": "mesh-build-image's holder of the image build seat", + "input": { + "type": "object", + "properties": { + "source": { + "type": "string", + "description": "the outside image's reference" + }, + "digest": { + "type": "string", + "description": "its pinned digest, sha256:" + }, + "name": { + "type": "string", + "description": "the name it is kept under" + } + }, + "required": [ + "source", + "digest", + "name" + ] + }, + "answer": { + "type": "object", + "properties": { + "reference": { + "type": "string", + "description": "the reference the seat answers" + }, + "digest": { + "type": "string", + "description": "the digest stored" + } + }, + "required": [ + "reference", + "digest" + ] + }, + "optional": true + }, + { + "name": "tags", + "description": "The tags of one image and the digest each names.", + "callers": "any caller whose role grants it", + "input": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the image" + } + }, + "required": [ + "name" + ] + }, + "answer": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the image" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tag": { + "type": "string", + "description": "the tag" + }, + "digest": { + "type": "string", + "description": "its digest" + } + }, + "required": [ + "tag", + "digest" + ] + }, + "description": "every tag" + } + }, + "required": [ + "name", + "tags" + ] + }, + "optional": true + }, + { + "name": "hold", + "description": "Keep one untagged bundle, archive or copied base from removal by retention, as the artifact sweep's rules keep it (ADR 0617 §2); never an image of the holder's own. Emits held.", + "callers": "the module that runs the artifact sweep alone", + "input": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the name" + }, + "digest": { + "type": "string", + "description": "the digest" + }, + "reason": { + "type": "string", + "description": "which of the sweep's rules keeps it" + } + }, + "required": [ + "name", + "digest", + "reason" + ] + }, + "answer": { + "type": "object", + "properties": { + "held": { + "type": "boolean", + "description": "it is held" + }, + "since": { + "type": "string", + "description": "when the hold began, RFC 3339" + } + }, + "required": [ + "held" + ] + }, + "optional": true + }, + { + "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.", + "callers": "the module that runs the artifact sweep; the operator's session through the holder's tool", + "input": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the name" + }, + "digest": { + "type": "string", + "description": "the digest" + }, + "reason": { + "type": "string", + "description": "why it is let go" + } + }, + "required": [ + "name", + "digest", + "reason" + ] + }, + "answer": { + "type": "object", + "properties": { + "released": { + "type": "boolean", + "description": "a hold was let go; false when there was none" + } + }, + "required": [ + "released" + ] + }, + "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": [ + { + "name": "held", + "body": { + "$ref": "#/defs/Hold" + } + }, + { + "name": "released", + "body": { + "$ref": "#/defs/Hold" + } + } + ], + "family": { + "name": "container-image-registry.events", + "max-age-days": 7 + }, + "defs": { + "Hold": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the name" + }, + "digest": { + "type": "string", + "description": "the digest" + }, + "reason": { + "type": "string", + "description": "why" + }, + "holder": { + "type": "string", + "description": "the module holding the seat" + }, + "by": { + "type": "string", + "description": "the caller the holder proved" + }, + "at": { + "type": "string", + "description": "when, RFC 3339" + } + }, + "required": [ + "name", + "digest", + "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/contracts/seats/go-package-registry/fixtures/events.json b/contracts/seats/go-package-registry/fixtures/events.json new file mode 100644 index 00000000..c3a8b16d --- /dev/null +++ b/contracts/seats/go-package-registry/fixtures/events.json @@ -0,0 +1,73 @@ +[ + { + "name": "published on the seat's subject", + "of": "event:published", + "subject": "mesh.seat.go-package-registry.event.published", + "given": { + "name": "git.novox.be/novox/mesh-sdk/go", + "module": "git.novox.be/novox/mesh-sdk/go", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "gitea", + "transfer": "t1", + "reference": "git.novox.be/novox/mesh-sdk/go@0.1.19" + } + }, + { + "name": "refused on the seat's subject", + "of": "event:refused", + "subject": "mesh.seat.go-package-registry.event.refused", + "given": { + "name": "git.novox.be/novox/mesh-sdk/go", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "gitea", + "transfer": "t1", + "reference": "git.novox.be/novox/mesh-sdk/go@0.1.19", + "reason": "retention" + } + }, + { + "name": "removed on the seat's subject", + "of": "event:removed", + "subject": "mesh.seat.go-package-registry.event.removed", + "given": { + "name": "git.novox.be/novox/mesh-sdk/go", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "gitea", + "transfer": "t1", + "reference": "git.novox.be/novox/mesh-sdk/go@0.1.19", + "reason": "retention" + } + }, + { + "name": "kept on the seat's subject: a tagged version sharing its digest stays, naming the tags on it", + "of": "event:kept", + "subject": "mesh.seat.go-package-registry.event.kept", + "given": { + "name": "git.novox.be/novox/mesh-sdk/go", + "version": "0.1.19-feat-x.2", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "gitea", + "reference": "git.novox.be/novox/mesh-sdk/go@0.1.19", + "reason": "shared-digest", + "tag": "0.1.19-feat-x.2", + "tags": [ + "0.1.19" + ] + } + } +] diff --git a/contracts/seats/go-package-registry/fixtures/verbs.json b/contracts/seats/go-package-registry/fixtures/verbs.json new file mode 100644 index 00000000..be6a53a3 --- /dev/null +++ b/contracts/seats/go-package-registry/fixtures/verbs.json @@ -0,0 +1,110 @@ +[ + { + "name": "publish opens a transfer", + "of": "verb:publish.input", + "given": { + "name": "git.novox.be/novox/mesh-sdk/go", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ] + } + }, + { + "name": "a field the reader does not know is dropped", + "of": "verb:publish.input", + "why": "ADR 0608 §6: a reader ignores a field it does not know", + "given": { + "name": "git.novox.be/novox/mesh-sdk/go", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ], + "later": { + "x": 1 + } + }, + "wire": { + "name": "git.novox.be/novox/mesh-sdk/go", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ] + } + }, + { + "name": "publish answers what it lacks", + "of": "verb:publish.answer", + "given": { + "transfer": "t1", + "lacks": [ + "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + ] + } + }, + { + "name": "a chunk of a part", + "of": "verb:publish-part.input", + "given": { + "transfer": "t1", + "part": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "sequence": 0, + "last": true, + "data": "aGVsbG8=" + } + }, + { + "name": "the versions held", + "of": "verb:versions.answer", + "given": { + "name": "git.novox.be/novox/mesh-sdk/go", + "versions": [ + { + "name": "git.novox.be/novox/mesh-sdk/go", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc" + } + ] + } + }, + { + "name": "a tagged version sharing its digest with another tag is kept, not removed", + "of": "verb:remove.answer", + "why": "G-sdk-contracts 3: the store cannot remove the tag alone, so the answer says what holds", + "given": { + "removed": false, + "kept": true, + "reason": "shared-digest", + "tags": [ + "0.1.19" + ] + } + } +] diff --git a/contracts/seats/go-package-registry/seat.json b/contracts/seats/go-package-registry/seat.json new file mode 100644 index 00000000..edf39986 --- /dev/null +++ b/contracts/seats/go-package-registry/seat.json @@ -0,0 +1,34 @@ +{ + "format": 1, + "seat": "go-package-registry", + "scope": "mesh", + "grain": "whole", + "decision": "novox/hq ADR 0605", + "description": "The mesh's Go modules: Go's module proxy protocol's read side, and publishing over the bus.", + "delivers": "go-package-registry", + "includes": [ + "registry" + ], + "family": { + "name": "go-package-registry.events", + "max-age-days": 7 + }, + "provisions": [ + { + "name": "go-package-registry", + "form": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "the read side's address" + } + }, + "required": [ + "url" + ], + "description": "form none: the Go module proxy's read side, answered anonymously, with no credential (design 51)" + } + } + ] +} diff --git a/contracts/seats/npm-package-registry/fixtures/events.json b/contracts/seats/npm-package-registry/fixtures/events.json new file mode 100644 index 00000000..4cadc302 --- /dev/null +++ b/contracts/seats/npm-package-registry/fixtures/events.json @@ -0,0 +1,72 @@ +[ + { + "name": "published on the seat's subject", + "of": "event:published", + "subject": "mesh.seat.npm-package-registry.event.published", + "given": { + "name": "@novox/mesh-sdk", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "gitea", + "transfer": "t1", + "reference": "@novox/mesh-sdk@0.1.19" + } + }, + { + "name": "refused on the seat's subject", + "of": "event:refused", + "subject": "mesh.seat.npm-package-registry.event.refused", + "given": { + "name": "@novox/mesh-sdk", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "gitea", + "transfer": "t1", + "reference": "@novox/mesh-sdk@0.1.19", + "reason": "retention" + } + }, + { + "name": "removed on the seat's subject", + "of": "event:removed", + "subject": "mesh.seat.npm-package-registry.event.removed", + "given": { + "name": "@novox/mesh-sdk", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "gitea", + "transfer": "t1", + "reference": "@novox/mesh-sdk@0.1.19", + "reason": "retention" + } + }, + { + "name": "kept on the seat's subject: a tagged version sharing its digest stays, naming the tags on it", + "of": "event:kept", + "subject": "mesh.seat.npm-package-registry.event.kept", + "given": { + "name": "@novox/mesh-sdk", + "version": "0.1.19-feat-x.2", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "gitea", + "reference": "@novox/mesh-sdk@0.1.19", + "reason": "shared-digest", + "tag": "0.1.19-feat-x.2", + "tags": [ + "0.1.19" + ] + } + } +] diff --git a/contracts/seats/npm-package-registry/fixtures/verbs.json b/contracts/seats/npm-package-registry/fixtures/verbs.json new file mode 100644 index 00000000..0b88f08e --- /dev/null +++ b/contracts/seats/npm-package-registry/fixtures/verbs.json @@ -0,0 +1,110 @@ +[ + { + "name": "publish opens a transfer", + "of": "verb:publish.input", + "given": { + "name": "@novox/mesh-sdk", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ] + } + }, + { + "name": "a field the reader does not know is dropped", + "of": "verb:publish.input", + "why": "ADR 0608 §6: a reader ignores a field it does not know", + "given": { + "name": "@novox/mesh-sdk", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ], + "later": { + "x": 1 + } + }, + "wire": { + "name": "@novox/mesh-sdk", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ] + } + }, + { + "name": "publish answers what it lacks", + "of": "verb:publish.answer", + "given": { + "transfer": "t1", + "lacks": [ + "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + ] + } + }, + { + "name": "a chunk of a part", + "of": "verb:publish-part.input", + "given": { + "transfer": "t1", + "part": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "sequence": 0, + "last": true, + "data": "aGVsbG8=" + } + }, + { + "name": "the versions held", + "of": "verb:versions.answer", + "given": { + "name": "@novox/mesh-sdk", + "versions": [ + { + "name": "@novox/mesh-sdk", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc" + } + ] + } + }, + { + "name": "a tagged version sharing its digest with another tag is kept, not removed", + "of": "verb:remove.answer", + "why": "G-sdk-contracts 3: the store cannot remove the tag alone, so the answer says what holds", + "given": { + "removed": false, + "kept": true, + "reason": "shared-digest", + "tags": [ + "0.1.19" + ] + } + } +] diff --git a/contracts/seats/npm-package-registry/seat.json b/contracts/seats/npm-package-registry/seat.json new file mode 100644 index 00000000..61f77c32 --- /dev/null +++ b/contracts/seats/npm-package-registry/seat.json @@ -0,0 +1,34 @@ +{ + "format": 1, + "seat": "npm-package-registry", + "scope": "mesh", + "grain": "whole", + "decision": "novox/hq ADR 0109", + "description": "The mesh's npm packages: the npm registry protocol's read side, and publishing over the bus.", + "delivers": "npm-package-registry", + "includes": [ + "registry" + ], + "family": { + "name": "npm-package-registry.events", + "max-age-days": 7 + }, + "provisions": [ + { + "name": "npm-package-registry", + "form": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "the read side's address" + } + }, + "required": [ + "url" + ], + "description": "form none: the npm registry's read side, answered anonymously, with no credential (design 51)" + } + } + ] +} diff --git a/contracts/seats/oci-artifact-registry/fixtures/events.json b/contracts/seats/oci-artifact-registry/fixtures/events.json new file mode 100644 index 00000000..e0a8cde3 --- /dev/null +++ b/contracts/seats/oci-artifact-registry/fixtures/events.json @@ -0,0 +1,98 @@ +[ + { + "name": "published on the seat's subject", + "of": "event:published", + "subject": "mesh.seat.oci-artifact-registry.event.published", + "given": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "bundle", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "distribution", + "transfer": "t1", + "reference": "oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + }, + { + "name": "refused on the seat's subject", + "of": "event:refused", + "subject": "mesh.seat.oci-artifact-registry.event.refused", + "given": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "bundle", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "distribution", + "transfer": "t1", + "reference": "oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "reason": "retention" + } + }, + { + "name": "removed on the seat's subject", + "of": "event:removed", + "subject": "mesh.seat.oci-artifact-registry.event.removed", + "given": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "bundle", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "distribution", + "transfer": "t1", + "reference": "oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "reason": "retention" + } + }, + { + "name": "kept on the seat's subject: a tagged version sharing its digest stays, naming the tags on it", + "of": "event:kept", + "subject": "mesh.seat.oci-artifact-registry.event.kept", + "given": { + "name": "mesh-sdk", + "version": "0.1.19-feat-x.2", + "kind": "bundle", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "holder": "distribution", + "reference": "oci-artifact-registry/mesh-sdk/lib@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "reason": "shared-digest", + "tag": "0.1.19-feat-x.2", + "tags": [ + "0.1.19" + ] + } + }, + { + "name": "held on the seat's subject", + "of": "event:held", + "subject": "mesh.seat.oci-artifact-registry.event.held", + "given": { + "name": "mesh-sdk", + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "reason": "kept by the sweep's rule", + "holder": "distribution", + "by": "mesh-artifact-sweep", + "at": "2026-11-04T12:00:00Z" + } + }, + { + "name": "released on the seat's subject", + "of": "event:released", + "subject": "mesh.seat.oci-artifact-registry.event.released", + "given": { + "name": "mesh-sdk", + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "reason": "kept by the sweep's rule", + "holder": "distribution", + "by": "mesh-artifact-sweep", + "at": "2026-11-04T12:00:00Z" + } + } +] diff --git a/contracts/seats/oci-artifact-registry/fixtures/verbs.json b/contracts/seats/oci-artifact-registry/fixtures/verbs.json new file mode 100644 index 00000000..039c38b6 --- /dev/null +++ b/contracts/seats/oci-artifact-registry/fixtures/verbs.json @@ -0,0 +1,138 @@ +[ + { + "name": "publish opens a transfer", + "of": "verb:publish.input", + "given": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "bundle", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ] + } + }, + { + "name": "a field the reader does not know is dropped", + "of": "verb:publish.input", + "why": "ADR 0608 §6: a reader ignores a field it does not know", + "given": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "bundle", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ], + "later": { + "x": 1 + } + }, + "wire": { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "bundle", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc", + "transfer": "t1", + "parts": [ + { + "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "size": 1048576 + } + ] + } + }, + { + "name": "publish answers what it lacks", + "of": "verb:publish.answer", + "given": { + "transfer": "t1", + "lacks": [ + "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + ] + } + }, + { + "name": "a chunk of a part", + "of": "verb:publish-part.input", + "given": { + "transfer": "t1", + "part": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "sequence": 0, + "last": true, + "data": "aGVsbG8=" + } + }, + { + "name": "the versions held", + "of": "verb:versions.answer", + "given": { + "name": "mesh-sdk", + "versions": [ + { + "name": "mesh-sdk", + "version": "0.1.19", + "kind": "package", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fingerprint": "f1", + "commit": "cccccccccccccccccccccccccccccccccccccccc" + } + ] + } + }, + { + "name": "a release of what is not held answers released false", + "of": "verb:release.answer", + "why": "A3t: the outcome asked holds already, so it is no error", + "given": { + "released": false + } + }, + { + "name": "a tagged version sharing its digest with another tag is kept, not removed", + "of": "verb:remove.answer", + "why": "G-sdk-contracts 3: the store cannot remove the tag alone, so the answer says what holds", + "given": { + "removed": false, + "kept": true, + "reason": "shared-digest", + "tags": [ + "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/contracts/seats/oci-artifact-registry/seat.json b/contracts/seats/oci-artifact-registry/seat.json new file mode 100644 index 00000000..9d37fce7 --- /dev/null +++ b/contracts/seats/oci-artifact-registry/seat.json @@ -0,0 +1,247 @@ +{ + "format": 1, + "seat": "oci-artifact-registry", + "scope": "mesh", + "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.", + "includes": [ + "registry" + ], + "verbs": [ + { + "name": "hold", + "description": "Keep one untagged bundle, archive or copied base from removal by retention, as the artifact sweep's rules keep it (ADR 0617 §2); never an image of the holder's own. Emits held.", + "callers": "the module that runs the artifact sweep alone", + "input": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the name" + }, + "digest": { + "type": "string", + "description": "the digest" + }, + "reason": { + "type": "string", + "description": "which of the sweep's rules keeps it" + } + }, + "required": [ + "name", + "digest", + "reason" + ] + }, + "answer": { + "type": "object", + "properties": { + "held": { + "type": "boolean", + "description": "it is held" + }, + "since": { + "type": "string", + "description": "when the hold began, RFC 3339" + } + }, + "required": [ + "held" + ] + }, + "optional": true + }, + { + "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.", + "callers": "the module that runs the artifact sweep; the operator's session through the holder's tool", + "input": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the name" + }, + "digest": { + "type": "string", + "description": "the digest" + }, + "reason": { + "type": "string", + "description": "why it is let go" + } + }, + "required": [ + "name", + "digest", + "reason" + ] + }, + "answer": { + "type": "object", + "properties": { + "released": { + "type": "boolean", + "description": "a hold was let go; false when there was none" + } + }, + "required": [ + "released" + ] + }, + "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": [ + { + "name": "held", + "body": { + "$ref": "#/defs/Hold" + } + }, + { + "name": "released", + "body": { + "$ref": "#/defs/Hold" + } + } + ], + "family": { + "name": "oci-artifact-registry.events", + "max-age-days": 7 + }, + "defs": { + "Hold": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "the name" + }, + "digest": { + "type": "string", + "description": "the digest" + }, + "reason": { + "type": "string", + "description": "why" + }, + "holder": { + "type": "string", + "description": "the module holding the seat" + }, + "by": { + "type": "string", + "description": "the caller the holder proved" + }, + "at": { + "type": "string", + "description": "when, RFC 3339" + } + }, + "required": [ + "name", + "digest", + "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/contracts/ts/seats/containerimageregistry/package-lock.json b/contracts/ts/seats/containerimageregistry/package-lock.json new file mode 100644 index 00000000..91b0e989 --- /dev/null +++ b/contracts/ts/seats/containerimageregistry/package-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 3, + "name": "@novox/mesh-delivery-contracts-container-image-registry", + "packages": { + "": { + "name": "@novox/mesh-delivery-contracts-container-image-registry", + "version": "0.1.0" + } + }, + "requires": true, + "version": "0.1.0" +} diff --git a/contracts/ts/seats/containerimageregistry/package.json b/contracts/ts/seats/containerimageregistry/package.json new file mode 100644 index 00000000..0cbf3724 --- /dev/null +++ b/contracts/ts/seats/containerimageregistry/package.json @@ -0,0 +1,13 @@ +{ + "description": "The contract of the seat container-image-registry, generated from its definition.", + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src" + ], + "name": "@novox/mesh-delivery-contracts-container-image-registry", + "type": "module", + "types": "./src/index.ts", + "version": "0.1.0" +} diff --git a/contracts/ts/seats/containerimageregistry/src/index.ts b/contracts/ts/seats/containerimageregistry/src/index.ts new file mode 100644 index 00000000..941ed44b --- /dev/null +++ b/contracts/ts/seats/containerimageregistry/src/index.ts @@ -0,0 +1,190 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The contract of the seat "container-image-registry", defined whole (seats/container-image-registry/seat.json): 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. + +import type * as registry from "./parts/registry.js"; + +export const seat = "container-image-registry"; + +export const verbs = { + publish: "publish", + publishPart: "publish-part", + versions: "versions", + remove: "remove", + mirror: "mirror", + tags: "tags", + hold: "hold", + release: "release", + list: "list", +} as const; + +export const events = { + published: "published", + refused: "refused", + removed: "removed", + kept: "kept", + held: "held", + released: "released", +} as const; + +export const family = { name: "container-image-registry.events", maxAgeDays: 7 } as const; + +export const publishedSubject = "mesh.seat.container-image-registry.event.published"; + +export const refusedSubject = "mesh.seat.container-image-registry.event.refused"; + +export const removedSubject = "mesh.seat.container-image-registry.event.removed"; + +export const keptSubject = "mesh.seat.container-image-registry.event.kept"; + +export const heldSubject = "mesh.seat.container-image-registry.event.held"; + +export const releasedSubject = "mesh.seat.container-image-registry.event.released"; + +export type Artifact = registry.Artifact; +export type KeptEvent = registry.KeptEvent; +export type Outcome = registry.Outcome; +export type Part = registry.Part; +export type PublishAnswer = registry.PublishAnswer; +export type PublishInput = registry.PublishInput; +export type PublishPartAnswer = registry.PublishPartAnswer; +export type PublishPartInput = registry.PublishPartInput; +export type PublishedEvent = registry.PublishedEvent; +export type RefusedEvent = registry.RefusedEvent; +export type RemoveAnswer = registry.RemoveAnswer; +export type RemoveInput = registry.RemoveInput; +export type RemovedEvent = registry.RemovedEvent; +export type VersionsAnswer = registry.VersionsAnswer; +export type VersionsInput = registry.VersionsInput; + +/** the body of held and released */ +export interface Hold { + /** when, RFC 3339 */ + "at"?: string; + /** the caller the holder proved */ + "by"?: string; + /** the digest */ + "digest": string; + /** the module holding the seat */ + "holder": string; + /** the name */ + "name": string; + /** why */ + "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: */ + "digest": string; + /** the name it is kept under */ + "name": string; + /** the outside image's reference */ + "source": string; +} + +/** is what mirror answers. */ +export interface MirrorAnswer { + /** the digest stored */ + "digest": string; + /** the reference the seat answers */ + "reference": string; +} + +/** is what tags takes. */ +export interface TagsInput { + /** the image */ + "name": string; +} + +export interface TagsAnswerTagsItem { + /** its digest */ + "digest": string; + /** the tag */ + "tag": string; +} + +/** is what tags answers. */ +export interface TagsAnswer { + /** the image */ + "name": string; + /** every tag */ + "tags": TagsAnswerTagsItem[]; +} + +/** is what hold takes. */ +export interface HoldInput { + /** the digest */ + "digest": string; + /** the name */ + "name": string; + /** which of the sweep's rules keeps it */ + "reason": string; +} + +/** is what hold answers. */ +export interface HoldAnswer { + /** it is held */ + "held": boolean; + /** when the hold began, RFC 3339 */ + "since"?: string; +} + +/** is what release takes. */ +export interface ReleaseInput { + /** the digest */ + "digest": string; + /** the name */ + "name": string; + /** why it is let go */ + "reason": string; +} + +/** is what release answers. */ +export interface ReleaseAnswer { + /** a hold was let go; false when there was none */ + "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; + diff --git a/contracts/ts/seats/containerimageregistry/src/parts/registry.ts b/contracts/ts/seats/containerimageregistry/src/parts/registry.ts new file mode 100644 index 00000000..17a8de46 --- /dev/null +++ b/contracts/ts/seats/containerimageregistry/src/parts/registry.ts @@ -0,0 +1,187 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The part "registry" several seats include whole: The verbs and events every registry seat shares, written once so the four cannot drift (design 51, design 26). Who may call each verb is the holder's trusted config value listing its callers (ADR 0363's form), checked against the caller the tool runner proved. + +export const verbs = { + publish: "publish", + publishPart: "publish-part", + versions: "versions", + remove: "remove", +} as const; + +export const events = { + published: "published", + refused: "refused", + removed: "removed", + kept: "kept", +} as const; + +/** one published thing, as every registry event and answer names it */ +export interface Artifact { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** 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) */ + "name": string; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** the body of published, refused and removed */ +export interface Outcome { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** the module holding the seat that said it */ + "holder": string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** the Go module path a Go package is fetched under, equal to name for a Go package (go-package-registry's published); empty for every other kind of artifact */ + "module"?: string; + /** 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) */ + "name": string; + /** why: a refusal's cause, a removal's (retention, tag-gone, by-hand), or why a removal was kept (shared-digest) */ + "reason"?: string; + /** the reference the seat answers: //@sha256:, or @ for a package (ADR 0605 §9) */ + "reference"?: string; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** for kept: the other tags still naming its digest */ + "tags"?: string[]; + /** the transfer it ends, when it ended one */ + "transfer"?: string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** one part of a transfer */ +export interface Part { + /** the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob */ + "digest": string; + /** its size in bytes */ + "size": number; +} + +/** is what publish takes. */ +export interface PublishInput { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** 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) */ + "name": string; + /** every part's digest and size; none when the digest is already held */ + "parts"?: Part[]; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** the transfer's name, the caller's own; the same name opens the same transfer again */ + "transfer": string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** is what publish answers. */ +export interface PublishAnswer { + /** the digest is held already: nothing to send; the outcome follows as an event */ + "held"?: boolean; + /** the digests of the parts the holder lacks, to send by publish-part */ + "lacks"?: string[]; + /** the transfer */ + "transfer": string; +} + +/** is what publish-part takes. */ +export interface PublishPartInput { + /** the chunk's bytes, base64 (at most 760 KiB before encoding) */ + "data": string; + /** this chunk is the part's last */ + "last": boolean; + /** the part's digest */ + "part": string; + /** the chunk's place in its part, from 0 */ + "sequence": number; + /** the transfer */ + "transfer": string; +} + +/** is what publish-part answers. */ +export interface PublishPartAnswer { + /** the part is whole and its bytes match its digest */ + "complete"?: boolean; + /** the part */ + "part": string; + /** the chunks of the part held so far */ + "received"?: number; + /** the transfer */ + "transfer": string; +} + +/** is what versions takes. */ +export interface VersionsInput { + /** narrowed to one kind (optional) */ + "kind"?: string; + /** the name */ + "name": string; +} + +/** is what versions answers. */ +export interface VersionsAnswer { + /** the name */ + "name": string; + /** newest first */ + "versions": Artifact[]; +} + +/** is what remove takes. */ +export interface RemoveInput { + /** the digest (for a bundle, archive or copied base) */ + "digest"?: string; + /** the name */ + "name": string; + /** why it goes */ + "reason": "tag-gone" | "retention"; + /** the tag (for a tagged version) */ + "tag"?: string; + /** the version */ + "version"?: string; +} + +/** is what remove answers. */ +export interface RemoveAnswer { + "artifact"?: Artifact; + /** it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags) */ + "kept"?: boolean; + /** why it was kept: shared-digest, another tag names its digest */ + "reason"?: "shared-digest"; + /** it is gone */ + "removed": boolean; + /** the other tags still naming its digest, when kept */ + "tags"?: string[]; +} + +/** is the body of the event published. */ +export type PublishedEvent = Outcome; + +/** is the body of the event refused. */ +export type RefusedEvent = Outcome; + +/** is the body of the event removed. */ +export type RemovedEvent = Outcome; + +/** is the body of the event kept. */ +export type KeptEvent = Outcome; + diff --git a/contracts/ts/seats/containerimageregistry/tsconfig.json b/contracts/ts/seats/containerimageregistry/tsconfig.json new file mode 100644 index 00000000..60274c6a --- /dev/null +++ b/contracts/ts/seats/containerimageregistry/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"] +} diff --git a/contracts/ts/seats/gopackageregistry/package-lock.json b/contracts/ts/seats/gopackageregistry/package-lock.json new file mode 100644 index 00000000..345a71a1 --- /dev/null +++ b/contracts/ts/seats/gopackageregistry/package-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 3, + "name": "@novox/mesh-delivery-contracts-go-package-registry", + "packages": { + "": { + "name": "@novox/mesh-delivery-contracts-go-package-registry", + "version": "0.1.0" + } + }, + "requires": true, + "version": "0.1.0" +} diff --git a/contracts/ts/seats/gopackageregistry/package.json b/contracts/ts/seats/gopackageregistry/package.json new file mode 100644 index 00000000..1ea96fb4 --- /dev/null +++ b/contracts/ts/seats/gopackageregistry/package.json @@ -0,0 +1,13 @@ +{ + "description": "The contract of the seat go-package-registry, generated from its definition.", + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src" + ], + "name": "@novox/mesh-delivery-contracts-go-package-registry", + "type": "module", + "types": "./src/index.ts", + "version": "0.1.0" +} diff --git a/contracts/ts/seats/gopackageregistry/src/index.ts b/contracts/ts/seats/gopackageregistry/src/index.ts new file mode 100644 index 00000000..f0720909 --- /dev/null +++ b/contracts/ts/seats/gopackageregistry/src/index.ts @@ -0,0 +1,54 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The contract of the seat "go-package-registry", defined whole (seats/go-package-registry/seat.json): The mesh's Go modules: Go's module proxy protocol's read side, and publishing over the bus. + +import type * as registry from "./parts/registry.js"; + +export const seat = "go-package-registry"; + +export const verbs = { + publish: "publish", + publishPart: "publish-part", + versions: "versions", + remove: "remove", +} as const; + +export const events = { + published: "published", + refused: "refused", + removed: "removed", + kept: "kept", +} as const; + +export const family = { name: "go-package-registry.events", maxAgeDays: 7 } as const; + +export const publishedSubject = "mesh.seat.go-package-registry.event.published"; + +export const refusedSubject = "mesh.seat.go-package-registry.event.refused"; + +export const removedSubject = "mesh.seat.go-package-registry.event.removed"; + +export const keptSubject = "mesh.seat.go-package-registry.event.kept"; + +export type Artifact = registry.Artifact; +export type KeptEvent = registry.KeptEvent; +export type Outcome = registry.Outcome; +export type Part = registry.Part; +export type PublishAnswer = registry.PublishAnswer; +export type PublishInput = registry.PublishInput; +export type PublishPartAnswer = registry.PublishPartAnswer; +export type PublishPartInput = registry.PublishPartInput; +export type PublishedEvent = registry.PublishedEvent; +export type RefusedEvent = registry.RefusedEvent; +export type RemoveAnswer = registry.RemoveAnswer; +export type RemoveInput = registry.RemoveInput; +export type RemovedEvent = registry.RemovedEvent; +export type VersionsAnswer = registry.VersionsAnswer; +export type VersionsInput = registry.VersionsInput; + +/** is the form of the provision go-package-registry. */ +export interface GoPackageRegistryForm { + /** the read side's address */ + "url": string; +} + diff --git a/contracts/ts/seats/gopackageregistry/src/parts/registry.ts b/contracts/ts/seats/gopackageregistry/src/parts/registry.ts new file mode 100644 index 00000000..17a8de46 --- /dev/null +++ b/contracts/ts/seats/gopackageregistry/src/parts/registry.ts @@ -0,0 +1,187 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The part "registry" several seats include whole: The verbs and events every registry seat shares, written once so the four cannot drift (design 51, design 26). Who may call each verb is the holder's trusted config value listing its callers (ADR 0363's form), checked against the caller the tool runner proved. + +export const verbs = { + publish: "publish", + publishPart: "publish-part", + versions: "versions", + remove: "remove", +} as const; + +export const events = { + published: "published", + refused: "refused", + removed: "removed", + kept: "kept", +} as const; + +/** one published thing, as every registry event and answer names it */ +export interface Artifact { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** 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) */ + "name": string; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** the body of published, refused and removed */ +export interface Outcome { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** the module holding the seat that said it */ + "holder": string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** the Go module path a Go package is fetched under, equal to name for a Go package (go-package-registry's published); empty for every other kind of artifact */ + "module"?: string; + /** 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) */ + "name": string; + /** why: a refusal's cause, a removal's (retention, tag-gone, by-hand), or why a removal was kept (shared-digest) */ + "reason"?: string; + /** the reference the seat answers: //@sha256:, or @ for a package (ADR 0605 §9) */ + "reference"?: string; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** for kept: the other tags still naming its digest */ + "tags"?: string[]; + /** the transfer it ends, when it ended one */ + "transfer"?: string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** one part of a transfer */ +export interface Part { + /** the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob */ + "digest": string; + /** its size in bytes */ + "size": number; +} + +/** is what publish takes. */ +export interface PublishInput { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** 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) */ + "name": string; + /** every part's digest and size; none when the digest is already held */ + "parts"?: Part[]; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** the transfer's name, the caller's own; the same name opens the same transfer again */ + "transfer": string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** is what publish answers. */ +export interface PublishAnswer { + /** the digest is held already: nothing to send; the outcome follows as an event */ + "held"?: boolean; + /** the digests of the parts the holder lacks, to send by publish-part */ + "lacks"?: string[]; + /** the transfer */ + "transfer": string; +} + +/** is what publish-part takes. */ +export interface PublishPartInput { + /** the chunk's bytes, base64 (at most 760 KiB before encoding) */ + "data": string; + /** this chunk is the part's last */ + "last": boolean; + /** the part's digest */ + "part": string; + /** the chunk's place in its part, from 0 */ + "sequence": number; + /** the transfer */ + "transfer": string; +} + +/** is what publish-part answers. */ +export interface PublishPartAnswer { + /** the part is whole and its bytes match its digest */ + "complete"?: boolean; + /** the part */ + "part": string; + /** the chunks of the part held so far */ + "received"?: number; + /** the transfer */ + "transfer": string; +} + +/** is what versions takes. */ +export interface VersionsInput { + /** narrowed to one kind (optional) */ + "kind"?: string; + /** the name */ + "name": string; +} + +/** is what versions answers. */ +export interface VersionsAnswer { + /** the name */ + "name": string; + /** newest first */ + "versions": Artifact[]; +} + +/** is what remove takes. */ +export interface RemoveInput { + /** the digest (for a bundle, archive or copied base) */ + "digest"?: string; + /** the name */ + "name": string; + /** why it goes */ + "reason": "tag-gone" | "retention"; + /** the tag (for a tagged version) */ + "tag"?: string; + /** the version */ + "version"?: string; +} + +/** is what remove answers. */ +export interface RemoveAnswer { + "artifact"?: Artifact; + /** it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags) */ + "kept"?: boolean; + /** why it was kept: shared-digest, another tag names its digest */ + "reason"?: "shared-digest"; + /** it is gone */ + "removed": boolean; + /** the other tags still naming its digest, when kept */ + "tags"?: string[]; +} + +/** is the body of the event published. */ +export type PublishedEvent = Outcome; + +/** is the body of the event refused. */ +export type RefusedEvent = Outcome; + +/** is the body of the event removed. */ +export type RemovedEvent = Outcome; + +/** is the body of the event kept. */ +export type KeptEvent = Outcome; + diff --git a/contracts/ts/seats/gopackageregistry/tsconfig.json b/contracts/ts/seats/gopackageregistry/tsconfig.json new file mode 100644 index 00000000..60274c6a --- /dev/null +++ b/contracts/ts/seats/gopackageregistry/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"] +} diff --git a/contracts/ts/seats/npmpackageregistry/package-lock.json b/contracts/ts/seats/npmpackageregistry/package-lock.json new file mode 100644 index 00000000..321b0c94 --- /dev/null +++ b/contracts/ts/seats/npmpackageregistry/package-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 3, + "name": "@novox/mesh-delivery-contracts-npm-package-registry", + "packages": { + "": { + "name": "@novox/mesh-delivery-contracts-npm-package-registry", + "version": "0.1.0" + } + }, + "requires": true, + "version": "0.1.0" +} diff --git a/contracts/ts/seats/npmpackageregistry/package.json b/contracts/ts/seats/npmpackageregistry/package.json new file mode 100644 index 00000000..ca10dfc9 --- /dev/null +++ b/contracts/ts/seats/npmpackageregistry/package.json @@ -0,0 +1,13 @@ +{ + "description": "The contract of the seat npm-package-registry, generated from its definition.", + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src" + ], + "name": "@novox/mesh-delivery-contracts-npm-package-registry", + "type": "module", + "types": "./src/index.ts", + "version": "0.1.0" +} diff --git a/contracts/ts/seats/npmpackageregistry/src/index.ts b/contracts/ts/seats/npmpackageregistry/src/index.ts new file mode 100644 index 00000000..a704e8b6 --- /dev/null +++ b/contracts/ts/seats/npmpackageregistry/src/index.ts @@ -0,0 +1,54 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The contract of the seat "npm-package-registry", defined whole (seats/npm-package-registry/seat.json): The mesh's npm packages: the npm registry protocol's read side, and publishing over the bus. + +import type * as registry from "./parts/registry.js"; + +export const seat = "npm-package-registry"; + +export const verbs = { + publish: "publish", + publishPart: "publish-part", + versions: "versions", + remove: "remove", +} as const; + +export const events = { + published: "published", + refused: "refused", + removed: "removed", + kept: "kept", +} as const; + +export const family = { name: "npm-package-registry.events", maxAgeDays: 7 } as const; + +export const publishedSubject = "mesh.seat.npm-package-registry.event.published"; + +export const refusedSubject = "mesh.seat.npm-package-registry.event.refused"; + +export const removedSubject = "mesh.seat.npm-package-registry.event.removed"; + +export const keptSubject = "mesh.seat.npm-package-registry.event.kept"; + +export type Artifact = registry.Artifact; +export type KeptEvent = registry.KeptEvent; +export type Outcome = registry.Outcome; +export type Part = registry.Part; +export type PublishAnswer = registry.PublishAnswer; +export type PublishInput = registry.PublishInput; +export type PublishPartAnswer = registry.PublishPartAnswer; +export type PublishPartInput = registry.PublishPartInput; +export type PublishedEvent = registry.PublishedEvent; +export type RefusedEvent = registry.RefusedEvent; +export type RemoveAnswer = registry.RemoveAnswer; +export type RemoveInput = registry.RemoveInput; +export type RemovedEvent = registry.RemovedEvent; +export type VersionsAnswer = registry.VersionsAnswer; +export type VersionsInput = registry.VersionsInput; + +/** is the form of the provision npm-package-registry. */ +export interface NpmPackageRegistryForm { + /** the read side's address */ + "url": string; +} + diff --git a/contracts/ts/seats/npmpackageregistry/src/parts/registry.ts b/contracts/ts/seats/npmpackageregistry/src/parts/registry.ts new file mode 100644 index 00000000..17a8de46 --- /dev/null +++ b/contracts/ts/seats/npmpackageregistry/src/parts/registry.ts @@ -0,0 +1,187 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The part "registry" several seats include whole: The verbs and events every registry seat shares, written once so the four cannot drift (design 51, design 26). Who may call each verb is the holder's trusted config value listing its callers (ADR 0363's form), checked against the caller the tool runner proved. + +export const verbs = { + publish: "publish", + publishPart: "publish-part", + versions: "versions", + remove: "remove", +} as const; + +export const events = { + published: "published", + refused: "refused", + removed: "removed", + kept: "kept", +} as const; + +/** one published thing, as every registry event and answer names it */ +export interface Artifact { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** 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) */ + "name": string; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** the body of published, refused and removed */ +export interface Outcome { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** the module holding the seat that said it */ + "holder": string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** the Go module path a Go package is fetched under, equal to name for a Go package (go-package-registry's published); empty for every other kind of artifact */ + "module"?: string; + /** 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) */ + "name": string; + /** why: a refusal's cause, a removal's (retention, tag-gone, by-hand), or why a removal was kept (shared-digest) */ + "reason"?: string; + /** the reference the seat answers: //@sha256:, or @ for a package (ADR 0605 §9) */ + "reference"?: string; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** for kept: the other tags still naming its digest */ + "tags"?: string[]; + /** the transfer it ends, when it ended one */ + "transfer"?: string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** one part of a transfer */ +export interface Part { + /** the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob */ + "digest": string; + /** its size in bytes */ + "size": number; +} + +/** is what publish takes. */ +export interface PublishInput { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** 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) */ + "name": string; + /** every part's digest and size; none when the digest is already held */ + "parts"?: Part[]; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** the transfer's name, the caller's own; the same name opens the same transfer again */ + "transfer": string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** is what publish answers. */ +export interface PublishAnswer { + /** the digest is held already: nothing to send; the outcome follows as an event */ + "held"?: boolean; + /** the digests of the parts the holder lacks, to send by publish-part */ + "lacks"?: string[]; + /** the transfer */ + "transfer": string; +} + +/** is what publish-part takes. */ +export interface PublishPartInput { + /** the chunk's bytes, base64 (at most 760 KiB before encoding) */ + "data": string; + /** this chunk is the part's last */ + "last": boolean; + /** the part's digest */ + "part": string; + /** the chunk's place in its part, from 0 */ + "sequence": number; + /** the transfer */ + "transfer": string; +} + +/** is what publish-part answers. */ +export interface PublishPartAnswer { + /** the part is whole and its bytes match its digest */ + "complete"?: boolean; + /** the part */ + "part": string; + /** the chunks of the part held so far */ + "received"?: number; + /** the transfer */ + "transfer": string; +} + +/** is what versions takes. */ +export interface VersionsInput { + /** narrowed to one kind (optional) */ + "kind"?: string; + /** the name */ + "name": string; +} + +/** is what versions answers. */ +export interface VersionsAnswer { + /** the name */ + "name": string; + /** newest first */ + "versions": Artifact[]; +} + +/** is what remove takes. */ +export interface RemoveInput { + /** the digest (for a bundle, archive or copied base) */ + "digest"?: string; + /** the name */ + "name": string; + /** why it goes */ + "reason": "tag-gone" | "retention"; + /** the tag (for a tagged version) */ + "tag"?: string; + /** the version */ + "version"?: string; +} + +/** is what remove answers. */ +export interface RemoveAnswer { + "artifact"?: Artifact; + /** it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags) */ + "kept"?: boolean; + /** why it was kept: shared-digest, another tag names its digest */ + "reason"?: "shared-digest"; + /** it is gone */ + "removed": boolean; + /** the other tags still naming its digest, when kept */ + "tags"?: string[]; +} + +/** is the body of the event published. */ +export type PublishedEvent = Outcome; + +/** is the body of the event refused. */ +export type RefusedEvent = Outcome; + +/** is the body of the event removed. */ +export type RemovedEvent = Outcome; + +/** is the body of the event kept. */ +export type KeptEvent = Outcome; + diff --git a/contracts/ts/seats/npmpackageregistry/tsconfig.json b/contracts/ts/seats/npmpackageregistry/tsconfig.json new file mode 100644 index 00000000..60274c6a --- /dev/null +++ b/contracts/ts/seats/npmpackageregistry/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"] +} diff --git a/contracts/ts/seats/ociartifactregistry/package-lock.json b/contracts/ts/seats/ociartifactregistry/package-lock.json new file mode 100644 index 00000000..0ff49c30 --- /dev/null +++ b/contracts/ts/seats/ociartifactregistry/package-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 3, + "name": "@novox/mesh-delivery-contracts-oci-artifact-registry", + "packages": { + "": { + "name": "@novox/mesh-delivery-contracts-oci-artifact-registry", + "version": "0.1.0" + } + }, + "requires": true, + "version": "0.1.0" +} diff --git a/contracts/ts/seats/ociartifactregistry/package.json b/contracts/ts/seats/ociartifactregistry/package.json new file mode 100644 index 00000000..7f05486f --- /dev/null +++ b/contracts/ts/seats/ociartifactregistry/package.json @@ -0,0 +1,13 @@ +{ + "description": "The contract of the seat oci-artifact-registry, generated from its definition.", + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src" + ], + "name": "@novox/mesh-delivery-contracts-oci-artifact-registry", + "type": "module", + "types": "./src/index.ts", + "version": "0.1.0" +} diff --git a/contracts/ts/seats/ociartifactregistry/src/index.ts b/contracts/ts/seats/ociartifactregistry/src/index.ts new file mode 100644 index 00000000..ebf6242e --- /dev/null +++ b/contracts/ts/seats/ociartifactregistry/src/index.ts @@ -0,0 +1,149 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The contract of the seat "oci-artifact-registry", defined whole (seats/oci-artifact-registry/seat.json): The mesh's bundles and archives, by digest, in the one artifact store: the OCI distribution protocol's read side. + +import type * as registry from "./parts/registry.js"; + +export const seat = "oci-artifact-registry"; + +export const verbs = { + publish: "publish", + publishPart: "publish-part", + versions: "versions", + remove: "remove", + hold: "hold", + release: "release", + list: "list", +} as const; + +export const events = { + published: "published", + refused: "refused", + removed: "removed", + kept: "kept", + held: "held", + released: "released", +} as const; + +export const family = { name: "oci-artifact-registry.events", maxAgeDays: 7 } as const; + +export const publishedSubject = "mesh.seat.oci-artifact-registry.event.published"; + +export const refusedSubject = "mesh.seat.oci-artifact-registry.event.refused"; + +export const removedSubject = "mesh.seat.oci-artifact-registry.event.removed"; + +export const keptSubject = "mesh.seat.oci-artifact-registry.event.kept"; + +export const heldSubject = "mesh.seat.oci-artifact-registry.event.held"; + +export const releasedSubject = "mesh.seat.oci-artifact-registry.event.released"; + +export type Artifact = registry.Artifact; +export type KeptEvent = registry.KeptEvent; +export type Outcome = registry.Outcome; +export type Part = registry.Part; +export type PublishAnswer = registry.PublishAnswer; +export type PublishInput = registry.PublishInput; +export type PublishPartAnswer = registry.PublishPartAnswer; +export type PublishPartInput = registry.PublishPartInput; +export type PublishedEvent = registry.PublishedEvent; +export type RefusedEvent = registry.RefusedEvent; +export type RemoveAnswer = registry.RemoveAnswer; +export type RemoveInput = registry.RemoveInput; +export type RemovedEvent = registry.RemovedEvent; +export type VersionsAnswer = registry.VersionsAnswer; +export type VersionsInput = registry.VersionsInput; + +/** the body of held and released */ +export interface Hold { + /** when, RFC 3339 */ + "at"?: string; + /** the caller the holder proved */ + "by"?: string; + /** the digest */ + "digest": string; + /** the module holding the seat */ + "holder": string; + /** the name */ + "name": string; + /** why */ + "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 */ + "digest": string; + /** the name */ + "name": string; + /** which of the sweep's rules keeps it */ + "reason": string; +} + +/** is what hold answers. */ +export interface HoldAnswer { + /** it is held */ + "held": boolean; + /** when the hold began, RFC 3339 */ + "since"?: string; +} + +/** is what release takes. */ +export interface ReleaseInput { + /** the digest */ + "digest": string; + /** the name */ + "name": string; + /** why it is let go */ + "reason": string; +} + +/** is what release answers. */ +export interface ReleaseAnswer { + /** a hold was let go; false when there was none */ + "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; + diff --git a/contracts/ts/seats/ociartifactregistry/src/parts/registry.ts b/contracts/ts/seats/ociartifactregistry/src/parts/registry.ts new file mode 100644 index 00000000..17a8de46 --- /dev/null +++ b/contracts/ts/seats/ociartifactregistry/src/parts/registry.ts @@ -0,0 +1,187 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The part "registry" several seats include whole: The verbs and events every registry seat shares, written once so the four cannot drift (design 51, design 26). Who may call each verb is the holder's trusted config value listing its callers (ADR 0363's form), checked against the caller the tool runner proved. + +export const verbs = { + publish: "publish", + publishPart: "publish-part", + versions: "versions", + remove: "remove", +} as const; + +export const events = { + published: "published", + refused: "refused", + removed: "removed", + kept: "kept", +} as const; + +/** one published thing, as every registry event and answer names it */ +export interface Artifact { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** 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) */ + "name": string; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** the body of published, refused and removed */ +export interface Outcome { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** the module holding the seat that said it */ + "holder": string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** the Go module path a Go package is fetched under, equal to name for a Go package (go-package-registry's published); empty for every other kind of artifact */ + "module"?: string; + /** 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) */ + "name": string; + /** why: a refusal's cause, a removal's (retention, tag-gone, by-hand), or why a removal was kept (shared-digest) */ + "reason"?: string; + /** the reference the seat answers: //@sha256:, or @ for a package (ADR 0605 §9) */ + "reference"?: string; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** for kept: the other tags still naming its digest */ + "tags"?: string[]; + /** the transfer it ends, when it ended one */ + "transfer"?: string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** one part of a transfer */ +export interface Part { + /** the part's digest, sha256:: an image's layer or config, a package's file, a bundle's blob */ + "digest": string; + /** its size in bytes */ + "size": number; +} + +/** is what publish takes. */ +export interface PublishInput { + /** the source commit it was built from */ + "commit"?: string; + /** its digest, sha256: (a Go package's h1: hash beside it in the version's record) */ + "digest": string; + /** the build's fingerprint */ + "fingerprint"?: string; + /** what it is */ + "kind": "package" | "image" | "bundle" | "archive" | "upstream"; + /** 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) */ + "name": string; + /** every part's digest and size; none when the digest is already held */ + "parts"?: Part[]; + /** the tag it carries, for a tagged version */ + "tag"?: string; + /** the transfer's name, the caller's own; the same name opens the same transfer again */ + "transfer": string; + /** the version the builder gave it (design 51 *The versions*); empty for a bundle or archive published by its digest alone */ + "version"?: string; +} + +/** is what publish answers. */ +export interface PublishAnswer { + /** the digest is held already: nothing to send; the outcome follows as an event */ + "held"?: boolean; + /** the digests of the parts the holder lacks, to send by publish-part */ + "lacks"?: string[]; + /** the transfer */ + "transfer": string; +} + +/** is what publish-part takes. */ +export interface PublishPartInput { + /** the chunk's bytes, base64 (at most 760 KiB before encoding) */ + "data": string; + /** this chunk is the part's last */ + "last": boolean; + /** the part's digest */ + "part": string; + /** the chunk's place in its part, from 0 */ + "sequence": number; + /** the transfer */ + "transfer": string; +} + +/** is what publish-part answers. */ +export interface PublishPartAnswer { + /** the part is whole and its bytes match its digest */ + "complete"?: boolean; + /** the part */ + "part": string; + /** the chunks of the part held so far */ + "received"?: number; + /** the transfer */ + "transfer": string; +} + +/** is what versions takes. */ +export interface VersionsInput { + /** narrowed to one kind (optional) */ + "kind"?: string; + /** the name */ + "name": string; +} + +/** is what versions answers. */ +export interface VersionsAnswer { + /** the name */ + "name": string; + /** newest first */ + "versions": Artifact[]; +} + +/** is what remove takes. */ +export interface RemoveInput { + /** the digest (for a bundle, archive or copied base) */ + "digest"?: string; + /** the name */ + "name": string; + /** why it goes */ + "reason": "tag-gone" | "retention"; + /** the tag (for a tagged version) */ + "tag"?: string; + /** the version */ + "version"?: string; +} + +/** is what remove answers. */ +export interface RemoveAnswer { + "artifact"?: Artifact; + /** it stays whole, its tag and its bytes: the store cannot remove the tag alone while another tag names its digest (reason, tags) */ + "kept"?: boolean; + /** why it was kept: shared-digest, another tag names its digest */ + "reason"?: "shared-digest"; + /** it is gone */ + "removed": boolean; + /** the other tags still naming its digest, when kept */ + "tags"?: string[]; +} + +/** is the body of the event published. */ +export type PublishedEvent = Outcome; + +/** is the body of the event refused. */ +export type RefusedEvent = Outcome; + +/** is the body of the event removed. */ +export type RemovedEvent = Outcome; + +/** is the body of the event kept. */ +export type KeptEvent = Outcome; + diff --git a/contracts/ts/seats/ociartifactregistry/tsconfig.json b/contracts/ts/seats/ociartifactregistry/tsconfig.json new file mode 100644 index 00000000..60274c6a --- /dev/null +++ b/contracts/ts/seats/ociartifactregistry/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"] +}