Compare commits

..
Author SHA1 Message Date
jschoubben b8d4a77e75 mesh-wants upgrade reads through mesh-sdk's watched reader, keeping no mirror of its own
One helper serves every part that reads whole states (ADR 0580 clause 3): the assigner's own watched mirror goes, its
compare reads the large states from stdio.WatchedStates and each manifest once per digest. mesh-sdk pinned at 8311ecb.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 03:34:21 +01:00
jschoubben f95d5b4d23 mesh-wants upgrade: read the large states by one watch each, started once, not a get per key, so compare answers in time (issue 1953 L5f)
Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 03:22:12 +01:00
815 changed files with 17829 additions and 100235 deletions
@@ -1,63 +0,0 @@
// 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-delivery/sdk/parts"
)
// Call asks one verb of the seat, `seat:<seat>.<verb>`, 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
}
@@ -1,57 +0,0 @@
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-delivery/sdk/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)
}
}
@@ -1,18 +0,0 @@
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-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
)
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
)
@@ -1,12 +0,0 @@
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=
@@ -1,116 +0,0 @@
// Package reference holds the form of an artifact's reference once a registry seat published it: `<seat>/<module>/<artifact>@<digest>`
// (novox/hq ADR 0605 §9), and the older `artifact-store://<path>` form read beside it. It is the rule the holders and callers of the two
// registry seats that front the artifact store share (the seats' client, novox/hq ADR 0625 §4, ADR 0634 §2); it names no seat itself (ADR 0634 §4, ADR 0635 §4): the
// caller gives the seats' names from their contracts (Seats), so a package here cannot drift from a contract.
package reference
import "strings"
// ArtifactStoreScheme marks a reference to something in the mesh's artifact store, kept without the store's address.
const ArtifactStoreScheme = "artifact-store://"
// UpstreamPrefix opens the holder's own name for an upstream copy it keeps: `upstream/<host>/<path>@<digest>`, after
// `<image seat>/` in the seat's form and after `artifact-store://` in the older one.
const UpstreamPrefix = "upstream/"
// Seats are the names of the two registry seats a reference of the registry form names first: Image the seat that fronts
// images and upstream copies (container-image-registry), Object the seat that fronts bundles and archives
// (oci-artifact-registry). The caller reads them from the seats' contracts.
type Seats struct{ Image, Object string }
// All is the two names.
func (s Seats) All() []string { return []string{s.Image, s.Object} }
// Recorded is a reference as the mesh records it: the artifact store's address, if the builder wrote one, taken off.
func (s Seats) Recorded(reference string) string {
if _, _, seat := s.InRegistrySeat(reference); seat || strings.HasPrefix(reference, ArtifactStoreScheme) {
return reference
}
if rest, isURL := strings.CutPrefix(reference, "http://"); isURL {
if _, path, ok := strings.Cut(rest, "/v2/"); ok && strings.Contains(path, "/blobs/") {
return ArtifactStoreScheme + path
}
return reference
}
host, path, ok := strings.Cut(reference, "/")
if !ok || !isRegistryHost(host) || !strings.Contains(path, "@sha256:") {
return reference
}
return ArtifactStoreScheme + path
}
// isRegistryHost is the runtime's own rule for reading the first component of a reference as a registry rather than a
// namespace: it has a dot or a port in it, or it is localhost.
func isRegistryHost(component string) bool {
return component == "localhost" || strings.ContainsAny(component, ".:")
}
// InArtifactStore reports whether a reference is a kept one, and what it names there: `<module>/<artifact>@<digest>` for an
// image, `<module>/<artifact>/blobs/<digest>` for a bundle or an archive. It reads both forms (ADR 0605 §9).
func (s Seats) InArtifactStore(reference string) (path string, kept bool) {
if seat, rest, ok := s.InRegistrySeat(reference); ok {
if seat == s.Object {
name, digest, _ := strings.Cut(rest, "@")
return name + "/blobs/" + digest, true
}
return rest, true
}
return strings.CutPrefix(reference, ArtifactStoreScheme)
}
// SeatReference is the reference a build names an image, bundle, archive or upstream copy by once it published it through
// a registry seat: `<seat>/<module>/<artifact>@sha256:<digest>`, as InRegistrySeat reads it back.
func SeatReference(seat, module, artifact, digest string) string {
return seat + "/" + module + "/" + artifact + "@" + digest
}
// InRegistrySeat reads a reference in the form a registry seat answers: the seat it came through and the rest. A seat's
// name holds no dot or port, so no image of an outside registry reads as one, and a kept `artifact-store://` reference is
// not one. It also reads the holder's own naming of an upstream copy through the image seat, whose rest InUpstream reads.
func (s Seats) InRegistrySeat(reference string) (seat, path string, ok bool) {
first, rest, found := strings.Cut(reference, "/")
if !found || !strings.Contains(rest, "@sha256:") {
return "", "", false
}
if _, _, upstream := InUpstream(rest); upstream && first == s.Image {
return first, rest, true
}
if strings.Count(rest, "/") != 1 {
return "", "", false
}
for _, name := range s.All() {
if first == name {
return name, rest, true
}
}
return "", "", false
}
// InUpstream reads a path in the holder's naming of an upstream copy, `upstream/<host>/<path>@sha256:<digest>`: the
// upstream registry host and the image's path there, with its digest.
func InUpstream(path string) (host, image string, ok bool) {
rest, found := strings.CutPrefix(path, UpstreamPrefix)
if !found {
return "", "", false
}
host, image, found = strings.Cut(rest, "/")
name, digest, pinned := strings.Cut(image, "@")
if !found || !isRegistryHost(host) || name == "" || strings.HasPrefix(name, "/") || strings.HasSuffix(name, "/") ||
strings.Contains(name, "//") || !pinned || !strings.HasPrefix(digest, "sha256:") || strings.Contains(digest, "/") {
return "", "", false
}
return host, image, true
}
// Routed is a kept reference as a machine fetches it, through the artifact store at `address` (host:port). A reference
// that is not a kept one is what it was.
func (s Seats) Routed(reference, address string) string {
path, kept := s.InArtifactStore(reference)
if !kept {
return reference
}
if strings.Contains(path, "/blobs/") {
return "http://" + address + "/v2/" + path
}
return address + "/" + path
}
@@ -1,69 +0,0 @@
package reference
import (
"strings"
"testing"
)
var digest = "sha256:" + strings.Repeat("d", 64)
var seats = Seats{Image: "container-image-registry", Object: "object-registry"}
func TestAReferenceIsRecordedWithoutTheStoresAddress(t *testing.T) {
cases := map[string]string{
"anchor.internal:5100/gitea/server@" + digest: ArtifactStoreScheme + "gitea/server@" + digest,
"localhost:5000/gitea/server@" + digest: ArtifactStoreScheme + "gitea/server@" + digest,
"http://anchor.internal:5100/v2/gitea/config/blobs/" + digest: ArtifactStoreScheme + "gitea/config/blobs/" + digest,
ArtifactStoreScheme + "gitea/server@" + digest: ArtifactStoreScheme + "gitea/server@" + digest,
digest: digest,
"@novox/sdk@1.2.3": "@novox/sdk@1.2.3",
"gitea/gitea@" + digest: "gitea/gitea@" + digest,
"https://registry.example/v2/gitea/config/blobs/" + digest: "https://registry.example/v2/gitea/config/blobs/" + digest,
// The form a registry seat answers is recorded as it is (ADR 0605 §9).
"container-image-registry/gitea/server@" + digest: "container-image-registry/gitea/server@" + digest,
"object-registry/gitea/config@" + digest: "object-registry/gitea/config@" + digest,
}
for announced, want := range cases {
if got := seats.Recorded(announced); got != want {
t.Errorf("Recorded(%q) = %q, want %q", announced, got, want)
}
}
// And every reader reads both forms: a kept reference and the seat's are fetched from the same place.
for reference, want := range map[string]string{
ArtifactStoreScheme + "gitea/server@" + digest: "10.0.0.1:5000/gitea/server@" + digest,
"container-image-registry/gitea/server@" + digest: "10.0.0.1:5000/gitea/server@" + digest,
ArtifactStoreScheme + "gitea/config/blobs/" + digest: "http://10.0.0.1:5000/v2/gitea/config/blobs/" + digest,
"object-registry/gitea/config@" + digest: "http://10.0.0.1:5000/v2/gitea/config/blobs/" + digest,
"registry.example/gitea/server@" + digest: "registry.example/gitea/server@" + digest,
"mesh-artifact-store/gitea/server@" + digest: "mesh-artifact-store/gitea/server@" + digest,
// An upstream copy under the holder's own naming, in both forms.
"container-image-registry/upstream/quay.io:443/org/app@" + digest: "10.0.0.1:5000/upstream/quay.io:443/org/app@" + digest,
ArtifactStoreScheme + "upstream/quay.io:443/org/app@" + digest: "10.0.0.1:5000/upstream/quay.io:443/org/app@" + digest,
} {
if got := seats.Routed(reference, "10.0.0.1:5000"); got != want {
t.Errorf("Routed(%q) = %q, want %q", reference, got, want)
}
}
// A build module names what it published in the seat's form, and reads it back as the seat it came through.
for _, seat := range seats.All() {
ref := SeatReference(seat, "gitea", "config", digest)
if got, path, ok := seats.InRegistrySeat(ref); !ok || got != seat || path != "gitea/config@"+digest {
t.Errorf("SeatReference(%s) = %q, read back as %q %q %v", seat, ref, got, path, ok)
}
}
// The holder's naming of an upstream copy reads through the image seat only, and InUpstream reads its host and path.
for reference, want := range map[string]bool{
"container-image-registry/upstream/quay.io:443/org/app@" + digest: true,
"object-registry/upstream/quay.io:443/org/app@" + digest: false,
"container-image-registry/upstream/org/app@" + digest: false,
"container-image-registry/upstream/quay.io/app": false,
} {
_, path, ok := seats.InRegistrySeat(reference)
if ok != want {
t.Errorf("InRegistrySeat(%q) read %v, want %v", reference, ok, want)
}
if host, image, up := InUpstream(path); ok && (!up || host != "quay.io:443" || image != "org/app@"+digest) {
t.Errorf("InUpstream(%q) = %q %q %v", path, host, image, up)
}
}
}
@@ -1,63 +0,0 @@
// 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-delivery/sdk/parts"
)
// Call asks one verb of the seat, `seat:<seat>.<verb>`, 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
}
@@ -1,57 +0,0 @@
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-delivery/sdk/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)
}
}
@@ -1,18 +0,0 @@
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-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
)
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
)
@@ -1,12 +0,0 @@
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=
@@ -1,74 +0,0 @@
package meshcatalogclient
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"regexp"
"sort"
"strings"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// FingerprintSet is the `fpset` of a candidate's pull requests: the first 12 hex digits of the sha256 over the lines
// `<module>=<fp2>` or `<module>=none:<reason>` of every module with an entry in a pull's fps, sorted and joined by a newline,
// so it is the same for every order and changes with any fingerprint or reason. A manifest digest does not enter: the
// fingerprint already covers the manifest. It is the empty string when no pull request carries a fingerprint. The checker
// keys its verdict by it.
func FingerprintSet(pulls []seat.CandidatesByGroupValuePullsItem) string {
lines := map[string]bool{}
for _, p := range pulls {
for module, m := range p.Fps {
if m.Fp2 != "" {
lines[module+"="+m.Fp2] = true
} else {
lines[module+"=none:"+m.None2] = true
}
}
}
if len(lines) == 0 {
return ""
}
all := make([]string, 0, len(lines))
for l := range lines {
all = append(all, l)
}
sort.Strings(all)
sum := sha256.Sum256([]byte(strings.Join(all, "\n")))
return hex.EncodeToString(sum[:])[:12]
}
// CandidatesKey is the key, in the state candidates, of a group: `<group>` escaped (keys.EscapeName), the branch name the
// group's pull requests share (a slash is allowed); a lone pull request is a group of one under its branch name.
func CandidatesKey(group string) (string, error) {
if group == "" {
return "", fmt.Errorf("meshcatalogclient: the group name is empty")
}
return seat.CandidatesByGroupKey(keys.EscapeName(group)), nil
}
// ParseCandidatesKey is the inverse of CandidatesKey.
func ParseCandidatesKey(key string) (string, error) {
part, ok := seat.ParseCandidatesByGroupKey(key)
if !ok {
return "", fmt.Errorf("meshcatalogclient: %q is not a candidates key", key)
}
group, err := keys.UnescapeName(part)
if err != nil {
return "", fmt.Errorf("meshcatalogclient: %q is not a candidates key: %w", key, err)
}
return group, nil
}
var tryGroupOK = regexp.MustCompile(`^try-[0-9]{1,20}$`)
// IsTryGroup says a candidates key is the group of a whole try, try-<digits>, which is also the try's id: the indexer makes the
// candidate of a try's group from its previews, and the checker judges it by its try, never as a pull request's check.
func IsTryGroup(key string) bool { return tryGroupOK.MatchString(key) }
// NoneNoPreview is why a candidate's touched module has no fingerprint of the new form yet: its merge preview is not
// computed. The indexer writes it; a reader that waits for a try's candidate takes one that still says it as not yet the
// candidate of the heads previewed.
const NoneNoPreview = "no merge preview is computed"
@@ -1,167 +0,0 @@
package meshcatalogclient
import (
"errors"
"strings"
"testing"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/keys"
)
func hex64(c string) string { return strings.Repeat(c, 64) }
// The digest and the candidate's fingerprint set equal what the trunk's go/delivery computed for the same input (the
// expected strings are its output), so a key or a verdict written before is read after.
func TestDigestAndFingerprintSetKeepTheirForms(t *testing.T) {
if got := ManifestDigest([]byte(`{"module":"nats"}`)); got != "19a7450f557153c318558acac91cd6972321a192f6f95c3698a0e4c57edd4359" {
t.Errorf("ManifestDigest = %s", got)
}
pull := func(fps map[string]seat.CandidatesByGroupValuePullsItemFpsValue) []seat.CandidatesByGroupValuePullsItem {
return []seat.CandidatesByGroupValuePullsItem{{Fps: fps}}
}
fps := map[string]seat.CandidatesByGroupValuePullsItemFpsValue{"nats": {Fp2: SourcePrefix2 + hex64("a")}, "zsh": {None2: "no manifest"}}
set := FingerprintSet(pull(fps))
if set != "50a3f57cc4e2" {
t.Errorf("FingerprintSet = %s", set)
}
fps["zsh"] = seat.CandidatesByGroupValuePullsItemFpsValue{None2: "another reason"}
if FingerprintSet(pull(fps)) == set {
t.Error("a changed reason leaves the set as it was")
}
if FingerprintSet(pull(nil)) != "" {
t.Error("no fingerprint gives a set")
}
}
// A manifest and a module are read back as written, and each rule of the old values is kept: a refusal names its reason.
func TestManifestAndModuleValuesKeepTheirRules(t *testing.T) {
if _, err := ManifestsKey("abc"); err == nil || !strings.Contains(err.Error(), `"abc"`) {
t.Errorf("ManifestsKey(abc) = %v", err)
}
if k, err := ParseManifestsKey(hex64("b")); err != nil || k != hex64("b") {
t.Errorf("ParseManifestsKey = %q, %v", k, err)
}
raw, err := EncodeManifest(seat.ManifestsByDigestValue{Module: "nats", Manifest: `{"module":"nats"}`})
if err != nil {
t.Fatal(err)
}
if m, err := DecodeManifest(raw); err != nil || m.Module != "nats" || m.V != keys.Version {
t.Errorf("DecodeManifest = %+v, %v", m, err)
}
for _, bad := range []seat.ManifestsByDigestValue{{Module: "a.b", Manifest: "{}"}, {Module: "nats"}, {Module: "nats", Manifest: "not json"}, {Module: "nats", Manifest: "{\"a\":\"\xff\"}"}} {
if _, err := EncodeManifest(bad); err == nil {
t.Errorf("EncodeManifest(%+v) is accepted", bad)
}
}
if _, err := DecodeManifest([]byte(`{"module":"nats","manifest":"{}"}`)); !errors.Is(err, keys.ErrUnknownVersion) {
t.Errorf("a manifest with no version: %v", err)
}
good := seat.ModulesByModuleValue{Fp2: SourcePrefix2 + hex64("a"), Repository: "novox/mesh-catalog", Commit: "c", Manifest: hex64("b"), Path: RootPath}
raw, err = EncodeModule(good)
if err != nil {
t.Fatal(err)
}
if m, err := DecodeModule(raw); err != nil || m.Fp2 != good.Fp2 || m.Path != RootPath {
t.Errorf("DecodeModule = %+v, %v", m, err)
}
refused := good
refused.Verdict, refused.Problems = VerdictRefused, []string{"p"}
if _, err := EncodeModule(refused); err != nil {
t.Errorf("a refused module: %v", err)
}
for name, mutate := range map[string]func(m *seat.ModulesByModuleValue){
"neither a fingerprint nor a reason": func(m *seat.ModulesByModuleValue) { m.Fp2 = "" },
"both fp2 and none2": func(m *seat.ModulesByModuleValue) { m.None2 = "x" },
"an old fingerprint of another form": func(m *seat.ModulesByModuleValue) { m.Fp = "src2:" + hex64("a") },
"a fingerprint of the old key form": func(m *seat.ModulesByModuleValue) { m.Fp2 = "src1:" + hex64("a") },
"inputs and no manifest": func(m *seat.ModulesByModuleValue) {
m.Manifest = ""
m.Inputs2 = &seat.ModulesByModuleValueInputs2{Tree: "t"}
},
"a refusal with no problem": func(m *seat.ModulesByModuleValue) { m.Verdict = VerdictRefused },
"problems with no refusal": func(m *seat.ModulesByModuleValue) { m.Problems = []string{"p"} },
"unknown fields with no refusal": func(m *seat.ModulesByModuleValue) { m.Unknown = []string{"series"} },
"a path outside the directory": func(m *seat.ModulesByModuleValue) { m.Paths = map[string][]string{"tools": {"../x"}} },
"a toolchain with no recipe": func(m *seat.ModulesByModuleValue) {
m.Toolchains = map[string]seat.ToolchainUsed{"go": {Language: "go", Version: "1.26"}}
},
"an import form with no imports": func(m *seat.ModulesByModuleValue) {
m.Inputs2 = &seat.ModulesByModuleValueInputs2{Tree: "t", ImportForm: SourcePrefix2 + hex64("a")}
},
} {
m := good
mutate(&m)
if _, err := EncodeModule(m); err == nil {
t.Errorf("%s: EncodeModule accepts %+v", name, m)
}
}
}
// A verdict is taken as the readers have always taken it: a missing one accepted, a refusal held with its problems, a wait
// held with the fields it waits on, and a module's own verdict field gives the same.
func TestVerdictsAreTakenAsTheReadersTookThem(t *testing.T) {
if refused, problems := TakeVerdict(nil); refused || problems != nil {
t.Errorf("no verdict: %v %v", refused, problems)
}
waits := seat.VerdictsByDigestValue{Module: "nats", State: VerdictManifestWaits, Unknown: []string{"series"}, Judge: "j"}
refused, problems := TakeVerdict(&waits)
if !refused || len(problems) != 1 || problems[0] != "it carries series, which the indexer's judge does not know yet" {
t.Errorf("a wait: %v %v", refused, problems)
}
if got := VerdictWhy(waits); got != "the indexer's judge (j) holds the manifest of nats: "+problems[0] {
t.Errorf("VerdictWhy(wait) = %q", got)
}
if as := VerdictAsRefused(waits); as.State != VerdictRefused || len(as.Unknown) != 0 || as.Problems[0] != problems[0] {
t.Errorf("VerdictAsRefused = %+v", as)
}
no := seat.VerdictsByDigestValue{Module: "nats", State: VerdictRefused, Problems: []string{"a", "b"}, Judge: "j"}
if got := VerdictWhy(no); got != "the indexer's judge (j) refused the manifest of nats: a; b" {
t.Errorf("VerdictWhy(refused) = %q", got)
}
if !SameVerdict(no, seat.VerdictsByDigestValue{Module: "nats", State: VerdictRefused, Problems: []string{"a", "b"}, Judge: "k"}) {
t.Error("another judge is another verdict")
}
fromModule := ModuleVerdict("nats", seat.ModulesByModuleValue{Verdict: VerdictRefused, Unknown: []string{"series"}, Problems: []string{"x"}})
if fromModule == nil || fromModule.State != VerdictManifestWaits || fromModule.Unknown[0] != "series" {
t.Errorf("a refusal naming unknown fields is the wait it was written as: %+v", fromModule)
}
if ModuleVerdict("nats", seat.ModulesByModuleValue{}) != nil {
t.Error("a module with no verdict carries one")
}
for _, bad := range []seat.VerdictsByDigestValue{{Module: "nats", State: "accepted", Problems: []string{"p"}, Judge: "j"}, {Module: "nats", State: "refused", Judge: "j"}, {Module: "nats", State: "accepted"}, {Module: "nats", State: "odd", Judge: "j"}} {
if _, err := EncodeVerdict(bad); err == nil {
t.Errorf("EncodeVerdict(%+v) is accepted", bad)
}
}
raw, err := EncodeVerdict(no)
if err != nil {
t.Fatal(err)
}
if v, err := DecodeVerdict(raw); err != nil || !SameVerdict(v, no) {
t.Errorf("DecodeVerdict = %+v, %v", v, err)
}
if k, err := VerdictsKey(hex64("c")); err != nil || k != hex64("c") {
t.Errorf("VerdictsKey = %q %v", k, err)
}
if _, err := ModulesKey("a.b"); err == nil {
t.Error("ModulesKey accepts a dotted name")
}
}
func TestCandidatesKeyEscapesTheGroupAndReadsBack(t *testing.T) {
key, err := CandidatesKey("fix/1.2")
if err != nil {
t.Fatal(err)
}
if key == "fix/1.2" {
t.Errorf("the group is not escaped: %q", key)
}
if group, err := ParseCandidatesKey(key); err != nil || group != "fix/1.2" {
t.Errorf("read back = %q, %v", group, err)
}
if _, err := CandidatesKey(""); err == nil {
t.Error("an empty group keys")
}
}
@@ -1,41 +0,0 @@
// Package meshcatalogclient holds the logic the holders and callers of the seat mesh-catalog share over its contract (novox/hq
// ADR 0625 §4, ADR 0634 §2): the form of a module's source fingerprint, `src2:` and 64 lowercase hex digits (ADR 0593 §2),
// over the `fp2` field of the seat's `modules` value. The indexer computes a fingerprint alone, in its own package; every part
// that keys a state by it, the node-engine among them, reads this form here.
package meshcatalogclient
import (
"fmt"
"strings"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// SourcePrefix2 opens every source fingerprint of the form.
const SourcePrefix2 = "src2:"
// Toolchain2 is what an artifact's toolchain enters the fingerprint as (novox/hq ADR 0405 clause 7): the artifact's
// language, the installed toolchain's version and the build module's recipe version.
type Toolchain2 struct {
Language string
Version string
Recipe string
}
// KeyPart is a fingerprint as a key part: its 64 hex digits without the prefix, since a bus key may not hold ':'. It
// refuses a value that is not `src2:` and 64 lowercase hex digits, a src1 fingerprint included, naming it.
func KeyPart(fp string) (string, error) {
part, ok := strings.CutPrefix(fp, SourcePrefix2)
if !ok || !keys.IsHex64(part) {
return "", fmt.Errorf("fingerprint: %q is not a fingerprint of the form, %s and 64 lowercase hex digits", fp, SourcePrefix2)
}
return part, nil
}
// ParseKeyPart is the inverse of KeyPart. It refuses a part that is not 64 lowercase hex digits, naming it.
func ParseKeyPart(part string) (string, error) {
if !keys.IsHex64(part) {
return "", fmt.Errorf("fingerprint: %q is not the key part of a fingerprint, 64 lowercase hex digits", part)
}
return SourcePrefix2 + part, nil
}
@@ -1,34 +0,0 @@
package meshcatalogclient
import (
"strings"
"testing"
)
// The key part holds only bus key characters and parsing gives the fingerprint back; a src1 or malformed value is refused,
// naming it.
func TestFingerprintKeyPart(t *testing.T) {
fp := SourcePrefix2 + strings.Repeat("ab", 32)
part, err := KeyPart(fp)
if err != nil {
t.Fatal(err)
}
if len(part) != 64 || strings.Trim(part, "0123456789abcdef") != "" {
t.Errorf("key part %q is not 64 lowercase hex characters", part)
}
back, err := ParseKeyPart(part)
if err != nil || back != fp {
t.Errorf("parse(%q) = %q, %v; want %q", part, back, err, fp)
}
src1 := "src1:" + strings.Repeat("c", 64)
for _, bad := range []string{src1, "src2:", "src2:xyz", "", "src2:" + strings.Repeat("A", 64), "src2:" + strings.Repeat("a", 63)} {
if _, err := KeyPart(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
t.Errorf("KeyPart(%q) = %v, want a refusal naming it", bad, err)
}
}
for _, bad := range []string{src1, "src2:" + strings.Repeat("a", 64), strings.Repeat("a", 63), strings.Repeat("A", 64), "", "zz"} {
if _, err := ParseKeyPart(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
t.Errorf("ParseKeyPart(%q) = %v, want a refusal naming it", bad, err)
}
}
}
@@ -1,17 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient
go 1.26.0
require (
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
)
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
)
@@ -1,12 +0,0 @@
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=
@@ -1,79 +0,0 @@
package meshcatalogclient
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"unicode/utf8"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// ManifestDigest is the hex sha256 of a manifest's bytes exactly as read at the commit: the key of a `manifests` value and
// the digest a `modules` value and a candidate's module carry (novox/hq ADR 0429 clause 16).
func ManifestDigest(raw []byte) string {
sum := sha256.Sum256(raw)
return hex.EncodeToString(sum[:])
}
// ManifestsKey is the key of a `manifests` value: the manifest's digest, 64 lowercase hex digits. The indexer writes it
// once, by a create that fails when the key exists.
func ManifestsKey(digest string) (string, error) {
if !keys.IsHex64(digest) {
return "", fmt.Errorf("meshcatalogclient: %q is not a manifest digest, 64 lowercase hex digits", digest)
}
return seat.ManifestsByDigestKey(digest), nil
}
// ParseManifestsKey is the inverse of ManifestsKey.
func ParseManifestsKey(key string) (string, error) {
digest, err := ManifestsKey(key)
if err != nil {
return "", fmt.Errorf("meshcatalogclient: %q is not a manifests key: %w", key, err)
}
return digest, nil
}
func checkManifest(m seat.ManifestsByDigestValue) error {
if err := keys.CheckName("module", m.Module); err != nil {
return err
}
if m.Manifest == "" {
return fmt.Errorf("meshcatalogclient: the manifest of %s is empty", m.Module)
}
if !utf8.ValidString(m.Manifest) {
return fmt.Errorf("meshcatalogclient: the manifest of %s is not UTF-8 text, which a value cannot hold byte for byte", m.Module)
}
if !json.Valid([]byte(m.Manifest)) {
return fmt.Errorf("meshcatalogclient: the manifest of %s is not JSON", m.Module)
}
return nil
}
// EncodeManifest is m as JSON at the kept values' version; it refuses a module that is not a name and a manifest that is
// empty, not UTF-8 or not JSON.
func EncodeManifest(m seat.ManifestsByDigestValue) ([]byte, error) {
if err := checkManifest(m); err != nil {
return nil, err
}
m.V = keys.Version
return json.Marshal(m)
}
// DecodeManifest reads a `manifests` value; it refuses an unknown version (keys.ErrUnknownVersion) and what EncodeManifest
// refuses.
func DecodeManifest(data []byte) (seat.ManifestsByDigestValue, error) {
if err := keys.CheckVersion(data); err != nil {
return seat.ManifestsByDigestValue{}, err
}
var m seat.ManifestsByDigestValue
if err := json.Unmarshal(data, &m); err != nil {
return seat.ManifestsByDigestValue{}, fmt.Errorf("meshcatalogclient: value does not fit its shape: %w", err)
}
if err := checkManifest(m); err != nil {
return seat.ManifestsByDigestValue{}, err
}
return m, nil
}
@@ -1,133 +0,0 @@
package meshcatalogclient
import (
"encoding/json"
"fmt"
"path"
"strings"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// RootPath is a `modules` value's path when the module's manifest is its repository's root.
const RootPath = "."
// SourcePrefix1 opens the fingerprint of the old form a `modules` value may still carry in `fp`.
const SourcePrefix1 = "src1:"
// ModulesKey is the key of a `modules` value: the module's name.
func ModulesKey(module string) (string, error) {
if err := keys.CheckName("module", module); err != nil {
return "", err
}
return seat.ModulesByModuleKey(module), nil
}
// ParseModulesKey is the inverse of ModulesKey.
func ParseModulesKey(key string) (string, error) {
module, err := ModulesKey(key)
if err != nil {
return "", fmt.Errorf("meshcatalogclient: %q is not a modules key: %w", key, err)
}
return module, nil
}
// CheckModule refuses a `modules` value that contradicts itself: both or neither of a fingerprint and the reason for none,
// of either form, an old fingerprint not of the form `src1:`, kept inputs without a manifest digest, a verdict the indexer
// does not write, problems without a refusal (or a refusal without problems), a path that is not clean inside the module's
// directory, and a fingerprint or a digest not of its form.
func CheckModule(m seat.ModulesByModuleValue) error {
switch {
case m.Fp != "" && m.None != "":
return fmt.Errorf("meshcatalogclient: a module has both a fingerprint and a reason for none")
case m.Fp == "" && m.None == "" && m.Fp2 == "" && m.None2 == "":
return fmt.Errorf("meshcatalogclient: a module has neither a fingerprint nor a reason for none, of either form")
case m.Fp != "" && !strings.HasPrefix(m.Fp, SourcePrefix1):
return fmt.Errorf("meshcatalogclient: the module's fingerprint %q does not start with %s", m.Fp, SourcePrefix1)
case m.Fp2 != "" && m.None2 != "":
return fmt.Errorf("meshcatalogclient: a module has both fp2 and none2")
}
for language, t := range m.Toolchains {
if t.Version == "" || t.Recipe == "" {
return fmt.Errorf("meshcatalogclient: the module's toolchain of %q needs its version and its recipe version", language)
}
}
if m.Inputs2 != nil && m.Manifest == "" {
return fmt.Errorf("meshcatalogclient: a module keeps new-form inputs but has no manifest digest")
}
for artifact, paths := range m.Paths {
if artifact == "" || len(paths) == 0 {
return fmt.Errorf("meshcatalogclient: the module's named paths of the artifact %q are empty or name no artifact", artifact)
}
for _, p := range paths {
if !cleanInside(p) {
return fmt.Errorf("meshcatalogclient: the artifact %q names the path %q, not a clean path inside the module's directory", artifact, p)
}
}
}
if in := m.Inputs2; in != nil {
if f := in.ImportForm; f != "" && (len(in.Imports) == 0 || !strings.HasPrefix(f, SourcePrefix2) || !keys.IsHex64(f[len(SourcePrefix2):])) {
return fmt.Errorf("meshcatalogclient: the kept import form %q is set only with imports, and is %s and 64 hex digits", f, SourcePrefix2)
}
for p, object := range in.Paths {
if !cleanInside(p) {
return fmt.Errorf("meshcatalogclient: the named path %q is not a clean path inside the module's directory", p)
}
if object == "" {
return fmt.Errorf("meshcatalogclient: the named path %q names no git object", p)
}
}
}
switch {
case m.Verdict != "" && m.Verdict != VerdictAccepted && m.Verdict != VerdictRefused:
return fmt.Errorf("meshcatalogclient: the module's verdict %q is neither %s nor %s", m.Verdict, VerdictAccepted, VerdictRefused)
case len(m.Unknown) > 0 && m.Verdict != VerdictRefused:
return fmt.Errorf("meshcatalogclient: a module's unknown fields are named only with the refusal a wait is written as")
case m.Verdict != "" && m.Manifest == "":
return fmt.Errorf("meshcatalogclient: a module carries a verdict and no manifest digest")
case (m.Verdict == VerdictRefused) != (len(m.Problems) > 0):
return fmt.Errorf("meshcatalogclient: a module's problems are named only with a refusal, and a refusal names them")
case m.Verdict == VerdictRefused && len(m.Warnings) > 0:
return fmt.Errorf("meshcatalogclient: a refused module warns: a refusal names problems")
}
if m.Fp2 != "" {
if _, err := KeyPart(m.Fp2); err != nil {
return err
}
}
if m.Manifest != "" {
if _, err := ManifestsKey(m.Manifest); err != nil {
return err
}
}
return nil
}
func cleanInside(p string) bool {
return p != "" && p == path.Clean(p) && !strings.HasPrefix(p, "/") && p != ".." && !strings.HasPrefix(p, "../")
}
// EncodeModule is m as JSON at the kept values' version; it refuses what CheckModule refuses.
func EncodeModule(m seat.ModulesByModuleValue) ([]byte, error) {
if err := CheckModule(m); err != nil {
return nil, err
}
m.V = keys.Version
return json.Marshal(m)
}
// DecodeModule reads a `modules` value; it refuses an unknown version (keys.ErrUnknownVersion) and what EncodeModule refuses.
func DecodeModule(data []byte) (seat.ModulesByModuleValue, error) {
if err := keys.CheckVersion(data); err != nil {
return seat.ModulesByModuleValue{}, err
}
var m seat.ModulesByModuleValue
if err := json.Unmarshal(data, &m); err != nil {
return seat.ModulesByModuleValue{}, fmt.Errorf("meshcatalogclient: value does not fit its shape: %w", err)
}
if err := CheckModule(m); err != nil {
return seat.ModulesByModuleValue{}, err
}
return m, nil
}
@@ -1,168 +0,0 @@
package meshcatalogclient
import (
"encoding/json"
"fmt"
"slices"
"strings"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// The states of a verdict on a manifest (novox/hq ADR 0578 §1): the indexer judges every manifest it takes in once and
// every other part takes the verdict from the seat's state `verdicts`.
const (
// VerdictAccepted is the state of a manifest the judge found no problem in.
VerdictAccepted = "accepted"
// VerdictRefused is the state of a manifest the judge found a problem in; the verdict's problems say each.
VerdictRefused = "refused"
// VerdictManifestWaits is the state of a manifest that carries a field the judge does not know: it is not refused,
// because a newer judge may know the field; the verdict's unknown names each. A reader holds the module as a refused
// one and keeps what runs. In a `modules` value it is written as a refusal naming the fields.
VerdictManifestWaits = "waits"
)
// VerdictsKey is the key of a `verdicts` value: the digest of the manifest it judges.
func VerdictsKey(digest string) (string, error) {
if !keys.IsHex64(digest) {
return "", fmt.Errorf("meshcatalogclient: %q is not a manifest digest, 64 lowercase hex digits", digest)
}
return seat.VerdictsByDigestKey(digest), nil
}
// VerdictRefuses says whether v refuses its manifest.
func VerdictRefuses(v seat.VerdictsByDigestValue) bool { return v.State == VerdictRefused }
// VerdictWaits says whether v says the manifest waits for a judge that knows its fields.
func VerdictWaits(v seat.VerdictsByDigestValue) bool { return v.State == VerdictManifestWaits }
// VerdictHeld says whether a reader holds the module as refused: the manifest is refused or waits.
func VerdictHeld(v seat.VerdictsByDigestValue) bool { return VerdictRefuses(v) || VerdictWaits(v) }
// WaitsOn says, in words, what a waiting manifest waits on.
func WaitsOn(v seat.VerdictsByDigestValue) string {
return "it carries " + strings.Join(v.Unknown, ", ") + ", which the indexer's judge does not know yet"
}
// TakeVerdict is how a reader takes the verdict on a manifest (ADR 0578 §3): held, with its problems, when the indexer
// wrote a refusal, or, for a manifest that waits, with the words that say which fields it waits on. A missing verdict (nil)
// is taken as accepted: every manifest the indexer holds before it has judged it was taken in under the judges that ran
// until then. A reader holds a refused module and names it with VerdictWhy, and reads the rest.
func TakeVerdict(v *seat.VerdictsByDigestValue) (refused bool, problems []string) {
if v == nil || !VerdictHeld(*v) {
return false, nil
}
if VerdictWaits(*v) {
return true, []string{WaitsOn(*v)}
}
return true, v.Problems
}
// VerdictWhy is a refusal in a reader's words: the module, that the indexer's judge refused its manifest, and each problem.
func VerdictWhy(v seat.VerdictsByDigestValue) string {
if !VerdictHeld(v) {
return ""
}
judge := "the indexer's judge"
if v.Judge != "" {
judge += " (" + v.Judge + ")"
}
if VerdictWaits(v) {
return fmt.Sprintf("%s holds the manifest of %s: %s", judge, v.Module, WaitsOn(v))
}
why := fmt.Sprintf("%s refused the manifest of %s:", judge, v.Module)
for _, p := range v.Problems {
why += " " + p + ";"
}
return why[:len(why)-1]
}
// VerdictAsRefused is v as a reader that does not know the waiting state takes it: a waiting verdict is a refusal naming what
// it waits on, any other verdict is v.
func VerdictAsRefused(v seat.VerdictsByDigestValue) seat.VerdictsByDigestValue {
if !VerdictWaits(v) {
return v
}
v.State, v.Problems, v.Unknown = VerdictRefused, []string{WaitsOn(v)}, nil
return v
}
// SameVerdict says whether v and o say the same of a manifest: the module, the state, the problems, the unknown fields and
// the warnings, whoever judged and when. The indexer writes a verdict only when it is not the same as the one it holds.
func SameVerdict(v, o seat.VerdictsByDigestValue) bool {
return v.Module == o.Module && v.State == o.State && slices.Equal(v.Problems, o.Problems) && slices.Equal(v.Unknown, o.Unknown) && slices.Equal(v.Warnings, o.Warnings)
}
func checkVerdict(v seat.VerdictsByDigestValue) error {
if err := keys.CheckName("module", v.Module); err != nil {
return err
}
if v.State != VerdictManifestWaits && len(v.Unknown) > 0 {
return fmt.Errorf("meshcatalogclient: the verdict on %s names unknown fields and does not wait", v.Module)
}
switch v.State {
case VerdictAccepted:
if len(v.Problems) > 0 {
return fmt.Errorf("meshcatalogclient: the verdict on %s accepts its manifest and names problems", v.Module)
}
case VerdictRefused:
if len(v.Problems) == 0 {
return fmt.Errorf("meshcatalogclient: the verdict on %s refuses its manifest and names no problem", v.Module)
}
if len(v.Warnings) > 0 {
return fmt.Errorf("meshcatalogclient: the verdict on %s refuses its manifest and warns: a refusal names problems", v.Module)
}
case VerdictManifestWaits:
if len(v.Unknown) == 0 || len(v.Problems) > 0 || len(v.Warnings) > 0 {
return fmt.Errorf("meshcatalogclient: the verdict on %s waits and names no unknown field, or names problems or warnings: a wait names the fields", v.Module)
}
default:
return fmt.Errorf("meshcatalogclient: the verdict on %s is %q, none of %s, %s and %s", v.Module, v.State, VerdictAccepted, VerdictRefused, VerdictManifestWaits)
}
if v.Judge == "" {
return fmt.Errorf("meshcatalogclient: the verdict on %s names no judge", v.Module)
}
return nil
}
// EncodeVerdict is v as JSON at the kept values' version; it refuses a verdict without a module or a judge, a state other
// than accepted, refused or waits, a refusal naming no problem and an acceptance naming one.
func EncodeVerdict(v seat.VerdictsByDigestValue) ([]byte, error) {
if err := checkVerdict(v); err != nil {
return nil, err
}
v.V = keys.Version
return json.Marshal(v)
}
// DecodeVerdict reads a `verdicts` value; it refuses an unknown version (keys.ErrUnknownVersion) and what EncodeVerdict
// refuses.
func DecodeVerdict(data []byte) (seat.VerdictsByDigestValue, error) {
if err := keys.CheckVersion(data); err != nil {
return seat.VerdictsByDigestValue{}, err
}
var v seat.VerdictsByDigestValue
if err := json.Unmarshal(data, &v); err != nil {
return seat.VerdictsByDigestValue{}, fmt.Errorf("meshcatalogclient: value does not fit its shape: %w", err)
}
if err := checkVerdict(v); err != nil {
return seat.VerdictsByDigestValue{}, err
}
return v, nil
}
// ModuleVerdict is the verdict a `modules` value carries, or nil when it carries none (taken as accepted, TakeVerdict). A
// refusal naming unknown fields is the wait it was written as.
func ModuleVerdict(module string, m seat.ModulesByModuleValue) *seat.VerdictsByDigestValue {
if m.Verdict == "" && len(m.Warnings) == 0 {
return nil
}
if m.Verdict == "" {
return &seat.VerdictsByDigestValue{Module: module, State: VerdictAccepted, Warnings: m.Warnings}
}
if m.Verdict == VerdictRefused && len(m.Unknown) > 0 {
return &seat.VerdictsByDigestValue{Module: module, State: VerdictManifestWaits, Unknown: m.Unknown}
}
return &seat.VerdictsByDigestValue{Module: module, State: m.Verdict, Problems: m.Problems}
}
@@ -1,312 +0,0 @@
// Package meshdeliverycheckerclient holds the logic the holders and callers of the seat mesh-delivery-checker share over its
// contract (novox/hq ADR 0625 §4, ADR 0634 §2): the rules a verdict and a kept verdict must keep to be written or read. The
// checker writes them, and the view, the gate and the indexer read them; the contract's decoding is strict by schema only, so
// these rules are written once here and no caller copies them.
package meshdeliverycheckerclient
import (
"fmt"
pathpkg "path"
"strings"
checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// The states of a verdict, the levels of a check and the compositions' states.
const (
VerdictRunning = "running"
VerdictPass = "pass"
VerdictFail = "fail"
LevelUnit = "unit"
LevelIntegration = "integration"
CompositionNotRun = "not-run"
CompositionPass = "pass"
CompositionFail = "fail"
)
// MaxFailures is the most failures a verdict names; it names the count of the rest beside them.
const MaxFailures = 50
func checkLevel(level string) error {
if level != LevelUnit && level != LevelIntegration {
return fmt.Errorf("meshdeliverycheckerclient: the level %q is not %s or %s", level, LevelUnit, LevelIntegration)
}
return nil
}
func isHex12(s string) bool { return len(s) == 12 && strings.Trim(s, "0123456789abcdef") == "" }
// CheckRepository refuses a repository that is not <owner>/<name> with exactly one slash, each part a key part.
func CheckRepository(repository string) error {
owner, name, ok := strings.Cut(repository, "/")
if !ok || strings.Contains(name, "/") {
return fmt.Errorf("meshdeliverycheckerclient: the repository %q is not <owner>/<name>, with exactly one slash", repository)
}
if err := keys.CheckName("repository owner", owner); err != nil {
return fmt.Errorf("meshdeliverycheckerclient: the repository %q: %w", repository, err)
}
if err := keys.CheckName("repository name", name); err != nil {
return fmt.Errorf("meshdeliverycheckerclient: the repository %q: %w", repository, err)
}
return nil
}
func checkComposition(c *checker.VerdictsByGroupHeadsValueComposition) error {
if c == nil {
return nil
}
switch c.State {
case CompositionNotRun:
if c.Sdk != "" || len(c.Machines) > 0 {
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition is not run and names no mesh-sdk commit or machines")
}
case CompositionPass:
if c.Sdk == "" {
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition passed and names no mesh-sdk commit")
}
case CompositionFail:
if c.Words == "" {
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition failed and gives no words")
}
default:
return fmt.Errorf("meshdeliverycheckerclient: the verdict's composition state %q is not %s, %s or %s", c.State, CompositionNotRun, CompositionPass, CompositionFail)
}
return nil
}
// CheckVerdict refuses a verdict a reader could not place: no group, heads that are not 12 lowercase hex digits, a repository
// both merged and read beside, a carried-from that carries nothing, a series change from a series to itself or named twice, a
// level that is neither unit nor integration or counts a negative number, a time below zero, a pass that says nothing of a
// level, a fail that names neither a test nor a machine, and failures on a verdict that did not fail.
func CheckVerdict(v checker.VerdictsByGroupHeadsValue) error {
const who = "meshdeliverycheckerclient: "
if v.Group == "" {
return fmt.Errorf(who + "a verdict names no group")
}
if !isHex12(v.Heads) {
return fmt.Errorf(who+"the verdict's heads hash %q is not 12 lowercase hex digits", v.Heads)
}
for repository, head := range v.Trunk {
if head == "" {
return fmt.Errorf(who+"the verdict's trunk head of %s is empty", repository)
}
}
for repository, commit := range v.Beside {
if commit == "" {
return fmt.Errorf(who+"the verdict's sibling commit of %s is empty", repository)
}
if _, ok := v.Trunk[repository]; ok {
return fmt.Errorf(who+"the verdict names %s as a trunk it merged and as a sibling it read", repository)
}
}
if len(v.CarriedFrom) > 0 {
if v.State == VerdictRunning {
return fmt.Errorf(who + "a running verdict is carried from nothing: carried-from is written with its end")
}
moved := false
for repository, commit := range v.CarriedFrom {
now, inTrunk := v.Trunk[repository]
if !inTrunk {
now = v.Beside[repository]
}
if commit == "" || now == "" {
return fmt.Errorf(who+"the verdict's carried-from names %s, which it judged on no commit of", repository)
}
moved = moved || commit != now
}
if !moved {
return fmt.Errorf(who + "the verdict's carried-from names the commits it is judged on: nothing moved, so nothing was carried")
}
}
changed := map[string]bool{}
for _, c := range v.Series {
if c.Module == "" || c.Artifact == "" || c.From == "" || c.To == "" {
return fmt.Errorf(who+"the verdict's series change %+v names no module, artifact, from or to", c)
}
if c.From == c.To {
return fmt.Errorf(who+"the verdict's series change of %s/%s is from %s to the same series", c.Module, c.Artifact, c.From)
}
if changed[c.Module+"/"+c.Artifact] {
return fmt.Errorf(who+"the verdict names the series change of %s/%s twice", c.Module, c.Artifact)
}
changed[c.Module+"/"+c.Artifact] = true
}
for level, r := range v.Levels {
if err := checkLevel(level); err != nil {
return err
}
if r.Run < 0 || r.Kept < 0 || r.Skipped < 0 {
return fmt.Errorf(who+"the verdict's %s level counts a negative number of tests", level)
}
}
if err := checkComposition(v.Composition); err != nil {
return err
}
seen := map[string]bool{}
for _, level := range v.OverBound {
if err := checkLevel(level); err != nil {
return fmt.Errorf(who+"the verdict's over-bound names: %w", err)
}
if seen[level] {
return fmt.Errorf(who+"the verdict's over-bound names the level %s twice", level)
}
seen[level] = true
}
if t := v.Took; t != nil {
for part, ms := range map[string]int64{"queue": t.Queue, "clone": t.Clone, "unit": t.Unit, "lint": t.Lint,
"integration-setup": t.IntegrationSetup, "integration-run": t.IntegrationRun, "composition": t.Composition} {
if ms < 0 {
return fmt.Errorf(who+"the verdict's time of the part %s is negative", part)
}
}
}
named := v.Failed != nil && (v.Failed.Test != "" || v.Failed.Machine != "")
if len(v.Failures) > 0 && v.State != VerdictFail {
return fmt.Errorf(who+"a %s verdict names no failures", v.State)
}
if len(v.Failures) > MaxFailures+1 {
return fmt.Errorf(who+"the verdict names %d failures, more than %d and the count of the rest", len(v.Failures), MaxFailures)
}
switch v.State {
case VerdictRunning:
if v.Failed != nil && *v.Failed != (checker.VerdictsByGroupHeadsValueFailed{}) {
return fmt.Errorf(who + "a running verdict names no failure")
}
case VerdictPass:
if v.Failed != nil && *v.Failed != (checker.VerdictsByGroupHeadsValueFailed{}) {
return fmt.Errorf(who + "a pass names no failure")
}
for _, level := range []string{LevelUnit, LevelIntegration} {
if _, ok := v.Levels[level]; !ok {
return fmt.Errorf(who+"a pass says nothing of the %s level", level)
}
}
case VerdictFail:
if !named {
return fmt.Errorf(who + "a fail names neither a test nor a machine")
}
default:
return fmt.Errorf(who+"the verdict state %q is not running, pass or fail", v.State)
}
return nil
}
// CheckKept refuses a kept verdict that is not a pass of a repository's package at a level, with its toolchain and its time:
// a failure is never kept.
func CheckKept(k checker.KeptByLevelDigestValue) error {
const who = "meshdeliverycheckerclient: "
if k.State != VerdictPass {
return fmt.Errorf(who+"a kept verdict is a pass, never %q: a failure is never kept", k.State)
}
if err := CheckRepository(k.Repository); err != nil {
return err
}
if k.Package == "" {
return fmt.Errorf(who + "a kept verdict names no package")
}
if err := checkLevel(k.Level); err != nil {
return err
}
if k.Tests < 0 || k.Took < 0 {
return fmt.Errorf(who + "a kept verdict counts a negative number of tests or a negative time")
}
if k.Toolchain == "" {
return fmt.Errorf(who + "a kept verdict names no toolchain version")
}
if k.At == "" {
return fmt.Errorf(who + "a kept verdict says no time")
}
return nil
}
// CheckPreview refuses a preview a reader could not place (the checker writes it, the indexer reads it for the candidates'
// fingerprints): a repository, head or trunk commit that does not key, a head in conflict that names no file or has a merge
// commit or modules, a head that merged and names no merge commit, and a module with no directory, no tree, a manifest
// digest that is not 64 lowercase hex digits, a lock without path or hash, or a named path that is not clean and inside the
// module's directory or names no git object.
func CheckPreview(p checker.PreviewsByRepositoryHeadTrunkValue) error {
const who = "meshdeliverycheckerclient: "
if err := CheckRepository(p.Repository); err != nil {
return err
}
if err := keys.CheckName("head", p.Head); err != nil {
return fmt.Errorf(who+"the preview: %w", err)
}
if err := keys.CheckName("trunk commit", p.Trunk); err != nil {
return fmt.Errorf(who+"the preview: %w", err)
}
for _, f := range p.Conflicts {
if f == "" {
return fmt.Errorf(who + "a preview names a conflicting file with an empty path")
}
}
if len(p.Conflicts) > 0 {
if p.Merge != "" || len(p.Modules) > 0 {
return fmt.Errorf(who + "a preview of a head in conflict has no merge commit and no modules")
}
return nil
}
if p.Merge == "" {
return fmt.Errorf(who + "a preview names no merge commit")
}
for dir, m := range p.Modules {
if dir == "" {
return fmt.Errorf(who + "a preview names a module with an empty directory")
}
if m.Tree == "" {
return fmt.Errorf(who+"the preview's module %q has no tree", dir)
}
if !keys.IsHex64(m.Manifest) {
return fmt.Errorf(who+"the preview's module %q: %q is not a manifest digest, 64 lowercase hex digits", dir, m.Manifest)
}
for path, hash := range m.Locks {
if path == "" || hash == "" {
return fmt.Errorf(who+"the lock %q of the preview's module %q has an empty path or hash", path, dir)
}
}
for p, object := range m.Paths {
if p == "" || p != pathpkg.Clean(p) || strings.HasPrefix(p, "/") || p == ".." || strings.HasPrefix(p, "../") {
return fmt.Errorf(who+"the preview's module %q: the named path %q is not a clean path inside the module's directory", dir, p)
}
if object == "" {
return fmt.Errorf(who+"the preview's module %q: the named path %q names no git object", dir, p)
}
}
}
return nil
}
// VerdictKey is the key, in the state verdicts, of a group's check at its heads: `<escaped group>.<heads hash>`, the
// group escaped so a branch such as `fix/1.2` keys, the heads hash as the mesh-delivery client's HeadsHash gives it
// (12 lowercase hex digits).
func VerdictKey(group, headsHash string) (string, error) {
if group == "" {
return "", fmt.Errorf("meshdeliverycheckerclient: the group name is empty")
}
if !isHex12(headsHash) {
return "", fmt.Errorf("meshdeliverycheckerclient: %q is not a heads hash, 12 lowercase hex digits", headsHash)
}
return keys.EscapeName(group) + "." + headsHash, nil
}
// ParseVerdictKey is the inverse of VerdictKey.
func ParseVerdictKey(key string) (group, headsHash string, err error) {
i := strings.LastIndex(key, ".")
if i < 0 {
return "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a verdict key, <group>.<heads hash>", key)
}
part, headsHash := key[:i], key[i+1:]
if !isHex12(headsHash) {
return "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a verdict key: %q is not a heads hash, 12 lowercase hex digits", key, headsHash)
}
if part == "" || strings.Contains(part, ".") {
return "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a verdict key: the group part is empty or holds a dot", key)
}
if group, err = keys.UnescapeName(part); err != nil {
return "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a verdict key: %w", key, err)
}
return group, headsHash, nil
}
@@ -1,223 +0,0 @@
package meshdeliverycheckerclient
import (
"strings"
"testing"
checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
)
type verdict = checker.VerdictsByGroupHeadsValue
func passing() verdict {
return verdict{Group: "g", Heads: "0123456789ab", State: VerdictPass, Trunk: map[string]string{"novox/a": "c1"},
Levels: map[string]checker.VerdictsByGroupHeadsValueLevelsValue{LevelUnit: {Run: 3}, LevelIntegration: {Run: 1}}}
}
// The rules a verdict keeps to be written or read: each case edits a passing verdict and names what the refusal says.
func TestAVerdictIsRefusedForWhatAReaderCouldNotPlace(t *testing.T) {
if err := CheckVerdict(passing()); err != nil {
t.Fatalf("a passing verdict is refused: %v", err)
}
for name, c := range map[string]struct {
edit func(*verdict)
says string
}{
"no group": {func(v *verdict) { v.Group = "" }, "no group"},
"heads of 11 digits": {func(v *verdict) { v.Heads = "0123456789a" }, "12 lowercase hex"},
"an empty trunk head": {func(v *verdict) { v.Trunk["novox/a"] = "" }, "trunk head"},
"merged and read": {func(v *verdict) { v.Beside = map[string]string{"novox/a": "c2"} }, "trunk it merged and as a sibling"},
"nothing carried": {func(v *verdict) { v.CarriedFrom = map[string]string{"novox/a": "c1"} }, "nothing moved"},
"a running carry": {func(v *verdict) { v.State, v.CarriedFrom = VerdictRunning, map[string]string{"novox/a": "c0"} }, "carried from nothing"},
"a series to itself": {func(v *verdict) {
v.Series = []checker.VerdictsByGroupHeadsValueSeriesItem{{Module: "m", Artifact: "a", From: "0.1", To: "0.1"}}
}, "same series"},
"an unknown level": {func(v *verdict) { v.Levels["smoke"] = checker.VerdictsByGroupHeadsValueLevelsValue{} }, "smoke"},
"a negative count": {func(v *verdict) { v.Levels[LevelUnit] = checker.VerdictsByGroupHeadsValueLevelsValue{Run: -1} }, "negative"},
"a negative time": {func(v *verdict) { v.Took = &checker.VerdictsByGroupHeadsValueTook{Unit: -1} }, "negative"},
"a pass without a level": {func(v *verdict) { delete(v.Levels, LevelIntegration) }, "integration level"},
"a pass that failed": {func(v *verdict) { v.Failed = &checker.VerdictsByGroupHeadsValueFailed{Test: "T"} }, "names no failure"},
"failures of a pass": {func(v *verdict) { v.Failures = []checker.VerdictsByGroupHeadsValueFailuresItem{{Test: "T"}} }, "names no failures"},
"a fail naming nothing": {func(v *verdict) { v.State = VerdictFail }, "neither a test nor a machine"},
"an unknown state": {func(v *verdict) { v.State = "maybe" }, "maybe"},
"a composition passed without a commit": {func(v *verdict) {
v.Composition = &checker.VerdictsByGroupHeadsValueComposition{State: CompositionPass}
}, "mesh-sdk commit"},
"the same level over bound twice": {func(v *verdict) { v.OverBound = []string{LevelUnit, LevelUnit} }, "twice"},
} {
v := passing()
c.edit(&v)
if err := CheckVerdict(v); err == nil || !strings.Contains(err.Error(), c.says) {
t.Errorf("%s: %v, want a refusal saying %q", name, err, c.says)
}
}
failed := passing()
failed.State, failed.Failed = VerdictFail, &checker.VerdictsByGroupHeadsValueFailed{Machine: "ace"}
if err := CheckVerdict(failed); err != nil {
t.Errorf("a fail naming a machine is refused: %v", err)
}
}
func TestAKeptVerdictIsAPassOfAPackageAtALevel(t *testing.T) {
t.Run("a preview is refused for what a reader of candidates could not place", previewIsRefused)
ok := checker.KeptByLevelDigestValue{State: VerdictPass, Repository: "novox/mesh-sdk", Package: "./go/keys", Level: LevelUnit, Toolchain: "1.26.0", At: "2026-11-05T10:00:00Z"}
if err := CheckKept(ok); err != nil {
t.Fatalf("a kept pass is refused: %v", err)
}
for name, edit := range map[string]func(*checker.KeptByLevelDigestValue){
"a fail": func(k *checker.KeptByLevelDigestValue) { k.State = VerdictFail },
"no owner": func(k *checker.KeptByLevelDigestValue) { k.Repository = "mesh-sdk" },
"no package": func(k *checker.KeptByLevelDigestValue) { k.Package = "" },
"an unknown level": func(k *checker.KeptByLevelDigestValue) { k.Level = "smoke" },
"negative tests": func(k *checker.KeptByLevelDigestValue) { k.Tests = -1 },
"no toolchain": func(k *checker.KeptByLevelDigestValue) { k.Toolchain = "" },
"no time": func(k *checker.KeptByLevelDigestValue) { k.At = "" },
} {
k := ok
edit(&k)
if CheckKept(k) == nil {
t.Errorf("%s was taken", name)
}
}
}
// A preview is refused for what a reader of candidates could not place.
func previewIsRefused(t *testing.T) {
good := func() checker.PreviewsByRepositoryHeadTrunkValue {
return checker.PreviewsByRepositoryHeadTrunkValue{Repository: "novox/a", Head: "h1", Trunk: "t1", Merge: "m1",
Modules: map[string]checker.PreviewsByRepositoryHeadTrunkValueModulesValue{"mod": {Tree: "tr", Manifest: strings.Repeat("a", 64),
Locks: map[string]string{"go.sum": "x"}, Paths: map[string]string{"src": "o1"}}}}
}
if err := CheckPreview(good()); err != nil {
t.Fatalf("a good preview is refused: %v", err)
}
for name, c := range map[string]struct {
edit func(*checker.PreviewsByRepositoryHeadTrunkValue)
says string
}{
"a repository with no owner": {func(p *checker.PreviewsByRepositoryHeadTrunkValue) { p.Repository = "a" }, "<owner>/<name>"},
"a head with a dot": {func(p *checker.PreviewsByRepositoryHeadTrunkValue) { p.Head = "h.1" }, "head"},
"a conflict with a merge": {func(p *checker.PreviewsByRepositoryHeadTrunkValue) { p.Conflicts = []string{"f"} }, "in conflict"},
"no merge commit": {func(p *checker.PreviewsByRepositoryHeadTrunkValue) { p.Merge = "" }, "no merge commit"},
"a module with no tree": {func(p *checker.PreviewsByRepositoryHeadTrunkValue) {
m := p.Modules["mod"]
m.Tree = ""
p.Modules["mod"] = m
}, "no tree"},
"a short manifest digest": {func(p *checker.PreviewsByRepositoryHeadTrunkValue) {
m := p.Modules["mod"]
m.Manifest = "abc"
p.Modules["mod"] = m
}, "manifest digest"},
"a path outside the module": {func(p *checker.PreviewsByRepositoryHeadTrunkValue) {
m := p.Modules["mod"]
m.Paths = map[string]string{"../x": "o"}
p.Modules["mod"] = m
}, "clean path"},
} {
p := good()
c.edit(&p)
if err := CheckPreview(p); err == nil || !strings.Contains(err.Error(), c.says) {
t.Errorf("%s: CheckPreview = %v, want it to say %q", name, err, c.says)
}
}
if err := CheckPreview(checker.PreviewsByRepositoryHeadTrunkValue{Repository: "novox/a", Head: "h", Trunk: "t", Conflicts: []string{"f"}}); err != nil {
t.Errorf("a head in conflict is refused: %v", err)
}
}
// A verdict's key as go/delivery's CheckerVerdictKey wrote it (golden): the group escaped, the heads hash after the
// last dot.
func TestVerdictKeyAsGoDeliveryWroteIt(t *testing.T) {
key, err := VerdictKey("fix/1.2", "0123456789ab")
if err != nil || key != "fix/1=2e2.0123456789ab" {
t.Fatalf("%q %v", key, err)
}
group, heads, err := ParseVerdictKey(key)
if err != nil || group != "fix/1.2" || heads != "0123456789ab" {
t.Fatalf("%q %q %v", group, heads, err)
}
for _, bad := range []string{"nodot", "g.XYZ", ".0123456789ab", "a.b.0123456789ab", "g=zz.0123456789ab"} {
if _, _, err := ParseVerdictKey(bad); err == nil {
t.Errorf("%q parses", bad)
}
}
if _, err := VerdictKey("", "0123456789ab"); err == nil {
t.Error("an empty group keys")
}
if _, err := VerdictKey("g", "0123"); err == nil {
t.Error("a short hash keys")
}
}
// The codecs write a value at the kept values' version after its rules and read it back through them.
func TestTheCodecsWriteAtTheVersionAndReadBackThroughTheRules(t *testing.T) {
raw, err := EncodeVerdict(passing())
if err != nil {
t.Fatal(err)
}
if got, err := DecodeVerdict(raw); err != nil || got.V == 0 || got.Group != "g" {
t.Errorf("verdict read back = %+v, %v", got, err)
}
bad := passing()
bad.Heads = "x"
if _, err := EncodeVerdict(bad); err == nil {
t.Error("a verdict whose heads are no hash is written")
}
if _, err := DecodeVerdict([]byte(`{"v":99,"group":"g","heads":"0123456789ab","state":"running"}`)); err == nil {
t.Error("a verdict of an unknown version is read")
}
k := checker.KeptByLevelDigestValue{State: VerdictPass, Repository: "novox/mesh-sdk", Package: "./go/keys", Level: LevelUnit, Toolchain: "1.26.0", At: "2026-11-05T10:00:00Z"}
raw, err = EncodeKept(k)
if err != nil {
t.Fatal(err)
}
if got, err := DecodeKept(raw); err != nil || got.Package != k.Package {
t.Errorf("kept read back = %+v, %v", got, err)
}
k.State = VerdictFail
if _, err := EncodeKept(k); err == nil {
t.Error("a failure is kept")
}
p := checker.PreviewsByRepositoryHeadTrunkValue{Repository: "novox/a", Head: "h1", Trunk: "t1", Conflicts: []string{"f.go"}}
raw, err = EncodePreview(p)
if err != nil {
t.Fatal(err)
}
if got, err := DecodePreview(raw); err != nil || got.Head != "h1" {
t.Errorf("preview read back = %+v, %v", got, err)
}
p.Conflicts = nil
if _, err := EncodePreview(p); err == nil {
t.Error("a preview with no merge commit and no conflict is written")
}
}
// The kept and preview keys: a key reads back to its parts, and a part that does not key is refused.
func TestKeptAndPreviewKeysReadBack(t *testing.T) {
digest := strings.Repeat("ab", 32)
key, err := KeptKey(LevelUnit, digest)
if err != nil || key != "unit."+digest {
t.Fatalf("kept key = %q, %v", key, err)
}
if level, d, err := ParseKeptKey(key); err != nil || level != LevelUnit || d != digest {
t.Errorf("kept key read back = %q %q, %v", level, d, err)
}
for _, bad := range []string{"fast." + digest, "unit.XY", "unit"} {
if _, _, err := ParseKeptKey(bad); err == nil {
t.Errorf("kept key %q is read", bad)
}
}
key, err = PreviewKey("novox/mesh-sdk", "h1", "t1")
if err != nil {
t.Fatal(err)
}
if repo, h, tr, err := ParsePreviewKey(key); err != nil || repo != "novox/mesh-sdk" || h != "h1" || tr != "t1" {
t.Errorf("preview key %q read back = %q %q %q, %v", key, repo, h, tr, err)
}
for _, bad := range []string{"novox.h1.t1", "a/b.h1", "a/b.h1.t1.x"} {
if _, _, _, err := ParsePreviewKey(bad); err == nil {
t.Errorf("preview key %q is read", bad)
}
}
}
@@ -1,150 +0,0 @@
package meshdeliverycheckerclient
import (
"encoding/json"
"fmt"
"strings"
checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// The codecs of the three states the checker writes and others read (the view and the gate read verdicts, the indexer reads
// previews, the checker and the tools read kept verdicts): a value is written at the version of the kept values after its rules
// (CheckVerdict, CheckKept, CheckPreview) and read back through the same rules. The checker's owed state is read by no other
// part and keeps its own codec.
// EncodeVerdict is v as JSON at the kept values' version, refused by CheckVerdict's rules.
func EncodeVerdict(v checker.VerdictsByGroupHeadsValue) ([]byte, error) {
if err := CheckVerdict(v); err != nil {
return nil, err
}
v.V = keys.Version
return json.Marshal(v)
}
// DecodeVerdict reads a `verdicts` value; it refuses an unknown version (keys.ErrUnknownVersion) and what EncodeVerdict refuses.
func DecodeVerdict(data []byte) (checker.VerdictsByGroupHeadsValue, error) {
var v checker.VerdictsByGroupHeadsValue
if err := decode(data, &v); err != nil {
return v, err
}
if err := CheckVerdict(v); err != nil {
return checker.VerdictsByGroupHeadsValue{}, err
}
return v, nil
}
// EncodeKept is k as JSON at the kept values' version, refused by CheckKept's rules.
func EncodeKept(k checker.KeptByLevelDigestValue) ([]byte, error) {
if err := CheckKept(k); err != nil {
return nil, err
}
k.V = keys.Version
return json.Marshal(k)
}
// DecodeKept reads a `kept` value; it refuses an unknown version and what EncodeKept refuses.
func DecodeKept(data []byte) (checker.KeptByLevelDigestValue, error) {
var k checker.KeptByLevelDigestValue
if err := decode(data, &k); err != nil {
return k, err
}
if err := CheckKept(k); err != nil {
return checker.KeptByLevelDigestValue{}, err
}
return k, nil
}
// EncodePreview is p as JSON at the kept values' version, refused by CheckPreview's rules.
func EncodePreview(p checker.PreviewsByRepositoryHeadTrunkValue) ([]byte, error) {
if err := CheckPreview(p); err != nil {
return nil, err
}
p.V = keys.Version
return json.Marshal(p)
}
// DecodePreview reads a `previews` value; it refuses an unknown version and what EncodePreview refuses.
func DecodePreview(data []byte) (checker.PreviewsByRepositoryHeadTrunkValue, error) {
var p checker.PreviewsByRepositoryHeadTrunkValue
if err := decode(data, &p); err != nil {
return p, err
}
if err := CheckPreview(p); err != nil {
return checker.PreviewsByRepositoryHeadTrunkValue{}, err
}
return p, nil
}
func decode(data []byte, into any) error {
if err := keys.CheckVersion(data); err != nil {
return err
}
if err := json.Unmarshal(data, into); err != nil {
return fmt.Errorf("meshdeliverycheckerclient: value does not fit its shape: %w", err)
}
return nil
}
// KeptKey is the key, in the state kept, of a kept verdict: `<level>.<digest>`, the level unit or integration and the digest,
// lowercase hex, of everything that package's tests read at that level as the language's build module names it.
func KeptKey(level, digest string) (string, error) {
if err := checkLevel(level); err != nil {
return "", err
}
if digest == "" || strings.Trim(digest, "0123456789abcdef") != "" {
return "", fmt.Errorf("meshdeliverycheckerclient: %q is not a digest, lowercase hex", digest)
}
return checker.KeptByLevelDigestKey(level, digest), nil
}
// ParseKeptKey is the inverse of KeptKey.
func ParseKeptKey(key string) (level, digest string, err error) {
level, digest, ok := checker.ParseKeptByLevelDigestKey(key)
if !ok {
return "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a kept key, <level>.<digest>", key)
}
if _, err = KeptKey(level, digest); err != nil {
return "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a kept key: %w", key, err)
}
return level, digest, nil
}
// PreviewKey is the key, in the state previews, of a head merged onto a trunk commit: `<repository>.<head>.<trunk>`, the
// repository's owner and name each escaped (keys.EscapeName).
func PreviewKey(repository, head, trunk string) (string, error) {
if err := CheckRepository(repository); err != nil {
return "", err
}
if err := keys.CheckName("head", head); err != nil {
return "", err
}
if err := keys.CheckName("trunk commit", trunk); err != nil {
return "", err
}
owner, name, _ := strings.Cut(repository, "/")
return checker.PreviewsByRepositoryHeadTrunkKey(keys.EscapeName(owner)+"/"+keys.EscapeName(name), head, trunk), nil
}
// ParsePreviewKey is the inverse of PreviewKey.
func ParsePreviewKey(key string) (repository, head, trunk string, err error) {
repo, head, trunk, ok := checker.ParsePreviewsByRepositoryHeadTrunkKey(key)
if !ok || strings.Contains(trunk, ".") {
return "", "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a preview key, <repository>.<head>.<trunk>", key)
}
owner, name, found := strings.Cut(repo, "/")
if !found {
return "", "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a preview key: the repository has no slash", key)
}
owner, err1 := keys.UnescapeName(owner)
name, err2 := keys.UnescapeName(name)
if err1 != nil || err2 != nil {
return "", "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a preview key: the repository does not unescape", key)
}
repository = owner + "/" + name
if _, err := PreviewKey(repository, head, trunk); err != nil {
return "", "", "", fmt.Errorf("meshdeliverycheckerclient: %q is not a preview key: %w", key, err)
}
return repository, head, trunk, nil
}
@@ -1,9 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
)
@@ -1,323 +0,0 @@
// Package meshdeliveryclient holds the logic the holders and callers of the seat mesh-delivery share over its contract
// (novox/hq ADR 0625 §4, ADR 0634 §2, ADR 0635 §5): the keys of its states `merges`, `asks` and `owed` and the rules their
// values keep, the id the teller gives the router for an ask, and the act an ask's options bind. The gate writes them, the
// forge bridge, the teller and the assigner read them; the contract's decoding is strict by schema only, so these rules are
// written once here and no caller copies them.
package meshdeliveryclient
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// AskKindMerge is the kind of the gate's ask for a group's merge, the first part of its key.
const AskKindMerge = "merge"
// VerbMerge is the act an ask for a group's merge binds in its options, `mesh-delivery.merge` (Approve).
const VerbMerge = "merge"
// Head is one pull request at one commit: the repository as `<owner>/<name>` and the full sha.
type Head struct {
Repository string `json:"repository"`
Number int `json:"number"`
Head string `json:"head"`
}
// HeadsHash is the first 12 hex digits of the sha256 over the lines `<repository>#<number>=<head>`, sorted and joined by a
// newline: the same for every order of heads, different when any head changes. An empty list is refused.
func HeadsHash(heads []Head) (string, error) {
if len(heads) == 0 {
return "", fmt.Errorf("meshdeliveryclient: a heads hash needs at least one pull request")
}
lines := make([]string, len(heads))
for i, h := range heads {
lines[i] = fmt.Sprintf("%s#%d=%s", h.Repository, h.Number, h.Head)
}
sort.Strings(lines)
sum := sha256.Sum256([]byte(strings.Join(lines, "\n")))
return hex.EncodeToString(sum[:])[:12], nil
}
var hashOK = regexp.MustCompile(`^[0-9a-f]{12}$`)
func groupKey(group string) (string, error) {
if group == "" {
return "", fmt.Errorf("meshdeliveryclient: the group name is empty")
}
return keys.EscapeName(group), nil
}
// MergesKey is the key, in the state merges, of a group: `<group>`, escaped so a branch such as `fix/1.2` keys.
func MergesKey(group string) (string, error) { return groupKey(group) }
// MergeAskKey is the key, in the state asks, of the ask for merging a group at these heads: `merge.<group>.<heads hash>`.
// The teller's answer has the same key.
func MergeAskKey(group string, heads []Head) (string, error) {
part, err := groupKey(group)
if err != nil {
return "", err
}
hash, err := HeadsHash(heads)
if err != nil {
return "", err
}
return AskKindMerge + "." + part + "." + hash, nil
}
// ParseAskKey is the inverse of MergeAskKey: the kind, the group and the heads hash.
func ParseAskKey(key string) (kind, group, hash string, err error) {
parts := strings.Split(key, ".")
if len(parts) != 3 || parts[0] != AskKindMerge {
return "", "", "", fmt.Errorf("meshdeliveryclient: %q is not an ask key, merge.<group>.<heads hash>", key)
}
if parts[1] == "" {
return "", "", "", fmt.Errorf("meshdeliveryclient: %q is not an ask key: the group name is empty", key)
}
if group, err = keys.UnescapeName(parts[1]); err != nil {
return "", "", "", fmt.Errorf("meshdeliveryclient: %q is not an ask key: %w", key, err)
}
if !hashOK.MatchString(parts[2]) {
return "", "", "", fmt.Errorf("meshdeliveryclient: %q is not an ask key: %q is not 12 hex digits", key, parts[2])
}
return parts[0], group, parts[2], nil
}
// SettleGroupPrefix begins the group of a settle ask (novox/hq ADR 0556 clause 5): a person's ask to settle deliveries held
// because their merge reached the trunk without a passing check. It is kept in the asks under MergeAskKey like a merge ask,
// so the teller carries it unchanged. Git forbids a colon in a branch name, so no pull request's group ever begins with it.
const SettleGroupPrefix = "settle:"
// SettleGroup is the group of the settle ask of these delivery ids: the prefix and twelve hex digits of the sha256 over the
// ids, sorted and joined by a newline, so the same deliveries asked again keep one key.
func SettleGroup(ids []string) string {
sorted := append([]string(nil), ids...)
sort.Strings(sorted)
sum := sha256.Sum256([]byte(strings.Join(sorted, "\n")))
return SettleGroupPrefix + hex.EncodeToString(sum[:])[:12]
}
// AskVerb is the act the options of an ask bind, by its group: a settle ask's `mesh-delivery.settle`, any other
// `mesh-delivery.merge`. The gate composes the act with it and the teller checks a warrant against it.
func AskVerb(group string) string {
if strings.HasPrefix(group, SettleGroupPrefix) {
return seat.Seat + "." + seat.VerbSettle
}
return seat.Seat + "." + VerbMerge
}
// AskID is the id the teller gives the router for the ask at askKey. The router's ids refuse dots, so in the readable part a
// dot becomes `-` and any other byte outside letters, digits, `_` and `-` becomes `_`; that part is cut to fit, and every id
// ends in `-` and the first 12 hex digits of the sha256 of the whole key, so two keys never give one id. It cannot be read
// back: the teller keeps the key beside it.
func AskID(askKey string) string {
const most = 64 // the router's bound on an id
const tail = 13 // "-" and 12 hex digits
readable := []byte(askKey)
for i, c := range readable {
switch {
case c >= 'A' && c <= 'Z', c >= 'a' && c <= 'z', c >= '0' && c <= '9':
case c == '.':
readable[i] = '-'
default: // a slash, an equals sign or any other byte the router's id refuses
readable[i] = '_'
}
}
if len(readable) > 0 && (readable[0] == '-' || readable[0] == '_') {
readable[0] = 'k' // an id starts with a letter or digit
}
if len(readable) > most-tail {
readable = readable[:most-tail]
}
sum := sha256.Sum256([]byte(askKey))
return string(readable) + "-" + hex.EncodeToString(sum[:])[:12]
}
// AskingID is the id of the asking-th writing of the ask at askKey. Asking 0 and 1 give AskID(askKey), so the first asking's
// id is unchanged; asking n of 2 or more gives the AskID form of askKey + "." + n, so each asking has an id of its own and the
// router, which drops an ask under an id it keeps as a redelivery, takes each as new and an answer to an earlier asking is
// never taken for a later one.
func AskingID(askKey string, asking int) string {
if asking < 2 {
return AskID(askKey)
}
return AskID(askKey + "." + strconv.Itoa(asking))
}
// The acts the gate owes the forge, the last part of an owed key.
const (
OwedStatus = "status"
OwedView = "view"
OwedNote = "note"
OwedComment = "comment"
)
// The status contexts the mesh owes the forge.
const (
StatusContextMergeGate = "mesh/merge-gate"
StatusContextDelivery = "mesh/delivery"
StatusContextDeliveryGroup = "mesh/delivery-group"
StatusContextRepoCheck = "mesh/repo-check"
StatusContextCheck = "mesh/check"
)
var statusContexts = []string{StatusContextMergeGate, StatusContextDelivery, StatusContextDeliveryGroup, StatusContextRepoCheck, StatusContextCheck}
// StatusContexts is the contexts, in a new slice.
func StatusContexts() []string { return append([]string(nil), statusContexts...) }
// The states a status the gate owes the forge may carry.
var statusStates = []string{"pending", "success", "failure", "error", "warning"}
func checkStatusContext(context string) error {
for _, c := range statusContexts {
if context == c {
return nil
}
}
return fmt.Errorf("meshdeliveryclient: the status context %q is not one of %s", context, strings.Join(statusContexts, ", "))
}
// ParseOwedAct splits an owed act into its kind and, for a status, its context, for a comment its sequence as digits. The act
// is `status=<context>`, `view`, `note` or `comment.<seq>`.
func ParseOwedAct(act string) (kind, context string, err error) {
kind, context, hasContext := strings.Cut(act, "=")
switch kind {
case OwedStatus:
if !hasContext {
return "", "", fmt.Errorf("meshdeliveryclient: the owed act %q is a status without its context, status=<context>", act)
}
if err := checkStatusContext(context); err != nil {
return "", "", err
}
return kind, context, nil
case OwedView, OwedNote:
if hasContext {
return "", "", fmt.Errorf("meshdeliveryclient: the owed act %q takes no context", act)
}
return kind, "", nil
}
if kind, seq, ok := strings.Cut(act, "."); ok && kind == OwedComment {
if n, err := strconv.Atoi(seq); err != nil || n < 1 || strconv.Itoa(n) != seq {
return "", "", fmt.Errorf("meshdeliveryclient: the owed act %q is a comment without its sequence, comment.<seq> with seq of 1 or more", act)
}
return OwedComment, seq, nil
}
return "", "", fmt.Errorf("meshdeliveryclient: the owed act %q is not status=<context>, view, note or comment.<seq>", act)
}
// OwedCommentAct is the act of the seq-th comment on a head: `comment.<seq>`, seq of 1 or more, so a second comment has a key
// of its own and does not overwrite the first.
func OwedCommentAct(seq int) (string, error) {
if seq < 1 {
return "", fmt.Errorf("meshdeliveryclient: a comment's sequence is 1 or more, not %d", seq)
}
return OwedComment + "." + strconv.Itoa(seq), nil
}
func escapeRepository(repository string) (string, error) {
owner, name, ok := strings.Cut(repository, "/")
if !ok || owner == "" || name == "" || strings.Contains(name, "/") {
return "", fmt.Errorf("meshdeliveryclient: the repository %q is not <owner>/<name>, with exactly one slash", repository)
}
return keys.EscapeName(owner) + "/" + keys.EscapeName(name), nil
}
func unescapeRepository(part string) (string, error) {
owner, name, ok := strings.Cut(part, "/")
if !ok || owner == "" || name == "" || strings.Contains(name, "/") {
return "", fmt.Errorf("meshdeliveryclient: the repository %q is not <owner>/<name>, with exactly one slash", part)
}
o, err := keys.UnescapeName(owner)
if err != nil {
return "", err
}
n, err := keys.UnescapeName(name)
if err != nil {
return "", err
}
return o + "/" + n, nil
}
// OwedKey is the key, in the state owed, of an act the gate owes the forge on a pull request at a head:
// `<repository>.<number>.<head>.<act>`. The number is 0 for a commit no pull request names, and never negative.
func OwedKey(repository string, number int, head, act string) (string, error) {
repo, err := escapeRepository(repository)
if err != nil {
return "", err
}
if number < 0 {
return "", fmt.Errorf("meshdeliveryclient: an owed key's pull request number is 0 (none) or more, not %d", number)
}
if err := keys.CheckName("head", head); err != nil {
return "", err
}
if _, _, err := ParseOwedAct(act); err != nil {
return "", err
}
return repo + "." + strconv.Itoa(number) + "." + head + "." + act, nil
}
// ParseOwedKey is the inverse of OwedKey; act is whole, as `status=<context>` for a status.
func ParseOwedKey(key string) (repository string, number int, head, act string, err error) {
parts := strings.SplitN(key, ".", 4) // a comment's act holds a dot
if len(parts) != 4 {
return "", 0, "", "", fmt.Errorf("meshdeliveryclient: %q is not an owed key, <repository>.<number>.<head>.<act>", key)
}
number, convErr := strconv.Atoi(parts[1])
if convErr != nil {
return "", 0, "", "", fmt.Errorf("meshdeliveryclient: %q is not an owed key: %q is not a pull request number", key, parts[1])
}
if repository, err = unescapeRepository(parts[0]); err != nil {
return "", 0, "", "", fmt.Errorf("meshdeliveryclient: %q is not an owed key: %w", key, err)
}
if _, err := OwedKey(repository, number, parts[2], parts[3]); err != nil {
return "", 0, "", "", fmt.Errorf("meshdeliveryclient: %q is not an owed key: %w", key, err)
}
return repository, number, parts[2], parts[3], nil
}
// CheckOwed refuses an owed value the forge bridge could not perform: a status needs its context (one the mesh owes), a
// state of the list and a target; a view and a comment need text; a note needs text and a ref.
func CheckOwed(o seat.OwedByRepositoryNumberHeadActValue) error {
const who = "meshdeliveryclient: "
switch o.Act {
case OwedStatus:
if o.Context == "" {
return fmt.Errorf(who + "an owed status has no context")
}
if err := checkStatusContext(o.Context); err != nil {
return err
}
if o.Target == "" {
return fmt.Errorf(who + "an owed status has no target")
}
for _, s := range statusStates {
if o.State == s {
return nil
}
}
return fmt.Errorf(who+"the owed status state %q is not %s", o.State, strings.Join(statusStates, ", "))
case OwedView, OwedComment:
if o.Text == "" {
return fmt.Errorf(who+"an owed %s has no text", o.Act)
}
case OwedNote:
if o.Text == "" {
return fmt.Errorf(who + "an owed note has no text")
}
if o.Ref == "" {
return fmt.Errorf(who + "an owed note has no ref")
}
default:
return fmt.Errorf(who+"the owed act %q is not status, view, note or comment", o.Act)
}
return nil
}
@@ -1,129 +0,0 @@
package meshdeliveryclient
import (
"encoding/json"
"os"
"strings"
"testing"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
)
// The keys, ids and acts equal what the gate and the teller have always written (the expected strings are the trunk's own
// output for the same input), so a value written before is read after.
func TestTheAsksAndOwedActsKeepTheirFormsAndRules(t *testing.T) {
heads := []Head{{Repository: "novox/a", Number: 3, Head: "abc"}, {Repository: "novox/b", Number: 1, Head: "def"}}
key, err := MergeAskKey("fix/1.2", heads)
if err != nil || key != "merge.fix/1=2e2.d106c7950d28" {
t.Fatalf("MergeAskKey = %q, %v", key, err)
}
if rev, _ := MergeAskKey("fix/1.2", []Head{heads[1], heads[0]}); rev != key {
t.Errorf("the heads' order changes the key: %q", rev)
}
if kind, group, hash, err := ParseAskKey(key); err != nil || kind != "merge" || group != "fix/1.2" || hash != "d106c7950d28" {
t.Errorf("ParseAskKey = %q %q %q %v", kind, group, hash, err)
}
for _, bad := range []string{"merge.g", "settle.g.d106c7950d28", "merge..d106c7950d28", "merge.g.D106C7950D28", "merge.g.d106"} {
if _, _, _, err := ParseAskKey(bad); err == nil {
t.Errorf("ParseAskKey(%q) reads a wrong key", bad)
}
}
if _, err := MergeAskKey("", heads); err == nil {
t.Error("an empty group keys")
}
if _, err := MergeAskKey("g", nil); err == nil {
t.Error("no heads keys")
}
if id := AskID(key); id != "merge-fix_1_2e2-d106c7950d28-400030402c88" || AskingID(key, 1) != id || AskingID(key, 0) != id {
t.Errorf("AskID = %q", id)
}
if id := AskingID(key, 3); id != "merge-fix_1_2e2-d106c7950d28-3-ea06879e48d8" || len(id) > 64 {
t.Errorf("AskingID(3) = %q", id)
}
if g := SettleGroup([]string{"b", "a"}); g != "settle:7e18f737311b" || g != SettleGroup([]string{"a", "b"}) {
t.Errorf("SettleGroup = %q", g)
}
if AskVerb(SettleGroup([]string{"a"})) != "mesh-delivery.settle" || AskVerb("fix/1") != "mesh-delivery.merge" {
t.Error("AskVerb binds the wrong act")
}
for act, want := range map[string]string{"status=mesh/check": "novox/a.3.abc.status=mesh/check", "view": "novox/a.3.abc.view", "comment.2": "novox/a.3.abc.comment.2"} {
k, err := OwedKey("novox/a", 3, "abc", act)
if err != nil || k != want {
t.Errorf("OwedKey(%q) = %q, %v", act, k, err)
}
if repo, n, head, a, err := ParseOwedKey(k); err != nil || repo != "novox/a" || n != 3 || head != "abc" || a != act {
t.Errorf("ParseOwedKey(%q) = %q %d %q %q %v", k, repo, n, head, a, err)
}
}
for _, act := range []string{"status", "status=mesh/other", "view=x", "comment", "comment.0", "comment.01", "other"} {
if _, err := OwedKey("novox/a", 3, "abc", act); err == nil {
t.Errorf("OwedKey takes the act %q", act)
}
}
if _, err := OwedKey("novox", 0, "abc", "view"); err == nil {
t.Error("a repository with no slash keys")
}
if _, err := OwedKey("novox/a", -1, "abc", "view"); err == nil {
t.Error("a negative number keys")
}
if act, err := OwedCommentAct(2); err != nil || act != "comment.2" {
t.Errorf("OwedCommentAct = %q, %v", act, err)
}
type owed = seat.OwedByRepositoryNumberHeadActValue
for name, c := range map[string]struct {
v owed
says string
}{
"a good status": {owed{Act: "status", Context: StatusContextCheck, Target: "http://x", State: "success"}, ""},
"a good note": {owed{Act: "note", Text: "t", Ref: "mesh-plan"}, ""},
"a status no target": {owed{Act: "status", Context: StatusContextCheck, State: "success"}, "no target"},
"a status wrong state": {owed{Act: "status", Context: StatusContextCheck, Target: "x", State: "done"}, "state"},
"a status no context": {owed{Act: "status", Target: "x", State: "success"}, "no context"},
"a view no text": {owed{Act: "view"}, "no text"},
"a note no ref": {owed{Act: "note", Text: "t"}, "no ref"},
"an unknown act": {owed{Act: "merge"}, "not status"},
} {
err := CheckOwed(c.v)
if (c.says == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), c.says)) {
t.Errorf("%s: CheckOwed = %v, want %q", name, err, c.says)
}
}
}
// The removed verbs are the optional rows of the seat's definition that say they were removed, each with the replacement
// the definition gives: the list and the definition cannot drift apart.
func TestTheRemovedVerbsAreTheDefinitionsRemovedRows(t *testing.T) {
raw, err := os.ReadFile("../../../seats/mesh-delivery/seat.json")
if err != nil {
t.Fatal(err)
}
var def struct {
Verbs []struct {
Name string `json:"name"`
Description string `json:"description"`
Optional bool `json:"optional"`
} `json:"verbs"`
}
if err := json.Unmarshal(raw, &def); err != nil {
t.Fatal(err)
}
removed := RemovedVerbs()
rows := 0
for _, v := range def.Verbs {
if !strings.HasPrefix(v.Description, RemovedPrefix) {
if _, in := removed[v.Name]; in {
t.Errorf("%s is in the removed list, the definition keeps it", v.Name)
}
continue
}
rows++
if want, in := removed[v.Name]; !in || !v.Optional || v.Description != RemovedPrefix+want {
t.Errorf("%s: in the list %v, optional %v, described %q, want %q", v.Name, in, v.Optional, v.Description, RemovedPrefix+want)
}
}
if rows != len(removed) || rows == 0 {
t.Errorf("the definition removes %d verbs, the list names %d", rows, len(removed))
}
}
@@ -1,407 +0,0 @@
package meshdeliveryclient
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"time"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// The values of the seat's states `source`, `merges`, `asks`, `owed` and `turns`, in the seat's types: each is stamped
// with Version when encoded, read strictly (an unknown field refused) and its version checked when decoded, and its rules
// held on both sides, so the gate that writes them and the readers (the forge bridge, the teller, the assigner, the view)
// agree. What the schema cannot say is written here once (ADR 0625 §4).
// Version is the layout version of the seat's values.
const Version = 1
// MergeState is the state of a pull request in a merge.
type MergeState string
// The states a merge's pull request is in.
const (
MergeMerging MergeState = "merging"
MergeMerged MergeState = "merged"
MergeRefused MergeState = "refused"
)
// SourceMainKey is the one key of the state source.
const SourceMainKey = "main"
func encodeValue(v any, check error, stamp func()) ([]byte, error) {
if check != nil {
return nil, check
}
stamp()
return json.Marshal(v)
}
// ApprovedKey is the key, in a merge's approved heads, of a pull request: `<repository>@<number>`.
func ApprovedKey(repository string, number int) string {
return repository + "@" + strconv.Itoa(number)
}
// TurnsKey is the key, in the state turns, of a repository: `<owner>.<name>`, lower-case and escaped.
func TurnsKey(repository string) (string, error) {
repository = strings.ToLower(repository)
part, err := escapeRepository(repository)
if err != nil {
return "", err
}
return strings.Replace(part, "/", ".", 1), nil
}
// ParseTurnsKey is the inverse of TurnsKey; a key that does not name a lower-case <owner>/<name> is refused.
func ParseTurnsKey(key string) (string, error) {
owner, name, ok := strings.Cut(key, ".")
if !ok || owner == "" || name == "" || strings.Contains(name, ".") {
return "", fmt.Errorf("meshdeliveryclient: %q is not a turns key, <owner>.<name>", key)
}
repository, err := unescapeRepository(owner + "/" + name)
if err != nil {
return "", fmt.Errorf("meshdeliveryclient: %q is not a turns key: %w", key, err)
}
if again, err := TurnsKey(repository); err != nil || again != key {
return "", fmt.Errorf("meshdeliveryclient: %q is not a turns key: it does not name a lowercase <owner>/<name>", key)
}
return repository, nil
}
// ----- source
// CheckSource refuses a source naming no repository, a repository with no name or an empty head.
func CheckSource(s seat.SourceByMainValue) error {
if len(s.Heads) == 0 {
return fmt.Errorf("meshdeliveryclient: a source names no repository")
}
for repository, head := range s.Heads {
if repository == "" {
return fmt.Errorf("meshdeliveryclient: a source names a repository with no name")
}
if head == "" {
return fmt.Errorf("meshdeliveryclient: the source's head of %s is empty", repository)
}
}
return nil
}
// EncodeSource is s as the value of the key main.
func EncodeSource(s seat.SourceByMainValue) ([]byte, error) {
return encodeValue(&s, CheckSource(s), func() { s.V = Version })
}
// DecodeSource reads the value of the key main.
func DecodeSource(data []byte) (seat.SourceByMainValue, error) {
if err := keys.CheckVersion(data); err != nil {
return seat.SourceByMainValue{}, err
}
s, err := seat.DecodeSourceByMainValue(data)
if err != nil {
return s, fmt.Errorf("meshdeliveryclient: a source does not fit its shape: %w", err)
}
return s, CheckSource(s)
}
// SameSource says whether two sources are the same.
func SameSource(a, b seat.SourceByMainValue) bool { return reflect.DeepEqual(a, b) }
// ----- merge
// CheckMerge refuses a merge naming no group, ask or pull request, a pull request outside the approved heads or at
// another head than the Approve was for, and a state that lacks what it needs: merged its commit, refused the forge's words.
func CheckMerge(m seat.MergesByGroupValue) error {
if m.Group == "" {
return fmt.Errorf("meshdeliveryclient: a merge names no group")
}
if m.Ask == "" {
return fmt.Errorf("meshdeliveryclient: a merge names no ask")
}
if len(m.Pulls) == 0 {
return fmt.Errorf("meshdeliveryclient: a merge names no pull request")
}
for key, sha := range m.Approved {
if sha == "" {
return fmt.Errorf("meshdeliveryclient: the approved head of %s is empty", key)
}
}
for _, p := range m.Pulls {
who := fmt.Sprintf("%s#%d", p.Repository, p.Number)
if len(m.Approved) > 0 {
sha, ok := m.Approved[ApprovedKey(p.Repository, int(p.Number))]
if !ok {
return fmt.Errorf("meshdeliveryclient: the pull request %s is handed to the merge but is not among the approved heads", who)
}
if sha != p.Head {
return fmt.Errorf("meshdeliveryclient: the pull request %s is handed at head %q but the Approve was for %q", who, p.Head, sha)
}
}
switch MergeState(p.State) {
case MergeMerging:
case MergeMerged:
if p.Commit == "" {
return fmt.Errorf("meshdeliveryclient: the merged pull request %s has no merge commit", who)
}
case MergeRefused:
if p.Words == "" {
return fmt.Errorf("meshdeliveryclient: the refused pull request %s has no words of the forge", who)
}
default:
return fmt.Errorf("meshdeliveryclient: the state %q of %s is not merging, merged or refused", p.State, who)
}
}
return nil
}
// EncodeMerge is m as the value of a group's key.
func EncodeMerge(m seat.MergesByGroupValue) ([]byte, error) {
return encodeValue(&m, CheckMerge(m), func() { m.V = Version })
}
// DecodeMerge reads the value of a group's key.
func DecodeMerge(data []byte) (seat.MergesByGroupValue, error) {
if err := keys.CheckVersion(data); err != nil {
return seat.MergesByGroupValue{}, err
}
m, err := seat.DecodeMergesByGroupValue(data)
if err != nil {
return m, fmt.Errorf("meshdeliveryclient: a merge does not fit its shape: %w", err)
}
return m, CheckMerge(m)
}
// SameMerge says whether two merges are the same.
func SameMerge(a, b seat.MergesByGroupValue) bool { return reflect.DeepEqual(a, b) }
// ----- ask
// HeadsOf are an ask's heads as Heads (for HeadsHash and MergeAskKey).
func HeadsOf(items []seat.AsksByMergeGroupHeadsValueHeadsItem) []Head {
out := make([]Head, len(items))
for i, h := range items {
out[i] = Head{Repository: h.Repository, Number: int(h.Number), Head: h.Head}
}
return out
}
// CheckAsk refuses an ask with no group or heads, a negative count, or an ask that is no object.
func CheckAsk(a seat.AsksByMergeGroupHeadsValue) error {
if a.Asking < 0 {
return fmt.Errorf("meshdeliveryclient: the ask's asking %d is negative", a.Asking)
}
if _, err := MergeAskKey(a.Group, HeadsOf(a.Heads)); err != nil {
return fmt.Errorf("meshdeliveryclient: an ask needs its group and at least one pull request's head: %w", err)
}
raw, err := json.Marshal(a.Ask)
var fields map[string]json.RawMessage
if err != nil || len(bytes.TrimSpace(raw)) == 0 || json.Unmarshal(raw, &fields) != nil || fields == nil {
return fmt.Errorf("meshdeliveryclient: the value carries no router's ask")
}
return nil
}
// EncodeAsk is a as the value of a merge ask's key; a.Ask may be a json.RawMessage.
func EncodeAsk(a seat.AsksByMergeGroupHeadsValue) ([]byte, error) {
return encodeValue(&a, CheckAsk(a), func() { a.V = Version })
}
// DecodeAsk reads the value of a merge ask's key; its Ask is the router's ask as a decoded object.
func DecodeAsk(data []byte) (seat.AsksByMergeGroupHeadsValue, error) {
if err := keys.CheckVersion(data); err != nil {
return seat.AsksByMergeGroupHeadsValue{}, err
}
a, err := seat.DecodeAsksByMergeGroupHeadsValue(data)
if err != nil {
return a, fmt.Errorf("meshdeliveryclient: an ask does not fit its shape: %w", err)
}
return a, CheckAsk(a)
}
// SameAsk says whether two asks are the same (the router's ask by its content).
func SameAsk(a, b seat.AsksByMergeGroupHeadsValue) bool { return reflect.DeepEqual(a, b) }
// ----- owed
// EncodeOwed is o as the value of an owed key, after CheckOwed.
func EncodeOwed(o seat.OwedByRepositoryNumberHeadActValue) ([]byte, error) {
return encodeValue(&o, CheckOwed(o), func() { o.V = Version })
}
// DecodeOwed reads the value of an owed key.
func DecodeOwed(data []byte) (seat.OwedByRepositoryNumberHeadActValue, error) {
if err := keys.CheckVersion(data); err != nil {
return seat.OwedByRepositoryNumberHeadActValue{}, err
}
o, err := seat.DecodeOwedByRepositoryNumberHeadActValue(data)
if err != nil {
return o, fmt.Errorf("meshdeliveryclient: an owed value does not fit its shape: %w", err)
}
return o, CheckOwed(o)
}
// SameOwed says whether two owed values are the same.
func SameOwed(a, b seat.OwedByRepositoryNumberHeadActValue) bool { return a == b }
// ----- turns
// CheckTurn refuses a turn whose repository is not lower-case, that names no delivery, or whose deliveries repeat a heads
// hash, lack their times, are out of the order of the Approve, or name a wait without a verdict key.
func CheckTurn(t seat.TurnsByRepositoryValue) error {
repository := strings.ToLower(t.Repository)
if repository != t.Repository {
return fmt.Errorf("meshdeliveryclient: the turn's repository %q is not lowercase", t.Repository)
}
if _, err := escapeRepository(repository); err != nil {
return err
}
if len(t.Waiting) == 0 {
return fmt.Errorf("meshdeliveryclient: the turn of %s names no delivery: its key is deleted instead", t.Repository)
}
seen := map[string]bool{}
for i, w := range t.Waiting {
if w.Group == "" {
return fmt.Errorf("meshdeliveryclient: a delivery in the turn of %s names no group", t.Repository)
}
if !hashOK.MatchString(w.Heads) {
return fmt.Errorf("meshdeliveryclient: the heads hash %q in the turn of %s is not 12 lowercase hex digits", w.Heads, t.Repository)
}
if seen[w.Heads] {
return fmt.Errorf("meshdeliveryclient: the turn of %s names the heads %s twice", t.Repository, w.Heads)
}
seen[w.Heads] = true
if w.Approved.IsZero() || w.Since.IsZero() {
return fmt.Errorf("meshdeliveryclient: the delivery %s in the turn of %s says no time of its Approve or of its wait", w.Group, t.Repository)
}
if i > 0 && w.Approved.Before(t.Waiting[i-1].Approved) {
return fmt.Errorf("meshdeliveryclient: the turn of %s is not in the order of the Approve: %s was approved before %s", t.Repository, w.Group, t.Waiting[i-1].Group)
}
if w.WaitsFor != "" {
if _, _, err := parseVerdictKey(w.WaitsFor); err != nil {
return fmt.Errorf("meshdeliveryclient: the delivery %s in the turn of %s waits for what is not a verdict key: %w", w.Group, t.Repository, err)
}
} else if (w.WaitsSince != nil && !w.WaitsSince.IsZero()) || w.Step != "" {
return fmt.Errorf("meshdeliveryclient: the delivery %s in the turn of %s says when and where a check runs, and names no check", w.Group, t.Repository)
}
}
return nil
}
// parseVerdictKey is the checker's verdict key `<group>.<heads hash>` (the checker's client has the same rule over its
// own seat; a client uses its own seat's contract alone).
func parseVerdictKey(key string) (group, heads string, err error) {
i := strings.LastIndex(key, ".")
if i < 0 || !hashOK.MatchString(key[i+1:]) || i == 0 || strings.Contains(key[:i], ".") {
return "", "", fmt.Errorf("%q is not a verdict key, <group>.<heads hash>", key)
}
if group, err = keys.UnescapeName(key[:i]); err != nil {
return "", "", err
}
return group, key[i+1:], nil
}
// TurnHolds says whether a delivery at these heads is not in the turn while the turn names others: the delivery's
// check or merge holds back for the one before it.
func TurnHolds(t seat.TurnsByRepositoryValue, heads string) bool {
if len(t.Waiting) == 0 {
return false
}
for _, w := range t.Waiting {
if w.Heads == heads {
return false
}
}
return true
}
// EncodeTurn is t as the value of a repository's key.
func EncodeTurn(t seat.TurnsByRepositoryValue) ([]byte, error) {
if err := CheckTurn(t); err != nil {
return nil, err
}
t.V = Version
return json.Marshal(t)
}
// DecodeTurn reads the value of a repository's key.
func DecodeTurn(data []byte) (seat.TurnsByRepositoryValue, error) {
if err := keys.CheckVersion(data); err != nil {
return seat.TurnsByRepositoryValue{}, err
}
t, err := seat.DecodeTurnsByRepositoryValue(data)
if err != nil {
return t, fmt.Errorf("meshdeliveryclient: a turn does not fit its shape: %w", err)
}
return t, CheckTurn(t)
}
// SameTurn says whether two turns are the same, a time by the instant it names.
func SameTurn(a, b seat.TurnsByRepositoryValue) bool {
if len(a.Waiting) != len(b.Waiting) || a.Repository != b.Repository || a.V != b.V {
return false
}
for i := range a.Waiting {
x, y := a.Waiting[i], b.Waiting[i]
if !x.Approved.Equal(y.Approved) || !x.Since.Equal(y.Since) || !sameTime(x.WaitsSince, y.WaitsSince) {
return false
}
x.Approved, y.Approved, x.Since, y.Since, x.WaitsSince, y.WaitsSince = time.Time{}, time.Time{}, time.Time{}, time.Time{}, nil, nil
if !reflect.DeepEqual(x, y) {
return false
}
}
return true
}
func sameTime(a, b *time.Time) bool {
if a == nil || a.IsZero() {
return b == nil || b.IsZero()
}
return b != nil && a.Equal(*b)
}
// Answer is the operator's answer to a merge ask as the gate acts on it: the option chosen, the warrant that proves who
// answered, and the words said. It is the teller's state `answers` under the ask's key (the teller's own record; this is
// its layout for the gate that reads it).
type Answer struct {
V int `json:"v"`
Option string `json:"option"`
Warrant string `json:"warrant"`
Words string `json:"words"`
}
// CheckAnswer refuses an answer lacking its option, warrant or words.
func CheckAnswer(a Answer) error {
for _, f := range []struct{ name, value string }{{"option", a.Option}, {"warrant", a.Warrant}, {"words", a.Words}} {
if f.value == "" {
return fmt.Errorf("meshdeliveryclient: an answer has no %s", f.name)
}
}
return nil
}
// EncodeAnswer is a as the value of an answer's key.
func EncodeAnswer(a Answer) ([]byte, error) {
return encodeValue(&a, CheckAnswer(a), func() { a.V = Version })
}
// DecodeAnswer reads the value of an answer's key.
func DecodeAnswer(data []byte) (Answer, error) {
if err := keys.CheckVersion(data); err != nil {
return Answer{}, err
}
var a Answer
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
if err := dec.Decode(&a); err != nil {
return Answer{}, fmt.Errorf("meshdeliveryclient: an answer does not fit its shape: %w", err)
}
return a, CheckAnswer(a)
}
// SameAnswer says whether two answers are the same.
func SameAnswer(a, b Answer) bool { return a == b }
@@ -1,186 +0,0 @@
package meshdeliveryclient
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
"time"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// sameJSON says whether two documents hold the same values (the generated types write their fields in alphabetical
// order, the old package's in declaration order: the bytes differ, the documents do not).
func sameJSON(t *testing.T, got []byte, want string) {
t.Helper()
var g, w any
if err := json.Unmarshal(got, &g); err != nil {
t.Fatalf("%s: %v", got, err)
}
if err := json.Unmarshal([]byte(want), &w); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(g, w) {
t.Errorf("got %s\nwant %s", got, want)
}
}
// The goldens are the output of go/delivery's Encode* before it was deleted; each value must encode to the same
// document and decode back to what it was.
func TestGateValuesEncodeAsGoDeliveryDid(t *testing.T) {
ap := time.Date(2026, 11, 5, 10, 0, 0, 0, time.UTC)
wait := ap.Add(2 * time.Minute)
for _, c := range []struct {
name string
encode func() ([]byte, error)
decode func([]byte) (any, error)
value any
golden string
}{
{"source", func() ([]byte, error) {
return EncodeSource(seat.SourceByMainValue{Group: "fix/1.2", Heads: map[string]string{"novox/a": "aaaa", "novox/b": "bbbb"}})
}, func(b []byte) (any, error) { return DecodeSource(b) },
seat.SourceByMainValue{V: 1, Group: "fix/1.2", Heads: map[string]string{"novox/a": "aaaa", "novox/b": "bbbb"}},
`{"v":1,"group":"fix/1.2","heads":{"novox/a":"aaaa","novox/b":"bbbb"}}`},
{"merge", func() ([]byte, error) {
return EncodeMerge(seat.MergesByGroupValue{Group: "g", Ask: "merge.g.0123456789ab", Approved: map[string]string{"novox/a@7": "h1"},
Pulls: []seat.MergesByGroupValuePullsItem{{Repository: "novox/a", Number: 7, Head: "h1", State: "merged", Commit: "c1"}}})
}, func(b []byte) (any, error) { return DecodeMerge(b) },
seat.MergesByGroupValue{V: 1, Group: "g", Ask: "merge.g.0123456789ab", Approved: map[string]string{"novox/a@7": "h1"},
Pulls: []seat.MergesByGroupValuePullsItem{{Repository: "novox/a", Number: 7, Head: "h1", State: "merged", Commit: "c1"}}},
`{"v":1,"group":"g","ask":"merge.g.0123456789ab","approved":{"novox/a@7":"h1"},"pulls":[{"repository":"novox/a","number":7,"head":"h1","state":"merged","commit":"c1"}]}`},
{"ask", func() ([]byte, error) {
return EncodeAsk(seat.AsksByMergeGroupHeadsValue{Group: "g", Asking: 2, Ask: json.RawMessage(`{"headline":"x"}`),
Heads: []seat.AsksByMergeGroupHeadsValueHeadsItem{{Repository: "novox/a", Number: 7, Head: "h1"}}})
}, func(b []byte) (any, error) { return DecodeAsk(b) },
seat.AsksByMergeGroupHeadsValue{V: 1, Group: "g", Asking: 2, Ask: map[string]any{"headline": "x"},
Heads: []seat.AsksByMergeGroupHeadsValueHeadsItem{{Repository: "novox/a", Number: 7, Head: "h1"}}},
`{"v":1,"group":"g","heads":[{"repository":"novox/a","number":7,"head":"h1"}],"ask":{"headline":"x"},"asking":2}`},
{"owed", func() ([]byte, error) {
return EncodeOwed(seat.OwedByRepositoryNumberHeadActValue{Act: OwedStatus, Context: StatusContextCheck, Target: "t", State: "pending"})
}, func(b []byte) (any, error) { return DecodeOwed(b) },
seat.OwedByRepositoryNumberHeadActValue{V: 1, Act: OwedStatus, Context: StatusContextCheck, Target: "t", State: "pending"},
`{"v":1,"act":"status","context":"mesh/check","target":"t","state":"pending"}`},
{"turn", func() ([]byte, error) {
return EncodeTurn(seat.TurnsByRepositoryValue{Repository: "novox/a", Waiting: []seat.TurnsByRepositoryValueWaitingItem{{
Group: "g", Heads: "0123456789ab", Ask: "k", Approved: ap, Since: ap.Add(time.Minute), WaitsFor: "g.0123456789ab", WaitsSince: &wait, Step: "s"}}})
}, func(b []byte) (any, error) { return DecodeTurn(b) },
seat.TurnsByRepositoryValue{V: 1, Repository: "novox/a", Waiting: []seat.TurnsByRepositoryValueWaitingItem{{
Group: "g", Heads: "0123456789ab", Ask: "k", Approved: ap, Since: ap.Add(time.Minute), WaitsFor: "g.0123456789ab", WaitsSince: &wait, Step: "s"}}},
`{"v":1,"repository":"novox/a","waiting":[{"group":"g","heads":"0123456789ab","ask":"k","approved":"2026-11-05T10:00:00Z","since":"2026-11-05T10:01:00Z","waits-for":"g.0123456789ab","waits-since":"2026-11-05T10:02:00Z","step":"s"}]}`},
{"answer", func() ([]byte, error) { return EncodeAnswer(Answer{Option: "merge", Warrant: "w", Words: "ok"}) },
func(b []byte) (any, error) { return DecodeAnswer(b) }, Answer{V: 1, Option: "merge", Warrant: "w", Words: "ok"},
`{"v":1,"option":"merge","warrant":"w","words":"ok"}`},
} {
raw, err := c.encode()
if err != nil {
t.Fatalf("%s: %v", c.name, err)
}
sameJSON(t, raw, c.golden)
back, err := c.decode([]byte(c.golden))
if err != nil || !reflect.DeepEqual(back, c.value) {
t.Errorf("%s decoded %+v, %v; want %+v", c.name, back, err, c.value)
}
}
}
func TestGateKeysAsGoDeliveryHadThem(t *testing.T) {
for in, want := range map[string]string{"novox/Mesh-sdk": "novox.mesh-sdk", "novox/mesh-sdk": "novox.mesh-sdk", "novox/mesh.x": "novox.mesh=2ex"} {
got, err := TurnsKey(in)
if err != nil || got != want {
t.Errorf("TurnsKey(%q) = %q, %v; want %q", in, got, err, want)
}
}
if r, err := ParseTurnsKey("novox.mesh=2ex"); err != nil || r != "novox/mesh.x" {
t.Errorf("%q %v", r, err)
}
for _, bad := range []string{"novox", "novox.Mesh", "a.b.c", ".x", "novox.mesh-sdk."} {
if _, err := ParseTurnsKey(bad); err == nil {
t.Errorf("%q parses as a turns key", bad)
}
}
if _, err := TurnsKey("novox"); err == nil {
t.Error("a repository with no slash keys")
}
if k := ApprovedKey("novox/a", 7); k != "novox/a@7" {
t.Error(k)
}
if SourceMainKey != seat.SourceByMainKey() {
t.Error("the source's key is the seat's")
}
}
func TestGateValuesRefuseWhatTheirRulesRefuse(t *testing.T) {
ap := time.Date(2026, 11, 5, 10, 0, 0, 0, time.UTC)
pull := seat.MergesByGroupValuePullsItem{Repository: "novox/a", Number: 7, Head: "h1", State: "merging"}
merge := func(edit func(*seat.MergesByGroupValue)) error {
m := seat.MergesByGroupValue{Group: "g", Ask: "k", Pulls: []seat.MergesByGroupValuePullsItem{pull}}
edit(&m)
_, err := EncodeMerge(m)
return err
}
turn := func(edit func(*seat.TurnsByRepositoryValue)) error {
tv := seat.TurnsByRepositoryValue{Repository: "novox/a", Waiting: []seat.TurnsByRepositoryValueWaitingItem{{Group: "g", Heads: "0123456789ab", Approved: ap, Since: ap}}}
edit(&tv)
_, err := EncodeTurn(tv)
return err
}
for name, c := range map[string]struct {
err error
want string
}{
"merge: no group": {merge(func(m *seat.MergesByGroupValue) { m.Group = "" }), "no group"},
"merge: no pull": {merge(func(m *seat.MergesByGroupValue) { m.Pulls = nil }), "no pull request"},
"merge: not approved": {merge(func(m *seat.MergesByGroupValue) { m.Approved = map[string]string{"novox/b@1": "x"} }), "not among the approved"},
"merge: other head": {merge(func(m *seat.MergesByGroupValue) { m.Approved = map[string]string{"novox/a@7": "x"} }), "Approve was for"},
"merge: merged no commit": {merge(func(m *seat.MergesByGroupValue) { m.Pulls[0].State = "merged" }), "no merge commit"},
"merge: refused no words": {merge(func(m *seat.MergesByGroupValue) { m.Pulls[0].State = "refused" }), "no words"},
"merge: unknown state": {merge(func(m *seat.MergesByGroupValue) { m.Pulls[0].State = "lost" }), "not merging, merged or refused"},
"turn: upper case": {turn(func(v *seat.TurnsByRepositoryValue) { v.Repository = "Novox/a" }), "not lowercase"},
"turn: empty": {turn(func(v *seat.TurnsByRepositoryValue) { v.Waiting = nil }), "names no delivery"},
"turn: bad hash": {turn(func(v *seat.TurnsByRepositoryValue) { v.Waiting[0].Heads = "xyz" }), "12 lowercase hex"},
"turn: no time": {turn(func(v *seat.TurnsByRepositoryValue) { v.Waiting[0].Since = time.Time{} }), "no time"},
"turn: step with no wait": {turn(func(v *seat.TurnsByRepositoryValue) { v.Waiting[0].Step = "s" }), "names no check"},
"turn: not a verdict key": {turn(func(v *seat.TurnsByRepositoryValue) { v.Waiting[0].WaitsFor = "nope" }), "not a verdict key"},
} {
if c.err == nil || !strings.Contains(c.err.Error(), c.want) {
t.Errorf("%s: %v, want %q", name, c.err, c.want)
}
}
if _, err := EncodeSource(seat.SourceByMainValue{Group: "g"}); err == nil {
t.Error("an empty source encodes")
}
if _, err := EncodeAsk(seat.AsksByMergeGroupHeadsValue{Group: "g", Ask: "text", Heads: []seat.AsksByMergeGroupHeadsValueHeadsItem{{Repository: "novox/a", Number: 1, Head: "h"}}}); err == nil {
t.Error("an ask that is no object encodes")
}
if _, err := EncodeAnswer(Answer{Option: "merge"}); err == nil {
t.Error("an answer with no warrant encodes")
}
if _, err := DecodeSource([]byte(`{"v":2,"group":"g","heads":{"a":"b"}}`)); !errors.Is(err, keys.ErrUnknownVersion) {
t.Errorf("another version: %v", err)
}
if _, err := DecodeMerge([]byte(`{"v":1,"group":"g","ask":"k","pulls":[],"extra":1}`)); err == nil {
t.Error("an unknown field decodes")
}
}
func TestTurnHoldsAndSameTurn(t *testing.T) {
ap := time.Date(2026, 11, 5, 10, 0, 0, 0, time.UTC)
tv := seat.TurnsByRepositoryValue{V: 1, Repository: "novox/a", Waiting: []seat.TurnsByRepositoryValueWaitingItem{{Group: "g", Heads: "0123456789ab", Approved: ap, Since: ap}}}
if TurnHolds(tv, "0123456789ab") || !TurnHolds(tv, "ba9876543210") || TurnHolds(seat.TurnsByRepositoryValue{}, "x") {
t.Error("a turn holds a delivery that is not in it while others wait")
}
other := tv
other.Waiting = []seat.TurnsByRepositoryValueWaitingItem{tv.Waiting[0]}
other.Waiting[0].Approved = ap.In(time.FixedZone("x", 3600))
if !SameTurn(tv, other) {
t.Error("the same instant in another zone is another turn")
}
other.Waiting[0].Since = ap.Add(time.Second)
if SameTurn(tv, other) {
t.Error("another instant is the same turn")
}
}
@@ -1,17 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient
go 1.26.0
require (
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
)
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
)
@@ -1,12 +0,0 @@
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=
@@ -1,18 +0,0 @@
package meshdeliveryclient
// RemovedPrefix opens the description of a verb release 6 removed from the seat mesh-delivery (novox/hq ADR 0539 clause 17):
// the definition keeps it as an optional row with no input, so the holder of before and the holder of release 6 both hold
// the seat, and a caller is answered by name with its replacement.
const RemovedPrefix = "Removed in R6 (ADR 0539): refused by name, naming its replacement: "
// RemovedVerbs are the seat's verbs release 6 removed, each with its replacement in words: with no plan the gate keeps the
// merge's states and derives nothing after the merge. A new map each call.
func RemovedVerbs() map[string]string {
return map[string]string{
"times": "the delivery view (nox-delivery): each machine's want and have, and since when",
"what-if": "the merge check's composition, which composes every machine a change touches",
"release": "the assigner's asks (hold, restore, backup, record), answered on the phone or in the delivery view",
"stop": "a bad fingerprint is held and its canary set back by the assigner (ADR 0429 §6, ADR 0539 clause 18)",
"retire-history": "nothing to retire: the gate keeps no delivery after its merge",
}
}
@@ -1,8 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient
go 1.26.0
require (
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
)
@@ -1,326 +0,0 @@
// Package meshwantsclient is the client of the seat mesh-wants: the keys of its state wants, and the check and the codec
// of a want, a node's own want and a hold, which every module that reads or writes wants shares (the assigner that writes
// them, the node-engine, the provisioner and the artifact store that read them). The layouts are the seat's contract; this
// file writes the version and refuses what the earlier codec refused (novox/hq ADR 0528, ADR 0539, ADR 0546, ADR 0634 §4).
package meshwantsclient
import (
"encoding/json"
"fmt"
"regexp"
"strings"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
"git.novox.be/novox/mesh-sdk/go/keys"
)
// The three values of the state wants, in the seat's types.
type (
// Want is a node's want for a module: the fingerprint it should run and the digest of the one composition it allows,
// or Removed, or Self when the node composes it itself; Newest and Rule name what holds the node back.
Want = seat.WantsByWantNodeModuleValue
// NodeWant is a node's own want: the digest of the node's own composition it may apply.
NodeWant = seat.WantsByWantNodeNodeValue
// Held is a fingerprint the assigner holds: the node whose health check failed on it, and the check's words.
Held = seat.WantsByHeldModuleFingerprintValue
)
// The rules of ADR 0429 §6 and ADR 0539 clause 6, by number, as a want or a difference names the one that holds a node back.
const (
RuleBuildDone = 1 // the build of F is done
RuleNotHeld = 2 // F is not held
RuleCanary = 3 // the node is the module's canary, or the canary runs F healthy
RuleStep = 4 // at most step other nodes are ahead (step 0: no limit, ADR 0528 §3)
RuleProviders = 5 // every provider the module requires runs healthy on that node, or at its canary
RuleGrant = 6 // the module's credential for F exists (grants.<module>.<fp>)
RuleComposed = 7 // the module's composition for that node at F exists (ADR 0539 §6)
// RuleProvisions: every provision the module requires on that node is provided (novox/hq ADR 0570 §4).
RuleProvisions = 8
)
// ruleRead is the highest rule a reader takes (novox/hq ADR 0614 §2, §4): 9 and 10 are kept for the verb wait and the
// capability hold of a later release.
const ruleRead = 10
// The answers a want carries to a failed backup before an apply (ADR 0539 clause 16).
const (
// BackupAgain is the answer to back the module up again before the apply.
BackupAgain = seat.WantsByWantNodeModuleValueBackupAgain
// BackupSkip is the answer to go on without a backup.
BackupSkip = seat.WantsByWantNodeModuleValueBackupSkip
)
// NodeWord is the last part of a node's own key.
const NodeWord = "node"
const (
wantWord = "want"
heldWord = "held"
srcWord = "src2:"
)
var (
moduleNameOK = regexp.MustCompile(`^[-/_a-zA-Z0-9]+(\.[-/_a-zA-Z0-9]+)*$`)
digestForm = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`)
)
func moduleKeyPart(module string) (string, error) {
if module == "" {
return "", fmt.Errorf("wants: the module name is empty")
}
if !moduleNameOK.MatchString(module) {
return "", fmt.Errorf("wants: the module name %q holds a wildcard, whitespace, '=', an empty part between dots or a character a key refuses", module)
}
return keys.EscapeName(module), nil
}
func parseModuleKeyPart(part string) (string, error) {
if err := keys.CheckName("module", part); err != nil {
return "", err
}
module, err := keys.UnescapeName(part)
if err != nil {
return "", err
}
if back, err := moduleKeyPart(module); err != nil || back != part {
return "", fmt.Errorf("wants: the module part %q is not a module name as a key writes it", part)
}
return module, nil
}
func checkFingerprint(fp, of string) error {
if part, ok := strings.CutPrefix(fp, srcWord); !ok || !keys.IsHex64(part) {
return fmt.Errorf("wants: %s: %q is not a fingerprint of the new form, %s and 64 lowercase hex digits", of, fp, srcWord)
}
return nil
}
func checkDigest(digest, of string) error {
if !digestForm.MatchString(digest) {
return fmt.Errorf("wants: %s %q is not sha256:<64 lowercase hex digits>", of, digest)
}
return nil
}
func checkRule(rule int64, of string) error {
if rule < RuleBuildDone || rule > ruleRead {
return fmt.Errorf("wants: %s names the rule %d, which is not one of 1 to %d", of, rule, ruleRead)
}
return nil
}
// WantKey is the key, in the state wants, of a node's want for a module: `want.<node>.<module>`. A module named `node` is
// refused, since `want.<node>.node` is the node's own key.
func WantKey(node, module string) (string, error) {
if err := keys.CheckName("node", node); err != nil {
return "", err
}
part, err := moduleKeyPart(module)
if err != nil {
return "", err
}
if module == NodeWord {
return "", fmt.Errorf(`wants: the module name "node" is refused: want.%s.node is the node's own key`, node)
}
return seat.WantsByWantNodeModuleKey(node, part), nil
}
// WantNodeKey is the key of a node's own want: `want.<node>.node`.
func WantNodeKey(node string) (string, error) {
if err := keys.CheckName("node", node); err != nil {
return "", err
}
return seat.WantsByWantNodeNodeKey(node), nil
}
// ParseWantNodeKey is the inverse of WantNodeKey; it refuses a module's want key and any key of another form.
func ParseWantNodeKey(key string) (node string, err error) {
node, ok := seat.ParseWantsByWantNodeNodeKey(key)
if !ok || keys.CheckName("node", node) != nil {
return "", fmt.Errorf("wants: %q is not a node's own want key, want.<node>.node", key)
}
return node, nil
}
// ParseWantKey is the inverse of WantKey; it refuses a held key, the node's own want key and any key of another form.
func ParseWantKey(key string) (node, module string, err error) {
refused := fmt.Errorf("wants: %q is not a want key, want.<node>.<module>", key)
node, part, ok := seat.ParseWantsByWantNodeModuleKey(key)
if !ok || part == NodeWord || keys.CheckName("node", node) != nil {
return "", "", refused
}
if module, err = parseModuleKeyPart(part); err != nil {
return "", "", refused
}
return node, module, nil
}
// HeldKey is the key of a module's fingerprint the assigner holds: `held.<module>.<hex>`, the hex being the
// fingerprint's key part.
func HeldKey(module, fingerprint string) (string, error) {
if err := keys.CheckName("module", module); err != nil {
return "", err
}
if err := checkFingerprint(fingerprint, "a held key's fingerprint"); err != nil {
return "", err
}
return seat.WantsByHeldModuleFingerprintKey(module, strings.TrimPrefix(fingerprint, srcWord)), nil
}
// ParseHeldKey is the inverse of HeldKey: the module and the fingerprint (`src2:` and the hex).
func ParseHeldKey(key string) (module, fingerprint string, err error) {
module, part, ok := seat.ParseWantsByHeldModuleFingerprintKey(key)
if !ok || keys.CheckName("module", module) != nil || !keys.IsHex64(part) {
return "", "", fmt.Errorf("wants: %q is not a held key, held.<module>.<fingerprint hex>", key)
}
return module, srcWord + part, nil
}
func checkWant(w Want) error {
if w.Removed {
if w.Fp != "" || w.Composed != "" || w.Newest != "" || w.Rule != 0 || w.Backup != "" || w.Put || w.Hold || w.Self || w.Yes != "" || w.Release != "" {
return fmt.Errorf("wants: a removed want names no fingerprint, composition, rule, backup answer or put")
}
return nil
}
if w.Hold && !w.Put {
return fmt.Errorf("wants: only a put holds a want")
}
if w.Put && (w.Newest != "" || w.Rule != 0) {
return fmt.Errorf("wants: a put's want is held back by no rule")
}
if err := checkFingerprint(w.Fp, "a want's fingerprint"); err != nil {
return err
}
// A node the catalogue places composes its own modules (ADR 0547 §2): its want is Self and names the build alone. Any
// other want names the one composition it allows (ADR 0539 clause 1).
switch {
case w.Self && w.Composed != "":
return fmt.Errorf("wants: the want of %s says the node composes it, and names a composition", w.Fp)
case !w.Self && w.Composed == "":
return fmt.Errorf("wants: the want of %s names no composition: a want names the digest of the one composition it allows (ADR 0539 clause 1), unless the node composes it itself", w.Fp)
case w.Composed != "":
if err := checkDigest(w.Composed, "a want's composition digest"); err != nil {
return err
}
}
if w.Yes != "" {
if err := checkDigest(w.Yes, "the digest a want's yes allows"); err != nil {
return err
}
} else if w.Release != "" {
return fmt.Errorf("wants: the want of %s names the release %s and no yes it gave", w.Fp, w.Release)
}
switch w.Backup {
case "", BackupAgain, BackupSkip:
default:
return fmt.Errorf("wants: the backup answer %q is not %s or %s", w.Backup, BackupAgain, BackupSkip)
}
if w.Newest == "" && w.Rule == 0 {
return nil
}
if err := checkFingerprint(w.Newest, "a want's newest fingerprint"); err != nil {
return err
}
if w.Newest == w.Fp {
return fmt.Errorf("wants: a want at its newest fingerprint names no rule holding it back")
}
return checkRule(w.Rule, "a want held back")
}
// CheckWant refuses a want the codec refuses.
func CheckWant(w Want) error { return checkWant(w) }
// EncodeWant is w as JSON at the kept values' version; it refuses a fingerprint not of the new form, a removed want naming
// one, and a want that names no composition unless the node composes it itself.
func EncodeWant(w Want) ([]byte, error) {
if err := checkWant(w); err != nil {
return nil, err
}
w.V = keys.Version
return json.Marshal(w)
}
// DecodeWant reads a want; it refuses an unknown version (keys.ErrUnknownVersion), a field the contract does not know and
// what EncodeWant refuses.
func DecodeWant(data []byte) (Want, error) {
if err := keys.CheckVersion(data); err != nil {
return Want{}, err
}
w, err := seat.DecodeWantsByWantNodeModuleValue(data)
if err != nil {
return Want{}, fmt.Errorf("wants: value does not fit its shape: %w", err)
}
if err := checkWant(w); err != nil {
return Want{}, err
}
return w, nil
}
// SameWant says whether two wants are the same value, so the assigner writes only when they differ.
func SameWant(a, b Want) bool { return a == b }
func checkNodeWant(w NodeWant) error {
switch {
case w.Self && w.Composed != "":
return fmt.Errorf("wants: the node's own want says the node composes it, and names a composition")
case w.Self:
return nil
}
return checkDigest(w.Composed, "the node's own want's composition digest")
}
// EncodeNodeWant is w as JSON; it refuses a digest not of the sha256 form, and a want that names no composition unless it
// says the node composes its own (Self, ADR 0547 §2).
func EncodeNodeWant(w NodeWant) ([]byte, error) {
if err := checkNodeWant(w); err != nil {
return nil, err
}
w.V = keys.Version
return json.Marshal(w)
}
// DecodeNodeWant reads a node's own want; it refuses an unknown version, a field a node's own want does not have (a
// fingerprint among them), and what EncodeNodeWant refuses.
func DecodeNodeWant(data []byte) (NodeWant, error) {
if err := keys.CheckVersion(data); err != nil {
return NodeWant{}, err
}
w, err := seat.DecodeWantsByWantNodeNodeValue(data)
if err != nil {
return NodeWant{}, fmt.Errorf("wants: a node's own want does not fit its shape: %w", err)
}
if err := checkNodeWant(w); err != nil {
return NodeWant{}, err
}
return w, nil
}
func checkHeld(h Held) error { return keys.CheckName("node", h.Node) }
// EncodeHeld is h as JSON; it refuses a node name a key refuses.
func EncodeHeld(h Held) ([]byte, error) {
if err := checkHeld(h); err != nil {
return nil, err
}
h.V = keys.Version
return json.Marshal(h)
}
// DecodeHeld reads a hold; it refuses an unknown version and what EncodeHeld refuses.
func DecodeHeld(data []byte) (Held, error) {
if err := keys.CheckVersion(data); err != nil {
return Held{}, err
}
h, err := seat.DecodeWantsByHeldModuleFingerprintValue(data)
if err != nil {
return Held{}, fmt.Errorf("wants: value does not fit its shape: %w", err)
}
if err := checkHeld(h); err != nil {
return Held{}, err
}
return h, nil
}
// SameHeld says whether two holds are the same value.
func SameHeld(a, b Held) bool { return a == b }
@@ -1,162 +0,0 @@
package meshwantsclient
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/keys"
)
func rep(c string) string { return strings.Repeat(c, 64) }
// TestWantsAreWhatTheEarlierCodecWrote holds the values and keys equal to what go/delivery's codec wrote for the same
// values (the golden texts are its output; the field order is the contract's now, so values are compared decoded), and the
// refusals it made.
func TestWantsAreWhatTheEarlierCodecWrote(t *testing.T) {
a, b, c, d := "src2:"+rep("a"), "src2:"+rep("b"), "sha256:"+rep("c"), "sha256:"+rep("d")
sameJSON := func(t *testing.T, got []byte, want string) {
t.Helper()
var g, w any
if err := json.Unmarshal(got, &g); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal([]byte(want), &w); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(g, w) {
t.Fatalf("wrote %s, the earlier codec wrote %s", got, want)
}
}
t.Run("wants", func(t *testing.T) {
for name, row := range map[string]struct {
w Want
golden string
}{
"composed": {Want{Fp: a, Composed: c, Words: "w"}, `{"v":1,"fp":"` + a + `","composed":"` + c + `","words":"w"}`},
"held back": {Want{Fp: a, Self: true, Newest: b, Rule: RuleGrant, Backup: BackupAgain, Words: "held"}, `{"v":1,"fp":"` + a + `","newest":"` + b + `","rule":6,"backup":"again","words":"held","self":true}`},
"put": {Want{Fp: a, Self: true, Put: true, Hold: true, Yes: d, Release: "r1"}, `{"v":1,"fp":"` + a + `","put":true,"hold":true,"yes":"` + d + `","release":"r1","self":true}`},
"removed": {Want{Removed: true, Words: "gone"}, `{"v":1,"removed":true,"words":"gone"}`},
} {
t.Run(name, func(t *testing.T) {
raw, err := EncodeWant(row.w)
if err != nil {
t.Fatal(err)
}
sameJSON(t, raw, row.golden)
back, err := DecodeWant(raw)
if err != nil || !SameWant(back, func() Want { w := row.w; w.V = keys.Version; return w }()) {
t.Fatalf("read back %+v, %v", back, err)
}
})
}
})
t.Run("refused", func(t *testing.T) {
for name, w := range map[string]Want{
"old fingerprint": {Fp: "src1:abc", Composed: c},
"no composition": {Fp: a},
"self and composition": {Fp: a, Self: true, Composed: c},
"hold without put": {Fp: a, Self: true, Hold: true},
"put held back": {Fp: a, Self: true, Put: true, Newest: b, Rule: RuleStep},
"removed names a rule": {Removed: true, Rule: 1},
"release without yes": {Fp: a, Self: true, Release: "r"},
"backup answer unknown": {Fp: a, Self: true, Backup: "later"},
"newest equals fp": {Fp: a, Self: true, Newest: a, Rule: RuleStep},
"rule 11": {Fp: a, Self: true, Newest: b, Rule: 11},
"newest without a rule": {Fp: a, Self: true, Newest: b},
"composition not sha256": {Fp: a, Composed: "sha256:xyz"},
} {
if _, err := EncodeWant(w); err == nil {
t.Errorf("%s: written", name)
}
}
if _, err := EncodeWant(Want{Fp: a, Self: true, Newest: b, Rule: 10}); err != nil {
t.Errorf("rule 10 is read: %v", err)
}
if _, err := DecodeWant([]byte(`{"fp":"` + a + `","self":true}`)); !errors.Is(err, keys.ErrUnknownVersion) {
t.Errorf("no version: %v", err)
}
if _, err := DecodeWant([]byte(`{"v":1,"fp":"` + a + `","self":true,"extra":1}`)); err == nil {
t.Error("an unknown field is read")
}
})
t.Run("node want and hold", func(t *testing.T) {
raw, err := EncodeNodeWant(NodeWant{Composed: c, Words: "n"})
if err != nil {
t.Fatal(err)
}
sameJSON(t, raw, `{"v":1,"composed":"`+c+`","words":"n"}`)
raw, err = EncodeNodeWant(NodeWant{Self: true})
if err != nil {
t.Fatal(err)
}
sameJSON(t, raw, `{"v":1,"composed":"","self":true}`)
if back, err := DecodeNodeWant(raw); err != nil || !back.Self {
t.Fatalf("%+v %v", back, err)
}
for name, w := range map[string]NodeWant{"none": {}, "both": {Self: true, Composed: c}, "bad": {Composed: "x"}} {
if _, err := EncodeNodeWant(w); err == nil {
t.Errorf("%s: written", name)
}
}
if _, err := DecodeNodeWant([]byte(`{"v":1,"composed":"` + c + `","fp":"` + a + `"}`)); err == nil {
t.Error("a fingerprint in a node's own want is read")
}
raw, err = EncodeHeld(Held{Node: "ace", Words: "unhealthy"})
if err != nil {
t.Fatal(err)
}
sameJSON(t, raw, `{"v":1,"node":"ace","words":"unhealthy"}`)
if h, err := DecodeHeld(raw); err != nil || !SameHeld(h, Held{V: 1, Node: "ace", Words: "unhealthy"}) {
t.Fatalf("%+v %v", h, err)
}
if _, err := EncodeHeld(Held{Node: "a.b"}); err == nil {
t.Error("a node with a dot is held")
}
})
t.Run("keys", func(t *testing.T) {
for _, row := range []struct{ module, want string }{
{"mesh-delivery-assigner", "want.ace.mesh-delivery-assigner"},
{"novox.be", "want.ace.novox=2ebe"},
} {
k, err := WantKey("ace", row.module)
if err != nil || k != row.want {
t.Fatalf("WantKey %s = %q, %v", row.module, k, err)
}
if n, m, err := ParseWantKey(k); err != nil || n != "ace" || m != row.module {
t.Fatalf("ParseWantKey %q = %q %q %v", k, n, m, err)
}
}
if k, err := HeldKey("mesh-delivery-assigner", a); err != nil || k != "held.mesh-delivery-assigner."+rep("a") {
t.Fatalf("HeldKey = %q %v", k, err)
}
if m, fp, err := ParseHeldKey("held.mesh-delivery-assigner." + rep("a")); err != nil || m != "mesh-delivery-assigner" || fp != a {
t.Fatalf("ParseHeldKey = %q %q %v", m, fp, err)
}
if k, err := WantNodeKey("ace"); err != nil || k != "want.ace.node" {
t.Fatalf("WantNodeKey = %q %v", k, err)
}
if n, err := ParseWantNodeKey("want.ace.node"); err != nil || n != "ace" {
t.Fatalf("ParseWantNodeKey = %q %v", n, err)
}
if _, err := WantKey("ace", "node"); err == nil {
t.Error("a module named node has a want key")
}
if _, err := HeldKey("novox.be", a); err == nil {
t.Error("a module with a dot has a held key")
}
for _, key := range []string{"want.ace.node", "held.m." + rep("a"), "ace.m", "want.ace", "want..m"} {
if _, _, err := ParseWantKey(key); err == nil {
t.Errorf("%q is a want key", key)
}
}
if _, err := ParseWantNodeKey("want.ace.m"); err == nil {
t.Error("a module's want is a node's own")
}
if _, _, err := ParseHeldKey("want.ace.m"); err == nil {
t.Error("a want key is a held key")
}
})
}
@@ -1,63 +0,0 @@
// 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-delivery/sdk/parts"
)
// Call asks one verb of the seat, `seat:<seat>.<verb>`, 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
}
@@ -1,57 +0,0 @@
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-delivery/sdk/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)
}
}
@@ -1,18 +0,0 @@
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-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
)
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
)
@@ -1,12 +0,0 @@
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=
@@ -1,63 +0,0 @@
// 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-delivery/sdk/parts"
)
// Call asks one verb of the seat, `seat:<seat>.<verb>`, 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
}
@@ -1,57 +0,0 @@
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-delivery/sdk/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)
}
}
@@ -1,18 +0,0 @@
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-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
)
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
)
@@ -1,12 +0,0 @@
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=
@@ -1,528 +0,0 @@
// 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
import (
"bytes"
"encoding/json"
"errors"
"strings"
)
// 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"
const (
StateHolds = "holds"
StateWindows = "windows"
)
// HoldsBySeatNameDigestKey is the key of one value of the state holds under the form "<seat>.<name>.<digest>".
func HoldsBySeatNameDigestKey(seat, name, digest string) string {
return seat + "." + name + "." + digest
}
// ParseHoldsBySeatNameDigestKey reads the parts of a key of the form "<seat>.<name>.<digest>", in the order of the form; the last result is false for a key of another form.
func ParseHoldsBySeatNameDigestKey(key string) (string, string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", "", false
}
rest = after0
v2, after2, found2 := strings.Cut(rest, ".")
if !found2 || v2 == "" {
return "", "", "", false
}
rest = after2
if rest == "" {
return "", "", "", false
}
v4 := rest
rest = ""
if rest != "" {
return "", "", "", false
}
return v0, v2, v4, true
}
// DecodeHoldsBySeatNameDigestValue reads one value of the state holds under the form "<seat>.<name>.<digest>", refusing a field HoldsBySeatNameDigestValue does not know and any text after the value.
func DecodeHoldsBySeatNameDigestValue(raw []byte) (HoldsBySeatNameDigestValue, error) {
var v HoldsBySeatNameDigestValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// WindowsByCallerKey is the key of one value of the state windows under the form "<caller>".
func WindowsByCallerKey(caller string) string { return caller }
// ParseWindowsByCallerKey reads the parts of a key of the form "<caller>", in the order of the form; the last result is false for a key of another form.
func ParseWindowsByCallerKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodeWindowsByCallerValue reads one value of the state windows under the form "<caller>", refusing a field WindowsByCallerValue does not know and any text after the value.
func DecodeWindowsByCallerValue(raw []byte) (WindowsByCallerValue, error) {
var v WindowsByCallerValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// 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:<hex> (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:<hex>
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:<hex> (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: <registry>/<module>/<artifact>@sha256:<digest>, or <name>@<version> 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:<hex>: 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:<hex> (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:<hex>
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, <module>/<artifact>; 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
// HoldsBySeatNameDigestValue is one value of the state holds under the key <seat>.<name>.<digest>.
type HoldsBySeatNameDigestValue struct {
By string `json:"by"`
Digest string `json:"digest"`
Kind string `json:"kind"`
Name string `json:"name"`
Seat string `json:"seat"`
// when (RFC 3339 time)
Since string `json:"since"`
Why string `json:"why,omitempty"`
}
// WindowsByCallerValueDroppedItem is a type of the contract.
type WindowsByCallerValueDroppedItem struct {
// when (RFC 3339 time)
At string `json:"at,omitempty"`
By string `json:"by,omitempty"`
Node string `json:"node"`
Why string `json:"why,omitempty"`
}
// WindowsByCallerValue is one value of the state windows under the key <caller>.
type WindowsByCallerValue struct {
// when it last closed (RFC 3339 time)
Closed string `json:"closed,omitempty"`
// retired nodes dropped by hand
Dropped []WindowsByCallerValueDroppedItem `json:"dropped,omitempty"`
// the nodes holding it open
Holding []string `json:"holding,omitempty"`
LastHolding []string `json:"last_holding,omitempty"`
// the caller whose window it is
Name string `json:"name"`
Open bool `json:"open,omitempty"`
// node to when it began to hold
Since map[string]string `json:"since,omitempty"`
// of each node holding the window, why it holds it
Why map[string]string `json:"why,omitempty"`
}
@@ -1,82 +0,0 @@
// 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: "a hold", subject: "", given: "{\"by\":\"operator@shanks\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"kind\":\"base\",\"name\":\"base\",\"seat\":\"container-image-registry\",\"since\":\"2026-11-05T10:00:00Z\"}", wire: "{\"by\":\"operator@shanks\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"kind\":\"base\",\"name\":\"base\",\"seat\":\"container-image-registry\",\"since\":\"2026-11-05T10:00:00Z\"}",
decode: func(b []byte) (any, error) {
var v HoldsBySeatNameDigestValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "an open window", subject: "", given: "{\"holding\":[\"ace\"],\"name\":\"mesh-delivery-builder\",\"open\":true,\"since\":{\"ace\":\"2026-11-05T10:00:00Z\"}}", wire: "{\"holding\":[\"ace\"],\"name\":\"mesh-delivery-builder\",\"open\":true,\"since\":{\"ace\":\"2026-11-05T10:00:00Z\"}}",
decode: func(b []byte) (any, error) { var v WindowsByCallerValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{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)
}
}
}
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry
go 1.26.0
@@ -1,262 +0,0 @@
// 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:<hex> (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:<hex> (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: <registry>/<module>/<artifact>@sha256:<digest>, or <name>@<version> 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:<hex>: 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:<hex> (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"`
}
@@ -1,66 +0,0 @@
// 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)
}
}
}
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry
go 1.26.0
@@ -1,129 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// Package meshartifactsweep is the contract of the seat "mesh-artifact-sweep", defined whole (seats/mesh-artifact-sweep/seat.json): no description.
package meshartifactsweep
import "time"
// Seat is the seat's name.
const Seat = "mesh-artifact-sweep"
const (
VerbArtifacts = "artifacts"
VerbHeld = "held"
VerbCollect = "collect"
VerbMirrors = "mirrors"
VerbImages = "images"
)
const (
EventArtifactsGone = "artifacts-gone"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbArtifacts, VerbHeld, VerbCollect, VerbMirrors, VerbImages}
}
// Events is every event published on the seat's subject: a new copy each call.
func Events() []string {
return []string{EventArtifactsGone}
}
// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects.
const (
Family = "mesh-artifact-sweep.events"
FamilyMaxAgeDays = 7
)
// SubjectArtifactsGone is where artifacts-gone is published.
const SubjectArtifactsGone = "mesh.seat.mesh-artifact-sweep.event.artifacts-gone"
// ArtifactsGone is the body of artifacts-gone.
type ArtifactsGone struct {
// when the sweep found it
At *time.Time `json:"at,omitempty"`
// artifacts-gone
Event string `json:"event"`
// the key's fingerprint
Fingerprint string `json:"fingerprint"`
// every reference of the key the store does not hold, sorted
Gone []string `json:"gone"`
// the builder's done key
Key string `json:"key,omitempty"`
// the module whose builder's key names a gone artifact
Module string `json:"module"`
}
// ArtifactsInput is what artifacts takes.
type ArtifactsInput struct {
// "true": list the collected references too (counted always)
Collected string `json:"collected,omitempty"`
// only this repository's references, as <module>/<artifact> (the counts stay whole)
Repository string `json:"repository,omitempty"`
}
// The values of ArtifactsInput's collected.
const (
ArtifactsInputCollectedTrue = "true"
ArtifactsInputCollectedFalse = "false"
)
// ArtifactsAnswer is what artifacts answers.
type ArtifactsAnswer = map[string]any
// HeldInput is what held takes.
type HeldInput struct {
// the artifacts' references, artifact-store://<module>/<artifact>/blobs/sha256:<hex>: a list, or one text separated by commas
References []string `json:"references"`
}
// HeldAnswer is what held answers.
type HeldAnswer struct {
// the references the store answered 404 for
Gone []string `json:"gone"`
// the references the store holds
Held []string `json:"held"`
// the store asked, host and port
Store string `json:"store,omitempty"`
// a reference the store could not be asked about, to why
Unknown map[string]string `json:"unknown,omitempty"`
}
// CollectInput is what collect takes.
type CollectInput struct {
// "true": let go of what is eligible (needs why); without it nothing is done
Confirm string `json:"confirm,omitempty"`
// let go of at most this many (500 by default, at most 5000)
Most string `json:"most,omitempty"`
// with confirm: why, required and recorded
Why string `json:"why,omitempty"`
}
// The values of CollectInput's confirm.
const (
CollectInputConfirmTrue = "true"
CollectInputConfirmFalse = "false"
)
// CollectAnswer is what collect answers.
type CollectAnswer = map[string]any
// MirrorsInput is what mirrors takes.
type MirrorsInput struct {
}
// MirrorsAnswer is what mirrors answers.
type MirrorsAnswer = map[string]any
// ImagesInput is what images takes.
type ImagesInput struct {
// the machine's name
Node string `json:"node"`
}
// ImagesAnswer is what images answers.
type ImagesAnswer = map[string]any
// ArtifactsGoneEvent is the body of the event artifacts-gone.
type ArtifactsGoneEvent = ArtifactsGone
@@ -1,54 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package meshartifactsweep
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/mesh-artifact-sweep/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: "artifacts-gone on the seat's subject", subject: "mesh.seat.mesh-artifact-sweep.event.artifacts-gone", given: "{\"at\":\"2026-11-05T15:04:05Z\",\"event\":\"artifacts-gone\",\"fingerprint\":\"f1\",\"gone\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"key\":\"ca-trust.archive.f1\",\"module\":\"ca-trust\"}", wire: "{\"at\":\"2026-11-05T15:04:05Z\",\"event\":\"artifacts-gone\",\"fingerprint\":\"f1\",\"gone\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"key\":\"ca-trust.archive.f1\",\"module\":\"ca-trust\"}",
decode: func(b []byte) (any, error) { var v ArtifactsGoneEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-artifact-sweep\\.event\\.artifacts-gone$")},
{name: "held asks the references", subject: "", given: "{\"references\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\"]}", wire: "{\"references\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\"]}",
decode: func(b []byte) (any, error) { var v HeldInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "held answers held, gone and unknown", subject: "", given: "{\"gone\":[\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"held\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"store\":\"store.test:5100\",\"unknown\":{\"artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\":\"the store answered 503\"}}", wire: "{\"gone\":[\"artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"],\"held\":[\"artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"store\":\"store.test:5100\",\"unknown\":{\"artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc\":\"the store answered 503\"}}",
decode: func(b []byte) (any, error) { var v HeldAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "held answers with nothing unknown", subject: "", given: "{\"gone\":[],\"held\":[],\"store\":\"store.test:5100\"}", wire: "{\"gone\":[],\"held\":[],\"store\":\"store.test:5100\"}",
decode: func(b []byte) (any, error) { var v HeldAnswer; 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)
}
}
}
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep
go 1.26.0
-717
View File
@@ -1,717 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// Package meshcatalog is the contract of the seat "mesh-catalog", defined whole (seats/mesh-catalog/seat.json): The catalog of the mesh: every module the trunk holds with its manifest, fingerprint and verdict, the packages published, and the seat list (ADR 0636 §1, §4).
package meshcatalog
import (
"bytes"
"encoding/json"
"errors"
"strings"
"time"
)
// Seat is the seat's name.
const Seat = "mesh-catalog"
const (
VerbModules = "modules"
VerbModule = "module"
VerbProvides = "provides"
VerbDependents = "dependents"
VerbSeatDefinitions = "seat-definitions"
)
const (
EventSeatDefinitionChanged = "seat-definition-changed"
EventSeatDefinitionRemoved = "seat-definition-removed"
EventManifestAccepted = "manifest-accepted"
EventManifestRefused = "manifest-refused"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbModules, VerbModule, VerbProvides, VerbDependents, VerbSeatDefinitions}
}
// Events is every event published on the seat's subject: a new copy each call.
func Events() []string {
return []string{EventSeatDefinitionChanged, EventSeatDefinitionRemoved, EventManifestAccepted, EventManifestRefused}
}
// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects.
const (
Family = "mesh-catalog.events"
FamilyMaxAgeDays = 7
)
// SubjectSeatDefinitionChanged is where seat-definition-changed is published.
const SubjectSeatDefinitionChanged = "mesh.seat.mesh-catalog.event.seat-definition-changed"
// SubjectSeatDefinitionRemoved is where seat-definition-removed is published.
const SubjectSeatDefinitionRemoved = "mesh.seat.mesh-catalog.event.seat-definition-removed"
// SubjectManifestAccepted is where manifest-accepted is published.
const SubjectManifestAccepted = "mesh.seat.mesh-catalog.event.manifest-accepted"
// SubjectManifestRefused is where manifest-refused is published.
const SubjectManifestRefused = "mesh.seat.mesh-catalog.event.manifest-refused"
const (
StateManifests = "manifests"
StateModules = "modules"
StateCandidates = "candidates"
StateVerdicts = "verdicts"
StatePredefined = "predefined"
StateConfigCandidates = "config-candidates"
StatePackages = "packages"
)
// ManifestsByDigestKey is the key of one value of the state manifests under the form "<digest>".
func ManifestsByDigestKey(digest string) string { return digest }
// ParseManifestsByDigestKey reads the parts of a key of the form "<digest>", in the order of the form; the last result is false for a key of another form.
func ParseManifestsByDigestKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodeManifestsByDigestValue reads one value of the state manifests under the form "<digest>", refusing a field ManifestsByDigestValue does not know and any text after the value.
func DecodeManifestsByDigestValue(raw []byte) (ManifestsByDigestValue, error) {
var v ManifestsByDigestValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// ModulesByModuleKey is the key of one value of the state modules under the form "<module>".
func ModulesByModuleKey(module string) string { return module }
// ParseModulesByModuleKey reads the parts of a key of the form "<module>", in the order of the form; the last result is false for a key of another form.
func ParseModulesByModuleKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodeModulesByModuleValue reads one value of the state modules under the form "<module>", refusing a field ModulesByModuleValue does not know and any text after the value.
func DecodeModulesByModuleValue(raw []byte) (ModulesByModuleValue, error) {
var v ModulesByModuleValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// CandidatesByGroupKey is the key of one value of the state candidates under the form "<group>".
func CandidatesByGroupKey(group string) string { return group }
// ParseCandidatesByGroupKey reads the parts of a key of the form "<group>", in the order of the form; the last result is false for a key of another form.
func ParseCandidatesByGroupKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodeCandidatesByGroupValue reads one value of the state candidates under the form "<group>", refusing a field CandidatesByGroupValue does not know and any text after the value.
func DecodeCandidatesByGroupValue(raw []byte) (CandidatesByGroupValue, error) {
var v CandidatesByGroupValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// VerdictsByDigestKey is the key of one value of the state verdicts under the form "<digest>".
func VerdictsByDigestKey(digest string) string { return digest }
// ParseVerdictsByDigestKey reads the parts of a key of the form "<digest>", in the order of the form; the last result is false for a key of another form.
func ParseVerdictsByDigestKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodeVerdictsByDigestValue reads one value of the state verdicts under the form "<digest>", refusing a field VerdictsByDigestValue does not know and any text after the value.
func DecodeVerdictsByDigestValue(raw []byte) (VerdictsByDigestValue, error) {
var v VerdictsByDigestValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PredefinedByListKey is the key of one value of the state predefined under the form "list".
func PredefinedByListKey() string { return "list" }
// ParsePredefinedByListKey reads the parts of a key of the form "list", in the order of the form; the last result is false for a key of another form.
func ParsePredefinedByListKey(key string) bool {
rest := key
if !strings.HasPrefix(rest, "list") {
return false
}
rest = rest[4:]
if rest != "" {
return false
}
return true
}
// DecodePredefinedByListValue reads one value of the state predefined under the form "list", refusing a field PredefinedByListValue does not know and any text after the value.
func DecodePredefinedByListValue(raw []byte) (PredefinedByListValue, error) {
var v PredefinedByListValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// ConfigCandidatesByPullKey is the key of one value of the state config-candidates under the form "<pull>".
func ConfigCandidatesByPullKey(pull string) string { return pull }
// ParseConfigCandidatesByPullKey reads the parts of a key of the form "<pull>", in the order of the form; the last result is false for a key of another form.
func ParseConfigCandidatesByPullKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodeConfigCandidatesByPullValue reads one value of the state config-candidates under the form "<pull>", refusing a field ConfigCandidatesByPullValue does not know and any text after the value.
func DecodeConfigCandidatesByPullValue(raw []byte) (ConfigCandidatesByPullValue, error) {
var v ConfigCandidatesByPullValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PackagesByModuleKey is the key of one value of the state packages under the form "<module>".
func PackagesByModuleKey(module string) string { return module }
// ParsePackagesByModuleKey reads the parts of a key of the form "<module>", in the order of the form; the last result is false for a key of another form.
func ParsePackagesByModuleKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodePackagesByModuleValue reads one value of the state packages under the form "<module>", refusing a field PackagesByModuleValue does not know and any text after the value.
func DecodePackagesByModuleValue(raw []byte) (PackagesByModuleValue, error) {
var v PackagesByModuleValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PackagesByPackageSeriesKey is the key of one value of the state packages under the form "<package>.<series>".
func PackagesByPackageSeriesKey(packagePart, series string) string { return packagePart + "." + series }
// ParsePackagesByPackageSeriesKey reads the parts of a key of the form "<package>.<series>", in the order of the form; the last result is false for a key of another form.
func ParsePackagesByPackageSeriesKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodePackagesByPackageSeriesValue reads one value of the state packages under the form "<package>.<series>", refusing a field PackagesByPackageSeriesValue does not know and any text after the value.
func DecodePackagesByPackageSeriesValue(raw []byte) (PackagesByPackageSeriesValue, error) {
var v PackagesByPackageSeriesValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// ToolchainUsed is a toolchain a fingerprint was computed with (ADR 0405 clause 7).
type ToolchainUsed struct {
Language string `json:"language"`
// the build module's recipe version
Recipe string `json:"recipe"`
// the installed toolchain's version
Version string `json:"version"`
}
// ModulesInput is what modules takes.
type ModulesInput struct {
}
// ModulesAnswerModulesItem is a type of the contract.
type ModulesAnswerModulesItem struct {
// the merge commit it was last changed at
Commit string `json:"commit"`
// its fingerprint (src2: and 64 hex digits); absent when it has none
Fp string `json:"fp,omitempty"`
Module string `json:"module"`
// why it has none
None string `json:"none,omitempty"`
// the module's directory in its repository, '.' for a root
Path string `json:"path,omitempty"`
Repository string `json:"repository"`
}
// ModulesAnswer is what modules answers.
type ModulesAnswer struct {
Modules []ModulesAnswerModulesItem `json:"modules"`
}
// ModuleInput is what module takes.
type ModuleInput struct {
// the module's name
Module string `json:"module"`
}
// ModuleAnswerModule is a type of the contract.
type ModuleAnswerModule struct {
// the merge commit it was last changed at
Commit string `json:"commit"`
// its fingerprint (src2: and 64 hex digits); absent when it has none
Fp string `json:"fp,omitempty"`
// the module's manifest as the trunk holds it
Manifest map[string]any `json:"manifest,omitempty"`
Module string `json:"module"`
// why it has none
None string `json:"none,omitempty"`
// the module's directory in its repository, '.' for a root
Path string `json:"path,omitempty"`
Repository string `json:"repository"`
}
// ModuleAnswer is what module answers.
type ModuleAnswer struct {
Module ModuleAnswerModule `json:"module"`
}
// ProvidesInput is what provides takes.
type ProvidesInput struct {
// the provision, such as postgres-database
Provision string `json:"provision"`
}
// ProvidesAnswer is what provides answers.
type ProvidesAnswer = map[string]any
// DependentsInput is what dependents takes.
type DependentsInput struct {
// the module's name
Module string `json:"module"`
}
// DependentsAnswer is what dependents answers.
type DependentsAnswer = map[string]any
// SeatDefinitionsInput is what seat-definitions takes.
type SeatDefinitionsInput struct {
// the seat's name; none lists every trunk seat
Seat string `json:"seat,omitempty"`
}
// SeatDefinitionsAnswerEntry is a type of the contract.
type SeatDefinitionsAnswerEntry struct {
// the definition, resolved with the parts it includes
Definition map[string]any `json:"definition,omitempty"`
Digest string `json:"digest"`
Seat string `json:"seat"`
// trunk
Source string `json:"source"`
// why the definition could not be read, when it could not
Unparsed string `json:"unparsed,omitempty"`
}
// SeatDefinitionsAnswerSeatsItem is a type of the contract.
type SeatDefinitionsAnswerSeatsItem struct {
Digest string `json:"digest"`
Seat string `json:"seat"`
// trunk
Source string `json:"source"`
}
// SeatDefinitionsAnswer is what seat-definitions answers.
type SeatDefinitionsAnswer struct {
Entry *SeatDefinitionsAnswerEntry `json:"entry,omitempty"`
Found bool `json:"found,omitempty"`
Revision int64 `json:"revision,omitempty"`
Seat string `json:"seat,omitempty"`
Seats []SeatDefinitionsAnswerSeatsItem `json:"seats,omitempty"`
}
// SeatDefinitionChangedEvent is the body of the event seat-definition-changed.
type SeatDefinitionChangedEvent struct {
Digest string `json:"digest"`
Seat string `json:"seat"`
// trunk
Source string `json:"source"`
}
// SeatDefinitionRemovedEvent is the body of the event seat-definition-removed.
type SeatDefinitionRemovedEvent struct {
Seat string `json:"seat"`
}
// ManifestAcceptedEventBusStateItem is a type of the contract.
type ManifestAcceptedEventBusStateItem struct {
Configuration map[string]any `json:"configuration,omitempty"`
Name string `json:"name"`
Options map[string]any `json:"options,omitempty"`
Storage string `json:"storage"`
Template string `json:"template,omitempty"`
Types []map[string]any `json:"types,omitempty"`
}
// ManifestAcceptedEvent is the body of the event manifest-accepted.
type ManifestAcceptedEvent struct {
BusState []ManifestAcceptedEventBusStateItem `json:"bus-state"`
Digest string `json:"digest"`
Judge string `json:"judge"`
Module string `json:"module"`
// the indexer's revision of the verdict it says; a reader asks its first view again on a gap in it; zero from an indexer that does not say it
Revision int64 `json:"revision,omitempty"`
V int64 `json:"v"`
}
// ManifestRefusedEvent is the body of the event manifest-refused.
type ManifestRefusedEvent struct {
Digest string `json:"digest"`
Judge string `json:"judge"`
Module string `json:"module"`
Problems []string `json:"problems"`
// the indexer's revision of the verdict it says; a reader asks its first view again on a gap in it; zero from an indexer that does not say it
Revision int64 `json:"revision,omitempty"`
V int64 `json:"v"`
}
// ManifestsByDigestValue is one value of the state manifests under the key <digest>.
type ManifestsByDigestValue struct {
// the manifest, UTF-8 JSON text
Manifest string `json:"manifest"`
Module string `json:"module"`
// the layout version
V int64 `json:"v,omitempty"`
}
// ModulesByModuleValueInputs2 is a type of the contract.
type ModulesByModuleValueInputs2 struct {
Bases []string `json:"bases,omitempty"`
Builder string `json:"builder,omitempty"`
Contexts map[string]string `json:"contexts,omitempty"`
ImportForm string `json:"import-form,omitempty"`
Imports []string `json:"imports,omitempty"`
Locks map[string]string `json:"locks,omitempty"`
Packages []string `json:"packages,omitempty"`
Paths map[string]string `json:"paths,omitempty"`
Tree string `json:"tree"`
}
// ModulesByModuleValue is one value of the state modules under the key <module>.
type ModulesByModuleValue struct {
Commit string `json:"commit"`
// the old-form fingerprint
Fp string `json:"fp,omitempty"`
FpSrc int64 `json:"fp-src,omitempty"`
// src2: fingerprint
Fp2 string `json:"fp2,omitempty"`
Inputs2 *ModulesByModuleValueInputs2 `json:"inputs2,omitempty"`
// manifest digest
Manifest string `json:"manifest,omitempty"`
Needs []string `json:"needs,omitempty"`
None string `json:"none,omitempty"`
// why it has none
None2 string `json:"none2,omitempty"`
Packages map[string]string `json:"packages,omitempty"`
Path string `json:"path,omitempty"`
Paths map[string][]string `json:"paths,omitempty"`
Problems []string `json:"problems,omitempty"`
Repository string `json:"repository"`
// the source revision
Src int64 `json:"src,omitempty"`
// by language: the toolchain FP2 was computed with
Toolchains map[string]ToolchainUsed `json:"toolchains,omitempty"`
Unknown []string `json:"unknown,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
Verdict string `json:"verdict,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
// CandidatesByGroupValuePullsItemFpsValue is a type of the contract.
type CandidatesByGroupValuePullsItemFpsValue struct {
Fp2 string `json:"fp2,omitempty"`
Manifest string `json:"manifest,omitempty"`
None2 string `json:"none2,omitempty"`
Packages map[string]string `json:"packages,omitempty"`
Paths map[string]string `json:"paths,omitempty"`
}
// CandidatesByGroupValuePullsItem is a type of the contract.
type CandidatesByGroupValuePullsItem struct {
Fps map[string]CandidatesByGroupValuePullsItemFpsValue `json:"fps,omitempty"`
Head string `json:"head"`
Modules []string `json:"modules"`
Number int64 `json:"number"`
Repository string `json:"repository"`
}
// CandidatesByGroupValue is one value of the state candidates under the key <group>.
type CandidatesByGroupValue struct {
// the source revision it is read on
Base int64 `json:"base"`
Fpset string `json:"fpset,omitempty"`
Group string `json:"group"`
Heads string `json:"heads"`
Pulls []CandidatesByGroupValuePullsItem `json:"pulls"`
// the layout version
V int64 `json:"v,omitempty"`
}
// VerdictsByDigestValue is one value of the state verdicts under the key <digest>.
type VerdictsByDigestValue struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
Judge string `json:"judge,omitempty"`
Module string `json:"module"`
Problems []string `json:"problems,omitempty"`
State string `json:"state"`
Unknown []string `json:"unknown,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
// PredefinedByListValue is one value of the state predefined under the key list.
type PredefinedByListValue struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
Commit string `json:"commit"`
// the foundation's predefined list as kept
List any `json:"list"`
// the layout version
V int64 `json:"v,omitempty"`
}
// ConfigCandidatesByPullValueDeclaresValueValue is a type of the contract.
type ConfigCandidatesByPullValueDeclaresValueValue struct {
// the default
Default any `json:"default"`
Description string `json:"description,omitempty"`
// the allowed values
Enum any `json:"enum,omitempty"`
Kind string `json:"kind"`
// a declared bound; 0 is a bound, absent is none
Maximum *float64 `json:"maximum,omitempty"`
// a declared bound; 0 is a bound, absent is none
Minimum *float64 `json:"minimum,omitempty"`
Pattern string `json:"pattern,omitempty"`
Predefined string `json:"predefined,omitempty"`
Required bool `json:"required,omitempty"`
Runtime bool `json:"runtime,omitempty"`
Trusted bool `json:"trusted,omitempty"`
Type string `json:"type,omitempty"`
Why string `json:"why"`
}
// ConfigCandidatesByPullValue is one value of the state config-candidates under the key <pull>.
type ConfigCandidatesByPullValue struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
// module to key to declaration
Declares map[string]map[string]ConfigCandidatesByPullValueDeclaresValueValue `json:"declares"`
Head string `json:"head"`
Pull string `json:"pull"`
// the layout version
V int64 `json:"v,omitempty"`
}
// PackagesByModuleValuePackagesItem is a type of the contract.
type PackagesByModuleValuePackagesItem struct {
Digest string `json:"digest"`
Package string `json:"package"`
}
// PackagesByModuleValue is one value of the state packages under the key <module>.
type PackagesByModuleValue struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
Commit string `json:"commit"`
Dir string `json:"dir,omitempty"`
// the Go module path
Module string `json:"module"`
Packages []PackagesByModuleValuePackagesItem `json:"packages"`
Repository string `json:"repository"`
// digest of go.mod and go.sum
Requires string `json:"requires,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
}
// PackagesByPackageSeriesValueNewestContentsPackagesItem is a type of the contract.
type PackagesByPackageSeriesValueNewestContentsPackagesItem struct {
Digest string `json:"digest"`
Package string `json:"package"`
}
// PackagesByPackageSeriesValueNewestContents is a type of the contract.
type PackagesByPackageSeriesValueNewestContents struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
Commit string `json:"commit"`
Dir string `json:"dir,omitempty"`
// the Go module path
Module string `json:"module"`
Packages []PackagesByPackageSeriesValueNewestContentsPackagesItem `json:"packages"`
Repository string `json:"repository"`
// digest of go.mod and go.sum
Requires string `json:"requires,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
}
// PackagesByPackageSeriesValueNewest is a type of the contract.
type PackagesByPackageSeriesValueNewest struct {
Commit string `json:"commit,omitempty"`
Contents *PackagesByPackageSeriesValueNewestContents `json:"contents,omitempty"`
Digest string `json:"digest,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Version string `json:"version"`
}
// PackagesByPackageSeriesValueNumberedContentsPackagesItem is a type of the contract.
type PackagesByPackageSeriesValueNumberedContentsPackagesItem struct {
Digest string `json:"digest"`
Package string `json:"package"`
}
// PackagesByPackageSeriesValueNumberedContents is a type of the contract.
type PackagesByPackageSeriesValueNumberedContents struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
Commit string `json:"commit"`
Dir string `json:"dir,omitempty"`
// the Go module path
Module string `json:"module"`
Packages []PackagesByPackageSeriesValueNumberedContentsPackagesItem `json:"packages"`
Repository string `json:"repository"`
// digest of go.mod and go.sum
Requires string `json:"requires,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
}
// PackagesByPackageSeriesValueNumbered is a type of the contract.
type PackagesByPackageSeriesValueNumbered struct {
Commit string `json:"commit,omitempty"`
Contents *PackagesByPackageSeriesValueNumberedContents `json:"contents,omitempty"`
Digest string `json:"digest,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Version string `json:"version"`
}
// PackagesByPackageSeriesValue is one value of the state packages under the key <package>.<series>.
type PackagesByPackageSeriesValue struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
GoModule string `json:"go-module,omitempty"`
Name string `json:"name"`
Newest *PackagesByPackageSeriesValueNewest `json:"newest,omitempty"`
Numbered *PackagesByPackageSeriesValueNumbered `json:"numbered,omitempty"`
Package string `json:"package,omitempty"`
Seat string `json:"seat,omitempty"`
Series string `json:"series"`
// the layout version
V int64 `json:"v,omitempty"`
}
@@ -1,82 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package meshcatalog
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/mesh-catalog/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: "a manifest (xclip, read through mesh-catalog.module)", subject: "", given: "{\"manifest\":\"{\\\"build\\\":{\\\"artifacts\\\":[{\\\"binary\\\":\\\"xclip-tools\\\",\\\"from\\\":\\\"cmd/xclip-tools\\\",\\\"kind\\\":\\\"bundle\\\",\\\"language\\\":\\\"go\\\",\\\"loads\\\":[\\\"xclip-tools\\\"],\\\"name\\\":\\\"tools\\\",\\\"system\\\":\\\"arch\\\"}]},\\\"capabilities\\\":[\\\"package-manager\\\"],\\\"module\\\":\\\"xclip\\\",\\\"resources\\\":[{\\\"id\\\":\\\"package\\\",\\\"package\\\":\\\"xclip\\\",\\\"type\\\":\\\"package\\\"}],\\\"tools\\\":[\\\"xclip_copy\\\",\\\"xclip_paste\\\",\\\"xclip_targets\\\",\\\"xclip_session\\\"],\\\"version\\\":\\\"1\\\"}\",\"module\":\"xclip\",\"v\":1}", wire: "{\"manifest\":\"{\\\"build\\\":{\\\"artifacts\\\":[{\\\"binary\\\":\\\"xclip-tools\\\",\\\"from\\\":\\\"cmd/xclip-tools\\\",\\\"kind\\\":\\\"bundle\\\",\\\"language\\\":\\\"go\\\",\\\"loads\\\":[\\\"xclip-tools\\\"],\\\"name\\\":\\\"tools\\\",\\\"system\\\":\\\"arch\\\"}]},\\\"capabilities\\\":[\\\"package-manager\\\"],\\\"module\\\":\\\"xclip\\\",\\\"resources\\\":[{\\\"id\\\":\\\"package\\\",\\\"package\\\":\\\"xclip\\\",\\\"type\\\":\\\"package\\\"}],\\\"tools\\\":[\\\"xclip_copy\\\",\\\"xclip_paste\\\",\\\"xclip_targets\\\",\\\"xclip_session\\\"],\\\"version\\\":\\\"1\\\"}\",\"module\":\"xclip\",\"v\":1}",
decode: func(b []byte) (any, error) { var v ManifestsByDigestValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a module (xclip, read through mesh-catalog.modules; the inputs and toolchains no tool reads are not here)", subject: "", given: "{\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp2\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"path\":\"modules/xclip\",\"repository\":\"novox/mesh-catalog\",\"v\":1}", wire: "{\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp2\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"path\":\"modules/xclip\",\"repository\":\"novox/mesh-catalog\",\"v\":1}",
decode: func(b []byte) (any, error) { var v ModulesByModuleValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a candidate", subject: "", given: "{\"base\":3,\"group\":\"feat/registry\",\"heads\":\"0123456789ab\",\"pulls\":[{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"modules\":[\"nats\"],\"number\":1,\"repository\":\"novox/mesh-sdk\"}],\"v\":1}", wire: "{\"base\":3,\"group\":\"feat/registry\",\"heads\":\"0123456789ab\",\"pulls\":[{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"modules\":[\"nats\"],\"number\":1,\"repository\":\"novox/mesh-sdk\"}],\"v\":1}",
decode: func(b []byte) (any, error) { var v CandidatesByGroupValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a refused verdict (nats, read through the indexer's verdicts tool)", subject: "", given: "{\"at\":\"2026-11-04T18:51:12.614583047+01:00\",\"judge\":\"exe:661e6fb424d7ffea\",\"module\":\"nats\",\"problems\":[\"this is not a module manifest: a state is either a name or {name, history, ttl-seconds, per-machine, key-ttl}: json: unknown field \\\"read-only\\\" (read without state[2].read-only)\"],\"state\":\"refused\"}", wire: "{\"at\":\"2026-11-04T18:51:12.614583047+01:00\",\"judge\":\"exe:661e6fb424d7ffea\",\"module\":\"nats\",\"problems\":[\"this is not a module manifest: a state is either a name or {name, history, ttl-seconds, per-machine, key-ttl}: json: unknown field \\\"read-only\\\" (read without state[2].read-only)\"],\"state\":\"refused\"}",
decode: func(b []byte) (any, error) { var v VerdictsByDigestValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "the predefined list", subject: "", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"list\":{\"streams\":[]},\"v\":1}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"list\":{\"streams\":[]},\"v\":1}",
decode: func(b []byte) (any, error) { var v PredefinedByListValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a pull request's configuration", subject: "", given: "{\"declares\":{\"nats\":{\"limit\":{\"default\":1,\"kind\":\"setting\",\"why\":\"a bound\"}}},\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"pull\":\"novox/mesh-catalog#1\",\"v\":1}", wire: "{\"declares\":{\"nats\":{\"limit\":{\"default\":1,\"kind\":\"setting\",\"why\":\"a bound\"}}},\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"pull\":\"novox/mesh-catalog#1\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v ConfigCandidatesByPullValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a pull request's configuration state whose bounds are declared 0 (a zero is a bound, not none)", subject: "", given: "{\"declares\":{\"nats\":{\"limit\":{\"default\":0,\"kind\":\"setting\",\"maximum\":0,\"minimum\":0,\"why\":\"a bound\"}}},\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"pull\":\"novox/mesh-catalog#1\",\"v\":1}", wire: "{\"declares\":{\"nats\":{\"limit\":{\"default\":0,\"kind\":\"setting\",\"maximum\":0,\"minimum\":0,\"why\":\"a bound\"}}},\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"pull\":\"novox/mesh-catalog#1\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v ConfigCandidatesByPullValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a published Go module", subject: "", given: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"module\":\"git.novox.be/novox/mesh-sdk/go\",\"packages\":[{\"digest\":\"ab\",\"package\":\"git.novox.be/novox/mesh-sdk/go/keys\"}],\"repository\":\"novox/mesh-sdk\",\"v\":1}", wire: "{\"commit\":\"cccccccccccccccccccccccccccccccccccccccc\",\"module\":\"git.novox.be/novox/mesh-sdk/go\",\"packages\":[{\"digest\":\"ab\",\"package\":\"git.novox.be/novox/mesh-sdk/go/keys\"}],\"repository\":\"novox/mesh-sdk\",\"v\":1}",
decode: func(b []byte) (any, error) { var v PackagesByModuleValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a package series", subject: "", given: "{\"name\":\"go-keys\",\"series\":\"0.1\",\"v\":1}", wire: "{\"name\":\"go-keys\",\"series\":\"0.1\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v PackagesByPackageSeriesValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a module with its toolchain", subject: "", given: "{\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp2\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"repository\":\"novox/mesh-catalog\",\"toolchains\":{\"go\":{\"language\":\"go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"v\":1}", wire: "{\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp2\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"repository\":\"novox/mesh-catalog\",\"toolchains\":{\"go\":{\"language\":\"go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"v\":1}",
decode: func(b []byte) (any, error) { var v ModulesByModuleValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "the modules verb (captured: the first three modules)", subject: "", given: "{\"modules\":[{\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp\":\"src2:5e4a1b4ad4ea6175c05e4655ce58aeb957547149d4c9805eb27636efd50c1c87\",\"module\":\"adwaita\",\"path\":\"modules/adwaita\",\"repository\":\"novox/mesh-catalog\"},{\"commit\":\"b112adcecc41d18f54190cbd2fc864199b70c518\",\"fp\":\"src2:7dd0db8e98224f46121a5ae05c0b9c5a430eff531a5120aa40b1a59237853a3f\",\"module\":\"amqp-email-forwarder\",\"path\":\".\",\"repository\":\"jschoubben/amqp-email-forwarder\"},{\"commit\":\"dd458cb155f5f5fa3ed824a23728e444be5b156a\",\"fp\":\"src2:a98f8152f0a837dd5ba883a15efaf110ba88100193157e4bdb1a93ba55209371\",\"module\":\"artifact-store-tools\",\"path\":\"modules/artifact-store-tools\",\"repository\":\"novox/mesh-catalog\"}]}", wire: "{\"modules\":[{\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp\":\"src2:5e4a1b4ad4ea6175c05e4655ce58aeb957547149d4c9805eb27636efd50c1c87\",\"module\":\"adwaita\",\"path\":\"modules/adwaita\",\"repository\":\"novox/mesh-catalog\"},{\"commit\":\"b112adcecc41d18f54190cbd2fc864199b70c518\",\"fp\":\"src2:7dd0db8e98224f46121a5ae05c0b9c5a430eff531a5120aa40b1a59237853a3f\",\"module\":\"amqp-email-forwarder\",\"path\":\".\",\"repository\":\"jschoubben/amqp-email-forwarder\"},{\"commit\":\"dd458cb155f5f5fa3ed824a23728e444be5b156a\",\"fp\":\"src2:a98f8152f0a837dd5ba883a15efaf110ba88100193157e4bdb1a93ba55209371\",\"module\":\"artifact-store-tools\",\"path\":\"modules/artifact-store-tools\",\"repository\":\"novox/mesh-catalog\"}]}",
decode: func(b []byte) (any, error) { var v ModulesAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "the module verb (captured: xclip)", subject: "", given: "{\"module\":{\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"manifest\":{\"build\":{\"artifacts\":[{\"binary\":\"xclip-tools\",\"from\":\"cmd/xclip-tools\",\"kind\":\"bundle\",\"language\":\"go\",\"loads\":[\"xclip-tools\"],\"name\":\"tools\",\"system\":\"arch\"}]},\"capabilities\":[\"package-manager\"],\"module\":\"xclip\",\"resources\":[{\"id\":\"package\",\"package\":\"xclip\",\"type\":\"package\"}],\"tools\":[\"xclip_copy\",\"xclip_paste\",\"xclip_targets\",\"xclip_session\"],\"version\":\"1\"},\"module\":\"xclip\",\"path\":\"modules/xclip\",\"repository\":\"novox/mesh-catalog\"}}", wire: "{\"module\":{\"commit\":\"f469eb387a3015569d9b16c66ed418e9dd098f64\",\"fp\":\"src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56\",\"manifest\":{\"build\":{\"artifacts\":[{\"binary\":\"xclip-tools\",\"from\":\"cmd/xclip-tools\",\"kind\":\"bundle\",\"language\":\"go\",\"loads\":[\"xclip-tools\"],\"name\":\"tools\",\"system\":\"arch\"}]},\"capabilities\":[\"package-manager\"],\"module\":\"xclip\",\"resources\":[{\"id\":\"package\",\"package\":\"xclip\",\"type\":\"package\"}],\"tools\":[\"xclip_copy\",\"xclip_paste\",\"xclip_targets\",\"xclip_session\"],\"version\":\"1\"},\"module\":\"xclip\",\"path\":\"modules/xclip\",\"repository\":\"novox/mesh-catalog\"}}",
decode: func(b []byte) (any, error) { var v ModuleAnswer; 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)
}
}
}
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog
go 1.26.0
-455
View File
@@ -1,455 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// Package meshdelivery is the contract of the seat "mesh-delivery", defined whole (seats/mesh-delivery/seat.json): The delivery gate: reads and steers deliveries, merges and the checks that decide them; the merge is made on the operator's Approve once both checks passed.
package meshdelivery
import (
"bytes"
"encoding/json"
"errors"
"strings"
"time"
)
// Seat is the seat's name.
const Seat = "mesh-delivery"
const (
VerbDeliveries = "deliveries"
VerbTimes = "times"
VerbShow = "show"
VerbGroups = "groups"
VerbWhatIf = "what-if"
VerbChecks = "checks"
VerbTable = "table"
VerbStalled = "stalled"
VerbRecheck = "recheck"
VerbRelease = "release"
VerbStop = "stop"
VerbClose = "close"
VerbRetireHistory = "retire-history"
VerbSettle = "settle"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbDeliveries, VerbTimes, VerbShow, VerbGroups, VerbWhatIf, VerbChecks, VerbTable, VerbStalled, VerbRecheck, VerbRelease, VerbStop, VerbClose, VerbRetireHistory, VerbSettle}
}
const (
StateSource = "source"
StateMerges = "merges"
StateAsks = "asks"
StateOwed = "owed"
StateTurns = "turns"
)
// SourceByMainKey is the key of one value of the state source under the form "main".
func SourceByMainKey() string { return "main" }
// ParseSourceByMainKey reads the parts of a key of the form "main", in the order of the form; the last result is false for a key of another form.
func ParseSourceByMainKey(key string) bool {
rest := key
if !strings.HasPrefix(rest, "main") {
return false
}
rest = rest[4:]
if rest != "" {
return false
}
return true
}
// DecodeSourceByMainValue reads one value of the state source under the form "main", refusing a field SourceByMainValue does not know and any text after the value.
func DecodeSourceByMainValue(raw []byte) (SourceByMainValue, error) {
var v SourceByMainValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// MergesByGroupKey is the key of one value of the state merges under the form "<group>".
func MergesByGroupKey(group string) string { return group }
// ParseMergesByGroupKey reads the parts of a key of the form "<group>", in the order of the form; the last result is false for a key of another form.
func ParseMergesByGroupKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodeMergesByGroupValue reads one value of the state merges under the form "<group>", refusing a field MergesByGroupValue does not know and any text after the value.
func DecodeMergesByGroupValue(raw []byte) (MergesByGroupValue, error) {
var v MergesByGroupValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// AsksByMergeGroupHeadsKey is the key of one value of the state asks under the form "merge.<group>.<heads>".
func AsksByMergeGroupHeadsKey(group, heads string) string { return "merge." + group + "." + heads }
// ParseAsksByMergeGroupHeadsKey reads the parts of a key of the form "merge.<group>.<heads>", in the order of the form; the last result is false for a key of another form.
func ParseAsksByMergeGroupHeadsKey(key string) (string, string, bool) {
rest := key
if !strings.HasPrefix(rest, "merge.") {
return "", "", false
}
rest = rest[6:]
v1, after1, found1 := strings.Cut(rest, ".")
if !found1 || v1 == "" {
return "", "", false
}
rest = after1
if rest == "" {
return "", "", false
}
v3 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v1, v3, true
}
// DecodeAsksByMergeGroupHeadsValue reads one value of the state asks under the form "merge.<group>.<heads>", refusing a field AsksByMergeGroupHeadsValue does not know and any text after the value.
func DecodeAsksByMergeGroupHeadsValue(raw []byte) (AsksByMergeGroupHeadsValue, error) {
var v AsksByMergeGroupHeadsValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// OwedByRepositoryNumberHeadActKey is the key of one value of the state owed under the form "<repository>.<number>.<head>.<act>".
func OwedByRepositoryNumberHeadActKey(repository, number, head, act string) string {
return repository + "." + number + "." + head + "." + act
}
// ParseOwedByRepositoryNumberHeadActKey reads the parts of a key of the form "<repository>.<number>.<head>.<act>", in the order of the form; the last result is false for a key of another form.
func ParseOwedByRepositoryNumberHeadActKey(key string) (string, string, string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", "", "", false
}
rest = after0
v2, after2, found2 := strings.Cut(rest, ".")
if !found2 || v2 == "" {
return "", "", "", "", false
}
rest = after2
v4, after4, found4 := strings.Cut(rest, ".")
if !found4 || v4 == "" {
return "", "", "", "", false
}
rest = after4
if rest == "" {
return "", "", "", "", false
}
v6 := rest
rest = ""
if rest != "" {
return "", "", "", "", false
}
return v0, v2, v4, v6, true
}
// DecodeOwedByRepositoryNumberHeadActValue reads one value of the state owed under the form "<repository>.<number>.<head>.<act>", refusing a field OwedByRepositoryNumberHeadActValue does not know and any text after the value.
func DecodeOwedByRepositoryNumberHeadActValue(raw []byte) (OwedByRepositoryNumberHeadActValue, error) {
var v OwedByRepositoryNumberHeadActValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// TurnsByRepositoryKey is the key of one value of the state turns under the form "<repository>".
func TurnsByRepositoryKey(repository string) string { return repository }
// ParseTurnsByRepositoryKey reads the parts of a key of the form "<repository>", in the order of the form; the last result is false for a key of another form.
func ParseTurnsByRepositoryKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodeTurnsByRepositoryValue reads one value of the state turns under the form "<repository>", refusing a field TurnsByRepositoryValue does not know and any text after the value.
func DecodeTurnsByRepositoryValue(raw []byte) (TurnsByRepositoryValue, error) {
var v TurnsByRepositoryValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// DeliveriesInput is what deliveries takes.
type DeliveriesInput struct {
// "true": the final ones of the last thirty days too
All string `json:"all,omitempty"`
// a group's id (its branch name)
Group string `json:"group,omitempty"`
// owner/repository
Repository string `json:"repository,omitempty"`
// one state, e.g. delivering or held
State string `json:"state,omitempty"`
}
// The values of DeliveriesInput's all.
const (
DeliveriesInputAllTrue = "true"
DeliveriesInputAllFalse = "false"
)
// DeliveriesAnswer is what deliveries answers.
type DeliveriesAnswer = map[string]any
// TimesInput is what times takes.
type TimesInput struct {
}
// TimesAnswer is what times answers.
type TimesAnswer = map[string]any
// ShowInput is what show takes.
type ShowInput struct {
// a delivery's id, or a group's
ID string `json:"id"`
}
// ShowAnswer is what show answers.
type ShowAnswer = map[string]any
// GroupsInput is what groups takes.
type GroupsInput struct {
// "true": the groups that ended too
All string `json:"all,omitempty"`
}
// The values of GroupsInput's all.
const (
GroupsInputAllTrue = "true"
GroupsInputAllFalse = "false"
)
// GroupsAnswer is what groups answers.
type GroupsAnswer = map[string]any
// WhatIfInput is what what-if takes.
type WhatIfInput struct {
}
// WhatIfAnswer is what what-if answers.
type WhatIfAnswer = map[string]any
// ChecksInput is what checks takes.
type ChecksInput struct {
// a commit's sha, or the start of one, instead of a pull request
Commit string `json:"commit,omitempty"`
// a pull request's number: its head is read
Number string `json:"number,omitempty"`
// owner/repository
Repository string `json:"repository"`
}
// ChecksAnswer is what checks answers.
type ChecksAnswer = map[string]any
// TableInput is what table takes.
type TableInput struct {
}
// TableAnswer is what table answers.
type TableAnswer = map[string]any
// StalledInput is what stalled takes.
type StalledInput struct {
}
// StalledAnswer is what stalled answers.
type StalledAnswer = map[string]any
// RecheckInput is what recheck takes.
type RecheckInput struct {
// the delivery's id
ID string `json:"id"`
// why, kept with the transition
Why string `json:"why"`
}
// RecheckAnswer is what recheck answers.
type RecheckAnswer = map[string]any
// ReleaseInput is what release takes.
type ReleaseInput struct {
}
// ReleaseAnswer is what release answers.
type ReleaseAnswer = map[string]any
// StopInput is what stop takes.
type StopInput struct {
}
// StopAnswer is what stop answers.
type StopAnswer = map[string]any
// CloseInput is what close takes.
type CloseInput struct {
// the delivery's id
ID string `json:"id"`
// the condition it answers
Why string `json:"why"`
}
// CloseAnswer is what close answers.
type CloseAnswer = map[string]any
// RetireHistoryInput is what retire-history takes.
type RetireHistoryInput struct {
}
// RetireHistoryAnswer is what retire-history answers.
type RetireHistoryAnswer = map[string]any
// SettleInput is what settle takes.
type SettleInput struct {
// the held deliveries' ids (owner/repository@commit)
Ids []string `json:"ids"`
// why they may be settled, shown to the operator
Why string `json:"why"`
}
// SettleAnswer is what settle answers.
type SettleAnswer = map[string]any
// SourceByMainValue is one value of the state source under the key main.
type SourceByMainValue struct {
// the group that moved it
Group string `json:"group"`
// repository to the merged head, a full commit
Heads map[string]string `json:"heads"`
// the layout version
V int64 `json:"v,omitempty"`
}
// MergesByGroupValuePullsItem is a type of the contract.
type MergesByGroupValuePullsItem struct {
Commit string `json:"commit,omitempty"`
Head string `json:"head"`
Number int64 `json:"number"`
Repository string `json:"repository"`
// merging, merged or refused
State string `json:"state"`
Words string `json:"words,omitempty"`
}
// MergesByGroupValue is one value of the state merges under the key <group>.
type MergesByGroupValue struct {
// <repository>@<number> to the approved head
Approved map[string]string `json:"approved,omitempty"`
// the ask key
Ask string `json:"ask"`
Group string `json:"group"`
Pulls []MergesByGroupValuePullsItem `json:"pulls"`
// the layout version
V int64 `json:"v,omitempty"`
}
// AsksByMergeGroupHeadsValueHeadsItem is a type of the contract.
type AsksByMergeGroupHeadsValueHeadsItem struct {
// the full commit
Head string `json:"head"`
Number int64 `json:"number"`
// <owner>/<name>
Repository string `json:"repository"`
}
// AsksByMergeGroupHeadsValue is one value of the state asks under the key merge.<group>.<heads>.
type AsksByMergeGroupHeadsValue struct {
// the operator-channel ask as raised
Ask any `json:"ask"`
// the number of times it was asked
Asking int64 `json:"asking,omitempty"`
Group string `json:"group"`
Heads []AsksByMergeGroupHeadsValueHeadsItem `json:"heads"`
// the layout version
V int64 `json:"v,omitempty"`
}
// OwedByRepositoryNumberHeadActValue is one value of the state owed under the key <repository>.<number>.<head>.<act>.
type OwedByRepositoryNumberHeadActValue struct {
// status=<context>, view, note or comment.<seq>
Act string `json:"act"`
Context string `json:"context,omitempty"`
Ref string `json:"ref,omitempty"`
State string `json:"state,omitempty"`
Target string `json:"target,omitempty"`
Text string `json:"text,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
}
// TurnsByRepositoryValueWaitingItem is a type of the contract.
type TurnsByRepositoryValueWaitingItem struct {
// when (RFC 3339 time)
Approved time.Time `json:"approved"`
Ask string `json:"ask,omitempty"`
Group string `json:"group"`
Heads string `json:"heads"`
// when (RFC 3339 time)
Since time.Time `json:"since"`
Step string `json:"step,omitempty"`
WaitsFor string `json:"waits-for,omitempty"`
// when (RFC 3339 time)
WaitsSince *time.Time `json:"waits-since,omitempty"`
}
// TurnsByRepositoryValue is one value of the state turns under the key <repository>.
type TurnsByRepositoryValue struct {
Repository string `json:"repository"`
// the layout version
V int64 `json:"v,omitempty"`
Waiting []TurnsByRepositoryValueWaitingItem `json:"waiting"`
}
@@ -1,64 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package meshdelivery
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/mesh-delivery/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: "the source revision", subject: "", given: "{\"group\":\"feat/registry\",\"heads\":{\"novox/mesh-sdk\":\"cccccccccccccccccccccccccccccccccccccccc\"},\"v\":1}", wire: "{\"group\":\"feat/registry\",\"heads\":{\"novox/mesh-sdk\":\"cccccccccccccccccccccccccccccccccccccccc\"},\"v\":1}",
decode: func(b []byte) (any, error) { var v SourceByMainValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a merge", subject: "", given: "{\"ask\":\"merge.feat=registry.0123456789ab\",\"group\":\"feat/registry\",\"pulls\":[{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"number\":1,\"repository\":\"novox/mesh-sdk\",\"state\":\"merging\"}],\"v\":1}", wire: "{\"ask\":\"merge.feat=registry.0123456789ab\",\"group\":\"feat/registry\",\"pulls\":[{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"number\":1,\"repository\":\"novox/mesh-sdk\",\"state\":\"merging\"}],\"v\":1}",
decode: func(b []byte) (any, error) { var v MergesByGroupValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "an ask", subject: "", given: "{\"ask\":{\"headline\":\"merge\"},\"group\":\"feat/registry\",\"heads\":[{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"number\":1,\"repository\":\"novox/mesh-sdk\"}],\"v\":1}", wire: "{\"ask\":{\"headline\":\"merge\"},\"group\":\"feat/registry\",\"heads\":[{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"number\":1,\"repository\":\"novox/mesh-sdk\"}],\"v\":1}",
decode: func(b []byte) (any, error) {
var v AsksByMergeGroupHeadsValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "an act owed", subject: "", given: "{\"act\":\"view\",\"v\":1}", wire: "{\"act\":\"view\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v OwedByRepositoryNumberHeadActValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a turn", subject: "", given: "{\"repository\":\"novox/mesh-sdk\",\"v\":1,\"waiting\":[{\"approved\":\"2026-11-05T10:00:00Z\",\"group\":\"feat/registry\",\"heads\":\"0123456789ab\",\"since\":\"2026-11-05T10:01:00Z\"}]}", wire: "{\"repository\":\"novox/mesh-sdk\",\"v\":1,\"waiting\":[{\"approved\":\"2026-11-05T10:00:00Z\",\"group\":\"feat/registry\",\"heads\":\"0123456789ab\",\"since\":\"2026-11-05T10:01:00Z\"}]}",
decode: func(b []byte) (any, error) { var v TurnsByRepositoryValue; 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)
}
}
}
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery
go 1.26.0
@@ -1,450 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// Package meshdeliverychecker is the contract of the seat "mesh-delivery-checker", defined whole (seats/mesh-delivery-checker/seat.json): The merge check's tries: start one, read one, end one. The checker's own events stay under its own subjects (ADR 0620 §2).
package meshdeliverychecker
import (
"bytes"
"encoding/json"
"errors"
"strings"
)
// Seat is the seat's name.
const Seat = "mesh-delivery-checker"
const (
VerbTry = "try"
VerbTryRead = "try-read"
VerbTryEnd = "try-end"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbTry, VerbTryRead, VerbTryEnd}
}
// The holder's own events, listed beside the seat and published under the holder's subjects (ADR 0620 §2).
const (
HolderEventVerdict = "verdict"
HolderEventTagWanted = "tag-wanted"
HolderEventPreviewWritten = "preview.written"
)
// HolderSubjectVerdict is where the holder publishes verdict, kept in checker.asks.
func HolderSubjectVerdict(holder string) string { return "mesh.stream." + holder + ".event.verdict" }
// HolderSubjectTagWanted is where the holder publishes tag-wanted, kept in checker.asks.
func HolderSubjectTagWanted(holder string) string {
return "mesh.stream." + holder + ".event.tag-wanted"
}
// HolderSubjectPreviewWritten is where the holder publishes preview.written, kept in checks.
func HolderSubjectPreviewWritten(holder string) string {
return "mesh.stream." + holder + ".event.preview.written"
}
const (
StateVerdicts = "verdicts"
StateKept = "kept"
StateOwed = "owed"
StatePreviews = "previews"
)
// VerdictsByGroupHeadsKey is the key of one value of the state verdicts under the form "<group>.<heads>".
func VerdictsByGroupHeadsKey(group, heads string) string { return group + "." + heads }
// ParseVerdictsByGroupHeadsKey reads the parts of a key of the form "<group>.<heads>", in the order of the form; the last result is false for a key of another form.
func ParseVerdictsByGroupHeadsKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeVerdictsByGroupHeadsValue reads one value of the state verdicts under the form "<group>.<heads>", refusing a field VerdictsByGroupHeadsValue does not know and any text after the value.
func DecodeVerdictsByGroupHeadsValue(raw []byte) (VerdictsByGroupHeadsValue, error) {
var v VerdictsByGroupHeadsValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// KeptByLevelDigestKey is the key of one value of the state kept under the form "<level>.<digest>".
func KeptByLevelDigestKey(level, digest string) string { return level + "." + digest }
// ParseKeptByLevelDigestKey reads the parts of a key of the form "<level>.<digest>", in the order of the form; the last result is false for a key of another form.
func ParseKeptByLevelDigestKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeKeptByLevelDigestValue reads one value of the state kept under the form "<level>.<digest>", refusing a field KeptByLevelDigestValue does not know and any text after the value.
func DecodeKeptByLevelDigestValue(raw []byte) (KeptByLevelDigestValue, error) {
var v KeptByLevelDigestValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// OwedByRepositoryNumberHeadActKey is the key of one value of the state owed under the form "<repository>.<number>.<head>.<act>".
func OwedByRepositoryNumberHeadActKey(repository, number, head, act string) string {
return repository + "." + number + "." + head + "." + act
}
// ParseOwedByRepositoryNumberHeadActKey reads the parts of a key of the form "<repository>.<number>.<head>.<act>", in the order of the form; the last result is false for a key of another form.
func ParseOwedByRepositoryNumberHeadActKey(key string) (string, string, string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", "", "", false
}
rest = after0
v2, after2, found2 := strings.Cut(rest, ".")
if !found2 || v2 == "" {
return "", "", "", "", false
}
rest = after2
v4, after4, found4 := strings.Cut(rest, ".")
if !found4 || v4 == "" {
return "", "", "", "", false
}
rest = after4
if rest == "" {
return "", "", "", "", false
}
v6 := rest
rest = ""
if rest != "" {
return "", "", "", "", false
}
return v0, v2, v4, v6, true
}
// DecodeOwedByRepositoryNumberHeadActValue reads one value of the state owed under the form "<repository>.<number>.<head>.<act>", refusing a field OwedByRepositoryNumberHeadActValue does not know and any text after the value.
func DecodeOwedByRepositoryNumberHeadActValue(raw []byte) (OwedByRepositoryNumberHeadActValue, error) {
var v OwedByRepositoryNumberHeadActValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PreviewsByRepositoryHeadTrunkKey is the key of one value of the state previews under the form "<repository>.<head>.<trunk>".
func PreviewsByRepositoryHeadTrunkKey(repository, head, trunk string) string {
return repository + "." + head + "." + trunk
}
// ParsePreviewsByRepositoryHeadTrunkKey reads the parts of a key of the form "<repository>.<head>.<trunk>", in the order of the form; the last result is false for a key of another form.
func ParsePreviewsByRepositoryHeadTrunkKey(key string) (string, string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", "", false
}
rest = after0
v2, after2, found2 := strings.Cut(rest, ".")
if !found2 || v2 == "" {
return "", "", "", false
}
rest = after2
if rest == "" {
return "", "", "", false
}
v4 := rest
rest = ""
if rest != "" {
return "", "", "", false
}
return v0, v2, v4, true
}
// DecodePreviewsByRepositoryHeadTrunkValue reads one value of the state previews under the form "<repository>.<head>.<trunk>", refusing a field PreviewsByRepositoryHeadTrunkValue does not know and any text after the value.
func DecodePreviewsByRepositoryHeadTrunkValue(raw []byte) (PreviewsByRepositoryHeadTrunkValue, error) {
var v PreviewsByRepositoryHeadTrunkValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// TryInputMembersItem is a type of the contract.
type TryInputMembersItem struct {
// the full commit to try, 40 or 64 hex digits
Head string `json:"head"`
// <owner>/<name>
Repository string `json:"repository"`
// the trunk commit to merge it onto, or the trunk's branch for its head now; the trunk's head when absent
Trunk string `json:"trunk,omitempty"`
}
// TryInput is what try takes.
type TryInput struct {
// the full commit to try, 40 or 64 hex digits, for a try of one member (instead of members)
Head string `json:"head,omitempty"`
// unit or integration: only this level; both when absent
Level string `json:"level,omitempty"`
// the group: one repository at most once, each at the exact commit asked and the trunk commit it is merged onto; instead of repository and head
Members []TryInputMembersItem `json:"members,omitempty"`
// <owner>/<name>, for a try of one member (instead of members)
Repository string `json:"repository,omitempty"`
// a go test -run pattern: only those tests run, and nothing is kept
Tests string `json:"tests,omitempty"`
// the trunk commit to merge it onto, or the trunk's branch for its head now; the trunk's head when absent
Trunk string `json:"trunk,omitempty"`
}
// TryAnswer is what try answers.
type TryAnswer struct {
// the try's id
ID string `json:"id"`
// its state
State string `json:"state,omitempty"`
}
// TryReadInput is what try-read takes.
type TryReadInput struct {
// the try's id, try-<digits>
ID string `json:"id"`
}
// TryReadAnswer is what try-read answers.
type TryReadAnswer = map[string]any
// TryEndInput is what try-end takes.
type TryEndInput struct {
// the try's id, try-<digits>
ID string `json:"id"`
// why it is ended, in words
Why string `json:"why"`
}
// TryEndAnswer is what try-end answers.
type TryEndAnswer struct {
// it is ended
Ended bool `json:"ended"`
// the try
ID string `json:"id"`
}
// VerdictHolderEventSeriesItem is a type of the contract.
type VerdictHolderEventSeriesItem struct {
// its artifact, or the package's ecosystem name as the module's packages name it
Artifact string `json:"artifact"`
// the series on trunk
From string `json:"from"`
// the module whose artifact's series changes
Module string `json:"module"`
// the series the checked head gives it
To string `json:"to"`
}
// VerdictHolderEvent is the body of the holder's event verdict.
type VerdictHolderEvent struct {
// the commit checked
Head string `json:"head"`
// the generated files out of step with their seat definitions, each path (design 26)
OutOfStep []string `json:"out-of-step,omitempty"`
// the pull request's number, for a pull request's check
Pull int64 `json:"pull,omitempty"`
// owner/repo
Repository string `json:"repository"`
// each change of an artifact's series the checked head makes against trunk's manifests, once each (design 32); absent when it changes none
Series []VerdictHolderEventSeriesItem `json:"series,omitempty"`
// pass or fail
Verdict string `json:"verdict"`
}
// TagWantedHolderEvent is the body of the holder's event tag-wanted.
type TagWantedHolderEvent struct {
// the pull request's branch: its tag is removed when the branch ends
Branch string `json:"branch"`
// its head
Head string `json:"head"`
// the group member
Module string `json:"module"`
// the package it declares
Package string `json:"package"`
// the pull request
Pull int64 `json:"pull,omitempty"`
// owner/repo
Repository string `json:"repository"`
// the tag wanted
Tag string `json:"tag,omitempty"`
}
// VerdictsByGroupHeadsValueComposition is a type of the contract.
type VerdictsByGroupHeadsValueComposition struct {
Fpset string `json:"fpset,omitempty"`
Machines []string `json:"machines,omitempty"`
Sdk string `json:"sdk,omitempty"`
// not-run, pass or fail
State string `json:"state"`
Words string `json:"words,omitempty"`
}
// VerdictsByGroupHeadsValueFailed is a type of the contract.
type VerdictsByGroupHeadsValueFailed struct {
Machine string `json:"machine,omitempty"`
Test string `json:"test,omitempty"`
Words string `json:"words,omitempty"`
}
// VerdictsByGroupHeadsValueFailuresItem is a type of the contract.
type VerdictsByGroupHeadsValueFailuresItem struct {
Machine string `json:"machine,omitempty"`
Test string `json:"test,omitempty"`
Words string `json:"words,omitempty"`
}
// VerdictsByGroupHeadsValueLevelsValue is a type of the contract.
type VerdictsByGroupHeadsValueLevelsValue struct {
Kept int64 `json:"kept"`
Run int64 `json:"run"`
Skipped int64 `json:"skipped,omitempty"`
}
// VerdictsByGroupHeadsValueSeriesItem is a type of the contract.
type VerdictsByGroupHeadsValueSeriesItem struct {
Artifact string `json:"artifact"`
From string `json:"from"`
Module string `json:"module"`
To string `json:"to"`
}
// VerdictsByGroupHeadsValueTook is milliseconds.
type VerdictsByGroupHeadsValueTook struct {
Clone int64 `json:"clone,omitempty"`
Composition int64 `json:"composition,omitempty"`
IntegrationRun int64 `json:"integration-run,omitempty"`
IntegrationSetup int64 `json:"integration-setup,omitempty"`
Lint int64 `json:"lint,omitempty"`
Queue int64 `json:"queue,omitempty"`
Unit int64 `json:"unit,omitempty"`
}
// VerdictsByGroupHeadsValue is one value of the state verdicts under the key <group>.<heads>.
type VerdictsByGroupHeadsValue struct {
Beside map[string]string `json:"beside,omitempty"`
CarriedFrom map[string]string `json:"carried-from,omitempty"`
Composition *VerdictsByGroupHeadsValueComposition `json:"composition,omitempty"`
Failed *VerdictsByGroupHeadsValueFailed `json:"failed,omitempty"`
Failures []VerdictsByGroupHeadsValueFailuresItem `json:"failures,omitempty"`
Group string `json:"group"`
// 12 hex digits
Heads string `json:"heads"`
// by level, unit or integration
Levels map[string]VerdictsByGroupHeadsValueLevelsValue `json:"levels,omitempty"`
Machines []string `json:"machines,omitempty"`
OverBound []string `json:"over-bound,omitempty"`
Sdk string `json:"sdk,omitempty"`
Series []VerdictsByGroupHeadsValueSeriesItem `json:"series,omitempty"`
// running, pass or fail
State string `json:"state"`
// milliseconds
Took *VerdictsByGroupHeadsValueTook `json:"took,omitempty"`
// repository to the trunk head merged onto
Trunk map[string]string `json:"trunk,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
}
// KeptByLevelDigestValue is one value of the state kept under the key <level>.<digest>.
type KeptByLevelDigestValue struct {
// when (RFC 3339 time)
At string `json:"at,omitempty"`
Level string `json:"level"`
Package string `json:"package"`
Repository string `json:"repository"`
// pass: a failure is never kept
State string `json:"state"`
Tests int64 `json:"tests,omitempty"`
Took int64 `json:"took,omitempty"`
Toolchain string `json:"toolchain,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
}
// OwedByRepositoryNumberHeadActValue is one value of the state owed under the key <repository>.<number>.<head>.<act>.
type OwedByRepositoryNumberHeadActValue struct {
// status=<context>, view, note or comment.<seq>
Act string `json:"act"`
Context string `json:"context,omitempty"`
Ref string `json:"ref,omitempty"`
State string `json:"state,omitempty"`
Target string `json:"target,omitempty"`
Text string `json:"text,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
}
// PreviewsByRepositoryHeadTrunkValueModulesValue is a type of the contract.
type PreviewsByRepositoryHeadTrunkValueModulesValue struct {
Locks map[string]string `json:"locks,omitempty"`
// manifest digest
Manifest string `json:"manifest"`
Packages []string `json:"packages,omitempty"`
Paths map[string]string `json:"paths,omitempty"`
Tree string `json:"tree"`
}
// PreviewsByRepositoryHeadTrunkValue is one value of the state previews under the key <repository>.<head>.<trunk>.
type PreviewsByRepositoryHeadTrunkValue struct {
// files in conflict
Conflicts []string `json:"conflicts,omitempty"`
Head string `json:"head"`
// the merge commit
Merge string `json:"merge,omitempty"`
// by directory
Modules map[string]PreviewsByRepositoryHeadTrunkValueModulesValue `json:"modules,omitempty"`
Repository string `json:"repository"`
Trunk string `json:"trunk"`
// the layout version
V int64 `json:"v,omitempty"`
}
@@ -1,72 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package meshdeliverychecker
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/mesh-delivery-checker/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: "verdict on the checker's own family", subject: "mesh.stream.mesh-delivery-checker.event.verdict", given: "{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"out-of-step\":[\"go/seats/meshbroker/contract.go\"],\"pull\":3,\"repository\":\"novox/mesh-contracts\",\"series\":[{\"artifact\":\"go\",\"from\":\"0.1\",\"module\":\"mesh-contracts\",\"to\":\"0.2\"}],\"verdict\":\"fail\"}", wire: "{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"out-of-step\":[\"go/seats/meshbroker/contract.go\"],\"pull\":3,\"repository\":\"novox/mesh-contracts\",\"series\":[{\"artifact\":\"go\",\"from\":\"0.1\",\"module\":\"mesh-contracts\",\"to\":\"0.2\"}],\"verdict\":\"fail\"}",
decode: func(b []byte) (any, error) { var v VerdictHolderEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.stream\\.[a-z0-9-]+\\.event\\.verdict$")},
{name: "tag-wanted on the checker's own family", subject: "mesh.stream.mesh-delivery-checker.event.tag-wanted", given: "{\"branch\":\"feat/registry\",\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"module\":\"mesh-sdk\",\"package\":\"go\",\"pull\":170,\"repository\":\"novox/mesh-sdk\",\"tag\":\"registry-1966\"}", wire: "{\"branch\":\"feat/registry\",\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"module\":\"mesh-sdk\",\"package\":\"go\",\"pull\":170,\"repository\":\"novox/mesh-sdk\",\"tag\":\"registry-1966\"}",
decode: func(b []byte) (any, error) { var v TagWantedHolderEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.stream\\.[a-z0-9-]+\\.event\\.tag-wanted$")},
{name: "preview.written on the checker's stream", subject: "mesh.stream.mesh-delivery-checker.event.preview.written", given: "{}", wire: "{}",
decode: func(b []byte) (any, error) { var v map[string]any; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.stream\\.[a-z0-9-]+\\.event\\.preview\\.written$")},
{name: "a running verdict", subject: "", given: "{\"group\":\"feat/registry\",\"heads\":\"0123456789ab\",\"state\":\"running\",\"v\":1}", wire: "{\"group\":\"feat/registry\",\"heads\":\"0123456789ab\",\"state\":\"running\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v VerdictsByGroupHeadsValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a kept pass", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"level\":\"unit\",\"package\":\"go/keys\",\"repository\":\"novox/mesh-sdk\",\"state\":\"pass\",\"tests\":4,\"took\":120,\"toolchain\":\"go1.26\",\"v\":1}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"level\":\"unit\",\"package\":\"go/keys\",\"repository\":\"novox/mesh-sdk\",\"state\":\"pass\",\"tests\":4,\"took\":120,\"toolchain\":\"go1.26\",\"v\":1}",
decode: func(b []byte) (any, error) { var v KeptByLevelDigestValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "the status owed for a head", subject: "", given: "{\"act\":\"status=mesh/check\",\"context\":\"mesh/check\",\"state\":\"success\",\"v\":1}", wire: "{\"act\":\"status=mesh/check\",\"context\":\"mesh/check\",\"state\":\"success\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v OwedByRepositoryNumberHeadActValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a preview", subject: "", given: "{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"merge\":\"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\",\"repository\":\"novox/mesh-sdk\",\"trunk\":\"dddddddddddddddddddddddddddddddddddddddd\",\"v\":1}", wire: "{\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"merge\":\"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\",\"repository\":\"novox/mesh-sdk\",\"trunk\":\"dddddddddddddddddddddddddddddddddddddddd\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v PreviewsByRepositoryHeadTrunkValue
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)
}
}
}
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker
go 1.26.0
-337
View File
@@ -1,337 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// Package meshwants is the contract of the seat "mesh-wants", defined whole (seats/mesh-wants/seat.json): The seat that says what each node should run and whether each module's new builds wait for a person (ADR 0565, ADR 0539).
package meshwants
import (
"bytes"
"encoding/json"
"errors"
"strings"
)
// Seat is the seat's name.
const Seat = "mesh-wants"
const (
VerbUpgrade = "upgrade"
VerbBus = "bus"
)
const (
EventPolicyChanged = "policy-changed"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbUpgrade, VerbBus}
}
// Events is every event published on the seat's subject: a new copy each call.
func Events() []string {
return []string{EventPolicyChanged}
}
// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects.
const (
Family = "mesh-wants.events"
FamilyMaxAgeDays = 7
)
// SubjectPolicyChanged is where policy-changed is published.
const SubjectPolicyChanged = "mesh.seat.mesh-wants.event.policy-changed"
const (
StateWants = "wants"
StateDifferences = "differences"
StateCompositions = "compositions"
)
// WantsByWantNodeModuleKey is the key of one value of the state wants under the form "want.<node>.<module>".
func WantsByWantNodeModuleKey(node, module string) string { return "want." + node + "." + module }
// ParseWantsByWantNodeModuleKey reads the parts of a key of the form "want.<node>.<module>", in the order of the form; the last result is false for a key of another form.
func ParseWantsByWantNodeModuleKey(key string) (string, string, bool) {
rest := key
if !strings.HasPrefix(rest, "want.") {
return "", "", false
}
rest = rest[5:]
v1, after1, found1 := strings.Cut(rest, ".")
if !found1 || v1 == "" {
return "", "", false
}
rest = after1
if rest == "" {
return "", "", false
}
v3 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v1, v3, true
}
// DecodeWantsByWantNodeModuleValue reads one value of the state wants under the form "want.<node>.<module>", refusing a field WantsByWantNodeModuleValue does not know and any text after the value.
func DecodeWantsByWantNodeModuleValue(raw []byte) (WantsByWantNodeModuleValue, error) {
var v WantsByWantNodeModuleValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// WantsByWantNodeNodeKey is the key of one value of the state wants under the form "want.<node>.node".
func WantsByWantNodeNodeKey(node string) string { return "want." + node + ".node" }
// ParseWantsByWantNodeNodeKey reads the parts of a key of the form "want.<node>.node", in the order of the form; the last result is false for a key of another form.
func ParseWantsByWantNodeNodeKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "want.") {
return "", false
}
rest = rest[5:]
v1, after1, found1 := strings.Cut(rest, ".node")
if !found1 || v1 == "" {
return "", false
}
rest = after1
if rest != "" {
return "", false
}
return v1, true
}
// DecodeWantsByWantNodeNodeValue reads one value of the state wants under the form "want.<node>.node", refusing a field WantsByWantNodeNodeValue does not know and any text after the value.
func DecodeWantsByWantNodeNodeValue(raw []byte) (WantsByWantNodeNodeValue, error) {
var v WantsByWantNodeNodeValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// WantsByHeldModuleFingerprintKey is the key of one value of the state wants under the form "held.<module>.<fingerprint>".
func WantsByHeldModuleFingerprintKey(module, fingerprint string) string {
return "held." + module + "." + fingerprint
}
// ParseWantsByHeldModuleFingerprintKey reads the parts of a key of the form "held.<module>.<fingerprint>", in the order of the form; the last result is false for a key of another form.
func ParseWantsByHeldModuleFingerprintKey(key string) (string, string, bool) {
rest := key
if !strings.HasPrefix(rest, "held.") {
return "", "", false
}
rest = rest[5:]
v1, after1, found1 := strings.Cut(rest, ".")
if !found1 || v1 == "" {
return "", "", false
}
rest = after1
if rest == "" {
return "", "", false
}
v3 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v1, v3, true
}
// DecodeWantsByHeldModuleFingerprintValue reads one value of the state wants under the form "held.<module>.<fingerprint>", refusing a field WantsByHeldModuleFingerprintValue does not know and any text after the value.
func DecodeWantsByHeldModuleFingerprintValue(raw []byte) (WantsByHeldModuleFingerprintValue, error) {
var v WantsByHeldModuleFingerprintValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// DifferencesByNodeModuleKey is the key of one value of the state differences under the form "<node>.<module>".
func DifferencesByNodeModuleKey(node, module string) string { return node + "." + module }
// ParseDifferencesByNodeModuleKey reads the parts of a key of the form "<node>.<module>", in the order of the form; the last result is false for a key of another form.
func ParseDifferencesByNodeModuleKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeDifferencesByNodeModuleValue reads one value of the state differences under the form "<node>.<module>", refusing a field DifferencesByNodeModuleValue does not know and any text after the value.
func DecodeDifferencesByNodeModuleValue(raw []byte) (DifferencesByNodeModuleValue, error) {
var v DifferencesByNodeModuleValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// CompositionsByGroupHeadsKey is the key of one value of the state compositions under the form "<group>.<heads>".
func CompositionsByGroupHeadsKey(group, heads string) string { return group + "." + heads }
// ParseCompositionsByGroupHeadsKey reads the parts of a key of the form "<group>.<heads>", in the order of the form; the last result is false for a key of another form.
func ParseCompositionsByGroupHeadsKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeCompositionsByGroupHeadsValue reads one value of the state compositions under the form "<group>.<heads>", refusing a field CompositionsByGroupHeadsValue does not know and any text after the value.
func DecodeCompositionsByGroupHeadsValue(raw []byte) (CompositionsByGroupHeadsValue, error) {
var v CompositionsByGroupHeadsValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// UpgradeInput is what upgrade takes.
type UpgradeInput struct {
// "true": each node's own composition, by its digest, and the assigner's findings
Compare string `json:"compare,omitempty"`
// one module
Module string `json:"module,omitempty"`
// with module: roll, together, record, or default to take a person's choice back
Policy string `json:"policy,omitempty"`
// with policy: why, required and kept
Why string `json:"why,omitempty"`
}
// The values of UpgradeInput's compare.
const (
UpgradeInputCompareTrue = "true"
UpgradeInputCompareFalse = "false"
)
// UpgradeAnswer is what upgrade answers.
type UpgradeAnswer = map[string]any
// BusInput is what bus takes.
type BusInput struct {
}
// BusAnswer is what bus answers.
type BusAnswer = map[string]any
// PolicyChangedEvent is the body of the event policy-changed.
type PolicyChangedEvent = map[string]any
// WantsByWantNodeModuleValue is one value of the state wants under the key want.<node>.<module>.
type WantsByWantNodeModuleValue struct {
// again or skip
Backup string `json:"backup,omitempty"`
Composed string `json:"composed,omitempty"`
Fp string `json:"fp,omitempty"`
Hold bool `json:"hold,omitempty"`
Newest string `json:"newest,omitempty"`
Put bool `json:"put,omitempty"`
Release string `json:"release,omitempty"`
Removed bool `json:"removed,omitempty"`
// the rule that holds the node back, 1 to 10
Rule int64 `json:"rule,omitempty"`
Self bool `json:"self,omitempty"`
V int64 `json:"v,omitempty"`
Words string `json:"words,omitempty"`
Yes string `json:"yes,omitempty"`
}
// The values of WantsByWantNodeModuleValue's backup.
const (
WantsByWantNodeModuleValueBackupAgain = "again"
WantsByWantNodeModuleValueBackupSkip = "skip"
)
// WantsByWantNodeNodeValue is one value of the state wants under the key want.<node>.node.
type WantsByWantNodeNodeValue struct {
Composed string `json:"composed"`
Self bool `json:"self,omitempty"`
V int64 `json:"v,omitempty"`
Words string `json:"words,omitempty"`
}
// WantsByHeldModuleFingerprintValue is one value of the state wants under the key held.<module>.<fingerprint>.
type WantsByHeldModuleFingerprintValue struct {
Node string `json:"node"`
V int64 `json:"v,omitempty"`
Words string `json:"words,omitempty"`
}
// DifferencesByNodeModuleValue is one value of the state differences under the key <node>.<module>.
type DifferencesByNodeModuleValue struct {
Rule int64 `json:"rule,omitempty"`
Sent string `json:"sent,omitempty"`
SentBuild string `json:"sent-build,omitempty"`
Unexplained bool `json:"unexplained,omitempty"`
V int64 `json:"v,omitempty"`
Want string `json:"want,omitempty"`
WantRemoved bool `json:"want-removed,omitempty"`
Words string `json:"words,omitempty"`
}
// CompositionsByGroupHeadsValue is one value of the state compositions under the key <group>.<heads>.
type CompositionsByGroupHeadsValue struct {
Group string `json:"group"`
// 12 lowercase hex digits
Heads string `json:"heads"`
Machine string `json:"machine,omitempty"`
Machines []string `json:"machines,omitempty"`
Sdk string `json:"sdk,omitempty"`
// pass, fail or waits
State string `json:"state"`
// milliseconds
Took int64 `json:"took,omitempty"`
V int64 `json:"v,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Words string `json:"words,omitempty"`
}
@@ -1,76 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package meshwants
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/mesh-wants/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: "a want", subject: "", given: "{\"fp\":\"ab12\",\"rule\":3,\"v\":1}", wire: "{\"fp\":\"ab12\",\"rule\":3,\"v\":1}",
decode: func(b []byte) (any, error) {
var v WantsByWantNodeModuleValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a node's own want", subject: "", given: "{\"composed\":\"sha256:aa\",\"self\":true,\"v\":1}", wire: "{\"composed\":\"sha256:aa\",\"self\":true,\"v\":1}",
decode: func(b []byte) (any, error) {
var v WantsByWantNodeNodeValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a held fingerprint", subject: "", given: "{\"node\":\"ace\",\"v\":1}", wire: "{\"node\":\"ace\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v WantsByHeldModuleFingerprintValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a difference", subject: "", given: "{\"unexplained\":true,\"v\":1,\"want\":\"ab12\"}", wire: "{\"unexplained\":true,\"v\":1,\"want\":\"ab12\"}",
decode: func(b []byte) (any, error) {
var v DifferencesByNodeModuleValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a composition", subject: "", given: "{\"group\":\"g1\",\"heads\":\"0123456789ab\",\"state\":\"pass\",\"v\":1}", wire: "{\"group\":\"g1\",\"heads\":\"0123456789ab\",\"state\":\"pass\",\"v\":1}",
decode: func(b []byte) (any, error) {
var v CompositionsByGroupHeadsValue
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)
}
}
}
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants
go 1.26.0
-557
View File
@@ -1,557 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// Package nodebuildgo is the contract of the seat "node-build-go", defined whole (seats/node-build-go/seat.json): The Go build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers.
package nodebuildgo
import (
"bytes"
"encoding/json"
"errors"
"strings"
"time"
)
// Seat is the seat's name.
const Seat = "node-build-go"
const (
VerbCurrent = "current"
VerbPause = "pause"
VerbResume = "resume"
)
const (
AcceptDelivery = "delivery"
AcceptCheck = "check"
AcceptRecheck = "recheck"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbCurrent, VerbPause, VerbResume}
}
// AcceptSubjectDelivery is where delivery is sent.
const AcceptSubjectDelivery = "mesh.seat.node-build-go.accept.delivery"
// AcceptSubjectCheck is where check is sent.
const AcceptSubjectCheck = "mesh.seat.node-build-go.accept.check"
// AcceptSubjectRecheck is where recheck is sent.
const AcceptSubjectRecheck = "mesh.seat.node-build-go.accept.recheck"
const (
StateSteps = "steps"
StatePauses = "pauses"
StateToolchains = "toolchains"
)
// StepsByNodeIDKey is the key of one value of the state steps under the form "<node>.<id>".
func StepsByNodeIDKey(node, id string) string { return node + "." + id }
// ParseStepsByNodeIDKey reads the parts of a key of the form "<node>.<id>", in the order of the form; the last result is false for a key of another form.
func ParseStepsByNodeIDKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeStepsByNodeIDValue reads one value of the state steps under the form "<node>.<id>", refusing a field StepsByNodeIDValue does not know and any text after the value.
func DecodeStepsByNodeIDValue(raw []byte) (StepsByNodeIDValue, error) {
var v StepsByNodeIDValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PausesByPausedNodeKey is the key of one value of the state pauses under the form "paused.<node>".
func PausesByPausedNodeKey(node string) string { return "paused." + node }
// ParsePausesByPausedNodeKey reads the parts of a key of the form "paused.<node>", in the order of the form; the last result is false for a key of another form.
func ParsePausesByPausedNodeKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "paused.") {
return "", false
}
rest = rest[7:]
if rest == "" {
return "", false
}
v1 := rest
rest = ""
if rest != "" {
return "", false
}
return v1, true
}
// DecodePausesByPausedNodeValue reads one value of the state pauses under the form "paused.<node>", refusing a field PausesByPausedNodeValue does not know and any text after the value.
func DecodePausesByPausedNodeValue(raw []byte) (PausesByPausedNodeValue, error) {
var v PausesByPausedNodeValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PausesByCapacityNodeKey is the key of one value of the state pauses under the form "capacity.<node>".
func PausesByCapacityNodeKey(node string) string { return "capacity." + node }
// ParsePausesByCapacityNodeKey reads the parts of a key of the form "capacity.<node>", in the order of the form; the last result is false for a key of another form.
func ParsePausesByCapacityNodeKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "capacity.") {
return "", false
}
rest = rest[9:]
if rest == "" {
return "", false
}
v1 := rest
rest = ""
if rest != "" {
return "", false
}
return v1, true
}
// DecodePausesByCapacityNodeValue reads one value of the state pauses under the form "capacity.<node>", refusing a field PausesByCapacityNodeValue does not know and any text after the value.
func DecodePausesByCapacityNodeValue(raw []byte) (PausesByCapacityNodeValue, error) {
var v PausesByCapacityNodeValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// ToolchainsByNodeLanguageKey is the key of one value of the state toolchains under the form "<node>.<language>".
func ToolchainsByNodeLanguageKey(node, language string) string { return node + "." + language }
// ParseToolchainsByNodeLanguageKey reads the parts of a key of the form "<node>.<language>", in the order of the form; the last result is false for a key of another form.
func ParseToolchainsByNodeLanguageKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeToolchainsByNodeLanguageValue reads one value of the state toolchains under the form "<node>.<language>", refusing a field ToolchainsByNodeLanguageValue does not know and any text after the value.
func DecodeToolchainsByNodeLanguageValue(raw []byte) (ToolchainsByNodeLanguageValue, error) {
var v ToolchainsByNodeLanguageValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// AskArtifact is one artifact of a build ask: built from the fetched tree, or copied from an upstream pin.
type AskArtifact struct {
Artifact string `json:"artifact"`
From string `json:"from,omitempty"`
Seat string `json:"seat,omitempty"`
Version string `json:"version,omitempty"`
}
// BuildAnswer is a type of the contract.
type BuildAnswer struct {
Artifacts []BuiltArtifact `json:"artifacts"`
Imports []string `json:"imports,omitempty"`
OutOfStep []string `json:"out-of-step,omitempty"`
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// BuiltArtifact is one artifact a build made: its digest and the reference it was published under.
type BuiltArtifact struct {
Artifact string `json:"artifact"`
Digest string `json:"digest"`
Reference string `json:"reference,omitempty"`
}
// Capacity is how many steps the node runs at once.
type Capacity struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
BuildHours string `json:"build-hours,omitempty"`
BuildHoursFault string `json:"build-hours-fault,omitempty"`
ByCpu int64 `json:"by-cpu,omitempty"`
Configured int64 `json:"configured"`
ConfiguredFrom string `json:"configured-from,omitempty"`
CoresPerStep float64 `json:"cores-per-step,omitempty"`
Effective int64 `json:"effective"`
Ineligible string `json:"ineligible,omitempty"`
OutsideHours bool `json:"outside-hours,omitempty"`
QuotaCores float64 `json:"quota-cores,omitempty"`
QuotaFrom string `json:"quota-from,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
Why string `json:"why"`
}
// CheckAnswer is a type of the contract.
type CheckAnswer struct {
Fetch FetchAnswer `json:"fetch"`
Levels []LevelAnswer `json:"levels,omitempty"`
}
// ComposeAnswer is a type of the contract.
type ComposeAnswer struct {
Built bool `json:"built"`
Key string `json:"key,omitempty"`
// the compose-candidate verdict
Verdict any `json:"verdict,omitempty"`
}
// FetchBesideItem is a type of the contract.
type FetchBesideItem struct {
Commit string `json:"commit"`
Repository string `json:"repository"`
}
// FetchPackage is the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's.
type FetchPackage struct {
Commit string `json:"commit,omitempty"`
Name string `json:"name,omitempty"`
Ref string `json:"ref,omitempty"`
Repository string `json:"repository,omitempty"`
Version string `json:"version,omitempty"`
}
// Fetch is what a build module's fetch tool is asked: the repository and the full commit, the pull request's ref when only its head carries the commit, the trunk commit to merge onto for a merge preview, the module's directory and named paths, the mesh packages to build against, and the sibling repositories placed beside.
type Fetch struct {
// the trunk commit a merge preview merges onto
Base string `json:"base,omitempty"`
// the sibling repositories placed beside the fetched tree
Beside []FetchBesideItem `json:"beside,omitempty"`
// the full commit
Commit string `json:"commit"`
// the module's directory in the repository ("." for its root), with paths
Dir string `json:"dir,omitempty"`
// the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's
Package *FetchPackage `json:"package,omitempty"`
// the version (a full commit) of each mesh package to build against, by its module path
Packages map[string]string `json:"packages,omitempty"`
// the paths the module names, relative to dir
Paths []string `json:"paths,omitempty"`
// the pull request's ref, when only its head carries the commit
Ref string `json:"ref,omitempty"`
// <owner>/<name>
Repository string `json:"repository"`
}
// FetchAnswer is what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package.
type FetchAnswer struct {
Base string `json:"base,omitempty"`
Beside []PlacedBeside `json:"beside,omitempty"`
Changed []string `json:"changed,omitempty"`
Commit string `json:"commit"`
Conflicts []string `json:"conflicts,omitempty"`
Group string `json:"group,omitempty"`
Installed []string `json:"installed,omitempty"`
Merge string `json:"merge,omitempty"`
Modules []string `json:"modules"`
Packages []TakenVersion `json:"packages,omitempty"`
Programs []Program `json:"programs,omitempty"`
Refused []FetchRefusal `json:"refused,omitempty"`
Trees map[string]ModuleTree `json:"trees,omitempty"`
Workspace string `json:"workspace"`
}
// FetchRefusal is a TypeScript module whose packages the fetch did not install, with why.
type FetchRefusal struct {
Module string `json:"module"`
Reason string `json:"reason"`
}
// LevelAnswerDigestNeedsItem is a type of the contract.
type LevelAnswerDigestNeedsItem struct {
Needs []string `json:"needs,omitempty"`
Package string `json:"package,omitempty"`
}
// LevelAnswerDigestPackagesItem is a type of the contract.
type LevelAnswerDigestPackagesItem struct {
Digest string `json:"digest"`
Package string `json:"package"`
}
// LevelAnswerDigest is a type of the contract.
type LevelAnswerDigest struct {
Beside []string `json:"beside,omitempty"`
JudgedBy string `json:"judged_by,omitempty"`
Level string `json:"level,omitempty"`
Needs []LevelAnswerDigestNeedsItem `json:"needs,omitempty"`
Packages []LevelAnswerDigestPackagesItem `json:"packages,omitempty"`
WithoutTests []string `json:"without_tests,omitempty"`
}
// LevelAnswerKeptItem is a type of the contract.
type LevelAnswerKeptItem struct {
Digest string `json:"digest,omitempty"`
Package string `json:"package,omitempty"`
Tests int64 `json:"tests,omitempty"`
}
// LevelAnswerUnmetItem is a type of the contract.
type LevelAnswerUnmetItem struct {
Needs []string `json:"needs,omitempty"`
Package string `json:"package,omitempty"`
}
// LevelAnswer is one level of a check ask: the digest's answer, the kept verdicts taken, one test result per test step run, and the packages the node could not test.
type LevelAnswer struct {
Digest *LevelAnswerDigest `json:"digest,omitempty"`
Judged string `json:"judged,omitempty"`
Kept []LevelAnswerKeptItem `json:"kept,omitempty"`
Level string `json:"level"`
Tests []any `json:"tests,omitempty"`
Unmet []LevelAnswerUnmetItem `json:"unmet,omitempty"`
}
// ModuleTree is the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths.
type ModuleTree struct {
Locks map[string]string `json:"locks,omitempty"`
Manifest string `json:"manifest"`
// no longer filled (ADR 0593 section 2); kept so a preview written before still reads
Packages []string `json:"packages,omitempty"`
Paths map[string]string `json:"paths,omitempty"`
Tree string `json:"tree"`
}
// Pause is whether the node takes no new ask.
type Pause struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
By string `json:"by,omitempty"`
Paused bool `json:"paused"`
// the layout version
V int64 `json:"v,omitempty"`
}
// PlacedBeside is a sibling repository placed as a plain directory under the workspace's beside directory.
type PlacedBeside struct {
Commit string `json:"commit"`
Dir string `json:"dir"`
Repository string `json:"repository"`
}
// Program is a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it.
type Program struct {
Commit string `json:"commit,omitempty"`
// ELF, Mach-O or PE
Kind string `json:"kind"`
Path string `json:"path"`
}
// Step is one step a build module runs on a node.
type Step struct {
// when (RFC 3339 time)
Ended *time.Time `json:"ended,omitempty"`
ID string `json:"id"`
Node string `json:"node"`
Owner string `json:"owner,omitempty"`
Phase string `json:"phase,omitempty"`
// when (RFC 3339 time)
PhaseAt *time.Time `json:"phase-at,omitempty"`
Reason string `json:"reason,omitempty"`
Result *StepResult `json:"result,omitempty"`
// when (RFC 3339 time)
Started time.Time `json:"started"`
// running, passed, failed or refused
State string `json:"state"`
Tool string `json:"tool"`
// the layout version
V int64 `json:"v,omitempty"`
What string `json:"what,omitempty"`
}
// StepAskBuild is a type of the contract.
type StepAskBuild struct {
Artifacts []AskArtifact `json:"artifacts"`
Fetch *Fetch `json:"fetch,omitempty"`
Fp string `json:"fp,omitempty"`
Module string `json:"module"`
Path string `json:"path,omitempty"`
Publish bool `json:"publish,omitempty"`
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// StepAskCheck is a type of the contract.
type StepAskCheck struct {
Fetch Fetch `json:"fetch"`
Kept bool `json:"kept,omitempty"`
Levels []string `json:"levels"`
// the node names the check's tests run against
NodeNames []string `json:"node-names,omitempty"`
Packages []string `json:"packages,omitempty"`
Run string `json:"run,omitempty"`
Runner string `json:"runner,omitempty"`
RunnerWhen string `json:"runner_when,omitempty"`
// the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// StepAskCompose is a type of the contract.
type StepAskCompose struct {
Dir string `json:"dir"`
Fetch Fetch `json:"fetch"`
Package string `json:"package"`
Reader string `json:"reader,omitempty"`
// the compose-candidate request
Request any `json:"request"`
}
// StepAsk is one ask of a build module.
type StepAsk struct {
Asker string `json:"asker"`
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
Build *StepAskBuild `json:"build,omitempty"`
Check *StepAskCheck `json:"check,omitempty"`
Compose *StepAskCompose `json:"compose,omitempty"`
ID string `json:"id"`
// build, check or compose
Kind string `json:"kind"`
// go, typescript or image
Language string `json:"language"`
Needs []string `json:"needs,omitempty"`
// package to the tagged version a build pins
Packages map[string]string `json:"packages,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
}
// StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four.
type StepResult struct {
Artifacts []BuiltArtifact `json:"artifacts,omitempty"`
Built bool `json:"built,omitempty"`
// the holder declined the ask: its node lacks what the ask needs
Declined bool `json:"declined,omitempty"`
Fetch *FetchAnswer `json:"fetch,omitempty"`
Imports []string `json:"imports,omitempty"`
Key string `json:"key,omitempty"`
// what the holder's node lacks of it
Lacks []string `json:"lacks,omitempty"`
Levels []LevelAnswer `json:"levels,omitempty"`
// what the declined ask needs
Needs []string `json:"needs,omitempty"`
OutOfStep []string `json:"out-of-step,omitempty"`
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
// the compose-candidate verdict (the composer's own layout)
Verdict any `json:"verdict,omitempty"`
}
// TakenVersion is of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said.
type TakenVersion struct {
Asked string `json:"asked"`
Built string `json:"built"`
Dir string `json:"dir"`
Named string `json:"named"`
Package string `json:"package"`
Said string `json:"said,omitempty"`
Sum string `json:"sum,omitempty"`
}
// Toolchain is the toolchain a node reports.
type Toolchain struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
// go or typescript
Language string `json:"language"`
Recipe string `json:"recipe"`
// the layout version
V int64 `json:"v,omitempty"`
Version string `json:"version"`
}
// ToolchainUsed is the toolchain a build was made with: the language, the installed version and the build module's recipe.
type ToolchainUsed struct {
Language string `json:"language"`
Recipe string `json:"recipe"`
Version string `json:"version"`
}
// CurrentInput is what current takes.
type CurrentInput struct {
}
// CurrentAnswer is what current answers.
type CurrentAnswer = map[string]any
// PauseInput is what pause takes.
type PauseInput struct {
}
// PauseAnswer is what pause answers.
type PauseAnswer = map[string]any
// ResumeInput is what resume takes.
type ResumeInput struct {
}
// ResumeAnswer is what resume answers.
type ResumeAnswer = map[string]any
// DeliveryAccept is the body of delivery.
type DeliveryAccept = StepAsk
// CheckAccept is the body of check.
type CheckAccept = StepAsk
// RecheckAccept is the body of recheck.
type RecheckAccept = StepAsk
// StepsByNodeIDValue is one value of the state steps under the key <node>.<id>.
type StepsByNodeIDValue = Step
// PausesByPausedNodeValue is one value of the state pauses under the key paused.<node>.
type PausesByPausedNodeValue = Pause
// PausesByCapacityNodeValue is one value of the state pauses under the key capacity.<node>.
type PausesByCapacityNodeValue = Capacity
// ToolchainsByNodeLanguageValue is one value of the state toolchains under the key <node>.<language>.
type ToolchainsByNodeLanguageValue = Toolchain
@@ -1,78 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package nodebuildgo
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/node-build-go/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: "a running step", subject: "", given: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}", wire: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a paused node", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}",
decode: func(b []byte) (any, error) {
var v PausesByPausedNodeValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a node's capacity", subject: "", given: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}", wire: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}",
decode: func(b []byte) (any, error) {
var v PausesByCapacityNodeValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a node's toolchain", subject: "", given: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}", wire: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}",
decode: func(b []byte) (any, error) {
var v ToolchainsByNodeLanguageValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a build ask", subject: "mesh.seat.node-build-go.accept.delivery", given: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", wire: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}",
decode: func(b []byte) (any, error) { var v DeliveryAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.accept\\.delivery$")},
{name: "a check ask with its fetch, siblings and levels", subject: "mesh.seat.node-build-go.accept.check", given: "{\"asker\":\"mesh-delivery-checker\",\"at\":\"2026-11-05T10:00:00Z\",\"check\":{\"fetch\":{\"base\":\"89abcdef0123456789abcdef0123456789abcdef\",\"beside\":[{\"commit\":\"fedcba9876543210fedcba9876543210fedcba98\",\"repository\":\"novox/mesh-sdk\"}],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"fedcba9876543210fedcba9876543210fedcba98\"},\"ref\":\"refs/pull/7/head\",\"repository\":\"novox/mesh-catalog\"},\"kept\":true,\"levels\":[\"unit\",\"integration\"],\"node-names\":[\"ace\",\"g14\"],\"toolchain\":{\"language\":\"go\",\"recipe\":\"2\",\"version\":\"1.26.0\"}},\"id\":\"c3d4\",\"kind\":\"check\",\"language\":\"go\",\"v\":1}", wire: "{\"asker\":\"mesh-delivery-checker\",\"at\":\"2026-11-05T10:00:00Z\",\"check\":{\"fetch\":{\"base\":\"89abcdef0123456789abcdef0123456789abcdef\",\"beside\":[{\"commit\":\"fedcba9876543210fedcba9876543210fedcba98\",\"repository\":\"novox/mesh-sdk\"}],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"fedcba9876543210fedcba9876543210fedcba98\"},\"ref\":\"refs/pull/7/head\",\"repository\":\"novox/mesh-catalog\"},\"kept\":true,\"levels\":[\"unit\",\"integration\"],\"node-names\":[\"ace\",\"g14\"],\"toolchain\":{\"language\":\"go\",\"recipe\":\"2\",\"version\":\"1.26.0\"}},\"id\":\"c3d4\",\"kind\":\"check\",\"language\":\"go\",\"v\":1}",
decode: func(b []byte) (any, error) { var v CheckAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.accept\\.check$")},
{name: "a finished build step with its artifacts", subject: "", given: "{\"ended\":\"2026-11-05T10:03:00Z\",\"id\":\"a1b2\",\"node\":\"ace\",\"result\":{\"artifacts\":[{\"artifact\":\"go\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"reference\":\"container-image-registry/nats/go@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}],\"imports\":[\"git.novox.be/novox/mesh-sdk/go\"],\"toolchain\":{\"language\":\"go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"build\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:03:00Z\",\"id\":\"a1b2\",\"node\":\"ace\",\"result\":{\"artifacts\":[{\"artifact\":\"go\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"reference\":\"container-image-registry/nats/go@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}],\"imports\":[\"git.novox.be/novox/mesh-sdk/go\"],\"toolchain\":{\"language\":\"go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"build\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a finished check step with its levels", subject: "", given: "{\"ended\":\"2026-11-05T10:06:00Z\",\"id\":\"c3d4\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"changed\":[\"a.go\",\"modules/m/main.go\"],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"merge\":\"fedcba9876543210fedcba9876543210fedcba98\",\"modules\":[\".\"],\"trees\":{\"modules/m\":{\"locks\":{\"go.sum\":\"c5fc83c01e92404452b986527d239140ccf9a48b88e0c268fbf38c2e1429e9c9\"},\"manifest\":\"ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356\",\"tree\":\"1111111111111111111111111111111111111111\"}},\"workspace\":\"mvii1iwu5414e4\"},\"levels\":[{\"digest\":{\"level\":\"unit\",\"packages\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\"}]},\"kept\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\",\"tests\":12}],\"level\":\"unit\",\"tests\":[{\"package\":\"./modules/b\",\"pass\":true}],\"unmet\":[{\"needs\":[\"docker\"],\"package\":\"./modules/c\"}]}]},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:06:00Z\",\"id\":\"c3d4\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"changed\":[\"a.go\",\"modules/m/main.go\"],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"merge\":\"fedcba9876543210fedcba9876543210fedcba98\",\"modules\":[\".\"],\"trees\":{\"modules/m\":{\"locks\":{\"go.sum\":\"c5fc83c01e92404452b986527d239140ccf9a48b88e0c268fbf38c2e1429e9c9\"},\"manifest\":\"ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356\",\"tree\":\"1111111111111111111111111111111111111111\"}},\"workspace\":\"mvii1iwu5414e4\"},\"levels\":[{\"digest\":{\"level\":\"unit\",\"packages\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\"}]},\"kept\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\",\"tests\":12}],\"level\":\"unit\",\"tests\":[{\"package\":\"./modules/b\",\"pass\":true}],\"unmet\":[{\"needs\":[\"docker\"],\"package\":\"./modules/c\"}]}]},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a declined step", subject: "", given: "{\"id\":\"c3d5\",\"node\":\"ace\",\"result\":{\"declined\":true,\"lacks\":[\"docker\"],\"needs\":[\"docker\"]},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"refused\",\"tool\":\"check\",\"v\":1}", wire: "{\"id\":\"c3d5\",\"node\":\"ace\",\"result\":{\"declined\":true,\"lacks\":[\"docker\"],\"needs\":[\"docker\"]},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"refused\",\"tool\":\"check\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a finished check step whose head does not merge", subject: "", given: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d5\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"conflicts\":[\"a.go\",\"sub/b.go\"],\"modules\":[\".\"],\"workspace\":\"mvii1iwz5050eb\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d5\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"conflicts\":[\"a.go\",\"sub/b.go\"],\"modules\":[\".\"],\"workspace\":\"mvii1iwz5050eb\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; 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)
}
}
}
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo
go 1.26.0
@@ -1,557 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// Package nodebuildimage is the contract of the seat "node-build-image", defined whole (seats/node-build-image/seat.json): The image build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers.
package nodebuildimage
import (
"bytes"
"encoding/json"
"errors"
"strings"
"time"
)
// Seat is the seat's name.
const Seat = "node-build-image"
const (
VerbCurrent = "current"
VerbPause = "pause"
VerbResume = "resume"
)
const (
AcceptDelivery = "delivery"
AcceptCheck = "check"
AcceptRecheck = "recheck"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbCurrent, VerbPause, VerbResume}
}
// AcceptSubjectDelivery is where delivery is sent.
const AcceptSubjectDelivery = "mesh.seat.node-build-image.accept.delivery"
// AcceptSubjectCheck is where check is sent.
const AcceptSubjectCheck = "mesh.seat.node-build-image.accept.check"
// AcceptSubjectRecheck is where recheck is sent.
const AcceptSubjectRecheck = "mesh.seat.node-build-image.accept.recheck"
const (
StateSteps = "steps"
StatePauses = "pauses"
StateToolchains = "toolchains"
)
// StepsByNodeIDKey is the key of one value of the state steps under the form "<node>.<id>".
func StepsByNodeIDKey(node, id string) string { return node + "." + id }
// ParseStepsByNodeIDKey reads the parts of a key of the form "<node>.<id>", in the order of the form; the last result is false for a key of another form.
func ParseStepsByNodeIDKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeStepsByNodeIDValue reads one value of the state steps under the form "<node>.<id>", refusing a field StepsByNodeIDValue does not know and any text after the value.
func DecodeStepsByNodeIDValue(raw []byte) (StepsByNodeIDValue, error) {
var v StepsByNodeIDValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PausesByPausedNodeKey is the key of one value of the state pauses under the form "paused.<node>".
func PausesByPausedNodeKey(node string) string { return "paused." + node }
// ParsePausesByPausedNodeKey reads the parts of a key of the form "paused.<node>", in the order of the form; the last result is false for a key of another form.
func ParsePausesByPausedNodeKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "paused.") {
return "", false
}
rest = rest[7:]
if rest == "" {
return "", false
}
v1 := rest
rest = ""
if rest != "" {
return "", false
}
return v1, true
}
// DecodePausesByPausedNodeValue reads one value of the state pauses under the form "paused.<node>", refusing a field PausesByPausedNodeValue does not know and any text after the value.
func DecodePausesByPausedNodeValue(raw []byte) (PausesByPausedNodeValue, error) {
var v PausesByPausedNodeValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PausesByCapacityNodeKey is the key of one value of the state pauses under the form "capacity.<node>".
func PausesByCapacityNodeKey(node string) string { return "capacity." + node }
// ParsePausesByCapacityNodeKey reads the parts of a key of the form "capacity.<node>", in the order of the form; the last result is false for a key of another form.
func ParsePausesByCapacityNodeKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "capacity.") {
return "", false
}
rest = rest[9:]
if rest == "" {
return "", false
}
v1 := rest
rest = ""
if rest != "" {
return "", false
}
return v1, true
}
// DecodePausesByCapacityNodeValue reads one value of the state pauses under the form "capacity.<node>", refusing a field PausesByCapacityNodeValue does not know and any text after the value.
func DecodePausesByCapacityNodeValue(raw []byte) (PausesByCapacityNodeValue, error) {
var v PausesByCapacityNodeValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// ToolchainsByNodeLanguageKey is the key of one value of the state toolchains under the form "<node>.<language>".
func ToolchainsByNodeLanguageKey(node, language string) string { return node + "." + language }
// ParseToolchainsByNodeLanguageKey reads the parts of a key of the form "<node>.<language>", in the order of the form; the last result is false for a key of another form.
func ParseToolchainsByNodeLanguageKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeToolchainsByNodeLanguageValue reads one value of the state toolchains under the form "<node>.<language>", refusing a field ToolchainsByNodeLanguageValue does not know and any text after the value.
func DecodeToolchainsByNodeLanguageValue(raw []byte) (ToolchainsByNodeLanguageValue, error) {
var v ToolchainsByNodeLanguageValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// AskArtifact is one artifact of a build ask: built from the fetched tree, or copied from an upstream pin.
type AskArtifact struct {
Artifact string `json:"artifact"`
From string `json:"from,omitempty"`
Seat string `json:"seat,omitempty"`
Version string `json:"version,omitempty"`
}
// BuildAnswer is a type of the contract.
type BuildAnswer struct {
Artifacts []BuiltArtifact `json:"artifacts"`
Imports []string `json:"imports,omitempty"`
OutOfStep []string `json:"out-of-step,omitempty"`
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// BuiltArtifact is one artifact a build made: its digest and the reference it was published under.
type BuiltArtifact struct {
Artifact string `json:"artifact"`
Digest string `json:"digest"`
Reference string `json:"reference,omitempty"`
}
// Capacity is how many steps the node runs at once.
type Capacity struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
BuildHours string `json:"build-hours,omitempty"`
BuildHoursFault string `json:"build-hours-fault,omitempty"`
ByCpu int64 `json:"by-cpu,omitempty"`
Configured int64 `json:"configured"`
ConfiguredFrom string `json:"configured-from,omitempty"`
CoresPerStep float64 `json:"cores-per-step,omitempty"`
Effective int64 `json:"effective"`
Ineligible string `json:"ineligible,omitempty"`
OutsideHours bool `json:"outside-hours,omitempty"`
QuotaCores float64 `json:"quota-cores,omitempty"`
QuotaFrom string `json:"quota-from,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
Why string `json:"why"`
}
// CheckAnswer is a type of the contract.
type CheckAnswer struct {
Fetch FetchAnswer `json:"fetch"`
Levels []LevelAnswer `json:"levels,omitempty"`
}
// ComposeAnswer is a type of the contract.
type ComposeAnswer struct {
Built bool `json:"built"`
Key string `json:"key,omitempty"`
// the compose-candidate verdict
Verdict any `json:"verdict,omitempty"`
}
// FetchBesideItem is a type of the contract.
type FetchBesideItem struct {
Commit string `json:"commit"`
Repository string `json:"repository"`
}
// FetchPackage is the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's.
type FetchPackage struct {
Commit string `json:"commit,omitempty"`
Name string `json:"name,omitempty"`
Ref string `json:"ref,omitempty"`
Repository string `json:"repository,omitempty"`
Version string `json:"version,omitempty"`
}
// Fetch is what a build module's fetch tool is asked: the repository and the full commit, the pull request's ref when only its head carries the commit, the trunk commit to merge onto for a merge preview, the module's directory and named paths, the mesh packages to build against, and the sibling repositories placed beside.
type Fetch struct {
// the trunk commit a merge preview merges onto
Base string `json:"base,omitempty"`
// the sibling repositories placed beside the fetched tree
Beside []FetchBesideItem `json:"beside,omitempty"`
// the full commit
Commit string `json:"commit"`
// the module's directory in the repository ("." for its root), with paths
Dir string `json:"dir,omitempty"`
// the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's
Package *FetchPackage `json:"package,omitempty"`
// the version (a full commit) of each mesh package to build against, by its module path
Packages map[string]string `json:"packages,omitempty"`
// the paths the module names, relative to dir
Paths []string `json:"paths,omitempty"`
// the pull request's ref, when only its head carries the commit
Ref string `json:"ref,omitempty"`
// <owner>/<name>
Repository string `json:"repository"`
}
// FetchAnswer is what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package.
type FetchAnswer struct {
Base string `json:"base,omitempty"`
Beside []PlacedBeside `json:"beside,omitempty"`
Changed []string `json:"changed,omitempty"`
Commit string `json:"commit"`
Conflicts []string `json:"conflicts,omitempty"`
Group string `json:"group,omitempty"`
Installed []string `json:"installed,omitempty"`
Merge string `json:"merge,omitempty"`
Modules []string `json:"modules"`
Packages []TakenVersion `json:"packages,omitempty"`
Programs []Program `json:"programs,omitempty"`
Refused []FetchRefusal `json:"refused,omitempty"`
Trees map[string]ModuleTree `json:"trees,omitempty"`
Workspace string `json:"workspace"`
}
// FetchRefusal is a TypeScript module whose packages the fetch did not install, with why.
type FetchRefusal struct {
Module string `json:"module"`
Reason string `json:"reason"`
}
// LevelAnswerDigestNeedsItem is a type of the contract.
type LevelAnswerDigestNeedsItem struct {
Needs []string `json:"needs,omitempty"`
Package string `json:"package,omitempty"`
}
// LevelAnswerDigestPackagesItem is a type of the contract.
type LevelAnswerDigestPackagesItem struct {
Digest string `json:"digest"`
Package string `json:"package"`
}
// LevelAnswerDigest is a type of the contract.
type LevelAnswerDigest struct {
Beside []string `json:"beside,omitempty"`
JudgedBy string `json:"judged_by,omitempty"`
Level string `json:"level,omitempty"`
Needs []LevelAnswerDigestNeedsItem `json:"needs,omitempty"`
Packages []LevelAnswerDigestPackagesItem `json:"packages,omitempty"`
WithoutTests []string `json:"without_tests,omitempty"`
}
// LevelAnswerKeptItem is a type of the contract.
type LevelAnswerKeptItem struct {
Digest string `json:"digest,omitempty"`
Package string `json:"package,omitempty"`
Tests int64 `json:"tests,omitempty"`
}
// LevelAnswerUnmetItem is a type of the contract.
type LevelAnswerUnmetItem struct {
Needs []string `json:"needs,omitempty"`
Package string `json:"package,omitempty"`
}
// LevelAnswer is one level of a check ask: the digest's answer, the kept verdicts taken, one test result per test step run, and the packages the node could not test.
type LevelAnswer struct {
Digest *LevelAnswerDigest `json:"digest,omitempty"`
Judged string `json:"judged,omitempty"`
Kept []LevelAnswerKeptItem `json:"kept,omitempty"`
Level string `json:"level"`
Tests []any `json:"tests,omitempty"`
Unmet []LevelAnswerUnmetItem `json:"unmet,omitempty"`
}
// ModuleTree is the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths.
type ModuleTree struct {
Locks map[string]string `json:"locks,omitempty"`
Manifest string `json:"manifest"`
// no longer filled (ADR 0593 section 2); kept so a preview written before still reads
Packages []string `json:"packages,omitempty"`
Paths map[string]string `json:"paths,omitempty"`
Tree string `json:"tree"`
}
// Pause is whether the node takes no new ask.
type Pause struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
By string `json:"by,omitempty"`
Paused bool `json:"paused"`
// the layout version
V int64 `json:"v,omitempty"`
}
// PlacedBeside is a sibling repository placed as a plain directory under the workspace's beside directory.
type PlacedBeside struct {
Commit string `json:"commit"`
Dir string `json:"dir"`
Repository string `json:"repository"`
}
// Program is a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it.
type Program struct {
Commit string `json:"commit,omitempty"`
// ELF, Mach-O or PE
Kind string `json:"kind"`
Path string `json:"path"`
}
// Step is one step a build module runs on a node.
type Step struct {
// when (RFC 3339 time)
Ended *time.Time `json:"ended,omitempty"`
ID string `json:"id"`
Node string `json:"node"`
Owner string `json:"owner,omitempty"`
Phase string `json:"phase,omitempty"`
// when (RFC 3339 time)
PhaseAt *time.Time `json:"phase-at,omitempty"`
Reason string `json:"reason,omitempty"`
Result *StepResult `json:"result,omitempty"`
// when (RFC 3339 time)
Started time.Time `json:"started"`
// running, passed, failed or refused
State string `json:"state"`
Tool string `json:"tool"`
// the layout version
V int64 `json:"v,omitempty"`
What string `json:"what,omitempty"`
}
// StepAskBuild is a type of the contract.
type StepAskBuild struct {
Artifacts []AskArtifact `json:"artifacts"`
Fetch *Fetch `json:"fetch,omitempty"`
Fp string `json:"fp,omitempty"`
Module string `json:"module"`
Path string `json:"path,omitempty"`
Publish bool `json:"publish,omitempty"`
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// StepAskCheck is a type of the contract.
type StepAskCheck struct {
Fetch Fetch `json:"fetch"`
Kept bool `json:"kept,omitempty"`
Levels []string `json:"levels"`
// the node names the check's tests run against
NodeNames []string `json:"node-names,omitempty"`
Packages []string `json:"packages,omitempty"`
Run string `json:"run,omitempty"`
Runner string `json:"runner,omitempty"`
RunnerWhen string `json:"runner_when,omitempty"`
// the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// StepAskCompose is a type of the contract.
type StepAskCompose struct {
Dir string `json:"dir"`
Fetch Fetch `json:"fetch"`
Package string `json:"package"`
Reader string `json:"reader,omitempty"`
// the compose-candidate request
Request any `json:"request"`
}
// StepAsk is one ask of a build module.
type StepAsk struct {
Asker string `json:"asker"`
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
Build *StepAskBuild `json:"build,omitempty"`
Check *StepAskCheck `json:"check,omitempty"`
Compose *StepAskCompose `json:"compose,omitempty"`
ID string `json:"id"`
// build, check or compose
Kind string `json:"kind"`
// go, typescript or image
Language string `json:"language"`
Needs []string `json:"needs,omitempty"`
// package to the tagged version a build pins
Packages map[string]string `json:"packages,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
}
// StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four.
type StepResult struct {
Artifacts []BuiltArtifact `json:"artifacts,omitempty"`
Built bool `json:"built,omitempty"`
// the holder declined the ask: its node lacks what the ask needs
Declined bool `json:"declined,omitempty"`
Fetch *FetchAnswer `json:"fetch,omitempty"`
Imports []string `json:"imports,omitempty"`
Key string `json:"key,omitempty"`
// what the holder's node lacks of it
Lacks []string `json:"lacks,omitempty"`
Levels []LevelAnswer `json:"levels,omitempty"`
// what the declined ask needs
Needs []string `json:"needs,omitempty"`
OutOfStep []string `json:"out-of-step,omitempty"`
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
// the compose-candidate verdict (the composer's own layout)
Verdict any `json:"verdict,omitempty"`
}
// TakenVersion is of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said.
type TakenVersion struct {
Asked string `json:"asked"`
Built string `json:"built"`
Dir string `json:"dir"`
Named string `json:"named"`
Package string `json:"package"`
Said string `json:"said,omitempty"`
Sum string `json:"sum,omitempty"`
}
// Toolchain is the toolchain a node reports.
type Toolchain struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
// go or typescript
Language string `json:"language"`
Recipe string `json:"recipe"`
// the layout version
V int64 `json:"v,omitempty"`
Version string `json:"version"`
}
// ToolchainUsed is the toolchain a build was made with: the language, the installed version and the build module's recipe.
type ToolchainUsed struct {
Language string `json:"language"`
Recipe string `json:"recipe"`
Version string `json:"version"`
}
// CurrentInput is what current takes.
type CurrentInput struct {
}
// CurrentAnswer is what current answers.
type CurrentAnswer = map[string]any
// PauseInput is what pause takes.
type PauseInput struct {
}
// PauseAnswer is what pause answers.
type PauseAnswer = map[string]any
// ResumeInput is what resume takes.
type ResumeInput struct {
}
// ResumeAnswer is what resume answers.
type ResumeAnswer = map[string]any
// DeliveryAccept is the body of delivery.
type DeliveryAccept = StepAsk
// CheckAccept is the body of check.
type CheckAccept = StepAsk
// RecheckAccept is the body of recheck.
type RecheckAccept = StepAsk
// StepsByNodeIDValue is one value of the state steps under the key <node>.<id>.
type StepsByNodeIDValue = Step
// PausesByPausedNodeValue is one value of the state pauses under the key paused.<node>.
type PausesByPausedNodeValue = Pause
// PausesByCapacityNodeValue is one value of the state pauses under the key capacity.<node>.
type PausesByCapacityNodeValue = Capacity
// ToolchainsByNodeLanguageValue is one value of the state toolchains under the key <node>.<language>.
type ToolchainsByNodeLanguageValue = Toolchain
@@ -1,68 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package nodebuildimage
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/node-build-image/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: "a running step", subject: "", given: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}", wire: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a paused node", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}",
decode: func(b []byte) (any, error) {
var v PausesByPausedNodeValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a node's capacity", subject: "", given: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}", wire: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}",
decode: func(b []byte) (any, error) {
var v PausesByCapacityNodeValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a node's toolchain", subject: "", given: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}", wire: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}",
decode: func(b []byte) (any, error) {
var v ToolchainsByNodeLanguageValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a build ask", subject: "mesh.seat.node-build-image.accept.delivery", given: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", wire: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}",
decode: func(b []byte) (any, error) { var v DeliveryAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-image\\.accept\\.delivery$")},
}
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)
}
}
}
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage
go 1.26.0
@@ -1,557 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
// Package nodebuildnode is the contract of the seat "node-build-node", defined whole (seats/node-build-node/seat.json): The Node build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers.
package nodebuildnode
import (
"bytes"
"encoding/json"
"errors"
"strings"
"time"
)
// Seat is the seat's name.
const Seat = "node-build-node"
const (
VerbCurrent = "current"
VerbPause = "pause"
VerbResume = "resume"
)
const (
AcceptDelivery = "delivery"
AcceptCheck = "check"
AcceptRecheck = "recheck"
)
// Verbs is every verb the seat serves, in its definition's order: a new copy each call.
func Verbs() []string {
return []string{VerbCurrent, VerbPause, VerbResume}
}
// AcceptSubjectDelivery is where delivery is sent.
const AcceptSubjectDelivery = "mesh.seat.node-build-node.accept.delivery"
// AcceptSubjectCheck is where check is sent.
const AcceptSubjectCheck = "mesh.seat.node-build-node.accept.check"
// AcceptSubjectRecheck is where recheck is sent.
const AcceptSubjectRecheck = "mesh.seat.node-build-node.accept.recheck"
const (
StateSteps = "steps"
StatePauses = "pauses"
StateToolchains = "toolchains"
)
// StepsByNodeIDKey is the key of one value of the state steps under the form "<node>.<id>".
func StepsByNodeIDKey(node, id string) string { return node + "." + id }
// ParseStepsByNodeIDKey reads the parts of a key of the form "<node>.<id>", in the order of the form; the last result is false for a key of another form.
func ParseStepsByNodeIDKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeStepsByNodeIDValue reads one value of the state steps under the form "<node>.<id>", refusing a field StepsByNodeIDValue does not know and any text after the value.
func DecodeStepsByNodeIDValue(raw []byte) (StepsByNodeIDValue, error) {
var v StepsByNodeIDValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PausesByPausedNodeKey is the key of one value of the state pauses under the form "paused.<node>".
func PausesByPausedNodeKey(node string) string { return "paused." + node }
// ParsePausesByPausedNodeKey reads the parts of a key of the form "paused.<node>", in the order of the form; the last result is false for a key of another form.
func ParsePausesByPausedNodeKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "paused.") {
return "", false
}
rest = rest[7:]
if rest == "" {
return "", false
}
v1 := rest
rest = ""
if rest != "" {
return "", false
}
return v1, true
}
// DecodePausesByPausedNodeValue reads one value of the state pauses under the form "paused.<node>", refusing a field PausesByPausedNodeValue does not know and any text after the value.
func DecodePausesByPausedNodeValue(raw []byte) (PausesByPausedNodeValue, error) {
var v PausesByPausedNodeValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// PausesByCapacityNodeKey is the key of one value of the state pauses under the form "capacity.<node>".
func PausesByCapacityNodeKey(node string) string { return "capacity." + node }
// ParsePausesByCapacityNodeKey reads the parts of a key of the form "capacity.<node>", in the order of the form; the last result is false for a key of another form.
func ParsePausesByCapacityNodeKey(key string) (string, bool) {
rest := key
if !strings.HasPrefix(rest, "capacity.") {
return "", false
}
rest = rest[9:]
if rest == "" {
return "", false
}
v1 := rest
rest = ""
if rest != "" {
return "", false
}
return v1, true
}
// DecodePausesByCapacityNodeValue reads one value of the state pauses under the form "capacity.<node>", refusing a field PausesByCapacityNodeValue does not know and any text after the value.
func DecodePausesByCapacityNodeValue(raw []byte) (PausesByCapacityNodeValue, error) {
var v PausesByCapacityNodeValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// ToolchainsByNodeLanguageKey is the key of one value of the state toolchains under the form "<node>.<language>".
func ToolchainsByNodeLanguageKey(node, language string) string { return node + "." + language }
// ParseToolchainsByNodeLanguageKey reads the parts of a key of the form "<node>.<language>", in the order of the form; the last result is false for a key of another form.
func ParseToolchainsByNodeLanguageKey(key string) (string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", false
}
rest = after0
if rest == "" {
return "", "", false
}
v2 := rest
rest = ""
if rest != "" {
return "", "", false
}
return v0, v2, true
}
// DecodeToolchainsByNodeLanguageValue reads one value of the state toolchains under the form "<node>.<language>", refusing a field ToolchainsByNodeLanguageValue does not know and any text after the value.
func DecodeToolchainsByNodeLanguageValue(raw []byte) (ToolchainsByNodeLanguageValue, error) {
var v ToolchainsByNodeLanguageValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// AskArtifact is one artifact of a build ask: built from the fetched tree, or copied from an upstream pin.
type AskArtifact struct {
Artifact string `json:"artifact"`
From string `json:"from,omitempty"`
Seat string `json:"seat,omitempty"`
Version string `json:"version,omitempty"`
}
// BuildAnswer is a type of the contract.
type BuildAnswer struct {
Artifacts []BuiltArtifact `json:"artifacts"`
Imports []string `json:"imports,omitempty"`
OutOfStep []string `json:"out-of-step,omitempty"`
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// BuiltArtifact is one artifact a build made: its digest and the reference it was published under.
type BuiltArtifact struct {
Artifact string `json:"artifact"`
Digest string `json:"digest"`
Reference string `json:"reference,omitempty"`
}
// Capacity is how many steps the node runs at once.
type Capacity struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
BuildHours string `json:"build-hours,omitempty"`
BuildHoursFault string `json:"build-hours-fault,omitempty"`
ByCpu int64 `json:"by-cpu,omitempty"`
Configured int64 `json:"configured"`
ConfiguredFrom string `json:"configured-from,omitempty"`
CoresPerStep float64 `json:"cores-per-step,omitempty"`
Effective int64 `json:"effective"`
Ineligible string `json:"ineligible,omitempty"`
OutsideHours bool `json:"outside-hours,omitempty"`
QuotaCores float64 `json:"quota-cores,omitempty"`
QuotaFrom string `json:"quota-from,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
Why string `json:"why"`
}
// CheckAnswer is a type of the contract.
type CheckAnswer struct {
Fetch FetchAnswer `json:"fetch"`
Levels []LevelAnswer `json:"levels,omitempty"`
}
// ComposeAnswer is a type of the contract.
type ComposeAnswer struct {
Built bool `json:"built"`
Key string `json:"key,omitempty"`
// the compose-candidate verdict
Verdict any `json:"verdict,omitempty"`
}
// FetchBesideItem is a type of the contract.
type FetchBesideItem struct {
Commit string `json:"commit"`
Repository string `json:"repository"`
}
// FetchPackage is the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's.
type FetchPackage struct {
Commit string `json:"commit,omitempty"`
Name string `json:"name,omitempty"`
Ref string `json:"ref,omitempty"`
Repository string `json:"repository,omitempty"`
Version string `json:"version,omitempty"`
}
// Fetch is what a build module's fetch tool is asked: the repository and the full commit, the pull request's ref when only its head carries the commit, the trunk commit to merge onto for a merge preview, the module's directory and named paths, the mesh packages to build against, and the sibling repositories placed beside.
type Fetch struct {
// the trunk commit a merge preview merges onto
Base string `json:"base,omitempty"`
// the sibling repositories placed beside the fetched tree
Beside []FetchBesideItem `json:"beside,omitempty"`
// the full commit
Commit string `json:"commit"`
// the module's directory in the repository ("." for its root), with paths
Dir string `json:"dir,omitempty"`
// the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's
Package *FetchPackage `json:"package,omitempty"`
// the version (a full commit) of each mesh package to build against, by its module path
Packages map[string]string `json:"packages,omitempty"`
// the paths the module names, relative to dir
Paths []string `json:"paths,omitempty"`
// the pull request's ref, when only its head carries the commit
Ref string `json:"ref,omitempty"`
// <owner>/<name>
Repository string `json:"repository"`
}
// FetchAnswer is what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package.
type FetchAnswer struct {
Base string `json:"base,omitempty"`
Beside []PlacedBeside `json:"beside,omitempty"`
Changed []string `json:"changed,omitempty"`
Commit string `json:"commit"`
Conflicts []string `json:"conflicts,omitempty"`
Group string `json:"group,omitempty"`
Installed []string `json:"installed,omitempty"`
Merge string `json:"merge,omitempty"`
Modules []string `json:"modules"`
Packages []TakenVersion `json:"packages,omitempty"`
Programs []Program `json:"programs,omitempty"`
Refused []FetchRefusal `json:"refused,omitempty"`
Trees map[string]ModuleTree `json:"trees,omitempty"`
Workspace string `json:"workspace"`
}
// FetchRefusal is a TypeScript module whose packages the fetch did not install, with why.
type FetchRefusal struct {
Module string `json:"module"`
Reason string `json:"reason"`
}
// LevelAnswerDigestNeedsItem is a type of the contract.
type LevelAnswerDigestNeedsItem struct {
Needs []string `json:"needs,omitempty"`
Package string `json:"package,omitempty"`
}
// LevelAnswerDigestPackagesItem is a type of the contract.
type LevelAnswerDigestPackagesItem struct {
Digest string `json:"digest"`
Package string `json:"package"`
}
// LevelAnswerDigest is a type of the contract.
type LevelAnswerDigest struct {
Beside []string `json:"beside,omitempty"`
JudgedBy string `json:"judged_by,omitempty"`
Level string `json:"level,omitempty"`
Needs []LevelAnswerDigestNeedsItem `json:"needs,omitempty"`
Packages []LevelAnswerDigestPackagesItem `json:"packages,omitempty"`
WithoutTests []string `json:"without_tests,omitempty"`
}
// LevelAnswerKeptItem is a type of the contract.
type LevelAnswerKeptItem struct {
Digest string `json:"digest,omitempty"`
Package string `json:"package,omitempty"`
Tests int64 `json:"tests,omitempty"`
}
// LevelAnswerUnmetItem is a type of the contract.
type LevelAnswerUnmetItem struct {
Needs []string `json:"needs,omitempty"`
Package string `json:"package,omitempty"`
}
// LevelAnswer is one level of a check ask: the digest's answer, the kept verdicts taken, one test result per test step run, and the packages the node could not test.
type LevelAnswer struct {
Digest *LevelAnswerDigest `json:"digest,omitempty"`
Judged string `json:"judged,omitempty"`
Kept []LevelAnswerKeptItem `json:"kept,omitempty"`
Level string `json:"level"`
Tests []any `json:"tests,omitempty"`
Unmet []LevelAnswerUnmetItem `json:"unmet,omitempty"`
}
// ModuleTree is the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths.
type ModuleTree struct {
Locks map[string]string `json:"locks,omitempty"`
Manifest string `json:"manifest"`
// no longer filled (ADR 0593 section 2); kept so a preview written before still reads
Packages []string `json:"packages,omitempty"`
Paths map[string]string `json:"paths,omitempty"`
Tree string `json:"tree"`
}
// Pause is whether the node takes no new ask.
type Pause struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
By string `json:"by,omitempty"`
Paused bool `json:"paused"`
// the layout version
V int64 `json:"v,omitempty"`
}
// PlacedBeside is a sibling repository placed as a plain directory under the workspace's beside directory.
type PlacedBeside struct {
Commit string `json:"commit"`
Dir string `json:"dir"`
Repository string `json:"repository"`
}
// Program is a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it.
type Program struct {
Commit string `json:"commit,omitempty"`
// ELF, Mach-O or PE
Kind string `json:"kind"`
Path string `json:"path"`
}
// Step is one step a build module runs on a node.
type Step struct {
// when (RFC 3339 time)
Ended *time.Time `json:"ended,omitempty"`
ID string `json:"id"`
Node string `json:"node"`
Owner string `json:"owner,omitempty"`
Phase string `json:"phase,omitempty"`
// when (RFC 3339 time)
PhaseAt *time.Time `json:"phase-at,omitempty"`
Reason string `json:"reason,omitempty"`
Result *StepResult `json:"result,omitempty"`
// when (RFC 3339 time)
Started time.Time `json:"started"`
// running, passed, failed or refused
State string `json:"state"`
Tool string `json:"tool"`
// the layout version
V int64 `json:"v,omitempty"`
What string `json:"what,omitempty"`
}
// StepAskBuild is a type of the contract.
type StepAskBuild struct {
Artifacts []AskArtifact `json:"artifacts"`
Fetch *Fetch `json:"fetch,omitempty"`
Fp string `json:"fp,omitempty"`
Module string `json:"module"`
Path string `json:"path,omitempty"`
Publish bool `json:"publish,omitempty"`
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// StepAskCheck is a type of the contract.
type StepAskCheck struct {
Fetch Fetch `json:"fetch"`
Kept bool `json:"kept,omitempty"`
Levels []string `json:"levels"`
// the node names the check's tests run against
NodeNames []string `json:"node-names,omitempty"`
Packages []string `json:"packages,omitempty"`
Run string `json:"run,omitempty"`
Runner string `json:"runner,omitempty"`
RunnerWhen string `json:"runner_when,omitempty"`
// the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// StepAskCompose is a type of the contract.
type StepAskCompose struct {
Dir string `json:"dir"`
Fetch Fetch `json:"fetch"`
Package string `json:"package"`
Reader string `json:"reader,omitempty"`
// the compose-candidate request
Request any `json:"request"`
}
// StepAsk is one ask of a build module.
type StepAsk struct {
Asker string `json:"asker"`
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
Build *StepAskBuild `json:"build,omitempty"`
Check *StepAskCheck `json:"check,omitempty"`
Compose *StepAskCompose `json:"compose,omitempty"`
ID string `json:"id"`
// build, check or compose
Kind string `json:"kind"`
// go, typescript or image
Language string `json:"language"`
Needs []string `json:"needs,omitempty"`
// package to the tagged version a build pins
Packages map[string]string `json:"packages,omitempty"`
// the layout version
V int64 `json:"v,omitempty"`
}
// StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four.
type StepResult struct {
Artifacts []BuiltArtifact `json:"artifacts,omitempty"`
Built bool `json:"built,omitempty"`
// the holder declined the ask: its node lacks what the ask needs
Declined bool `json:"declined,omitempty"`
Fetch *FetchAnswer `json:"fetch,omitempty"`
Imports []string `json:"imports,omitempty"`
Key string `json:"key,omitempty"`
// what the holder's node lacks of it
Lacks []string `json:"lacks,omitempty"`
Levels []LevelAnswer `json:"levels,omitempty"`
// what the declined ask needs
Needs []string `json:"needs,omitempty"`
OutOfStep []string `json:"out-of-step,omitempty"`
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
// the compose-candidate verdict (the composer's own layout)
Verdict any `json:"verdict,omitempty"`
}
// TakenVersion is of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said.
type TakenVersion struct {
Asked string `json:"asked"`
Built string `json:"built"`
Dir string `json:"dir"`
Named string `json:"named"`
Package string `json:"package"`
Said string `json:"said,omitempty"`
Sum string `json:"sum,omitempty"`
}
// Toolchain is the toolchain a node reports.
type Toolchain struct {
// when (RFC 3339 time)
At *time.Time `json:"at,omitempty"`
// go or typescript
Language string `json:"language"`
Recipe string `json:"recipe"`
// the layout version
V int64 `json:"v,omitempty"`
Version string `json:"version"`
}
// ToolchainUsed is the toolchain a build was made with: the language, the installed version and the build module's recipe.
type ToolchainUsed struct {
Language string `json:"language"`
Recipe string `json:"recipe"`
Version string `json:"version"`
}
// CurrentInput is what current takes.
type CurrentInput struct {
}
// CurrentAnswer is what current answers.
type CurrentAnswer = map[string]any
// PauseInput is what pause takes.
type PauseInput struct {
}
// PauseAnswer is what pause answers.
type PauseAnswer = map[string]any
// ResumeInput is what resume takes.
type ResumeInput struct {
}
// ResumeAnswer is what resume answers.
type ResumeAnswer = map[string]any
// DeliveryAccept is the body of delivery.
type DeliveryAccept = StepAsk
// CheckAccept is the body of check.
type CheckAccept = StepAsk
// RecheckAccept is the body of recheck.
type RecheckAccept = StepAsk
// StepsByNodeIDValue is one value of the state steps under the key <node>.<id>.
type StepsByNodeIDValue = Step
// PausesByPausedNodeValue is one value of the state pauses under the key paused.<node>.
type PausesByPausedNodeValue = Pause
// PausesByCapacityNodeValue is one value of the state pauses under the key capacity.<node>.
type PausesByCapacityNodeValue = Capacity
// ToolchainsByNodeLanguageValue is one value of the state toolchains under the key <node>.<language>.
type ToolchainsByNodeLanguageValue = Toolchain
@@ -1,72 +0,0 @@
// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package nodebuildnode
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/node-build-node/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: "a running step", subject: "", given: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}", wire: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a paused node", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}",
decode: func(b []byte) (any, error) {
var v PausesByPausedNodeValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a node's capacity", subject: "", given: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}", wire: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}",
decode: func(b []byte) (any, error) {
var v PausesByCapacityNodeValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a node's toolchain", subject: "", given: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}", wire: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}",
decode: func(b []byte) (any, error) {
var v ToolchainsByNodeLanguageValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "a build ask", subject: "mesh.seat.node-build-node.accept.delivery", given: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", wire: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}",
decode: func(b []byte) (any, error) { var v DeliveryAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-node\\.accept\\.delivery$")},
{name: "a check ask with the group's tagged package", subject: "mesh.seat.node-build-node.accept.check", given: "{\"asker\":\"mesh-delivery-checker\",\"at\":\"2026-11-05T10:00:00Z\",\"check\":{\"fetch\":{\"base\":\"89abcdef0123456789abcdef0123456789abcdef\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"package\":{\"name\":\"@novox/mesh-sdk\",\"version\":\"0.1.15-feat-x.1\"},\"ref\":\"refs/pull/7/head\",\"repository\":\"novox/mesh-delivery\"},\"kept\":true,\"levels\":[\"unit\",\"integration\"],\"node-names\":[\"ace\",\"g14\"],\"toolchain\":{\"language\":\"typescript\",\"recipe\":\"1\",\"version\":\"26.12.0\"}},\"id\":\"c3d5\",\"kind\":\"check\",\"language\":\"typescript\",\"v\":1}", wire: "{\"asker\":\"mesh-delivery-checker\",\"at\":\"2026-11-05T10:00:00Z\",\"check\":{\"fetch\":{\"base\":\"89abcdef0123456789abcdef0123456789abcdef\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"package\":{\"name\":\"@novox/mesh-sdk\",\"version\":\"0.1.15-feat-x.1\"},\"ref\":\"refs/pull/7/head\",\"repository\":\"novox/mesh-delivery\"},\"kept\":true,\"levels\":[\"unit\",\"integration\"],\"node-names\":[\"ace\",\"g14\"],\"toolchain\":{\"language\":\"typescript\",\"recipe\":\"1\",\"version\":\"26.12.0\"}},\"id\":\"c3d5\",\"kind\":\"check\",\"language\":\"typescript\",\"v\":1}",
decode: func(b []byte) (any, error) { var v CheckAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-node\\.accept\\.check$")},
{name: "a finished check step of a TypeScript merge preview", subject: "", given: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d6\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"changed\":[\"modules/m/package.json\"],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"merge\":\"0123456789abcdef0123456789abcdef01234567\",\"modules\":[\"modules/m\"],\"trees\":{\"modules/m\":{\"manifest\":\"0581b699d9d05517e69b21f5cedbeb2eee8aba2b205280b561be731594d7feef\",\"tree\":\"0123456789abcdef0123456789abcdef01234567\"}},\"workspace\":\"mvii1sxt979f67\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d6\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"changed\":[\"modules/m/package.json\"],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"merge\":\"0123456789abcdef0123456789abcdef01234567\",\"modules\":[\"modules/m\"],\"trees\":{\"modules/m\":{\"manifest\":\"0581b699d9d05517e69b21f5cedbeb2eee8aba2b205280b561be731594d7feef\",\"tree\":\"0123456789abcdef0123456789abcdef01234567\"}},\"workspace\":\"mvii1sxt979f67\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; 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)
}
}
}
-3
View File
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode
go 1.26.0
@@ -1,262 +0,0 @@
// 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:<hex> (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:<hex> (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: <registry>/<module>/<artifact>@sha256:<digest>, or <name>@<version> 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:<hex>: 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:<hex> (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"`
}
@@ -1,66 +0,0 @@
// 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)
}
}
}
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry
go 1.26.0
@@ -1,486 +0,0 @@
// 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
import (
"bytes"
"encoding/json"
"errors"
"strings"
)
// 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"
const (
StateHolds = "holds"
StateWindows = "windows"
)
// HoldsBySeatNameDigestKey is the key of one value of the state holds under the form "<seat>.<name>.<digest>".
func HoldsBySeatNameDigestKey(seat, name, digest string) string {
return seat + "." + name + "." + digest
}
// ParseHoldsBySeatNameDigestKey reads the parts of a key of the form "<seat>.<name>.<digest>", in the order of the form; the last result is false for a key of another form.
func ParseHoldsBySeatNameDigestKey(key string) (string, string, string, bool) {
rest := key
v0, after0, found0 := strings.Cut(rest, ".")
if !found0 || v0 == "" {
return "", "", "", false
}
rest = after0
v2, after2, found2 := strings.Cut(rest, ".")
if !found2 || v2 == "" {
return "", "", "", false
}
rest = after2
if rest == "" {
return "", "", "", false
}
v4 := rest
rest = ""
if rest != "" {
return "", "", "", false
}
return v0, v2, v4, true
}
// DecodeHoldsBySeatNameDigestValue reads one value of the state holds under the form "<seat>.<name>.<digest>", refusing a field HoldsBySeatNameDigestValue does not know and any text after the value.
func DecodeHoldsBySeatNameDigestValue(raw []byte) (HoldsBySeatNameDigestValue, error) {
var v HoldsBySeatNameDigestValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// WindowsByCallerKey is the key of one value of the state windows under the form "<caller>".
func WindowsByCallerKey(caller string) string { return caller }
// ParseWindowsByCallerKey reads the parts of a key of the form "<caller>", in the order of the form; the last result is false for a key of another form.
func ParseWindowsByCallerKey(key string) (string, bool) {
rest := key
if rest == "" {
return "", false
}
v0 := rest
rest = ""
if rest != "" {
return "", false
}
return v0, true
}
// DecodeWindowsByCallerValue reads one value of the state windows under the form "<caller>", refusing a field WindowsByCallerValue does not know and any text after the value.
func DecodeWindowsByCallerValue(raw []byte) (WindowsByCallerValue, error) {
var v WindowsByCallerValue
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&v); err != nil {
return v, err
}
if dec.More() {
return v, errors.New("text after the value")
}
return v, nil
}
// 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:<hex> (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:<hex>
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:<hex> (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: <registry>/<module>/<artifact>@sha256:<digest>, or <name>@<version> 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:<hex>: 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:<hex> (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, <module>/<artifact>; 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
// HoldsBySeatNameDigestValue is one value of the state holds under the key <seat>.<name>.<digest>.
type HoldsBySeatNameDigestValue struct {
By string `json:"by"`
Digest string `json:"digest"`
Kind string `json:"kind"`
Name string `json:"name"`
Seat string `json:"seat"`
// when (RFC 3339 time)
Since string `json:"since"`
Why string `json:"why,omitempty"`
}
// WindowsByCallerValueDroppedItem is a type of the contract.
type WindowsByCallerValueDroppedItem struct {
// when (RFC 3339 time)
At string `json:"at,omitempty"`
By string `json:"by,omitempty"`
Node string `json:"node"`
Why string `json:"why,omitempty"`
}
// WindowsByCallerValue is one value of the state windows under the key <caller>.
type WindowsByCallerValue struct {
// when it last closed (RFC 3339 time)
Closed string `json:"closed,omitempty"`
// retired nodes dropped by hand
Dropped []WindowsByCallerValueDroppedItem `json:"dropped,omitempty"`
// the nodes holding it open
Holding []string `json:"holding,omitempty"`
LastHolding []string `json:"last_holding,omitempty"`
// the caller whose window it is
Name string `json:"name"`
Open bool `json:"open,omitempty"`
// node to when it began to hold
Since map[string]string `json:"since,omitempty"`
// of each node holding the window, why it holds it
Why map[string]string `json:"why,omitempty"`
}
@@ -1,82 +0,0 @@
// 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: "a hold", subject: "", given: "{\"by\":\"operator@shanks\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"kind\":\"base\",\"name\":\"base\",\"seat\":\"oci-artifact-registry\",\"since\":\"2026-11-05T10:00:00Z\"}", wire: "{\"by\":\"operator@shanks\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"kind\":\"base\",\"name\":\"base\",\"seat\":\"oci-artifact-registry\",\"since\":\"2026-11-05T10:00:00Z\"}",
decode: func(b []byte) (any, error) {
var v HoldsBySeatNameDigestValue
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: nil},
{name: "an open window", subject: "", given: "{\"holding\":[\"ace\"],\"name\":\"mesh-delivery-builder\",\"open\":true,\"since\":{\"ace\":\"2026-11-05T10:00:00Z\"}}", wire: "{\"holding\":[\"ace\"],\"name\":\"mesh-delivery-builder\",\"open\":true,\"since\":{\"ace\":\"2026-11-05T10:00:00Z\"}}",
decode: func(b []byte) (any, error) { var v WindowsByCallerValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{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)
}
}
}
@@ -1,3 +0,0 @@
module git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry
go 1.26.0
-321
View File
@@ -1,321 +0,0 @@
{
"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-meshartifactsweep",
"kind": "package",
"language": "go",
"paths": [
"go/seats/meshartifactsweep"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-meshcatalog",
"kind": "package",
"language": "go",
"paths": [
"go/seats/meshcatalog"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-meshdelivery",
"kind": "package",
"language": "go",
"paths": [
"go/seats/meshdelivery"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-meshdeliverychecker",
"kind": "package",
"language": "go",
"paths": [
"go/seats/meshdeliverychecker"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-nodebuildgo",
"kind": "package",
"language": "go",
"paths": [
"go/seats/nodebuildgo"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-nodebuildimage",
"kind": "package",
"language": "go",
"paths": [
"go/seats/nodebuildimage"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-nodebuildnode",
"kind": "package",
"language": "go",
"paths": [
"go/seats/nodebuildnode"
],
"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-meshcatalogclient",
"kind": "package",
"language": "go",
"paths": [
"go/clients/meshcatalogclient"
],
"series": "0.1"
},
{
"name": "go-meshdeliveryclient",
"kind": "package",
"language": "go",
"paths": [
"go/clients/meshdeliveryclient"
],
"series": "0.1"
},
{
"name": "go-meshdeliverycheckerclient",
"kind": "package",
"language": "go",
"paths": [
"go/clients/meshdeliverycheckerclient"
],
"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-meshartifactsweep",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/meshartifactsweep"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "ts-meshcatalog",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/meshcatalog"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "ts-meshdelivery",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/meshdelivery"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "ts-meshdeliverychecker",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/meshdeliverychecker"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "ts-nodebuildgo",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/nodebuildgo"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "ts-nodebuildimage",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/nodebuildimage"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "ts-nodebuildnode",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/nodebuildnode"
],
"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": "."
},
{
"name": "go-meshwants",
"kind": "package",
"language": "go",
"paths": [
"go/seats/meshwants"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "ts-meshwants",
"kind": "package",
"language": "typescript",
"paths": [
"ts/seats/meshwants"
],
"series": "0.1",
"generated-from": "."
},
{
"name": "go-meshwantsclient",
"kind": "package",
"language": "go",
"paths": [
"go/clients/meshwantsclient"
],
"series": "0.1"
}
]
},
"resources": []
}
-948
View File
@@ -1,948 +0,0 @@
{
"format": 1,
"part": "build-step",
"description": "What the three build seats share: the verbs that read and steer a node's builds, and the layouts of the asks they accept and the steps they keep. A seat lists its states against these (a part carries no states).",
"verbs": [
{
"name": "current",
"description": "The asks this node runs now (each ask's id, kind, module and when it started), its build capacity, and whether it is paused, taking no new ask.",
"input": {
"type": "object",
"properties": {}
},
"answer": {
"type": "object",
"description": "the steps running, the capacity and whether paused"
}
},
{
"name": "pause",
"description": "Take no new ask on this node until resumed; an ask running here ends in its place. Kept across a restart of the build module. A capacity of 0 is the lasting way to keep a node from building.",
"input": {
"type": "object",
"properties": {}
},
"answer": {
"type": "object",
"description": "whether the node is paused"
}
},
{
"name": "resume",
"description": "Take asks again on this node.",
"input": {
"type": "object",
"properties": {}
},
"answer": {
"type": "object",
"description": "whether the node is paused"
}
}
],
"defs": {
"StepAsk": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"id": {
"type": "string"
},
"kind": {
"type": "string",
"description": "build, check or compose"
},
"language": {
"type": "string",
"description": "go, typescript or image"
},
"asker": {
"type": "string"
},
"at": {
"type": "string",
"description": "when (RFC 3339 time)",
"format": "date-time"
},
"build": {
"type": "object",
"properties": {
"module": {
"type": "string"
},
"path": {
"type": "string"
},
"fp": {
"type": "string"
},
"fetch": {
"$ref": "#/defs/Fetch"
},
"artifacts": {
"type": "array",
"items": {
"$ref": "#/defs/AskArtifact"
}
},
"publish": {
"type": "boolean"
},
"toolchain": {
"$ref": "#/defs/ToolchainUsed"
}
},
"required": [
"module",
"artifacts"
]
},
"check": {
"type": "object",
"properties": {
"fetch": {
"$ref": "#/defs/Fetch"
},
"levels": {
"type": "array",
"items": {
"type": "string",
"description": "unit or integration"
}
},
"kept": {
"type": "boolean"
},
"runner": {
"type": "string"
},
"runner_when": {
"type": "string"
},
"run": {
"type": "string"
},
"packages": {
"type": "array",
"items": {
"type": "string"
}
},
"toolchain": {
"$ref": "#/defs/ToolchainUsed",
"description": "the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check"
},
"node-names": {
"type": "array",
"items": {
"type": "string"
},
"description": "the node names the check's tests run against"
}
},
"required": [
"fetch",
"levels"
]
},
"compose": {
"type": "object",
"properties": {
"fetch": {
"$ref": "#/defs/Fetch"
},
"dir": {
"type": "string"
},
"package": {
"type": "string"
},
"request": {
"description": "the compose-candidate request"
},
"reader": {
"type": "string"
}
},
"required": [
"fetch",
"dir",
"package",
"request"
]
},
"needs": {
"type": "array",
"items": {
"type": "string"
}
},
"packages": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "package to the tagged version a build pins"
}
},
"required": [
"id",
"kind",
"language",
"asker"
],
"description": "one ask of a build module"
},
"BuildAnswer": {
"type": "object",
"properties": {
"artifacts": {
"type": "array",
"items": {
"$ref": "#/defs/BuiltArtifact"
}
},
"toolchain": {
"$ref": "#/defs/ToolchainUsed"
},
"imports": {
"type": "array",
"items": {
"type": "string"
}
},
"out-of-step": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"artifacts"
]
},
"CheckAnswer": {
"type": "object",
"properties": {
"fetch": {
"$ref": "#/defs/FetchAnswer"
},
"levels": {
"type": "array",
"items": {
"$ref": "#/defs/LevelAnswer"
}
}
},
"required": [
"fetch"
]
},
"ComposeAnswer": {
"type": "object",
"properties": {
"built": {
"type": "boolean"
},
"key": {
"type": "string"
},
"verdict": {
"description": "the compose-candidate verdict"
}
},
"required": [
"built"
]
},
"Step": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"id": {
"type": "string"
},
"tool": {
"type": "string"
},
"node": {
"type": "string"
},
"state": {
"type": "string",
"description": "running, passed, failed or refused"
},
"started": {
"type": "string",
"description": "when (RFC 3339 time)",
"format": "date-time"
},
"ended": {
"type": "string",
"description": "when (RFC 3339 time)",
"format": "date-time"
},
"reason": {
"type": "string"
},
"result": {
"$ref": "#/defs/StepResult"
},
"what": {
"type": "string"
},
"phase": {
"type": "string"
},
"phase-at": {
"type": "string",
"description": "when (RFC 3339 time)",
"format": "date-time"
},
"owner": {
"type": "string"
}
},
"required": [
"id",
"tool",
"node",
"state",
"started"
],
"description": "one step a build module runs on a node"
},
"Toolchain": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"language": {
"type": "string",
"description": "go or typescript"
},
"version": {
"type": "string"
},
"recipe": {
"type": "string"
},
"at": {
"type": "string",
"description": "when (RFC 3339 time)",
"format": "date-time"
}
},
"required": [
"language",
"version",
"recipe"
],
"description": "the toolchain a node reports"
},
"Pause": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"paused": {
"type": "boolean"
},
"by": {
"type": "string"
},
"at": {
"type": "string",
"description": "when (RFC 3339 time)",
"format": "date-time"
}
},
"required": [
"paused"
],
"description": "whether the node takes no new ask"
},
"Capacity": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"effective": {
"type": "integer"
},
"configured": {
"type": "integer"
},
"configured-from": {
"type": "string"
},
"quota-cores": {
"type": "number"
},
"quota-from": {
"type": "string"
},
"cores-per-step": {
"type": "number"
},
"by-cpu": {
"type": "integer"
},
"build-hours": {
"type": "string"
},
"outside-hours": {
"type": "boolean"
},
"build-hours-fault": {
"type": "string"
},
"ineligible": {
"type": "string"
},
"why": {
"type": "string"
},
"at": {
"type": "string",
"description": "when (RFC 3339 time)",
"format": "date-time"
}
},
"required": [
"effective",
"configured",
"why"
],
"description": "how many steps the node runs at once"
},
"Fetch": {
"type": "object",
"description": "what a build module's fetch tool is asked: the repository and the full commit, the pull request's ref when only its head carries the commit, the trunk commit to merge onto for a merge preview, the module's directory and named paths, the mesh packages to build against, and the sibling repositories placed beside",
"properties": {
"repository": {
"type": "string",
"description": "<owner>/<name>"
},
"commit": {
"type": "string",
"description": "the full commit"
},
"ref": {
"type": "string",
"description": "the pull request's ref, when only its head carries the commit"
},
"base": {
"type": "string",
"description": "the trunk commit a merge preview merges onto"
},
"dir": {
"type": "string",
"description": "the module's directory in the repository (\".\" for its root), with paths"
},
"paths": {
"type": "array",
"items": {
"type": "string"
},
"description": "the paths the module names, relative to dir"
},
"packages": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "the version (a full commit) of each mesh package to build against, by its module path"
},
"package": {
"type": "object",
"description": "the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's",
"properties": {
"name": { "type": "string" },
"version": { "type": "string" },
"repository": { "type": "string" },
"commit": { "type": "string" },
"ref": { "type": "string" }
}
},
"beside": {
"type": "array",
"items": {
"type": "object",
"properties": {
"repository": {
"type": "string"
},
"commit": {
"type": "string"
}
},
"required": [
"repository",
"commit"
]
},
"description": "the sibling repositories placed beside the fetched tree"
}
},
"required": [
"repository",
"commit"
]
},
"ToolchainUsed": {
"type": "object",
"properties": {
"language": {
"type": "string"
},
"version": {
"type": "string"
},
"recipe": {
"type": "string"
}
},
"required": [
"language",
"version",
"recipe"
],
"description": "the toolchain a build was made with: the language, the installed version and the build module's recipe"
},
"AskArtifact": {
"type": "object",
"properties": {
"artifact": {
"type": "string"
},
"from": {
"type": "string"
},
"seat": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"artifact"
],
"description": "one artifact of a build ask: built from the fetched tree, or copied from an upstream pin"
},
"BuiltArtifact": {
"type": "object",
"properties": {
"artifact": {
"type": "string"
},
"digest": {
"type": "string"
},
"reference": {
"type": "string"
}
},
"required": [
"artifact",
"digest"
],
"description": "one artifact a build made: its digest and the reference it was published under"
},
"LevelAnswer": {
"type": "object",
"properties": {
"level": {
"type": "string"
},
"judged": {
"type": "string"
},
"digest": {
"type": "object",
"properties": {
"level": {
"type": "string"
},
"packages": {
"type": "array",
"items": {
"type": "object",
"properties": {
"package": {
"type": "string"
},
"digest": {
"type": "string"
}
},
"required": [
"package",
"digest"
]
}
},
"beside": {
"type": "array",
"items": {
"type": "string"
}
},
"without_tests": {
"type": "array",
"items": {
"type": "string"
}
},
"judged_by": {
"type": "string"
},
"needs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"package": {
"type": "string"
},
"needs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
},
"kept": {
"type": "array",
"items": {
"type": "object",
"properties": {
"package": {
"type": "string"
},
"digest": {
"type": "string"
},
"tests": {
"type": "integer"
}
}
}
},
"tests": {
"type": "array",
"items": {
"description": "one test's result"
}
},
"unmet": {
"type": "array",
"items": {
"type": "object",
"properties": {
"package": {
"type": "string"
},
"needs": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
},
"required": [
"level"
],
"description": "one level of a check ask: the digest's answer, the kept verdicts taken, one test result per test step run, and the packages the node could not test"
},
"StepResult": {
"type": "object",
"description": "what a step answered, by the kind of ask: a build's artifacts, toolchain, imports and out-of-step modules; a check's fetch and levels; a compose's built, key and verdict; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the four",
"properties": {
"artifacts": {
"type": "array",
"items": {
"$ref": "#/defs/BuiltArtifact"
}
},
"toolchain": {
"$ref": "#/defs/ToolchainUsed"
},
"imports": {
"type": "array",
"items": {
"type": "string"
}
},
"out-of-step": {
"type": "array",
"items": {
"type": "string"
}
},
"fetch": {
"$ref": "#/defs/FetchAnswer"
},
"levels": {
"type": "array",
"items": {
"$ref": "#/defs/LevelAnswer"
}
},
"built": {
"type": "boolean"
},
"key": {
"type": "string"
},
"verdict": {
"description": "the compose-candidate verdict (the composer's own layout)"
},
"declined": {
"type": "boolean",
"description": "the holder declined the ask: its node lacks what the ask needs"
},
"needs": {
"type": "array",
"items": {
"type": "string"
},
"description": "what the declined ask needs"
},
"lacks": {
"type": "array",
"items": {
"type": "string"
},
"description": "what the holder's node lacks of it"
}
}
},
"ModuleTree": {
"type": "object",
"description": "the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths",
"properties": {
"tree": {
"type": "string"
},
"manifest": {
"type": "string"
},
"locks": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"paths": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"packages": {
"type": "array",
"items": {
"type": "string"
},
"description": "no longer filled (ADR 0593 section 2); kept so a preview written before still reads"
}
},
"required": [
"tree",
"manifest"
]
},
"Program": {
"type": "object",
"description": "a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it",
"properties": {
"path": {
"type": "string"
},
"kind": {
"type": "string",
"description": "ELF, Mach-O or PE"
},
"commit": {
"type": "string"
}
},
"required": [
"path",
"kind"
]
},
"PlacedBeside": {
"type": "object",
"description": "a sibling repository placed as a plain directory under the workspace's beside directory",
"properties": {
"repository": {
"type": "string"
},
"commit": {
"type": "string"
},
"dir": {
"type": "string"
}
},
"required": [
"repository",
"commit",
"dir"
]
},
"TakenVersion": {
"type": "object",
"description": "of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said",
"properties": {
"dir": {
"type": "string"
},
"package": {
"type": "string"
},
"asked": {
"type": "string"
},
"named": {
"type": "string"
},
"built": {
"type": "string"
},
"sum": {
"type": "string"
},
"said": {
"type": "string"
}
},
"required": [
"dir",
"package",
"asked",
"named",
"built"
]
},
"FetchRefusal": {
"type": "object",
"description": "a TypeScript module whose packages the fetch did not install, with why",
"properties": {
"module": {
"type": "string"
},
"reason": {
"type": "string"
}
},
"required": [
"module",
"reason"
]
},
"FetchAnswer": {
"type": "object",
"description": "what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package",
"properties": {
"workspace": {
"type": "string"
},
"commit": {
"type": "string"
},
"modules": {
"type": "array",
"items": {
"type": "string"
}
},
"base": {
"type": "string"
},
"merge": {
"type": "string"
},
"conflicts": {
"type": "array",
"items": {
"type": "string"
}
},
"changed": {
"type": "array",
"items": {
"type": "string"
}
},
"programs": {
"type": "array",
"items": {
"$ref": "#/defs/Program"
}
},
"trees": {
"type": "object",
"additionalProperties": {
"$ref": "#/defs/ModuleTree"
}
},
"beside": {
"type": "array",
"items": {
"$ref": "#/defs/PlacedBeside"
}
},
"packages": {
"type": "array",
"items": {
"$ref": "#/defs/TakenVersion"
}
},
"installed": {
"type": "array",
"items": {
"type": "string"
}
},
"refused": {
"type": "array",
"items": {
"$ref": "#/defs/FetchRefusal"
}
},
"group": {
"type": "string"
}
},
"required": [
"workspace",
"commit",
"modules"
]
}
}
}
-436
View File
@@ -1,436 +0,0 @@
{
"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:<hex> (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:<hex> (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:<hex>: 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:<hex> (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: <registry>/<module>/<artifact>@sha256:<digest>, or <name>@<version> 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"
}
}
}
@@ -1,98 +0,0 @@
[
{
"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"
}
}
]
@@ -1,28 +0,0 @@
[
{
"name": "a hold",
"of": "state:holds/<seat>.<name>.<digest>",
"given": {
"seat": "container-image-registry",
"name": "base",
"kind": "base",
"digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"by": "operator@shanks",
"since": "2026-11-05T10:00:00Z"
}
},
{
"name": "an open window",
"of": "state:windows/<caller>",
"given": {
"name": "mesh-delivery-builder",
"open": true,
"holding": [
"ace"
],
"since": {
"ace": "2026-11-05T10:00:00Z"
}
}
}
]
@@ -1,149 +0,0 @@
[
{
"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
}
]
}
]
}
}
]
@@ -1,480 +0,0 @@
{
"format": 1,
"seat": "container-image-registry",
"scope": "mesh",
"grain": "whole",
"domain": "Delivery",
"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:<hex>"
},
"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, <module>/<artifact>; 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:<hex>"
},
"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"
]
}
},
"states": [
{
"name": "holds",
"template": "state",
"history": 1,
"description": "the bundles, archives and copied bases kept from retention's removal",
"key-forms": [
{
"key": "<seat>.<name>.<digest>",
"value": {
"type": "object",
"description": "one hold: an untagged bundle, archive or copied base kept from retention's removal",
"properties": {
"seat": {
"type": "string"
},
"name": {
"type": "string"
},
"kind": {
"type": "string"
},
"digest": {
"type": "string"
},
"by": {
"type": "string"
},
"why": {
"type": "string"
},
"since": {
"type": "string",
"description": "when (RFC 3339 time)"
}
},
"required": [
"seat",
"name",
"kind",
"digest",
"by",
"since"
]
},
"description": "one hold"
}
]
},
{
"name": "windows",
"template": "state",
"history": 1,
"description": "the direct-write windows, one per caller",
"key-forms": [
{
"key": "<caller>",
"value": {
"type": "object",
"description": "the direct-write window of one caller: open while a node holds it open",
"properties": {
"name": {
"type": "string",
"description": "the caller whose window it is"
},
"open": {
"type": "boolean"
},
"holding": {
"type": "array",
"items": {
"type": "string"
},
"description": "the nodes holding it open"
},
"since": {
"type": "object",
"additionalProperties": {
"type": "string",
"description": "when (RFC 3339 time)"
},
"description": "node to when it began to hold"
},
"closed": {
"type": "string",
"description": "when it last closed (RFC 3339 time)"
},
"last_holding": {
"type": "array",
"items": {
"type": "string"
}
},
"dropped": {
"type": "array",
"items": {
"type": "object",
"properties": {
"node": {
"type": "string"
},
"by": {
"type": "string"
},
"why": {
"type": "string"
},
"at": {
"type": "string",
"description": "when (RFC 3339 time)"
}
},
"required": [
"node"
]
},
"description": "retired nodes dropped by hand"
},
"why": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "of each node holding the window, why it holds it"
}
},
"required": [
"name"
]
},
"description": "one caller's window"
}
]
}
]
}
-9
View File
@@ -1,9 +0,0 @@
{
"format": 1,
"seat": "git",
"scope": "mesh",
"grain": "seat-list",
"domain": "Delivery",
"decision": "novox/hq ADR 0111",
"delivers": "git"
}
@@ -1,73 +0,0 @@
[
{
"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"
]
}
}
]
@@ -1,110 +0,0 @@
[
{
"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"
]
}
}
]
@@ -1,35 +0,0 @@
{
"format": 1,
"seat": "go-package-registry",
"scope": "mesh",
"grain": "whole",
"domain": "Delivery",
"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)"
}
}
]
}
@@ -1,8 +0,0 @@
{
"format": 1,
"seat": "mesh-artifact-store",
"scope": "mesh",
"grain": "seat-list",
"domain": "Delivery",
"decision": "novox/hq ADR 0075"
}
@@ -1,15 +0,0 @@
[
{
"name": "artifacts-gone on the seat's subject",
"of": "event:artifacts-gone",
"subject": "mesh.seat.mesh-artifact-sweep.event.artifacts-gone",
"given": {
"event": "artifacts-gone",
"module": "ca-trust",
"key": "ca-trust.archive.f1",
"fingerprint": "f1",
"gone": ["artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"],
"at": "2026-11-05T15:04:05Z"
}
}
]
@@ -1 +0,0 @@
[]
@@ -1,24 +0,0 @@
[
{
"name": "held asks the references",
"of": "verb:held.input",
"why": "the references are the holder's own test values (the tool refuses a call without them through the console)",
"given": {"references": ["artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"]}
},
{
"name": "held answers held, gone and unknown",
"of": "verb:held.answer",
"why": "the answer of the holder's test (cmd/artifact-store-tools): every reference in one of the three",
"given": {
"store": "store.test:5100",
"held": ["artifact-store://ca-trust/archive/blobs/sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
"gone": ["artifact-store://ca-trust/archive/blobs/sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"],
"unknown": {"artifact-store://ca-trust/archive/blobs/sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc": "the store answered 503"}
}
},
{
"name": "held answers with nothing unknown",
"of": "verb:held.answer",
"given": {"store": "store.test:5100", "held": [], "gone": []}
}
]
@@ -1,204 +0,0 @@
{
"format": 1,
"seat": "mesh-artifact-sweep",
"scope": "mesh",
"grain": "whole",
"domain": "Delivery",
"decision": "novox/hq ADR 0571",
"verbs": [
{
"name": "artifacts",
"description": "Every artifact the mesh made that lives in its artifact store, each with its repository, digest, kind (image or archive) and state: kept, and why (a want names it, or one of the five newest builds of its module in the builder's log), eligible (made, kept for no reason, not yet let go), or unrecorded (in the store and named by nothing the mesh made: never removed). Reads only.",
"input": {
"properties": {
"collected": {
"description": "\"true\": list the collected references too (counted always)",
"enum": [
"true",
"false"
],
"type": "string"
},
"repository": {
"description": "only this repository's references, as \u003cmodule\u003e/\u003cartifact\u003e (the counts stay whole)",
"type": "string"
}
},
"type": "object"
},
"answer": {
"type": "object",
"description": "what artifacts answers, as its description says"
}
},
{
"name": "held",
"description": "Whether the artifact store holds each reference given, asked with one HEAD each: held, gone (the store answered 404) or unknown with why. The builder asks it before it reuses a done key. Reads only.",
"input": {
"properties": {
"references": {
"description": "the artifacts' references, artifact-store://\u003cmodule\u003e/\u003cartifact\u003e/blobs/sha256:\u003chex\u003e: a list, or one text separated by commas",
"items": {
"type": "string"
},
"type": "array"
}
},
"required": [
"references"
],
"type": "object"
},
"answer": {
"type": "object",
"properties": {
"store": {
"type": "string",
"description": "the store asked, host and port"
},
"held": {
"type": "array",
"items": {
"type": "string"
},
"description": "the references the store holds"
},
"gone": {
"type": "array",
"items": {
"type": "string"
},
"description": "the references the store answered 404 for"
},
"unknown": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "a reference the store could not be asked about, to why"
}
},
"required": [
"held",
"gone"
],
"description": "what held answers: a reference is in held, gone or unknown, never none of them"
}
},
{
"name": "collect",
"description": "The artifact store's sweep, now: what is eligible is let go of. Without confirm a dry run: what would be let go, nothing held or deleted. With confirm and why: every kept archive held first, then each eligible artifact let go of, oldest first, an index with the platform manifests no kept index names, each deletion recorded in the hand-act log. Bounded by most (500 by default, at most 5000) and 45 seconds; what is left is said. Never touches a digest the mesh did not make.",
"input": {
"properties": {
"confirm": {
"description": "\"true\": let go of what is eligible (needs why); without it nothing is done",
"enum": [
"true",
"false"
],
"type": "string"
},
"most": {
"description": "let go of at most this many (500 by default, at most 5000)",
"type": "string"
},
"why": {
"description": "with confirm: why, required and recorded",
"type": "string"
}
},
"type": "object"
},
"answer": {
"type": "object",
"description": "what collect answers, as its description says"
}
},
{
"name": "mirrors",
"description": "The bases the mesh copied into its artifact store, each kept (a kept build stood on it), eligible (only builds no longer kept stood on it) or unrecorded (no build the builder's log holds stood on it: never removed), by what the builder's log says each build stood on. Records nothing by hand.",
"input": {
"properties": {},
"type": "object"
},
"answer": {
"type": "object",
"description": "what mirrors answers, as its description says"
}
},
{
"name": "images",
"description": "Every container image one machine's want and have name, each with the resources naming it: what the machine keeps when it prunes its images. Reads only.",
"input": {
"properties": {
"node": {
"description": "the machine's name",
"type": "string"
}
},
"required": [
"node"
],
"type": "object"
},
"answer": {
"type": "object",
"description": "what images answers, as its description says"
}
}
],
"family": {
"name": "mesh-artifact-sweep.events",
"max-age-days": 7
},
"events": [
{
"name": "artifacts-gone",
"body": {
"$ref": "#/defs/ArtifactsGone"
}
}
],
"defs": {
"ArtifactsGone": {
"type": "object",
"properties": {
"event": {
"type": "string",
"description": "artifacts-gone"
},
"module": {
"type": "string",
"description": "the module whose builder's key names a gone artifact"
},
"key": {
"type": "string",
"description": "the builder's done key"
},
"fingerprint": {
"type": "string",
"description": "the key's fingerprint"
},
"gone": {
"type": "array",
"items": {
"type": "string"
},
"description": "every reference of the key the store does not hold, sorted"
},
"at": {
"type": "string",
"format": "date-time",
"description": "when the sweep found it"
}
},
"required": [
"event",
"module",
"fingerprint",
"gone"
],
"description": "the body of artifacts-gone"
}
}
}
@@ -1,223 +0,0 @@
[
{
"name": "a manifest (xclip, read through mesh-catalog.module)",
"of": "state:manifests/<digest>",
"given": {
"v": 1,
"module": "xclip",
"manifest": "{\"build\":{\"artifacts\":[{\"binary\":\"xclip-tools\",\"from\":\"cmd/xclip-tools\",\"kind\":\"bundle\",\"language\":\"go\",\"loads\":[\"xclip-tools\"],\"name\":\"tools\",\"system\":\"arch\"}]},\"capabilities\":[\"package-manager\"],\"module\":\"xclip\",\"resources\":[{\"id\":\"package\",\"package\":\"xclip\",\"type\":\"package\"}],\"tools\":[\"xclip_copy\",\"xclip_paste\",\"xclip_targets\",\"xclip_session\"],\"version\":\"1\"}"
}
},
{
"name": "a module (xclip, read through mesh-catalog.modules; the inputs and toolchains no tool reads are not here)",
"of": "state:modules/<module>",
"given": {
"v": 1,
"repository": "novox/mesh-catalog",
"commit": "f469eb387a3015569d9b16c66ed418e9dd098f64",
"path": "modules/xclip",
"fp2": "src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56"
}
},
{
"name": "a candidate",
"of": "state:candidates/<group>",
"given": {
"v": 1,
"group": "feat/registry",
"base": 3,
"heads": "0123456789ab",
"pulls": [
{
"repository": "novox/mesh-sdk",
"number": 1,
"head": "cccccccccccccccccccccccccccccccccccccccc",
"modules": [
"nats"
]
}
]
}
},
{
"name": "a refused verdict (nats, read through the indexer's verdicts tool)",
"of": "state:verdicts/<digest>",
"given": {
"module": "nats",
"state": "refused",
"problems": [
"this is not a module manifest: a state is either a name or {name, history, ttl-seconds, per-machine, key-ttl}: json: unknown field \"read-only\" (read without state[2].read-only)"
],
"judge": "exe:661e6fb424d7ffea",
"at": "2026-11-04T18:51:12.614583047+01:00"
}
},
{
"name": "the predefined list",
"of": "state:predefined/list",
"given": {
"v": 1,
"commit": "cccccccccccccccccccccccccccccccccccccccc",
"list": {
"streams": []
}
}
},
{
"name": "a pull request's configuration",
"of": "state:config-candidates/<pull>",
"given": {
"v": 1,
"pull": "novox/mesh-catalog#1",
"head": "cccccccccccccccccccccccccccccccccccccccc",
"declares": {
"nats": {
"limit": {
"kind": "setting",
"default": 1,
"why": "a bound"
}
}
}
}
},
{
"name": "a pull request's configuration state whose bounds are declared 0 (a zero is a bound, not none)",
"of": "state:config-candidates/<pull>",
"given": {
"v": 1,
"pull": "novox/mesh-catalog#1",
"head": "cccccccccccccccccccccccccccccccccccccccc",
"declares": {
"nats": {
"limit": {
"kind": "setting",
"default": 0,
"why": "a bound",
"minimum": 0,
"maximum": 0
}
}
}
}
},
{
"name": "a published Go module",
"of": "state:packages/<module>",
"given": {
"v": 1,
"module": "git.novox.be/novox/mesh-sdk/go",
"repository": "novox/mesh-sdk",
"commit": "cccccccccccccccccccccccccccccccccccccccc",
"packages": [
{
"package": "git.novox.be/novox/mesh-sdk/go/keys",
"digest": "ab"
}
]
}
},
{
"name": "a package series",
"of": "state:packages/<package>.<series>",
"given": {
"v": 1,
"name": "go-keys",
"series": "0.1"
}
},
{
"name": "a module with its toolchain",
"of": "state:modules/<module>",
"given": {
"v": 1,
"repository": "novox/mesh-catalog",
"commit": "f469eb387a3015569d9b16c66ed418e9dd098f64",
"fp2": "src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56",
"toolchains": {
"go": {
"language": "go",
"version": "1.26.0",
"recipe": "1"
}
}
}
},
{
"name": "the modules verb (captured: the first three modules)",
"of": "verb:modules.answer",
"given": {
"modules": [
{
"commit": "f469eb387a3015569d9b16c66ed418e9dd098f64",
"fp": "src2:5e4a1b4ad4ea6175c05e4655ce58aeb957547149d4c9805eb27636efd50c1c87",
"module": "adwaita",
"path": "modules/adwaita",
"repository": "novox/mesh-catalog"
},
{
"commit": "b112adcecc41d18f54190cbd2fc864199b70c518",
"fp": "src2:7dd0db8e98224f46121a5ae05c0b9c5a430eff531a5120aa40b1a59237853a3f",
"module": "amqp-email-forwarder",
"path": ".",
"repository": "jschoubben/amqp-email-forwarder"
},
{
"commit": "dd458cb155f5f5fa3ed824a23728e444be5b156a",
"fp": "src2:a98f8152f0a837dd5ba883a15efaf110ba88100193157e4bdb1a93ba55209371",
"module": "artifact-store-tools",
"path": "modules/artifact-store-tools",
"repository": "novox/mesh-catalog"
}
]
}
},
{
"name": "the module verb (captured: xclip)",
"of": "verb:module.answer",
"given": {
"module": {
"commit": "f469eb387a3015569d9b16c66ed418e9dd098f64",
"fp": "src2:b427bdde1f100e2f52490cfa9df0c5112dd3700226b4892349f0c14a15a4af56",
"manifest": {
"build": {
"artifacts": [
{
"binary": "xclip-tools",
"from": "cmd/xclip-tools",
"kind": "bundle",
"language": "go",
"loads": [
"xclip-tools"
],
"name": "tools",
"system": "arch"
}
]
},
"capabilities": [
"package-manager"
],
"module": "xclip",
"resources": [
{
"id": "package",
"package": "xclip",
"type": "package"
}
],
"tools": [
"xclip_copy",
"xclip_paste",
"xclip_targets",
"xclip_session"
],
"version": "1"
},
"module": "xclip",
"path": "modules/xclip",
"repository": "novox/mesh-catalog"
}
}
}
]
File diff suppressed because it is too large Load Diff
@@ -1,44 +0,0 @@
[
{
"name": "verdict on the checker's own family",
"of": "holder-event:verdict",
"subject": "mesh.stream.mesh-delivery-checker.event.verdict",
"given": {
"repository": "novox/mesh-contracts",
"pull": 3,
"head": "cccccccccccccccccccccccccccccccccccccccc",
"verdict": "fail",
"out-of-step": [
"go/seats/meshbroker/contract.go"
],
"series": [
{
"module": "mesh-contracts",
"artifact": "go",
"from": "0.1",
"to": "0.2"
}
]
}
},
{
"name": "tag-wanted on the checker's own family",
"of": "holder-event:tag-wanted",
"subject": "mesh.stream.mesh-delivery-checker.event.tag-wanted",
"given": {
"repository": "novox/mesh-sdk",
"pull": 170,
"head": "cccccccccccccccccccccccccccccccccccccccc",
"branch": "feat/registry",
"module": "mesh-sdk",
"package": "go",
"tag": "registry-1966"
}
},
{
"name": "preview.written on the checker's stream",
"of": "holder-event:preview.written",
"subject": "mesh.stream.mesh-delivery-checker.event.preview.written",
"given": {}
}
]
@@ -1,48 +0,0 @@
[
{
"name": "a running verdict",
"of": "state:verdicts/<group>.<heads>",
"given": {
"v": 1,
"group": "feat/registry",
"heads": "0123456789ab",
"state": "running"
}
},
{
"name": "a kept pass",
"of": "state:kept/<level>.<digest>",
"given": {
"v": 1,
"state": "pass",
"repository": "novox/mesh-sdk",
"package": "go/keys",
"level": "unit",
"tests": 4,
"took": 120,
"toolchain": "go1.26",
"at": "2026-11-05T10:00:00Z"
}
},
{
"name": "the status owed for a head",
"of": "state:owed/<repository>.<number>.<head>.<act>",
"given": {
"v": 1,
"act": "status=mesh/check",
"context": "mesh/check",
"state": "success"
}
},
{
"name": "a preview",
"of": "state:previews/<repository>.<head>.<trunk>",
"given": {
"v": 1,
"repository": "novox/mesh-sdk",
"head": "cccccccccccccccccccccccccccccccccccccccc",
"trunk": "dddddddddddddddddddddddddddddddddddddddd",
"merge": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
}
}
]
@@ -1,657 +0,0 @@
{
"format": 1,
"seat": "mesh-delivery-checker",
"scope": "mesh",
"grain": "whole",
"domain": "Delivery",
"decision": "novox/hq ADR 0547",
"verbs": [
{
"name": "try",
"description": "Try pushed heads before their pull requests are opened: the checker judges them as it judges a delivery group's pull requests, each member merged onto its trunk, its unit and integration levels through the build modules, then the members' manifests, the module graph and the composition on every placed node, and owes the forge nothing. Name the group's members (each a repository and the exact commit), or one repository and its head for a try of one member. Answers the try's id at once; try-read reads where it stands. The same members at the same commits, in any order, are the answer to a second ask while that try waits or runs; at most 4 run on a build node, and 8 wait for one, mesh-wide.",
"optional": true,
"replaces": [
"sh merge-check.sh",
"docker run postgres"
],
"input": {
"properties": {
"head": {
"description": "the full commit to try, 40 or 64 hex digits, for a try of one member (instead of members)",
"type": "string"
},
"level": {
"description": "unit or integration: only this level; both when absent",
"type": "string"
},
"repository": {
"description": "<owner>/<name>, for a try of one member (instead of members)",
"type": "string"
},
"tests": {
"description": "a go test -run pattern: only those tests run, and nothing is kept",
"type": "string"
},
"trunk": {
"description": "the trunk commit to merge it onto, or the trunk's branch for its head now; the trunk's head when absent",
"type": "string"
},
"members": {
"description": "the group: one repository at most once, each at the exact commit asked and the trunk commit it is merged onto; instead of repository and head",
"type": "array",
"items": {
"type": "object",
"properties": {
"repository": {
"description": "<owner>/<name>",
"type": "string"
},
"head": {
"description": "the full commit to try, 40 or 64 hex digits",
"type": "string"
},
"trunk": {
"description": "the trunk commit to merge it onto, or the trunk's branch for its head now; the trunk's head when absent",
"type": "string"
}
},
"required": [
"repository",
"head"
]
}
}
},
"type": "object"
},
"answer": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "the try's id"
},
"state": {
"type": "string",
"description": "its state"
}
},
"required": [
"id"
]
}
},
{
"name": "try-read",
"description": "Where a try stands, as stored: queued, running, or its end (pass, fail, or error when it could not run), with what was said of it, the head and the trunk commit it was merged onto, the time it took and what failed first. Read-only.",
"optional": true,
"input": {
"properties": {
"id": {
"description": "the try's id, try-<digits>",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"answer": {
"type": "object",
"description": "the try: its state, its steps and its verdict when it has one"
}
},
{
"name": "try-end",
"description": "End a try that waits or runs, as error, by hand: say why (kept in what it says). Its place among the tries is free at once and the next try of its head asks anew with a new id. A try that ended already is refused.",
"optional": true,
"input": {
"properties": {
"id": {
"description": "the try's id, try-<digits>",
"type": "string"
},
"why": {
"description": "why it is ended, in words",
"type": "string"
}
},
"required": [
"id",
"why"
],
"type": "object"
},
"answer": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "the try"
},
"ended": {
"type": "boolean",
"description": "it is ended"
}
},
"required": [
"id",
"ended"
]
}
}
],
"description": "The merge check's tries: start one, read one, end one. The checker's own events stay under its own subjects (ADR 0620 §2).",
"holder-events": [
{
"name": "verdict",
"subject": "mesh.stream.<holder>.event.verdict",
"family": "checker.asks",
"body": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"description": "owner/repo"
},
"pull": {
"type": "integer",
"description": "the pull request's number, for a pull request's check"
},
"head": {
"type": "string",
"description": "the commit checked"
},
"verdict": {
"type": "string",
"description": "pass or fail"
},
"out-of-step": {
"type": "array",
"items": {
"type": "string"
},
"description": "the generated files out of step with their seat definitions, each path (design 26)"
},
"series": {
"type": "array",
"items": {
"type": "object",
"properties": {
"module": {
"type": "string",
"description": "the module whose artifact's series changes"
},
"artifact": {
"type": "string",
"description": "its artifact, or the package's ecosystem name as the module's packages name it"
},
"from": {
"type": "string",
"description": "the series on trunk"
},
"to": {
"type": "string",
"description": "the series the checked head gives it"
}
},
"required": [
"module",
"artifact",
"from",
"to"
]
},
"description": "each change of an artifact's series the checked head makes against trunk's manifests, once each (design 32); absent when it changes none"
}
},
"required": [
"repository",
"head",
"verdict"
]
}
},
{
"name": "tag-wanted",
"subject": "mesh.stream.<holder>.event.tag-wanted",
"family": "checker.asks",
"body": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"description": "owner/repo"
},
"pull": {
"type": "integer",
"description": "the pull request"
},
"head": {
"type": "string",
"description": "its head"
},
"branch": {
"type": "string",
"description": "the pull request's branch: its tag is removed when the branch ends"
},
"module": {
"type": "string",
"description": "the group member"
},
"package": {
"type": "string",
"description": "the package it declares"
},
"tag": {
"type": "string",
"description": "the tag wanted"
}
},
"required": [
"repository",
"head",
"branch",
"module",
"package"
]
}
},
{
"name": "preview.written",
"subject": "mesh.stream.<holder>.event.preview.written",
"family": "checks"
}
],
"states": [
{
"name": "verdicts",
"template": "state",
"key-forms": [
{
"key": "<group>.<heads>",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"group": {
"type": "string"
},
"heads": {
"type": "string",
"description": "12 hex digits"
},
"trunk": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "repository to the trunk head merged onto"
},
"state": {
"type": "string",
"description": "running, pass or fail"
},
"failed": {
"type": "object",
"properties": {
"test": {
"type": "string"
},
"machine": {
"type": "string"
},
"words": {
"type": "string"
}
}
},
"failures": {
"type": "array",
"items": {
"type": "object",
"properties": {
"test": {
"type": "string"
},
"machine": {
"type": "string"
},
"words": {
"type": "string"
}
}
}
},
"levels": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"run": {
"type": "integer"
},
"kept": {
"type": "integer"
},
"skipped": {
"type": "integer"
}
},
"required": [
"run",
"kept"
]
},
"description": "by level, unit or integration"
},
"took": {
"type": "object",
"properties": {
"queue": {
"type": "integer"
},
"clone": {
"type": "integer"
},
"unit": {
"type": "integer"
},
"lint": {
"type": "integer"
},
"integration-setup": {
"type": "integer"
},
"integration-run": {
"type": "integer"
},
"composition": {
"type": "integer"
}
},
"description": "milliseconds"
},
"machines": {
"type": "array",
"items": {
"type": "string"
}
},
"sdk": {
"type": "string"
},
"composition": {
"type": "object",
"properties": {
"state": {
"type": "string",
"description": "not-run, pass or fail"
},
"words": {
"type": "string"
},
"sdk": {
"type": "string"
},
"machines": {
"type": "array",
"items": {
"type": "string"
}
},
"fpset": {
"type": "string"
}
},
"required": [
"state"
]
},
"over-bound": {
"type": "array",
"items": {
"type": "string"
}
},
"beside": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"carried-from": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"series": {
"type": "array",
"items": {
"type": "object",
"properties": {
"module": {
"type": "string"
},
"artifact": {
"type": "string"
},
"from": {
"type": "string"
},
"to": {
"type": "string"
}
},
"required": [
"module",
"artifact",
"from",
"to"
]
}
}
},
"required": [
"group",
"heads",
"state"
],
"description": "one check of a group at one set of heads"
},
"description": "one check of a group; the group is an escaped branch name, heads 12 hex digits"
}
],
"description": "the checker's verdict of each group at each set of heads",
"history": 5
},
{
"name": "kept",
"template": "state",
"key-forms": [
{
"key": "<level>.<digest>",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"state": {
"type": "string",
"description": "pass: a failure is never kept"
},
"repository": {
"type": "string"
},
"package": {
"type": "string"
},
"level": {
"type": "string"
},
"tests": {
"type": "integer"
},
"took": {
"type": "integer"
},
"toolchain": {
"type": "string"
},
"at": {
"type": "string",
"description": "when (RFC 3339 time)"
}
},
"required": [
"state",
"repository",
"package",
"level"
],
"description": "one passing level of one package, kept so the next check does not run it again"
},
"description": "a passing level of one package by its content digest"
}
],
"description": "the kept passing levels",
"history": 1
},
{
"name": "owed",
"template": "state",
"key-forms": [
{
"key": "<repository>.<number>.<head>.<act>",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"act": {
"type": "string",
"description": "status=<context>, view, note or comment.<seq>"
},
"context": {
"type": "string"
},
"target": {
"type": "string"
},
"state": {
"type": "string"
},
"text": {
"type": "string"
},
"ref": {
"type": "string"
}
},
"required": [
"act"
],
"description": "one act owed to the forge, written by its owner and done by the forge bridge"
},
"description": "the status mesh/check the checker owes the forge for a head (its act is status=mesh/check)"
}
],
"description": "the status the checker owes the forge",
"history": 5
},
{
"name": "previews",
"template": "state",
"key-forms": [
{
"key": "<repository>.<head>.<trunk>",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"repository": {
"type": "string"
},
"head": {
"type": "string"
},
"trunk": {
"type": "string"
},
"merge": {
"type": "string",
"description": "the merge commit"
},
"conflicts": {
"type": "array",
"items": {
"type": "string"
},
"description": "files in conflict"
},
"modules": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"tree": {
"type": "string"
},
"manifest": {
"type": "string",
"description": "manifest digest"
},
"locks": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"paths": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"packages": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"tree",
"manifest"
]
},
"description": "by directory"
}
},
"required": [
"repository",
"head",
"trunk"
],
"description": "the merge of a head onto a trunk commit, read before a pull request is checked"
},
"description": "the merge of a head onto a trunk commit"
}
],
"description": "the previews of a head merged onto a trunk",
"history": 5
}
]
}
@@ -1,72 +0,0 @@
[
{
"name": "the source revision",
"of": "state:source/main",
"given": {
"v": 1,
"group": "feat/registry",
"heads": {
"novox/mesh-sdk": "cccccccccccccccccccccccccccccccccccccccc"
}
}
},
{
"name": "a merge",
"of": "state:merges/<group>",
"given": {
"v": 1,
"group": "feat/registry",
"ask": "merge.feat=registry.0123456789ab",
"pulls": [
{
"repository": "novox/mesh-sdk",
"number": 1,
"head": "cccccccccccccccccccccccccccccccccccccccc",
"state": "merging"
}
]
}
},
{
"name": "an ask",
"of": "state:asks/merge.<group>.<heads>",
"given": {
"v": 1,
"group": "feat/registry",
"heads": [
{
"repository": "novox/mesh-sdk",
"number": 1,
"head": "cccccccccccccccccccccccccccccccccccccccc"
}
],
"ask": {
"headline": "merge"
}
}
},
{
"name": "an act owed",
"of": "state:owed/<repository>.<number>.<head>.<act>",
"given": {
"v": 1,
"act": "view"
}
},
{
"name": "a turn",
"of": "state:turns/<repository>",
"given": {
"v": 1,
"repository": "novox/mesh-sdk",
"waiting": [
{
"group": "feat/registry",
"heads": "0123456789ab",
"approved": "2026-11-05T10:00:00Z",
"since": "2026-11-05T10:01:00Z"
}
]
}
}
]
-574
View File
@@ -1,574 +0,0 @@
{
"format": 1,
"seat": "mesh-delivery",
"scope": "mesh",
"grain": "whole",
"domain": "Delivery",
"decision": "novox/hq ADR 0239",
"verbs": [
{
"name": "deliveries",
"description": "Every delivery not final, and those that ended in the last day, one line each: its id (owner/repository@commit), its state, its group, what it waits for and since when. Narrowed by state, repository or group.",
"input": {
"properties": {
"all": {
"description": "\"true\": the final ones of the last thirty days too",
"enum": [
"true",
"false"
],
"type": "string"
},
"group": {
"description": "a group's id (its branch name)",
"type": "string"
},
"repository": {
"description": "owner/repository",
"type": "string"
},
"state": {
"description": "one state, e.g. delivering or held",
"type": "string"
}
},
"type": "object"
},
"answer": {
"type": "object",
"description": "what deliveries answers, as its description says"
}
},
{
"name": "times",
"description": "Removed in R6 (ADR 0539): refused by name, naming its replacement: the delivery view (nox-delivery): each machine's want and have, and since when",
"optional": true,
"input": {
"properties": {},
"type": "object"
},
"answer": {
"type": "object",
"description": "what times answers, as its description says"
}
},
{
"name": "show",
"description": "One delivery or group whole: its delivery plan (what it builds, what each machine receives, what is not an ordinary send), every transition with when and why, the machine steps of its walk, its group and its order.",
"input": {
"properties": {
"id": {
"description": "a delivery's id, or a group's",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"answer": {
"type": "object",
"description": "what show answers, as its description says"
}
},
{
"name": "groups",
"description": "Every delivery group: its members in order, why each pair is ordered (declared, built by, version skew, engine before controller, by name), its composed check and its state, derived from its members.",
"input": {
"properties": {
"all": {
"description": "\"true\": the groups that ended too",
"enum": [
"true",
"false"
],
"type": "string"
}
},
"type": "object"
},
"answer": {
"type": "object",
"description": "what groups answers, as its description says"
}
},
{
"name": "what-if",
"description": "Removed in R6 (ADR 0539): refused by name, naming its replacement: the merge check's composition, which composes every machine a change touches",
"optional": true,
"input": {
"properties": {},
"type": "object"
},
"answer": {
"type": "object",
"description": "what what-if answers, as its description says"
}
},
{
"name": "checks",
"description": "What the mesh's checks said of a pull request's head or of one commit: each of the commit's mesh statuses (mesh/check, the one a main requires from R4e, hq's mesh/repo-check, mesh/delivery, …) with its state, description and when it was set; the merge check's full verdict as the controller said it — each layer's summary, the machine that ran it, when, its build id and its report; and whether the branch's protection would let it merge, every required status being success.",
"optional": true,
"input": {
"properties": {
"commit": {
"description": "a commit's sha, or the start of one, instead of a pull request",
"type": "string"
},
"number": {
"description": "a pull request's number: its head is read",
"type": "string"
},
"repository": {
"description": "owner/repository",
"type": "string"
}
},
"required": [
"repository"
],
"type": "object"
},
"answer": {
"type": "object",
"description": "what checks answers, as its description says"
}
},
{
"name": "table",
"description": "The state table every delivery runs by: each transition with its guard, each state's bound and what healer H2 may do once it has passed; and the machine steps' table.",
"input": {
"properties": {},
"type": "object"
},
"answer": {
"type": "object",
"description": "what table answers, as its description says"
}
},
{
"name": "stalled",
"description": "Every delivery held past its state's bound, with the transition the table lets healer H2 take for it, or none.",
"input": {
"properties": {},
"type": "object"
},
"answer": {
"type": "object",
"description": "what stalled answers, as its description says"
}
},
{
"name": "recheck",
"description": "Check a rejected or ready delivery again: it goes back to proposed and the controller is asked for its check. With why.",
"input": {
"properties": {
"id": {
"description": "the delivery's id",
"type": "string"
},
"why": {
"description": "why, kept with the transition",
"type": "string"
}
},
"required": [
"id",
"why"
],
"type": "object"
},
"answer": {
"type": "object",
"description": "what recheck answers, as its description says"
}
},
{
"name": "release",
"description": "Removed in R6 (ADR 0539): refused by name, naming its replacement: the assigner's asks (hold, restore, backup, record), answered on the phone or in the delivery view",
"optional": true,
"input": {
"properties": {},
"type": "object"
},
"answer": {
"type": "object",
"description": "what release answers, as its description says"
}
},
{
"name": "stop",
"description": "Removed in R6 (ADR 0539): refused by name, naming its replacement: a bad fingerprint is held and its canary set back by the assigner (ADR 0429 §6, ADR 0539 clause 18)",
"optional": true,
"input": {
"properties": {},
"type": "object"
},
"answer": {
"type": "object",
"description": "what stop answers, as its description says"
}
},
{
"name": "close",
"description": "Healer H2's verb: take the one transition the table names for a delivery held past its bound. Refused for any other.",
"input": {
"properties": {
"id": {
"description": "the delivery's id",
"type": "string"
},
"why": {
"description": "the condition it answers",
"type": "string"
}
},
"required": [
"id",
"why"
],
"type": "object"
},
"answer": {
"type": "object",
"description": "what close answers, as its description says"
}
},
{
"name": "retire-history",
"description": "Removed in R6 (ADR 0539): refused by name, naming its replacement: nothing to retire: the gate keeps no delivery after its merge",
"optional": true,
"input": {
"properties": {},
"type": "object"
},
"answer": {
"type": "object",
"description": "what retire-history answers, as its description says"
}
},
{
"name": "settle",
"description": "Ask the operator to settle deliveries held because their merge reached the trunk without a passing check: one ask on the operator's channel for all of them, as an Approve is asked. The verb moves nothing; on the operator's answer each goes to merged, and whether it is on the machines is derived as for any merge (novox/hq ADR 0556). Refused for a delivery not held, or held for another reason, naming why.",
"optional": true,
"input": {
"additionalProperties": false,
"properties": {
"ids": {
"description": "the held deliveries' ids (owner/repository@commit)",
"items": {
"type": "string"
},
"minItems": 1,
"type": "array"
},
"why": {
"description": "why they may be settled, shown to the operator",
"type": "string"
}
},
"required": [
"ids",
"why"
],
"type": "object"
},
"answer": {
"type": "object",
"description": "what settle answers, as its description says"
}
}
],
"description": "The delivery gate: reads and steers deliveries, merges and the checks that decide them; the merge is made on the operator's Approve once both checks passed.",
"states": [
{
"name": "source",
"template": "state",
"key-forms": [
{
"key": "main",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"group": {
"type": "string",
"description": "the group that moved it"
},
"heads": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "repository to the merged head, a full commit"
}
},
"required": [
"group",
"heads"
],
"description": "every repository's merged head; its revision is the mesh's source revision"
},
"description": "the one key, written by the holder alone by compare-and-set"
}
],
"description": "the mesh's source revision",
"history": 64
},
{
"name": "merges",
"template": "state",
"key-forms": [
{
"key": "<group>",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"group": {
"type": "string"
},
"ask": {
"type": "string",
"description": "the ask key"
},
"approved": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "<repository>@<number> to the approved head"
},
"pulls": {
"type": "array",
"items": {
"type": "object",
"properties": {
"repository": {
"type": "string"
},
"number": {
"type": "integer"
},
"head": {
"type": "string"
},
"state": {
"type": "string",
"description": "merging, merged or refused"
},
"commit": {
"type": "string"
},
"words": {
"type": "string"
}
},
"required": [
"repository",
"number",
"head",
"state"
]
}
}
},
"required": [
"group",
"ask",
"pulls"
],
"description": "one group's merge and how far each pull request is"
},
"description": "a group's merge"
}
],
"description": "the merges",
"history": 10
},
{
"name": "asks",
"template": "state",
"key-forms": [
{
"key": "merge.<group>.<heads>",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"group": {
"type": "string"
},
"heads": {
"type": "array",
"items": {
"type": "object",
"properties": {
"repository": {
"type": "string",
"description": "<owner>/<name>"
},
"number": {
"type": "integer"
},
"head": {
"type": "string",
"description": "the full commit"
}
},
"required": [
"repository",
"number",
"head"
]
}
},
"ask": {
"description": "the operator-channel ask as raised"
},
"asking": {
"type": "integer",
"description": "the number of times it was asked"
}
},
"required": [
"group",
"heads",
"ask"
]
},
"description": "the ask of the operator for one group at one set of heads"
}
],
"description": "the asks of the operator for a merge",
"history": 5
},
{
"name": "owed",
"template": "state",
"key-forms": [
{
"key": "<repository>.<number>.<head>.<act>",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"act": {
"type": "string",
"description": "status=<context>, view, note or comment.<seq>"
},
"context": {
"type": "string"
},
"target": {
"type": "string"
},
"state": {
"type": "string"
},
"text": {
"type": "string"
},
"ref": {
"type": "string"
}
},
"required": [
"act"
],
"description": "one act owed to the forge, written by its owner and done by the forge bridge"
},
"description": "one act owed to the forge"
}
],
"description": "the acts the gate owes the forge",
"history": 5
},
{
"name": "turns",
"template": "state",
"key-forms": [
{
"key": "<repository>",
"value": {
"type": "object",
"properties": {
"v": {
"type": "integer",
"description": "the layout version"
},
"repository": {
"type": "string"
},
"waiting": {
"type": "array",
"items": {
"type": "object",
"properties": {
"group": {
"type": "string"
},
"heads": {
"type": "string"
},
"ask": {
"type": "string"
},
"approved": {
"type": "string",
"format": "date-time",
"description": "when (RFC 3339 time)"
},
"since": {
"type": "string",
"format": "date-time",
"description": "when (RFC 3339 time)"
},
"waits-for": {
"type": "string"
},
"waits-since": {
"type": "string",
"format": "date-time",
"description": "when (RFC 3339 time)"
},
"step": {
"type": "string"
}
},
"required": [
"group",
"heads",
"approved",
"since"
]
}
}
},
"required": [
"repository",
"waiting"
],
"description": "the merges of a repository in the order they wait for their turn"
},
"description": "the merges waiting on a repository"
}
],
"description": "which merge waits for which",
"history": 5
}
]
}

Some files were not shown because too many files have changed in this diff Show More