Merge branch 'b2/registry' into b7/registry

This commit is contained in:
2026-11-05 16:30:17 +01:00
18 changed files with 1003 additions and 38 deletions
+72 -8
View File
@@ -233,7 +233,7 @@ type Capacity struct {
// CheckAnswer is a type of the contract.
type CheckAnswer struct {
Fetch Fetch `json:"fetch"`
Fetch FetchAnswer `json:"fetch"`
Levels []LevelAnswer `json:"levels,omitempty"`
}
@@ -271,6 +271,30 @@ type Fetch struct {
Repository string `json:"repository"`
}
// FetchAnswer is what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package.
type FetchAnswer struct {
Base string `json:"base,omitempty"`
Beside []PlacedBeside `json:"beside,omitempty"`
Changed []string `json:"changed,omitempty"`
Commit string `json:"commit"`
Conflicts []string `json:"conflicts,omitempty"`
Group string `json:"group,omitempty"`
Installed []string `json:"installed,omitempty"`
Merge string `json:"merge,omitempty"`
Modules []string `json:"modules"`
Packages []TakenVersion `json:"packages,omitempty"`
Programs []Program `json:"programs,omitempty"`
Refused []FetchRefusal `json:"refused,omitempty"`
Trees map[string]ModuleTree `json:"trees,omitempty"`
Workspace string `json:"workspace"`
}
// FetchRefusal is a TypeScript module whose packages the fetch did not install, with why.
type FetchRefusal struct {
Module string `json:"module"`
Reason string `json:"reason"`
}
// LevelAnswerDigestNeedsItem is a type of the contract.
type LevelAnswerDigestNeedsItem struct {
Needs []string `json:"needs,omitempty"`
@@ -316,6 +340,16 @@ type LevelAnswer struct {
Unmet []LevelAnswerUnmetItem `json:"unmet,omitempty"`
}
// ModuleTree is the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths.
type ModuleTree struct {
Locks map[string]string `json:"locks,omitempty"`
Manifest string `json:"manifest"`
// no longer filled (ADR 0593 section 2); kept so a preview written before still reads
Packages []string `json:"packages,omitempty"`
Paths map[string]string `json:"paths,omitempty"`
Tree string `json:"tree"`
}
// Pause is whether the node takes no new ask.
type Pause struct {
// when (RFC 3339 time)
@@ -326,6 +360,21 @@ type Pause struct {
V int64 `json:"v,omitempty"`
}
// PlacedBeside is a sibling repository placed as a plain directory under the workspace's beside directory.
type PlacedBeside struct {
Commit string `json:"commit"`
Dir string `json:"dir"`
Repository string `json:"repository"`
}
// Program is a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it.
type Program struct {
Commit string `json:"commit,omitempty"`
// ELF, Mach-O or PE
Kind string `json:"kind"`
Path string `json:"path"`
}
// Step is one step a build module runs on a node.
type Step struct {
// when (RFC 3339 time)
@@ -361,13 +410,17 @@ type StepAskBuild struct {
// StepAskCheck is a type of the contract.
type StepAskCheck struct {
Fetch Fetch `json:"fetch"`
Kept bool `json:"kept,omitempty"`
Levels []string `json:"levels"`
Fetch Fetch `json:"fetch"`
Kept bool `json:"kept,omitempty"`
Levels []string `json:"levels"`
// the node names the check's tests run against
NodeNames []string `json:"node-names,omitempty"`
Packages []string `json:"packages,omitempty"`
Run string `json:"run,omitempty"`
Runner string `json:"runner,omitempty"`
RunnerWhen string `json:"runner_when,omitempty"`
// the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// StepAskCompose is a type of the contract.
@@ -405,10 +458,10 @@ type StepResult struct {
Artifacts []BuiltArtifact `json:"artifacts,omitempty"`
Built bool `json:"built,omitempty"`
// the holder declined the ask: its node lacks what the ask needs
Declined bool `json:"declined,omitempty"`
Fetch *Fetch `json:"fetch,omitempty"`
Imports []string `json:"imports,omitempty"`
Key string `json:"key,omitempty"`
Declined bool `json:"declined,omitempty"`
Fetch *FetchAnswer `json:"fetch,omitempty"`
Imports []string `json:"imports,omitempty"`
Key string `json:"key,omitempty"`
// what the holder's node lacks of it
Lacks []string `json:"lacks,omitempty"`
Levels []LevelAnswer `json:"levels,omitempty"`
@@ -420,6 +473,17 @@ type StepResult struct {
Verdict any `json:"verdict,omitempty"`
}
// TakenVersion is of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said.
type TakenVersion struct {
Asked string `json:"asked"`
Built string `json:"built"`
Dir string `json:"dir"`
Named string `json:"named"`
Package string `json:"package"`
Said string `json:"said,omitempty"`
Sum string `json:"sum,omitempty"`
}
// Toolchain is the toolchain a node reports.
type Toolchain struct {
// when (RFC 3339 time)
@@ -39,14 +39,16 @@ func TestFixtures(t *testing.T) {
}, subjectIs: nil},
{name: "a build ask", subject: "mesh.seat.node-build-go.accept.delivery", given: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", wire: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}",
decode: func(b []byte) (any, error) { var v DeliveryAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.accept\\.delivery$")},
{name: "a check ask with its fetch, siblings and levels", subject: "mesh.seat.node-build-go.accept.check", given: "{\"asker\":\"mesh-delivery-checker\",\"at\":\"2026-11-05T10:00:00Z\",\"check\":{\"fetch\":{\"base\":\"89abcdef0123456789abcdef0123456789abcdef\",\"beside\":[{\"commit\":\"fedcba9876543210fedcba9876543210fedcba98\",\"repository\":\"novox/mesh-sdk\"}],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"fedcba9876543210fedcba9876543210fedcba98\"},\"ref\":\"refs/pull/7/head\",\"repository\":\"novox/mesh-catalog\"},\"kept\":true,\"levels\":[\"unit\",\"integration\"]},\"id\":\"c3d4\",\"kind\":\"check\",\"language\":\"go\",\"v\":1}", wire: "{\"asker\":\"mesh-delivery-checker\",\"at\":\"2026-11-05T10:00:00Z\",\"check\":{\"fetch\":{\"base\":\"89abcdef0123456789abcdef0123456789abcdef\",\"beside\":[{\"commit\":\"fedcba9876543210fedcba9876543210fedcba98\",\"repository\":\"novox/mesh-sdk\"}],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"fedcba9876543210fedcba9876543210fedcba98\"},\"ref\":\"refs/pull/7/head\",\"repository\":\"novox/mesh-catalog\"},\"kept\":true,\"levels\":[\"unit\",\"integration\"]},\"id\":\"c3d4\",\"kind\":\"check\",\"language\":\"go\",\"v\":1}",
{name: "a check ask with its fetch, siblings and levels", subject: "mesh.seat.node-build-go.accept.check", given: "{\"asker\":\"mesh-delivery-checker\",\"at\":\"2026-11-05T10:00:00Z\",\"check\":{\"fetch\":{\"base\":\"89abcdef0123456789abcdef0123456789abcdef\",\"beside\":[{\"commit\":\"fedcba9876543210fedcba9876543210fedcba98\",\"repository\":\"novox/mesh-sdk\"}],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"fedcba9876543210fedcba9876543210fedcba98\"},\"ref\":\"refs/pull/7/head\",\"repository\":\"novox/mesh-catalog\"},\"kept\":true,\"levels\":[\"unit\",\"integration\"],\"node-names\":[\"ace\",\"g14\"],\"toolchain\":{\"language\":\"go\",\"recipe\":\"2\",\"version\":\"1.26.0\"}},\"id\":\"c3d4\",\"kind\":\"check\",\"language\":\"go\",\"v\":1}", wire: "{\"asker\":\"mesh-delivery-checker\",\"at\":\"2026-11-05T10:00:00Z\",\"check\":{\"fetch\":{\"base\":\"89abcdef0123456789abcdef0123456789abcdef\",\"beside\":[{\"commit\":\"fedcba9876543210fedcba9876543210fedcba98\",\"repository\":\"novox/mesh-sdk\"}],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"packages\":{\"git.novox.be/novox/mesh-sdk/go\":\"fedcba9876543210fedcba9876543210fedcba98\"},\"ref\":\"refs/pull/7/head\",\"repository\":\"novox/mesh-catalog\"},\"kept\":true,\"levels\":[\"unit\",\"integration\"],\"node-names\":[\"ace\",\"g14\"],\"toolchain\":{\"language\":\"go\",\"recipe\":\"2\",\"version\":\"1.26.0\"}},\"id\":\"c3d4\",\"kind\":\"check\",\"language\":\"go\",\"v\":1}",
decode: func(b []byte) (any, error) { var v CheckAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-go\\.accept\\.check$")},
{name: "a finished build step with its artifacts", subject: "", given: "{\"ended\":\"2026-11-05T10:03:00Z\",\"id\":\"a1b2\",\"node\":\"ace\",\"result\":{\"artifacts\":[{\"artifact\":\"go\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"reference\":\"container-image-registry/nats/go@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}],\"imports\":[\"git.novox.be/novox/mesh-sdk/go\"],\"toolchain\":{\"language\":\"go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"build\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:03:00Z\",\"id\":\"a1b2\",\"node\":\"ace\",\"result\":{\"artifacts\":[{\"artifact\":\"go\",\"digest\":\"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"reference\":\"container-image-registry/nats/go@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"}],\"imports\":[\"git.novox.be/novox/mesh-sdk/go\"],\"toolchain\":{\"language\":\"go\",\"recipe\":\"1\",\"version\":\"1.26.0\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"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 finished check step with its levels", subject: "", given: "{\"ended\":\"2026-11-05T10:06:00Z\",\"id\":\"c3d4\",\"node\":\"ace\",\"result\":{\"fetch\":{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"repository\":\"novox/mesh-catalog\"},\"levels\":[{\"digest\":{\"level\":\"unit\",\"packages\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\"}]},\"kept\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\",\"tests\":12}],\"level\":\"unit\",\"tests\":[{\"package\":\"./modules/b\",\"pass\":true}],\"unmet\":[{\"needs\":[\"docker\"],\"package\":\"./modules/c\"}]}]},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:06:00Z\",\"id\":\"c3d4\",\"node\":\"ace\",\"result\":{\"fetch\":{\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"repository\":\"novox/mesh-catalog\"},\"levels\":[{\"digest\":{\"level\":\"unit\",\"packages\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\"}]},\"kept\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\",\"tests\":12}],\"level\":\"unit\",\"tests\":[{\"package\":\"./modules/b\",\"pass\":true}],\"unmet\":[{\"needs\":[\"docker\"],\"package\":\"./modules/c\"}]}]},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}",
{name: "a finished check step with its levels", subject: "", given: "{\"ended\":\"2026-11-05T10:06:00Z\",\"id\":\"c3d4\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"changed\":[\"a.go\",\"modules/m/main.go\"],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"merge\":\"fedcba9876543210fedcba9876543210fedcba98\",\"modules\":[\".\"],\"trees\":{\"modules/m\":{\"locks\":{\"go.sum\":\"c5fc83c01e92404452b986527d239140ccf9a48b88e0c268fbf38c2e1429e9c9\"},\"manifest\":\"ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356\",\"tree\":\"1111111111111111111111111111111111111111\"}},\"workspace\":\"mvii1iwu5414e4\"},\"levels\":[{\"digest\":{\"level\":\"unit\",\"packages\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\"}]},\"kept\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\",\"tests\":12}],\"level\":\"unit\",\"tests\":[{\"package\":\"./modules/b\",\"pass\":true}],\"unmet\":[{\"needs\":[\"docker\"],\"package\":\"./modules/c\"}]}]},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:06:00Z\",\"id\":\"c3d4\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"changed\":[\"a.go\",\"modules/m/main.go\"],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"merge\":\"fedcba9876543210fedcba9876543210fedcba98\",\"modules\":[\".\"],\"trees\":{\"modules/m\":{\"locks\":{\"go.sum\":\"c5fc83c01e92404452b986527d239140ccf9a48b88e0c268fbf38c2e1429e9c9\"},\"manifest\":\"ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356\",\"tree\":\"1111111111111111111111111111111111111111\"}},\"workspace\":\"mvii1iwu5414e4\"},\"levels\":[{\"digest\":{\"level\":\"unit\",\"packages\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\"}]},\"kept\":[{\"digest\":\"sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\",\"package\":\"./modules/a\",\"tests\":12}],\"level\":\"unit\",\"tests\":[{\"package\":\"./modules/b\",\"pass\":true}],\"unmet\":[{\"needs\":[\"docker\"],\"package\":\"./modules/c\"}]}]},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a declined step", subject: "", given: "{\"id\":\"c3d5\",\"node\":\"ace\",\"result\":{\"declined\":true,\"lacks\":[\"docker\"],\"needs\":[\"docker\"]},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"refused\",\"tool\":\"check\",\"v\":1}", wire: "{\"id\":\"c3d5\",\"node\":\"ace\",\"result\":{\"declined\":true,\"lacks\":[\"docker\"],\"needs\":[\"docker\"]},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"refused\",\"tool\":\"check\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a finished check step whose head does not merge", subject: "", given: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d5\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"conflicts\":[\"a.go\",\"sub/b.go\"],\"modules\":[\".\"],\"workspace\":\"mvii1iwz5050eb\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d5\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"conflicts\":[\"a.go\",\"sub/b.go\"],\"modules\":[\".\"],\"workspace\":\"mvii1iwz5050eb\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
+72 -8
View File
@@ -233,7 +233,7 @@ type Capacity struct {
// CheckAnswer is a type of the contract.
type CheckAnswer struct {
Fetch Fetch `json:"fetch"`
Fetch FetchAnswer `json:"fetch"`
Levels []LevelAnswer `json:"levels,omitempty"`
}
@@ -271,6 +271,30 @@ type Fetch struct {
Repository string `json:"repository"`
}
// FetchAnswer is what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package.
type FetchAnswer struct {
Base string `json:"base,omitempty"`
Beside []PlacedBeside `json:"beside,omitempty"`
Changed []string `json:"changed,omitempty"`
Commit string `json:"commit"`
Conflicts []string `json:"conflicts,omitempty"`
Group string `json:"group,omitempty"`
Installed []string `json:"installed,omitempty"`
Merge string `json:"merge,omitempty"`
Modules []string `json:"modules"`
Packages []TakenVersion `json:"packages,omitempty"`
Programs []Program `json:"programs,omitempty"`
Refused []FetchRefusal `json:"refused,omitempty"`
Trees map[string]ModuleTree `json:"trees,omitempty"`
Workspace string `json:"workspace"`
}
// FetchRefusal is a TypeScript module whose packages the fetch did not install, with why.
type FetchRefusal struct {
Module string `json:"module"`
Reason string `json:"reason"`
}
// LevelAnswerDigestNeedsItem is a type of the contract.
type LevelAnswerDigestNeedsItem struct {
Needs []string `json:"needs,omitempty"`
@@ -316,6 +340,16 @@ type LevelAnswer struct {
Unmet []LevelAnswerUnmetItem `json:"unmet,omitempty"`
}
// ModuleTree is the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths.
type ModuleTree struct {
Locks map[string]string `json:"locks,omitempty"`
Manifest string `json:"manifest"`
// no longer filled (ADR 0593 section 2); kept so a preview written before still reads
Packages []string `json:"packages,omitempty"`
Paths map[string]string `json:"paths,omitempty"`
Tree string `json:"tree"`
}
// Pause is whether the node takes no new ask.
type Pause struct {
// when (RFC 3339 time)
@@ -326,6 +360,21 @@ type Pause struct {
V int64 `json:"v,omitempty"`
}
// PlacedBeside is a sibling repository placed as a plain directory under the workspace's beside directory.
type PlacedBeside struct {
Commit string `json:"commit"`
Dir string `json:"dir"`
Repository string `json:"repository"`
}
// Program is a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it.
type Program struct {
Commit string `json:"commit,omitempty"`
// ELF, Mach-O or PE
Kind string `json:"kind"`
Path string `json:"path"`
}
// Step is one step a build module runs on a node.
type Step struct {
// when (RFC 3339 time)
@@ -361,13 +410,17 @@ type StepAskBuild struct {
// StepAskCheck is a type of the contract.
type StepAskCheck struct {
Fetch Fetch `json:"fetch"`
Kept bool `json:"kept,omitempty"`
Levels []string `json:"levels"`
Fetch Fetch `json:"fetch"`
Kept bool `json:"kept,omitempty"`
Levels []string `json:"levels"`
// the node names the check's tests run against
NodeNames []string `json:"node-names,omitempty"`
Packages []string `json:"packages,omitempty"`
Run string `json:"run,omitempty"`
Runner string `json:"runner,omitempty"`
RunnerWhen string `json:"runner_when,omitempty"`
// the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// StepAskCompose is a type of the contract.
@@ -405,10 +458,10 @@ type StepResult struct {
Artifacts []BuiltArtifact `json:"artifacts,omitempty"`
Built bool `json:"built,omitempty"`
// the holder declined the ask: its node lacks what the ask needs
Declined bool `json:"declined,omitempty"`
Fetch *Fetch `json:"fetch,omitempty"`
Imports []string `json:"imports,omitempty"`
Key string `json:"key,omitempty"`
Declined bool `json:"declined,omitempty"`
Fetch *FetchAnswer `json:"fetch,omitempty"`
Imports []string `json:"imports,omitempty"`
Key string `json:"key,omitempty"`
// what the holder's node lacks of it
Lacks []string `json:"lacks,omitempty"`
Levels []LevelAnswer `json:"levels,omitempty"`
@@ -420,6 +473,17 @@ type StepResult struct {
Verdict any `json:"verdict,omitempty"`
}
// TakenVersion is of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said.
type TakenVersion struct {
Asked string `json:"asked"`
Built string `json:"built"`
Dir string `json:"dir"`
Named string `json:"named"`
Package string `json:"package"`
Said string `json:"said,omitempty"`
Sum string `json:"sum,omitempty"`
}
// Toolchain is the toolchain a node reports.
type Toolchain struct {
// when (RFC 3339 time)
+72 -8
View File
@@ -233,7 +233,7 @@ type Capacity struct {
// CheckAnswer is a type of the contract.
type CheckAnswer struct {
Fetch Fetch `json:"fetch"`
Fetch FetchAnswer `json:"fetch"`
Levels []LevelAnswer `json:"levels,omitempty"`
}
@@ -271,6 +271,30 @@ type Fetch struct {
Repository string `json:"repository"`
}
// FetchAnswer is what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package.
type FetchAnswer struct {
Base string `json:"base,omitempty"`
Beside []PlacedBeside `json:"beside,omitempty"`
Changed []string `json:"changed,omitempty"`
Commit string `json:"commit"`
Conflicts []string `json:"conflicts,omitempty"`
Group string `json:"group,omitempty"`
Installed []string `json:"installed,omitempty"`
Merge string `json:"merge,omitempty"`
Modules []string `json:"modules"`
Packages []TakenVersion `json:"packages,omitempty"`
Programs []Program `json:"programs,omitempty"`
Refused []FetchRefusal `json:"refused,omitempty"`
Trees map[string]ModuleTree `json:"trees,omitempty"`
Workspace string `json:"workspace"`
}
// FetchRefusal is a TypeScript module whose packages the fetch did not install, with why.
type FetchRefusal struct {
Module string `json:"module"`
Reason string `json:"reason"`
}
// LevelAnswerDigestNeedsItem is a type of the contract.
type LevelAnswerDigestNeedsItem struct {
Needs []string `json:"needs,omitempty"`
@@ -316,6 +340,16 @@ type LevelAnswer struct {
Unmet []LevelAnswerUnmetItem `json:"unmet,omitempty"`
}
// ModuleTree is the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths.
type ModuleTree struct {
Locks map[string]string `json:"locks,omitempty"`
Manifest string `json:"manifest"`
// no longer filled (ADR 0593 section 2); kept so a preview written before still reads
Packages []string `json:"packages,omitempty"`
Paths map[string]string `json:"paths,omitempty"`
Tree string `json:"tree"`
}
// Pause is whether the node takes no new ask.
type Pause struct {
// when (RFC 3339 time)
@@ -326,6 +360,21 @@ type Pause struct {
V int64 `json:"v,omitempty"`
}
// PlacedBeside is a sibling repository placed as a plain directory under the workspace's beside directory.
type PlacedBeside struct {
Commit string `json:"commit"`
Dir string `json:"dir"`
Repository string `json:"repository"`
}
// Program is a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it.
type Program struct {
Commit string `json:"commit,omitempty"`
// ELF, Mach-O or PE
Kind string `json:"kind"`
Path string `json:"path"`
}
// Step is one step a build module runs on a node.
type Step struct {
// when (RFC 3339 time)
@@ -361,13 +410,17 @@ type StepAskBuild struct {
// StepAskCheck is a type of the contract.
type StepAskCheck struct {
Fetch Fetch `json:"fetch"`
Kept bool `json:"kept,omitempty"`
Levels []string `json:"levels"`
Fetch Fetch `json:"fetch"`
Kept bool `json:"kept,omitempty"`
Levels []string `json:"levels"`
// the node names the check's tests run against
NodeNames []string `json:"node-names,omitempty"`
Packages []string `json:"packages,omitempty"`
Run string `json:"run,omitempty"`
Runner string `json:"runner,omitempty"`
RunnerWhen string `json:"runner_when,omitempty"`
// the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// StepAskCompose is a type of the contract.
@@ -405,10 +458,10 @@ type StepResult struct {
Artifacts []BuiltArtifact `json:"artifacts,omitempty"`
Built bool `json:"built,omitempty"`
// the holder declined the ask: its node lacks what the ask needs
Declined bool `json:"declined,omitempty"`
Fetch *Fetch `json:"fetch,omitempty"`
Imports []string `json:"imports,omitempty"`
Key string `json:"key,omitempty"`
Declined bool `json:"declined,omitempty"`
Fetch *FetchAnswer `json:"fetch,omitempty"`
Imports []string `json:"imports,omitempty"`
Key string `json:"key,omitempty"`
// what the holder's node lacks of it
Lacks []string `json:"lacks,omitempty"`
Levels []LevelAnswer `json:"levels,omitempty"`
@@ -420,6 +473,17 @@ type StepResult struct {
Verdict any `json:"verdict,omitempty"`
}
// TakenVersion is of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said.
type TakenVersion struct {
Asked string `json:"asked"`
Built string `json:"built"`
Dir string `json:"dir"`
Named string `json:"named"`
Package string `json:"package"`
Said string `json:"said,omitempty"`
Sum string `json:"sum,omitempty"`
}
// Toolchain is the toolchain a node reports.
type Toolchain struct {
// when (RFC 3339 time)
@@ -39,6 +39,8 @@ func TestFixtures(t *testing.T) {
}, subjectIs: nil},
{name: "a build ask", subject: "mesh.seat.node-build-node.accept.delivery", given: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}", wire: "{\"asker\":\"mesh-delivery-builder\",\"build\":{\"artifacts\":[{\"artifact\":\"go\"}],\"module\":\"nats\"},\"id\":\"a1b2\",\"kind\":\"build\",\"language\":\"go\"}",
decode: func(b []byte) (any, error) { var v DeliveryAccept; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-build-node\\.accept\\.delivery$")},
{name: "a finished check step of a TypeScript merge preview", subject: "", given: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d6\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"changed\":[\"modules/m/package.json\"],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"merge\":\"0123456789abcdef0123456789abcdef01234567\",\"modules\":[\"modules/m\"],\"trees\":{\"modules/m\":{\"manifest\":\"0581b699d9d05517e69b21f5cedbeb2eee8aba2b205280b561be731594d7feef\",\"tree\":\"0123456789abcdef0123456789abcdef01234567\"}},\"workspace\":\"mvii1sxt979f67\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}", wire: "{\"ended\":\"2026-11-05T10:01:00Z\",\"id\":\"c3d6\",\"node\":\"ace\",\"result\":{\"fetch\":{\"base\":\"abcdef0123456789abcdef0123456789abcdef01\",\"changed\":[\"modules/m/package.json\"],\"commit\":\"0123456789abcdef0123456789abcdef01234567\",\"merge\":\"0123456789abcdef0123456789abcdef01234567\",\"modules\":[\"modules/m\"],\"trees\":{\"modules/m\":{\"manifest\":\"0581b699d9d05517e69b21f5cedbeb2eee8aba2b205280b561be731594d7feef\",\"tree\":\"0123456789abcdef0123456789abcdef01234567\"}},\"workspace\":\"mvii1sxt979f67\"}},\"started\":\"2026-11-05T10:00:00Z\",\"state\":\"passed\",\"tool\":\"check\",\"v\":1}",
decode: func(b []byte) (any, error) { var v StepsByNodeIDValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
+218 -2
View File
@@ -130,6 +130,17 @@
"items": {
"type": "string"
}
},
"toolchain": {
"$ref": "#/defs/ToolchainUsed",
"description": "the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check"
},
"node-names": {
"type": "array",
"items": {
"type": "string"
},
"description": "the node names the check's tests run against"
}
},
"required": [
@@ -218,7 +229,7 @@
"type": "object",
"properties": {
"fetch": {
"$ref": "#/defs/Fetch"
"$ref": "#/defs/FetchAnswer"
},
"levels": {
"type": "array",
@@ -680,7 +691,7 @@
}
},
"fetch": {
"$ref": "#/defs/Fetch"
"$ref": "#/defs/FetchAnswer"
},
"levels": {
"type": "array",
@@ -716,6 +727,211 @@
"description": "what the holder's node lacks of it"
}
}
},
"ModuleTree": {
"type": "object",
"description": "the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths",
"properties": {
"tree": {
"type": "string"
},
"manifest": {
"type": "string"
},
"locks": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"paths": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"packages": {
"type": "array",
"items": {
"type": "string"
},
"description": "no longer filled (ADR 0593 section 2); kept so a preview written before still reads"
}
},
"required": [
"tree",
"manifest"
]
},
"Program": {
"type": "object",
"description": "a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it",
"properties": {
"path": {
"type": "string"
},
"kind": {
"type": "string",
"description": "ELF, Mach-O or PE"
},
"commit": {
"type": "string"
}
},
"required": [
"path",
"kind"
]
},
"PlacedBeside": {
"type": "object",
"description": "a sibling repository placed as a plain directory under the workspace's beside directory",
"properties": {
"repository": {
"type": "string"
},
"commit": {
"type": "string"
},
"dir": {
"type": "string"
}
},
"required": [
"repository",
"commit",
"dir"
]
},
"TakenVersion": {
"type": "object",
"description": "of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said",
"properties": {
"dir": {
"type": "string"
},
"package": {
"type": "string"
},
"asked": {
"type": "string"
},
"named": {
"type": "string"
},
"built": {
"type": "string"
},
"sum": {
"type": "string"
},
"said": {
"type": "string"
}
},
"required": [
"dir",
"package",
"asked",
"named",
"built"
]
},
"FetchRefusal": {
"type": "object",
"description": "a TypeScript module whose packages the fetch did not install, with why",
"properties": {
"module": {
"type": "string"
},
"reason": {
"type": "string"
}
},
"required": [
"module",
"reason"
]
},
"FetchAnswer": {
"type": "object",
"description": "what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package",
"properties": {
"workspace": {
"type": "string"
},
"commit": {
"type": "string"
},
"modules": {
"type": "array",
"items": {
"type": "string"
}
},
"base": {
"type": "string"
},
"merge": {
"type": "string"
},
"conflicts": {
"type": "array",
"items": {
"type": "string"
}
},
"changed": {
"type": "array",
"items": {
"type": "string"
}
},
"programs": {
"type": "array",
"items": {
"$ref": "#/defs/Program"
}
},
"trees": {
"type": "object",
"additionalProperties": {
"$ref": "#/defs/ModuleTree"
}
},
"beside": {
"type": "array",
"items": {
"$ref": "#/defs/PlacedBeside"
}
},
"packages": {
"type": "array",
"items": {
"$ref": "#/defs/TakenVersion"
}
},
"installed": {
"type": "array",
"items": {
"type": "string"
}
},
"refused": {
"type": "array",
"items": {
"$ref": "#/defs/FetchRefusal"
}
},
"group": {
"type": "string"
}
},
"required": [
"workspace",
"commit",
"modules"
]
}
}
}
@@ -91,7 +91,16 @@
"unit",
"integration"
],
"kept": true
"kept": true,
"toolchain": {
"language": "go",
"version": "1.26.0",
"recipe": "2"
},
"node-names": [
"ace",
"g14"
]
}
}
},
@@ -138,8 +147,26 @@
"ended": "2026-11-05T10:06:00Z",
"result": {
"fetch": {
"repository": "novox/mesh-catalog",
"commit": "0123456789abcdef0123456789abcdef01234567"
"workspace": "mvii1iwu5414e4",
"commit": "0123456789abcdef0123456789abcdef01234567",
"modules": [
"."
],
"base": "abcdef0123456789abcdef0123456789abcdef01",
"merge": "fedcba9876543210fedcba9876543210fedcba98",
"changed": [
"a.go",
"modules/m/main.go"
],
"trees": {
"modules/m": {
"tree": "1111111111111111111111111111111111111111",
"manifest": "ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
"locks": {
"go.sum": "c5fc83c01e92404452b986527d239140ccf9a48b88e0c268fbf38c2e1429e9c9"
}
}
}
},
"levels": [
{
@@ -199,5 +226,32 @@
]
}
}
},
{
"name": "a finished check step whose head does not merge",
"of": "state:steps/<node>.<id>",
"given": {
"v": 1,
"id": "c3d5",
"tool": "check",
"node": "ace",
"state": "passed",
"started": "2026-11-05T10:00:00Z",
"ended": "2026-11-05T10:01:00Z",
"result": {
"fetch": {
"workspace": "mvii1iwz5050eb",
"commit": "0123456789abcdef0123456789abcdef01234567",
"modules": [
"."
],
"base": "abcdef0123456789abcdef0123456789abcdef01",
"conflicts": [
"a.go",
"sub/b.go"
]
}
}
}
}
]
@@ -59,5 +59,38 @@
]
}
}
},
{
"name": "a finished check step of a TypeScript merge preview",
"of": "state:steps/<node>.<id>",
"given": {
"v": 1,
"id": "c3d6",
"tool": "check",
"node": "ace",
"state": "passed",
"started": "2026-11-05T10:00:00Z",
"ended": "2026-11-05T10:01:00Z",
"result": {
"fetch": {
"workspace": "mvii1sxt979f67",
"commit": "0123456789abcdef0123456789abcdef01234567",
"modules": [
"modules/m"
],
"base": "abcdef0123456789abcdef0123456789abcdef01",
"merge": "0123456789abcdef0123456789abcdef01234567",
"changed": [
"modules/m/package.json"
],
"trees": {
"modules/m": {
"tree": "0123456789abcdef0123456789abcdef01234567",
"manifest": "0581b699d9d05517e69b21f5cedbeb2eee8aba2b205280b561be731594d7feef"
}
}
}
}
}
}
]
@@ -111,16 +111,21 @@ export type ComposeAnswer = buildStep.ComposeAnswer;
export type CurrentAnswer = buildStep.CurrentAnswer;
export type CurrentInput = buildStep.CurrentInput;
export type Fetch = buildStep.Fetch;
export type FetchAnswer = buildStep.FetchAnswer;
export type FetchBesideItem = buildStep.FetchBesideItem;
export type FetchRefusal = buildStep.FetchRefusal;
export type LevelAnswer = buildStep.LevelAnswer;
export type LevelAnswerDigest = buildStep.LevelAnswerDigest;
export type LevelAnswerDigestNeedsItem = buildStep.LevelAnswerDigestNeedsItem;
export type LevelAnswerDigestPackagesItem = buildStep.LevelAnswerDigestPackagesItem;
export type LevelAnswerKeptItem = buildStep.LevelAnswerKeptItem;
export type LevelAnswerUnmetItem = buildStep.LevelAnswerUnmetItem;
export type ModuleTree = buildStep.ModuleTree;
export type Pause = buildStep.Pause;
export type PauseAnswer = buildStep.PauseAnswer;
export type PauseInput = buildStep.PauseInput;
export type PlacedBeside = buildStep.PlacedBeside;
export type Program = buildStep.Program;
export type ResumeAnswer = buildStep.ResumeAnswer;
export type ResumeInput = buildStep.ResumeInput;
export type Step = buildStep.Step;
@@ -129,6 +134,7 @@ export type StepAskBuild = buildStep.StepAskBuild;
export type StepAskCheck = buildStep.StepAskCheck;
export type StepAskCompose = buildStep.StepAskCompose;
export type StepResult = buildStep.StepResult;
export type TakenVersion = buildStep.TakenVersion;
export type Toolchain = buildStep.Toolchain;
export type ToolchainUsed = buildStep.ToolchainUsed;
@@ -51,7 +51,7 @@ export interface Capacity {
}
export interface CheckAnswer {
"fetch": Fetch;
"fetch": FetchAnswer;
"levels"?: LevelAnswer[];
}
@@ -87,6 +87,30 @@ export interface Fetch {
"repository": string;
}
/** what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package */
export interface FetchAnswer {
"base"?: string;
"beside"?: PlacedBeside[];
"changed"?: string[];
"commit": string;
"conflicts"?: string[];
"group"?: string;
"installed"?: string[];
"merge"?: string;
"modules": string[];
"packages"?: TakenVersion[];
"programs"?: Program[];
"refused"?: FetchRefusal[];
"trees"?: Record<string, ModuleTree>;
"workspace": string;
}
/** a TypeScript module whose packages the fetch did not install, with why */
export interface FetchRefusal {
"module": string;
"reason": string;
}
export interface LevelAnswerDigestNeedsItem {
"needs"?: string[];
"package"?: string;
@@ -127,6 +151,16 @@ export interface LevelAnswer {
"unmet"?: LevelAnswerUnmetItem[];
}
/** the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths */
export interface ModuleTree {
"locks"?: Record<string, string>;
"manifest": string;
/** no longer filled (ADR 0593 section 2); kept so a preview written before still reads */
"packages"?: string[];
"paths"?: Record<string, string>;
"tree": string;
}
/** whether the node takes no new ask */
export interface Pause {
/** when (RFC 3339 time) */
@@ -137,6 +171,21 @@ export interface Pause {
"v"?: number;
}
/** a sibling repository placed as a plain directory under the workspace's beside directory */
export interface PlacedBeside {
"commit": string;
"dir": string;
"repository": string;
}
/** a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it */
export interface Program {
"commit"?: string;
/** ELF, Mach-O or PE */
"kind": string;
"path": string;
}
/** one step a build module runs on a node */
export interface Step {
/** when (RFC 3339 time) */
@@ -173,10 +222,14 @@ export interface StepAskCheck {
"fetch": Fetch;
"kept"?: boolean;
"levels": string[];
/** the node names the check's tests run against */
"node-names"?: string[];
"packages"?: string[];
"run"?: string;
"runner"?: string;
"runner_when"?: string;
/** the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check */
"toolchain"?: ToolchainUsed;
}
export interface StepAskCompose {
@@ -214,7 +267,7 @@ export interface StepResult {
"built"?: boolean;
/** the holder declined the ask: its node lacks what the ask needs */
"declined"?: boolean;
"fetch"?: Fetch;
"fetch"?: FetchAnswer;
"imports"?: string[];
"key"?: string;
/** what the holder's node lacks of it */
@@ -228,6 +281,17 @@ export interface StepResult {
"verdict"?: unknown;
}
/** of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said */
export interface TakenVersion {
"asked": string;
"built": string;
"dir": string;
"named": string;
"package": string;
"said"?: string;
"sum"?: string;
}
/** the toolchain a node reports */
export interface Toolchain {
/** when (RFC 3339 time) */
@@ -111,16 +111,21 @@ export type ComposeAnswer = buildStep.ComposeAnswer;
export type CurrentAnswer = buildStep.CurrentAnswer;
export type CurrentInput = buildStep.CurrentInput;
export type Fetch = buildStep.Fetch;
export type FetchAnswer = buildStep.FetchAnswer;
export type FetchBesideItem = buildStep.FetchBesideItem;
export type FetchRefusal = buildStep.FetchRefusal;
export type LevelAnswer = buildStep.LevelAnswer;
export type LevelAnswerDigest = buildStep.LevelAnswerDigest;
export type LevelAnswerDigestNeedsItem = buildStep.LevelAnswerDigestNeedsItem;
export type LevelAnswerDigestPackagesItem = buildStep.LevelAnswerDigestPackagesItem;
export type LevelAnswerKeptItem = buildStep.LevelAnswerKeptItem;
export type LevelAnswerUnmetItem = buildStep.LevelAnswerUnmetItem;
export type ModuleTree = buildStep.ModuleTree;
export type Pause = buildStep.Pause;
export type PauseAnswer = buildStep.PauseAnswer;
export type PauseInput = buildStep.PauseInput;
export type PlacedBeside = buildStep.PlacedBeside;
export type Program = buildStep.Program;
export type ResumeAnswer = buildStep.ResumeAnswer;
export type ResumeInput = buildStep.ResumeInput;
export type Step = buildStep.Step;
@@ -129,6 +134,7 @@ export type StepAskBuild = buildStep.StepAskBuild;
export type StepAskCheck = buildStep.StepAskCheck;
export type StepAskCompose = buildStep.StepAskCompose;
export type StepResult = buildStep.StepResult;
export type TakenVersion = buildStep.TakenVersion;
export type Toolchain = buildStep.Toolchain;
export type ToolchainUsed = buildStep.ToolchainUsed;
@@ -51,7 +51,7 @@ export interface Capacity {
}
export interface CheckAnswer {
"fetch": Fetch;
"fetch": FetchAnswer;
"levels"?: LevelAnswer[];
}
@@ -87,6 +87,30 @@ export interface Fetch {
"repository": string;
}
/** what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package */
export interface FetchAnswer {
"base"?: string;
"beside"?: PlacedBeside[];
"changed"?: string[];
"commit": string;
"conflicts"?: string[];
"group"?: string;
"installed"?: string[];
"merge"?: string;
"modules": string[];
"packages"?: TakenVersion[];
"programs"?: Program[];
"refused"?: FetchRefusal[];
"trees"?: Record<string, ModuleTree>;
"workspace": string;
}
/** a TypeScript module whose packages the fetch did not install, with why */
export interface FetchRefusal {
"module": string;
"reason": string;
}
export interface LevelAnswerDigestNeedsItem {
"needs"?: string[];
"package"?: string;
@@ -127,6 +151,16 @@ export interface LevelAnswer {
"unmet"?: LevelAnswerUnmetItem[];
}
/** the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths */
export interface ModuleTree {
"locks"?: Record<string, string>;
"manifest": string;
/** no longer filled (ADR 0593 section 2); kept so a preview written before still reads */
"packages"?: string[];
"paths"?: Record<string, string>;
"tree": string;
}
/** whether the node takes no new ask */
export interface Pause {
/** when (RFC 3339 time) */
@@ -137,6 +171,21 @@ export interface Pause {
"v"?: number;
}
/** a sibling repository placed as a plain directory under the workspace's beside directory */
export interface PlacedBeside {
"commit": string;
"dir": string;
"repository": string;
}
/** a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it */
export interface Program {
"commit"?: string;
/** ELF, Mach-O or PE */
"kind": string;
"path": string;
}
/** one step a build module runs on a node */
export interface Step {
/** when (RFC 3339 time) */
@@ -173,10 +222,14 @@ export interface StepAskCheck {
"fetch": Fetch;
"kept"?: boolean;
"levels": string[];
/** the node names the check's tests run against */
"node-names"?: string[];
"packages"?: string[];
"run"?: string;
"runner"?: string;
"runner_when"?: string;
/** the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check */
"toolchain"?: ToolchainUsed;
}
export interface StepAskCompose {
@@ -214,7 +267,7 @@ export interface StepResult {
"built"?: boolean;
/** the holder declined the ask: its node lacks what the ask needs */
"declined"?: boolean;
"fetch"?: Fetch;
"fetch"?: FetchAnswer;
"imports"?: string[];
"key"?: string;
/** what the holder's node lacks of it */
@@ -228,6 +281,17 @@ export interface StepResult {
"verdict"?: unknown;
}
/** of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said */
export interface TakenVersion {
"asked": string;
"built": string;
"dir": string;
"named": string;
"package": string;
"said"?: string;
"sum"?: string;
}
/** the toolchain a node reports */
export interface Toolchain {
/** when (RFC 3339 time) */
@@ -111,16 +111,21 @@ export type ComposeAnswer = buildStep.ComposeAnswer;
export type CurrentAnswer = buildStep.CurrentAnswer;
export type CurrentInput = buildStep.CurrentInput;
export type Fetch = buildStep.Fetch;
export type FetchAnswer = buildStep.FetchAnswer;
export type FetchBesideItem = buildStep.FetchBesideItem;
export type FetchRefusal = buildStep.FetchRefusal;
export type LevelAnswer = buildStep.LevelAnswer;
export type LevelAnswerDigest = buildStep.LevelAnswerDigest;
export type LevelAnswerDigestNeedsItem = buildStep.LevelAnswerDigestNeedsItem;
export type LevelAnswerDigestPackagesItem = buildStep.LevelAnswerDigestPackagesItem;
export type LevelAnswerKeptItem = buildStep.LevelAnswerKeptItem;
export type LevelAnswerUnmetItem = buildStep.LevelAnswerUnmetItem;
export type ModuleTree = buildStep.ModuleTree;
export type Pause = buildStep.Pause;
export type PauseAnswer = buildStep.PauseAnswer;
export type PauseInput = buildStep.PauseInput;
export type PlacedBeside = buildStep.PlacedBeside;
export type Program = buildStep.Program;
export type ResumeAnswer = buildStep.ResumeAnswer;
export type ResumeInput = buildStep.ResumeInput;
export type Step = buildStep.Step;
@@ -129,6 +134,7 @@ export type StepAskBuild = buildStep.StepAskBuild;
export type StepAskCheck = buildStep.StepAskCheck;
export type StepAskCompose = buildStep.StepAskCompose;
export type StepResult = buildStep.StepResult;
export type TakenVersion = buildStep.TakenVersion;
export type Toolchain = buildStep.Toolchain;
export type ToolchainUsed = buildStep.ToolchainUsed;
@@ -51,7 +51,7 @@ export interface Capacity {
}
export interface CheckAnswer {
"fetch": Fetch;
"fetch": FetchAnswer;
"levels"?: LevelAnswer[];
}
@@ -87,6 +87,30 @@ export interface Fetch {
"repository": string;
}
/** what a build module's fetch tool answers, whole: the workspace it made, the commit in it, the module directories found; and, when the call gave a base, the merge preview (the trunk commit, the merge commit, the files that conflict, the paths the merge changes, the compiled programs among them, the tree of each module directory). A head that does not merge cleanly is an answer, not an error: merge is empty and conflicts names the files. The Go module adds the sibling repositories placed and the package versions taken; the TypeScript module the directories installed and refused and the group's package */
export interface FetchAnswer {
"base"?: string;
"beside"?: PlacedBeside[];
"changed"?: string[];
"commit": string;
"conflicts"?: string[];
"group"?: string;
"installed"?: string[];
"merge"?: string;
"modules": string[];
"packages"?: TakenVersion[];
"programs"?: Program[];
"refused"?: FetchRefusal[];
"trees"?: Record<string, ModuleTree>;
"workspace": string;
}
/** a TypeScript module whose packages the fetch did not install, with why */
export interface FetchRefusal {
"module": string;
"reason": string;
}
export interface LevelAnswerDigestNeedsItem {
"needs"?: string[];
"package"?: string;
@@ -127,6 +151,16 @@ export interface LevelAnswer {
"unmet"?: LevelAnswerUnmetItem[];
}
/** the tree of one module directory in a merge preview: its git tree, the digest of its manifest, the lock files' digests and the digests of the named paths */
export interface ModuleTree {
"locks"?: Record<string, string>;
"manifest": string;
/** no longer filled (ADR 0593 section 2); kept so a preview written before still reads */
"packages"?: string[];
"paths"?: Record<string, string>;
"tree": string;
}
/** whether the node takes no new ask */
export interface Pause {
/** when (RFC 3339 time) */
@@ -137,6 +171,21 @@ export interface Pause {
"v"?: number;
}
/** a sibling repository placed as a plain directory under the workspace's beside directory */
export interface PlacedBeside {
"commit": string;
"dir": string;
"repository": string;
}
/** a changed file that is a compiled program (issue 1577): its path, its executable format, and the commit of the branch that added it when the merge's tree no longer holds it */
export interface Program {
"commit"?: string;
/** ELF, Mach-O or PE */
"kind": string;
"path": string;
}
/** one step a build module runs on a node */
export interface Step {
/** when (RFC 3339 time) */
@@ -173,10 +222,14 @@ export interface StepAskCheck {
"fetch": Fetch;
"kept"?: boolean;
"levels": string[];
/** the node names the check's tests run against */
"node-names"?: string[];
"packages"?: string[];
"run"?: string;
"runner"?: string;
"runner_when"?: string;
/** the toolchain the checker agreed for the language (ADR 0584 clause 2): a node on another declines the check */
"toolchain"?: ToolchainUsed;
}
export interface StepAskCompose {
@@ -214,7 +267,7 @@ export interface StepResult {
"built"?: boolean;
/** the holder declined the ask: its node lacks what the ask needs */
"declined"?: boolean;
"fetch"?: Fetch;
"fetch"?: FetchAnswer;
"imports"?: string[];
"key"?: string;
/** what the holder's node lacks of it */
@@ -228,6 +281,17 @@ export interface StepResult {
"verdict"?: unknown;
}
/** of a directory whose go.mod requires an asked mesh package, the version it is built against: the asked one, or go.mod's when it names a newer one, said */
export interface TakenVersion {
"asked": string;
"built": string;
"dir": string;
"named": string;
"package": string;
"said"?: string;
"sum"?: string;
}
/** the toolchain a node reports */
export interface Toolchain {
/** when (RFC 3339 time) */
+78 -1
View File
@@ -258,6 +258,9 @@ type CheckJob struct {
Run string `json:"run,omitempty"`
Packages []string `json:"packages,omitempty"`
NodeNames []string `json:"node-names,omitempty"`
// Toolchain is the toolchain the checker agreed for the language (ADR 0584 clause 2): a holder whose node reports
// another version declines the check.
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
}
// ComposeJob asks one compose-candidate build.
@@ -409,6 +412,9 @@ func (c CheckJob) check(id string) error {
return fmt.Errorf("buildstep: the check ask %s names a package %q that is no import path", id, p)
}
}
if t := c.Toolchain; t != nil && (t.Language == "" || t.Version == "" || t.Recipe == "") {
return fmt.Errorf("buildstep: the check ask %s names a toolchain without its language, version and recipe", id)
}
seen := map[string]bool{}
for _, n := range c.NodeNames {
if err := meshkeys.CheckName("node", n); err != nil || strings.Contains(n, ",") || seen[n] {
@@ -520,10 +526,81 @@ type AskBuilt struct {
// CheckAnswer is what a check ask's step answers.
type CheckAnswer struct {
Fetch Fetch `json:"fetch"`
Fetch FetchAnswer `json:"fetch"`
Levels []LevelAnswer `json:"levels,omitempty"`
}
// FetchAnswer is what a build module's fetch tool answers, whole: the part's `FetchAnswer`. A check keeps it as the
// answer's fetch, so the checker reads a conflict (a head that does not merge is an answer, not an error), the changed
// paths, the programs and the trees from it.
type FetchAnswer struct {
Workspace string `json:"workspace"`
Commit string `json:"commit"`
Modules []string `json:"modules"`
// The merge preview, only when the call gave a base.
Base string `json:"base,omitempty"`
Merge string `json:"merge,omitempty"`
Conflicts []string `json:"conflicts,omitempty"`
Changed []string `json:"changed,omitempty"`
Programs []Program `json:"programs,omitempty"`
Trees map[string]ModuleTree `json:"trees,omitempty"`
// The Go module's.
Beside []PlacedBeside `json:"beside,omitempty"`
Packages []TakenVersion `json:"packages,omitempty"`
// The TypeScript module's.
Installed []string `json:"installed,omitempty"`
Refused []FetchRefusal `json:"refused,omitempty"`
Group string `json:"group,omitempty"`
}
// Program is a changed file that is a compiled program (issue 1577).
type Program struct {
Path string `json:"path"`
Kind string `json:"kind"` // ELF, Mach-O or PE
Commit string `json:"commit,omitempty"`
}
// PlacedBeside is a sibling repository placed as a plain directory under the workspace's beside directory.
type PlacedBeside struct {
Repository string `json:"repository"`
Commit string `json:"commit"`
Dir string `json:"dir"`
}
// TakenVersion is, of a directory whose go.mod requires an asked mesh package, the version it is built against.
type TakenVersion struct {
Dir string `json:"dir"`
Package string `json:"package"`
Asked string `json:"asked"`
Named string `json:"named"`
Built string `json:"built"`
Sum string `json:"sum,omitempty"`
Said string `json:"said,omitempty"`
}
// FetchRefusal is a TypeScript module whose packages the fetch did not install, with why.
type FetchRefusal struct {
Module string `json:"module"`
Reason string `json:"reason"`
}
// StepResult is what any step answered: one layout for the four kinds, as the seats' `StepResult` (the fields of the
// kinds do not meet). A step's result bytes read into it strictly.
type StepResult struct {
Artifacts []AskBuilt `json:"artifacts,omitempty"`
Toolchain *ToolchainUsed `json:"toolchain,omitempty"`
Imports []string `json:"imports,omitempty"`
OutOfStep []string `json:"out-of-step,omitempty"`
Fetch *FetchAnswer `json:"fetch,omitempty"`
Levels []LevelAnswer `json:"levels,omitempty"`
Built bool `json:"built,omitempty"`
Key string `json:"key,omitempty"`
Verdict json.RawMessage `json:"verdict,omitempty"`
Declined bool `json:"declined,omitempty"`
Needs []string `json:"needs,omitempty"`
Lacks []string `json:"lacks,omitempty"`
}
// ComposeAnswer is what a compose ask's step answers.
type ComposeAnswer struct {
Built bool `json:"built"`
+18
View File
@@ -72,6 +72,19 @@ func TestAskCheckAndRoundTrip(t *testing.T) {
t.Errorf("a level: %v", err)
}
chk = goodCheck()
chk.Check.Toolchain = &ToolchainUsed{Language: "go", Version: "1.26.0", Recipe: "2"}
raw, err := EncodeStepAsk(chk)
if err != nil {
t.Fatal(err)
}
if back, err := DecodeStepAsk(raw); err != nil || back.Check.Toolchain == nil || back.Check.Toolchain.Version != "1.26.0" {
t.Errorf("the check's toolchain is lost in a round trip: %+v, %v", back.Check, err)
}
chk.Check.Toolchain.Recipe = ""
if _, err := EncodeStepAsk(chk); err == nil || !strings.Contains(err.Error(), "toolchain") {
t.Errorf("a toolchain without a recipe: %v", err)
}
chk = goodCheck()
chk.Language = AskImage
if _, err := EncodeStepAsk(chk); err == nil || !strings.Contains(err.Error(), "no tests") {
t.Errorf("an image check: %v", err)
@@ -251,6 +264,11 @@ func TestTheThreeSeatsFixturesFitTheNeutralForms(t *testing.T) {
case strings.HasPrefix(f.Of, "state:steps/"):
seen++
strict(t, f.Given, &Step{})
var st Step
if err := json.Unmarshal(f.Given, &st); err == nil && len(st.Result) > 0 {
// a step's result keeps what the tool answered, whole (the fetch tool's answer too)
strict(t, st.Result, &StepResult{})
}
}
})
}
+81
View File
@@ -0,0 +1,81 @@
package buildstep
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"time"
)
// ToolchainVersion is the layout version of a toolchains value.
const ToolchainVersion = 1
// Toolchain is the value of a build module's state toolchains under the key <node>.<language>: the version of the
// language's toolchain installed on the node, the build module's recipe version, and when the module saw them. It is
// the part build-step's `Toolchain`, the layout every node-build seat gives that state; the three build modules share
// this neutral form (a seat's generated type is one package per seat). A test reads each seat's fixture strictly into it.
type Toolchain struct {
V int `json:"v,omitempty"`
Language string `json:"language"`
Version string `json:"version"`
Recipe string `json:"recipe"`
At time.Time `json:"at,omitzero"`
}
func checkToolchainLanguage(language string) error {
switch language {
case "go", "typescript":
return nil
}
return fmt.Errorf("buildstep: the toolchain language %q is not go or typescript", language)
}
// ToolchainKey is the key of a node's toolchain of one language: <node>.<language>. It refuses an empty node, a node
// with a dot and a language other than go or typescript.
func ToolchainKey(node, language string) (string, error) {
if node == "" || strings.ContainsAny(node, ". ") {
return "", fmt.Errorf("buildstep: %q is not a node name", node)
}
if err := checkToolchainLanguage(language); err != nil {
return "", err
}
return node + "." + language, nil
}
func (t Toolchain) check() error {
if err := checkToolchainLanguage(t.Language); err != nil {
return err
}
if t.Version == "" || t.Recipe == "" {
return fmt.Errorf("buildstep: a %s toolchain needs its version and its recipe", t.Language)
}
return nil
}
// EncodeToolchain is t as JSON; it refuses a language other than go or typescript and an empty version or recipe.
func EncodeToolchain(t Toolchain) ([]byte, error) {
if err := t.check(); err != nil {
return nil, err
}
t.V = ToolchainVersion
return json.Marshal(t)
}
// DecodeToolchain reads a toolchains value, refusing an unknown field, another layout version and what
// EncodeToolchain refuses.
func DecodeToolchain(data []byte) (Toolchain, error) {
var t Toolchain
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
if err := dec.Decode(&t); err != nil {
return Toolchain{}, fmt.Errorf("buildstep: a toolchain does not read: %w", err)
}
if t.V != ToolchainVersion {
return Toolchain{}, fmt.Errorf("buildstep: a toolchain of version %d, this reads %d", t.V, ToolchainVersion)
}
if err := t.check(); err != nil {
return Toolchain{}, err
}
return t, nil
}
+80
View File
@@ -0,0 +1,80 @@
package buildstep
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// The toolchains value and key are the layout all three node-build seats generate: each seat's fixture of the state
// reads strictly into Toolchain, and the key is the seat's key form <node>.<language>.
func TestTheToolchainsValueAndKeyAreTheSeatsLayout(t *testing.T) {
read := 0
for _, seat := range []string{"node-build-go", "node-build-image", "node-build-node"} {
raw, err := os.ReadFile(filepath.Join("..", "..", "contracts", "seats", seat, "fixtures", "states.json"))
if err != nil {
t.Fatal(err)
}
var fixtures []struct {
Of string `json:"of"`
Given json.RawMessage `json:"given"`
}
if err := json.Unmarshal(raw, &fixtures); err != nil {
t.Fatal(err)
}
for _, f := range fixtures {
if f.Of != "state:toolchains/<node>.<language>" {
continue
}
if back, err := DecodeToolchain(f.Given); err != nil || back.Version == "" {
t.Errorf("%s: the fixture reads as %+v, %v", seat, back, err)
}
if _, err := EncodeToolchain(mustToolchain(t, f.Given)); err != nil {
t.Errorf("%s: %v", seat, err)
}
dec := json.NewDecoder(bytes.NewReader(f.Given))
dec.DisallowUnknownFields()
if err := dec.Decode(&Toolchain{}); err != nil {
t.Errorf("%s: %v", seat, err)
}
read++
}
}
if read != 3 {
t.Errorf("%d toolchain fixtures read, want 3", read)
}
raw, err := EncodeToolchain(Toolchain{Language: "go", Version: "go1.27.1", Recipe: "3", At: time.Date(2026, 11, 5, 10, 0, 0, 0, time.UTC)})
if err != nil {
t.Fatal(err)
}
if back, err := DecodeToolchain(raw); err != nil || back.Version != "go1.27.1" || back.At.IsZero() {
t.Errorf("the value reads back as %+v, %v", back, err)
}
if key, err := ToolchainKey("ace", "typescript"); err != nil || key != "ace.typescript" {
t.Errorf("key %q, %v", key, err)
}
for _, bad := range [][2]string{{"", "go"}, {"a.b", "go"}, {"ace", "rust"}} {
if _, err := ToolchainKey(bad[0], bad[1]); err == nil {
t.Errorf("the key of %v was made", bad)
}
}
if _, err := EncodeToolchain(Toolchain{Language: "go", Version: "go1.27"}); err == nil {
t.Error("a toolchain without its recipe was written")
}
if _, err := DecodeToolchain([]byte(`{"v":1,"language":"go","version":"x","recipe":"1","extra":1}`)); err == nil || !strings.Contains(err.Error(), "extra") {
t.Errorf("a field the layout lacks was read: %v", err)
}
}
func mustToolchain(t *testing.T, raw []byte) Toolchain {
t.Helper()
var tc Toolchain
if err := json.Unmarshal(raw, &tc); err != nil {
t.Fatal(err)
}
return tc
}