diff --git a/contracts/go/seats/git/contract.go b/contracts/go/seats/git/contract.go new file mode 100644 index 00000000..30f7c416 --- /dev/null +++ b/contracts/go/seats/git/contract.go @@ -0,0 +1,752 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// Package git is the contract of the seat "git", defined whole (seats/git/seat.json): The forge: every read and act a delivery part makes of the mesh's git repositories, and what the forge says happened (design 47 *The seat git*, ADR 0596 §1, §2). A failed read answers an error, never an empty answer. The act verbs are the forge bridge's alone. +package git + +import ( + "bytes" + "encoding/json" + "errors" + "strings" + "time" +) + +// Seat is the seat's name. +const Seat = "git" + +const ( + VerbPull = "pull" + VerbBranch = "branch" + VerbOpenPulls = "open-pulls" + VerbTags = "tags" + VerbTrees = "trees" + VerbFile = "file" + VerbContains = "contains" + VerbStatuses = "statuses" + VerbStatus = "status" + VerbComment = "comment" + VerbView = "view" + VerbNote = "note" + VerbMergePull = "merge-pull" + VerbTag = "tag" +) + +const ( + EventPullUpdated = "pull-updated" + EventPullMerged = "pull-merged" + EventPullClosed = "pull-closed" + EventForgePull = "forge-pull" + EventForgeStatus = "forge-status" + EventForgeDelete = "forge-delete" + EventRepoWritable = "repo-writable" + EventBranchMoved = "branch-moved" +) + +// Verbs is every verb the seat serves, in its definition's order: a new copy each call. +func Verbs() []string { + return []string{VerbPull, VerbBranch, VerbOpenPulls, VerbTags, VerbTrees, VerbFile, VerbContains, VerbStatuses, VerbStatus, VerbComment, VerbView, VerbNote, VerbMergePull, VerbTag} +} + +// Events is every event published on the seat's subject: a new copy each call. +func Events() []string { + return []string{EventPullUpdated, EventPullMerged, EventPullClosed, EventForgePull, EventForgeStatus, EventForgeDelete, EventRepoWritable, EventBranchMoved} +} + +// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. +const ( + Family = "git.events" + FamilyMaxAgeDays = 7 +) + +// SubjectPullUpdated is where pull-updated is published. +const SubjectPullUpdated = "mesh.seat.git.event.pull-updated" + +// SubjectPullMerged is where pull-merged is published. +const SubjectPullMerged = "mesh.seat.git.event.pull-merged" + +// SubjectPullClosed is where pull-closed is published. +const SubjectPullClosed = "mesh.seat.git.event.pull-closed" + +// SubjectForgePull is where forge-pull is published. +const SubjectForgePull = "mesh.seat.git.event.forge-pull" + +// SubjectForgeStatus is where forge-status is published. +const SubjectForgeStatus = "mesh.seat.git.event.forge-status" + +// SubjectForgeDelete is where forge-delete is published. +const SubjectForgeDelete = "mesh.seat.git.event.forge-delete" + +// SubjectRepoWritable is where repo-writable is published. +const SubjectRepoWritable = "mesh.seat.git.event.repo-writable" + +// StreamBranchMoved is the stream branch-moved is kept in. +const StreamBranchMoved = "SEAT_git_branch-moved" + +// SubjectBranchMoved is where branch-moved is published. +const SubjectBranchMoved = "mesh.seat.git.event.branch-moved" + +const ( + StateFailedSteps = "failed-steps" +) + +// FailedStepsByModuleFingerprintStepKey is the key of one value of the state failed-steps under the form "..". +func FailedStepsByModuleFingerprintStepKey(module, fingerprint, step string) string { + return module + "." + fingerprint + "." + step +} + +// ParseFailedStepsByModuleFingerprintStepKey reads the parts of a key of the form "..", in the order of the form; the last result is false for a key of another form. +func ParseFailedStepsByModuleFingerprintStepKey(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 +} + +// DecodeFailedStepsByModuleFingerprintStepValue reads one value of the state failed-steps under the form "..", refusing a field FailedStepsByModuleFingerprintStepValue does not know and any text after the value. +func DecodeFailedStepsByModuleFingerprintStepValue(raw []byte) (FailedStepsByModuleFingerprintStepValue, error) { + var v FailedStepsByModuleFingerprintStepValue + 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 +} + +// Change is what a pull request changed. +type Change struct { + // the changed directories that hold a module at its head; absent when they could not be read + ModuleDirs []string `json:"module-dirs,omitempty"` + // the paths it changed + Paths []string `json:"paths"` + // the forge cut the list + PathsTruncated bool `json:"paths-truncated,omitempty"` + // which of them it deleted + Removed []string `json:"removed,omitempty"` +} + +// ErrorKind is which kind of error, set with error alone: refused, the forge or the holder refused and error says why in the forge's own words; not-found, the forge said that what was asked about (a repository, a commit, a pull request, a branch or a tag) does not exist, so a caller never reads it out of error's words; failed, the forge failed or could not be reached, so the act or read could not be performed. +type ErrorKind = string + +// The values of ErrorKind. +const ( + ErrorKindRefused = "refused" + ErrorKindNotFound = "not-found" + ErrorKindFailed = "failed" +) + +// FailedStep is a failed required step at a build-input fingerprint, with the commits it was seen at and the run that failed it (design 47 *Judged from its own state, never by a call*); cleared by a later passing rerun of that step whose proved caller is the operator or a healer. +type FailedStep struct { + // the artifact reference, : + Artifact string `json:"artifact,omitempty"` + At *time.Time `json:"at,omitempty"` + // every commit it was seen at + Commits []string `json:"commits"` + // the build-input fingerprint the step failed at + Fingerprint string `json:"fingerprint"` + Module string `json:"module"` + // the run that failed it: the runner's pipeline key + Run string `json:"run"` + // why it failed, as the runner's step-ended said + Said string `json:"said,omitempty"` + Step string `json:"step"` + // the layout version + V int64 `json:"v,omitempty"` +} + +// Merging is what the forge would merge now, for an open pull request (issues 1275, 1945; ADR 0441 §1): answered whole or not at all, and a read the forge could not make answers the verb's error, never a guess; absent for a merged or closed one. +type Merging struct { + // the forge's own word on whether it merges into its base now: false while it is in conflict with its base, or while the forge works that out after the base moved + Mergeable bool `json:"mergeable"` + // whether a protection rule guards its base branch + Protected bool `json:"protected"` + // the status contexts the base branch's protection requires; none when it requires none + Required []string `json:"required"` + // whether the forge's merging account may write the repository: false, its statuses and its merge are refused + Writes bool `json:"writes"` +} + +// OpenPull is an open pull request, with the statuses its base requires and those its head has. +type OpenPull struct { + // the head's statuses by context, the newest of each + Checks map[string]Status `json:"checks"` + Pull Pull `json:"pull"` + // the contexts the base branch's protection requires; none when it requires none + Required []string `json:"required"` +} + +// Pull is one pull request as the forge holds it; never its description (ADR 0659 §1). +type Pull struct { + // who opened it, the forge's login + Author string `json:"author,omitempty"` + // the branch it merges into + Base string `json:"base"` + // its head branch: what a delivery group is named by + Branch string `json:"branch"` + // a commit, its full 40-hex sha + Head string `json:"head"` + // the merge commit, once merged + MergeCommit string `json:"merge-commit,omitempty"` + // when it merged + MergedAt *time.Time `json:"merged-at,omitempty"` + // the pull request's number + Number int64 `json:"number"` + // the repository, / + Repository string `json:"repository"` + // open, merged or closed + State string `json:"state"` + // its title + Title string `json:"title,omitempty"` + // its page on the forge + URL string `json:"url,omitempty"` +} + +// The values of Pull's state. +const ( + PullStateOpen = "open" + PullStateMerged = "merged" + PullStateClosed = "closed" +) + +// Status is the newest status of one context on a commit. +type Status struct { + // when it was set + At *time.Time `json:"at,omitempty"` + // who set it + By string `json:"by,omitempty"` + // the status's context, such as mesh/check + Context string `json:"context"` + // what it says + Description string `json:"description,omitempty"` + // pending, success, failure, error or warning + State string `json:"state"` + // the page it links to + Target string `json:"target,omitempty"` +} + +// PullInput is what pull takes. +type PullInput struct { + // instead of the number: the merge commit it made + MergeCommit string `json:"merge-commit,omitempty"` + // its number + Number int64 `json:"number,omitempty"` + // the repository, / + Repository string `json:"repository"` +} + +// PullAnswer is what pull answers. +type PullAnswer struct { + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + Merging *Merging `json:"merging,omitempty"` + Pull *Pull `json:"pull,omitempty"` +} + +// BranchInput is what branch takes. +type BranchInput struct { + Branch string `json:"branch"` + // the repository, / + Repository string `json:"repository"` +} + +// BranchAnswer is what branch answers. +type BranchAnswer struct { + Branch string `json:"branch,omitempty"` + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // a commit, its full 40-hex sha + Head string `json:"head,omitempty"` + // a protection rule refuses a direct push and admits only merges + Protected bool `json:"protected,omitempty"` + Repository string `json:"repository,omitempty"` +} + +// OpenPullsInput is what open-pulls takes. +type OpenPullsInput struct { +} + +// OpenPullsAnswer is what open-pulls answers. +type OpenPullsAnswer struct { + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // every open pull request; said empty when none is open, so an answer that lacks it is never read as none + Pulls []OpenPull `json:"pulls"` +} + +// TagsInput is what tags takes. +type TagsInput struct { + // the page, from 1 + Page int64 `json:"page,omitempty"` + // only tags starting with it + Prefix string `json:"prefix,omitempty"` + // the repository, / + Repository string `json:"repository"` +} + +// TagsAnswer is what tags answers. +type TagsAnswer struct { + // how many tags a page holds + Bound int64 `json:"bound,omitempty"` + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // another page exists + More bool `json:"more,omitempty"` + // the page + Page int64 `json:"page,omitempty"` + // the repository, / + Repository string `json:"repository,omitempty"` + // the tags of this page + Tags []string `json:"tags,omitempty"` +} + +// TreesInput is what trees takes. +type TreesInput struct { + // a commit, its full 40-hex sha + Commit string `json:"commit"` + // directories whose every blob is answered; the root is the empty name + Files []string `json:"files,omitempty"` + // {directory: [path relative to it]}; the root is the empty name + Paths map[string][]string `json:"paths,omitempty"` + // the repository, / + Repository string `json:"repository"` +} + +// TreesAnswer is what trees answers. +type TreesAnswer struct { + // a commit, its full 40-hex sha + Commit string `json:"commit,omitempty"` + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // directory to {path: blob id} + Files map[string]map[string]string `json:"files,omitempty"` + // directory to {path: object id} + Paths map[string]map[string]string `json:"paths,omitempty"` + // the root tree's hash + Root string `json:"root,omitempty"` + // directory to its tree's hash, the root as the empty name + Trees map[string]string `json:"trees,omitempty"` +} + +// FileInput is what file takes. +type FileInput struct { + // the file's path from the repository's root + Path string `json:"path"` + // a commit, branch or tag; the default branch when absent + Ref string `json:"ref,omitempty"` + // the repository, / + Repository string `json:"repository"` +} + +// FileAnswer is what file answers. +type FileAnswer struct { + // the forge holds no such file there + Absent bool `json:"absent,omitempty"` + // the file's git blob id + Blob string `json:"blob,omitempty"` + // its content + Content string `json:"content,omitempty"` + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // the path + Path string `json:"path,omitempty"` + // what was read + Ref string `json:"ref,omitempty"` + // its size in bytes + Size int64 `json:"size,omitempty"` +} + +// ContainsInput is what contains takes. +type ContainsInput struct { + // the commit asked about + Commit string `json:"commit"` + // the commit it may be contained in + In string `json:"in"` + // the repository, / + Repository string `json:"repository"` +} + +// ContainsAnswer is what contains answers. +type ContainsAnswer struct { + // how many commits commit is ahead of in + Ahead int64 `json:"ahead,omitempty"` + // the commit + Commit string `json:"commit,omitempty"` + // in contains commit + Contains bool `json:"contains,omitempty"` + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // the other + In string `json:"in,omitempty"` +} + +// StatusesInput is what statuses takes. +type StatusesInput struct { + // a commit, its full 40-hex sha + Commit string `json:"commit"` + // the repository, / + Repository string `json:"repository"` +} + +// StatusesAnswer is what statuses answers. +type StatusesAnswer struct { + // the forge's combined state + Combined string `json:"combined,omitempty"` + // a commit, its full 40-hex sha + Commit string `json:"commit,omitempty"` + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // the newest of each context + Statuses []Status `json:"statuses,omitempty"` +} + +// StatusInput is what status takes. +type StatusInput struct { + // a commit, its full 40-hex sha + Commit string `json:"commit"` + // the context, such as mesh/check + Context string `json:"context"` + // what it says + Description string `json:"description,omitempty"` + // the act's id + ID string `json:"id"` + // the repository, / + Repository string `json:"repository"` + // the state + State string `json:"state"` + // the page it links to + Target string `json:"target,omitempty"` +} + +// The values of StatusInput's state. +const ( + StatusInputStatePending = "pending" + StatusInputStateSuccess = "success" + StatusInputStateFailure = "failure" + StatusInputStateError = "error" + StatusInputStateWarning = "warning" +) + +// StatusAnswer is what status answers. +type StatusAnswer struct { + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // the id was already held on the pull request or the note, so nothing was done again + Held bool `json:"held,omitempty"` + // the act's id, as the caller gave it + ID string `json:"id,omitempty"` + // the forge holds the act now + Performed bool `json:"performed,omitempty"` +} + +// CommentInput is what comment takes. +type CommentInput struct { + // the act's id + ID string `json:"id"` + // the pull request's number + Number int64 `json:"number"` + // the repository, / + Repository string `json:"repository"` + // the comment, markdown + Text string `json:"text"` +} + +// CommentAnswer is what comment answers. +type CommentAnswer struct { + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // the id was already held on the pull request or the note, so nothing was done again + Held bool `json:"held,omitempty"` + // the act's id, as the caller gave it + ID string `json:"id,omitempty"` + // the forge holds the act now + Performed bool `json:"performed,omitempty"` +} + +// ViewInput is what view takes. +type ViewInput struct { + // the act's id + ID string `json:"id"` + // the pull request's number + Number int64 `json:"number"` + // the repository, / + Repository string `json:"repository"` + // the view, markdown + Text string `json:"text"` +} + +// ViewAnswer is what view answers. +type ViewAnswer struct { + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // the id was already held on the pull request or the note, so nothing was done again + Held bool `json:"held,omitempty"` + // the act's id, as the caller gave it + ID string `json:"id,omitempty"` + // the forge holds the act now + Performed bool `json:"performed,omitempty"` +} + +// NoteInput is what note takes. +type NoteInput struct { + // a commit, its full 40-hex sha + Commit string `json:"commit"` + // the act's id + ID string `json:"id"` + // the line, one line + Line string `json:"line"` + // the repository, / + Repository string `json:"repository"` +} + +// NoteAnswer is what note answers. +type NoteAnswer struct { + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // the id was already held on the pull request or the note, so nothing was done again + Held bool `json:"held,omitempty"` + // the act's id, as the caller gave it + ID string `json:"id,omitempty"` + // the forge holds the act now + Performed bool `json:"performed,omitempty"` +} + +// MergePullInput is what merge-pull takes. +type MergePullInput struct { + // the head the caller names + Head string `json:"head"` + // the act's id + ID string `json:"id"` + // the pull request's number + Number int64 `json:"number"` + // the repository, / + Repository string `json:"repository"` +} + +// MergePullAnswer is what merge-pull answers. +type MergePullAnswer struct { + // why the forge could not answer, or refused: set alone with error-kind, and a caller reads it as a failure, never as none + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // the id was already held on the pull request or the note, so nothing was done again + Held bool `json:"held,omitempty"` + // the act's id, as the caller gave it + ID string `json:"id,omitempty"` + // the merge commit + MergeCommit string `json:"merge-commit,omitempty"` + // for policy-failed, the module + Module string `json:"module,omitempty"` + // the forge holds the act now + Performed bool `json:"performed,omitempty"` + // why the forge or the holder refused the merge; the act is not performed + Refused string `json:"refused,omitempty"` + // which refusal, set with refused: head-moved, the pull request's head is not the one named; unprotected, the base branch's protection does not refuse a direct push; policy-failed, a run at the head's fingerprint failed a required step (run, module and step name it); no-registrations, no registration-changed heard yet (ADR 0667 section 10); git-refused, git refused the merge + RefusedKind string `json:"refused-kind,omitempty"` + // for policy-failed, the run that failed the step: the runner's pipeline key + Run string `json:"run,omitempty"` + // for policy-failed, the step + Step string `json:"step,omitempty"` +} + +// The values of MergePullAnswer's refused-kind. +const ( + MergePullAnswerRefusedKindHeadMoved = "head-moved" + MergePullAnswerRefusedKindUnprotected = "unprotected" + MergePullAnswerRefusedKindPolicyFailed = "policy-failed" + MergePullAnswerRefusedKindNoRegistrations = "no-registrations" + MergePullAnswerRefusedKindGitRefused = "git-refused" +) + +// TagInput is what tag takes. +type TagInput struct { + // the commit it is written at + Commit string `json:"commit"` + // the repository, / + Repository string `json:"repository"` + // the tag + Tag string `json:"tag"` +} + +// TagAnswer is what tag answers. +type TagAnswer struct { + // why the forge could not write it: set alone with error-kind + Error string `json:"error,omitempty"` + ErrorKind ErrorKind `json:"error-kind,omitempty"` + // the forge holds the tag at that commit now + Performed bool `json:"performed,omitempty"` + // the tag, as asked + Tag string `json:"tag,omitempty"` +} + +// PullUpdatedEvent is the body of the event pull-updated. +type PullUpdatedEvent struct { + // who opened it + Author string `json:"author,omitempty"` + // the branch it merges into + Base string `json:"base"` + // the base branch's head as the head was announced; absent when it could not be read + BaseHead string `json:"base-head,omitempty"` + // its head branch + Branch string `json:"branch"` + Change *Change `json:"change,omitempty"` + // a commit, its full 40-hex sha + Head string `json:"head"` + // the pull request's number + Number int64 `json:"number"` + // the repository, / + Repository string `json:"repository"` + // its title + Title string `json:"title,omitempty"` + // its page + URL string `json:"url,omitempty"` +} + +// PullMergedEvent is the body of the event pull-merged. +type PullMergedEvent struct { + // who opened it + Author string `json:"author,omitempty"` + // the branch it merges into + Base string `json:"base"` + // its head branch + Branch string `json:"branch"` + Change *Change `json:"change,omitempty"` + // a commit, its full 40-hex sha + Head string `json:"head"` + // the head's statuses by context, their states; absent when they could not be read + HeadChecks map[string]string `json:"head-checks,omitempty"` + // a commit, its full 40-hex sha + MergeCommit string `json:"merge-commit"` + // when + MergedAt *time.Time `json:"merged-at,omitempty"` + // the pull request's number + Number int64 `json:"number"` + // the repository, / + Repository string `json:"repository"` + // its title + Title string `json:"title,omitempty"` + // its page + URL string `json:"url,omitempty"` +} + +// PullClosedEvent is the body of the event pull-closed. +type PullClosedEvent struct { + // who opened it + Author string `json:"author,omitempty"` + // the branch it merges into + Base string `json:"base"` + // its head branch + Branch string `json:"branch"` + // a commit, its full 40-hex sha + Head string `json:"head"` + // the pull request's number + Number int64 `json:"number"` + // the repository, / + Repository string `json:"repository"` + // its title + Title string `json:"title,omitempty"` + // its page + URL string `json:"url,omitempty"` +} + +// ForgePullEvent is the body of the event forge-pull. +type ForgePullEvent struct { + // what the forge said: opened, reopened, synchronized, edited, closed, merged + Action string `json:"action"` + // who opened it + Author string `json:"author,omitempty"` + // its base branch + Base string `json:"base,omitempty"` + // its head branch + Branch string `json:"branch,omitempty"` + // its head commit + Head string `json:"head,omitempty"` + // the merge commit + MergeCommit string `json:"merge-commit,omitempty"` + // merged + Merged bool `json:"merged,omitempty"` + // its number + Number int64 `json:"number"` + // the repository, / + Repository string `json:"repository"` + // who acted + Sender string `json:"sender,omitempty"` +} + +// ForgeStatusEvent is the body of the event forge-status. +type ForgeStatusEvent struct { + // the commit + Commit string `json:"commit"` + // the context + Context string `json:"context"` + // what it says + Description string `json:"description,omitempty"` + // the repository, / + Repository string `json:"repository"` + // who set it + Sender string `json:"sender,omitempty"` + // the state + State string `json:"state"` +} + +// ForgeDeleteEvent is the body of the event forge-delete. +type ForgeDeleteEvent struct { + // the deleted branch or tag + Ref string `json:"ref"` + // branch or tag + RefType string `json:"ref-type,omitempty"` + // the repository, / + Repository string `json:"repository"` + // who deleted it + Sender string `json:"sender,omitempty"` +} + +// RepoWritableEvent is the body of the event repo-writable. +type RepoWritableEvent struct { + // the account that writes it now + Account string `json:"account"` + // the repository, / + Repository string `json:"repository"` +} + +// BranchMovedEvent is the body of the event branch-moved. +type BranchMovedEvent struct { + At *time.Time `json:"at,omitempty"` + Branch string `json:"branch"` + // the head the webhook said, a commit's full 40-hex sha; read again by branch + Head string `json:"head,omitempty"` + // the repository, / + Repository string `json:"repository"` +} + +// FailedStepsByModuleFingerprintStepValue is one value of the state failed-steps under the key ... +type FailedStepsByModuleFingerprintStepValue = FailedStep diff --git a/contracts/go/seats/git/contract_test.go b/contracts/go/seats/git/contract_test.go new file mode 100644 index 00000000..315ed3a0 --- /dev/null +++ b/contracts/go/seats/git/contract_test.go @@ -0,0 +1,164 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +package git + +import ( + "encoding/json" + "reflect" + "regexp" + "testing" +) + +// TestFixtures reads every fixture of seats/git/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: "pull.updated is said", subject: "mesh.seat.git.event.pull-updated", given: "{\"author\":\"jochen\",\"base\":\"main\",\"base-head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"branch\":\"feat/47-G\",\"change\":{\"module-dirs\":[\"go\"],\"paths\":[\"go/seatlist/seatlist.go\"]},\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"title\":\"One wiring\",\"url\":\"https://forge.test/p/178\"}", wire: "{\"author\":\"jochen\",\"base\":\"main\",\"base-head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"branch\":\"feat/47-G\",\"change\":{\"module-dirs\":[\"go\"],\"paths\":[\"go/seatlist/seatlist.go\"]},\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"title\":\"One wiring\",\"url\":\"https://forge.test/p/178\"}", + decode: func(b []byte) (any, error) { var v PullUpdatedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.git\\.event\\.pull-updated$")}, + {name: "pull.merged is said", subject: "mesh.seat.git.event.pull-merged", given: "{\"base\":\"main\",\"branch\":\"feat/47-G\",\"change\":{\"module-dirs\":[\"go\"],\"paths\":[\"go/seatlist/seatlist.go\"]},\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"head-checks\":{\"mesh/check\":\"success\"},\"merge-commit\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"merged-at\":\"2026-11-09T19:40:00Z\",\"number\":178,\"repository\":\"novox/mesh-sdk\"}", wire: "{\"base\":\"main\",\"branch\":\"feat/47-G\",\"change\":{\"module-dirs\":[\"go\"],\"paths\":[\"go/seatlist/seatlist.go\"]},\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"head-checks\":{\"mesh/check\":\"success\"},\"merge-commit\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"merged-at\":\"2026-11-09T19:40:00Z\",\"number\":178,\"repository\":\"novox/mesh-sdk\"}", + decode: func(b []byte) (any, error) { var v PullMergedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.git\\.event\\.pull-merged$")}, + {name: "pull.closed is said", subject: "mesh.seat.git.event.pull-closed", given: "{\"base\":\"main\",\"branch\":\"fix/x\",\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"number\":179,\"repository\":\"novox/mesh-sdk\"}", wire: "{\"base\":\"main\",\"branch\":\"fix/x\",\"head\":\"cccccccccccccccccccccccccccccccccccccccc\",\"number\":179,\"repository\":\"novox/mesh-sdk\"}", + decode: func(b []byte) (any, error) { var v PullClosedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.git\\.event\\.pull-closed$")}, + {name: "forge.pull is said", subject: "mesh.seat.git.event.forge-pull", given: "{\"action\":\"synchronized\",\"author\":\"jochen\",\"base\":\"main\",\"branch\":\"feat/47-G\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"sender\":\"jochen\"}", wire: "{\"action\":\"synchronized\",\"author\":\"jochen\",\"base\":\"main\",\"branch\":\"feat/47-G\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"sender\":\"jochen\"}", + decode: func(b []byte) (any, error) { var v ForgePullEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.git\\.event\\.forge-pull$")}, + {name: "forge.status is said", subject: "mesh.seat.git.event.forge-status", given: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"context\":\"mesh/check\",\"description\":\"failed\",\"repository\":\"novox/mesh-sdk\",\"sender\":\"mesh-delivery-checker\",\"state\":\"failure\"}", wire: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"context\":\"mesh/check\",\"description\":\"failed\",\"repository\":\"novox/mesh-sdk\",\"sender\":\"mesh-delivery-checker\",\"state\":\"failure\"}", + decode: func(b []byte) (any, error) { var v ForgeStatusEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.git\\.event\\.forge-status$")}, + {name: "forge.delete is said", subject: "mesh.seat.git.event.forge-delete", given: "{\"ref\":\"feat/47-G\",\"ref-type\":\"branch\",\"repository\":\"novox/mesh-sdk\",\"sender\":\"jochen\"}", wire: "{\"ref\":\"feat/47-G\",\"ref-type\":\"branch\",\"repository\":\"novox/mesh-sdk\",\"sender\":\"jochen\"}", + decode: func(b []byte) (any, error) { var v ForgeDeleteEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.git\\.event\\.forge-delete$")}, + {name: "repo.writable is said", subject: "mesh.seat.git.event.repo-writable", given: "{\"account\":\"mesh-delivery\",\"repository\":\"jochen/notes\"}", wire: "{\"account\":\"mesh-delivery\",\"repository\":\"jochen/notes\"}", + decode: func(b []byte) (any, error) { var v RepoWritableEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.git\\.event\\.repo-writable$")}, + {name: "a branch moved by a merge", subject: "mesh.seat.git.event.branch-moved", given: "{\"at\":\"2026-11-10T10:00:00Z\",\"branch\":\"main\",\"head\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"repository\":\"novox/mesh-catalog\"}", wire: "{\"at\":\"2026-11-10T10:00:00Z\",\"branch\":\"main\",\"head\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"repository\":\"novox/mesh-catalog\"}", + decode: func(b []byte) (any, error) { var v BranchMovedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.git\\.event\\.branch-moved$")}, + {name: "a failed required step kept", subject: "", given: "{\"artifact\":\"nats:program\",\"at\":\"2026-11-10T10:00:00Z\",\"commits\":[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"fingerprint\":\"fp-0123\",\"module\":\"nats\",\"run\":\"main.12.nats\",\"said\":\"TestTheBusWhole failed\",\"step\":\"unit\",\"v\":1}", wire: "{\"artifact\":\"nats:program\",\"at\":\"2026-11-10T10:00:00Z\",\"commits\":[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"],\"fingerprint\":\"fp-0123\",\"module\":\"nats\",\"run\":\"main.12.nats\",\"said\":\"TestTheBusWhole failed\",\"step\":\"unit\",\"v\":1}", + decode: func(b []byte) (any, error) { + var v FailedStepsByModuleFingerprintStepValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, + {name: "pull asks", subject: "", given: "{\"merge-commit\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"repository\":\"novox/mesh-sdk\"}", wire: "{\"merge-commit\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"repository\":\"novox/mesh-sdk\"}", + decode: func(b []byte) (any, error) { var v PullInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "pull answers", subject: "", given: "{\"pull\":{\"author\":\"jochen\",\"base\":\"main\",\"branch\":\"feat/47-G\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"merge-commit\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"merged-at\":\"2026-11-09T19:40:00Z\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"state\":\"merged\",\"title\":\"One wiring of the seat list\",\"url\":\"https://forge.test/novox/mesh-sdk/pulls/178\"}}", wire: "{\"pull\":{\"author\":\"jochen\",\"base\":\"main\",\"branch\":\"feat/47-G\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"merge-commit\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"merged-at\":\"2026-11-09T19:40:00Z\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"state\":\"merged\",\"title\":\"One wiring of the seat list\",\"url\":\"https://forge.test/novox/mesh-sdk/pulls/178\"}}", + decode: func(b []byte) (any, error) { var v PullAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "pull answers an open one with what the forge would merge now", subject: "", given: "{\"merging\":{\"mergeable\":false,\"protected\":true,\"required\":[\"mesh/check\"],\"writes\":true},\"pull\":{\"author\":\"jochen\",\"base\":\"main\",\"branch\":\"feat/47-G\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"number\":179,\"repository\":\"novox/mesh-sdk\",\"state\":\"open\",\"title\":\"One wiring of the seat list\",\"url\":\"https://forge.test/novox/mesh-sdk/pulls/179\"}}", wire: "{\"merging\":{\"mergeable\":false,\"protected\":true,\"required\":[\"mesh/check\"],\"writes\":true},\"pull\":{\"author\":\"jochen\",\"base\":\"main\",\"branch\":\"feat/47-G\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"number\":179,\"repository\":\"novox/mesh-sdk\",\"state\":\"open\",\"title\":\"One wiring of the seat list\",\"url\":\"https://forge.test/novox/mesh-sdk/pulls/179\"}}", + decode: func(b []byte) (any, error) { var v PullAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "pull answers an error, never none", subject: "", given: "{\"error\":\"the forge holds no pull request novox/mesh-sdk#999\",\"error-kind\":\"refused\"}", wire: "{\"error\":\"the forge holds no pull request novox/mesh-sdk#999\",\"error-kind\":\"refused\"}", + decode: func(b []byte) (any, error) { var v PullAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "open-pulls asks", subject: "", given: "{}", wire: "{}", + decode: func(b []byte) (any, error) { var v OpenPullsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "open-pulls answers", subject: "", given: "{\"pulls\":[{\"checks\":{\"mesh/check\":{\"at\":\"2026-11-09T19:00:00Z\",\"by\":\"mesh-delivery-checker\",\"context\":\"mesh/check\",\"description\":\"passed\",\"state\":\"success\",\"target\":\"https://forge.test/x\"}},\"pull\":{\"author\":\"jochen\",\"base\":\"main\",\"branch\":\"feat/47-G\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"state\":\"open\",\"title\":\"One wiring of the seat list\",\"url\":\"https://forge.test/novox/mesh-sdk/pulls/178\"},\"required\":[\"mesh/check\"]}]}", wire: "{\"pulls\":[{\"checks\":{\"mesh/check\":{\"at\":\"2026-11-09T19:00:00Z\",\"by\":\"mesh-delivery-checker\",\"context\":\"mesh/check\",\"description\":\"passed\",\"state\":\"success\",\"target\":\"https://forge.test/x\"}},\"pull\":{\"author\":\"jochen\",\"base\":\"main\",\"branch\":\"feat/47-G\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"state\":\"open\",\"title\":\"One wiring of the seat list\",\"url\":\"https://forge.test/novox/mesh-sdk/pulls/178\"},\"required\":[\"mesh/check\"]}]}", + decode: func(b []byte) (any, error) { var v OpenPullsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "open-pulls answers none open as an empty list", subject: "", given: "{\"pulls\":[]}", wire: "{\"pulls\":[]}", + decode: func(b []byte) (any, error) { var v OpenPullsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "open-pulls answers an error, never none", subject: "", given: "{\"error\":\"the forge answered 502\",\"error-kind\":\"failed\",\"pulls\":[]}", wire: "{\"error\":\"the forge answered 502\",\"error-kind\":\"failed\",\"pulls\":[]}", + decode: func(b []byte) (any, error) { var v OpenPullsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "tags asks", subject: "", given: "{\"page\":1,\"prefix\":\"go/v\",\"repository\":\"novox/mesh-sdk\"}", wire: "{\"page\":1,\"prefix\":\"go/v\",\"repository\":\"novox/mesh-sdk\"}", + decode: func(b []byte) (any, error) { var v TagsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "tags answers", subject: "", given: "{\"bound\":50,\"more\":true,\"page\":1,\"repository\":\"novox/mesh-sdk\",\"tags\":[\"go/v0.1.15\",\"go/v0.1.14\"]}", wire: "{\"bound\":50,\"more\":true,\"page\":1,\"repository\":\"novox/mesh-sdk\",\"tags\":[\"go/v0.1.15\",\"go/v0.1.14\"]}", + decode: func(b []byte) (any, error) { var v TagsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "tags answers an error, never none", subject: "", given: "{\"error\":\"the forge answered 404 for novox/nothing\",\"error-kind\":\"refused\"}", wire: "{\"error\":\"the forge answered 404 for novox/nothing\",\"error-kind\":\"refused\"}", + decode: func(b []byte) (any, error) { var v TagsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "trees asks", subject: "", given: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"files\":[\"contracts\"],\"paths\":{\"\":[\"go.work\"]},\"repository\":\"novox/mesh-delivery\"}", wire: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"files\":[\"contracts\"],\"paths\":{\"\":[\"go.work\"]},\"repository\":\"novox/mesh-delivery\"}", + decode: func(b []byte) (any, error) { var v TreesInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "trees answers", subject: "", given: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"files\":{\"contracts\":{\"module.json\":\"ffffffffffffffffffffffffffffffffffffffff\"}},\"paths\":{\"\":{\"go.work\":\"1111111111111111111111111111111111111111\"}},\"root\":\"dddddddddddddddddddddddddddddddddddddddd\",\"trees\":{\"\":\"dddddddddddddddddddddddddddddddddddddddd\",\"contracts\":\"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\"}}", wire: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"files\":{\"contracts\":{\"module.json\":\"ffffffffffffffffffffffffffffffffffffffff\"}},\"paths\":{\"\":{\"go.work\":\"1111111111111111111111111111111111111111\"}},\"root\":\"dddddddddddddddddddddddddddddddddddddddd\",\"trees\":{\"\":\"dddddddddddddddddddddddddddddddddddddddd\",\"contracts\":\"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\"}}", + decode: func(b []byte) (any, error) { var v TreesAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "trees answers an error, never none", subject: "", given: "{\"error\":\"the forge's tree listing of novox/mesh-delivery at aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa was cut; no trees are answered from a part\",\"error-kind\":\"refused\"}", wire: "{\"error\":\"the forge's tree listing of novox/mesh-delivery at aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa was cut; no trees are answered from a part\",\"error-kind\":\"refused\"}", + decode: func(b []byte) (any, error) { var v TreesAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "file asks", subject: "", given: "{\"path\":\"seats/index.json\",\"ref\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"repository\":\"novox/mesh-contracts\"}", wire: "{\"path\":\"seats/index.json\",\"ref\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"repository\":\"novox/mesh-contracts\"}", + decode: func(b []byte) (any, error) { var v FileInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "file answers", subject: "", given: "{\"blob\":\"2222222222222222222222222222222222222222\",\"content\":\"{\\\"format\\\":1,\\\"seats\\\":[\\\"mesh-broker\\\"]}\",\"path\":\"seats/index.json\",\"ref\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"size\":31}", wire: "{\"blob\":\"2222222222222222222222222222222222222222\",\"content\":\"{\\\"format\\\":1,\\\"seats\\\":[\\\"mesh-broker\\\"]}\",\"path\":\"seats/index.json\",\"ref\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"size\":31}", + decode: func(b []byte) (any, error) { var v FileAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "file answers", subject: "", given: "{\"absent\":true,\"path\":\"seats/gone.json\",\"ref\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", wire: "{\"absent\":true,\"path\":\"seats/gone.json\",\"ref\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}", + decode: func(b []byte) (any, error) { var v FileAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "file answers an error, never none", subject: "", given: "{\"error\":\"seats/big.json is over 512 KiB\",\"error-kind\":\"refused\"}", wire: "{\"error\":\"seats/big.json is over 512 KiB\",\"error-kind\":\"refused\"}", + decode: func(b []byte) (any, error) { var v FileAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "contains asks", subject: "", given: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"in\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"repository\":\"novox/mesh-sdk\"}", wire: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"in\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"repository\":\"novox/mesh-sdk\"}", + decode: func(b []byte) (any, error) { var v ContainsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "contains answers", subject: "", given: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"contains\":true,\"in\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"}", wire: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"contains\":true,\"in\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"}", + decode: func(b []byte) (any, error) { var v ContainsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "contains answers an error, never none", subject: "", given: "{\"error\":\"the forge cannot compare bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"error-kind\":\"refused\"}", wire: "{\"error\":\"the forge cannot compare bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"error-kind\":\"refused\"}", + decode: func(b []byte) (any, error) { var v ContainsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "contains answers a commit the forge does not have as not-found", subject: "", given: "{\"error\":\"the forge has no commit aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"error-kind\":\"not-found\"}", wire: "{\"error\":\"the forge has no commit aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"error-kind\":\"not-found\"}", + decode: func(b []byte) (any, error) { var v ContainsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "statuses asks", subject: "", given: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"repository\":\"novox/mesh-sdk\"}", wire: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"repository\":\"novox/mesh-sdk\"}", + decode: func(b []byte) (any, error) { var v StatusesInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "statuses answers", subject: "", given: "{\"combined\":\"success\",\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"statuses\":[{\"at\":\"2026-11-09T19:00:00Z\",\"by\":\"mesh-delivery-checker\",\"context\":\"mesh/check\",\"description\":\"passed\",\"state\":\"success\",\"target\":\"https://forge.test/x\"}]}", wire: "{\"combined\":\"success\",\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"statuses\":[{\"at\":\"2026-11-09T19:00:00Z\",\"by\":\"mesh-delivery-checker\",\"context\":\"mesh/check\",\"description\":\"passed\",\"state\":\"success\",\"target\":\"https://forge.test/x\"}]}", + decode: func(b []byte) (any, error) { var v StatusesAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "statuses answers an error, never none", subject: "", given: "{\"error\":\"the forge answered 500\",\"error-kind\":\"failed\"}", wire: "{\"error\":\"the forge answered 500\",\"error-kind\":\"failed\"}", + decode: func(b []byte) (any, error) { var v StatusesAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "status asks", subject: "", given: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"context\":\"mesh/check\",\"description\":\"checking\",\"id\":\"owed-1\",\"repository\":\"novox/mesh-sdk\",\"state\":\"pending\"}", wire: "{\"commit\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"context\":\"mesh/check\",\"description\":\"checking\",\"id\":\"owed-1\",\"repository\":\"novox/mesh-sdk\",\"state\":\"pending\"}", + decode: func(b []byte) (any, error) { var v StatusInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "status answers", subject: "", given: "{\"id\":\"owed-1\",\"performed\":true}", wire: "{\"id\":\"owed-1\",\"performed\":true}", + decode: func(b []byte) (any, error) { var v StatusAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "status answers an error, never none", subject: "", given: "{\"error\":\"the forge account cannot write novox/mesh-sdk\",\"error-kind\":\"refused\"}", wire: "{\"error\":\"the forge account cannot write novox/mesh-sdk\",\"error-kind\":\"refused\"}", + decode: func(b []byte) (any, error) { var v StatusAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "comment asks", subject: "", given: "{\"id\":\"owed-2\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"text\":\"the delivery reached every node\"}", wire: "{\"id\":\"owed-2\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"text\":\"the delivery reached every node\"}", + decode: func(b []byte) (any, error) { var v CommentInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "comment answers", subject: "", given: "{\"held\":true,\"id\":\"owed-2\"}", wire: "{\"held\":true,\"id\":\"owed-2\"}", + decode: func(b []byte) (any, error) { var v CommentAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "comment answers an error, never none", subject: "", given: "{\"error\":\"the forge answered 403\",\"error-kind\":\"refused\"}", wire: "{\"error\":\"the forge answered 403\",\"error-kind\":\"refused\"}", + decode: func(b []byte) (any, error) { var v CommentAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "view asks", subject: "", given: "{\"id\":\"owed-3\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"text\":\"## Delivery\"}", wire: "{\"id\":\"owed-3\",\"number\":178,\"repository\":\"novox/mesh-sdk\",\"text\":\"## Delivery\"}", + decode: func(b []byte) (any, error) { var v ViewInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "view answers", subject: "", given: "{\"id\":\"owed-3\",\"performed\":true}", wire: "{\"id\":\"owed-3\",\"performed\":true}", + decode: func(b []byte) (any, error) { var v ViewAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "view answers an error, never none", subject: "", given: "{\"error\":\"the forge answered 502\",\"error-kind\":\"failed\"}", wire: "{\"error\":\"the forge answered 502\",\"error-kind\":\"failed\"}", + decode: func(b []byte) (any, error) { var v ViewAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "note asks", subject: "", given: "{\"commit\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"id\":\"owed-4\",\"line\":\"owed-4 delivered to ace\",\"repository\":\"novox/mesh-sdk\"}", wire: "{\"commit\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"id\":\"owed-4\",\"line\":\"owed-4 delivered to ace\",\"repository\":\"novox/mesh-sdk\"}", + decode: func(b []byte) (any, error) { var v NoteInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "note answers", subject: "", given: "{\"id\":\"owed-4\",\"performed\":true}", wire: "{\"id\":\"owed-4\",\"performed\":true}", + decode: func(b []byte) (any, error) { var v NoteAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "note answers an error, never none", subject: "", given: "{\"error\":\"the note ref could not be pushed\",\"error-kind\":\"failed\"}", wire: "{\"error\":\"the note ref could not be pushed\",\"error-kind\":\"failed\"}", + decode: func(b []byte) (any, error) { var v NoteAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "merge-pull asks", subject: "", given: "{\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"id\":\"owed-5\",\"number\":178,\"repository\":\"novox/mesh-sdk\"}", wire: "{\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"id\":\"owed-5\",\"number\":178,\"repository\":\"novox/mesh-sdk\"}", + decode: func(b []byte) (any, error) { var v MergePullInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "merge-pull answers", subject: "", given: "{\"id\":\"owed-5\",\"merge-commit\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"performed\":true}", wire: "{\"id\":\"owed-5\",\"merge-commit\":\"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"performed\":true}", + decode: func(b []byte) (any, error) { var v MergePullAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "merge-pull answers", subject: "", given: "{\"id\":\"owed-5\",\"refused\":\"the head moved: cccccccccccccccccccccccccccccccccccccccc, not the named aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"refused-kind\":\"head-moved\"}", wire: "{\"id\":\"owed-5\",\"refused\":\"the head moved: cccccccccccccccccccccccccccccccccccccccc, not the named aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"refused-kind\":\"head-moved\"}", + decode: func(b []byte) (any, error) { var v MergePullAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "merge-pull answers an error, never none", subject: "", given: "{\"error\":\"the forge answered 502\",\"error-kind\":\"failed\"}", wire: "{\"error\":\"the forge answered 502\",\"error-kind\":\"failed\"}", + decode: func(b []byte) (any, error) { var v MergePullAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "merge-pull refuses a failed required step, naming the run, the module and the step", subject: "", given: "{\"id\":\"merge-7\",\"module\":\"nats\",\"refused\":\"unit of nats:program failed at fp-0123 in main.12.nats\",\"refused-kind\":\"policy-failed\",\"run\":\"main.12.nats\",\"step\":\"unit\"}", wire: "{\"id\":\"merge-7\",\"module\":\"nats\",\"refused\":\"unit of nats:program failed at fp-0123 in main.12.nats\",\"refused-kind\":\"policy-failed\",\"run\":\"main.12.nats\",\"step\":\"unit\"}", + decode: func(b []byte) (any, error) { var v MergePullAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "branch asks a branch", subject: "", given: "{\"branch\":\"main\",\"repository\":\"novox/mesh-catalog\"}", wire: "{\"branch\":\"main\",\"repository\":\"novox/mesh-catalog\"}", + decode: func(b []byte) (any, error) { var v BranchInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "branch answers its head and protection", subject: "", given: "{\"branch\":\"main\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"protected\":true,\"repository\":\"novox/mesh-catalog\"}", wire: "{\"branch\":\"main\",\"head\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"protected\":true,\"repository\":\"novox/mesh-catalog\"}", + decode: func(b []byte) (any, error) { var v BranchAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "branch answers a branch the forge does not hold", subject: "", given: "{\"error\":\"no branch beta in novox/mesh-catalog\",\"error-kind\":\"not-found\"}", wire: "{\"error\":\"no branch beta in novox/mesh-catalog\",\"error-kind\":\"not-found\"}", + decode: func(b []byte) (any, error) { var v BranchAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "tag asks", subject: "", given: "{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"repository\":\"novox/mesh-sdk\",\"tag\":\"go/v0.1.15\"}", wire: "{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"repository\":\"novox/mesh-sdk\",\"tag\":\"go/v0.1.15\"}", + decode: func(b []byte) (any, error) { var v TagInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "tag answers performed", subject: "", given: "{\"performed\":true,\"tag\":\"go/v0.1.15\"}", wire: "{\"performed\":true,\"tag\":\"go/v0.1.15\"}", + decode: func(b []byte) (any, error) { var v TagAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "tag answers an error, never none", subject: "", given: "{\"error\":\"the forge answered 403\",\"error-kind\":\"refused\"}", wire: "{\"error\":\"the forge answered 403\",\"error-kind\":\"refused\"}", + decode: func(b []byte) (any, error) { var v TagAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + } + for _, c := range cases { + v, err := c.decode([]byte(c.given)) + if err != nil { + t.Errorf("%s: does not decode: %v", c.name, err) + continue + } + back, err := json.Marshal(v) + if err != nil { + t.Errorf("%s: does not encode: %v", c.name, err) + continue + } + var got, want any + if err := json.Unmarshal(back, &got); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal([]byte(c.wire), &want); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire) + } + if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) { + t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs) + } + } +} diff --git a/contracts/go/seats/git/go.mod b/contracts/go/seats/git/go.mod new file mode 100644 index 00000000..1f36d290 --- /dev/null +++ b/contracts/go/seats/git/go.mod @@ -0,0 +1,3 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/seats/git + +go 1.26.0 diff --git a/contracts/go/seats/meshbuilds/contract.go b/contracts/go/seats/meshbuilds/contract.go new file mode 100644 index 00000000..76437fcc --- /dev/null +++ b/contracts/go/seats/meshbuilds/contract.go @@ -0,0 +1,121 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// Package meshbuilds is the contract of the seat "mesh-builds", defined whole (seats/mesh-builds/seat.json): The builder's seat: it says each artifact it published and each agreed toolchain, and answers a module's reserved version. Held once for the mesh (design 47 G section 52, section 307). +package meshbuilds + +import "time" + +// Seat is the seat's name. +const Seat = "mesh-builds" + +const ( + VerbReserved = "reserved" +) + +const ( + EventPackagePublished = "package-published" + EventToolchainAgreed = "toolchain-agreed" +) + +// Verbs is every verb the seat serves, in its definition's order: a new copy each call. +func Verbs() []string { + return []string{VerbReserved} +} + +// Events is every event published on the seat's subject: a new copy each call. +func Events() []string { + return []string{EventPackagePublished, EventToolchainAgreed} +} + +// Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. +const ( + Family = "mesh-builds.events" + FamilyMaxAgeDays = 7 +) + +// StreamPackagePublished is the stream package-published is kept in. +const StreamPackagePublished = "SEAT_mesh-builds_package-published" + +// SubjectPackagePublished is where package-published is published. +const SubjectPackagePublished = "mesh.seat.mesh-builds.event.package-published" + +// StreamToolchainAgreed is the stream toolchain-agreed is kept in. +const StreamToolchainAgreed = "SEAT_mesh-builds_toolchain-agreed" + +// SubjectToolchainAgreed is where toolchain-agreed is published. +const SubjectToolchainAgreed = "mesh.seat.mesh-builds.event.toolchain-agreed" + +// Dependency is a dependency the build resolved, named by the build module in its step key and carried by the builder (design 47 G section 374). +type Dependency struct { + // the dependency's artifact reference, : + Artifact string `json:"artifact"` + // the digest of the part, as the build used it + Digest string `json:"digest"` + // 1 an imported package, 3 the build toolchain (design 47 G section 374) + Kind int64 `json:"kind"` + // the version of the dependency the build resolved + Version string `json:"version"` +} + +// PackagePublished is one artifact published at the module's reserved version (design 47 G section 52, section 334). +type PackagePublished struct { + // the package's artifact reference, :, from the catalog entry + Artifact string `json:"artifact"` + At *time.Time `json:"at,omitempty"` + // the watched branch the build was made from + Branch string `json:"branch"` + // each dependency the build resolved, with its version and digest + Dependencies []Dependency `json:"dependencies,omitempty"` + // the published artifact's digest + Digest string `json:"digest"` + // the module + Module string `json:"module"` + // the source revision of that branch the build was made from + Source int64 `json:"source"` + V int64 `json:"v"` + // the version the build reserved, MAJOR.MINOR.PATCH + Version string `json:"version"` +} + +// ToolchainAgreed is the agreed toolchain of a language changed (design 47 G section 376, ADR 0584 section 1). +type ToolchainAgreed struct { + At *time.Time `json:"at,omitempty"` + // the toolchain part's digest + Digest string `json:"digest"` + // the language whose agreed toolchain changed + Language string `json:"language"` + V int64 `json:"v"` + // the agreed toolchain's version + Version string `json:"version"` +} + +// ReservedInput is what reserved takes. +type ReservedInput struct { + // the module + Module string `json:"module"` + // the version, when asked for a particular one + Version string `json:"version,omitempty"` +} + +// ReservedAnswerArtifactsItem is a type of the contract. +type ReservedAnswerArtifactsItem struct { + // the artifact reference, : + Artifact string `json:"artifact"` + // its digest once built; empty before + Digest string `json:"digest,omitempty"` +} + +// ReservedAnswer is what reserved answers. +type ReservedAnswer struct { + Artifacts []ReservedAnswerArtifactsItem `json:"artifacts"` + // the module + Module string `json:"module"` + // the reserved version, MAJOR.MINOR.PATCH + Version string `json:"version"` +} + +// PackagePublishedEvent is the body of the event package-published. +type PackagePublishedEvent = PackagePublished + +// ToolchainAgreedEvent is the body of the event toolchain-agreed. +type ToolchainAgreedEvent = ToolchainAgreed diff --git a/contracts/go/seats/meshbuilds/contract_test.go b/contracts/go/seats/meshbuilds/contract_test.go new file mode 100644 index 00000000..abbc1cf1 --- /dev/null +++ b/contracts/go/seats/meshbuilds/contract_test.go @@ -0,0 +1,54 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +package meshbuilds + +import ( + "encoding/json" + "reflect" + "regexp" + "testing" +) + +// TestFixtures reads every fixture of seats/mesh-builds/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 package published with the dependencies the build resolved", subject: "mesh.seat.mesh-builds.event.package-published", given: "{\"artifact\":\"mesh-sdk:go\",\"at\":\"2026-11-10T10:00:00Z\",\"branch\":\"main\",\"dependencies\":[{\"artifact\":\"mesh-contracts:go-nodeengine\",\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"kind\":1,\"version\":\"0.1.4\"},{\"artifact\":\"mesh-build-go:toolchain\",\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"kind\":3,\"version\":\"1.26.0\"}],\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"module\":\"mesh-sdk\",\"source\":3589,\"v\":1,\"version\":\"0.1.15\"}", wire: "{\"artifact\":\"mesh-sdk:go\",\"at\":\"2026-11-10T10:00:00Z\",\"branch\":\"main\",\"dependencies\":[{\"artifact\":\"mesh-contracts:go-nodeengine\",\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"kind\":1,\"version\":\"0.1.4\"},{\"artifact\":\"mesh-build-go:toolchain\",\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"kind\":3,\"version\":\"1.26.0\"}],\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"module\":\"mesh-sdk\",\"source\":3589,\"v\":1,\"version\":\"0.1.15\"}", + decode: func(b []byte) (any, error) { var v PackagePublishedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-builds\\.event\\.package-published$")}, + {name: "a toolchain agreed", subject: "mesh.seat.mesh-builds.event.toolchain-agreed", given: "{\"at\":\"2026-11-10T10:00:00Z\",\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"language\":\"go\",\"v\":1,\"version\":\"1.26.0\"}", wire: "{\"at\":\"2026-11-10T10:00:00Z\",\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"language\":\"go\",\"v\":1,\"version\":\"1.26.0\"}", + decode: func(b []byte) (any, error) { var v ToolchainAgreedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-builds\\.event\\.toolchain-agreed$")}, + {name: "reserved asks a module", subject: "", given: "{\"module\":\"mesh-sdk\"}", wire: "{\"module\":\"mesh-sdk\"}", + decode: func(b []byte) (any, error) { var v ReservedInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "reserved answers the version and each artifact's digest", subject: "", given: "{\"artifacts\":[{\"artifact\":\"mesh-sdk:go\",\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"},{\"artifact\":\"mesh-sdk:ts\"}],\"module\":\"mesh-sdk\",\"version\":\"0.1.15\"}", wire: "{\"artifacts\":[{\"artifact\":\"mesh-sdk:go\",\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"},{\"artifact\":\"mesh-sdk:ts\"}],\"module\":\"mesh-sdk\",\"version\":\"0.1.15\"}", + decode: func(b []byte) (any, error) { var v ReservedAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + } + for _, c := range cases { + v, err := c.decode([]byte(c.given)) + if err != nil { + t.Errorf("%s: does not decode: %v", c.name, err) + continue + } + back, err := json.Marshal(v) + if err != nil { + t.Errorf("%s: does not encode: %v", c.name, err) + continue + } + var got, want any + if err := json.Unmarshal(back, &got); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal([]byte(c.wire), &want); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire) + } + if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) { + t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs) + } + } +} diff --git a/contracts/go/seats/meshbuilds/go.mod b/contracts/go/seats/meshbuilds/go.mod new file mode 100644 index 00000000..784ddfa7 --- /dev/null +++ b/contracts/go/seats/meshbuilds/go.mod @@ -0,0 +1,3 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds + +go 1.26.0 diff --git a/contracts/go/seats/meshcatalog/contract.go b/contracts/go/seats/meshcatalog/contract.go index 32870c7c..1a6b43f6 100644 --- a/contracts/go/seats/meshcatalog/contract.go +++ b/contracts/go/seats/meshcatalog/contract.go @@ -20,6 +20,16 @@ const ( VerbProvides = "provides" VerbDependents = "dependents" VerbSeatDefinitions = "seat-definitions" + VerbRevision = "revision" + VerbRegistrations = "registrations" + VerbRegister = "register" + VerbPolicy = "policy" + VerbWatch = "watch" + VerbUnwatch = "unwatch" + VerbVersions = "versions" + VerbOutdated = "outdated" + VerbInput = "input" + VerbEntry = "entry" ) const ( @@ -27,16 +37,23 @@ const ( EventSeatDefinitionRemoved = "seat-definition-removed" EventManifestAccepted = "manifest-accepted" EventManifestRefused = "manifest-refused" + EventCatalogRevision = "catalog-revision" + EventFilling = "filling" + EventFull = "full" + EventRoom = "room" + EventRegistrationChanged = "registration-changed" + EventVersionReleased = "version-released" + EventVersionsFilled = "versions-filled" ) // 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} + return []string{VerbModules, VerbModule, VerbProvides, VerbDependents, VerbSeatDefinitions, VerbRevision, VerbRegistrations, VerbRegister, VerbPolicy, VerbWatch, VerbUnwatch, VerbVersions, VerbOutdated, VerbInput, VerbEntry} } // Events is every event published on the seat's subject: a new copy each call. func Events() []string { - return []string{EventSeatDefinitionChanged, EventSeatDefinitionRemoved, EventManifestAccepted, EventManifestRefused} + return []string{EventSeatDefinitionChanged, EventSeatDefinitionRemoved, EventManifestAccepted, EventManifestRefused, EventCatalogRevision, EventFilling, EventFull, EventRoom, EventRegistrationChanged, EventVersionReleased, EventVersionsFilled} } // Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. @@ -57,14 +74,55 @@ 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" +// SubjectCatalogRevision is where catalog-revision is published, on mesh.seat.mesh-catalog.event.catalog-revision.. +func SubjectCatalogRevision(branch string) string { + return "mesh.seat.mesh-catalog.event.catalog-revision." + branch +} + +// SubjectCatalogRevisionAll hears every catalog-revision. +const SubjectCatalogRevisionAll = "mesh.seat.mesh-catalog.event.catalog-revision.*" + +// SubjectFilling is where filling is published. +const SubjectFilling = "mesh.seat.mesh-catalog.event.filling" + +// SubjectFull is where full is published. +const SubjectFull = "mesh.seat.mesh-catalog.event.full" + +// SubjectRoom is where room is published. +const SubjectRoom = "mesh.seat.mesh-catalog.event.room" + +// StreamRegistrationChanged is the stream registration-changed is kept in. +const StreamRegistrationChanged = "SEAT_mesh-catalog_registration-changed" + +// SubjectRegistrationChanged is where registration-changed is published. +const SubjectRegistrationChanged = "mesh.seat.mesh-catalog.event.registration-changed" + +// StreamVersionReleased is the stream version-released is kept in. +const StreamVersionReleased = "SEAT_mesh-catalog_version-released" + +// SubjectVersionReleased is where version-released is published. +const SubjectVersionReleased = "mesh.seat.mesh-catalog.event.version-released" + +// StreamVersionsFilled is the stream versions-filled is kept in. +const StreamVersionsFilled = "SEAT_mesh-catalog_versions-filled" + +// SubjectVersionsFilled is where versions-filled is published. +const SubjectVersionsFilled = "mesh.seat.mesh-catalog.event.versions-filled" + const ( - StateManifests = "manifests" - StateModules = "modules" - StateCandidates = "candidates" - StateVerdicts = "verdicts" - StatePredefined = "predefined" - StateConfigCandidates = "config-candidates" - StatePackages = "packages" + StateManifests = "manifests" + StateModules = "modules" + StateCandidates = "candidates" + StateVerdicts = "verdicts" + StatePredefined = "predefined" + StateConfigCandidates = "config-candidates" + StatePackages = "packages" + StateCatalogRevisions = "catalog-revisions" + StateRegistrations = "registrations" + StateRegistrationHistory = "registration-history" + StateVersions = "versions" + StateBuildEdges = "build-edges" + StateInputs = "inputs" ) // ManifestsByDigestKey is the key of one value of the state manifests under the form "". @@ -319,6 +377,630 @@ func DecodePackagesByPackageSeriesValue(raw []byte) (PackagesByPackageSeriesValu return v, nil } +// CatalogRevisionsByBranchRevisionKey is the key of one value of the state catalog-revisions under the form ".". +func CatalogRevisionsByBranchRevisionKey(branch, revision string) string { + return branch + "." + revision +} + +// ParseCatalogRevisionsByBranchRevisionKey reads the parts of a key of the form ".", in the order of the form; the last result is false for a key of another form. +func ParseCatalogRevisionsByBranchRevisionKey(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 +} + +// DecodeCatalogRevisionsByBranchRevisionValue reads one value of the state catalog-revisions under the form ".", refusing a field CatalogRevisionsByBranchRevisionValue does not know and any text after the value. +func DecodeCatalogRevisionsByBranchRevisionValue(raw []byte) (CatalogRevisionsByBranchRevisionValue, error) { + var v CatalogRevisionsByBranchRevisionValue + 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 +} + +// CatalogRevisionsByCandidateGroupHeadsKey is the key of one value of the state catalog-revisions under the form "candidate..". +func CatalogRevisionsByCandidateGroupHeadsKey(group, heads string) string { + return "candidate." + group + "." + heads +} + +// ParseCatalogRevisionsByCandidateGroupHeadsKey reads the parts of a key of the form "candidate..", in the order of the form; the last result is false for a key of another form. +func ParseCatalogRevisionsByCandidateGroupHeadsKey(key string) (string, string, bool) { + rest := key + if !strings.HasPrefix(rest, "candidate.") { + return "", "", false + } + rest = rest[10:] + 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 +} + +// DecodeCatalogRevisionsByCandidateGroupHeadsValue reads one value of the state catalog-revisions under the form "candidate..", refusing a field CatalogRevisionsByCandidateGroupHeadsValue does not know and any text after the value. +func DecodeCatalogRevisionsByCandidateGroupHeadsValue(raw []byte) (CatalogRevisionsByCandidateGroupHeadsValue, error) { + var v CatalogRevisionsByCandidateGroupHeadsValue + 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 +} + +// CatalogRevisionsByEntryDigestKey is the key of one value of the state catalog-revisions under the form "entry.". +func CatalogRevisionsByEntryDigestKey(digest string) string { return "entry." + digest } + +// ParseCatalogRevisionsByEntryDigestKey reads the parts of a key of the form "entry.", in the order of the form; the last result is false for a key of another form. +func ParseCatalogRevisionsByEntryDigestKey(key string) (string, bool) { + rest := key + if !strings.HasPrefix(rest, "entry.") { + return "", false + } + rest = rest[6:] + if rest == "" { + return "", false + } + v1 := rest + rest = "" + if rest != "" { + return "", false + } + return v1, true +} + +// DecodeCatalogRevisionsByEntryDigestValue reads one value of the state catalog-revisions under the form "entry.", refusing a field CatalogRevisionsByEntryDigestValue does not know and any text after the value. +func DecodeCatalogRevisionsByEntryDigestValue(raw []byte) (CatalogRevisionsByEntryDigestValue, error) { + var v CatalogRevisionsByEntryDigestValue + 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 +} + +// RegistrationsByModuleKey is the key of one value of the state registrations under the form "". +func RegistrationsByModuleKey(module string) string { return module } + +// ParseRegistrationsByModuleKey reads the parts of a key of the form "", in the order of the form; the last result is false for a key of another form. +func ParseRegistrationsByModuleKey(key string) (string, bool) { + rest := key + if rest == "" { + return "", false + } + v0 := rest + rest = "" + if rest != "" { + return "", false + } + return v0, true +} + +// DecodeRegistrationsByModuleValue reads one value of the state registrations under the form "", refusing a field RegistrationsByModuleValue does not know and any text after the value. +func DecodeRegistrationsByModuleValue(raw []byte) (RegistrationsByModuleValue, error) { + var v RegistrationsByModuleValue + 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 +} + +// RegistrationHistoryByModuleVersionKey is the key of one value of the state registration-history under the form ".". +func RegistrationHistoryByModuleVersionKey(module, version string) string { + return module + "." + version +} + +// ParseRegistrationHistoryByModuleVersionKey reads the parts of a key of the form ".", in the order of the form; the last result is false for a key of another form. +func ParseRegistrationHistoryByModuleVersionKey(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 +} + +// DecodeRegistrationHistoryByModuleVersionValue reads one value of the state registration-history under the form ".", refusing a field RegistrationHistoryByModuleVersionValue does not know and any text after the value. +func DecodeRegistrationHistoryByModuleVersionValue(raw []byte) (RegistrationHistoryByModuleVersionValue, error) { + var v RegistrationHistoryByModuleVersionValue + 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 +} + +// VersionsByModuleVersionKey is the key of one value of the state versions under the form ".". +func VersionsByModuleVersionKey(module, version string) string { return module + "." + version } + +// ParseVersionsByModuleVersionKey reads the parts of a key of the form ".", in the order of the form; the last result is false for a key of another form. +func ParseVersionsByModuleVersionKey(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 +} + +// DecodeVersionsByModuleVersionValue reads one value of the state versions under the form ".", refusing a field VersionsByModuleVersionValue does not know and any text after the value. +func DecodeVersionsByModuleVersionValue(raw []byte) (VersionsByModuleVersionValue, error) { + var v VersionsByModuleVersionValue + 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 +} + +// BuildEdgesByModuleBranchKey is the key of one value of the state build-edges under the form ".". +func BuildEdgesByModuleBranchKey(module, branch string) string { return module + "." + branch } + +// ParseBuildEdgesByModuleBranchKey reads the parts of a key of the form ".", in the order of the form; the last result is false for a key of another form. +func ParseBuildEdgesByModuleBranchKey(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 +} + +// DecodeBuildEdgesByModuleBranchValue reads one value of the state build-edges under the form ".", refusing a field BuildEdgesByModuleBranchValue does not know and any text after the value. +func DecodeBuildEdgesByModuleBranchValue(raw []byte) (BuildEdgesByModuleBranchValue, error) { + var v BuildEdgesByModuleBranchValue + 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 +} + +// InputsByTimeDigestKey is the key of one value of the state inputs under the form "