diff --git a/contracts/go/seats/meshwants/contract.go b/contracts/go/seats/meshwants/contract.go index 97003e33..9b9f4754 100644 --- a/contracts/go/seats/meshwants/contract.go +++ b/contracts/go/seats/meshwants/contract.go @@ -39,6 +39,7 @@ const ( EventRoom = "room" EventStepEnded = "step-ended" EventVersionDelivered = "version-delivered" + EventStepProgress = "step-progress" ) // Verbs is every verb the seat serves, in its definition's order: a new copy each call. @@ -48,7 +49,7 @@ func Verbs() []string { // Events is every event published on the seat's subject: a new copy each call. func Events() []string { - return []string{EventPolicyChanged, EventAssignmentRevision, EventDifference, EventWanted, EventWaitHeld, EventWaitEnded, EventFilling, EventFull, EventRoom, EventStepEnded, EventVersionDelivered} + return []string{EventPolicyChanged, EventAssignmentRevision, EventDifference, EventWanted, EventWaitHeld, EventWaitEnded, EventFilling, EventFull, EventRoom, EventStepEnded, EventVersionDelivered, EventStepProgress} } // Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. @@ -99,6 +100,17 @@ const StreamVersionDelivered = "SEAT_mesh-wants_version-delivered" // SubjectVersionDelivered is where version-delivered is published. const SubjectVersionDelivered = "mesh.seat.mesh-wants.event.version-delivered" +// StreamStepProgress is the stream step-progress is kept in. +const StreamStepProgress = "SEAT_mesh-wants_step-progress" + +// SubjectStepProgress is where step-progress is published, on mesh.seat.mesh-wants.event.step-progress.. +func SubjectStepProgress(node string) string { + return "mesh.seat.mesh-wants.event.step-progress." + node +} + +// SubjectStepProgressAll hears every step-progress. +const SubjectStepProgressAll = "mesh.seat.mesh-wants.event.step-progress.*" + // AcceptSubjectStep is where step is sent. const AcceptSubjectStep = "mesh.seat.mesh-wants.accept.step" @@ -823,6 +835,22 @@ type VersionDeliveredEvent struct { Version string `json:"version"` } +// StepProgressEvent is the body of the event step-progress. +type StepProgressEvent struct { + Ask string `json:"ask"` + Asker string `json:"asker"` + At time.Time `json:"at"` + Phase string `json:"phase"` +} + +// The values of StepProgressEvent's phase. +const ( + StepProgressEventPhaseFetch = "fetch" + StepProgressEventPhaseBuild = "build" + StepProgressEventPhaseRun = "run" + StepProgressEventPhaseReport = "report" +) + // WantsByWantNodeModuleValue is one value of the state wants under the key want... type WantsByWantNodeModuleValue struct { // again or skip diff --git a/contracts/go/seats/meshwants/contract_test.go b/contracts/go/seats/meshwants/contract_test.go index dcfe1e6c..6a6825e6 100644 --- a/contracts/go/seats/meshwants/contract_test.go +++ b/contracts/go/seats/meshwants/contract_test.go @@ -43,6 +43,8 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v StepEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-wants\\.event\\.step-ended\\.[^.*> ]+$")}, {name: "a version delivered with the machines away", subject: "mesh.seat.mesh-wants.event.version-delivered", given: "{\"away\":[\"laptop\"],\"module\":\"mesh-sdk\",\"parts\":{\"mesh-sdk:go\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"},\"v\":1,\"version\":\"0.1.15\"}", wire: "{\"away\":[\"laptop\"],\"module\":\"mesh-sdk\",\"parts\":{\"mesh-sdk:go\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\"},\"v\":1,\"version\":\"0.1.15\"}", decode: func(b []byte) (any, error) { var v VersionDeliveredEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-wants\\.event\\.version-delivered$")}, + {name: "a composition step's progress", subject: "mesh.seat.mesh-wants.event.step-progress.ace", given: "{\"ask\":\"a1b2\",\"asker\":\"assigner\",\"at\":\"2026-11-11T10:01:00Z\",\"phase\":\"build\"}", wire: "{\"ask\":\"a1b2\",\"asker\":\"assigner\",\"at\":\"2026-11-11T10:01:00Z\",\"phase\":\"build\"}", + decode: func(b []byte) (any, error) { var v StepProgressEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-wants\\.event\\.step-progress\\.[^.*> ]+$")}, {name: "a want", subject: "", given: "{\"fp\":\"ab12\",\"rule\":3,\"v\":1}", wire: "{\"fp\":\"ab12\",\"rule\":3,\"v\":1}", decode: func(b []byte) (any, error) { var v WantsByWantNodeModuleValue diff --git a/contracts/go/seats/nodebuildgo/contract.go b/contracts/go/seats/nodebuildgo/contract.go index 34785b37..7b1eaece 100644 --- a/contracts/go/seats/nodebuildgo/contract.go +++ b/contracts/go/seats/nodebuildgo/contract.go @@ -33,7 +33,9 @@ const ( ) const ( - EventStepEnded = "step-ended" + EventStepEnded = "step-ended" + EventToolchainReported = "toolchain-reported" + EventStepProgress = "step-progress" ) // Verbs is every verb the seat serves, in its definition's order: a new copy each call. @@ -43,7 +45,7 @@ func Verbs() []string { // Events is every event published on the seat's subject: a new copy each call. func Events() []string { - return []string{EventStepEnded} + return []string{EventStepEnded, EventToolchainReported, EventStepProgress} } // Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. @@ -61,6 +63,28 @@ func SubjectStepEnded(node string) string { return "mesh.seat.node-build-go.even // SubjectStepEndedAll hears every step-ended. const SubjectStepEndedAll = "mesh.seat.node-build-go.event.step-ended.*" +// StreamToolchainReported is the stream toolchain-reported is kept in. +const StreamToolchainReported = "SEAT_node-build-go_toolchain-reported" + +// SubjectToolchainReported is where toolchain-reported is published, on mesh.seat.node-build-go.event.toolchain-reported.. +func SubjectToolchainReported(node string) string { + return "mesh.seat.node-build-go.event.toolchain-reported." + node +} + +// SubjectToolchainReportedAll hears every toolchain-reported. +const SubjectToolchainReportedAll = "mesh.seat.node-build-go.event.toolchain-reported.*" + +// StreamStepProgress is the stream step-progress is kept in. +const StreamStepProgress = "SEAT_node-build-go_step-progress" + +// SubjectStepProgress is where step-progress is published, on mesh.seat.node-build-go.event.step-progress.. +func SubjectStepProgress(node string) string { + return "mesh.seat.node-build-go.event.step-progress." + node +} + +// SubjectStepProgressAll hears every step-progress. +const SubjectStepProgressAll = "mesh.seat.node-build-go.event.step-progress.*" + // AcceptSubjectDelivery is where delivery is sent. const AcceptSubjectDelivery = "mesh.seat.node-build-go.accept.delivery" @@ -86,6 +110,47 @@ const ( StateDetectedSteps = "detected-steps" ) +// StepsByAskerNodeIDKey is the key of one value of the state steps under the form "..". +func StepsByAskerNodeIDKey(asker, node, id string) string { return asker + "." + node + "." + id } + +// ParseStepsByAskerNodeIDKey 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 ParseStepsByAskerNodeIDKey(key string) (string, string, string, bool) { + rest := key + v0, after0, found0 := strings.Cut(rest, ".") + if !found0 || v0 == "" { + return "", "", "", false + } + rest = after0 + v2, after2, found2 := strings.Cut(rest, ".") + if !found2 || v2 == "" { + return "", "", "", false + } + rest = after2 + if rest == "" { + return "", "", "", false + } + v4 := rest + rest = "" + if rest != "" { + return "", "", "", false + } + return v0, v2, v4, true +} + +// DecodeStepsByAskerNodeIDValue reads one value of the state steps under the form "..", refusing a field StepsByAskerNodeIDValue does not know and any text after the value. +func DecodeStepsByAskerNodeIDValue(raw []byte) (StepsByAskerNodeIDValue, error) { + var v StepsByAskerNodeIDValue + 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 +} + // StepsByNodeIDKey is the key of one value of the state steps under the form ".". func StepsByNodeIDKey(node, id string) string { return node + "." + id } @@ -317,11 +382,21 @@ type BuildAnswer struct { Toolchain *ToolchainUsed `json:"toolchain,omitempty"` } +// BuiltArtifactResolvedItem is a type of the contract. +type BuiltArtifactResolvedItem struct { + // sha256: of the package's bytes as the build fetched them + Digest string `json:"digest"` + Package string `json:"package"` + Version string `json:"version"` +} + // BuiltArtifact is one artifact a build made: its digest and the reference it was published under. type BuiltArtifact struct { Artifact string `json:"artifact"` Digest string `json:"digest"` Reference string `json:"reference,omitempty"` + // one per mesh package the build resolved (the catalog graph entry's packages, the edges of kind 1) + Resolved []BuiltArtifactResolvedItem `json:"resolved,omitempty"` } // Capacity is how many steps the node runs at once. @@ -396,6 +471,16 @@ type FetchBesideItem struct { Repository string `json:"repository"` } +// FetchBuildsItem is a type of the contract. +type FetchBuildsItem struct { + Artifact string `json:"artifact"` + // the member's build digest, sha256: + Digest string `json:"digest"` + Module string `json:"module"` + // the name in its ecosystem (the Go module path, the npm name) + Package string `json:"package"` +} + // FetchPackage is the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's. type FetchPackage struct { Commit string `json:"commit,omitempty"` @@ -411,6 +496,8 @@ type Fetch struct { Base string `json:"base,omitempty"` // the sibling repositories placed beside the fetched tree Beside []FetchBesideItem `json:"beside,omitempty"` + // a group run's package members, each by its build's digest: the build module reads each from the artifact store at artifact-store:////blobs/, checks the bytes against the digest and lays them as the group package; a package named here is named in neither packages nor package + Builds []FetchBuildsItem `json:"builds,omitempty"` // the full commit Commit string `json:"commit"` // the module's directory in the repository ("." for its root), with paths @@ -735,6 +822,22 @@ type StepNames struct { Repositories []string `json:"repositories"` } +// StepProgress is a running ask's progress, said once a minute while the holder runs it (design 47 G section 496). +type StepProgress struct { + Ask string `json:"ask"` + Asker string `json:"asker"` + At time.Time `json:"at"` + Phase string `json:"phase"` +} + +// The values of StepProgress's phase. +const ( + StepProgressPhaseFetch = "fetch" + StepProgressPhaseBuild = "build" + StepProgressPhaseRun = "run" + StepProgressPhaseReport = "report" +) + // StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports, out-of-step modules and the artifact it kept; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five. type StepResult struct { Artifacts []BuiltArtifact `json:"artifacts,omitempty"` @@ -828,8 +931,20 @@ type Toolchain struct { Version string `json:"version"` } +// ToolchainReported is a node's toolchain of a language, said at the build module's start and each time its toolchains key of the language changes (design 47 G section 492). +type ToolchainReported struct { + // the digest of the program of the provision the build module requires for that language's toolchain, never the build module's own + Digest string `json:"digest"` + Language string `json:"language"` + Node string `json:"node"` + Recipe string `json:"recipe"` + Version string `json:"version"` +} + // ToolchainUsed is the toolchain a build was made with: the language, the installed version and the build module's recipe. type ToolchainUsed struct { + // the building node's digest of its toolchain program (kind 3's entry), never the build module's own program + Digest string `json:"digest,omitempty"` Language string `json:"language"` Recipe string `json:"recipe"` Version string `json:"version"` @@ -912,20 +1027,28 @@ const ( type StepsInput struct { // find them anew, not from the kept answer Again bool `json:"again,omitempty"` - // the artifact reference, : - Artifact string `json:"artifact"` - Fetch Fetch `json:"fetch"` + // the key of detected-steps, . + Artifact string `json:"artifact"` + // delete the artifact's kept key instead of detecting; needs why + Drop bool `json:"drop,omitempty"` + Fetch *Fetch `json:"fetch,omitempty"` Fingerprint string `json:"fingerprint,omitempty"` Module string `json:"module"` + // with drop, why: a non-empty text, written in the hand-act log with the caller the bus proved + Why string `json:"why,omitempty"` } // StepsAnswer is what steps answers. type StepsAnswer struct { - Artifact string `json:"artifact"` + Artifact string `json:"artifact,omitempty"` + // the artifact whose key a drop deleted + Dropped string `json:"dropped,omitempty"` + // why a drop deleted nothing, naming the hand-act log when it refused + Error string `json:"error,omitempty"` Fingerprint string `json:"fingerprint,omitempty"` // answered from the kept answer Kept bool `json:"kept,omitempty"` - Steps []DetectedStep `json:"steps"` + Steps []DetectedStep `json:"steps,omitempty"` } // DeliveryAccept is the body of delivery. @@ -949,6 +1072,15 @@ type CheckRunAccept = StepAsk // StepEndedEvent is the body of the event step-ended. type StepEndedEvent = StepEnded +// ToolchainReportedEvent is the body of the event toolchain-reported. +type ToolchainReportedEvent = ToolchainReported + +// StepProgressEvent is the body of the event step-progress. +type StepProgressEvent = StepProgress + +// StepsByAskerNodeIDValue is one value of the state steps under the key ... +type StepsByAskerNodeIDValue = Step + // StepsByNodeIDValue is one value of the state steps under the key .. type StepsByNodeIDValue = Step diff --git a/contracts/go/seats/nodebuildgo/contract_test.go b/contracts/go/seats/nodebuildgo/contract_test.go index b35c655d..14aca33e 100644 --- a/contracts/go/seats/nodebuildgo/contract_test.go +++ b/contracts/go/seats/nodebuildgo/contract_test.go @@ -21,6 +21,10 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v StepEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.event\\.step-ended\\.[^.*> ]+$")}, {name: "an integration step that could not run, naming the part not heard", subject: "mesh.seat.node-build-go.event.step-ended.g14", given: "{\"artifact\":\"nats:program\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"fingerprint\":\"fp-0123\",\"id\":\"m2kx9a1b3c4d5f\",\"module\":\"nats\",\"node\":\"g14\",\"outcome\":\"could not run\",\"output-object\":\"mesh-build-go_sent-objects/m2kx9a1b3c4d5f\",\"pipeline\":\"main.12.nats\",\"said\":\"the sandbox could not fetch novox/mesh-sdk\",\"step\":\"integration\",\"v\":1}", wire: "{\"artifact\":\"nats:program\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"fingerprint\":\"fp-0123\",\"id\":\"m2kx9a1b3c4d5f\",\"module\":\"nats\",\"node\":\"g14\",\"outcome\":\"could not run\",\"output-object\":\"mesh-build-go_sent-objects/m2kx9a1b3c4d5f\",\"pipeline\":\"main.12.nats\",\"said\":\"the sandbox could not fetch novox/mesh-sdk\",\"step\":\"integration\",\"v\":1}", decode: func(b []byte) (any, error) { var v StepEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.event\\.step-ended\\.[^.*> ]+$")}, + {name: "a node's toolchain reported", subject: "mesh.seat.node-build-go.event.toolchain-reported.ace", given: "{\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"language\":\"go\",\"node\":\"ace\",\"recipe\":\"1\",\"version\":\"1.26.0\"}", wire: "{\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"language\":\"go\",\"node\":\"ace\",\"recipe\":\"1\",\"version\":\"1.26.0\"}", + decode: func(b []byte) (any, error) { var v ToolchainReportedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.event\\.toolchain-reported\\.[^.*> ]+$")}, + {name: "a running ask says its progress", subject: "mesh.seat.node-build-go.event.step-progress.ace", given: "{\"ask\":\"a1b2\",\"asker\":\"runner\",\"at\":\"2026-11-11T10:01:00Z\",\"phase\":\"fetch\"}", wire: "{\"ask\":\"a1b2\",\"asker\":\"runner\",\"at\":\"2026-11-11T10:01:00Z\",\"phase\":\"fetch\"}", + decode: func(b []byte) (any, error) { var v StepProgressEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.event\\.step-progress\\.[^.*> ]+$")}, {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}", @@ -75,6 +79,12 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, + {name: "a step keyed under its asker", 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 StepsByAskerNodeIDValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, {name: "drop names the ask and why", subject: "", given: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}", wire: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}", decode: func(b []byte) (any, error) { var v DropInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "kept-artifact asks", subject: "", given: "{\"digest\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"fp\":\"src2:1480985e\",\"module\":\"artifact-store\"}", wire: "{\"digest\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"fp\":\"src2:1480985e\",\"module\":\"artifact-store\"}", @@ -93,6 +103,12 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v StepsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "steps answers lint and unit tests", subject: "", given: "{\"artifact\":\"nats:program\",\"fingerprint\":\"fp-0123\",\"steps\":[{\"step\":\"lint\"},{\"packages\":[\"./modules/nats\"],\"step\":\"unit\"}]}", wire: "{\"artifact\":\"nats:program\",\"fingerprint\":\"fp-0123\",\"steps\":[{\"step\":\"lint\"},{\"packages\":[\"./modules/nats\"],\"step\":\"unit\"}]}", decode: func(b []byte) (any, error) { var v StepsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "steps drops an artifact's kept steps with a reason", subject: "", given: "{\"artifact\":\"nats.program\",\"drop\":true,\"module\":\"nats\",\"why\":\"the detection was wrong\"}", wire: "{\"artifact\":\"nats.program\",\"drop\":true,\"module\":\"nats\",\"why\":\"the detection was wrong\"}", + decode: func(b []byte) (any, error) { var v StepsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "steps answers a drop with the artifact", subject: "", given: "{\"dropped\":\"nats.program\"}", wire: "{\"dropped\":\"nats.program\"}", + decode: func(b []byte) (any, error) { var v StepsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "steps answers a drop that the hand-act log refused", subject: "", given: "{\"error\":\"the hand-act log refused: nothing was deleted\"}", wire: "{\"error\":\"the hand-act log refused: nothing was deleted\"}", + decode: func(b []byte) (any, error) { var v StepsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, } for _, c := range cases { v, err := c.decode([]byte(c.given)) diff --git a/contracts/go/seats/nodebuildimage/contract.go b/contracts/go/seats/nodebuildimage/contract.go index 00f25faa..9769b994 100644 --- a/contracts/go/seats/nodebuildimage/contract.go +++ b/contracts/go/seats/nodebuildimage/contract.go @@ -32,7 +32,9 @@ const ( ) const ( - EventStepEnded = "step-ended" + EventStepEnded = "step-ended" + EventToolchainReported = "toolchain-reported" + EventStepProgress = "step-progress" ) // Verbs is every verb the seat serves, in its definition's order: a new copy each call. @@ -42,7 +44,7 @@ func Verbs() []string { // Events is every event published on the seat's subject: a new copy each call. func Events() []string { - return []string{EventStepEnded} + return []string{EventStepEnded, EventToolchainReported, EventStepProgress} } // Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. @@ -62,6 +64,28 @@ func SubjectStepEnded(node string) string { // SubjectStepEndedAll hears every step-ended. const SubjectStepEndedAll = "mesh.seat.node-build-image.event.step-ended.*" +// StreamToolchainReported is the stream toolchain-reported is kept in. +const StreamToolchainReported = "SEAT_node-build-image_toolchain-reported" + +// SubjectToolchainReported is where toolchain-reported is published, on mesh.seat.node-build-image.event.toolchain-reported.. +func SubjectToolchainReported(node string) string { + return "mesh.seat.node-build-image.event.toolchain-reported." + node +} + +// SubjectToolchainReportedAll hears every toolchain-reported. +const SubjectToolchainReportedAll = "mesh.seat.node-build-image.event.toolchain-reported.*" + +// StreamStepProgress is the stream step-progress is kept in. +const StreamStepProgress = "SEAT_node-build-image_step-progress" + +// SubjectStepProgress is where step-progress is published, on mesh.seat.node-build-image.event.step-progress.. +func SubjectStepProgress(node string) string { + return "mesh.seat.node-build-image.event.step-progress." + node +} + +// SubjectStepProgressAll hears every step-progress. +const SubjectStepProgressAll = "mesh.seat.node-build-image.event.step-progress.*" + // AcceptSubjectDelivery is where delivery is sent. const AcceptSubjectDelivery = "mesh.seat.node-build-image.accept.delivery" @@ -84,6 +108,47 @@ const ( StateDetectedSteps = "detected-steps" ) +// StepsByAskerNodeIDKey is the key of one value of the state steps under the form "..". +func StepsByAskerNodeIDKey(asker, node, id string) string { return asker + "." + node + "." + id } + +// ParseStepsByAskerNodeIDKey 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 ParseStepsByAskerNodeIDKey(key string) (string, string, string, bool) { + rest := key + v0, after0, found0 := strings.Cut(rest, ".") + if !found0 || v0 == "" { + return "", "", "", false + } + rest = after0 + v2, after2, found2 := strings.Cut(rest, ".") + if !found2 || v2 == "" { + return "", "", "", false + } + rest = after2 + if rest == "" { + return "", "", "", false + } + v4 := rest + rest = "" + if rest != "" { + return "", "", "", false + } + return v0, v2, v4, true +} + +// DecodeStepsByAskerNodeIDValue reads one value of the state steps under the form "..", refusing a field StepsByAskerNodeIDValue does not know and any text after the value. +func DecodeStepsByAskerNodeIDValue(raw []byte) (StepsByAskerNodeIDValue, error) { + var v StepsByAskerNodeIDValue + 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 +} + // StepsByNodeIDKey is the key of one value of the state steps under the form ".". func StepsByNodeIDKey(node, id string) string { return node + "." + id } @@ -315,11 +380,21 @@ type BuildAnswer struct { Toolchain *ToolchainUsed `json:"toolchain,omitempty"` } +// BuiltArtifactResolvedItem is a type of the contract. +type BuiltArtifactResolvedItem struct { + // sha256: of the package's bytes as the build fetched them + Digest string `json:"digest"` + Package string `json:"package"` + Version string `json:"version"` +} + // BuiltArtifact is one artifact a build made: its digest and the reference it was published under. type BuiltArtifact struct { Artifact string `json:"artifact"` Digest string `json:"digest"` Reference string `json:"reference,omitempty"` + // one per mesh package the build resolved (the catalog graph entry's packages, the edges of kind 1) + Resolved []BuiltArtifactResolvedItem `json:"resolved,omitempty"` } // Capacity is how many steps the node runs at once. @@ -394,6 +469,16 @@ type FetchBesideItem struct { Repository string `json:"repository"` } +// FetchBuildsItem is a type of the contract. +type FetchBuildsItem struct { + Artifact string `json:"artifact"` + // the member's build digest, sha256: + Digest string `json:"digest"` + Module string `json:"module"` + // the name in its ecosystem (the Go module path, the npm name) + Package string `json:"package"` +} + // FetchPackage is the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's. type FetchPackage struct { Commit string `json:"commit,omitempty"` @@ -409,6 +494,8 @@ type Fetch struct { Base string `json:"base,omitempty"` // the sibling repositories placed beside the fetched tree Beside []FetchBesideItem `json:"beside,omitempty"` + // a group run's package members, each by its build's digest: the build module reads each from the artifact store at artifact-store:////blobs/, checks the bytes against the digest and lays them as the group package; a package named here is named in neither packages nor package + Builds []FetchBuildsItem `json:"builds,omitempty"` // the full commit Commit string `json:"commit"` // the module's directory in the repository ("." for its root), with paths @@ -733,6 +820,22 @@ type StepNames struct { Repositories []string `json:"repositories"` } +// StepProgress is a running ask's progress, said once a minute while the holder runs it (design 47 G section 496). +type StepProgress struct { + Ask string `json:"ask"` + Asker string `json:"asker"` + At time.Time `json:"at"` + Phase string `json:"phase"` +} + +// The values of StepProgress's phase. +const ( + StepProgressPhaseFetch = "fetch" + StepProgressPhaseBuild = "build" + StepProgressPhaseRun = "run" + StepProgressPhaseReport = "report" +) + // StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports, out-of-step modules and the artifact it kept; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five. type StepResult struct { Artifacts []BuiltArtifact `json:"artifacts,omitempty"` @@ -826,8 +929,20 @@ type Toolchain struct { Version string `json:"version"` } +// ToolchainReported is a node's toolchain of a language, said at the build module's start and each time its toolchains key of the language changes (design 47 G section 492). +type ToolchainReported struct { + // the digest of the program of the provision the build module requires for that language's toolchain, never the build module's own + Digest string `json:"digest"` + Language string `json:"language"` + Node string `json:"node"` + Recipe string `json:"recipe"` + Version string `json:"version"` +} + // ToolchainUsed is the toolchain a build was made with: the language, the installed version and the build module's recipe. type ToolchainUsed struct { + // the building node's digest of its toolchain program (kind 3's entry), never the build module's own program + Digest string `json:"digest,omitempty"` Language string `json:"language"` Recipe string `json:"recipe"` Version string `json:"version"` @@ -910,20 +1025,28 @@ const ( type StepsInput struct { // find them anew, not from the kept answer Again bool `json:"again,omitempty"` - // the artifact reference, : - Artifact string `json:"artifact"` - Fetch Fetch `json:"fetch"` + // the key of detected-steps, . + Artifact string `json:"artifact"` + // delete the artifact's kept key instead of detecting; needs why + Drop bool `json:"drop,omitempty"` + Fetch *Fetch `json:"fetch,omitempty"` Fingerprint string `json:"fingerprint,omitempty"` Module string `json:"module"` + // with drop, why: a non-empty text, written in the hand-act log with the caller the bus proved + Why string `json:"why,omitempty"` } // StepsAnswer is what steps answers. type StepsAnswer struct { - Artifact string `json:"artifact"` + Artifact string `json:"artifact,omitempty"` + // the artifact whose key a drop deleted + Dropped string `json:"dropped,omitempty"` + // why a drop deleted nothing, naming the hand-act log when it refused + Error string `json:"error,omitempty"` Fingerprint string `json:"fingerprint,omitempty"` // answered from the kept answer Kept bool `json:"kept,omitempty"` - Steps []DetectedStep `json:"steps"` + Steps []DetectedStep `json:"steps,omitempty"` } // DeliveryAccept is the body of delivery. @@ -944,6 +1067,15 @@ type RecheckAccept = StepAsk // StepEndedEvent is the body of the event step-ended. type StepEndedEvent = StepEnded +// ToolchainReportedEvent is the body of the event toolchain-reported. +type ToolchainReportedEvent = ToolchainReported + +// StepProgressEvent is the body of the event step-progress. +type StepProgressEvent = StepProgress + +// StepsByAskerNodeIDValue is one value of the state steps under the key ... +type StepsByAskerNodeIDValue = Step + // StepsByNodeIDValue is one value of the state steps under the key .. type StepsByNodeIDValue = Step diff --git a/contracts/go/seats/nodebuildimage/contract_test.go b/contracts/go/seats/nodebuildimage/contract_test.go index fbbbb9c8..3ec60a4f 100644 --- a/contracts/go/seats/nodebuildimage/contract_test.go +++ b/contracts/go/seats/nodebuildimage/contract_test.go @@ -21,6 +21,10 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v StepEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-image\\.event\\.step-ended\\.[^.*> ]+$")}, {name: "a lint step that could not run, naming the part not heard", subject: "mesh.seat.node-build-image.event.step-ended.g14", given: "{\"artifact\":\"nats:program\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"fingerprint\":\"fp-0123\",\"id\":\"m2kx9a1b3c4d5f\",\"module\":\"nats\",\"node\":\"g14\",\"outcome\":\"could not run\",\"output-object\":\"mesh-build-go_sent-objects/m2kx9a1b3c4d5f\",\"pipeline\":\"main.12.nats\",\"said\":\"the sandbox could not fetch novox/mesh-sdk\",\"step\":\"lint\",\"v\":1}", wire: "{\"artifact\":\"nats:program\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"fingerprint\":\"fp-0123\",\"id\":\"m2kx9a1b3c4d5f\",\"module\":\"nats\",\"node\":\"g14\",\"outcome\":\"could not run\",\"output-object\":\"mesh-build-go_sent-objects/m2kx9a1b3c4d5f\",\"pipeline\":\"main.12.nats\",\"said\":\"the sandbox could not fetch novox/mesh-sdk\",\"step\":\"lint\",\"v\":1}", decode: func(b []byte) (any, error) { var v StepEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-image\\.event\\.step-ended\\.[^.*> ]+$")}, + {name: "a node's toolchain reported", subject: "mesh.seat.node-build-image.event.toolchain-reported.ace", given: "{\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"language\":\"go\",\"node\":\"ace\",\"recipe\":\"1\",\"version\":\"1.26.0\"}", wire: "{\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"language\":\"go\",\"node\":\"ace\",\"recipe\":\"1\",\"version\":\"1.26.0\"}", + decode: func(b []byte) (any, error) { var v ToolchainReportedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-image\\.event\\.toolchain-reported\\.[^.*> ]+$")}, + {name: "a running ask says its progress", subject: "mesh.seat.node-build-image.event.step-progress.ace", given: "{\"ask\":\"a1b2\",\"asker\":\"runner\",\"at\":\"2026-11-11T10:01:00Z\",\"phase\":\"fetch\"}", wire: "{\"ask\":\"a1b2\",\"asker\":\"runner\",\"at\":\"2026-11-11T10:01:00Z\",\"phase\":\"fetch\"}", + decode: func(b []byte) (any, error) { var v StepProgressEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-image\\.event\\.step-progress\\.[^.*> ]+$")}, {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}", @@ -57,6 +61,12 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, + {name: "a step keyed under its asker", 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 StepsByAskerNodeIDValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, {name: "drop names the ask and why", subject: "", given: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}", wire: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}", decode: func(b []byte) (any, error) { var v DropInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "kept-artifact asks", subject: "", given: "{\"digest\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"fp\":\"src2:1480985e\",\"module\":\"artifact-store\"}", wire: "{\"digest\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"fp\":\"src2:1480985e\",\"module\":\"artifact-store\"}", @@ -75,6 +85,12 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v StepsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "steps answers lint and unit tests", subject: "", given: "{\"artifact\":\"nats:program\",\"fingerprint\":\"fp-0123\",\"steps\":[{\"step\":\"lint\"},{\"packages\":[\"./modules/nats\"],\"step\":\"unit\"}]}", wire: "{\"artifact\":\"nats:program\",\"fingerprint\":\"fp-0123\",\"steps\":[{\"step\":\"lint\"},{\"packages\":[\"./modules/nats\"],\"step\":\"unit\"}]}", decode: func(b []byte) (any, error) { var v StepsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "steps drops an artifact's kept steps with a reason", subject: "", given: "{\"artifact\":\"nats.program\",\"drop\":true,\"module\":\"nats\",\"why\":\"the detection was wrong\"}", wire: "{\"artifact\":\"nats.program\",\"drop\":true,\"module\":\"nats\",\"why\":\"the detection was wrong\"}", + decode: func(b []byte) (any, error) { var v StepsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "steps answers a drop with the artifact", subject: "", given: "{\"dropped\":\"nats.program\"}", wire: "{\"dropped\":\"nats.program\"}", + decode: func(b []byte) (any, error) { var v StepsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "steps answers a drop that the hand-act log refused", subject: "", given: "{\"error\":\"the hand-act log refused: nothing was deleted\"}", wire: "{\"error\":\"the hand-act log refused: nothing was deleted\"}", + decode: func(b []byte) (any, error) { var v StepsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, } for _, c := range cases { v, err := c.decode([]byte(c.given)) diff --git a/contracts/go/seats/nodebuildnode/contract.go b/contracts/go/seats/nodebuildnode/contract.go index 2df88e8f..5004106f 100644 --- a/contracts/go/seats/nodebuildnode/contract.go +++ b/contracts/go/seats/nodebuildnode/contract.go @@ -32,7 +32,9 @@ const ( ) const ( - EventStepEnded = "step-ended" + EventStepEnded = "step-ended" + EventToolchainReported = "toolchain-reported" + EventStepProgress = "step-progress" ) // Verbs is every verb the seat serves, in its definition's order: a new copy each call. @@ -42,7 +44,7 @@ func Verbs() []string { // Events is every event published on the seat's subject: a new copy each call. func Events() []string { - return []string{EventStepEnded} + return []string{EventStepEnded, EventToolchainReported, EventStepProgress} } // Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. @@ -62,6 +64,28 @@ func SubjectStepEnded(node string) string { // SubjectStepEndedAll hears every step-ended. const SubjectStepEndedAll = "mesh.seat.node-build-node.event.step-ended.*" +// StreamToolchainReported is the stream toolchain-reported is kept in. +const StreamToolchainReported = "SEAT_node-build-node_toolchain-reported" + +// SubjectToolchainReported is where toolchain-reported is published, on mesh.seat.node-build-node.event.toolchain-reported.. +func SubjectToolchainReported(node string) string { + return "mesh.seat.node-build-node.event.toolchain-reported." + node +} + +// SubjectToolchainReportedAll hears every toolchain-reported. +const SubjectToolchainReportedAll = "mesh.seat.node-build-node.event.toolchain-reported.*" + +// StreamStepProgress is the stream step-progress is kept in. +const StreamStepProgress = "SEAT_node-build-node_step-progress" + +// SubjectStepProgress is where step-progress is published, on mesh.seat.node-build-node.event.step-progress.. +func SubjectStepProgress(node string) string { + return "mesh.seat.node-build-node.event.step-progress." + node +} + +// SubjectStepProgressAll hears every step-progress. +const SubjectStepProgressAll = "mesh.seat.node-build-node.event.step-progress.*" + // AcceptSubjectDelivery is where delivery is sent. const AcceptSubjectDelivery = "mesh.seat.node-build-node.accept.delivery" @@ -84,6 +108,47 @@ const ( StateDetectedSteps = "detected-steps" ) +// StepsByAskerNodeIDKey is the key of one value of the state steps under the form "..". +func StepsByAskerNodeIDKey(asker, node, id string) string { return asker + "." + node + "." + id } + +// ParseStepsByAskerNodeIDKey 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 ParseStepsByAskerNodeIDKey(key string) (string, string, string, bool) { + rest := key + v0, after0, found0 := strings.Cut(rest, ".") + if !found0 || v0 == "" { + return "", "", "", false + } + rest = after0 + v2, after2, found2 := strings.Cut(rest, ".") + if !found2 || v2 == "" { + return "", "", "", false + } + rest = after2 + if rest == "" { + return "", "", "", false + } + v4 := rest + rest = "" + if rest != "" { + return "", "", "", false + } + return v0, v2, v4, true +} + +// DecodeStepsByAskerNodeIDValue reads one value of the state steps under the form "..", refusing a field StepsByAskerNodeIDValue does not know and any text after the value. +func DecodeStepsByAskerNodeIDValue(raw []byte) (StepsByAskerNodeIDValue, error) { + var v StepsByAskerNodeIDValue + 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 +} + // StepsByNodeIDKey is the key of one value of the state steps under the form ".". func StepsByNodeIDKey(node, id string) string { return node + "." + id } @@ -315,11 +380,21 @@ type BuildAnswer struct { Toolchain *ToolchainUsed `json:"toolchain,omitempty"` } +// BuiltArtifactResolvedItem is a type of the contract. +type BuiltArtifactResolvedItem struct { + // sha256: of the package's bytes as the build fetched them + Digest string `json:"digest"` + Package string `json:"package"` + Version string `json:"version"` +} + // BuiltArtifact is one artifact a build made: its digest and the reference it was published under. type BuiltArtifact struct { Artifact string `json:"artifact"` Digest string `json:"digest"` Reference string `json:"reference,omitempty"` + // one per mesh package the build resolved (the catalog graph entry's packages, the edges of kind 1) + Resolved []BuiltArtifactResolvedItem `json:"resolved,omitempty"` } // Capacity is how many steps the node runs at once. @@ -394,6 +469,16 @@ type FetchBesideItem struct { Repository string `json:"repository"` } +// FetchBuildsItem is a type of the contract. +type FetchBuildsItem struct { + Artifact string `json:"artifact"` + // the member's build digest, sha256: + Digest string `json:"digest"` + Module string `json:"module"` + // the name in its ecosystem (the Go module path, the npm name) + Package string `json:"package"` +} + // FetchPackage is the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's. type FetchPackage struct { Commit string `json:"commit,omitempty"` @@ -409,6 +494,8 @@ type Fetch struct { Base string `json:"base,omitempty"` // the sibling repositories placed beside the fetched tree Beside []FetchBesideItem `json:"beside,omitempty"` + // a group run's package members, each by its build's digest: the build module reads each from the artifact store at artifact-store:////blobs/, checks the bytes against the digest and lays them as the group package; a package named here is named in neither packages nor package + Builds []FetchBuildsItem `json:"builds,omitempty"` // the full commit Commit string `json:"commit"` // the module's directory in the repository ("." for its root), with paths @@ -733,6 +820,22 @@ type StepNames struct { Repositories []string `json:"repositories"` } +// StepProgress is a running ask's progress, said once a minute while the holder runs it (design 47 G section 496). +type StepProgress struct { + Ask string `json:"ask"` + Asker string `json:"asker"` + At time.Time `json:"at"` + Phase string `json:"phase"` +} + +// The values of StepProgress's phase. +const ( + StepProgressPhaseFetch = "fetch" + StepProgressPhaseBuild = "build" + StepProgressPhaseRun = "run" + StepProgressPhaseReport = "report" +) + // StepResult is what a step answered, by the kind of ask: a build's artifacts, toolchain, imports, out-of-step modules and the artifact it kept; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five. type StepResult struct { Artifacts []BuiltArtifact `json:"artifacts,omitempty"` @@ -826,8 +929,20 @@ type Toolchain struct { Version string `json:"version"` } +// ToolchainReported is a node's toolchain of a language, said at the build module's start and each time its toolchains key of the language changes (design 47 G section 492). +type ToolchainReported struct { + // the digest of the program of the provision the build module requires for that language's toolchain, never the build module's own + Digest string `json:"digest"` + Language string `json:"language"` + Node string `json:"node"` + Recipe string `json:"recipe"` + Version string `json:"version"` +} + // ToolchainUsed is the toolchain a build was made with: the language, the installed version and the build module's recipe. type ToolchainUsed struct { + // the building node's digest of its toolchain program (kind 3's entry), never the build module's own program + Digest string `json:"digest,omitempty"` Language string `json:"language"` Recipe string `json:"recipe"` Version string `json:"version"` @@ -910,20 +1025,28 @@ const ( type StepsInput struct { // find them anew, not from the kept answer Again bool `json:"again,omitempty"` - // the artifact reference, : - Artifact string `json:"artifact"` - Fetch Fetch `json:"fetch"` + // the key of detected-steps, . + Artifact string `json:"artifact"` + // delete the artifact's kept key instead of detecting; needs why + Drop bool `json:"drop,omitempty"` + Fetch *Fetch `json:"fetch,omitempty"` Fingerprint string `json:"fingerprint,omitempty"` Module string `json:"module"` + // with drop, why: a non-empty text, written in the hand-act log with the caller the bus proved + Why string `json:"why,omitempty"` } // StepsAnswer is what steps answers. type StepsAnswer struct { - Artifact string `json:"artifact"` + Artifact string `json:"artifact,omitempty"` + // the artifact whose key a drop deleted + Dropped string `json:"dropped,omitempty"` + // why a drop deleted nothing, naming the hand-act log when it refused + Error string `json:"error,omitempty"` Fingerprint string `json:"fingerprint,omitempty"` // answered from the kept answer Kept bool `json:"kept,omitempty"` - Steps []DetectedStep `json:"steps"` + Steps []DetectedStep `json:"steps,omitempty"` } // DeliveryAccept is the body of delivery. @@ -944,6 +1067,15 @@ type RecheckAccept = StepAsk // StepEndedEvent is the body of the event step-ended. type StepEndedEvent = StepEnded +// ToolchainReportedEvent is the body of the event toolchain-reported. +type ToolchainReportedEvent = ToolchainReported + +// StepProgressEvent is the body of the event step-progress. +type StepProgressEvent = StepProgress + +// StepsByAskerNodeIDValue is one value of the state steps under the key ... +type StepsByAskerNodeIDValue = Step + // StepsByNodeIDValue is one value of the state steps under the key .. type StepsByNodeIDValue = Step diff --git a/contracts/go/seats/nodebuildnode/contract_test.go b/contracts/go/seats/nodebuildnode/contract_test.go index 982cd713..931da365 100644 --- a/contracts/go/seats/nodebuildnode/contract_test.go +++ b/contracts/go/seats/nodebuildnode/contract_test.go @@ -21,6 +21,10 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v StepEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-node\\.event\\.step-ended\\.[^.*> ]+$")}, {name: "an integration step that could not run, naming the part not heard", subject: "mesh.seat.node-build-node.event.step-ended.g14", given: "{\"artifact\":\"nats:program\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"fingerprint\":\"fp-0123\",\"id\":\"m2kx9a1b3c4d5f\",\"module\":\"nats\",\"node\":\"g14\",\"outcome\":\"could not run\",\"output-object\":\"mesh-build-go_sent-objects/m2kx9a1b3c4d5f\",\"pipeline\":\"main.12.nats\",\"said\":\"the sandbox could not fetch novox/mesh-sdk\",\"step\":\"integration\",\"v\":1}", wire: "{\"artifact\":\"nats:program\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"fingerprint\":\"fp-0123\",\"id\":\"m2kx9a1b3c4d5f\",\"module\":\"nats\",\"node\":\"g14\",\"outcome\":\"could not run\",\"output-object\":\"mesh-build-go_sent-objects/m2kx9a1b3c4d5f\",\"pipeline\":\"main.12.nats\",\"said\":\"the sandbox could not fetch novox/mesh-sdk\",\"step\":\"integration\",\"v\":1}", decode: func(b []byte) (any, error) { var v StepEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-node\\.event\\.step-ended\\.[^.*> ]+$")}, + {name: "a node's toolchain reported", subject: "mesh.seat.node-build-node.event.toolchain-reported.ace", given: "{\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"language\":\"go\",\"node\":\"ace\",\"recipe\":\"1\",\"version\":\"1.26.0\"}", wire: "{\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"language\":\"go\",\"node\":\"ace\",\"recipe\":\"1\",\"version\":\"1.26.0\"}", + decode: func(b []byte) (any, error) { var v ToolchainReportedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-node\\.event\\.toolchain-reported\\.[^.*> ]+$")}, + {name: "a running ask says its progress", subject: "mesh.seat.node-build-node.event.step-progress.ace", given: "{\"ask\":\"a1b2\",\"asker\":\"runner\",\"at\":\"2026-11-11T10:01:00Z\",\"phase\":\"fetch\"}", wire: "{\"ask\":\"a1b2\",\"asker\":\"runner\",\"at\":\"2026-11-11T10:01:00Z\",\"phase\":\"fetch\"}", + decode: func(b []byte) (any, error) { var v StepProgressEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-node\\.event\\.step-progress\\.[^.*> ]+$")}, {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}", @@ -61,6 +65,12 @@ func TestFixtures(t *testing.T) { err := json.Unmarshal(b, &v) return v, err }, subjectIs: nil}, + {name: "a step keyed under its asker", 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 StepsByAskerNodeIDValue + err := json.Unmarshal(b, &v) + return v, err + }, subjectIs: nil}, {name: "drop names the ask and why", subject: "", given: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}", wire: "{\"by\":\"mesh-delivery-builder\",\"id\":\"a1b2\",\"why\":\"a newer fingerprint supersedes it\"}", decode: func(b []byte) (any, error) { var v DropInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "kept-artifact asks", subject: "", given: "{\"digest\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"fp\":\"src2:1480985e\",\"module\":\"artifact-store\"}", wire: "{\"digest\":\"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd\",\"fp\":\"src2:1480985e\",\"module\":\"artifact-store\"}", @@ -79,6 +89,12 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v StepsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "steps answers lint and unit tests", subject: "", given: "{\"artifact\":\"nats:program\",\"fingerprint\":\"fp-0123\",\"steps\":[{\"step\":\"lint\"},{\"packages\":[\"./modules/nats\"],\"step\":\"unit\"}]}", wire: "{\"artifact\":\"nats:program\",\"fingerprint\":\"fp-0123\",\"steps\":[{\"step\":\"lint\"},{\"packages\":[\"./modules/nats\"],\"step\":\"unit\"}]}", decode: func(b []byte) (any, error) { var v StepsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "steps drops an artifact's kept steps with a reason", subject: "", given: "{\"artifact\":\"nats.program\",\"drop\":true,\"module\":\"nats\",\"why\":\"the detection was wrong\"}", wire: "{\"artifact\":\"nats.program\",\"drop\":true,\"module\":\"nats\",\"why\":\"the detection was wrong\"}", + decode: func(b []byte) (any, error) { var v StepsInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "steps answers a drop with the artifact", subject: "", given: "{\"dropped\":\"nats.program\"}", wire: "{\"dropped\":\"nats.program\"}", + decode: func(b []byte) (any, error) { var v StepsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, + {name: "steps answers a drop that the hand-act log refused", subject: "", given: "{\"error\":\"the hand-act log refused: nothing was deleted\"}", wire: "{\"error\":\"the hand-act log refused: nothing was deleted\"}", + decode: func(b []byte) (any, error) { var v StepsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, } for _, c := range cases { v, err := c.decode([]byte(c.given)) diff --git a/contracts/parts/build-step.json b/contracts/parts/build-step.json index 3aed594f..2179c177 100644 --- a/contracts/parts/build-step.json +++ b/contracts/parts/build-step.json @@ -159,10 +159,12 @@ }, { "name": "steps", - "description": "Which pipeline steps an artifact can run, found by its language's convention (design 47 *Pipeline steps and the policy*): lint always, unit and integration tests where its packages hold them. Kept by the artifact's fingerprint; again finds them anew.", + "description": "Which pipeline steps an artifact can run, found by its language's convention (design 47 *Pipeline steps and the policy*): lint always, unit and integration tests where its packages hold them. Kept by the artifact's fingerprint; again finds them anew. With drop and why it deletes the artifact's kept key instead, writes the act in the hand-act log of the seat mesh-hand-acts first (a log that refuses deletes nothing and answers error), and answers dropped (design 47 G section 463, section 480).", "callers": [ "seat:mesh-pipeline-runner", - "seat:mesh-catalog" + "seat:mesh-catalog", + "operator", + "sessions" ], "input": { "type": "object", @@ -172,7 +174,7 @@ }, "artifact": { "type": "string", - "description": "the artifact reference, :" + "description": "the key of detected-steps, ." }, "fingerprint": { "type": "string" @@ -183,13 +185,21 @@ "again": { "type": "boolean", "description": "find them anew, not from the kept answer" + }, + "drop": { + "type": "boolean", + "description": "delete the artifact's kept key instead of detecting; needs why" + }, + "why": { + "type": "string", + "description": "with drop, why: a non-empty text, written in the hand-act log with the caller the bus proved" } }, "required": [ "module", - "artifact", - "fetch" - ] + "artifact" + ], + "description": "fetch is required unless drop is given, and why with drop" }, "answer": { "type": "object", @@ -209,12 +219,17 @@ "kept": { "type": "boolean", "description": "answered from the kept answer" + }, + "dropped": { + "type": "string", + "description": "the artifact whose key a drop deleted" + }, + "error": { + "type": "string", + "description": "why a drop deleted nothing, naming the hand-act log when it refused" } }, - "required": [ - "artifact", - "steps" - ] + "required": [] }, "class": "act" } @@ -857,6 +872,35 @@ ] }, "description": "the sibling repositories placed beside the fetched tree" + }, + "builds": { + "type": "array", + "description": "a group run's package members, each by its build's digest: the build module reads each from the artifact store at artifact-store:////blobs/, checks the bytes against the digest and lays them as the group package; a package named here is named in neither packages nor package", + "items": { + "type": "object", + "properties": { + "package": { + "type": "string", + "description": "the name in its ecosystem (the Go module path, the npm name)" + }, + "module": { + "type": "string" + }, + "artifact": { + "type": "string" + }, + "digest": { + "type": "string", + "description": "the member's build digest, sha256:" + } + }, + "required": [ + "package", + "module", + "artifact", + "digest" + ] + } } }, "required": [ @@ -875,6 +919,10 @@ }, "recipe": { "type": "string" + }, + "digest": { + "type": "string", + "description": "the building node's digest of its toolchain program (kind 3's entry), never the build module's own program" } }, "required": [ @@ -916,6 +964,30 @@ }, "reference": { "type": "string" + }, + "resolved": { + "type": "array", + "description": "one per mesh package the build resolved (the catalog graph entry's packages, the edges of kind 1)", + "items": { + "type": "object", + "properties": { + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "digest": { + "type": "string", + "description": "sha256: of the package's bytes as the build fetched them" + } + }, + "required": [ + "package", + "version", + "digest" + ] + } } }, "required": [ @@ -1628,6 +1700,66 @@ "fingerprint", "steps" ] + }, + "ToolchainReported": { + "type": "object", + "description": "a node's toolchain of a language, said at the build module's start and each time its toolchains key of the language changes (design 47 G section 492)", + "properties": { + "node": { + "type": "string" + }, + "language": { + "type": "string" + }, + "version": { + "type": "string" + }, + "recipe": { + "type": "string" + }, + "digest": { + "type": "string", + "description": "the digest of the program of the provision the build module requires for that language's toolchain, never the build module's own" + } + }, + "required": [ + "node", + "language", + "version", + "recipe", + "digest" + ] + }, + "StepProgress": { + "type": "object", + "description": "a running ask's progress, said once a minute while the holder runs it (design 47 G section 496)", + "properties": { + "asker": { + "type": "string" + }, + "ask": { + "type": "string" + }, + "phase": { + "type": "string", + "enum": [ + "fetch", + "build", + "run", + "report" + ] + }, + "at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "asker", + "ask", + "phase", + "at" + ] } }, "events": [ @@ -1641,6 +1773,28 @@ "max-age-days": 90, "max-mib": 128 } + }, + { + "name": "toolchain-reported", + "subject": "toolchain-reported.", + "body": { + "$ref": "#/defs/ToolchainReported" + }, + "keep": { + "max-age-days": 90, + "max-mib": 16 + } + }, + { + "name": "step-progress", + "subject": "step-progress.", + "body": { + "$ref": "#/defs/StepProgress" + }, + "keep": { + "max-age-days": 1, + "max-mib": 16 + } } ] } diff --git a/contracts/seats/mesh-wants/fixtures/events.json b/contracts/seats/mesh-wants/fixtures/events.json index 70fbc77c..7c00e058 100644 --- a/contracts/seats/mesh-wants/fixtures/events.json +++ b/contracts/seats/mesh-wants/fixtures/events.json @@ -166,5 +166,16 @@ "laptop" ] } + }, + { + "name": "a composition step's progress", + "of": "event:step-progress", + "subject": "mesh.seat.mesh-wants.event.step-progress.ace", + "given": { + "asker": "assigner", + "ask": "a1b2", + "phase": "build", + "at": "2026-11-11T10:01:00Z" + } } ] diff --git a/contracts/seats/mesh-wants/seat.json b/contracts/seats/mesh-wants/seat.json index e002258b..aca8f3b9 100644 --- a/contracts/seats/mesh-wants/seat.json +++ b/contracts/seats/mesh-wants/seat.json @@ -427,6 +427,45 @@ "parts" ] } + }, + { + "name": "step-progress", + "subject": "step-progress.", + "keep": { + "max-age-days": 1, + "max-mib": 16 + }, + "body": { + "type": "object", + "description": "a composition step's progress, said once a minute while the assigner runs it (design 47 G section 496)", + "properties": { + "asker": { + "type": "string" + }, + "ask": { + "type": "string" + }, + "phase": { + "type": "string", + "enum": [ + "fetch", + "build", + "run", + "report" + ] + }, + "at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "asker", + "ask", + "phase", + "at" + ] + } } ], "family": { diff --git a/contracts/seats/node-build-go/fixtures/events.json b/contracts/seats/node-build-go/fixtures/events.json index 06ad58d6..1d3351e3 100644 --- a/contracts/seats/node-build-go/fixtures/events.json +++ b/contracts/seats/node-build-go/fixtures/events.json @@ -53,5 +53,28 @@ "said": "the sandbox could not fetch novox/mesh-sdk", "output-object": "mesh-build-go_sent-objects/m2kx9a1b3c4d5f" } + }, + { + "name": "a node's toolchain reported", + "of": "event:toolchain-reported", + "subject": "mesh.seat.node-build-go.event.toolchain-reported.ace", + "given": { + "node": "ace", + "language": "go", + "version": "1.26.0", + "recipe": "1", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + }, + { + "name": "a running ask says its progress", + "of": "event:step-progress", + "subject": "mesh.seat.node-build-go.event.step-progress.ace", + "given": { + "asker": "runner", + "ask": "a1b2", + "phase": "fetch", + "at": "2026-11-11T10:01:00Z" + } } ] diff --git a/contracts/seats/node-build-go/fixtures/states.json b/contracts/seats/node-build-go/fixtures/states.json index 9ee56a47..bb3685b7 100644 --- a/contracts/seats/node-build-go/fixtures/states.json +++ b/contracts/seats/node-build-go/fixtures/states.json @@ -526,5 +526,17 @@ ], "at": "2026-11-10T10:00:00Z" } + }, + { + "name": "a step keyed under its asker", + "of": "state:steps/..", + "given": { + "v": 1, + "id": "a1b2", + "tool": "build", + "node": "ace", + "state": "running", + "started": "2026-11-05T10:00:00Z" + } } ] diff --git a/contracts/seats/node-build-go/fixtures/verbs.json b/contracts/seats/node-build-go/fixtures/verbs.json index 1e4af4dd..7a02ff62 100644 --- a/contracts/seats/node-build-go/fixtures/verbs.json +++ b/contracts/seats/node-build-go/fixtures/verbs.json @@ -100,5 +100,29 @@ } ] } + }, + { + "name": "steps drops an artifact's kept steps with a reason", + "of": "verb:steps.input", + "given": { + "module": "nats", + "artifact": "nats.program", + "drop": true, + "why": "the detection was wrong" + } + }, + { + "name": "steps answers a drop with the artifact", + "of": "verb:steps.answer", + "given": { + "dropped": "nats.program" + } + }, + { + "name": "steps answers a drop that the hand-act log refused", + "of": "verb:steps.answer", + "given": { + "error": "the hand-act log refused: nothing was deleted" + } } ] diff --git a/contracts/seats/node-build-go/seat.json b/contracts/seats/node-build-go/seat.json index 4d30fcd7..f9be14f8 100644 --- a/contracts/seats/node-build-go/seat.json +++ b/contracts/seats/node-build-go/seat.json @@ -87,12 +87,19 @@ "name": "steps", "template": "state", "key-forms": [ + { + "key": "..", + "value": { + "$ref": "#/defs/Step" + }, + "description": "a step on a node, keyed by the asker the bus proves (design 47 G section 467, section 481)" + }, { "key": ".", "value": { "$ref": "#/defs/Step" }, - "description": "a step on a node" + "description": "a step whose ask came on main's subject, with no asker's name (design 47 G section 483)" } ], "description": "the steps this module runs", diff --git a/contracts/seats/node-build-image/fixtures/events.json b/contracts/seats/node-build-image/fixtures/events.json index 1e20d2ed..e8dca58f 100644 --- a/contracts/seats/node-build-image/fixtures/events.json +++ b/contracts/seats/node-build-image/fixtures/events.json @@ -36,5 +36,28 @@ "said": "the sandbox could not fetch novox/mesh-sdk", "output-object": "mesh-build-go_sent-objects/m2kx9a1b3c4d5f" } + }, + { + "name": "a node's toolchain reported", + "of": "event:toolchain-reported", + "subject": "mesh.seat.node-build-image.event.toolchain-reported.ace", + "given": { + "node": "ace", + "language": "go", + "version": "1.26.0", + "recipe": "1", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + }, + { + "name": "a running ask says its progress", + "of": "event:step-progress", + "subject": "mesh.seat.node-build-image.event.step-progress.ace", + "given": { + "asker": "runner", + "ask": "a1b2", + "phase": "fetch", + "at": "2026-11-11T10:01:00Z" + } } ] diff --git a/contracts/seats/node-build-image/fixtures/states.json b/contracts/seats/node-build-image/fixtures/states.json index 3cbef09e..149ea1ca 100644 --- a/contracts/seats/node-build-image/fixtures/states.json +++ b/contracts/seats/node-build-image/fixtures/states.json @@ -186,5 +186,17 @@ ], "at": "2026-11-10T10:00:00Z" } + }, + { + "name": "a step keyed under its asker", + "of": "state:steps/..", + "given": { + "v": 1, + "id": "a1b2", + "tool": "build", + "node": "ace", + "state": "running", + "started": "2026-11-05T10:00:00Z" + } } ] diff --git a/contracts/seats/node-build-image/fixtures/verbs.json b/contracts/seats/node-build-image/fixtures/verbs.json index 1e4af4dd..7a02ff62 100644 --- a/contracts/seats/node-build-image/fixtures/verbs.json +++ b/contracts/seats/node-build-image/fixtures/verbs.json @@ -100,5 +100,29 @@ } ] } + }, + { + "name": "steps drops an artifact's kept steps with a reason", + "of": "verb:steps.input", + "given": { + "module": "nats", + "artifact": "nats.program", + "drop": true, + "why": "the detection was wrong" + } + }, + { + "name": "steps answers a drop with the artifact", + "of": "verb:steps.answer", + "given": { + "dropped": "nats.program" + } + }, + { + "name": "steps answers a drop that the hand-act log refused", + "of": "verb:steps.answer", + "given": { + "error": "the hand-act log refused: nothing was deleted" + } } ] diff --git a/contracts/seats/node-build-image/seat.json b/contracts/seats/node-build-image/seat.json index 2d5f83ab..5528da1f 100644 --- a/contracts/seats/node-build-image/seat.json +++ b/contracts/seats/node-build-image/seat.json @@ -65,12 +65,19 @@ "name": "steps", "template": "state", "key-forms": [ + { + "key": "..", + "value": { + "$ref": "#/defs/Step" + }, + "description": "a step on a node, keyed by the asker the bus proves (design 47 G section 467, section 481)" + }, { "key": ".", "value": { "$ref": "#/defs/Step" }, - "description": "a step on a node" + "description": "a step whose ask came on main's subject, with no asker's name (design 47 G section 483)" } ], "description": "the steps this module runs", diff --git a/contracts/seats/node-build-node/fixtures/events.json b/contracts/seats/node-build-node/fixtures/events.json index 0fb1ac2d..1f1924a0 100644 --- a/contracts/seats/node-build-node/fixtures/events.json +++ b/contracts/seats/node-build-node/fixtures/events.json @@ -53,5 +53,28 @@ "said": "the sandbox could not fetch novox/mesh-sdk", "output-object": "mesh-build-go_sent-objects/m2kx9a1b3c4d5f" } + }, + { + "name": "a node's toolchain reported", + "of": "event:toolchain-reported", + "subject": "mesh.seat.node-build-node.event.toolchain-reported.ace", + "given": { + "node": "ace", + "language": "go", + "version": "1.26.0", + "recipe": "1", + "digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + }, + { + "name": "a running ask says its progress", + "of": "event:step-progress", + "subject": "mesh.seat.node-build-node.event.step-progress.ace", + "given": { + "asker": "runner", + "ask": "a1b2", + "phase": "fetch", + "at": "2026-11-11T10:01:00Z" + } } ] diff --git a/contracts/seats/node-build-node/fixtures/states.json b/contracts/seats/node-build-node/fixtures/states.json index ccf06869..e077d0d6 100644 --- a/contracts/seats/node-build-node/fixtures/states.json +++ b/contracts/seats/node-build-node/fixtures/states.json @@ -274,5 +274,17 @@ ], "at": "2026-11-10T10:00:00Z" } + }, + { + "name": "a step keyed under its asker", + "of": "state:steps/..", + "given": { + "v": 1, + "id": "a1b2", + "tool": "build", + "node": "ace", + "state": "running", + "started": "2026-11-05T10:00:00Z" + } } ] diff --git a/contracts/seats/node-build-node/fixtures/verbs.json b/contracts/seats/node-build-node/fixtures/verbs.json index 1e4af4dd..7a02ff62 100644 --- a/contracts/seats/node-build-node/fixtures/verbs.json +++ b/contracts/seats/node-build-node/fixtures/verbs.json @@ -100,5 +100,29 @@ } ] } + }, + { + "name": "steps drops an artifact's kept steps with a reason", + "of": "verb:steps.input", + "given": { + "module": "nats", + "artifact": "nats.program", + "drop": true, + "why": "the detection was wrong" + } + }, + { + "name": "steps answers a drop with the artifact", + "of": "verb:steps.answer", + "given": { + "dropped": "nats.program" + } + }, + { + "name": "steps answers a drop that the hand-act log refused", + "of": "verb:steps.answer", + "given": { + "error": "the hand-act log refused: nothing was deleted" + } } ] diff --git a/contracts/seats/node-build-node/seat.json b/contracts/seats/node-build-node/seat.json index 978f9dde..910ff77c 100644 --- a/contracts/seats/node-build-node/seat.json +++ b/contracts/seats/node-build-node/seat.json @@ -72,12 +72,19 @@ "name": "steps", "template": "state", "key-forms": [ + { + "key": "..", + "value": { + "$ref": "#/defs/Step" + }, + "description": "a step on a node, keyed by the asker the bus proves (design 47 G section 467, section 481)" + }, { "key": ".", "value": { "$ref": "#/defs/Step" }, - "description": "a step on a node" + "description": "a step whose ask came on main's subject, with no asker's name (design 47 G section 483)" } ], "description": "the steps this module runs", diff --git a/contracts/ts/seats/meshwants/index.d.ts b/contracts/ts/seats/meshwants/index.d.ts index 804dc8c1..a2760e6c 100644 --- a/contracts/ts/seats/meshwants/index.d.ts +++ b/contracts/ts/seats/meshwants/index.d.ts @@ -29,6 +29,7 @@ export declare const events: { readonly room: "room"; readonly stepEnded: "step-ended"; readonly versionDelivered: "version-delivered"; + readonly stepProgress: "step-progress"; }; export declare const family: { readonly name: "mesh-wants.events"; readonly maxAgeDays: 7 }; @@ -61,6 +62,12 @@ export declare const versionDeliveredStream: "SEAT_mesh-wants_version-delivered" export declare const versionDeliveredSubject: "mesh.seat.mesh-wants.event.version-delivered"; +export declare const stepProgressStream: "SEAT_mesh-wants_step-progress"; + +export declare function stepProgressSubject(node: string): string; + +export declare const stepProgressSubjectAll: "mesh.seat.mesh-wants.event.step-progress.*"; + export declare const stepSubject: "mesh.seat.mesh-wants.accept.step"; export declare const states: { @@ -474,6 +481,14 @@ export interface VersionDeliveredEvent { "version": string; } +/** is the body of the event step-progress. */ +export interface StepProgressEvent { + "ask": string; + "asker": string; + "at": string; + "phase": "fetch" | "build" | "run" | "report"; +} + /** is one value of the state wants under the key want... */ export interface WantsByWantNodeModuleValue { /** again or skip */ diff --git a/contracts/ts/seats/meshwants/index.js b/contracts/ts/seats/meshwants/index.js index 96565037..488f1228 100644 --- a/contracts/ts/seats/meshwants/index.js +++ b/contracts/ts/seats/meshwants/index.js @@ -29,6 +29,7 @@ export const events = { room: "room", stepEnded: "step-ended", versionDelivered: "version-delivered", + stepProgress: "step-progress", }; export const family = { name: "mesh-wants.events", maxAgeDays: 7 }; @@ -63,6 +64,14 @@ export const versionDeliveredStream = "SEAT_mesh-wants_version-delivered"; export const versionDeliveredSubject = "mesh.seat.mesh-wants.event.version-delivered"; +export const stepProgressStream = "SEAT_mesh-wants_step-progress"; + +export function stepProgressSubject(node) { + return "mesh.seat.mesh-wants.event.step-progress." + node; +} + +export const stepProgressSubjectAll = "mesh.seat.mesh-wants.event.step-progress.*"; + export const stepSubject = "mesh.seat.mesh-wants.accept.step"; export const states = { diff --git a/contracts/ts/seats/nodebuildgo/index.d.ts b/contracts/ts/seats/nodebuildgo/index.d.ts index ff6e8d0e..ac126e27 100644 --- a/contracts/ts/seats/nodebuildgo/index.d.ts +++ b/contracts/ts/seats/nodebuildgo/index.d.ts @@ -26,6 +26,8 @@ export declare const accepts: { export declare const events: { readonly stepEnded: "step-ended"; + readonly toolchainReported: "toolchain-reported"; + readonly stepProgress: "step-progress"; }; export declare const family: { readonly name: "node-build-go.events"; readonly maxAgeDays: 7 }; @@ -36,6 +38,18 @@ export declare function stepEndedSubject(node: string): string; export declare const stepEndedSubjectAll: "mesh.seat.node-build-go.event.step-ended.*"; +export declare const toolchainReportedStream: "SEAT_node-build-go_toolchain-reported"; + +export declare function toolchainReportedSubject(node: string): string; + +export declare const toolchainReportedSubjectAll: "mesh.seat.node-build-go.event.toolchain-reported.*"; + +export declare const stepProgressStream: "SEAT_node-build-go_step-progress"; + +export declare function stepProgressSubject(node: string): string; + +export declare const stepProgressSubjectAll: "mesh.seat.node-build-go.event.step-progress.*"; + export declare const deliverySubject: "mesh.seat.node-build-go.accept.delivery"; export declare const checkSubject: "mesh.seat.node-build-go.accept.check"; @@ -55,6 +69,12 @@ export declare const states: { readonly detectedSteps: "detected-steps"; }; +/** The key of one value of the state steps under the form "..". */ +export declare function stepsByAskerNodeIDKey(asker: string, node: string, id: string): string; + +/** The parts of a key of the form "..", or undefined for a key of another form. */ +export declare function parseStepsByAskerNodeIDKey(_key: string): { asker: string; node: string; id: string } | undefined; + /** The key of one value of the state steps under the form ".". */ export declare function stepsByNodeIDKey(node: string, id: string): string; @@ -94,6 +114,7 @@ export declare function parseDetectedStepsByModuleArtifactKey(_key: string): { m export type AskArtifact = buildStep.AskArtifact; export type BuildAnswer = buildStep.BuildAnswer; export type BuiltArtifact = buildStep.BuiltArtifact; +export type BuiltArtifactResolvedItem = buildStep.BuiltArtifactResolvedItem; export type Capacity = buildStep.Capacity; export type CheckAnswer = buildStep.CheckAnswer; export type ComposeAnswer = buildStep.ComposeAnswer; @@ -107,6 +128,7 @@ export type DropInput = buildStep.DropInput; export type Fetch = buildStep.Fetch; export type FetchAnswer = buildStep.FetchAnswer; export type FetchBesideItem = buildStep.FetchBesideItem; +export type FetchBuildsItem = buildStep.FetchBuildsItem; export type FetchPackage = buildStep.FetchPackage; export type FetchRefusal = buildStep.FetchRefusal; export type KeptArtifactAnswer = buildStep.KeptArtifactAnswer; @@ -139,6 +161,8 @@ export type StepAskCompose = buildStep.StepAskCompose; export type StepEnded = buildStep.StepEnded; export type StepEndedEvent = buildStep.StepEndedEvent; export type StepNames = buildStep.StepNames; +export type StepProgress = buildStep.StepProgress; +export type StepProgressEvent = buildStep.StepProgressEvent; export type StepResult = buildStep.StepResult; export type StepRun = buildStep.StepRun; export type StepsAnswer = buildStep.StepsAnswer; @@ -146,6 +170,8 @@ export type StepsInput = buildStep.StepsInput; export type TakenVersion = buildStep.TakenVersion; export type TestCounts = buildStep.TestCounts; export type Toolchain = buildStep.Toolchain; +export type ToolchainReported = buildStep.ToolchainReported; +export type ToolchainReportedEvent = buildStep.ToolchainReportedEvent; export type ToolchainUsed = buildStep.ToolchainUsed; /** is the body of delivery. */ @@ -166,6 +192,9 @@ export type RecheckAccept = StepAsk; /** is the body of check.run. */ export type CheckRunAccept = StepAsk; +/** is one value of the state steps under the key ... */ +export type StepsByAskerNodeIDValue = Step; + /** is one value of the state steps under the key .. */ export type StepsByNodeIDValue = Step; diff --git a/contracts/ts/seats/nodebuildgo/index.js b/contracts/ts/seats/nodebuildgo/index.js index e80d7956..cf4a3f99 100644 --- a/contracts/ts/seats/nodebuildgo/index.js +++ b/contracts/ts/seats/nodebuildgo/index.js @@ -24,6 +24,8 @@ export const accepts = { export const events = { stepEnded: "step-ended", + toolchainReported: "toolchain-reported", + stepProgress: "step-progress", }; export const family = { name: "node-build-go.events", maxAgeDays: 7 }; @@ -36,6 +38,22 @@ export function stepEndedSubject(node) { export const stepEndedSubjectAll = "mesh.seat.node-build-go.event.step-ended.*"; +export const toolchainReportedStream = "SEAT_node-build-go_toolchain-reported"; + +export function toolchainReportedSubject(node) { + return "mesh.seat.node-build-go.event.toolchain-reported." + node; +} + +export const toolchainReportedSubjectAll = "mesh.seat.node-build-go.event.toolchain-reported.*"; + +export const stepProgressStream = "SEAT_node-build-go_step-progress"; + +export function stepProgressSubject(node) { + return "mesh.seat.node-build-go.event.step-progress." + node; +} + +export const stepProgressSubjectAll = "mesh.seat.node-build-go.event.step-progress.*"; + export const deliverySubject = "mesh.seat.node-build-go.accept.delivery"; export const checkSubject = "mesh.seat.node-build-go.accept.check"; @@ -55,6 +73,29 @@ export const states = { detectedSteps: "detected-steps", }; +/** The key of one value of the state steps under the form "..". */ +export function stepsByAskerNodeIDKey(asker, node, id) { + return asker + "." + node + "." + id; +} + +/** The parts of a key of the form "..", or undefined for a key of another form. */ +export function parseStepsByAskerNodeIDKey(_key) { + let _rest = _key; + const _at0 = _rest.indexOf("."); + if (_at0 <= 0) return undefined; + const asker = _rest.slice(0, _at0); + _rest = _rest.slice(_at0 + 1); + const _at2 = _rest.indexOf("."); + if (_at2 <= 0) return undefined; + const node = _rest.slice(0, _at2); + _rest = _rest.slice(_at2 + 1); + if (_rest === "") return undefined; + const id = _rest; + _rest = ""; + if (_rest !== "") return undefined; + return { asker, node, id }; +} + /** The key of one value of the state steps under the form ".". */ export function stepsByNodeIDKey(node, id) { return node + "." + id; diff --git a/contracts/ts/seats/nodebuildgo/parts/build-step.d.ts b/contracts/ts/seats/nodebuildgo/parts/build-step.d.ts index 3a3c18ba..a6cc4551 100644 --- a/contracts/ts/seats/nodebuildgo/parts/build-step.d.ts +++ b/contracts/ts/seats/nodebuildgo/parts/build-step.d.ts @@ -13,6 +13,8 @@ export declare const verbs: { export declare const events: { readonly stepEnded: "step-ended"; + readonly toolchainReported: "toolchain-reported"; + readonly stepProgress: "step-progress"; }; /** one artifact of a build ask: built from the fetched tree, or copied from an upstream pin */ @@ -32,11 +34,20 @@ export interface BuildAnswer { "toolchain"?: ToolchainUsed; } +export interface BuiltArtifactResolvedItem { + /** sha256: of the package's bytes as the build fetched them */ + "digest": string; + "package": string; + "version": string; +} + /** one artifact a build made: its digest and the reference it was published under */ export interface BuiltArtifact { "artifact": string; "digest": string; "reference"?: string; + /** one per mesh package the build resolved (the catalog graph entry's packages, the edges of kind 1) */ + "resolved"?: BuiltArtifactResolvedItem[]; } /** how many steps the node runs at once */ @@ -100,6 +111,15 @@ export interface FetchBesideItem { "repository": string; } +export interface FetchBuildsItem { + "artifact": string; + /** the member's build digest, sha256: */ + "digest": string; + "module": string; + /** the name in its ecosystem (the Go module path, the npm name) */ + "package": string; +} + /** the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's */ export interface FetchPackage { "commit"?: string; @@ -115,6 +135,8 @@ export interface Fetch { "base"?: string; /** the sibling repositories placed beside the fetched tree */ "beside"?: FetchBesideItem[]; + /** a group run's package members, each by its build's digest: the build module reads each from the artifact store at artifact-store:////blobs/, checks the bytes against the digest and lays them as the group package; a package named here is named in neither packages nor package */ + "builds"?: FetchBuildsItem[]; /** the full commit */ "commit": string; /** the module's directory in the repository ("." for its root), with paths */ @@ -416,6 +438,14 @@ export interface StepNames { "repositories": string[]; } +/** a running ask's progress, said once a minute while the holder runs it (design 47 G section 496) */ +export interface StepProgress { + "ask": string; + "asker": string; + "at": string; + "phase": "fetch" | "build" | "run" | "report"; +} + /** what a step answered, by the kind of ask: a build's artifacts, toolchain, imports, out-of-step modules and the artifact it kept; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five */ export interface StepResult { "artifacts"?: BuiltArtifact[]; @@ -502,8 +532,20 @@ export interface Toolchain { "version": string; } +/** a node's toolchain of a language, said at the build module's start and each time its toolchains key of the language changes (design 47 G section 492) */ +export interface ToolchainReported { + /** the digest of the program of the provision the build module requires for that language's toolchain, never the build module's own */ + "digest": string; + "language": string; + "node": string; + "recipe": string; + "version": string; +} + /** the toolchain a build was made with: the language, the installed version and the build module's recipe */ export interface ToolchainUsed { + /** the building node's digest of its toolchain program (kind 3's entry), never the build module's own program */ + "digest"?: string; "language": string; "recipe": string; "version": string; @@ -579,22 +621,36 @@ export interface KeptArtifactAnswer { export interface StepsInput { /** find them anew, not from the kept answer */ "again"?: boolean; - /** the artifact reference, : */ + /** the key of detected-steps, . */ "artifact": string; - "fetch": Fetch; + /** delete the artifact's kept key instead of detecting; needs why */ + "drop"?: boolean; + "fetch"?: Fetch; "fingerprint"?: string; "module": string; + /** with drop, why: a non-empty text, written in the hand-act log with the caller the bus proved */ + "why"?: string; } /** is what steps answers. */ export interface StepsAnswer { - "artifact": string; + "artifact"?: string; + /** the artifact whose key a drop deleted */ + "dropped"?: string; + /** why a drop deleted nothing, naming the hand-act log when it refused */ + "error"?: string; "fingerprint"?: string; /** answered from the kept answer */ "kept"?: boolean; - "steps": DetectedStep[]; + "steps"?: DetectedStep[]; } /** is the body of the event step-ended. */ export type StepEndedEvent = StepEnded; +/** is the body of the event toolchain-reported. */ +export type ToolchainReportedEvent = ToolchainReported; + +/** is the body of the event step-progress. */ +export type StepProgressEvent = StepProgress; + diff --git a/contracts/ts/seats/nodebuildgo/parts/build-step.js b/contracts/ts/seats/nodebuildgo/parts/build-step.js index 027da325..155a68be 100644 --- a/contracts/ts/seats/nodebuildgo/parts/build-step.js +++ b/contracts/ts/seats/nodebuildgo/parts/build-step.js @@ -13,6 +13,8 @@ export const verbs = { export const events = { stepEnded: "step-ended", + toolchainReported: "toolchain-reported", + stepProgress: "step-progress", }; export {}; diff --git a/contracts/ts/seats/nodebuildimage/index.d.ts b/contracts/ts/seats/nodebuildimage/index.d.ts index 2cfdc13b..06659b29 100644 --- a/contracts/ts/seats/nodebuildimage/index.d.ts +++ b/contracts/ts/seats/nodebuildimage/index.d.ts @@ -25,6 +25,8 @@ export declare const accepts: { export declare const events: { readonly stepEnded: "step-ended"; + readonly toolchainReported: "toolchain-reported"; + readonly stepProgress: "step-progress"; }; export declare const family: { readonly name: "node-build-image.events"; readonly maxAgeDays: 7 }; @@ -35,6 +37,18 @@ export declare function stepEndedSubject(node: string): string; export declare const stepEndedSubjectAll: "mesh.seat.node-build-image.event.step-ended.*"; +export declare const toolchainReportedStream: "SEAT_node-build-image_toolchain-reported"; + +export declare function toolchainReportedSubject(node: string): string; + +export declare const toolchainReportedSubjectAll: "mesh.seat.node-build-image.event.toolchain-reported.*"; + +export declare const stepProgressStream: "SEAT_node-build-image_step-progress"; + +export declare function stepProgressSubject(node: string): string; + +export declare const stepProgressSubjectAll: "mesh.seat.node-build-image.event.step-progress.*"; + export declare const deliverySubject: "mesh.seat.node-build-image.accept.delivery"; export declare const checkSubject: "mesh.seat.node-build-image.accept.check"; @@ -52,6 +66,12 @@ export declare const states: { readonly detectedSteps: "detected-steps"; }; +/** The key of one value of the state steps under the form "..". */ +export declare function stepsByAskerNodeIDKey(asker: string, node: string, id: string): string; + +/** The parts of a key of the form "..", or undefined for a key of another form. */ +export declare function parseStepsByAskerNodeIDKey(_key: string): { asker: string; node: string; id: string } | undefined; + /** The key of one value of the state steps under the form ".". */ export declare function stepsByNodeIDKey(node: string, id: string): string; @@ -91,6 +111,7 @@ export declare function parseDetectedStepsByModuleArtifactKey(_key: string): { m export type AskArtifact = buildStep.AskArtifact; export type BuildAnswer = buildStep.BuildAnswer; export type BuiltArtifact = buildStep.BuiltArtifact; +export type BuiltArtifactResolvedItem = buildStep.BuiltArtifactResolvedItem; export type Capacity = buildStep.Capacity; export type CheckAnswer = buildStep.CheckAnswer; export type ComposeAnswer = buildStep.ComposeAnswer; @@ -104,6 +125,7 @@ export type DropInput = buildStep.DropInput; export type Fetch = buildStep.Fetch; export type FetchAnswer = buildStep.FetchAnswer; export type FetchBesideItem = buildStep.FetchBesideItem; +export type FetchBuildsItem = buildStep.FetchBuildsItem; export type FetchPackage = buildStep.FetchPackage; export type FetchRefusal = buildStep.FetchRefusal; export type KeptArtifactAnswer = buildStep.KeptArtifactAnswer; @@ -136,6 +158,8 @@ export type StepAskCompose = buildStep.StepAskCompose; export type StepEnded = buildStep.StepEnded; export type StepEndedEvent = buildStep.StepEndedEvent; export type StepNames = buildStep.StepNames; +export type StepProgress = buildStep.StepProgress; +export type StepProgressEvent = buildStep.StepProgressEvent; export type StepResult = buildStep.StepResult; export type StepRun = buildStep.StepRun; export type StepsAnswer = buildStep.StepsAnswer; @@ -143,6 +167,8 @@ export type StepsInput = buildStep.StepsInput; export type TakenVersion = buildStep.TakenVersion; export type TestCounts = buildStep.TestCounts; export type Toolchain = buildStep.Toolchain; +export type ToolchainReported = buildStep.ToolchainReported; +export type ToolchainReportedEvent = buildStep.ToolchainReportedEvent; export type ToolchainUsed = buildStep.ToolchainUsed; /** is the body of delivery. */ @@ -160,6 +186,9 @@ export type DeliveryStepAccept = StepAsk; /** is the body of recheck. */ export type RecheckAccept = StepAsk; +/** is one value of the state steps under the key ... */ +export type StepsByAskerNodeIDValue = Step; + /** is one value of the state steps under the key .. */ export type StepsByNodeIDValue = Step; diff --git a/contracts/ts/seats/nodebuildimage/index.js b/contracts/ts/seats/nodebuildimage/index.js index 6df298da..8a23cc70 100644 --- a/contracts/ts/seats/nodebuildimage/index.js +++ b/contracts/ts/seats/nodebuildimage/index.js @@ -23,6 +23,8 @@ export const accepts = { export const events = { stepEnded: "step-ended", + toolchainReported: "toolchain-reported", + stepProgress: "step-progress", }; export const family = { name: "node-build-image.events", maxAgeDays: 7 }; @@ -35,6 +37,22 @@ export function stepEndedSubject(node) { export const stepEndedSubjectAll = "mesh.seat.node-build-image.event.step-ended.*"; +export const toolchainReportedStream = "SEAT_node-build-image_toolchain-reported"; + +export function toolchainReportedSubject(node) { + return "mesh.seat.node-build-image.event.toolchain-reported." + node; +} + +export const toolchainReportedSubjectAll = "mesh.seat.node-build-image.event.toolchain-reported.*"; + +export const stepProgressStream = "SEAT_node-build-image_step-progress"; + +export function stepProgressSubject(node) { + return "mesh.seat.node-build-image.event.step-progress." + node; +} + +export const stepProgressSubjectAll = "mesh.seat.node-build-image.event.step-progress.*"; + export const deliverySubject = "mesh.seat.node-build-image.accept.delivery"; export const checkSubject = "mesh.seat.node-build-image.accept.check"; @@ -52,6 +70,29 @@ export const states = { detectedSteps: "detected-steps", }; +/** The key of one value of the state steps under the form "..". */ +export function stepsByAskerNodeIDKey(asker, node, id) { + return asker + "." + node + "." + id; +} + +/** The parts of a key of the form "..", or undefined for a key of another form. */ +export function parseStepsByAskerNodeIDKey(_key) { + let _rest = _key; + const _at0 = _rest.indexOf("."); + if (_at0 <= 0) return undefined; + const asker = _rest.slice(0, _at0); + _rest = _rest.slice(_at0 + 1); + const _at2 = _rest.indexOf("."); + if (_at2 <= 0) return undefined; + const node = _rest.slice(0, _at2); + _rest = _rest.slice(_at2 + 1); + if (_rest === "") return undefined; + const id = _rest; + _rest = ""; + if (_rest !== "") return undefined; + return { asker, node, id }; +} + /** The key of one value of the state steps under the form ".". */ export function stepsByNodeIDKey(node, id) { return node + "." + id; diff --git a/contracts/ts/seats/nodebuildimage/parts/build-step.d.ts b/contracts/ts/seats/nodebuildimage/parts/build-step.d.ts index 3a3c18ba..a6cc4551 100644 --- a/contracts/ts/seats/nodebuildimage/parts/build-step.d.ts +++ b/contracts/ts/seats/nodebuildimage/parts/build-step.d.ts @@ -13,6 +13,8 @@ export declare const verbs: { export declare const events: { readonly stepEnded: "step-ended"; + readonly toolchainReported: "toolchain-reported"; + readonly stepProgress: "step-progress"; }; /** one artifact of a build ask: built from the fetched tree, or copied from an upstream pin */ @@ -32,11 +34,20 @@ export interface BuildAnswer { "toolchain"?: ToolchainUsed; } +export interface BuiltArtifactResolvedItem { + /** sha256: of the package's bytes as the build fetched them */ + "digest": string; + "package": string; + "version": string; +} + /** one artifact a build made: its digest and the reference it was published under */ export interface BuiltArtifact { "artifact": string; "digest": string; "reference"?: string; + /** one per mesh package the build resolved (the catalog graph entry's packages, the edges of kind 1) */ + "resolved"?: BuiltArtifactResolvedItem[]; } /** how many steps the node runs at once */ @@ -100,6 +111,15 @@ export interface FetchBesideItem { "repository": string; } +export interface FetchBuildsItem { + "artifact": string; + /** the member's build digest, sha256: */ + "digest": string; + "module": string; + /** the name in its ecosystem (the Go module path, the npm name) */ + "package": string; +} + /** the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's */ export interface FetchPackage { "commit"?: string; @@ -115,6 +135,8 @@ export interface Fetch { "base"?: string; /** the sibling repositories placed beside the fetched tree */ "beside"?: FetchBesideItem[]; + /** a group run's package members, each by its build's digest: the build module reads each from the artifact store at artifact-store:////blobs/, checks the bytes against the digest and lays them as the group package; a package named here is named in neither packages nor package */ + "builds"?: FetchBuildsItem[]; /** the full commit */ "commit": string; /** the module's directory in the repository ("." for its root), with paths */ @@ -416,6 +438,14 @@ export interface StepNames { "repositories": string[]; } +/** a running ask's progress, said once a minute while the holder runs it (design 47 G section 496) */ +export interface StepProgress { + "ask": string; + "asker": string; + "at": string; + "phase": "fetch" | "build" | "run" | "report"; +} + /** what a step answered, by the kind of ask: a build's artifacts, toolchain, imports, out-of-step modules and the artifact it kept; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five */ export interface StepResult { "artifacts"?: BuiltArtifact[]; @@ -502,8 +532,20 @@ export interface Toolchain { "version": string; } +/** a node's toolchain of a language, said at the build module's start and each time its toolchains key of the language changes (design 47 G section 492) */ +export interface ToolchainReported { + /** the digest of the program of the provision the build module requires for that language's toolchain, never the build module's own */ + "digest": string; + "language": string; + "node": string; + "recipe": string; + "version": string; +} + /** the toolchain a build was made with: the language, the installed version and the build module's recipe */ export interface ToolchainUsed { + /** the building node's digest of its toolchain program (kind 3's entry), never the build module's own program */ + "digest"?: string; "language": string; "recipe": string; "version": string; @@ -579,22 +621,36 @@ export interface KeptArtifactAnswer { export interface StepsInput { /** find them anew, not from the kept answer */ "again"?: boolean; - /** the artifact reference, : */ + /** the key of detected-steps, . */ "artifact": string; - "fetch": Fetch; + /** delete the artifact's kept key instead of detecting; needs why */ + "drop"?: boolean; + "fetch"?: Fetch; "fingerprint"?: string; "module": string; + /** with drop, why: a non-empty text, written in the hand-act log with the caller the bus proved */ + "why"?: string; } /** is what steps answers. */ export interface StepsAnswer { - "artifact": string; + "artifact"?: string; + /** the artifact whose key a drop deleted */ + "dropped"?: string; + /** why a drop deleted nothing, naming the hand-act log when it refused */ + "error"?: string; "fingerprint"?: string; /** answered from the kept answer */ "kept"?: boolean; - "steps": DetectedStep[]; + "steps"?: DetectedStep[]; } /** is the body of the event step-ended. */ export type StepEndedEvent = StepEnded; +/** is the body of the event toolchain-reported. */ +export type ToolchainReportedEvent = ToolchainReported; + +/** is the body of the event step-progress. */ +export type StepProgressEvent = StepProgress; + diff --git a/contracts/ts/seats/nodebuildimage/parts/build-step.js b/contracts/ts/seats/nodebuildimage/parts/build-step.js index 027da325..155a68be 100644 --- a/contracts/ts/seats/nodebuildimage/parts/build-step.js +++ b/contracts/ts/seats/nodebuildimage/parts/build-step.js @@ -13,6 +13,8 @@ export const verbs = { export const events = { stepEnded: "step-ended", + toolchainReported: "toolchain-reported", + stepProgress: "step-progress", }; export {}; diff --git a/contracts/ts/seats/nodebuildnode/index.d.ts b/contracts/ts/seats/nodebuildnode/index.d.ts index 3a87dc86..0d4743ec 100644 --- a/contracts/ts/seats/nodebuildnode/index.d.ts +++ b/contracts/ts/seats/nodebuildnode/index.d.ts @@ -25,6 +25,8 @@ export declare const accepts: { export declare const events: { readonly stepEnded: "step-ended"; + readonly toolchainReported: "toolchain-reported"; + readonly stepProgress: "step-progress"; }; export declare const family: { readonly name: "node-build-node.events"; readonly maxAgeDays: 7 }; @@ -35,6 +37,18 @@ export declare function stepEndedSubject(node: string): string; export declare const stepEndedSubjectAll: "mesh.seat.node-build-node.event.step-ended.*"; +export declare const toolchainReportedStream: "SEAT_node-build-node_toolchain-reported"; + +export declare function toolchainReportedSubject(node: string): string; + +export declare const toolchainReportedSubjectAll: "mesh.seat.node-build-node.event.toolchain-reported.*"; + +export declare const stepProgressStream: "SEAT_node-build-node_step-progress"; + +export declare function stepProgressSubject(node: string): string; + +export declare const stepProgressSubjectAll: "mesh.seat.node-build-node.event.step-progress.*"; + export declare const deliverySubject: "mesh.seat.node-build-node.accept.delivery"; export declare const checkSubject: "mesh.seat.node-build-node.accept.check"; @@ -52,6 +66,12 @@ export declare const states: { readonly detectedSteps: "detected-steps"; }; +/** The key of one value of the state steps under the form "..". */ +export declare function stepsByAskerNodeIDKey(asker: string, node: string, id: string): string; + +/** The parts of a key of the form "..", or undefined for a key of another form. */ +export declare function parseStepsByAskerNodeIDKey(_key: string): { asker: string; node: string; id: string } | undefined; + /** The key of one value of the state steps under the form ".". */ export declare function stepsByNodeIDKey(node: string, id: string): string; @@ -91,6 +111,7 @@ export declare function parseDetectedStepsByModuleArtifactKey(_key: string): { m export type AskArtifact = buildStep.AskArtifact; export type BuildAnswer = buildStep.BuildAnswer; export type BuiltArtifact = buildStep.BuiltArtifact; +export type BuiltArtifactResolvedItem = buildStep.BuiltArtifactResolvedItem; export type Capacity = buildStep.Capacity; export type CheckAnswer = buildStep.CheckAnswer; export type ComposeAnswer = buildStep.ComposeAnswer; @@ -104,6 +125,7 @@ export type DropInput = buildStep.DropInput; export type Fetch = buildStep.Fetch; export type FetchAnswer = buildStep.FetchAnswer; export type FetchBesideItem = buildStep.FetchBesideItem; +export type FetchBuildsItem = buildStep.FetchBuildsItem; export type FetchPackage = buildStep.FetchPackage; export type FetchRefusal = buildStep.FetchRefusal; export type KeptArtifactAnswer = buildStep.KeptArtifactAnswer; @@ -136,6 +158,8 @@ export type StepAskCompose = buildStep.StepAskCompose; export type StepEnded = buildStep.StepEnded; export type StepEndedEvent = buildStep.StepEndedEvent; export type StepNames = buildStep.StepNames; +export type StepProgress = buildStep.StepProgress; +export type StepProgressEvent = buildStep.StepProgressEvent; export type StepResult = buildStep.StepResult; export type StepRun = buildStep.StepRun; export type StepsAnswer = buildStep.StepsAnswer; @@ -143,6 +167,8 @@ export type StepsInput = buildStep.StepsInput; export type TakenVersion = buildStep.TakenVersion; export type TestCounts = buildStep.TestCounts; export type Toolchain = buildStep.Toolchain; +export type ToolchainReported = buildStep.ToolchainReported; +export type ToolchainReportedEvent = buildStep.ToolchainReportedEvent; export type ToolchainUsed = buildStep.ToolchainUsed; /** is the body of delivery. */ @@ -160,6 +186,9 @@ export type DeliveryStepAccept = StepAsk; /** is the body of recheck. */ export type RecheckAccept = StepAsk; +/** is one value of the state steps under the key ... */ +export type StepsByAskerNodeIDValue = Step; + /** is one value of the state steps under the key .. */ export type StepsByNodeIDValue = Step; diff --git a/contracts/ts/seats/nodebuildnode/index.js b/contracts/ts/seats/nodebuildnode/index.js index 3c9047d8..6cda8ea2 100644 --- a/contracts/ts/seats/nodebuildnode/index.js +++ b/contracts/ts/seats/nodebuildnode/index.js @@ -23,6 +23,8 @@ export const accepts = { export const events = { stepEnded: "step-ended", + toolchainReported: "toolchain-reported", + stepProgress: "step-progress", }; export const family = { name: "node-build-node.events", maxAgeDays: 7 }; @@ -35,6 +37,22 @@ export function stepEndedSubject(node) { export const stepEndedSubjectAll = "mesh.seat.node-build-node.event.step-ended.*"; +export const toolchainReportedStream = "SEAT_node-build-node_toolchain-reported"; + +export function toolchainReportedSubject(node) { + return "mesh.seat.node-build-node.event.toolchain-reported." + node; +} + +export const toolchainReportedSubjectAll = "mesh.seat.node-build-node.event.toolchain-reported.*"; + +export const stepProgressStream = "SEAT_node-build-node_step-progress"; + +export function stepProgressSubject(node) { + return "mesh.seat.node-build-node.event.step-progress." + node; +} + +export const stepProgressSubjectAll = "mesh.seat.node-build-node.event.step-progress.*"; + export const deliverySubject = "mesh.seat.node-build-node.accept.delivery"; export const checkSubject = "mesh.seat.node-build-node.accept.check"; @@ -52,6 +70,29 @@ export const states = { detectedSteps: "detected-steps", }; +/** The key of one value of the state steps under the form "..". */ +export function stepsByAskerNodeIDKey(asker, node, id) { + return asker + "." + node + "." + id; +} + +/** The parts of a key of the form "..", or undefined for a key of another form. */ +export function parseStepsByAskerNodeIDKey(_key) { + let _rest = _key; + const _at0 = _rest.indexOf("."); + if (_at0 <= 0) return undefined; + const asker = _rest.slice(0, _at0); + _rest = _rest.slice(_at0 + 1); + const _at2 = _rest.indexOf("."); + if (_at2 <= 0) return undefined; + const node = _rest.slice(0, _at2); + _rest = _rest.slice(_at2 + 1); + if (_rest === "") return undefined; + const id = _rest; + _rest = ""; + if (_rest !== "") return undefined; + return { asker, node, id }; +} + /** The key of one value of the state steps under the form ".". */ export function stepsByNodeIDKey(node, id) { return node + "." + id; diff --git a/contracts/ts/seats/nodebuildnode/parts/build-step.d.ts b/contracts/ts/seats/nodebuildnode/parts/build-step.d.ts index 3a3c18ba..a6cc4551 100644 --- a/contracts/ts/seats/nodebuildnode/parts/build-step.d.ts +++ b/contracts/ts/seats/nodebuildnode/parts/build-step.d.ts @@ -13,6 +13,8 @@ export declare const verbs: { export declare const events: { readonly stepEnded: "step-ended"; + readonly toolchainReported: "toolchain-reported"; + readonly stepProgress: "step-progress"; }; /** one artifact of a build ask: built from the fetched tree, or copied from an upstream pin */ @@ -32,11 +34,20 @@ export interface BuildAnswer { "toolchain"?: ToolchainUsed; } +export interface BuiltArtifactResolvedItem { + /** sha256: of the package's bytes as the build fetched them */ + "digest": string; + "package": string; + "version": string; +} + /** one artifact a build made: its digest and the reference it was published under */ export interface BuiltArtifact { "artifact": string; "digest": string; "reference"?: string; + /** one per mesh package the build resolved (the catalog graph entry's packages, the edges of kind 1) */ + "resolved"?: BuiltArtifactResolvedItem[]; } /** how many steps the node runs at once */ @@ -100,6 +111,15 @@ export interface FetchBesideItem { "repository": string; } +export interface FetchBuildsItem { + "artifact": string; + /** the member's build digest, sha256: */ + "digest": string; + "module": string; + /** the name in its ecosystem (the Go module path, the npm name) */ + "package": string; +} + /** the TypeScript module's group package: the group's tagged version of the SDK ({name, version}), placed from the registry in each member whatever its lock names, or the group's package member ({repository, commit, ref}), packed by the fetch and served to the members in place of the registry's */ export interface FetchPackage { "commit"?: string; @@ -115,6 +135,8 @@ export interface Fetch { "base"?: string; /** the sibling repositories placed beside the fetched tree */ "beside"?: FetchBesideItem[]; + /** a group run's package members, each by its build's digest: the build module reads each from the artifact store at artifact-store:////blobs/, checks the bytes against the digest and lays them as the group package; a package named here is named in neither packages nor package */ + "builds"?: FetchBuildsItem[]; /** the full commit */ "commit": string; /** the module's directory in the repository ("." for its root), with paths */ @@ -416,6 +438,14 @@ export interface StepNames { "repositories": string[]; } +/** a running ask's progress, said once a minute while the holder runs it (design 47 G section 496) */ +export interface StepProgress { + "ask": string; + "asker": string; + "at": string; + "phase": "fetch" | "build" | "run" | "report"; +} + /** what a step answered, by the kind of ask: a build's artifacts, toolchain, imports, out-of-step modules and the artifact it kept; a check's fetch and levels; a compose's built, key and verdict; a run's built, key, exit, output and said; a declined ask's declined, needs and lacks. The fields of the kinds do not meet, so one layout holds the five */ export interface StepResult { "artifacts"?: BuiltArtifact[]; @@ -502,8 +532,20 @@ export interface Toolchain { "version": string; } +/** a node's toolchain of a language, said at the build module's start and each time its toolchains key of the language changes (design 47 G section 492) */ +export interface ToolchainReported { + /** the digest of the program of the provision the build module requires for that language's toolchain, never the build module's own */ + "digest": string; + "language": string; + "node": string; + "recipe": string; + "version": string; +} + /** the toolchain a build was made with: the language, the installed version and the build module's recipe */ export interface ToolchainUsed { + /** the building node's digest of its toolchain program (kind 3's entry), never the build module's own program */ + "digest"?: string; "language": string; "recipe": string; "version": string; @@ -579,22 +621,36 @@ export interface KeptArtifactAnswer { export interface StepsInput { /** find them anew, not from the kept answer */ "again"?: boolean; - /** the artifact reference, : */ + /** the key of detected-steps, . */ "artifact": string; - "fetch": Fetch; + /** delete the artifact's kept key instead of detecting; needs why */ + "drop"?: boolean; + "fetch"?: Fetch; "fingerprint"?: string; "module": string; + /** with drop, why: a non-empty text, written in the hand-act log with the caller the bus proved */ + "why"?: string; } /** is what steps answers. */ export interface StepsAnswer { - "artifact": string; + "artifact"?: string; + /** the artifact whose key a drop deleted */ + "dropped"?: string; + /** why a drop deleted nothing, naming the hand-act log when it refused */ + "error"?: string; "fingerprint"?: string; /** answered from the kept answer */ "kept"?: boolean; - "steps": DetectedStep[]; + "steps"?: DetectedStep[]; } /** is the body of the event step-ended. */ export type StepEndedEvent = StepEnded; +/** is the body of the event toolchain-reported. */ +export type ToolchainReportedEvent = ToolchainReported; + +/** is the body of the event step-progress. */ +export type StepProgressEvent = StepProgress; + diff --git a/contracts/ts/seats/nodebuildnode/parts/build-step.js b/contracts/ts/seats/nodebuildnode/parts/build-step.js index 027da325..155a68be 100644 --- a/contracts/ts/seats/nodebuildnode/parts/build-step.js +++ b/contracts/ts/seats/nodebuildnode/parts/build-step.js @@ -13,6 +13,8 @@ export const verbs = { export const events = { stepEnded: "step-ended", + toolchainReported: "toolchain-reported", + stepProgress: "step-progress", }; export {};