W12.5: the three build modules answer kept-artifact with the domain SDK's judgement (issue 1966)
Design 47 lines 1888-1906 at b229f5c4 (test 60): the verb sits in every build seat's definition. Its judgement (what may be kept, the kept builds a node holds, the three refusals) is shared code of one domain's modules, so it is the domain SDK's (sdk/buildstep KeptArtifact, ADR 0634 §2): mesh-build-go keeps its store, sweep and tools and drops its own copy of the judgement and its table; mesh-build-node and mesh-build-image keep no build and answer over none, so a module claiming no oci-artifact-registry is refused and the holder is answered not kept. Their manifests serve the verb and invoke seat:mesh-catalog.module, which the judgement asks. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ (Joined from 42d364fe.) Builds: design 47 G §271, §464 (BLD9) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"github.com/therootcompany/xz"
|
||||
@@ -45,7 +46,7 @@ func (m *Module) httpClient() *http.Client {
|
||||
|
||||
// buildArchive packs one archive artifact of the module in dir: a pinned file when it names a url, else the directory
|
||||
// `from` of the module's tree.
|
||||
func (m *Module) buildArchive(ctx context.Context, workspace, module, name, dir string, art catalogue.Artifact, keep *keptFor) (any, error) {
|
||||
func (m *Module) buildArchive(ctx context.Context, workspace, module, name, dir string, art catalogue.Artifact, keep *sdkstep.KeptFor) (any, error) {
|
||||
if art.URL != "" {
|
||||
return m.pinnedArchive(ctx, workspace, module, name, art, keep)
|
||||
}
|
||||
@@ -84,7 +85,7 @@ func (m *Module) buildArchive(ctx context.Context, workspace, module, name, dir
|
||||
|
||||
// pinnedArchive fetches the file an artifact is pinned to, checks it against the pin, lays it out as the artifact says
|
||||
// (kept whole as `file`, or unpacked from `within`) and packs it.
|
||||
func (m *Module) pinnedArchive(ctx context.Context, workspace, module, name string, art catalogue.Artifact, keep *keptFor) (any, error) {
|
||||
func (m *Module) pinnedArchive(ctx context.Context, workspace, module, name string, art catalogue.Artifact, keep *sdkstep.KeptFor) (any, error) {
|
||||
file, err := m.pinnedFile(ctx, workspace, art)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", art.Name, err)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
artifactref "git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient/reference"
|
||||
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
@@ -120,7 +121,7 @@ func (m *Module) build(ctx context.Context, args map[string]any) (any, error) {
|
||||
// The version the build ask carries is the build's argument MESH_VERSION for every artifact (design 47 G §370).
|
||||
version, _ := args["version"].(string)
|
||||
fp, _ := args["fp"].(string)
|
||||
keep := keptOf(publish, manifest, fp)
|
||||
keep := sdkstep.KeepOf(publish, manifest, fp)
|
||||
if art.Kind == catalogue.ArtifactPackage && art.Language == "go" {
|
||||
seat, _ := args["seat"].(string)
|
||||
return m.buildGoPackage(ctx, workspace, dir, *art, version, seat)
|
||||
@@ -183,7 +184,7 @@ func (m *Module) build(ctx context.Context, args map[string]any) (any, error) {
|
||||
// answers the reference; a publish the seat refuses fails the step `not stored:`, unless keep names the build of the
|
||||
// artifact store's holder, which this node then keeps and answers as kept (keptbuilds.go). The packed bytes never stay on
|
||||
// the node: they are held in this process until the publish answered (the lead's decision L9).
|
||||
func (m *Module) packInto(ctx context.Context, workspace, module, name, artifact, kind, root string, keep *keptFor) (BuildResult, error) {
|
||||
func (m *Module) packInto(ctx context.Context, workspace, module, name, artifact, kind, root string, keep *sdkstep.KeptFor) (BuildResult, error) {
|
||||
archive, digest, err := buildstep.Pack(root)
|
||||
if err != nil {
|
||||
return BuildResult{}, fmt.Errorf("packing %s: %w", artifact, err)
|
||||
|
||||
@@ -12,14 +12,13 @@ package main
|
||||
// §20): this module checks no caller of its own.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
|
||||
ociseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
@@ -38,37 +37,16 @@ type KeptObjects interface {
|
||||
Delete(name string) error
|
||||
}
|
||||
|
||||
// keptFor is what a build keeps a refused artifact for: the module, which claims the store's seat, and its build's
|
||||
// fingerprint, which kept-artifact is asked by.
|
||||
type keptFor struct{ Module, Fp string }
|
||||
|
||||
// keptOf is the keep of a build, or nil when nothing may be kept: only a trunk build (publish) of a module whose
|
||||
// manifest at the built commit claims oci-artifact-registry, named by its fingerprint.
|
||||
func keptOf(publish bool, manifest catalogue.Manifest, fp string) *keptFor {
|
||||
if !publish || fp == "" || !manifest.ClaimsSeat(ociseat.Seat) {
|
||||
return nil
|
||||
}
|
||||
return &keptFor{Module: manifest.Module, Fp: fp}
|
||||
}
|
||||
|
||||
// KeptBuild is one kept build: the object's description names its module, fingerprint and the node that kept it.
|
||||
type KeptBuild struct {
|
||||
Module string `json:"module"`
|
||||
Fp string `json:"fp"`
|
||||
Node string `json:"node"`
|
||||
Digest string `json:"digest"`
|
||||
Bytes uint64 `json:"bytes"`
|
||||
LastUsed time.Time `json:"last-used"`
|
||||
}
|
||||
// KeptBuild is one kept build, the domain SDK's form (sdk/buildstep KeptBuild).
|
||||
type KeptBuild = sdkstep.KeptBuild
|
||||
|
||||
// keepBuild keeps the archive of digest ("sha256:<hex>") for k on this node; the object is named by the hex digest.
|
||||
func (m *Module) keepBuild(k keptFor, digest string, archive []byte) error {
|
||||
func (m *Module) keepBuild(k sdkstep.KeptFor, digest string, archive []byte) error {
|
||||
if m.KeptBuilds == nil {
|
||||
return fmt.Errorf("this module keeps no builds (no %s store)", KeptBuildsStore)
|
||||
}
|
||||
hex := strings.TrimPrefix(digest, "sha256:")
|
||||
desc, _ := json.Marshal(KeptBuild{Module: k.Module, Fp: k.Fp, Node: m.Node})
|
||||
if _, err := m.KeptBuilds.Put(hex, archive, string(desc)); err != nil {
|
||||
hex, desc := sdkstep.KeptObject(k, m.Node, digest)
|
||||
if _, err := m.KeptBuilds.Put(hex, archive, desc); err != nil {
|
||||
return fmt.Errorf("keeping the build of %s on %s: %w", k.Module, KeptBuildsStore, err)
|
||||
}
|
||||
if m.Say != nil {
|
||||
@@ -86,17 +64,7 @@ func (m *Module) keptBuilds() ([]KeptBuild, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []KeptBuild{}
|
||||
for _, i := range infos {
|
||||
var k KeptBuild
|
||||
if i.Deleted || json.Unmarshal([]byte(i.Description), &k) != nil || k.Node != m.Node {
|
||||
continue
|
||||
}
|
||||
k.Digest, k.Bytes, k.LastUsed = i.Name, i.Size, i.Modified.UTC()
|
||||
out = append(out, k)
|
||||
}
|
||||
slices.SortFunc(out, func(a, b KeptBuild) int { return strings.Compare(a.Digest, b.Digest) })
|
||||
return out, nil
|
||||
return sdkstep.KeptBuildsOf(infos, m.Node), nil
|
||||
}
|
||||
|
||||
// dropKeptBuild removes the kept build of the hex digest this node keeps; one it does not keep is no error.
|
||||
@@ -130,70 +98,24 @@ func (m *Module) claimsStore(module string) (bool, error) {
|
||||
if ask == nil {
|
||||
ask = stdio.Ask
|
||||
}
|
||||
raw, err := ask(CatalogModuleVerb, map[string]any{"module": module})
|
||||
raw, err := ask(sdkstep.CatalogModuleVerb, map[string]any{"module": module})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var a struct {
|
||||
Module struct {
|
||||
Manifest json.RawMessage `json:"manifest"`
|
||||
} `json:"module"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return false, fmt.Errorf("%s answered what is no module: %w", CatalogModuleVerb, err)
|
||||
}
|
||||
if len(a.Module.Manifest) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
man, err := catalogue.Decode(a.Module.Manifest)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("the manifest of %s: %w", module, err)
|
||||
}
|
||||
return man.ClaimsSeat(ociseat.Seat), nil
|
||||
return sdkstep.ClaimsStoreOf(module, raw)
|
||||
}
|
||||
|
||||
// CatalogModuleVerb is the seat mesh-catalog's verb that answers one module's manifest, as the manifest invokes it.
|
||||
const CatalogModuleVerb = "seat:mesh-catalog.module"
|
||||
|
||||
// keptArtifact is the verb kept-artifact's answer for in.
|
||||
func (m *Module) keptArtifact(in seat.KeptArtifactInput) seat.KeptArtifactAnswer {
|
||||
a := seat.KeptArtifactAnswer{Module: in.Module, Fp: in.Fp}
|
||||
// keptArtifact is the verb kept-artifact's answer for in, the domain SDK's judgement over the builds this node keeps.
|
||||
func (m *Module) keptArtifact(in seat.KeptArtifactInput) sdkstep.KeptArtifactAnswer {
|
||||
kept, err := m.keptBuilds()
|
||||
if err != nil {
|
||||
a.Error = "reading the kept builds: " + err.Error()
|
||||
return a
|
||||
return sdkstep.KeptArtifactAnswer{Module: in.Module, Fp: in.Fp, Error: "reading the kept builds: " + err.Error()}
|
||||
}
|
||||
var other *KeptBuild
|
||||
for i, k := range kept {
|
||||
if k.Module != in.Module || k.Fp != in.Fp {
|
||||
continue
|
||||
}
|
||||
if k.Digest == in.Digest {
|
||||
a.Digest, a.Size, a.Object = k.Digest, int64(k.Bytes), k.Digest
|
||||
if m.Self == "" {
|
||||
a.Error = "this module runs with no name of its own (MESH_MODULE), so its store's name on the bus is unknown"
|
||||
return a
|
||||
}
|
||||
a.Store = catalogue.ObjectsBucket(m.Self, KeptBuildsStore)
|
||||
return a
|
||||
}
|
||||
other = &kept[i]
|
||||
store := ""
|
||||
if m.Self != "" {
|
||||
store = catalogue.ObjectsBucket(m.Self, KeptBuildsStore)
|
||||
}
|
||||
claims, err := m.claimsStore(in.Module)
|
||||
switch {
|
||||
case err != nil:
|
||||
a.Error = "asking whether " + in.Module + " claims " + ociseat.Seat + ": " + err.Error()
|
||||
case !claims:
|
||||
a.Refused = seat.KeptArtifactAnswerRefusedClaimsNoOciArtifactRegistry
|
||||
a.Words = fmt.Sprintf("%s claims no %s: a build node keeps builds for that seat's holder alone", in.Module, ociseat.Seat)
|
||||
case other != nil:
|
||||
a.Refused, a.Digest = seat.KeptArtifactAnswerRefusedDigestDiffers, other.Digest
|
||||
a.Words = fmt.Sprintf("this node keeps the build of %s at %s under the digest %s, not %s", in.Module, in.Fp, other.Digest, in.Digest)
|
||||
default:
|
||||
a.Refused = seat.KeptArtifactAnswerRefusedNotKept
|
||||
a.Words = fmt.Sprintf("this node keeps no build of %s at %s (never kept, or swept)", in.Module, in.Fp)
|
||||
}
|
||||
return a
|
||||
return sdkstep.KeptArtifact(in.Module, in.Fp, in.Digest, kept, store, m.claimsStore)
|
||||
}
|
||||
|
||||
// keptArtifactVerb serves `<seat>.kept-artifact`.
|
||||
|
||||
@@ -75,35 +75,26 @@ func TestTheStoreHoldersRefusedBuildIsKeptAndServedByKeptArtifact(t *testing.T)
|
||||
continue
|
||||
}
|
||||
hex := strings.TrimPrefix(res.Kept, "sha256:")
|
||||
m.AskInput = func(key string, body any) (json.RawMessage, error) {
|
||||
if key != CatalogModuleVerb {
|
||||
return nil, errors.New("asked " + key)
|
||||
}
|
||||
mod, _ := body.(map[string]any)["module"].(string)
|
||||
man := `{"module":"` + mod + `"}`
|
||||
if mod == "demo" || mod == "lost" {
|
||||
man = `{"module":"` + mod + `","claims":[{"name":"oci-artifact-registry","scope":"mesh"}]}`
|
||||
}
|
||||
return json.RawMessage(`{"module":{"module":"` + mod + `","manifest":` + man + `}}`), nil
|
||||
}
|
||||
// The answer's judgement is the domain SDK's (sdk/buildstep KeptArtifact, its own table); here only that the verb
|
||||
// answers this node's kept build from this module's store, asking the seat mesh-catalog nothing for it.
|
||||
m.AskInput = func(key string, _ any) (json.RawMessage, error) { return nil, errors.New("asked " + key) }
|
||||
fp := args["fp"].(string)
|
||||
for _, a := range []struct {
|
||||
name string
|
||||
in seat.KeptArtifactInput
|
||||
refused, store string
|
||||
}{
|
||||
{"the kept build", seat.KeptArtifactInput{Module: "demo", Fp: fp, Digest: hex}, "", "mesh-build-go_kept-builds"},
|
||||
{"another digest", seat.KeptArtifactInput{Module: "demo", Fp: fp, Digest: strings.Repeat("b", 64)}, seat.KeptArtifactAnswerRefusedDigestDiffers, ""},
|
||||
{"a module claiming no store", seat.KeptArtifactInput{Module: "other", Fp: fp, Digest: hex}, seat.KeptArtifactAnswerRefusedClaimsNoOciArtifactRegistry, ""},
|
||||
{"a holder not kept here", seat.KeptArtifactInput{Module: "lost", Fp: fp, Digest: hex}, seat.KeptArtifactAnswerRefusedNotKept, ""},
|
||||
} {
|
||||
got := m.keptArtifact(a.in)
|
||||
if got.Refused != a.refused || got.Store != a.store || (a.refused == "" && (got.Object != hex || got.Size == 0)) ||
|
||||
(a.refused != "" && !strings.Contains(got.Words, a.in.Module)) {
|
||||
t.Errorf("%s: %+v", a.name, got)
|
||||
var verb func(map[string]any) (any, error)
|
||||
for _, tool := range m.SeatTools(nil) {
|
||||
if tool.Name == Seat+"."+seat.VerbKeptArtifact {
|
||||
verb = tool.Run
|
||||
}
|
||||
}
|
||||
out, _ := verb(map[string]any{"module": "demo", "fp": fp, "digest": hex})
|
||||
raw, _ := json.Marshal(out)
|
||||
var got seat.KeptArtifactAnswer
|
||||
if err := json.Unmarshal(raw, &got); err != nil || got.Refused != "" || got.Error != "" || got.Store != "mesh-build-go_kept-builds" || got.Object != hex || got.Size == 0 {
|
||||
t.Errorf("kept-artifact of the kept build: %s", raw)
|
||||
}
|
||||
// Another node's build in the shared store is not this node's to answer.
|
||||
m.AskInput = func(string, any) (json.RawMessage, error) {
|
||||
return json.RawMessage(`{"module":{"module":"demo","manifest":` + holder + `}}`), nil
|
||||
}
|
||||
m.Node = "g14"
|
||||
if got := m.keptArtifact(seat.KeptArtifactInput{Module: "demo", Fp: fp, Digest: hex}); got.Refused != seat.KeptArtifactAnswerRefusedNotKept {
|
||||
t.Errorf("another node's kept build answered %+v", got)
|
||||
|
||||
@@ -77,7 +77,7 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
|
||||
|
||||
// SeatTools are the seat's verbs on this node (ADR 0525 §10).
|
||||
func (m *Module) SeatTools(p *buildstep.Puller) []stdio.Tool {
|
||||
return append(buildstep.SeatTools(Seat, "image", p), stepsTool())
|
||||
return append(buildstep.SeatTools(Seat, "image", p), stepsTool(), m.keptArtifactVerb())
|
||||
}
|
||||
|
||||
// StepEndTool ends one step left running (issue 1890); the module's steps state is the one it reads and writes.
|
||||
|
||||
@@ -2,12 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
const askCommit = "0123456789abcdef0123456789abcdef01234567"
|
||||
@@ -62,15 +64,33 @@ func TestAPulledImageAskCopiesThenBuilds(t *testing.T) {
|
||||
if got := strings.Join(calls, ","); got != "build_image_copy,build_image_fetch,build_image_build" {
|
||||
t.Errorf("calls = %s", got)
|
||||
}
|
||||
served := map[string]bool{}
|
||||
served := map[string]stdio.Tool{}
|
||||
for _, tool := range m.SeatTools(p) {
|
||||
served[tool.Name] = true
|
||||
served[tool.Name] = tool
|
||||
}
|
||||
for _, verb := range []string{"current", "pause", "resume"} {
|
||||
if !served[Seat+"."+verb] {
|
||||
for _, verb := range []string{"current", "pause", "resume", "kept-artifact"} {
|
||||
if _, ok := served[Seat+"."+verb]; !ok {
|
||||
t.Errorf("%s.%s is not served", Seat, verb)
|
||||
}
|
||||
}
|
||||
// Design 47 lines 1888-1906 (test 60): kept-artifact is the domain SDK's judgement over no kept build here: the
|
||||
// store's holder is not kept, any other module claims no oci-artifact-registry.
|
||||
m.Call = func(key string, body any) (json.RawMessage, error) {
|
||||
mod, _ := body.(map[string]any)["module"].(string)
|
||||
man := `{"module":"` + mod + `"}`
|
||||
if mod == "holder" {
|
||||
man = `{"module":"holder","claims":[{"name":"oci-artifact-registry","scope":"mesh"}]}`
|
||||
}
|
||||
return json.RawMessage(`{"module":{"module":"` + mod + `","manifest":` + man + `}}`), nil
|
||||
}
|
||||
for mod, refused := range map[string]string{"holder": "not kept", "web": "claims no oci-artifact-registry"} {
|
||||
if v, ok := served[Seat+".kept-artifact"]; ok {
|
||||
out, _ := v.Run(map[string]any{"module": mod, "fp": "fp1", "digest": strings.Repeat("a", 64)})
|
||||
if a, _ := out.(sdkstep.KeptArtifactAnswer); a.Refused != refused || !strings.Contains(a.Words, mod) {
|
||||
t.Errorf("kept-artifact of %s answered %+v, want refused %q", mod, out, refused)
|
||||
}
|
||||
}
|
||||
}
|
||||
// A node without /dev/net/tun (a build node, 2026-10-28) takes no ask: its capacity is 0 and says why, so the ask goes to a node
|
||||
// that can build it instead of failing in buildah's network setup; once it is there again, the capacity is back.
|
||||
tun := m.Tun
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestTheModuleInvokesTheImageSeatAndHoldsNoRegistry(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := []string{"seat:container-image-registry.publish", "seat:container-image-registry.publish-part",
|
||||
"seat:container-image-registry.mirror", "seat:mesh-module-assigner.assignments"}; !slices.Equal(m.Invokes, want) {
|
||||
"seat:container-image-registry.mirror", "seat:mesh-module-assigner.assignments", "seat:mesh-catalog.module"}; !slices.Equal(m.Invokes, want) {
|
||||
t.Errorf("invokes = %v, want %v", m.Invokes, want)
|
||||
}
|
||||
// The seat's outcomes a copy waits on; the version comes in the ask, so version.numbered is not heard (design 47 G §413).
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
// **The artifact store's holder takes its own new build even when its seat is the one broken** (novox/hq design 47,
|
||||
// lines 1888-1906 at b229f5c4; test 60; milestone 47/G W12.5, issue 1966): the verb kept-artifact sits in every build
|
||||
// seat's definition, so this module serves it too. It keeps no build of its own: its answer is the domain SDK's
|
||||
// judgement (sdk/buildstep KeptArtifact, ADR 0634 §2) over no kept build, so a module that claims no
|
||||
// oci-artifact-registry is refused as such and the store's holder is answered not kept here. Who may call it is the
|
||||
// seat definition's (the node-engines alone), granted by the grants part (ADR 0666 §20): this module checks no caller.
|
||||
|
||||
import (
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildimage"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
// keptArtifact is the verb kept-artifact's answer for in: this node keeps no build.
|
||||
func (m *Module) keptArtifact(in seat.KeptArtifactInput) sdkstep.KeptArtifactAnswer {
|
||||
return sdkstep.KeptArtifact(in.Module, in.Fp, in.Digest, nil, "", func(module string) (bool, error) {
|
||||
call := m.call()
|
||||
raw, err := call(sdkstep.CatalogModuleVerb, map[string]any{"module": module})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return sdkstep.ClaimsStoreOf(module, raw)
|
||||
})
|
||||
}
|
||||
|
||||
// keptArtifactVerb serves `<seat>.kept-artifact`.
|
||||
func (m *Module) keptArtifactVerb() stdio.Tool {
|
||||
return stdio.Tool{Name: Seat + "." + seat.VerbKeptArtifact,
|
||||
Description: "Where the build this node kept for the artifact store's holder (the holder of " + sdkstep.StoreSeat +
|
||||
") is. This build module keeps none: it refuses a module that claims no " + sdkstep.StoreSeat +
|
||||
" and answers the holder's build not kept here, naming the module.",
|
||||
Input: map[string]any{"type": "object", "required": []string{"module", "fp", "digest"}, "properties": map[string]any{
|
||||
"module": map[string]any{"type": "string", "description": "the module, the holder of " + sdkstep.StoreSeat},
|
||||
"fp": map[string]any{"type": "string", "description": "its build's fingerprint"},
|
||||
"digest": map[string]any{"type": "string", "description": "the artifact's sha256, 64 lowercase hex digits"}}},
|
||||
Run: func(args map[string]any) (any, error) {
|
||||
str := func(k string) string { s, _ := args[k].(string); return s }
|
||||
return m.keptArtifact(seat.KeptArtifactInput{Module: str("module"), Fp: str("fp"), Digest: str("digest")}), nil
|
||||
}}
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func TestManifestKeepsItsStatesClaimsItsBuildSeatAndDeclaresItsHealthTool(t *tes
|
||||
if want := []string{"keys", MirrorWaitsState, "pauses", "steps"}; !slices.Equal(states, want) {
|
||||
t.Errorf("state = %v, want %v", states, want)
|
||||
}
|
||||
if len(m.Claims) != 1 || m.Claims[0].Name != Seat || !slices.Equal(m.Claims[0].Serves, []string{"current", "drop", "pause", "resume", "steps"}) {
|
||||
if len(m.Claims) != 1 || m.Claims[0].Name != Seat || !slices.Equal(m.Claims[0].Serves, []string{"current", "drop", "pause", "resume", "steps", "kept-artifact"}) {
|
||||
t.Errorf("claims = %v, want the seat %s alone", m.Claims, Seat)
|
||||
}
|
||||
raw, _ := os.ReadFile(filepath.Join(moduleDir, "module.json"))
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
{
|
||||
"name": "node-build-image",
|
||||
"scope": "node",
|
||||
"serves": ["current", "drop", "pause", "resume", "steps"]
|
||||
"serves": ["current", "drop", "pause", "resume", "steps", "kept-artifact"]
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
@@ -65,7 +65,8 @@
|
||||
"seat:container-image-registry.publish",
|
||||
"seat:container-image-registry.publish-part",
|
||||
"seat:container-image-registry.mirror",
|
||||
"seat:mesh-module-assigner.assignments"
|
||||
"seat:mesh-module-assigner.assignments",
|
||||
"seat:mesh-catalog.module"
|
||||
],
|
||||
"consumes": [
|
||||
"container-image-registry.published",
|
||||
|
||||
@@ -87,7 +87,7 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep
|
||||
|
||||
// SeatTools are the seat's verbs on this node (ADR 0525 §10).
|
||||
func (m *Module) SeatTools(p *buildstep.Puller) []stdio.Tool {
|
||||
return append(buildstep.SeatTools(Seat, "Node", p), m.stepsTool())
|
||||
return append(buildstep.SeatTools(Seat, "Node", p), m.stepsTool(), m.keptArtifactVerb())
|
||||
}
|
||||
|
||||
// StepEndTool ends one step left running (issue 1890); the module's steps state is the one it reads and writes.
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
const askCommit = "0123456789abcdef0123456789abcdef01234567"
|
||||
@@ -123,13 +124,31 @@ func TestManifestTakesTheBuildCapacity(t *testing.T) {
|
||||
t.Error("no settings file")
|
||||
}
|
||||
m0 := &Module{Node: "ace", Capacity: func() int { return 0 }}
|
||||
served := map[string]bool{}
|
||||
served := map[string]stdio.Tool{}
|
||||
for _, tool := range m0.SeatTools(m0.Puller(nil, buildstep.NewFakeAsks())) {
|
||||
served[tool.Name] = true
|
||||
served[tool.Name] = tool
|
||||
}
|
||||
for _, verb := range []string{"current", "pause", "resume"} {
|
||||
if !served[Seat+"."+verb] {
|
||||
for _, verb := range []string{"current", "pause", "resume", "kept-artifact"} {
|
||||
if _, ok := served[Seat+"."+verb]; !ok {
|
||||
t.Errorf("%s.%s is not served", Seat, verb)
|
||||
}
|
||||
}
|
||||
// Design 47 lines 1888-1906 (test 60): kept-artifact is the domain SDK's judgement over no kept build here: the
|
||||
// store's holder is not kept, any other module claims no oci-artifact-registry.
|
||||
m0.Call = func(key string, body any) (json.RawMessage, error) {
|
||||
mod, _ := body.(map[string]any)["module"].(string)
|
||||
man := `{"module":"` + mod + `"}`
|
||||
if mod == "holder" {
|
||||
man = `{"module":"holder","claims":[{"name":"oci-artifact-registry","scope":"mesh"}]}`
|
||||
}
|
||||
return json.RawMessage(`{"module":{"module":"` + mod + `","manifest":` + man + `}}`), nil
|
||||
}
|
||||
for mod, refused := range map[string]string{"holder": "not kept", "web": "claims no oci-artifact-registry"} {
|
||||
if v, ok := served[Seat+".kept-artifact"]; ok {
|
||||
out, _ := v.Run(map[string]any{"module": mod, "fp": "fp1", "digest": strings.Repeat("a", 64)})
|
||||
if a, _ := out.(sdkstep.KeptArtifactAnswer); a.Refused != refused || !strings.Contains(a.Words, mod) {
|
||||
t.Errorf("kept-artifact of %s answered %+v, want refused %q", mod, out, refused)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
// **The artifact store's holder takes its own new build even when its seat is the one broken** (novox/hq design 47,
|
||||
// lines 1888-1906 at b229f5c4; test 60; milestone 47/G W12.5, issue 1966): the verb kept-artifact sits in every build
|
||||
// seat's definition, so this module serves it too. It keeps no build of its own: its answer is the domain SDK's
|
||||
// judgement (sdk/buildstep KeptArtifact, ADR 0634 §2) over no kept build, so a module that claims no
|
||||
// oci-artifact-registry is refused as such and the store's holder is answered not kept here. Who may call it is the
|
||||
// seat definition's (the node-engines alone), granted by the grants part (ADR 0666 §20): this module checks no caller.
|
||||
|
||||
import (
|
||||
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildnode"
|
||||
sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
)
|
||||
|
||||
// keptArtifact is the verb kept-artifact's answer for in: this node keeps no build.
|
||||
func (m *Module) keptArtifact(in seat.KeptArtifactInput) sdkstep.KeptArtifactAnswer {
|
||||
return sdkstep.KeptArtifact(in.Module, in.Fp, in.Digest, nil, "", func(module string) (bool, error) {
|
||||
call := m.Call
|
||||
if call == nil {
|
||||
call = stdio.Ask
|
||||
}
|
||||
raw, err := call(sdkstep.CatalogModuleVerb, map[string]any{"module": module})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return sdkstep.ClaimsStoreOf(module, raw)
|
||||
})
|
||||
}
|
||||
|
||||
// keptArtifactVerb serves `<seat>.kept-artifact`.
|
||||
func (m *Module) keptArtifactVerb() stdio.Tool {
|
||||
return stdio.Tool{Name: Seat + "." + seat.VerbKeptArtifact,
|
||||
Description: "Where the build this node kept for the artifact store's holder (the holder of " + sdkstep.StoreSeat +
|
||||
") is. This build module keeps none: it refuses a module that claims no " + sdkstep.StoreSeat +
|
||||
" and answers the holder's build not kept here, naming the module.",
|
||||
Input: map[string]any{"type": "object", "required": []string{"module", "fp", "digest"}, "properties": map[string]any{
|
||||
"module": map[string]any{"type": "string", "description": "the module, the holder of " + sdkstep.StoreSeat},
|
||||
"fp": map[string]any{"type": "string", "description": "its build's fingerprint"},
|
||||
"digest": map[string]any{"type": "string", "description": "the artifact's sha256, 64 lowercase hex digits"}}},
|
||||
Run: func(args map[string]any) (any, error) {
|
||||
str := func(k string) string { s, _ := args[k].(string); return s }
|
||||
return m.keptArtifact(seat.KeptArtifactInput{Module: str("module"), Fp: str("fp"), Digest: str("digest")}), nil
|
||||
}}
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func TestManifestKeepsItsStatesClaimsItsBuildSeatAndDeclaresItsHealthTool(t *tes
|
||||
if want := []string{"keys", "pauses", "steps", "toolchains"}; !slices.Equal(states, want) {
|
||||
t.Errorf("state = %v, want %v", states, want)
|
||||
}
|
||||
if len(m.Claims) != 1 || m.Claims[0].Name != Seat || !slices.Equal(m.Claims[0].Serves, []string{"current", "drop", "pause", "resume", "steps"}) {
|
||||
if len(m.Claims) != 1 || m.Claims[0].Name != Seat || !slices.Equal(m.Claims[0].Serves, []string{"current", "drop", "pause", "resume", "steps", "kept-artifact"}) {
|
||||
t.Errorf("claims = %v, want the seat %s alone", m.Claims, Seat)
|
||||
}
|
||||
if !slices.Contains(m.Tools, HealthTool) {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
{
|
||||
"name": "node-build-node",
|
||||
"scope": "node",
|
||||
"serves": ["current", "drop", "pause", "resume", "steps"]
|
||||
"serves": ["current", "drop", "pause", "resume", "steps", "kept-artifact"]
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
@@ -79,7 +79,8 @@
|
||||
"seat:oci-artifact-registry.publish-part",
|
||||
"seat:npm-package-registry.publish",
|
||||
"seat:npm-package-registry.publish-part",
|
||||
"seat:mesh-module-assigner.assignments"
|
||||
"seat:mesh-module-assigner.assignments",
|
||||
"seat:mesh-catalog.module"
|
||||
],
|
||||
"own-secrets": {
|
||||
"broker": "${dir:state}/broker"
|
||||
|
||||
Reference in New Issue
Block a user