diff --git a/contracts/go/seats/nodebuildgo/contract.go b/contracts/go/seats/nodebuildgo/contract.go index 7eec9ed5..d1a9288c 100644 --- a/contracts/go/seats/nodebuildgo/contract.go +++ b/contracts/go/seats/nodebuildgo/contract.go @@ -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) @@ -409,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"` @@ -424,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) diff --git a/contracts/go/seats/nodebuildgo/contract_test.go b/contracts/go/seats/nodebuildgo/contract_test.go index abc309d5..9148353a 100644 --- a/contracts/go/seats/nodebuildgo/contract_test.go +++ b/contracts/go/seats/nodebuildgo/contract_test.go @@ -43,10 +43,12 @@ func TestFixtures(t *testing.T) { 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)) diff --git a/contracts/go/seats/nodebuildimage/contract.go b/contracts/go/seats/nodebuildimage/contract.go index c9675571..69fc44b7 100644 --- a/contracts/go/seats/nodebuildimage/contract.go +++ b/contracts/go/seats/nodebuildimage/contract.go @@ -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) @@ -409,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"` @@ -424,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) diff --git a/contracts/go/seats/nodebuildnode/contract.go b/contracts/go/seats/nodebuildnode/contract.go index 966a4add..51a5a76e 100644 --- a/contracts/go/seats/nodebuildnode/contract.go +++ b/contracts/go/seats/nodebuildnode/contract.go @@ -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) @@ -409,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"` @@ -424,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) diff --git a/contracts/go/seats/nodebuildnode/contract_test.go b/contracts/go/seats/nodebuildnode/contract_test.go index 9bfd4c18..70e0c3a9 100644 --- a/contracts/go/seats/nodebuildnode/contract_test.go +++ b/contracts/go/seats/nodebuildnode/contract_test.go @@ -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)) diff --git a/contracts/parts/build-step.json b/contracts/parts/build-step.json index 9ee1d5d0..c272a8c2 100644 --- a/contracts/parts/build-step.json +++ b/contracts/parts/build-step.json @@ -229,7 +229,7 @@ "type": "object", "properties": { "fetch": { - "$ref": "#/defs/Fetch" + "$ref": "#/defs/FetchAnswer" }, "levels": { "type": "array", @@ -691,7 +691,7 @@ } }, "fetch": { - "$ref": "#/defs/Fetch" + "$ref": "#/defs/FetchAnswer" }, "levels": { "type": "array", @@ -727,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" + ] } } } diff --git a/contracts/seats/node-build-go/fixtures/states.json b/contracts/seats/node-build-go/fixtures/states.json index 0898036e..1a231fd5 100644 --- a/contracts/seats/node-build-go/fixtures/states.json +++ b/contracts/seats/node-build-go/fixtures/states.json @@ -147,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": [ { @@ -208,5 +226,32 @@ ] } } + }, + { + "name": "a finished check step whose head does not merge", + "of": "state:steps/.", + "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" + ] + } + } + } } ] diff --git a/contracts/seats/node-build-node/fixtures/states.json b/contracts/seats/node-build-node/fixtures/states.json index 9a80337a..a9f265f5 100644 --- a/contracts/seats/node-build-node/fixtures/states.json +++ b/contracts/seats/node-build-node/fixtures/states.json @@ -59,5 +59,38 @@ ] } } + }, + { + "name": "a finished check step of a TypeScript merge preview", + "of": "state:steps/.", + "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" + } + } + } + } + } } ] diff --git a/contracts/ts/seats/nodebuildgo/src/index.ts b/contracts/ts/seats/nodebuildgo/src/index.ts index 48102965..83d2a60a 100644 --- a/contracts/ts/seats/nodebuildgo/src/index.ts +++ b/contracts/ts/seats/nodebuildgo/src/index.ts @@ -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; diff --git a/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts b/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts index 2ce76a13..c61ca3bc 100644 --- a/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts +++ b/contracts/ts/seats/nodebuildgo/src/parts/build-step.ts @@ -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; + "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; + "manifest": string; + /** no longer filled (ADR 0593 section 2); kept so a preview written before still reads */ + "packages"?: string[]; + "paths"?: Record; + "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) */ @@ -218,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 */ @@ -232,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) */ diff --git a/contracts/ts/seats/nodebuildimage/src/index.ts b/contracts/ts/seats/nodebuildimage/src/index.ts index f0312a18..d0745fcc 100644 --- a/contracts/ts/seats/nodebuildimage/src/index.ts +++ b/contracts/ts/seats/nodebuildimage/src/index.ts @@ -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; diff --git a/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts b/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts index 2ce76a13..c61ca3bc 100644 --- a/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts +++ b/contracts/ts/seats/nodebuildimage/src/parts/build-step.ts @@ -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; + "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; + "manifest": string; + /** no longer filled (ADR 0593 section 2); kept so a preview written before still reads */ + "packages"?: string[]; + "paths"?: Record; + "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) */ @@ -218,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 */ @@ -232,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) */ diff --git a/contracts/ts/seats/nodebuildnode/src/index.ts b/contracts/ts/seats/nodebuildnode/src/index.ts index 99f5963c..0c66706f 100644 --- a/contracts/ts/seats/nodebuildnode/src/index.ts +++ b/contracts/ts/seats/nodebuildnode/src/index.ts @@ -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; diff --git a/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts b/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts index 2ce76a13..c61ca3bc 100644 --- a/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts +++ b/contracts/ts/seats/nodebuildnode/src/parts/build-step.ts @@ -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; + "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; + "manifest": string; + /** no longer filled (ADR 0593 section 2); kept so a preview written before still reads */ + "packages"?: string[]; + "paths"?: Record; + "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) */ @@ -218,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 */ @@ -232,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) */ diff --git a/sdk/buildstep/ask.go b/sdk/buildstep/ask.go index 1701cab3..7e46704e 100644 --- a/sdk/buildstep/ask.go +++ b/sdk/buildstep/ask.go @@ -526,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"` diff --git a/sdk/buildstep/ask_test.go b/sdk/buildstep/ask_test.go index b452a1cb..e75ef4a6 100644 --- a/sdk/buildstep/ask_test.go +++ b/sdk/buildstep/ask_test.go @@ -264,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{}) + } } }) }