diff --git a/contracts/go/seats/nodebuildgo/contract.go b/contracts/go/seats/nodebuildgo/contract.go new file mode 100644 index 00000000..59b72a28 --- /dev/null +++ b/contracts/go/seats/nodebuildgo/contract.go @@ -0,0 +1,447 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// Package nodebuildgo is the contract of the seat "node-build-go", defined whole (seats/node-build-go/seat.json): The Go build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers. +package nodebuildgo + +import ( + "bytes" + "encoding/json" + "errors" + "strings" +) + +// Seat is the seat's name. +const Seat = "node-build-go" + +const ( + VerbCurrent = "current" + VerbPause = "pause" + VerbResume = "resume" +) + +const ( + AcceptDelivery = "delivery" + AcceptCheck = "check" + AcceptRecheck = "recheck" +) + +// Verbs is every verb the seat serves, in its definition's order: a new copy each call. +func Verbs() []string { + return []string{VerbCurrent, VerbPause, VerbResume} +} + +// AcceptSubjectDelivery is where delivery is sent. +const AcceptSubjectDelivery = "mesh.seat.node-build-go.accept.delivery" + +// AcceptSubjectCheck is where check is sent. +const AcceptSubjectCheck = "mesh.seat.node-build-go.accept.check" + +// AcceptSubjectRecheck is where recheck is sent. +const AcceptSubjectRecheck = "mesh.seat.node-build-go.accept.recheck" + +const ( + StateSteps = "steps" + StatePauses = "pauses" + StateToolchains = "toolchains" +) + +// StepsByNodeIDKey is the key of one value of the state steps under the form ".". +func StepsByNodeIDKey(node, id string) string { return node + "." + id } + +// ParseStepsByNodeIDKey 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 ParseStepsByNodeIDKey(key string) (string, string, bool) { + rest := key + v0, after0, found0 := strings.Cut(rest, ".") + if !found0 || v0 == "" { + return "", "", false + } + rest = after0 + if rest == "" { + return "", "", false + } + v2 := rest + rest = "" + if rest != "" { + return "", "", false + } + return v0, v2, true +} + +// DecodeStepsByNodeIDValue reads one value of the state steps under the form ".", refusing a field StepsByNodeIDValue does not know and any text after the value. +func DecodeStepsByNodeIDValue(raw []byte) (StepsByNodeIDValue, error) { + var v StepsByNodeIDValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// PausesByPausedNodeKey is the key of one value of the state pauses under the form "paused.". +func PausesByPausedNodeKey(node string) string { return "paused." + node } + +// ParsePausesByPausedNodeKey reads the parts of a key of the form "paused.", in the order of the form; the last result is false for a key of another form. +func ParsePausesByPausedNodeKey(key string) (string, bool) { + rest := key + if !strings.HasPrefix(rest, "paused.") { + return "", false + } + rest = rest[7:] + if rest == "" { + return "", false + } + v1 := rest + rest = "" + if rest != "" { + return "", false + } + return v1, true +} + +// DecodePausesByPausedNodeValue reads one value of the state pauses under the form "paused.", refusing a field PausesByPausedNodeValue does not know and any text after the value. +func DecodePausesByPausedNodeValue(raw []byte) (PausesByPausedNodeValue, error) { + var v PausesByPausedNodeValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// PausesByCapacityNodeKey is the key of one value of the state pauses under the form "capacity.". +func PausesByCapacityNodeKey(node string) string { return "capacity." + node } + +// ParsePausesByCapacityNodeKey reads the parts of a key of the form "capacity.", in the order of the form; the last result is false for a key of another form. +func ParsePausesByCapacityNodeKey(key string) (string, bool) { + rest := key + if !strings.HasPrefix(rest, "capacity.") { + return "", false + } + rest = rest[9:] + if rest == "" { + return "", false + } + v1 := rest + rest = "" + if rest != "" { + return "", false + } + return v1, true +} + +// DecodePausesByCapacityNodeValue reads one value of the state pauses under the form "capacity.", refusing a field PausesByCapacityNodeValue does not know and any text after the value. +func DecodePausesByCapacityNodeValue(raw []byte) (PausesByCapacityNodeValue, error) { + var v PausesByCapacityNodeValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// ToolchainsByNodeLanguageKey is the key of one value of the state toolchains under the form ".". +func ToolchainsByNodeLanguageKey(node, language string) string { return node + "." + language } + +// ParseToolchainsByNodeLanguageKey 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 ParseToolchainsByNodeLanguageKey(key string) (string, string, bool) { + rest := key + v0, after0, found0 := strings.Cut(rest, ".") + if !found0 || v0 == "" { + return "", "", false + } + rest = after0 + if rest == "" { + return "", "", false + } + v2 := rest + rest = "" + if rest != "" { + return "", "", false + } + return v0, v2, true +} + +// DecodeToolchainsByNodeLanguageValue reads one value of the state toolchains under the form ".", refusing a field ToolchainsByNodeLanguageValue does not know and any text after the value. +func DecodeToolchainsByNodeLanguageValue(raw []byte) (ToolchainsByNodeLanguageValue, error) { + var v ToolchainsByNodeLanguageValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// BuildAnswerArtifactsItem is a type of the contract. +type BuildAnswerArtifactsItem struct { + Artifact string `json:"artifact"` + Digest string `json:"digest"` + Reference string `json:"reference,omitempty"` +} + +// BuildAnswerToolchain is a type of the contract. +type BuildAnswerToolchain struct { + Language string `json:"language"` + Recipe string `json:"recipe"` + Version string `json:"version"` +} + +// BuildAnswer is a type of the contract. +type BuildAnswer struct { + Artifacts []BuildAnswerArtifactsItem `json:"artifacts"` + Imports []string `json:"imports,omitempty"` + OutOfStep []string `json:"out-of-step,omitempty"` + Toolchain *BuildAnswerToolchain `json:"toolchain,omitempty"` +} + +// Capacity is how many steps the node runs at once. +type Capacity struct { + // when (RFC 3339 time) + At string `json:"at,omitempty"` + BuildHours string `json:"build-hours,omitempty"` + BuildHoursFault string `json:"build-hours-fault,omitempty"` + ByCpu int64 `json:"by-cpu,omitempty"` + Configured int64 `json:"configured"` + ConfiguredFrom string `json:"configured-from,omitempty"` + CoresPerStep float64 `json:"cores-per-step,omitempty"` + Effective int64 `json:"effective"` + Ineligible string `json:"ineligible,omitempty"` + OutsideHours bool `json:"outside-hours,omitempty"` + QuotaCores float64 `json:"quota-cores,omitempty"` + QuotaFrom string `json:"quota-from,omitempty"` + // the layout version + V int64 `json:"v,omitempty"` + Why string `json:"why"` +} + +// CheckAnswerLevelsItemDigestNeedsItem is a type of the contract. +type CheckAnswerLevelsItemDigestNeedsItem struct { + Needs []string `json:"needs,omitempty"` + Package string `json:"package,omitempty"` +} + +// CheckAnswerLevelsItemDigestPackagesItem is a type of the contract. +type CheckAnswerLevelsItemDigestPackagesItem struct { + Digest string `json:"digest"` + Package string `json:"package"` +} + +// CheckAnswerLevelsItemDigest is a type of the contract. +type CheckAnswerLevelsItemDigest struct { + Beside []string `json:"beside,omitempty"` + JudgedBy string `json:"judged_by,omitempty"` + Level string `json:"level,omitempty"` + Needs []CheckAnswerLevelsItemDigestNeedsItem `json:"needs,omitempty"` + Packages []CheckAnswerLevelsItemDigestPackagesItem `json:"packages,omitempty"` + WithoutTests []string `json:"without_tests,omitempty"` +} + +// CheckAnswerLevelsItemKeptItem is a type of the contract. +type CheckAnswerLevelsItemKeptItem struct { + Digest string `json:"digest,omitempty"` + Package string `json:"package,omitempty"` + Tests int64 `json:"tests,omitempty"` +} + +// CheckAnswerLevelsItemUnmetItem is a type of the contract. +type CheckAnswerLevelsItemUnmetItem struct { + Needs []string `json:"needs,omitempty"` + Package string `json:"package,omitempty"` +} + +// CheckAnswerLevelsItem is a type of the contract. +type CheckAnswerLevelsItem struct { + Digest *CheckAnswerLevelsItemDigest `json:"digest,omitempty"` + Judged string `json:"judged,omitempty"` + Kept []CheckAnswerLevelsItemKeptItem `json:"kept,omitempty"` + Level string `json:"level"` + Tests []any `json:"tests,omitempty"` + Unmet []CheckAnswerLevelsItemUnmetItem `json:"unmet,omitempty"` +} + +// CheckAnswer is a type of the contract. +type CheckAnswer struct { + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch"` + Levels []CheckAnswerLevelsItem `json:"levels,omitempty"` +} + +// ComposeAnswer is a type of the contract. +type ComposeAnswer struct { + Built bool `json:"built"` + Key string `json:"key,omitempty"` + // the compose-candidate verdict + Verdict any `json:"verdict,omitempty"` +} + +// Pause is whether the node takes no new ask. +type Pause struct { + // when (RFC 3339 time) + At string `json:"at,omitempty"` + By string `json:"by,omitempty"` + Paused bool `json:"paused"` + // the layout version + V int64 `json:"v,omitempty"` +} + +// Step is one step a build module runs on a node. +type Step struct { + // when (RFC 3339 time) + Ended string `json:"ended,omitempty"` + ID string `json:"id"` + Node string `json:"node"` + Owner string `json:"owner,omitempty"` + Phase string `json:"phase,omitempty"` + // when (RFC 3339 time) + PhaseAt string `json:"phase-at,omitempty"` + Reason string `json:"reason,omitempty"` + // what the step answered + Result any `json:"result,omitempty"` + // when (RFC 3339 time) + Started string `json:"started"` + // running, passed, failed or refused + State string `json:"state"` + Tool string `json:"tool"` + // the layout version + V int64 `json:"v,omitempty"` + What string `json:"what,omitempty"` +} + +// StepAskBuildArtifactsItem is a type of the contract. +type StepAskBuildArtifactsItem struct { + Artifact string `json:"artifact"` + From string `json:"from,omitempty"` + Seat string `json:"seat,omitempty"` + Version string `json:"version,omitempty"` +} + +// StepAskBuildToolchain is a type of the contract. +type StepAskBuildToolchain struct { + Language string `json:"language"` + Recipe string `json:"recipe"` + Version string `json:"version"` +} + +// StepAskBuild is a type of the contract. +type StepAskBuild struct { + Artifacts []StepAskBuildArtifactsItem `json:"artifacts"` + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch,omitempty"` + Fp string `json:"fp,omitempty"` + Module string `json:"module"` + Path string `json:"path,omitempty"` + Publish bool `json:"publish,omitempty"` + Toolchain *StepAskBuildToolchain `json:"toolchain,omitempty"` +} + +// StepAskCheck is a type of the contract. +type StepAskCheck struct { + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch"` + Kept bool `json:"kept,omitempty"` + Levels []string `json:"levels"` + Packages []string `json:"packages,omitempty"` + Run string `json:"run,omitempty"` + Runner string `json:"runner,omitempty"` + RunnerWhen string `json:"runner_when,omitempty"` +} + +// StepAskCompose is a type of the contract. +type StepAskCompose struct { + Dir string `json:"dir"` + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch"` + Package string `json:"package"` + Reader string `json:"reader,omitempty"` + // the compose-candidate request + Request any `json:"request"` +} + +// StepAsk is one ask of a build module. +type StepAsk struct { + Asker string `json:"asker"` + // when (RFC 3339 time) + At string `json:"at,omitempty"` + Build *StepAskBuild `json:"build,omitempty"` + Check *StepAskCheck `json:"check,omitempty"` + Compose *StepAskCompose `json:"compose,omitempty"` + ID string `json:"id"` + // build, check or compose + Kind string `json:"kind"` + // go, typescript or image + Language string `json:"language"` + Needs []string `json:"needs,omitempty"` + // package to the tagged version a build pins + Packages map[string]string `json:"packages,omitempty"` + // the layout version + V int64 `json:"v,omitempty"` +} + +// Toolchain is the toolchain a node reports. +type Toolchain struct { + // when (RFC 3339 time) + At string `json:"at,omitempty"` + // go or typescript + Language string `json:"language"` + Recipe string `json:"recipe"` + // the layout version + V int64 `json:"v,omitempty"` + Version string `json:"version"` +} + +// CurrentInput is what current takes. +type CurrentInput struct { +} + +// CurrentAnswer is what current answers. +type CurrentAnswer = map[string]any + +// PauseInput is what pause takes. +type PauseInput struct { +} + +// PauseAnswer is what pause answers. +type PauseAnswer = map[string]any + +// ResumeInput is what resume takes. +type ResumeInput struct { +} + +// ResumeAnswer is what resume answers. +type ResumeAnswer = map[string]any + +// DeliveryAccept is the body of delivery. +type DeliveryAccept = StepAsk + +// CheckAccept is the body of check. +type CheckAccept = StepAsk + +// RecheckAccept is the body of recheck. +type RecheckAccept = StepAsk + +// StepsByNodeIDValue is one value of the state steps under the key .. +type StepsByNodeIDValue = Step + +// PausesByPausedNodeValue is one value of the state pauses under the key paused.. +type PausesByPausedNodeValue = Pause + +// PausesByCapacityNodeValue is one value of the state pauses under the key capacity.. +type PausesByCapacityNodeValue = Capacity + +// ToolchainsByNodeLanguageValue is one value of the state toolchains under the key .. +type ToolchainsByNodeLanguageValue = Toolchain diff --git a/contracts/go/seats/nodebuildgo/contract_test.go b/contracts/go/seats/nodebuildgo/contract_test.go new file mode 100644 index 00000000..e7dd00cc --- /dev/null +++ b/contracts/go/seats/nodebuildgo/contract_test.go @@ -0,0 +1,68 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +package nodebuildgo + +import ( + "encoding/json" + "reflect" + "regexp" + "testing" +) + +// TestFixtures reads every fixture of seats/node-build-go/fixtures: each decodes into its type, a field the type does not +// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's. +func TestFixtures(t *testing.T) { + cases := []struct { + name, subject, given, wire string + decode func([]byte) (any, error) + subjectIs *regexp.Regexp + }{ + {name: "a running step", subject: "", given: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}", wire: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}", + decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a paused node", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}", + decode: func(b []byte) (any, error) { + var v PausesByPausedNodeValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, + {name: "a node's capacity", subject: "", given: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}", wire: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}", + decode: func(b []byte) (any, error) { + var v PausesByCapacityNodeValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, + {name: "a node's toolchain", subject: "", given: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}", wire: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}", + decode: func(b []byte) (any, error) { + var v ToolchainsByNodeLanguageValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, + {name: "a build ask", subject: "mesh.seat.node-build-go.accept.delivery", given: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", wire: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", + decode: func(b []byte) (any, error) { var v DeliveryAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.accept\\.delivery$")}, + } + 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/nodebuildgo/go.mod b/contracts/go/seats/nodebuildgo/go.mod new file mode 100644 index 00000000..7d786c14 --- /dev/null +++ b/contracts/go/seats/nodebuildgo/go.mod @@ -0,0 +1,3 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo + +go 1.26.0 diff --git a/contracts/go/seats/nodebuildimage/contract.go b/contracts/go/seats/nodebuildimage/contract.go new file mode 100644 index 00000000..843c04ae --- /dev/null +++ b/contracts/go/seats/nodebuildimage/contract.go @@ -0,0 +1,447 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// Package nodebuildimage is the contract of the seat "node-build-image", defined whole (seats/node-build-image/seat.json): The image build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers. +package nodebuildimage + +import ( + "bytes" + "encoding/json" + "errors" + "strings" +) + +// Seat is the seat's name. +const Seat = "node-build-image" + +const ( + VerbCurrent = "current" + VerbPause = "pause" + VerbResume = "resume" +) + +const ( + AcceptDelivery = "delivery" + AcceptCheck = "check" + AcceptRecheck = "recheck" +) + +// Verbs is every verb the seat serves, in its definition's order: a new copy each call. +func Verbs() []string { + return []string{VerbCurrent, VerbPause, VerbResume} +} + +// AcceptSubjectDelivery is where delivery is sent. +const AcceptSubjectDelivery = "mesh.seat.node-build-image.accept.delivery" + +// AcceptSubjectCheck is where check is sent. +const AcceptSubjectCheck = "mesh.seat.node-build-image.accept.check" + +// AcceptSubjectRecheck is where recheck is sent. +const AcceptSubjectRecheck = "mesh.seat.node-build-image.accept.recheck" + +const ( + StateSteps = "steps" + StatePauses = "pauses" + StateToolchains = "toolchains" +) + +// StepsByNodeIDKey is the key of one value of the state steps under the form ".". +func StepsByNodeIDKey(node, id string) string { return node + "." + id } + +// ParseStepsByNodeIDKey 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 ParseStepsByNodeIDKey(key string) (string, string, bool) { + rest := key + v0, after0, found0 := strings.Cut(rest, ".") + if !found0 || v0 == "" { + return "", "", false + } + rest = after0 + if rest == "" { + return "", "", false + } + v2 := rest + rest = "" + if rest != "" { + return "", "", false + } + return v0, v2, true +} + +// DecodeStepsByNodeIDValue reads one value of the state steps under the form ".", refusing a field StepsByNodeIDValue does not know and any text after the value. +func DecodeStepsByNodeIDValue(raw []byte) (StepsByNodeIDValue, error) { + var v StepsByNodeIDValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// PausesByPausedNodeKey is the key of one value of the state pauses under the form "paused.". +func PausesByPausedNodeKey(node string) string { return "paused." + node } + +// ParsePausesByPausedNodeKey reads the parts of a key of the form "paused.", in the order of the form; the last result is false for a key of another form. +func ParsePausesByPausedNodeKey(key string) (string, bool) { + rest := key + if !strings.HasPrefix(rest, "paused.") { + return "", false + } + rest = rest[7:] + if rest == "" { + return "", false + } + v1 := rest + rest = "" + if rest != "" { + return "", false + } + return v1, true +} + +// DecodePausesByPausedNodeValue reads one value of the state pauses under the form "paused.", refusing a field PausesByPausedNodeValue does not know and any text after the value. +func DecodePausesByPausedNodeValue(raw []byte) (PausesByPausedNodeValue, error) { + var v PausesByPausedNodeValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// PausesByCapacityNodeKey is the key of one value of the state pauses under the form "capacity.". +func PausesByCapacityNodeKey(node string) string { return "capacity." + node } + +// ParsePausesByCapacityNodeKey reads the parts of a key of the form "capacity.", in the order of the form; the last result is false for a key of another form. +func ParsePausesByCapacityNodeKey(key string) (string, bool) { + rest := key + if !strings.HasPrefix(rest, "capacity.") { + return "", false + } + rest = rest[9:] + if rest == "" { + return "", false + } + v1 := rest + rest = "" + if rest != "" { + return "", false + } + return v1, true +} + +// DecodePausesByCapacityNodeValue reads one value of the state pauses under the form "capacity.", refusing a field PausesByCapacityNodeValue does not know and any text after the value. +func DecodePausesByCapacityNodeValue(raw []byte) (PausesByCapacityNodeValue, error) { + var v PausesByCapacityNodeValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// ToolchainsByNodeLanguageKey is the key of one value of the state toolchains under the form ".". +func ToolchainsByNodeLanguageKey(node, language string) string { return node + "." + language } + +// ParseToolchainsByNodeLanguageKey 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 ParseToolchainsByNodeLanguageKey(key string) (string, string, bool) { + rest := key + v0, after0, found0 := strings.Cut(rest, ".") + if !found0 || v0 == "" { + return "", "", false + } + rest = after0 + if rest == "" { + return "", "", false + } + v2 := rest + rest = "" + if rest != "" { + return "", "", false + } + return v0, v2, true +} + +// DecodeToolchainsByNodeLanguageValue reads one value of the state toolchains under the form ".", refusing a field ToolchainsByNodeLanguageValue does not know and any text after the value. +func DecodeToolchainsByNodeLanguageValue(raw []byte) (ToolchainsByNodeLanguageValue, error) { + var v ToolchainsByNodeLanguageValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// BuildAnswerArtifactsItem is a type of the contract. +type BuildAnswerArtifactsItem struct { + Artifact string `json:"artifact"` + Digest string `json:"digest"` + Reference string `json:"reference,omitempty"` +} + +// BuildAnswerToolchain is a type of the contract. +type BuildAnswerToolchain struct { + Language string `json:"language"` + Recipe string `json:"recipe"` + Version string `json:"version"` +} + +// BuildAnswer is a type of the contract. +type BuildAnswer struct { + Artifacts []BuildAnswerArtifactsItem `json:"artifacts"` + Imports []string `json:"imports,omitempty"` + OutOfStep []string `json:"out-of-step,omitempty"` + Toolchain *BuildAnswerToolchain `json:"toolchain,omitempty"` +} + +// Capacity is how many steps the node runs at once. +type Capacity struct { + // when (RFC 3339 time) + At string `json:"at,omitempty"` + BuildHours string `json:"build-hours,omitempty"` + BuildHoursFault string `json:"build-hours-fault,omitempty"` + ByCpu int64 `json:"by-cpu,omitempty"` + Configured int64 `json:"configured"` + ConfiguredFrom string `json:"configured-from,omitempty"` + CoresPerStep float64 `json:"cores-per-step,omitempty"` + Effective int64 `json:"effective"` + Ineligible string `json:"ineligible,omitempty"` + OutsideHours bool `json:"outside-hours,omitempty"` + QuotaCores float64 `json:"quota-cores,omitempty"` + QuotaFrom string `json:"quota-from,omitempty"` + // the layout version + V int64 `json:"v,omitempty"` + Why string `json:"why"` +} + +// CheckAnswerLevelsItemDigestNeedsItem is a type of the contract. +type CheckAnswerLevelsItemDigestNeedsItem struct { + Needs []string `json:"needs,omitempty"` + Package string `json:"package,omitempty"` +} + +// CheckAnswerLevelsItemDigestPackagesItem is a type of the contract. +type CheckAnswerLevelsItemDigestPackagesItem struct { + Digest string `json:"digest"` + Package string `json:"package"` +} + +// CheckAnswerLevelsItemDigest is a type of the contract. +type CheckAnswerLevelsItemDigest struct { + Beside []string `json:"beside,omitempty"` + JudgedBy string `json:"judged_by,omitempty"` + Level string `json:"level,omitempty"` + Needs []CheckAnswerLevelsItemDigestNeedsItem `json:"needs,omitempty"` + Packages []CheckAnswerLevelsItemDigestPackagesItem `json:"packages,omitempty"` + WithoutTests []string `json:"without_tests,omitempty"` +} + +// CheckAnswerLevelsItemKeptItem is a type of the contract. +type CheckAnswerLevelsItemKeptItem struct { + Digest string `json:"digest,omitempty"` + Package string `json:"package,omitempty"` + Tests int64 `json:"tests,omitempty"` +} + +// CheckAnswerLevelsItemUnmetItem is a type of the contract. +type CheckAnswerLevelsItemUnmetItem struct { + Needs []string `json:"needs,omitempty"` + Package string `json:"package,omitempty"` +} + +// CheckAnswerLevelsItem is a type of the contract. +type CheckAnswerLevelsItem struct { + Digest *CheckAnswerLevelsItemDigest `json:"digest,omitempty"` + Judged string `json:"judged,omitempty"` + Kept []CheckAnswerLevelsItemKeptItem `json:"kept,omitempty"` + Level string `json:"level"` + Tests []any `json:"tests,omitempty"` + Unmet []CheckAnswerLevelsItemUnmetItem `json:"unmet,omitempty"` +} + +// CheckAnswer is a type of the contract. +type CheckAnswer struct { + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch"` + Levels []CheckAnswerLevelsItem `json:"levels,omitempty"` +} + +// ComposeAnswer is a type of the contract. +type ComposeAnswer struct { + Built bool `json:"built"` + Key string `json:"key,omitempty"` + // the compose-candidate verdict + Verdict any `json:"verdict,omitempty"` +} + +// Pause is whether the node takes no new ask. +type Pause struct { + // when (RFC 3339 time) + At string `json:"at,omitempty"` + By string `json:"by,omitempty"` + Paused bool `json:"paused"` + // the layout version + V int64 `json:"v,omitempty"` +} + +// Step is one step a build module runs on a node. +type Step struct { + // when (RFC 3339 time) + Ended string `json:"ended,omitempty"` + ID string `json:"id"` + Node string `json:"node"` + Owner string `json:"owner,omitempty"` + Phase string `json:"phase,omitempty"` + // when (RFC 3339 time) + PhaseAt string `json:"phase-at,omitempty"` + Reason string `json:"reason,omitempty"` + // what the step answered + Result any `json:"result,omitempty"` + // when (RFC 3339 time) + Started string `json:"started"` + // running, passed, failed or refused + State string `json:"state"` + Tool string `json:"tool"` + // the layout version + V int64 `json:"v,omitempty"` + What string `json:"what,omitempty"` +} + +// StepAskBuildArtifactsItem is a type of the contract. +type StepAskBuildArtifactsItem struct { + Artifact string `json:"artifact"` + From string `json:"from,omitempty"` + Seat string `json:"seat,omitempty"` + Version string `json:"version,omitempty"` +} + +// StepAskBuildToolchain is a type of the contract. +type StepAskBuildToolchain struct { + Language string `json:"language"` + Recipe string `json:"recipe"` + Version string `json:"version"` +} + +// StepAskBuild is a type of the contract. +type StepAskBuild struct { + Artifacts []StepAskBuildArtifactsItem `json:"artifacts"` + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch,omitempty"` + Fp string `json:"fp,omitempty"` + Module string `json:"module"` + Path string `json:"path,omitempty"` + Publish bool `json:"publish,omitempty"` + Toolchain *StepAskBuildToolchain `json:"toolchain,omitempty"` +} + +// StepAskCheck is a type of the contract. +type StepAskCheck struct { + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch"` + Kept bool `json:"kept,omitempty"` + Levels []string `json:"levels"` + Packages []string `json:"packages,omitempty"` + Run string `json:"run,omitempty"` + Runner string `json:"runner,omitempty"` + RunnerWhen string `json:"runner_when,omitempty"` +} + +// StepAskCompose is a type of the contract. +type StepAskCompose struct { + Dir string `json:"dir"` + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch"` + Package string `json:"package"` + Reader string `json:"reader,omitempty"` + // the compose-candidate request + Request any `json:"request"` +} + +// StepAsk is one ask of a build module. +type StepAsk struct { + Asker string `json:"asker"` + // when (RFC 3339 time) + At string `json:"at,omitempty"` + Build *StepAskBuild `json:"build,omitempty"` + Check *StepAskCheck `json:"check,omitempty"` + Compose *StepAskCompose `json:"compose,omitempty"` + ID string `json:"id"` + // build, check or compose + Kind string `json:"kind"` + // go, typescript or image + Language string `json:"language"` + Needs []string `json:"needs,omitempty"` + // package to the tagged version a build pins + Packages map[string]string `json:"packages,omitempty"` + // the layout version + V int64 `json:"v,omitempty"` +} + +// Toolchain is the toolchain a node reports. +type Toolchain struct { + // when (RFC 3339 time) + At string `json:"at,omitempty"` + // go or typescript + Language string `json:"language"` + Recipe string `json:"recipe"` + // the layout version + V int64 `json:"v,omitempty"` + Version string `json:"version"` +} + +// CurrentInput is what current takes. +type CurrentInput struct { +} + +// CurrentAnswer is what current answers. +type CurrentAnswer = map[string]any + +// PauseInput is what pause takes. +type PauseInput struct { +} + +// PauseAnswer is what pause answers. +type PauseAnswer = map[string]any + +// ResumeInput is what resume takes. +type ResumeInput struct { +} + +// ResumeAnswer is what resume answers. +type ResumeAnswer = map[string]any + +// DeliveryAccept is the body of delivery. +type DeliveryAccept = StepAsk + +// CheckAccept is the body of check. +type CheckAccept = StepAsk + +// RecheckAccept is the body of recheck. +type RecheckAccept = StepAsk + +// StepsByNodeIDValue is one value of the state steps under the key .. +type StepsByNodeIDValue = Step + +// PausesByPausedNodeValue is one value of the state pauses under the key paused.. +type PausesByPausedNodeValue = Pause + +// PausesByCapacityNodeValue is one value of the state pauses under the key capacity.. +type PausesByCapacityNodeValue = Capacity + +// ToolchainsByNodeLanguageValue is one value of the state toolchains under the key .. +type ToolchainsByNodeLanguageValue = Toolchain diff --git a/contracts/go/seats/nodebuildimage/contract_test.go b/contracts/go/seats/nodebuildimage/contract_test.go new file mode 100644 index 00000000..75649a4a --- /dev/null +++ b/contracts/go/seats/nodebuildimage/contract_test.go @@ -0,0 +1,68 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +package nodebuildimage + +import ( + "encoding/json" + "reflect" + "regexp" + "testing" +) + +// TestFixtures reads every fixture of seats/node-build-image/fixtures: each decodes into its type, a field the type does not +// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's. +func TestFixtures(t *testing.T) { + cases := []struct { + name, subject, given, wire string + decode func([]byte) (any, error) + subjectIs *regexp.Regexp + }{ + {name: "a running step", subject: "", given: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}", wire: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}", + decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a paused node", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}", + decode: func(b []byte) (any, error) { + var v PausesByPausedNodeValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, + {name: "a node's capacity", subject: "", given: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}", wire: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}", + decode: func(b []byte) (any, error) { + var v PausesByCapacityNodeValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, + {name: "a node's toolchain", subject: "", given: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}", wire: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}", + decode: func(b []byte) (any, error) { + var v ToolchainsByNodeLanguageValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, + {name: "a build ask", subject: "mesh.seat.node-build-image.accept.delivery", given: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", wire: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", + decode: func(b []byte) (any, error) { var v DeliveryAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-image\\.accept\\.delivery$")}, + } + for _, c := range cases { + v, err := c.decode([]byte(c.given)) + if err != nil { + t.Errorf("%s: does not decode: %v", c.name, err) + continue + } + back, err := json.Marshal(v) + if err != nil { + t.Errorf("%s: does not encode: %v", c.name, err) + continue + } + var got, want any + if err := json.Unmarshal(back, &got); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal([]byte(c.wire), &want); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire) + } + if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) { + t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs) + } + } +} diff --git a/contracts/go/seats/nodebuildimage/go.mod b/contracts/go/seats/nodebuildimage/go.mod new file mode 100644 index 00000000..33cb3f5b --- /dev/null +++ b/contracts/go/seats/nodebuildimage/go.mod @@ -0,0 +1,3 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage + +go 1.26.0 diff --git a/contracts/go/seats/nodebuildnode/contract.go b/contracts/go/seats/nodebuildnode/contract.go new file mode 100644 index 00000000..8e3e0448 --- /dev/null +++ b/contracts/go/seats/nodebuildnode/contract.go @@ -0,0 +1,447 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// Package nodebuildnode is the contract of the seat "node-build-node", defined whole (seats/node-build-node/seat.json): The Node build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers. +package nodebuildnode + +import ( + "bytes" + "encoding/json" + "errors" + "strings" +) + +// Seat is the seat's name. +const Seat = "node-build-node" + +const ( + VerbCurrent = "current" + VerbPause = "pause" + VerbResume = "resume" +) + +const ( + AcceptDelivery = "delivery" + AcceptCheck = "check" + AcceptRecheck = "recheck" +) + +// Verbs is every verb the seat serves, in its definition's order: a new copy each call. +func Verbs() []string { + return []string{VerbCurrent, VerbPause, VerbResume} +} + +// AcceptSubjectDelivery is where delivery is sent. +const AcceptSubjectDelivery = "mesh.seat.node-build-node.accept.delivery" + +// AcceptSubjectCheck is where check is sent. +const AcceptSubjectCheck = "mesh.seat.node-build-node.accept.check" + +// AcceptSubjectRecheck is where recheck is sent. +const AcceptSubjectRecheck = "mesh.seat.node-build-node.accept.recheck" + +const ( + StateSteps = "steps" + StatePauses = "pauses" + StateToolchains = "toolchains" +) + +// StepsByNodeIDKey is the key of one value of the state steps under the form ".". +func StepsByNodeIDKey(node, id string) string { return node + "." + id } + +// ParseStepsByNodeIDKey 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 ParseStepsByNodeIDKey(key string) (string, string, bool) { + rest := key + v0, after0, found0 := strings.Cut(rest, ".") + if !found0 || v0 == "" { + return "", "", false + } + rest = after0 + if rest == "" { + return "", "", false + } + v2 := rest + rest = "" + if rest != "" { + return "", "", false + } + return v0, v2, true +} + +// DecodeStepsByNodeIDValue reads one value of the state steps under the form ".", refusing a field StepsByNodeIDValue does not know and any text after the value. +func DecodeStepsByNodeIDValue(raw []byte) (StepsByNodeIDValue, error) { + var v StepsByNodeIDValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// PausesByPausedNodeKey is the key of one value of the state pauses under the form "paused.". +func PausesByPausedNodeKey(node string) string { return "paused." + node } + +// ParsePausesByPausedNodeKey reads the parts of a key of the form "paused.", in the order of the form; the last result is false for a key of another form. +func ParsePausesByPausedNodeKey(key string) (string, bool) { + rest := key + if !strings.HasPrefix(rest, "paused.") { + return "", false + } + rest = rest[7:] + if rest == "" { + return "", false + } + v1 := rest + rest = "" + if rest != "" { + return "", false + } + return v1, true +} + +// DecodePausesByPausedNodeValue reads one value of the state pauses under the form "paused.", refusing a field PausesByPausedNodeValue does not know and any text after the value. +func DecodePausesByPausedNodeValue(raw []byte) (PausesByPausedNodeValue, error) { + var v PausesByPausedNodeValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// PausesByCapacityNodeKey is the key of one value of the state pauses under the form "capacity.". +func PausesByCapacityNodeKey(node string) string { return "capacity." + node } + +// ParsePausesByCapacityNodeKey reads the parts of a key of the form "capacity.", in the order of the form; the last result is false for a key of another form. +func ParsePausesByCapacityNodeKey(key string) (string, bool) { + rest := key + if !strings.HasPrefix(rest, "capacity.") { + return "", false + } + rest = rest[9:] + if rest == "" { + return "", false + } + v1 := rest + rest = "" + if rest != "" { + return "", false + } + return v1, true +} + +// DecodePausesByCapacityNodeValue reads one value of the state pauses under the form "capacity.", refusing a field PausesByCapacityNodeValue does not know and any text after the value. +func DecodePausesByCapacityNodeValue(raw []byte) (PausesByCapacityNodeValue, error) { + var v PausesByCapacityNodeValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// ToolchainsByNodeLanguageKey is the key of one value of the state toolchains under the form ".". +func ToolchainsByNodeLanguageKey(node, language string) string { return node + "." + language } + +// ParseToolchainsByNodeLanguageKey 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 ParseToolchainsByNodeLanguageKey(key string) (string, string, bool) { + rest := key + v0, after0, found0 := strings.Cut(rest, ".") + if !found0 || v0 == "" { + return "", "", false + } + rest = after0 + if rest == "" { + return "", "", false + } + v2 := rest + rest = "" + if rest != "" { + return "", "", false + } + return v0, v2, true +} + +// DecodeToolchainsByNodeLanguageValue reads one value of the state toolchains under the form ".", refusing a field ToolchainsByNodeLanguageValue does not know and any text after the value. +func DecodeToolchainsByNodeLanguageValue(raw []byte) (ToolchainsByNodeLanguageValue, error) { + var v ToolchainsByNodeLanguageValue + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.DisallowUnknownFields() + if err := dec.Decode(&v); err != nil { + return v, err + } + if dec.More() { + return v, errors.New("text after the value") + } + return v, nil +} + +// BuildAnswerArtifactsItem is a type of the contract. +type BuildAnswerArtifactsItem struct { + Artifact string `json:"artifact"` + Digest string `json:"digest"` + Reference string `json:"reference,omitempty"` +} + +// BuildAnswerToolchain is a type of the contract. +type BuildAnswerToolchain struct { + Language string `json:"language"` + Recipe string `json:"recipe"` + Version string `json:"version"` +} + +// BuildAnswer is a type of the contract. +type BuildAnswer struct { + Artifacts []BuildAnswerArtifactsItem `json:"artifacts"` + Imports []string `json:"imports,omitempty"` + OutOfStep []string `json:"out-of-step,omitempty"` + Toolchain *BuildAnswerToolchain `json:"toolchain,omitempty"` +} + +// Capacity is how many steps the node runs at once. +type Capacity struct { + // when (RFC 3339 time) + At string `json:"at,omitempty"` + BuildHours string `json:"build-hours,omitempty"` + BuildHoursFault string `json:"build-hours-fault,omitempty"` + ByCpu int64 `json:"by-cpu,omitempty"` + Configured int64 `json:"configured"` + ConfiguredFrom string `json:"configured-from,omitempty"` + CoresPerStep float64 `json:"cores-per-step,omitempty"` + Effective int64 `json:"effective"` + Ineligible string `json:"ineligible,omitempty"` + OutsideHours bool `json:"outside-hours,omitempty"` + QuotaCores float64 `json:"quota-cores,omitempty"` + QuotaFrom string `json:"quota-from,omitempty"` + // the layout version + V int64 `json:"v,omitempty"` + Why string `json:"why"` +} + +// CheckAnswerLevelsItemDigestNeedsItem is a type of the contract. +type CheckAnswerLevelsItemDigestNeedsItem struct { + Needs []string `json:"needs,omitempty"` + Package string `json:"package,omitempty"` +} + +// CheckAnswerLevelsItemDigestPackagesItem is a type of the contract. +type CheckAnswerLevelsItemDigestPackagesItem struct { + Digest string `json:"digest"` + Package string `json:"package"` +} + +// CheckAnswerLevelsItemDigest is a type of the contract. +type CheckAnswerLevelsItemDigest struct { + Beside []string `json:"beside,omitempty"` + JudgedBy string `json:"judged_by,omitempty"` + Level string `json:"level,omitempty"` + Needs []CheckAnswerLevelsItemDigestNeedsItem `json:"needs,omitempty"` + Packages []CheckAnswerLevelsItemDigestPackagesItem `json:"packages,omitempty"` + WithoutTests []string `json:"without_tests,omitempty"` +} + +// CheckAnswerLevelsItemKeptItem is a type of the contract. +type CheckAnswerLevelsItemKeptItem struct { + Digest string `json:"digest,omitempty"` + Package string `json:"package,omitempty"` + Tests int64 `json:"tests,omitempty"` +} + +// CheckAnswerLevelsItemUnmetItem is a type of the contract. +type CheckAnswerLevelsItemUnmetItem struct { + Needs []string `json:"needs,omitempty"` + Package string `json:"package,omitempty"` +} + +// CheckAnswerLevelsItem is a type of the contract. +type CheckAnswerLevelsItem struct { + Digest *CheckAnswerLevelsItemDigest `json:"digest,omitempty"` + Judged string `json:"judged,omitempty"` + Kept []CheckAnswerLevelsItemKeptItem `json:"kept,omitempty"` + Level string `json:"level"` + Tests []any `json:"tests,omitempty"` + Unmet []CheckAnswerLevelsItemUnmetItem `json:"unmet,omitempty"` +} + +// CheckAnswer is a type of the contract. +type CheckAnswer struct { + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch"` + Levels []CheckAnswerLevelsItem `json:"levels,omitempty"` +} + +// ComposeAnswer is a type of the contract. +type ComposeAnswer struct { + Built bool `json:"built"` + Key string `json:"key,omitempty"` + // the compose-candidate verdict + Verdict any `json:"verdict,omitempty"` +} + +// Pause is whether the node takes no new ask. +type Pause struct { + // when (RFC 3339 time) + At string `json:"at,omitempty"` + By string `json:"by,omitempty"` + Paused bool `json:"paused"` + // the layout version + V int64 `json:"v,omitempty"` +} + +// Step is one step a build module runs on a node. +type Step struct { + // when (RFC 3339 time) + Ended string `json:"ended,omitempty"` + ID string `json:"id"` + Node string `json:"node"` + Owner string `json:"owner,omitempty"` + Phase string `json:"phase,omitempty"` + // when (RFC 3339 time) + PhaseAt string `json:"phase-at,omitempty"` + Reason string `json:"reason,omitempty"` + // what the step answered + Result any `json:"result,omitempty"` + // when (RFC 3339 time) + Started string `json:"started"` + // running, passed, failed or refused + State string `json:"state"` + Tool string `json:"tool"` + // the layout version + V int64 `json:"v,omitempty"` + What string `json:"what,omitempty"` +} + +// StepAskBuildArtifactsItem is a type of the contract. +type StepAskBuildArtifactsItem struct { + Artifact string `json:"artifact"` + From string `json:"from,omitempty"` + Seat string `json:"seat,omitempty"` + Version string `json:"version,omitempty"` +} + +// StepAskBuildToolchain is a type of the contract. +type StepAskBuildToolchain struct { + Language string `json:"language"` + Recipe string `json:"recipe"` + Version string `json:"version"` +} + +// StepAskBuild is a type of the contract. +type StepAskBuild struct { + Artifacts []StepAskBuildArtifactsItem `json:"artifacts"` + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch,omitempty"` + Fp string `json:"fp,omitempty"` + Module string `json:"module"` + Path string `json:"path,omitempty"` + Publish bool `json:"publish,omitempty"` + Toolchain *StepAskBuildToolchain `json:"toolchain,omitempty"` +} + +// StepAskCheck is a type of the contract. +type StepAskCheck struct { + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch"` + Kept bool `json:"kept,omitempty"` + Levels []string `json:"levels"` + Packages []string `json:"packages,omitempty"` + Run string `json:"run,omitempty"` + Runner string `json:"runner,omitempty"` + RunnerWhen string `json:"runner_when,omitempty"` +} + +// StepAskCompose is a type of the contract. +type StepAskCompose struct { + Dir string `json:"dir"` + // what the build module fetches: the repository, commit and directory, as the indexer states it + Fetch any `json:"fetch"` + Package string `json:"package"` + Reader string `json:"reader,omitempty"` + // the compose-candidate request + Request any `json:"request"` +} + +// StepAsk is one ask of a build module. +type StepAsk struct { + Asker string `json:"asker"` + // when (RFC 3339 time) + At string `json:"at,omitempty"` + Build *StepAskBuild `json:"build,omitempty"` + Check *StepAskCheck `json:"check,omitempty"` + Compose *StepAskCompose `json:"compose,omitempty"` + ID string `json:"id"` + // build, check or compose + Kind string `json:"kind"` + // go, typescript or image + Language string `json:"language"` + Needs []string `json:"needs,omitempty"` + // package to the tagged version a build pins + Packages map[string]string `json:"packages,omitempty"` + // the layout version + V int64 `json:"v,omitempty"` +} + +// Toolchain is the toolchain a node reports. +type Toolchain struct { + // when (RFC 3339 time) + At string `json:"at,omitempty"` + // go or typescript + Language string `json:"language"` + Recipe string `json:"recipe"` + // the layout version + V int64 `json:"v,omitempty"` + Version string `json:"version"` +} + +// CurrentInput is what current takes. +type CurrentInput struct { +} + +// CurrentAnswer is what current answers. +type CurrentAnswer = map[string]any + +// PauseInput is what pause takes. +type PauseInput struct { +} + +// PauseAnswer is what pause answers. +type PauseAnswer = map[string]any + +// ResumeInput is what resume takes. +type ResumeInput struct { +} + +// ResumeAnswer is what resume answers. +type ResumeAnswer = map[string]any + +// DeliveryAccept is the body of delivery. +type DeliveryAccept = StepAsk + +// CheckAccept is the body of check. +type CheckAccept = StepAsk + +// RecheckAccept is the body of recheck. +type RecheckAccept = StepAsk + +// StepsByNodeIDValue is one value of the state steps under the key .. +type StepsByNodeIDValue = Step + +// PausesByPausedNodeValue is one value of the state pauses under the key paused.. +type PausesByPausedNodeValue = Pause + +// PausesByCapacityNodeValue is one value of the state pauses under the key capacity.. +type PausesByCapacityNodeValue = Capacity + +// ToolchainsByNodeLanguageValue is one value of the state toolchains under the key .. +type ToolchainsByNodeLanguageValue = Toolchain diff --git a/contracts/go/seats/nodebuildnode/contract_test.go b/contracts/go/seats/nodebuildnode/contract_test.go new file mode 100644 index 00000000..9bfd4c18 --- /dev/null +++ b/contracts/go/seats/nodebuildnode/contract_test.go @@ -0,0 +1,68 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +package nodebuildnode + +import ( + "encoding/json" + "reflect" + "regexp" + "testing" +) + +// TestFixtures reads every fixture of seats/node-build-node/fixtures: each decodes into its type, a field the type does not +// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's. +func TestFixtures(t *testing.T) { + cases := []struct { + name, subject, given, wire string + decode func([]byte) (any, error) + subjectIs *regexp.Regexp + }{ + {name: "a running step", subject: "", given: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}", wire: "{\"id\":\"a1b2\",\"node\":\"ace\",\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"running\",\"tool\":\"build\",\"v\":1}", + decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "a paused node", subject: "", given: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}", wire: "{\"at\":\"2026-11-05T10:00:00Z\",\"by\":\"operator@shanks\",\"paused\":true,\"v\":1}", + decode: func(b []byte) (any, error) { + var v PausesByPausedNodeValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, + {name: "a node's capacity", subject: "", given: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}", wire: "{\"configured\":2,\"effective\":2,\"v\":1,\"why\":\"the configured steps fit the cores\"}", + decode: func(b []byte) (any, error) { + var v PausesByCapacityNodeValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, + {name: "a node's toolchain", subject: "", given: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}", wire: "{\"language\":\"go\",\"recipe\":\"1\",\"v\":1,\"version\":\"1.26.0\"}", + decode: func(b []byte) (any, error) { + var v ToolchainsByNodeLanguageValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, + {name: "a build ask", subject: "mesh.seat.node-build-node.accept.delivery", given: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", wire: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", + decode: func(b []byte) (any, error) { var v DeliveryAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-node\\.accept\\.delivery$")}, + } + 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/nodebuildnode/go.mod b/contracts/go/seats/nodebuildnode/go.mod new file mode 100644 index 00000000..836a6dc8 --- /dev/null +++ b/contracts/go/seats/nodebuildnode/go.mod @@ -0,0 +1,3 @@ +module git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode + +go 1.26.0 diff --git a/contracts/module.json b/contracts/module.json index c2266d3b..113c2fc6 100644 --- a/contracts/module.json +++ b/contracts/module.json @@ -53,6 +53,36 @@ "series": "0.1", "generated-from": "." }, + { + "name": "go-nodebuildgo", + "kind": "package", + "language": "go", + "paths": [ + "go/seats/nodebuildgo" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "go-nodebuildimage", + "kind": "package", + "language": "go", + "paths": [ + "go/seats/nodebuildimage" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "go-nodebuildnode", + "kind": "package", + "language": "go", + "paths": [ + "go/seats/nodebuildnode" + ], + "series": "0.1", + "generated-from": "." + }, { "name": "go-npmpackageregistry", "kind": "package", @@ -159,6 +189,36 @@ "series": "0.1", "generated-from": "." }, + { + "name": "ts-nodebuildgo", + "kind": "package", + "language": "typescript", + "paths": [ + "ts/seats/nodebuildgo" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "ts-nodebuildimage", + "kind": "package", + "language": "typescript", + "paths": [ + "ts/seats/nodebuildimage" + ], + "series": "0.1", + "generated-from": "." + }, + { + "name": "ts-nodebuildnode", + "kind": "package", + "language": "typescript", + "paths": [ + "ts/seats/nodebuildnode" + ], + "series": "0.1", + "generated-from": "." + }, { "name": "ts-npmpackageregistry", "kind": "package", diff --git a/contracts/parts/build-step.json b/contracts/parts/build-step.json new file mode 100644 index 00000000..dfc809f6 --- /dev/null +++ b/contracts/parts/build-step.json @@ -0,0 +1,588 @@ +{ + "format": 1, + "part": "build-step", + "description": "What the three build seats share: the verbs that read and steer a node's builds, and the layouts of the asks they accept and the steps they keep. A seat lists its states against these (a part carries no states).", + "verbs": [ + { + "name": "current", + "description": "The asks this node runs now (each ask's id, kind, module and when it started), its build capacity, and whether it is paused, taking no new ask.", + "input": { + "type": "object", + "properties": {} + }, + "answer": { + "type": "object", + "description": "the steps running, the capacity and whether paused" + } + }, + { + "name": "pause", + "description": "Take no new ask on this node until resumed; an ask running here ends in its place. Kept across a restart of the build module. A capacity of 0 is the lasting way to keep a node from building.", + "input": { + "type": "object", + "properties": {} + }, + "answer": { + "type": "object", + "description": "whether the node is paused" + } + }, + { + "name": "resume", + "description": "Take asks again on this node.", + "input": { + "type": "object", + "properties": {} + }, + "answer": { + "type": "object", + "description": "whether the node is paused" + } + } + ], + "defs": { + "StepAsk": { + "type": "object", + "properties": { + "v": { + "type": "integer", + "description": "the layout version" + }, + "id": { + "type": "string" + }, + "kind": { + "type": "string", + "description": "build, check or compose" + }, + "language": { + "type": "string", + "description": "go, typescript or image" + }, + "asker": { + "type": "string" + }, + "at": { + "type": "string", + "description": "when (RFC 3339 time)" + }, + "build": { + "type": "object", + "properties": { + "module": { + "type": "string" + }, + "path": { + "type": "string" + }, + "fp": { + "type": "string" + }, + "fetch": { + "description": "what the build module fetches: the repository, commit and directory, as the indexer states it" + }, + "artifacts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "artifact": { + "type": "string" + }, + "from": { + "type": "string" + }, + "seat": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "artifact" + ] + } + }, + "publish": { + "type": "boolean" + }, + "toolchain": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "version": { + "type": "string" + }, + "recipe": { + "type": "string" + } + }, + "required": [ + "language", + "version", + "recipe" + ] + } + }, + "required": [ + "module", + "artifacts" + ] + }, + "check": { + "type": "object", + "properties": { + "fetch": { + "description": "what the build module fetches: the repository, commit and directory, as the indexer states it" + }, + "levels": { + "type": "array", + "items": { + "type": "string", + "description": "unit or integration" + } + }, + "kept": { + "type": "boolean" + }, + "runner": { + "type": "string" + }, + "runner_when": { + "type": "string" + }, + "run": { + "type": "string" + }, + "packages": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "fetch", + "levels" + ] + }, + "compose": { + "type": "object", + "properties": { + "fetch": { + "description": "what the build module fetches: the repository, commit and directory, as the indexer states it" + }, + "dir": { + "type": "string" + }, + "package": { + "type": "string" + }, + "request": { + "description": "the compose-candidate request" + }, + "reader": { + "type": "string" + } + }, + "required": [ + "fetch", + "dir", + "package", + "request" + ] + }, + "needs": { + "type": "array", + "items": { + "type": "string" + } + }, + "packages": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "package to the tagged version a build pins" + } + }, + "required": [ + "id", + "kind", + "language", + "asker" + ], + "description": "one ask of a build module" + }, + "BuildAnswer": { + "type": "object", + "properties": { + "artifacts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "artifact": { + "type": "string" + }, + "digest": { + "type": "string" + }, + "reference": { + "type": "string" + } + }, + "required": [ + "artifact", + "digest" + ] + } + }, + "toolchain": { + "type": "object", + "properties": { + "language": { + "type": "string" + }, + "version": { + "type": "string" + }, + "recipe": { + "type": "string" + } + }, + "required": [ + "language", + "version", + "recipe" + ] + }, + "imports": { + "type": "array", + "items": { + "type": "string" + } + }, + "out-of-step": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "artifacts" + ] + }, + "CheckAnswer": { + "type": "object", + "properties": { + "fetch": { + "description": "what the build module fetches: the repository, commit and directory, as the indexer states it" + }, + "levels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "level": { + "type": "string" + }, + "judged": { + "type": "string" + }, + "digest": { + "type": "object", + "properties": { + "level": { + "type": "string" + }, + "packages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "package": { + "type": "string" + }, + "digest": { + "type": "string" + } + }, + "required": [ + "package", + "digest" + ] + } + }, + "beside": { + "type": "array", + "items": { + "type": "string" + } + }, + "without_tests": { + "type": "array", + "items": { + "type": "string" + } + }, + "judged_by": { + "type": "string" + }, + "needs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "package": { + "type": "string" + }, + "needs": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "kept": { + "type": "array", + "items": { + "type": "object", + "properties": { + "package": { + "type": "string" + }, + "digest": { + "type": "string" + }, + "tests": { + "type": "integer" + } + } + } + }, + "tests": { + "type": "array", + "items": { + "description": "one test's result" + } + }, + "unmet": { + "type": "array", + "items": { + "type": "object", + "properties": { + "package": { + "type": "string" + }, + "needs": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "required": [ + "level" + ] + } + } + }, + "required": [ + "fetch" + ] + }, + "ComposeAnswer": { + "type": "object", + "properties": { + "built": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "verdict": { + "description": "the compose-candidate verdict" + } + }, + "required": [ + "built" + ] + }, + "Step": { + "type": "object", + "properties": { + "v": { + "type": "integer", + "description": "the layout version" + }, + "id": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "node": { + "type": "string" + }, + "state": { + "type": "string", + "description": "running, passed, failed or refused" + }, + "started": { + "type": "string", + "description": "when (RFC 3339 time)" + }, + "ended": { + "type": "string", + "description": "when (RFC 3339 time)" + }, + "reason": { + "type": "string" + }, + "result": { + "description": "what the step answered" + }, + "what": { + "type": "string" + }, + "phase": { + "type": "string" + }, + "phase-at": { + "type": "string", + "description": "when (RFC 3339 time)" + }, + "owner": { + "type": "string" + } + }, + "required": [ + "id", + "tool", + "node", + "state", + "started" + ], + "description": "one step a build module runs on a node" + }, + "Toolchain": { + "type": "object", + "properties": { + "v": { + "type": "integer", + "description": "the layout version" + }, + "language": { + "type": "string", + "description": "go or typescript" + }, + "version": { + "type": "string" + }, + "recipe": { + "type": "string" + }, + "at": { + "type": "string", + "description": "when (RFC 3339 time)" + } + }, + "required": [ + "language", + "version", + "recipe" + ], + "description": "the toolchain a node reports" + }, + "Pause": { + "type": "object", + "properties": { + "v": { + "type": "integer", + "description": "the layout version" + }, + "paused": { + "type": "boolean" + }, + "by": { + "type": "string" + }, + "at": { + "type": "string", + "description": "when (RFC 3339 time)" + } + }, + "required": [ + "paused" + ], + "description": "whether the node takes no new ask" + }, + "Capacity": { + "type": "object", + "properties": { + "v": { + "type": "integer", + "description": "the layout version" + }, + "effective": { + "type": "integer" + }, + "configured": { + "type": "integer" + }, + "configured-from": { + "type": "string" + }, + "quota-cores": { + "type": "number" + }, + "quota-from": { + "type": "string" + }, + "cores-per-step": { + "type": "number" + }, + "by-cpu": { + "type": "integer" + }, + "build-hours": { + "type": "string" + }, + "outside-hours": { + "type": "boolean" + }, + "build-hours-fault": { + "type": "string" + }, + "ineligible": { + "type": "string" + }, + "why": { + "type": "string" + }, + "at": { + "type": "string", + "description": "when (RFC 3339 time)" + } + }, + "required": [ + "effective", + "configured", + "why" + ], + "description": "how many steps the node runs at once" + } + } +} diff --git a/contracts/seats/node-build-go/fixtures/states.json b/contracts/seats/node-build-go/fixtures/states.json new file mode 100644 index 00000000..1988c3d4 --- /dev/null +++ b/contracts/seats/node-build-go/fixtures/states.json @@ -0,0 +1,63 @@ +[ + { + "name": "a running step", + "of": "state:steps/.", + "given": { + "v": 1, + "id": "a1b2", + "tool": "build", + "node": "ace", + "state": "running", + "started": "2026-11-05T10:00:00Z" + } + }, + { + "name": "a paused node", + "of": "state:pauses/paused.", + "given": { + "v": 1, + "paused": true, + "by": "operator@shanks", + "at": "2026-11-05T10:00:00Z" + } + }, + { + "name": "a node's capacity", + "of": "state:pauses/capacity.", + "given": { + "v": 1, + "effective": 2, + "configured": 2, + "why": "the configured steps fit the cores" + } + }, + { + "name": "a node's toolchain", + "of": "state:toolchains/.", + "given": { + "v": 1, + "language": "go", + "version": "1.26.0", + "recipe": "1" + } + }, + { + "name": "a build ask", + "of": "accept:delivery", + "subject": "mesh.seat.node-build-go.accept.delivery", + "given": { + "id": "a1b2", + "kind": "build", + "language": "go", + "asker": "mesh-delivery-builder", + "build": { + "module": "nats", + "artifacts": [ + { + "artifact": "go" + } + ] + } + } + } +] diff --git a/contracts/seats/node-build-go/seat.json b/contracts/seats/node-build-go/seat.json new file mode 100644 index 00000000..ff273bdb --- /dev/null +++ b/contracts/seats/node-build-go/seat.json @@ -0,0 +1,87 @@ +{ + "format": 1, + "seat": "node-build-go", + "scope": "node", + "grain": "whole", + "decision": "novox/hq ADR 0525, ADR 0536, ADR 0553", + "pulled-by-kind": true, + "yields": true, + "accepts": [ + { + "name": "delivery", + "body": { + "$ref": "#/defs/StepAsk" + } + }, + { + "name": "check", + "body": { + "$ref": "#/defs/StepAsk" + } + }, + { + "name": "recheck", + "body": { + "$ref": "#/defs/StepAsk" + } + } + ], + "description": "The Go build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers.", + "includes": [ + "build-step" + ], + "states": [ + { + "name": "steps", + "template": "state", + "key-forms": [ + { + "key": ".", + "value": { + "$ref": "#/defs/Step" + }, + "description": "a step on a node" + } + ], + "description": "the steps this module runs", + "history": 5 + }, + { + "name": "pauses", + "template": "state", + "key-forms": [ + { + "key": "paused.", + "value": { + "$ref": "#/defs/Pause" + }, + "description": "whether a node is paused" + }, + { + "key": "capacity.", + "value": { + "$ref": "#/defs/Capacity" + }, + "description": "a node's capacity" + } + ], + "description": "the pause and the capacity of each node", + "history": 1 + }, + { + "name": "toolchains", + "template": "state", + "key-forms": [ + { + "key": ".", + "value": { + "$ref": "#/defs/Toolchain" + }, + "description": "the toolchain a node reports" + } + ], + "description": "the toolchains the nodes report", + "history": 1 + } + ] +} diff --git a/contracts/seats/node-build-image/fixtures/states.json b/contracts/seats/node-build-image/fixtures/states.json new file mode 100644 index 00000000..b49e8c53 --- /dev/null +++ b/contracts/seats/node-build-image/fixtures/states.json @@ -0,0 +1,63 @@ +[ + { + "name": "a running step", + "of": "state:steps/.", + "given": { + "v": 1, + "id": "a1b2", + "tool": "build", + "node": "ace", + "state": "running", + "started": "2026-11-05T10:00:00Z" + } + }, + { + "name": "a paused node", + "of": "state:pauses/paused.", + "given": { + "v": 1, + "paused": true, + "by": "operator@shanks", + "at": "2026-11-05T10:00:00Z" + } + }, + { + "name": "a node's capacity", + "of": "state:pauses/capacity.", + "given": { + "v": 1, + "effective": 2, + "configured": 2, + "why": "the configured steps fit the cores" + } + }, + { + "name": "a node's toolchain", + "of": "state:toolchains/.", + "given": { + "v": 1, + "language": "go", + "version": "1.26.0", + "recipe": "1" + } + }, + { + "name": "a build ask", + "of": "accept:delivery", + "subject": "mesh.seat.node-build-image.accept.delivery", + "given": { + "id": "a1b2", + "kind": "build", + "language": "go", + "asker": "mesh-delivery-builder", + "build": { + "module": "nats", + "artifacts": [ + { + "artifact": "go" + } + ] + } + } + } +] diff --git a/contracts/seats/node-build-image/seat.json b/contracts/seats/node-build-image/seat.json new file mode 100644 index 00000000..f0ca605b --- /dev/null +++ b/contracts/seats/node-build-image/seat.json @@ -0,0 +1,87 @@ +{ + "format": 1, + "seat": "node-build-image", + "scope": "node", + "grain": "whole", + "decision": "novox/hq ADR 0525, ADR 0536, ADR 0553", + "pulled-by-kind": true, + "yields": true, + "accepts": [ + { + "name": "delivery", + "body": { + "$ref": "#/defs/StepAsk" + } + }, + { + "name": "check", + "body": { + "$ref": "#/defs/StepAsk" + } + }, + { + "name": "recheck", + "body": { + "$ref": "#/defs/StepAsk" + } + } + ], + "description": "The image build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers.", + "includes": [ + "build-step" + ], + "states": [ + { + "name": "steps", + "template": "state", + "key-forms": [ + { + "key": ".", + "value": { + "$ref": "#/defs/Step" + }, + "description": "a step on a node" + } + ], + "description": "the steps this module runs", + "history": 5 + }, + { + "name": "pauses", + "template": "state", + "key-forms": [ + { + "key": "paused.", + "value": { + "$ref": "#/defs/Pause" + }, + "description": "whether a node is paused" + }, + { + "key": "capacity.", + "value": { + "$ref": "#/defs/Capacity" + }, + "description": "a node's capacity" + } + ], + "description": "the pause and the capacity of each node", + "history": 1 + }, + { + "name": "toolchains", + "template": "state", + "key-forms": [ + { + "key": ".", + "value": { + "$ref": "#/defs/Toolchain" + }, + "description": "the toolchain a node reports" + } + ], + "description": "the toolchains the nodes report", + "history": 1 + } + ] +} diff --git a/contracts/seats/node-build-node/fixtures/states.json b/contracts/seats/node-build-node/fixtures/states.json new file mode 100644 index 00000000..9a80337a --- /dev/null +++ b/contracts/seats/node-build-node/fixtures/states.json @@ -0,0 +1,63 @@ +[ + { + "name": "a running step", + "of": "state:steps/.", + "given": { + "v": 1, + "id": "a1b2", + "tool": "build", + "node": "ace", + "state": "running", + "started": "2026-11-05T10:00:00Z" + } + }, + { + "name": "a paused node", + "of": "state:pauses/paused.", + "given": { + "v": 1, + "paused": true, + "by": "operator@shanks", + "at": "2026-11-05T10:00:00Z" + } + }, + { + "name": "a node's capacity", + "of": "state:pauses/capacity.", + "given": { + "v": 1, + "effective": 2, + "configured": 2, + "why": "the configured steps fit the cores" + } + }, + { + "name": "a node's toolchain", + "of": "state:toolchains/.", + "given": { + "v": 1, + "language": "go", + "version": "1.26.0", + "recipe": "1" + } + }, + { + "name": "a build ask", + "of": "accept:delivery", + "subject": "mesh.seat.node-build-node.accept.delivery", + "given": { + "id": "a1b2", + "kind": "build", + "language": "go", + "asker": "mesh-delivery-builder", + "build": { + "module": "nats", + "artifacts": [ + { + "artifact": "go" + } + ] + } + } + } +] diff --git a/contracts/seats/node-build-node/seat.json b/contracts/seats/node-build-node/seat.json new file mode 100644 index 00000000..34ee7310 --- /dev/null +++ b/contracts/seats/node-build-node/seat.json @@ -0,0 +1,87 @@ +{ + "format": 1, + "seat": "node-build-node", + "scope": "node", + "grain": "whole", + "decision": "novox/hq ADR 0525, ADR 0536, ADR 0553", + "pulled-by-kind": true, + "yields": true, + "accepts": [ + { + "name": "delivery", + "body": { + "$ref": "#/defs/StepAsk" + } + }, + { + "name": "check", + "body": { + "$ref": "#/defs/StepAsk" + } + }, + { + "name": "recheck", + "body": { + "$ref": "#/defs/StepAsk" + } + } + ], + "description": "The Node build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers.", + "includes": [ + "build-step" + ], + "states": [ + { + "name": "steps", + "template": "state", + "key-forms": [ + { + "key": ".", + "value": { + "$ref": "#/defs/Step" + }, + "description": "a step on a node" + } + ], + "description": "the steps this module runs", + "history": 5 + }, + { + "name": "pauses", + "template": "state", + "key-forms": [ + { + "key": "paused.", + "value": { + "$ref": "#/defs/Pause" + }, + "description": "whether a node is paused" + }, + { + "key": "capacity.", + "value": { + "$ref": "#/defs/Capacity" + }, + "description": "a node's capacity" + } + ], + "description": "the pause and the capacity of each node", + "history": 1 + }, + { + "name": "toolchains", + "template": "state", + "key-forms": [ + { + "key": ".", + "value": { + "$ref": "#/defs/Toolchain" + }, + "description": "the toolchain a node reports" + } + ], + "description": "the toolchains the nodes report", + "history": 1 + } + ] +} diff --git a/contracts/ts/seats/nodebuildgo/package-lock.json b/contracts/ts/seats/nodebuildgo/package-lock.json new file mode 100644 index 00000000..bc0af20e --- /dev/null +++ b/contracts/ts/seats/nodebuildgo/package-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 3, + "name": "@novox/mesh-delivery-contracts-node-build-go", + "packages": { + "": { + "name": "@novox/mesh-delivery-contracts-node-build-go", + "version": "0.1.0" + } + }, + "requires": true, + "version": "0.1.0" +} diff --git a/contracts/ts/seats/nodebuildgo/package.json b/contracts/ts/seats/nodebuildgo/package.json new file mode 100644 index 00000000..074cc833 --- /dev/null +++ b/contracts/ts/seats/nodebuildgo/package.json @@ -0,0 +1,13 @@ +{ + "description": "The contract of the seat node-build-go, generated from its definition.", + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src" + ], + "name": "@novox/mesh-delivery-contracts-node-build-go", + "type": "module", + "types": "./src/index.ts", + "version": "0.1.0" +} diff --git a/contracts/ts/seats/nodebuildgo/src/index.ts b/contracts/ts/seats/nodebuildgo/src/index.ts new file mode 100644 index 00000000..24fa95b4 --- /dev/null +++ b/contracts/ts/seats/nodebuildgo/src/index.ts @@ -0,0 +1,153 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The contract of the seat "node-build-go", defined whole (seats/node-build-go/seat.json): The Go build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers. + +import type * as buildStep from "./parts/build-step.js"; + +export const seat = "node-build-go"; + +export const verbs = { + current: "current", + pause: "pause", + resume: "resume", +} as const; + +export const accepts = { + delivery: "delivery", + check: "check", + recheck: "recheck", +} as const; + +export const deliverySubject = "mesh.seat.node-build-go.accept.delivery"; + +export const checkSubject = "mesh.seat.node-build-go.accept.check"; + +export const recheckSubject = "mesh.seat.node-build-go.accept.recheck"; + +export const states = { + steps: "steps", + pauses: "pauses", + toolchains: "toolchains", +} as const; + +/** The key of one value of the state steps under the form ".". */ +export function stepsByNodeIDKey(node: string, id: string): string { + return node + "." + id; +} + +/** The parts of a key of the form ".", or undefined for a key of another form. */ +export function parseStepsByNodeIDKey(key: string): { node: string; id: string } | undefined { + let rest = key; + const at0 = rest.indexOf("."); + if (at0 <= 0) return undefined; + const node = rest.slice(0, at0); + rest = rest.slice(at0 + 1); + if (rest === "") return undefined; + const id = rest; + rest = ""; + if (rest !== "") return undefined; + return { node, id }; +} + +/** The key of one value of the state pauses under the form "paused.". */ +export function pausesByPausedNodeKey(node: string): string { + return "paused." + node; +} + +/** The parts of a key of the form "paused.", or undefined for a key of another form. */ +export function parsePausesByPausedNodeKey(key: string): { node: string } | undefined { + let rest = key; + if (!rest.startsWith("paused.")) return undefined; + rest = rest.slice(7); + if (rest === "") return undefined; + const node = rest; + rest = ""; + if (rest !== "") return undefined; + return { node }; +} + +/** The key of one value of the state pauses under the form "capacity.". */ +export function pausesByCapacityNodeKey(node: string): string { + return "capacity." + node; +} + +/** The parts of a key of the form "capacity.", or undefined for a key of another form. */ +export function parsePausesByCapacityNodeKey(key: string): { node: string } | undefined { + let rest = key; + if (!rest.startsWith("capacity.")) return undefined; + rest = rest.slice(9); + if (rest === "") return undefined; + const node = rest; + rest = ""; + if (rest !== "") return undefined; + return { node }; +} + +/** The key of one value of the state toolchains under the form ".". */ +export function toolchainsByNodeLanguageKey(node: string, language: string): string { + return node + "." + language; +} + +/** The parts of a key of the form ".", or undefined for a key of another form. */ +export function parseToolchainsByNodeLanguageKey(key: string): { node: string; language: string } | undefined { + let rest = key; + const at0 = rest.indexOf("."); + if (at0 <= 0) return undefined; + const node = rest.slice(0, at0); + rest = rest.slice(at0 + 1); + if (rest === "") return undefined; + const language = rest; + rest = ""; + if (rest !== "") return undefined; + return { node, language }; +} + +export type BuildAnswer = buildStep.BuildAnswer; +export type BuildAnswerArtifactsItem = buildStep.BuildAnswerArtifactsItem; +export type BuildAnswerToolchain = buildStep.BuildAnswerToolchain; +export type Capacity = buildStep.Capacity; +export type CheckAnswer = buildStep.CheckAnswer; +export type CheckAnswerLevelsItem = buildStep.CheckAnswerLevelsItem; +export type CheckAnswerLevelsItemDigest = buildStep.CheckAnswerLevelsItemDigest; +export type CheckAnswerLevelsItemDigestNeedsItem = buildStep.CheckAnswerLevelsItemDigestNeedsItem; +export type CheckAnswerLevelsItemDigestPackagesItem = buildStep.CheckAnswerLevelsItemDigestPackagesItem; +export type CheckAnswerLevelsItemKeptItem = buildStep.CheckAnswerLevelsItemKeptItem; +export type CheckAnswerLevelsItemUnmetItem = buildStep.CheckAnswerLevelsItemUnmetItem; +export type ComposeAnswer = buildStep.ComposeAnswer; +export type CurrentAnswer = buildStep.CurrentAnswer; +export type CurrentInput = buildStep.CurrentInput; +export type Pause = buildStep.Pause; +export type PauseAnswer = buildStep.PauseAnswer; +export type PauseInput = buildStep.PauseInput; +export type ResumeAnswer = buildStep.ResumeAnswer; +export type ResumeInput = buildStep.ResumeInput; +export type Step = buildStep.Step; +export type StepAsk = buildStep.StepAsk; +export type StepAskBuild = buildStep.StepAskBuild; +export type StepAskBuildArtifactsItem = buildStep.StepAskBuildArtifactsItem; +export type StepAskBuildToolchain = buildStep.StepAskBuildToolchain; +export type StepAskCheck = buildStep.StepAskCheck; +export type StepAskCompose = buildStep.StepAskCompose; +export type Toolchain = buildStep.Toolchain; + +/** is the body of delivery. */ +export type DeliveryAccept = StepAsk; + +/** is the body of check. */ +export type CheckAccept = StepAsk; + +/** is the body of recheck. */ +export type RecheckAccept = StepAsk; + +/** is one value of the state steps under the key .. */ +export type StepsByNodeIDValue = Step; + +/** is one value of the state pauses under the key paused.. */ +export type PausesByPausedNodeValue = Pause; + +/** is one value of the state pauses under the key capacity.. */ +export type PausesByCapacityNodeValue = Capacity; + +/** is one value of the state toolchains under the key .. */ +export type ToolchainsByNodeLanguageValue = Toolchain; + diff --git a/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts b/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts new file mode 100644 index 00000000..fa5146dc --- /dev/null +++ b/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts @@ -0,0 +1,232 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The part "build-step" several seats include whole: What the three build seats share: the verbs that read and steer a node's builds, and the layouts of the asks they accept and the steps they keep. A seat lists its states against these (a part carries no states). + +export const verbs = { + current: "current", + pause: "pause", + resume: "resume", +} as const; + +export interface BuildAnswerArtifactsItem { + "artifact": string; + "digest": string; + "reference"?: string; +} + +export interface BuildAnswerToolchain { + "language": string; + "recipe": string; + "version": string; +} + +export interface BuildAnswer { + "artifacts": BuildAnswerArtifactsItem[]; + "imports"?: string[]; + "out-of-step"?: string[]; + "toolchain"?: BuildAnswerToolchain; +} + +/** how many steps the node runs at once */ +export interface Capacity { + /** when (RFC 3339 time) */ + "at"?: string; + "build-hours"?: string; + "build-hours-fault"?: string; + "by-cpu"?: number; + "configured": number; + "configured-from"?: string; + "cores-per-step"?: number; + "effective": number; + "ineligible"?: string; + "outside-hours"?: boolean; + "quota-cores"?: number; + "quota-from"?: string; + /** the layout version */ + "v"?: number; + "why": string; +} + +export interface CheckAnswerLevelsItemDigestNeedsItem { + "needs"?: string[]; + "package"?: string; +} + +export interface CheckAnswerLevelsItemDigestPackagesItem { + "digest": string; + "package": string; +} + +export interface CheckAnswerLevelsItemDigest { + "beside"?: string[]; + "judged_by"?: string; + "level"?: string; + "needs"?: CheckAnswerLevelsItemDigestNeedsItem[]; + "packages"?: CheckAnswerLevelsItemDigestPackagesItem[]; + "without_tests"?: string[]; +} + +export interface CheckAnswerLevelsItemKeptItem { + "digest"?: string; + "package"?: string; + "tests"?: number; +} + +export interface CheckAnswerLevelsItemUnmetItem { + "needs"?: string[]; + "package"?: string; +} + +export interface CheckAnswerLevelsItem { + "digest"?: CheckAnswerLevelsItemDigest; + "judged"?: string; + "kept"?: CheckAnswerLevelsItemKeptItem[]; + "level": string; + "tests"?: unknown[]; + "unmet"?: CheckAnswerLevelsItemUnmetItem[]; +} + +export interface CheckAnswer { + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch": unknown; + "levels"?: CheckAnswerLevelsItem[]; +} + +export interface ComposeAnswer { + "built": boolean; + "key"?: string; + /** the compose-candidate verdict */ + "verdict"?: unknown; +} + +/** whether the node takes no new ask */ +export interface Pause { + /** when (RFC 3339 time) */ + "at"?: string; + "by"?: string; + "paused": boolean; + /** the layout version */ + "v"?: number; +} + +/** one step a build module runs on a node */ +export interface Step { + /** when (RFC 3339 time) */ + "ended"?: string; + "id": string; + "node": string; + "owner"?: string; + "phase"?: string; + /** when (RFC 3339 time) */ + "phase-at"?: string; + "reason"?: string; + /** what the step answered */ + "result"?: unknown; + /** when (RFC 3339 time) */ + "started": string; + /** running, passed, failed or refused */ + "state": string; + "tool": string; + /** the layout version */ + "v"?: number; + "what"?: string; +} + +export interface StepAskBuildArtifactsItem { + "artifact": string; + "from"?: string; + "seat"?: string; + "version"?: string; +} + +export interface StepAskBuildToolchain { + "language": string; + "recipe": string; + "version": string; +} + +export interface StepAskBuild { + "artifacts": StepAskBuildArtifactsItem[]; + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch"?: unknown; + "fp"?: string; + "module": string; + "path"?: string; + "publish"?: boolean; + "toolchain"?: StepAskBuildToolchain; +} + +export interface StepAskCheck { + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch": unknown; + "kept"?: boolean; + "levels": string[]; + "packages"?: string[]; + "run"?: string; + "runner"?: string; + "runner_when"?: string; +} + +export interface StepAskCompose { + "dir": string; + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch": unknown; + "package": string; + "reader"?: string; + /** the compose-candidate request */ + "request": unknown; +} + +/** one ask of a build module */ +export interface StepAsk { + "asker": string; + /** when (RFC 3339 time) */ + "at"?: string; + "build"?: StepAskBuild; + "check"?: StepAskCheck; + "compose"?: StepAskCompose; + "id": string; + /** build, check or compose */ + "kind": string; + /** go, typescript or image */ + "language": string; + "needs"?: string[]; + /** package to the tagged version a build pins */ + "packages"?: Record; + /** the layout version */ + "v"?: number; +} + +/** the toolchain a node reports */ +export interface Toolchain { + /** when (RFC 3339 time) */ + "at"?: string; + /** go or typescript */ + "language": string; + "recipe": string; + /** the layout version */ + "v"?: number; + "version": string; +} + +/** is what current takes. */ +export interface CurrentInput { +} + +/** is what current answers. */ +export type CurrentAnswer = Record; + +/** is what pause takes. */ +export interface PauseInput { +} + +/** is what pause answers. */ +export type PauseAnswer = Record; + +/** is what resume takes. */ +export interface ResumeInput { +} + +/** is what resume answers. */ +export type ResumeAnswer = Record; + diff --git a/contracts/ts/seats/nodebuildgo/tsconfig.json b/contracts/ts/seats/nodebuildgo/tsconfig.json new file mode 100644 index 00000000..60274c6a --- /dev/null +++ b/contracts/ts/seats/nodebuildgo/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"] +} diff --git a/contracts/ts/seats/nodebuildimage/package-lock.json b/contracts/ts/seats/nodebuildimage/package-lock.json new file mode 100644 index 00000000..ca80ceb0 --- /dev/null +++ b/contracts/ts/seats/nodebuildimage/package-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 3, + "name": "@novox/mesh-delivery-contracts-node-build-image", + "packages": { + "": { + "name": "@novox/mesh-delivery-contracts-node-build-image", + "version": "0.1.0" + } + }, + "requires": true, + "version": "0.1.0" +} diff --git a/contracts/ts/seats/nodebuildimage/package.json b/contracts/ts/seats/nodebuildimage/package.json new file mode 100644 index 00000000..8e2ca24e --- /dev/null +++ b/contracts/ts/seats/nodebuildimage/package.json @@ -0,0 +1,13 @@ +{ + "description": "The contract of the seat node-build-image, generated from its definition.", + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src" + ], + "name": "@novox/mesh-delivery-contracts-node-build-image", + "type": "module", + "types": "./src/index.ts", + "version": "0.1.0" +} diff --git a/contracts/ts/seats/nodebuildimage/src/index.ts b/contracts/ts/seats/nodebuildimage/src/index.ts new file mode 100644 index 00000000..b289e8f7 --- /dev/null +++ b/contracts/ts/seats/nodebuildimage/src/index.ts @@ -0,0 +1,153 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The contract of the seat "node-build-image", defined whole (seats/node-build-image/seat.json): The image build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers. + +import type * as buildStep from "./parts/build-step.js"; + +export const seat = "node-build-image"; + +export const verbs = { + current: "current", + pause: "pause", + resume: "resume", +} as const; + +export const accepts = { + delivery: "delivery", + check: "check", + recheck: "recheck", +} as const; + +export const deliverySubject = "mesh.seat.node-build-image.accept.delivery"; + +export const checkSubject = "mesh.seat.node-build-image.accept.check"; + +export const recheckSubject = "mesh.seat.node-build-image.accept.recheck"; + +export const states = { + steps: "steps", + pauses: "pauses", + toolchains: "toolchains", +} as const; + +/** The key of one value of the state steps under the form ".". */ +export function stepsByNodeIDKey(node: string, id: string): string { + return node + "." + id; +} + +/** The parts of a key of the form ".", or undefined for a key of another form. */ +export function parseStepsByNodeIDKey(key: string): { node: string; id: string } | undefined { + let rest = key; + const at0 = rest.indexOf("."); + if (at0 <= 0) return undefined; + const node = rest.slice(0, at0); + rest = rest.slice(at0 + 1); + if (rest === "") return undefined; + const id = rest; + rest = ""; + if (rest !== "") return undefined; + return { node, id }; +} + +/** The key of one value of the state pauses under the form "paused.". */ +export function pausesByPausedNodeKey(node: string): string { + return "paused." + node; +} + +/** The parts of a key of the form "paused.", or undefined for a key of another form. */ +export function parsePausesByPausedNodeKey(key: string): { node: string } | undefined { + let rest = key; + if (!rest.startsWith("paused.")) return undefined; + rest = rest.slice(7); + if (rest === "") return undefined; + const node = rest; + rest = ""; + if (rest !== "") return undefined; + return { node }; +} + +/** The key of one value of the state pauses under the form "capacity.". */ +export function pausesByCapacityNodeKey(node: string): string { + return "capacity." + node; +} + +/** The parts of a key of the form "capacity.", or undefined for a key of another form. */ +export function parsePausesByCapacityNodeKey(key: string): { node: string } | undefined { + let rest = key; + if (!rest.startsWith("capacity.")) return undefined; + rest = rest.slice(9); + if (rest === "") return undefined; + const node = rest; + rest = ""; + if (rest !== "") return undefined; + return { node }; +} + +/** The key of one value of the state toolchains under the form ".". */ +export function toolchainsByNodeLanguageKey(node: string, language: string): string { + return node + "." + language; +} + +/** The parts of a key of the form ".", or undefined for a key of another form. */ +export function parseToolchainsByNodeLanguageKey(key: string): { node: string; language: string } | undefined { + let rest = key; + const at0 = rest.indexOf("."); + if (at0 <= 0) return undefined; + const node = rest.slice(0, at0); + rest = rest.slice(at0 + 1); + if (rest === "") return undefined; + const language = rest; + rest = ""; + if (rest !== "") return undefined; + return { node, language }; +} + +export type BuildAnswer = buildStep.BuildAnswer; +export type BuildAnswerArtifactsItem = buildStep.BuildAnswerArtifactsItem; +export type BuildAnswerToolchain = buildStep.BuildAnswerToolchain; +export type Capacity = buildStep.Capacity; +export type CheckAnswer = buildStep.CheckAnswer; +export type CheckAnswerLevelsItem = buildStep.CheckAnswerLevelsItem; +export type CheckAnswerLevelsItemDigest = buildStep.CheckAnswerLevelsItemDigest; +export type CheckAnswerLevelsItemDigestNeedsItem = buildStep.CheckAnswerLevelsItemDigestNeedsItem; +export type CheckAnswerLevelsItemDigestPackagesItem = buildStep.CheckAnswerLevelsItemDigestPackagesItem; +export type CheckAnswerLevelsItemKeptItem = buildStep.CheckAnswerLevelsItemKeptItem; +export type CheckAnswerLevelsItemUnmetItem = buildStep.CheckAnswerLevelsItemUnmetItem; +export type ComposeAnswer = buildStep.ComposeAnswer; +export type CurrentAnswer = buildStep.CurrentAnswer; +export type CurrentInput = buildStep.CurrentInput; +export type Pause = buildStep.Pause; +export type PauseAnswer = buildStep.PauseAnswer; +export type PauseInput = buildStep.PauseInput; +export type ResumeAnswer = buildStep.ResumeAnswer; +export type ResumeInput = buildStep.ResumeInput; +export type Step = buildStep.Step; +export type StepAsk = buildStep.StepAsk; +export type StepAskBuild = buildStep.StepAskBuild; +export type StepAskBuildArtifactsItem = buildStep.StepAskBuildArtifactsItem; +export type StepAskBuildToolchain = buildStep.StepAskBuildToolchain; +export type StepAskCheck = buildStep.StepAskCheck; +export type StepAskCompose = buildStep.StepAskCompose; +export type Toolchain = buildStep.Toolchain; + +/** is the body of delivery. */ +export type DeliveryAccept = StepAsk; + +/** is the body of check. */ +export type CheckAccept = StepAsk; + +/** is the body of recheck. */ +export type RecheckAccept = StepAsk; + +/** is one value of the state steps under the key .. */ +export type StepsByNodeIDValue = Step; + +/** is one value of the state pauses under the key paused.. */ +export type PausesByPausedNodeValue = Pause; + +/** is one value of the state pauses under the key capacity.. */ +export type PausesByCapacityNodeValue = Capacity; + +/** is one value of the state toolchains under the key .. */ +export type ToolchainsByNodeLanguageValue = Toolchain; + diff --git a/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts b/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts new file mode 100644 index 00000000..fa5146dc --- /dev/null +++ b/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts @@ -0,0 +1,232 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The part "build-step" several seats include whole: What the three build seats share: the verbs that read and steer a node's builds, and the layouts of the asks they accept and the steps they keep. A seat lists its states against these (a part carries no states). + +export const verbs = { + current: "current", + pause: "pause", + resume: "resume", +} as const; + +export interface BuildAnswerArtifactsItem { + "artifact": string; + "digest": string; + "reference"?: string; +} + +export interface BuildAnswerToolchain { + "language": string; + "recipe": string; + "version": string; +} + +export interface BuildAnswer { + "artifacts": BuildAnswerArtifactsItem[]; + "imports"?: string[]; + "out-of-step"?: string[]; + "toolchain"?: BuildAnswerToolchain; +} + +/** how many steps the node runs at once */ +export interface Capacity { + /** when (RFC 3339 time) */ + "at"?: string; + "build-hours"?: string; + "build-hours-fault"?: string; + "by-cpu"?: number; + "configured": number; + "configured-from"?: string; + "cores-per-step"?: number; + "effective": number; + "ineligible"?: string; + "outside-hours"?: boolean; + "quota-cores"?: number; + "quota-from"?: string; + /** the layout version */ + "v"?: number; + "why": string; +} + +export interface CheckAnswerLevelsItemDigestNeedsItem { + "needs"?: string[]; + "package"?: string; +} + +export interface CheckAnswerLevelsItemDigestPackagesItem { + "digest": string; + "package": string; +} + +export interface CheckAnswerLevelsItemDigest { + "beside"?: string[]; + "judged_by"?: string; + "level"?: string; + "needs"?: CheckAnswerLevelsItemDigestNeedsItem[]; + "packages"?: CheckAnswerLevelsItemDigestPackagesItem[]; + "without_tests"?: string[]; +} + +export interface CheckAnswerLevelsItemKeptItem { + "digest"?: string; + "package"?: string; + "tests"?: number; +} + +export interface CheckAnswerLevelsItemUnmetItem { + "needs"?: string[]; + "package"?: string; +} + +export interface CheckAnswerLevelsItem { + "digest"?: CheckAnswerLevelsItemDigest; + "judged"?: string; + "kept"?: CheckAnswerLevelsItemKeptItem[]; + "level": string; + "tests"?: unknown[]; + "unmet"?: CheckAnswerLevelsItemUnmetItem[]; +} + +export interface CheckAnswer { + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch": unknown; + "levels"?: CheckAnswerLevelsItem[]; +} + +export interface ComposeAnswer { + "built": boolean; + "key"?: string; + /** the compose-candidate verdict */ + "verdict"?: unknown; +} + +/** whether the node takes no new ask */ +export interface Pause { + /** when (RFC 3339 time) */ + "at"?: string; + "by"?: string; + "paused": boolean; + /** the layout version */ + "v"?: number; +} + +/** one step a build module runs on a node */ +export interface Step { + /** when (RFC 3339 time) */ + "ended"?: string; + "id": string; + "node": string; + "owner"?: string; + "phase"?: string; + /** when (RFC 3339 time) */ + "phase-at"?: string; + "reason"?: string; + /** what the step answered */ + "result"?: unknown; + /** when (RFC 3339 time) */ + "started": string; + /** running, passed, failed or refused */ + "state": string; + "tool": string; + /** the layout version */ + "v"?: number; + "what"?: string; +} + +export interface StepAskBuildArtifactsItem { + "artifact": string; + "from"?: string; + "seat"?: string; + "version"?: string; +} + +export interface StepAskBuildToolchain { + "language": string; + "recipe": string; + "version": string; +} + +export interface StepAskBuild { + "artifacts": StepAskBuildArtifactsItem[]; + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch"?: unknown; + "fp"?: string; + "module": string; + "path"?: string; + "publish"?: boolean; + "toolchain"?: StepAskBuildToolchain; +} + +export interface StepAskCheck { + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch": unknown; + "kept"?: boolean; + "levels": string[]; + "packages"?: string[]; + "run"?: string; + "runner"?: string; + "runner_when"?: string; +} + +export interface StepAskCompose { + "dir": string; + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch": unknown; + "package": string; + "reader"?: string; + /** the compose-candidate request */ + "request": unknown; +} + +/** one ask of a build module */ +export interface StepAsk { + "asker": string; + /** when (RFC 3339 time) */ + "at"?: string; + "build"?: StepAskBuild; + "check"?: StepAskCheck; + "compose"?: StepAskCompose; + "id": string; + /** build, check or compose */ + "kind": string; + /** go, typescript or image */ + "language": string; + "needs"?: string[]; + /** package to the tagged version a build pins */ + "packages"?: Record; + /** the layout version */ + "v"?: number; +} + +/** the toolchain a node reports */ +export interface Toolchain { + /** when (RFC 3339 time) */ + "at"?: string; + /** go or typescript */ + "language": string; + "recipe": string; + /** the layout version */ + "v"?: number; + "version": string; +} + +/** is what current takes. */ +export interface CurrentInput { +} + +/** is what current answers. */ +export type CurrentAnswer = Record; + +/** is what pause takes. */ +export interface PauseInput { +} + +/** is what pause answers. */ +export type PauseAnswer = Record; + +/** is what resume takes. */ +export interface ResumeInput { +} + +/** is what resume answers. */ +export type ResumeAnswer = Record; + diff --git a/contracts/ts/seats/nodebuildimage/tsconfig.json b/contracts/ts/seats/nodebuildimage/tsconfig.json new file mode 100644 index 00000000..60274c6a --- /dev/null +++ b/contracts/ts/seats/nodebuildimage/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"] +} diff --git a/contracts/ts/seats/nodebuildnode/package-lock.json b/contracts/ts/seats/nodebuildnode/package-lock.json new file mode 100644 index 00000000..4622abb2 --- /dev/null +++ b/contracts/ts/seats/nodebuildnode/package-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 3, + "name": "@novox/mesh-delivery-contracts-node-build-node", + "packages": { + "": { + "name": "@novox/mesh-delivery-contracts-node-build-node", + "version": "0.1.0" + } + }, + "requires": true, + "version": "0.1.0" +} diff --git a/contracts/ts/seats/nodebuildnode/package.json b/contracts/ts/seats/nodebuildnode/package.json new file mode 100644 index 00000000..de82c7d0 --- /dev/null +++ b/contracts/ts/seats/nodebuildnode/package.json @@ -0,0 +1,13 @@ +{ + "description": "The contract of the seat node-build-node, generated from its definition.", + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src" + ], + "name": "@novox/mesh-delivery-contracts-node-build-node", + "type": "module", + "types": "./src/index.ts", + "version": "0.1.0" +} diff --git a/contracts/ts/seats/nodebuildnode/src/index.ts b/contracts/ts/seats/nodebuildnode/src/index.ts new file mode 100644 index 00000000..d84ca0ae --- /dev/null +++ b/contracts/ts/seats/nodebuildnode/src/index.ts @@ -0,0 +1,153 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The contract of the seat "node-build-node", defined whole (seats/node-build-node/seat.json): The Node build module on a node: it takes the asks of the builder and the checker, runs them as steps and answers. + +import type * as buildStep from "./parts/build-step.js"; + +export const seat = "node-build-node"; + +export const verbs = { + current: "current", + pause: "pause", + resume: "resume", +} as const; + +export const accepts = { + delivery: "delivery", + check: "check", + recheck: "recheck", +} as const; + +export const deliverySubject = "mesh.seat.node-build-node.accept.delivery"; + +export const checkSubject = "mesh.seat.node-build-node.accept.check"; + +export const recheckSubject = "mesh.seat.node-build-node.accept.recheck"; + +export const states = { + steps: "steps", + pauses: "pauses", + toolchains: "toolchains", +} as const; + +/** The key of one value of the state steps under the form ".". */ +export function stepsByNodeIDKey(node: string, id: string): string { + return node + "." + id; +} + +/** The parts of a key of the form ".", or undefined for a key of another form. */ +export function parseStepsByNodeIDKey(key: string): { node: string; id: string } | undefined { + let rest = key; + const at0 = rest.indexOf("."); + if (at0 <= 0) return undefined; + const node = rest.slice(0, at0); + rest = rest.slice(at0 + 1); + if (rest === "") return undefined; + const id = rest; + rest = ""; + if (rest !== "") return undefined; + return { node, id }; +} + +/** The key of one value of the state pauses under the form "paused.". */ +export function pausesByPausedNodeKey(node: string): string { + return "paused." + node; +} + +/** The parts of a key of the form "paused.", or undefined for a key of another form. */ +export function parsePausesByPausedNodeKey(key: string): { node: string } | undefined { + let rest = key; + if (!rest.startsWith("paused.")) return undefined; + rest = rest.slice(7); + if (rest === "") return undefined; + const node = rest; + rest = ""; + if (rest !== "") return undefined; + return { node }; +} + +/** The key of one value of the state pauses under the form "capacity.". */ +export function pausesByCapacityNodeKey(node: string): string { + return "capacity." + node; +} + +/** The parts of a key of the form "capacity.", or undefined for a key of another form. */ +export function parsePausesByCapacityNodeKey(key: string): { node: string } | undefined { + let rest = key; + if (!rest.startsWith("capacity.")) return undefined; + rest = rest.slice(9); + if (rest === "") return undefined; + const node = rest; + rest = ""; + if (rest !== "") return undefined; + return { node }; +} + +/** The key of one value of the state toolchains under the form ".". */ +export function toolchainsByNodeLanguageKey(node: string, language: string): string { + return node + "." + language; +} + +/** The parts of a key of the form ".", or undefined for a key of another form. */ +export function parseToolchainsByNodeLanguageKey(key: string): { node: string; language: string } | undefined { + let rest = key; + const at0 = rest.indexOf("."); + if (at0 <= 0) return undefined; + const node = rest.slice(0, at0); + rest = rest.slice(at0 + 1); + if (rest === "") return undefined; + const language = rest; + rest = ""; + if (rest !== "") return undefined; + return { node, language }; +} + +export type BuildAnswer = buildStep.BuildAnswer; +export type BuildAnswerArtifactsItem = buildStep.BuildAnswerArtifactsItem; +export type BuildAnswerToolchain = buildStep.BuildAnswerToolchain; +export type Capacity = buildStep.Capacity; +export type CheckAnswer = buildStep.CheckAnswer; +export type CheckAnswerLevelsItem = buildStep.CheckAnswerLevelsItem; +export type CheckAnswerLevelsItemDigest = buildStep.CheckAnswerLevelsItemDigest; +export type CheckAnswerLevelsItemDigestNeedsItem = buildStep.CheckAnswerLevelsItemDigestNeedsItem; +export type CheckAnswerLevelsItemDigestPackagesItem = buildStep.CheckAnswerLevelsItemDigestPackagesItem; +export type CheckAnswerLevelsItemKeptItem = buildStep.CheckAnswerLevelsItemKeptItem; +export type CheckAnswerLevelsItemUnmetItem = buildStep.CheckAnswerLevelsItemUnmetItem; +export type ComposeAnswer = buildStep.ComposeAnswer; +export type CurrentAnswer = buildStep.CurrentAnswer; +export type CurrentInput = buildStep.CurrentInput; +export type Pause = buildStep.Pause; +export type PauseAnswer = buildStep.PauseAnswer; +export type PauseInput = buildStep.PauseInput; +export type ResumeAnswer = buildStep.ResumeAnswer; +export type ResumeInput = buildStep.ResumeInput; +export type Step = buildStep.Step; +export type StepAsk = buildStep.StepAsk; +export type StepAskBuild = buildStep.StepAskBuild; +export type StepAskBuildArtifactsItem = buildStep.StepAskBuildArtifactsItem; +export type StepAskBuildToolchain = buildStep.StepAskBuildToolchain; +export type StepAskCheck = buildStep.StepAskCheck; +export type StepAskCompose = buildStep.StepAskCompose; +export type Toolchain = buildStep.Toolchain; + +/** is the body of delivery. */ +export type DeliveryAccept = StepAsk; + +/** is the body of check. */ +export type CheckAccept = StepAsk; + +/** is the body of recheck. */ +export type RecheckAccept = StepAsk; + +/** is one value of the state steps under the key .. */ +export type StepsByNodeIDValue = Step; + +/** is one value of the state pauses under the key paused.. */ +export type PausesByPausedNodeValue = Pause; + +/** is one value of the state pauses under the key capacity.. */ +export type PausesByCapacityNodeValue = Capacity; + +/** is one value of the state toolchains under the key .. */ +export type ToolchainsByNodeLanguageValue = Toolchain; + diff --git a/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts b/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts new file mode 100644 index 00000000..fa5146dc --- /dev/null +++ b/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts @@ -0,0 +1,232 @@ +// Code generated by seatgen from the seat definitions; DO NOT EDIT. + +// The part "build-step" several seats include whole: What the three build seats share: the verbs that read and steer a node's builds, and the layouts of the asks they accept and the steps they keep. A seat lists its states against these (a part carries no states). + +export const verbs = { + current: "current", + pause: "pause", + resume: "resume", +} as const; + +export interface BuildAnswerArtifactsItem { + "artifact": string; + "digest": string; + "reference"?: string; +} + +export interface BuildAnswerToolchain { + "language": string; + "recipe": string; + "version": string; +} + +export interface BuildAnswer { + "artifacts": BuildAnswerArtifactsItem[]; + "imports"?: string[]; + "out-of-step"?: string[]; + "toolchain"?: BuildAnswerToolchain; +} + +/** how many steps the node runs at once */ +export interface Capacity { + /** when (RFC 3339 time) */ + "at"?: string; + "build-hours"?: string; + "build-hours-fault"?: string; + "by-cpu"?: number; + "configured": number; + "configured-from"?: string; + "cores-per-step"?: number; + "effective": number; + "ineligible"?: string; + "outside-hours"?: boolean; + "quota-cores"?: number; + "quota-from"?: string; + /** the layout version */ + "v"?: number; + "why": string; +} + +export interface CheckAnswerLevelsItemDigestNeedsItem { + "needs"?: string[]; + "package"?: string; +} + +export interface CheckAnswerLevelsItemDigestPackagesItem { + "digest": string; + "package": string; +} + +export interface CheckAnswerLevelsItemDigest { + "beside"?: string[]; + "judged_by"?: string; + "level"?: string; + "needs"?: CheckAnswerLevelsItemDigestNeedsItem[]; + "packages"?: CheckAnswerLevelsItemDigestPackagesItem[]; + "without_tests"?: string[]; +} + +export interface CheckAnswerLevelsItemKeptItem { + "digest"?: string; + "package"?: string; + "tests"?: number; +} + +export interface CheckAnswerLevelsItemUnmetItem { + "needs"?: string[]; + "package"?: string; +} + +export interface CheckAnswerLevelsItem { + "digest"?: CheckAnswerLevelsItemDigest; + "judged"?: string; + "kept"?: CheckAnswerLevelsItemKeptItem[]; + "level": string; + "tests"?: unknown[]; + "unmet"?: CheckAnswerLevelsItemUnmetItem[]; +} + +export interface CheckAnswer { + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch": unknown; + "levels"?: CheckAnswerLevelsItem[]; +} + +export interface ComposeAnswer { + "built": boolean; + "key"?: string; + /** the compose-candidate verdict */ + "verdict"?: unknown; +} + +/** whether the node takes no new ask */ +export interface Pause { + /** when (RFC 3339 time) */ + "at"?: string; + "by"?: string; + "paused": boolean; + /** the layout version */ + "v"?: number; +} + +/** one step a build module runs on a node */ +export interface Step { + /** when (RFC 3339 time) */ + "ended"?: string; + "id": string; + "node": string; + "owner"?: string; + "phase"?: string; + /** when (RFC 3339 time) */ + "phase-at"?: string; + "reason"?: string; + /** what the step answered */ + "result"?: unknown; + /** when (RFC 3339 time) */ + "started": string; + /** running, passed, failed or refused */ + "state": string; + "tool": string; + /** the layout version */ + "v"?: number; + "what"?: string; +} + +export interface StepAskBuildArtifactsItem { + "artifact": string; + "from"?: string; + "seat"?: string; + "version"?: string; +} + +export interface StepAskBuildToolchain { + "language": string; + "recipe": string; + "version": string; +} + +export interface StepAskBuild { + "artifacts": StepAskBuildArtifactsItem[]; + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch"?: unknown; + "fp"?: string; + "module": string; + "path"?: string; + "publish"?: boolean; + "toolchain"?: StepAskBuildToolchain; +} + +export interface StepAskCheck { + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch": unknown; + "kept"?: boolean; + "levels": string[]; + "packages"?: string[]; + "run"?: string; + "runner"?: string; + "runner_when"?: string; +} + +export interface StepAskCompose { + "dir": string; + /** what the build module fetches: the repository, commit and directory, as the indexer states it */ + "fetch": unknown; + "package": string; + "reader"?: string; + /** the compose-candidate request */ + "request": unknown; +} + +/** one ask of a build module */ +export interface StepAsk { + "asker": string; + /** when (RFC 3339 time) */ + "at"?: string; + "build"?: StepAskBuild; + "check"?: StepAskCheck; + "compose"?: StepAskCompose; + "id": string; + /** build, check or compose */ + "kind": string; + /** go, typescript or image */ + "language": string; + "needs"?: string[]; + /** package to the tagged version a build pins */ + "packages"?: Record; + /** the layout version */ + "v"?: number; +} + +/** the toolchain a node reports */ +export interface Toolchain { + /** when (RFC 3339 time) */ + "at"?: string; + /** go or typescript */ + "language": string; + "recipe": string; + /** the layout version */ + "v"?: number; + "version": string; +} + +/** is what current takes. */ +export interface CurrentInput { +} + +/** is what current answers. */ +export type CurrentAnswer = Record; + +/** is what pause takes. */ +export interface PauseInput { +} + +/** is what pause answers. */ +export type PauseAnswer = Record; + +/** is what resume takes. */ +export interface ResumeInput { +} + +/** is what resume answers. */ +export type ResumeAnswer = Record; + diff --git a/contracts/ts/seats/nodebuildnode/tsconfig.json b/contracts/ts/seats/nodebuildnode/tsconfig.json new file mode 100644 index 00000000..60274c6a --- /dev/null +++ b/contracts/ts/seats/nodebuildnode/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*.ts"] +}