From 009c4c6e73be2142a177f599aadf4bb88c077ea0 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 11 Nov 2026 23:32:53 +0100 Subject: [PATCH] A-97: the seat mesh-builds accepts the build step and says its end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The accepted kind step (body StepRun: the pipeline, the branch, the source revision and each entry by module and fingerprint) and the event step-ended. (StepEnded: the same entries, the outcome), as mesh-wants has for its composition step. Builds: design 47 G A-97 (§230, §194, §473) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- contracts/go/seats/meshbuilds/contract.go | 93 +++++++++- .../go/seats/meshbuilds/contract_test.go | 4 + .../seats/mesh-builds/fixtures/events.json | 42 +++++ contracts/seats/mesh-builds/seat.json | 162 ++++++++++++++++++ contracts/ts/seats/meshbuilds/index.d.ts | 69 ++++++++ contracts/ts/seats/meshbuilds/index.js | 15 ++ 6 files changed, 384 insertions(+), 1 deletion(-) diff --git a/contracts/go/seats/meshbuilds/contract.go b/contracts/go/seats/meshbuilds/contract.go index bbf20ee0..836f14e0 100644 --- a/contracts/go/seats/meshbuilds/contract.go +++ b/contracts/go/seats/meshbuilds/contract.go @@ -12,9 +12,14 @@ const ( VerbReserved = "reserved" ) +const ( + AcceptStep = "step" +) + const ( EventPackagePublished = "package-published" EventToolchainAgreed = "toolchain-agreed" + EventStepEnded = "step-ended" ) // Verbs is every verb the seat serves, in its definition's order: a new copy each call. @@ -24,7 +29,7 @@ func Verbs() []string { // Events is every event published on the seat's subject: a new copy each call. func Events() []string { - return []string{EventPackagePublished, EventToolchainAgreed} + return []string{EventPackagePublished, EventToolchainAgreed, EventStepEnded} } // Family is where the seat's events are kept: the shared events stream, filtered to the seat's subjects. @@ -45,6 +50,26 @@ const StreamToolchainAgreed = "SEAT_mesh-builds_toolchain-agreed" // SubjectToolchainAgreed is where toolchain-agreed is published. const SubjectToolchainAgreed = "mesh.seat.mesh-builds.event.toolchain-agreed" +// StreamStepEnded is the stream step-ended is kept in. +const StreamStepEnded = "SEAT_mesh-builds_step-ended" + +// SubjectStepEnded is where step-ended is published, on mesh.seat.mesh-builds.event.step-ended.. +func SubjectStepEnded(node string) string { return "mesh.seat.mesh-builds.event.step-ended." + node } + +// SubjectStepEndedAll hears every step-ended. +const SubjectStepEndedAll = "mesh.seat.mesh-builds.event.step-ended.*" + +// AcceptSubjectStep is where step is sent. +const AcceptSubjectStep = "mesh.seat.mesh-builds.accept.step" + +// BuildEntry is one entry a candidate's build builds: a module at the fingerprint of its build inputs, and the commit that fingerprint was read at. +type BuildEntry struct { + Commit string `json:"commit,omitempty"` + // the module's build-input fingerprint at the commit + Fingerprint string `json:"fingerprint"` + Module string `json:"module"` +} + // Dependency is a dependency the build resolved: one item of kind 1 for each resolved package and one of kind 3 for the toolchain, named by the build module in its step key and carried by the builder (design 47 G 374, 673). type Dependency struct { // the digest of the part, as the build used it @@ -77,6 +102,66 @@ type PackagePublished struct { Version string `json:"version"` } +// StepEnded is the build step's end, said by the builder on the node it runs on (design 47 G A-97; ADR 0666 section 21): the runner takes it only from that holder's account, naming the ask and each entry's fingerprint it was asked at. +type StepEnded struct { + // for a rerun, the caller the runtime proved + By string `json:"by,omitempty"` + Ended *time.Time `json:"ended,omitempty"` + // each entry the step built, by module and fingerprint + Entries []BuildEntry `json:"entries"` + // the ask's id + ID string `json:"id"` + // the node the builder ran it on, as in the subject + Node string `json:"node"` + // never passed for a build that built nothing + Outcome string `json:"outcome"` + Pipeline string `json:"pipeline"` + Rerun bool `json:"rerun,omitempty"` + // why it failed or could not run: the entry not built, the part not heard + Said string `json:"said,omitempty"` + Started *time.Time `json:"started,omitempty"` + Step string `json:"step"` + // the layout version + V int64 `json:"v,omitempty"` +} + +// The values of StepEnded's outcome. +const ( + StepEndedOutcomePassed = "passed" + StepEndedOutcomeFailed = "failed" + StepEndedOutcomeNothingToRun = "nothing to run" + StepEndedOutcomeCouldNotRun = "could not run" +) + +// The values of StepEnded's step. +const ( + StepEndedStepBuild = "build" +) + +// StepRun is the build step of one module's pipeline (design 47 G section 230, A-97): the runner asks the seat mesh-builds to build a candidate's entries, each named by its fingerprint, and the builder, the one entry point for a candidate, takes the ask and answers the step's end. +type StepRun struct { + // the watched branch, or the branch of a run on request + Branch string `json:"branch"` + // for a rerun, the caller the runtime proved + By string `json:"by,omitempty"` + // each entry the candidate's build builds, by module and fingerprint + Entries []BuildEntry `json:"entries"` + // the runner's pipeline key, .., a branch escaped as a key's name is + Pipeline string `json:"pipeline"` + // the step is asked again by the runner's rerun + Rerun bool `json:"rerun,omitempty"` + // the catalog graph revision of the branch the pipeline runs for + Revision int64 `json:"revision,omitempty"` + // the source revision of the watched branch; absent for a run on request + Source int64 `json:"source,omitempty"` + Step string `json:"step"` +} + +// The values of StepRun's step. +const ( + StepRunStepBuild = "build" +) + // ToolchainAgreedDigestsItem is a type of the contract. type ToolchainAgreedDigestsItem struct { // the toolchain part's digest @@ -128,8 +213,14 @@ type ReservedAnswer struct { Version string `json:"version,omitempty"` } +// StepAccept is the body of step. +type StepAccept = StepRun + // PackagePublishedEvent is the body of the event package-published. type PackagePublishedEvent = PackagePublished // ToolchainAgreedEvent is the body of the event toolchain-agreed. type ToolchainAgreedEvent = ToolchainAgreed + +// StepEndedEvent is the body of the event step-ended. +type StepEndedEvent = StepEnded diff --git a/contracts/go/seats/meshbuilds/contract_test.go b/contracts/go/seats/meshbuilds/contract_test.go index 1edf441c..809011ad 100644 --- a/contracts/go/seats/meshbuilds/contract_test.go +++ b/contracts/go/seats/meshbuilds/contract_test.go @@ -21,6 +21,10 @@ func TestFixtures(t *testing.T) { decode: func(b []byte) (any, error) { var v PackagePublishedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-builds\\.event\\.package-published$")}, {name: "a toolchain agreed", subject: "mesh.seat.mesh-builds.event.toolchain-agreed", given: "{\"at\":\"2026-11-10T10:00:00Z\",\"digests\":[{\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"nodes\":[\"ace\",\"g14\"]},{\"digest\":\"sha256:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210\",\"nodes\":[\"novox\"]}],\"language\":\"go\",\"v\":1,\"version\":\"1.26.0\"}", wire: "{\"at\":\"2026-11-10T10:00:00Z\",\"digests\":[{\"digest\":\"sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\",\"nodes\":[\"ace\",\"g14\"]},{\"digest\":\"sha256:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210\",\"nodes\":[\"novox\"]}],\"language\":\"go\",\"v\":1,\"version\":\"1.26.0\"}", decode: func(b []byte) (any, error) { var v ToolchainAgreedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-builds\\.event\\.toolchain-agreed$")}, + {name: "the build step ended passed on the builder's node, each entry named by its fingerprint (A-97)", subject: "mesh.seat.mesh-builds.event.step-ended.novox", given: "{\"ended\":\"2026-11-10T10:01:00Z\",\"entries\":[{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"fingerprint\":\"fp-0123\",\"module\":\"nats\"}],\"id\":\"m2kx9a1b3c4d6a\",\"node\":\"novox\",\"outcome\":\"passed\",\"pipeline\":\"main.12.nats\",\"started\":\"2026-11-10T10:00:00Z\",\"step\":\"build\",\"v\":1}", wire: "{\"ended\":\"2026-11-10T10:01:00Z\",\"entries\":[{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"fingerprint\":\"fp-0123\",\"module\":\"nats\"}],\"id\":\"m2kx9a1b3c4d6a\",\"node\":\"novox\",\"outcome\":\"passed\",\"pipeline\":\"main.12.nats\",\"started\":\"2026-11-10T10:00:00Z\",\"step\":\"build\",\"v\":1}", + decode: func(b []byte) (any, error) { var v StepEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-builds\\.event\\.step-ended\\.[^.*> ]+$")}, + {name: "the build step failed on its entry", subject: "mesh.seat.mesh-builds.event.step-ended.novox", given: "{\"entries\":[{\"fingerprint\":\"fp-0123\",\"module\":\"nats\"}],\"id\":\"m2kx9a1b3c4d6b\",\"node\":\"novox\",\"outcome\":\"failed\",\"pipeline\":\"main.12.nats\",\"said\":\"nats:program did not build\",\"step\":\"build\",\"v\":1}", wire: "{\"entries\":[{\"fingerprint\":\"fp-0123\",\"module\":\"nats\"}],\"id\":\"m2kx9a1b3c4d6b\",\"node\":\"novox\",\"outcome\":\"failed\",\"pipeline\":\"main.12.nats\",\"said\":\"nats:program did not build\",\"step\":\"build\",\"v\":1}", + decode: func(b []byte) (any, error) { var v StepEndedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.mesh-builds\\.event\\.step-ended\\.[^.*> ]+$")}, {name: "reserved asks a module", subject: "", given: "{\"module\":\"mesh-sdk\"}", wire: "{\"module\":\"mesh-sdk\"}", decode: func(b []byte) (any, error) { var v ReservedInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil}, {name: "reserved asks a package by its name", subject: "", given: "{\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"version\":\"0.1.15\"}", wire: "{\"name\":\"git.novox.be/novox/mesh-sdk/go\",\"version\":\"0.1.15\"}", diff --git a/contracts/seats/mesh-builds/fixtures/events.json b/contracts/seats/mesh-builds/fixtures/events.json index 69f20574..af0d7617 100644 --- a/contracts/seats/mesh-builds/fixtures/events.json +++ b/contracts/seats/mesh-builds/fixtures/events.json @@ -53,5 +53,47 @@ ], "at": "2026-11-10T10:00:00Z" } + }, + { + "name": "the build step ended passed on the builder's node, each entry named by its fingerprint (A-97)", + "of": "event:step-ended", + "subject": "mesh.seat.mesh-builds.event.step-ended.novox", + "given": { + "v": 1, + "id": "m2kx9a1b3c4d6a", + "pipeline": "main.12.nats", + "step": "build", + "node": "novox", + "outcome": "passed", + "entries": [ + { + "module": "nats", + "commit": "0123456789abcdef0123456789abcdef01234567", + "fingerprint": "fp-0123" + } + ], + "started": "2026-11-10T10:00:00Z", + "ended": "2026-11-10T10:01:00Z" + } + }, + { + "name": "the build step failed on its entry", + "of": "event:step-ended", + "subject": "mesh.seat.mesh-builds.event.step-ended.novox", + "given": { + "v": 1, + "id": "m2kx9a1b3c4d6b", + "pipeline": "main.12.nats", + "step": "build", + "node": "novox", + "outcome": "failed", + "entries": [ + { + "module": "nats", + "fingerprint": "fp-0123" + } + ], + "said": "nats:program did not build" + } } ] diff --git a/contracts/seats/mesh-builds/seat.json b/contracts/seats/mesh-builds/seat.json index c984ac2e..b9920457 100644 --- a/contracts/seats/mesh-builds/seat.json +++ b/contracts/seats/mesh-builds/seat.json @@ -78,6 +78,17 @@ } } ], + "accepts": [ + { + "name": "step", + "body": { + "$ref": "#/defs/StepRun" + }, + "callers": [ + "seat:mesh-pipeline-runner" + ] + } + ], "events": [ { "name": "package-published", @@ -98,6 +109,17 @@ "body": { "$ref": "#/defs/ToolchainAgreed" } + }, + { + "name": "step-ended", + "subject": "step-ended.", + "body": { + "$ref": "#/defs/StepEnded" + }, + "keep": { + "max-age-days": 90, + "max-mib": 32 + } } ], "family": { @@ -240,6 +262,146 @@ "digests" ], "description": "the agreed toolchain of a language changed (design 47 G section 376, ADR 0584 section 1)" + }, + "StepRun": { + "type": "object", + "description": "the build step of one module's pipeline (design 47 G section 230, A-97): the runner asks the seat mesh-builds to build a candidate's entries, each named by its fingerprint, and the builder, the one entry point for a candidate, takes the ask and answers the step's end", + "properties": { + "pipeline": { + "type": "string", + "description": "the runner's pipeline key, .., a branch escaped as a key's name is" + }, + "step": { + "type": "string", + "enum": [ + "build" + ] + }, + "branch": { + "type": "string", + "description": "the watched branch, or the branch of a run on request" + }, + "revision": { + "type": "integer", + "description": "the catalog graph revision of the branch the pipeline runs for" + }, + "source": { + "type": "integer", + "description": "the source revision of the watched branch; absent for a run on request" + }, + "rerun": { + "type": "boolean", + "description": "the step is asked again by the runner's rerun" + }, + "by": { + "type": "string", + "description": "for a rerun, the caller the runtime proved" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/defs/BuildEntry" + }, + "description": "each entry the candidate's build builds, by module and fingerprint" + } + }, + "required": [ + "pipeline", + "step", + "branch", + "entries" + ] + }, + "StepEnded": { + "type": "object", + "description": "the build step's end, said by the builder on the node it runs on (design 47 G A-97; ADR 0666 section 21): the runner takes it only from that holder's account, naming the ask and each entry's fingerprint it was asked at", + "properties": { + "v": { + "type": "integer", + "description": "the layout version" + }, + "id": { + "type": "string", + "description": "the ask's id" + }, + "pipeline": { + "type": "string" + }, + "step": { + "type": "string", + "enum": [ + "build" + ] + }, + "node": { + "type": "string", + "description": "the node the builder ran it on, as in the subject" + }, + "outcome": { + "type": "string", + "enum": [ + "passed", + "failed", + "nothing to run", + "could not run" + ], + "description": "never passed for a build that built nothing" + }, + "said": { + "type": "string", + "description": "why it failed or could not run: the entry not built, the part not heard" + }, + "rerun": { + "type": "boolean" + }, + "by": { + "type": "string", + "description": "for a rerun, the caller the runtime proved" + }, + "started": { + "type": "string", + "format": "date-time" + }, + "ended": { + "type": "string", + "format": "date-time" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/defs/BuildEntry" + }, + "description": "each entry the step built, by module and fingerprint" + } + }, + "required": [ + "id", + "pipeline", + "step", + "node", + "outcome", + "entries" + ] + }, + "BuildEntry": { + "type": "object", + "description": "one entry a candidate's build builds: a module at the fingerprint of its build inputs, and the commit that fingerprint was read at", + "properties": { + "module": { + "type": "string" + }, + "commit": { + "type": "string" + }, + "fingerprint": { + "type": "string", + "description": "the module's build-input fingerprint at the commit" + } + }, + "required": [ + "module", + "fingerprint" + ] } } } diff --git a/contracts/ts/seats/meshbuilds/index.d.ts b/contracts/ts/seats/meshbuilds/index.d.ts index 426f20d5..d142d6d9 100644 --- a/contracts/ts/seats/meshbuilds/index.d.ts +++ b/contracts/ts/seats/meshbuilds/index.d.ts @@ -8,9 +8,14 @@ export declare const verbs: { readonly reserved: "reserved"; }; +export declare const accepts: { + readonly step: "step"; +}; + export declare const events: { readonly packagePublished: "package-published"; readonly toolchainAgreed: "toolchain-agreed"; + readonly stepEnded: "step-ended"; }; export declare const family: { readonly name: "mesh-builds.events"; readonly maxAgeDays: 7 }; @@ -23,6 +28,22 @@ export declare const toolchainAgreedStream: "SEAT_mesh-builds_toolchain-agreed"; export declare const toolchainAgreedSubject: "mesh.seat.mesh-builds.event.toolchain-agreed"; +export declare const stepEndedStream: "SEAT_mesh-builds_step-ended"; + +export declare function stepEndedSubject(node: string): string; + +export declare const stepEndedSubjectAll: "mesh.seat.mesh-builds.event.step-ended.*"; + +export declare const stepSubject: "mesh.seat.mesh-builds.accept.step"; + +/** one entry a candidate's build builds: a module at the fingerprint of its build inputs, and the commit that fingerprint was read at */ +export interface BuildEntry { + "commit"?: string; + /** the module's build-input fingerprint at the commit */ + "fingerprint": string; + "module": string; +} + /** a dependency the build resolved: one item of kind 1 for each resolved package and one of kind 3 for the toolchain, named by the build module in its step key and carried by the builder (design 47 G 374, 673) */ export interface Dependency { /** the digest of the part, as the build used it */ @@ -55,6 +76,48 @@ export interface PackagePublished { "version": string; } +/** the build step's end, said by the builder on the node it runs on (design 47 G A-97; ADR 0666 section 21): the runner takes it only from that holder's account, naming the ask and each entry's fingerprint it was asked at */ +export interface StepEnded { + /** for a rerun, the caller the runtime proved */ + "by"?: string; + "ended"?: string; + /** each entry the step built, by module and fingerprint */ + "entries": BuildEntry[]; + /** the ask's id */ + "id": string; + /** the node the builder ran it on, as in the subject */ + "node": string; + /** never passed for a build that built nothing */ + "outcome": "passed" | "failed" | "nothing to run" | "could not run"; + "pipeline": string; + "rerun"?: boolean; + /** why it failed or could not run: the entry not built, the part not heard */ + "said"?: string; + "started"?: string; + "step": "build"; + /** the layout version */ + "v"?: number; +} + +/** the build step of one module's pipeline (design 47 G section 230, A-97): the runner asks the seat mesh-builds to build a candidate's entries, each named by its fingerprint, and the builder, the one entry point for a candidate, takes the ask and answers the step's end */ +export interface StepRun { + /** the watched branch, or the branch of a run on request */ + "branch": string; + /** for a rerun, the caller the runtime proved */ + "by"?: string; + /** each entry the candidate's build builds, by module and fingerprint */ + "entries": BuildEntry[]; + /** the runner's pipeline key, .., a branch escaped as a key's name is */ + "pipeline": string; + /** the step is asked again by the runner's rerun */ + "rerun"?: boolean; + /** the catalog graph revision of the branch the pipeline runs for */ + "revision"?: number; + /** the source revision of the watched branch; absent for a run on request */ + "source"?: number; + "step": "build"; +} + export interface ToolchainAgreedDigestsItem { /** the toolchain part's digest */ "digest": string; @@ -104,9 +167,15 @@ export interface ReservedAnswer { "version"?: string; } +/** is the body of step. */ +export type StepAccept = StepRun; + /** is the body of the event package-published. */ export type PackagePublishedEvent = PackagePublished; /** is the body of the event toolchain-agreed. */ export type ToolchainAgreedEvent = ToolchainAgreed; +/** is the body of the event step-ended. */ +export type StepEndedEvent = StepEnded; + diff --git a/contracts/ts/seats/meshbuilds/index.js b/contracts/ts/seats/meshbuilds/index.js index 2a1f4d56..31cc6f7c 100644 --- a/contracts/ts/seats/meshbuilds/index.js +++ b/contracts/ts/seats/meshbuilds/index.js @@ -8,9 +8,14 @@ export const verbs = { reserved: "reserved", }; +export const accepts = { + step: "step", +}; + export const events = { packagePublished: "package-published", toolchainAgreed: "toolchain-agreed", + stepEnded: "step-ended", }; export const family = { name: "mesh-builds.events", maxAgeDays: 7 }; @@ -23,3 +28,13 @@ export const toolchainAgreedStream = "SEAT_mesh-builds_toolchain-agreed"; export const toolchainAgreedSubject = "mesh.seat.mesh-builds.event.toolchain-agreed"; +export const stepEndedStream = "SEAT_mesh-builds_step-ended"; + +export function stepEndedSubject(node) { + return "mesh.seat.mesh-builds.event.step-ended." + node; +} + +export const stepEndedSubjectAll = "mesh.seat.mesh-builds.event.step-ended.*"; + +export const stepSubject = "mesh.seat.mesh-builds.accept.step"; +