diff --git a/contracts/go/clients/meshdeliverycheckerclient/client.go b/contracts/go/clients/meshdeliverycheckerclient/client.go index 80915d61..c7d75a2e 100644 --- a/contracts/go/clients/meshdeliverycheckerclient/client.go +++ b/contracts/go/clients/meshdeliverycheckerclient/client.go @@ -6,6 +6,7 @@ package meshdeliverycheckerclient import ( "fmt" + pathpkg "path" "strings" checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" @@ -220,3 +221,60 @@ func CheckKept(k checker.KeptByLevelDigestValue) error { } 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 +} diff --git a/contracts/go/clients/meshdeliverycheckerclient/client_test.go b/contracts/go/clients/meshdeliverycheckerclient/client_test.go index 062efc25..04191ae5 100644 --- a/contracts/go/clients/meshdeliverycheckerclient/client_test.go +++ b/contracts/go/clients/meshdeliverycheckerclient/client_test.go @@ -59,6 +59,7 @@ func TestAVerdictIsRefusedForWhatAReaderCouldNotPlace(t *testing.T) { } 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) @@ -79,3 +80,48 @@ func TestAKeptVerdictIsAPassOfAPackageAtALevel(t *testing.T) { } } } + +// 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" }, "/"}, + "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) + } +} diff --git a/contracts/go/clients/meshdeliveryclient/client.go b/contracts/go/clients/meshdeliveryclient/client.go new file mode 100644 index 00000000..5754e183 --- /dev/null +++ b/contracts/go/clients/meshdeliveryclient/client.go @@ -0,0 +1,323 @@ +// 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 `/` 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 `#=`, 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: ``, 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..`. +// 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..", 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=`, `view`, `note` or `comment.`. +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=", 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. with seq of 1 or more", act) + } + return OwedComment, seq, nil + } + return "", "", fmt.Errorf("meshdeliveryclient: the owed act %q is not status=, view, note or comment.", act) +} + +// OwedCommentAct is the act of the seq-th comment on a head: `comment.`, 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 /, 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 /, 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: +// `...`. 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=` 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, ...", 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 +} diff --git a/contracts/go/clients/meshdeliveryclient/client_test.go b/contracts/go/clients/meshdeliveryclient/client_test.go new file mode 100644 index 00000000..71cf9ad1 --- /dev/null +++ b/contracts/go/clients/meshdeliveryclient/client_test.go @@ -0,0 +1,91 @@ +package meshdeliveryclient + +import ( + "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) + } + } +} diff --git a/contracts/go/clients/meshdeliveryclient/go.mod b/contracts/go/clients/meshdeliveryclient/go.mod new file mode 100644 index 00000000..975c6294 --- /dev/null +++ b/contracts/go/clients/meshdeliveryclient/go.mod @@ -0,0 +1,17 @@ +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 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef +) + +require ( + github.com/klauspost/compress v1.20.1 // indirect + github.com/nats-io/nats.go v1.54.0 // indirect + github.com/nats-io/nkeys v0.4.16 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + golang.org/x/crypto v0.57.0 // indirect + golang.org/x/sys v0.48.0 // indirect +) diff --git a/contracts/go/clients/meshdeliveryclient/go.sum b/contracts/go/clients/meshdeliveryclient/go.sum new file mode 100644 index 00000000..c811fda7 --- /dev/null +++ b/contracts/go/clients/meshdeliveryclient/go.sum @@ -0,0 +1,18 @@ +git.novox.be/novox/mesh-contracts/go v0.0.0-20261104172218-347a3053580a h1:E9ERPqR4uZXypudHGxv2/wNIOYcder9torVqJwJq/DI= +git.novox.be/novox/mesh-contracts/go v0.0.0-20261104172218-347a3053580a/go.mod h1:0dD1XEJBJ0G5UYDQxSZI84nuujb6m3tOpJl4C8rQVvg= +git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-20261105000000-000000000000 h1:BTqpa7dCCTQH9m08Jle8KVFwyIHiHPSUbwWxuqSg2yA= +git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-20261105000000-000000000000/go.mod h1:pPxwhTMb3hIk7UUrBicJnGQCqfTEoBGJvNFspEK2U18= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef h1:/Jic5ho0xz32Z9DH2NSUqVxCF1KyfIOWuRTrX1aLjus= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174118-f94abb95aaef/go.mod h1:2wHG7ntLCG8Tpm/SCfgp524a5emUJm1gAC4KrRYsU9s= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA= +github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k= +github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg= +github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= diff --git a/contracts/module.json b/contracts/module.json index a239ea00..d80c383c 100644 --- a/contracts/module.json +++ b/contracts/module.json @@ -121,6 +121,15 @@ ], "series": "0.1" }, + { + "name": "go-meshdeliveryclient", + "kind": "package", + "language": "go", + "paths": [ + "go/clients/meshdeliveryclient" + ], + "series": "0.1" + }, { "name": "go-meshdeliverycheckerclient", "kind": "package",