C2: the Go build module reads a compose request's manifests from the catalog entry of the fingerprint it names

Builds: design 47 G §615, §506, A-310

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 22:26:01 +01:00
parent f112011727
commit f6eb5d9efa
9 changed files with 121 additions and 109 deletions
@@ -17,7 +17,7 @@ var Kept = []announce.State{
{Name: "pauses", Readable: true},
// The steps detected for each artifact, kept by its fingerprint (design 47 G §326, §414).
{Name: "detected-steps", Readable: true},
// The condition keeper raises a first view of the indexer's manifests it could not get (ADR 0615 §6) from this.
// The condition keeper raises an entry of the seat mesh-catalog it could not get (ADR 0615 §6) from this.
{Name: "findings", Readable: true},
}
@@ -7,8 +7,7 @@ package main
// engine cannot judge its own next code. The module fetches the commit
// (its fetch step: the forge key and the network, none of the change's code), builds the composer package in the Go
// module the ask names, in a run step shut off with nothing fetched, and keeps the program under its key
// (sdkstep.ComposerKey), so each commit is built once on a node. It fills the request's manifests from the delivery
// indexer's manifests state, by digest, and runs the program shut off: no network, none of the account, the request a
// (sdkstep.ComposerKey), so each commit is built once on a node. It fills the request's manifests from the catalog graph's entries, by fingerprint, and runs the program shut off: no network, none of the account, the request a
// file of a workspace of its own. The answer is the composer's verdict whole (sdkstep.ComposeAnswer); the module
// judges nothing of it.
@@ -26,6 +25,7 @@ import (
seat "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions"
catalogclient "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
meshcat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/composer"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun"
@@ -34,18 +34,10 @@ import (
"git.novox.be/novox/mesh-sdk/go/views"
)
// ManifestsTool is the delivery indexer's read tool of its manifests by digest, which a compose ask's request names (the
// module invokes it, module.json invokes). A manifest by its digest never changes and a compose ask is rare, so the
// module asks for the one key it needs when it needs it, and keeps no picture of the indexer's whole state (ADR 0615).
// indexerModule is the module that holds the seat mesh-catalog, whose manifests state a compose ask reads.
// findingsModule is the module whose findings these are: this one, by the name the runtime gives it (design 47 G §7: no
// fixed module name in a build module's code).
func findingsModule() string { return os.Getenv("MESH_MODULE") }
const indexerModule = "mesh-delivery-indexer"
const ManifestsTool = indexerModule + ".indexer_read_manifests"
// composerLocks hold one mutex per composer key, so a composer two asks need at once is built once.
var composerLocks sync.Map
@@ -85,17 +77,18 @@ func (m *Module) compose(ctx context.Context, fetch steprun.StepFunc, a sdkstep.
return sdkstep.ComposeAnswer{Built: built, Key: key, Verdict: verdict}, nil
}
// askManifest asks the indexer's read tool for one manifest by its key: nil when it keeps none. A call the indexer does not
// answer is a finding of this module's own, which the condition keeper raises (ADR 0615 §6) and the next answer clears.
func askManifest(key string) (json.RawMessage, error) {
return askManifestVia(stdio.Ask, manifestFindings, time.Now, key)
// askEntry asks the seat mesh-catalog's verb entry for the catalog graph entry of one fingerprint (design 47 G §615), and
// reads the manifest whole from it. A call the seat does not answer is a finding of this module's own, which the condition
// keeper raises (ADR 0615 §6) and the next answer clears.
func askEntry(fingerprint string) (meshcat.CatalogEntry, error) {
return askEntryVia(stdio.Ask, manifestFindings, time.Now, fingerprint)
}
// manifestFindings is where askManifest writes its finding: the module's writer once main has started it (design 47 G §689,
// manifestFindings is where askEntry writes its finding: the module's writer once main has started it (design 47 G §689,
// A-16); outside a launch by the runtime, which has no node and no state, nothing is written.
var manifestFindings findingWriter = noFindings{}
// findingWriter is the module's writer (views.Writer) as askManifestVia writes and clears through it.
// findingWriter is the module's writer (views.Writer) as askEntryVia writes and clears through it.
type findingWriter interface {
Write(ctx context.Context, finding seat.Finding) error
Clear(ctx context.Context, scope, id, kind, why string) error
@@ -106,18 +99,18 @@ type noFindings struct{}
func (noFindings) Write(context.Context, seat.Finding) error { return nil }
func (noFindings) Clear(context.Context, string, string, string, string) error { return nil }
// unansweredManifest is the finding of an ask the indexer did not answer, laid out as mesh-sdk lays out a views reader's
// unansweredEntry is the finding of an ask the seat mesh-catalog did not answer, laid out as mesh-sdk lays out a views reader's
// first-view-unanswered (design 47 G §687, A-28): the id is the view's name, escaped as a key's name is.
func unansweredManifest(err string, asked, at time.Time) seat.Finding {
id := keys.EscapeName(findingsModule() + "." + indexerModule + ".manifests")
func unansweredEntry(err string, asked, at time.Time) seat.Finding {
id := keys.EscapeName(findingsModule() + "." + meshcat.Seat + ".entry")
return seat.Finding{Kind: views.UnansweredKind, Scope: "module", ID: id, Severity: seat.FindingSeverityWarning, Source: "views",
Subject: seat.Subject{Scope: "module", ID: findingsModule(), Machine: os.Getenv("MESH_NODE")},
Summary: fmt.Sprintf("%s has no first view of %s's manifests: %s has not answered since %s (%s)", findingsModule(), indexerModule, ManifestsTool,
Summary: fmt.Sprintf("%s has no entry of the seat %s: %s has not answered since %s (%s)", findingsModule(), meshcat.Seat, catalogclient.EntryVerb,
asked.Format(time.RFC3339), strings.TrimSpace(err)),
Headline: fmt.Sprintf("%s waits for %s", findingsModule(), indexerModule), At: at.UTC()}
Headline: fmt.Sprintf("%s waits for the seat %s", findingsModule(), meshcat.Seat), At: at.UTC()}
}
// newFindingsWriter is this module's finding writer: it states the finding of askManifest and observes none of its own.
// newFindingsWriter is this module's finding writer: it states the finding of askEntry and observes none of its own.
func newFindingsWriter() (*views.Writer[seat.Finding, seat.FindingCleared, seat.FindingsStated, seat.FindingsAskedEvent], error) {
return views.NewStdioWriter(views.WriterConfig[seat.Finding, seat.FindingCleared, seat.FindingsStated, seat.FindingsAskedEvent]{
Asked: seat.SubjectFindingsAsked, Module: findingsModule(), State: stdio.State("findings"),
@@ -126,49 +119,41 @@ func newFindingsWriter() (*views.Writer[seat.Finding, seat.FindingCleared, seat.
}})
}
func askManifestVia(ask func(string, any) (json.RawMessage, error), findings findingWriter, now func() time.Time, key string) (json.RawMessage, error) {
func askEntryVia(ask func(string, any) (json.RawMessage, error), findings findingWriter, now func() time.Time, fingerprint string) (meshcat.CatalogEntry, error) {
asked := now().UTC()
raw, err := ask(ManifestsTool, map[string]any{"key": key})
f := unansweredManifest("", asked, now())
raw, err := ask(catalogclient.EntryVerb, map[string]any{"fingerprint": fingerprint})
f := unansweredEntry("", asked, now())
if err != nil {
_ = findings.Write(context.Background(), unansweredManifest(err.Error(), asked, now()))
return nil, err
}
_ = findings.Clear(context.Background(), f.Scope, f.ID, f.Kind, "the indexer answered")
var a struct {
Found bool `json:"found"`
Value json.RawMessage `json:"value"`
_ = findings.Write(context.Background(), unansweredEntry(err.Error(), asked, now()))
return meshcat.CatalogEntry{}, err
}
_ = findings.Clear(context.Background(), f.Scope, f.ID, f.Kind, "the seat answered")
var a meshcat.EntryAnswer
if err := json.Unmarshal(raw, &a); err != nil {
return nil, fmt.Errorf("%s answered something that is not a key's value: %w", ManifestsTool, err)
return meshcat.CatalogEntry{}, fmt.Errorf("%s answered something that is not an entry: %w", catalogclient.EntryVerb, err)
}
if !a.Found {
return nil, nil
}
return a.Value, nil
return a.Entry, nil
}
// manifestOf is the manifest the indexer keeps under digest, as its repository holds it.
func (m *Module) manifestOf(digest string) ([]byte, error) {
if m.ReadManifest == nil {
return nil, errors.New("this module reads no manifests state")
// manifestOf is the manifest a compose request names by digest, whole from the catalog graph entry of the fingerprint it
// names beside it (design 47 G §615, A-310). A digest with no fingerprint, and an entry whose manifest-digest is another,
// refuse the request; composer.Fill names the module.
func (m *Module) manifestOf(man composer.Manifest) ([]byte, error) {
if m.ReadEntry == nil {
return nil, errors.New("this module reads no catalog entries")
}
key, err := catalogclient.ManifestsKey(digest)
if man.Fingerprint == "" {
return nil, fmt.Errorf("the manifest is named by its digest %s with no fingerprint, so no catalog entry holds it", man.Digest)
}
entry, err := m.ReadEntry(man.Fingerprint)
if err != nil {
return nil, err
}
raw, err := m.ReadManifest(key)
if err != nil {
return nil, err
if strings.TrimPrefix(entry.ManifestDigest, "sha256:") != strings.TrimPrefix(man.Digest, "sha256:") {
return nil, fmt.Errorf("the entry at fingerprint %s holds the manifest %s, not the digest %s the request names",
man.Fingerprint, entry.ManifestDigest, man.Digest)
}
if raw == nil {
return nil, fmt.Errorf("the indexer keeps no manifest of digest %s", digest)
}
man, err := catalogclient.DecodeManifest(raw)
if err != nil {
return nil, err
}
return []byte(man.Manifest), nil
return json.Marshal(entry.Manifest)
}
// composerOf is the composer kept under key, or, when none is, the one built now from the fetched commit (built true).
@@ -166,8 +166,8 @@ func TestAComposeAskIsBuiltOnceAndAnsweredOnTheBus(t *testing.T) {
}
return run(ctx, env, name, args...)
}
read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`)
m.ReadManifest = read
read, digests := catalogHolds(t, `{"module":"demo","version":"1"}`)
m.ReadEntry = read
fetches := 0
steps := fakeSteps(new([]string))
@@ -1,15 +0,0 @@
package main
import (
"testing"
"time"
)
// Design 47 G §7 (BG12, BLD16): the finding names the module as the runtime does, never a fixed name.
func TestUnansweredManifestNamesTheModuleTheRuntimeNames(t *testing.T) {
t.Setenv("MESH_MODULE", "mesh-build-go-renamed")
f := unansweredManifest("no answer", time.Unix(0, 0), time.Unix(60, 0))
if f.Subject.ID != "mesh-build-go-renamed" || f.Headline != "mesh-build-go-renamed waits for "+indexerModule {
t.Fatalf("the finding names %q, %q", f.Subject.ID, f.Headline)
}
}
@@ -25,26 +25,35 @@ const nodeEngineComposer = "github.com/novox/mesh-host/cmd/mesh-compose"
const composeCommit = "4444444444444444444444444444444444444444"
// indexerHolds is the delivery indexer's manifests state holding each body under its digest, as the indexer writes it.
func indexerHolds(t *testing.T, bodies ...string) (func(string) (json.RawMessage, error), []string) {
// fingerprintOf is the fingerprint the catalog graph's entry of a manifest's digest is kept under in these tests.
func fingerprintOf(digest string) string { return "fp-" + digest }
// catalogHolds is the seat mesh-catalog's entries, each body's under its fingerprint, with its manifest and the manifest's
// digest as the indexer writes them (design 47 G §506); it answers the digests.
func catalogHolds(t *testing.T, bodies ...string) (func(string) (meshcat.CatalogEntry, error), []string) {
t.Helper()
kept, digests := map[string]json.RawMessage{}, []string{}
kept, digests := map[string]meshcat.CatalogEntry{}, []string{}
for _, b := range bodies {
d := catalogclient.ManifestDigest([]byte(b))
key, _ := catalogclient.ManifestsKey(d)
raw, err := catalogclient.EncodeManifest(meshcat.ManifestsByDigestValue{Module: "demo", Manifest: b})
if err != nil {
var whole map[string]any
if err := json.Unmarshal([]byte(b), &whole); err != nil {
t.Fatal(err)
}
kept[key], digests = raw, append(digests, d)
kept[fingerprintOf(d)] = meshcat.CatalogEntry{Module: "demo", Fp: fingerprintOf(d), Manifest: whole, ManifestDigest: "sha256:" + d}
digests = append(digests, d)
}
return func(key string) (json.RawMessage, error) { return kept[key], nil }, digests
return func(fp string) (meshcat.CatalogEntry, error) {
if e, ok := kept[fp]; ok {
return e, nil
}
return meshcat.CatalogEntry{}, errors.New("the seat keeps no entry of " + fp)
}, digests
}
func composeAskOf(t *testing.T, digest string) sdkstep.StepAsk {
t.Helper()
req, _ := json.Marshal(composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "change",
Manifests: map[string]composer.Manifest{"demo": {Digest: digest}}}}})
Manifests: map[string]composer.Manifest{"demo": {Digest: digest, Fingerprint: fingerprintOf(digest)}}}}})
return sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: sdkstep.AskCheck, Language: sdkstep.AskGo, Asker: "mesh-delivery-assigner",
At: time.Now(), Compose: &sdkstep.ComposeJob{Fetch: sdkstep.Fetch{Repository: "novox/mesh-sdk", Commit: composeCommit},
Dir: "go", Package: composer.Package, Request: req}}
@@ -64,8 +73,8 @@ func composeModule(t *testing.T) *Module {
// indexer's state.
func TestAKeptComposerIsRunWithTheManifestsFilled(t *testing.T) {
m := composeModule(t)
read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`)
m.ReadManifest = read
read, digests := catalogHolds(t, `{"module":"demo","version":"1"}`)
m.ReadEntry = read
a := composeAskOf(t, digests[0])
key := sdkstep.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, sdkstep.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()}, nil)
exe := m.composerPath(key)
@@ -106,7 +115,7 @@ func TestAKeptComposerIsRunWithTheManifestsFilled(t *testing.T) {
// another; an ask naming none keeps the key it always had.
func TestAComposerIsKeptPerAskedVersions(t *testing.T) {
m := composeModule(t)
m.ReadManifest, _ = indexerHolds(t)
m.ReadEntry, _ = catalogHolds(t)
a := composeAskOf(t, "feedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed")
tc := sdkstep.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()}
plain := sdkstep.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, tc, nil)
@@ -133,7 +142,7 @@ func TestAComposerIsKeptPerAskedVersions(t *testing.T) {
// the ask builds fails it naming the directory, and its workspace is released.
func TestAComposeAskFailsLoudly(t *testing.T) {
m := composeModule(t)
m.ReadManifest, _ = indexerHolds(t)
m.ReadEntry, _ = catalogHolds(t)
a := composeAskOf(t, "feedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed")
key := sdkstep.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, sdkstep.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()}, nil)
os.MkdirAll(filepath.Dir(m.composerPath(key)), 0o700)
@@ -156,32 +165,64 @@ func TestAComposeAskFailsLoudly(t *testing.T) {
}
}
// 1622, ADR 0615: the module asks the delivery indexer's read tool for the manifests a compose ask names by digest, and
// reads none of the indexer's state. 1921 (B0): a run's input is asked of the checker seat's verb input, as the manifest
// invokes it, and no state of the checker's inputs is read (ADR 0596 §2).
func TestTheModuleReadsTheIndexersManifests(t *testing.T) {
// Design 47 G §615: the module reads the manifests a compose ask names from the catalog graph's entry, by the seat's verb
// entry, and invokes no tool of the delivery indexer. 1921 (B0): a run's input is asked of the checker seat's verb input, as
// the manifest invokes it, and no state of the checker's inputs is read (ADR 0596 §2).
func TestTheModuleReadsTheCatalogEntries(t *testing.T) {
m := readManifest(t)
if !slices.Contains(m.Invokes, ManifestsTool) || slices.Contains(m.Reads, indexerModule+".manifests") {
t.Errorf("invokes = %v, reads = %v: it asks %s", m.Invokes, m.Reads, ManifestsTool)
if !slices.Contains(m.Invokes, catalogclient.EntryVerb) || slices.ContainsFunc(m.Invokes, func(i string) bool { return strings.HasPrefix(i, "mesh-delivery-indexer") }) {
t.Errorf("invokes = %v: want %s and no tool of the indexer", m.Invokes, catalogclient.EntryVerb)
}
if !slices.Contains(m.Invokes, runInputVerb) || slices.Contains(m.Reads, "mesh-delivery-checker.inputs") {
t.Errorf("invokes = %v, reads = %v: want %s invoked and no inputs state read", m.Invokes, m.Reads, runInputVerb)
}
}
// ADR 0615 §6: an indexer that does not answer is a finding of the module's own, and its next answer clears it.
func TestAnIndexerThatDoesNotAnswerIsAFinding(t *testing.T) {
// Design 47 G §615, A-310: a manifest named by a digest is taken whole from the entry of the fingerprint beside it; a digest
// with no fingerprint, and an entry holding another digest, refuse the request, naming the manifest's module.
func TestAManifestComesWholeFromItsEntryAndAnyMismatchRefuses(t *testing.T) {
read, digests := catalogHolds(t, `{"module":"demo","version":"1"}`, `{"module":"demo","version":"2"}`)
m := composeModule(t)
m.ReadEntry = read
for _, c := range []struct {
name string
man composer.Manifest
want string // empty: the manifest whole
}{
{"the entry's manifest", composer.Manifest{Digest: digests[0], Fingerprint: fingerprintOf(digests[0])}, ""},
{"a digest with no fingerprint", composer.Manifest{Digest: digests[0]}, "no fingerprint"},
{"an entry holding another digest", composer.Manifest{Digest: digests[0], Fingerprint: fingerprintOf(digests[1])}, "not the digest"},
{"a fingerprint no entry has", composer.Manifest{Digest: digests[0], Fingerprint: "fp-none"}, "keeps no entry"},
} {
req := composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "change", Manifests: map[string]composer.Manifest{"demo": c.man}}}}
err := composer.Fill(&req, m.manifestOf)
switch {
case c.want == "" && (err != nil || string(req.Cases[0].Manifests["demo"].Raw) != `{"module":"demo","version":"1"}`):
t.Errorf("%s: %v, %s", c.name, err, req.Cases[0].Manifests["demo"].Raw)
case c.want != "" && (err == nil || !strings.Contains(err.Error(), c.want) || !strings.Contains(err.Error(), "demo")):
t.Errorf("%s: %v", c.name, err)
}
}
}
// ADR 0615 §6: a seat that does not answer is a finding of the module's own, and its next answer clears it.
func TestACatalogThatDoesNotAnswerIsAFinding(t *testing.T) {
// Design 47 G §7 (BG12, BLD16): the finding names the module as the runtime does, never a fixed name.
t.Setenv("MESH_MODULE", "mesh-build-go-renamed")
if f := unansweredEntry("no answer", time.Unix(0, 0), time.Unix(60, 0)); f.Subject.ID != "mesh-build-go-renamed" || f.Headline != "mesh-build-go-renamed waits for the seat mesh-catalog" {
t.Fatalf("the finding names %q, %q", f.Subject.ID, f.Headline)
}
held := map[string][]byte{}
f := mapFindings(held)
at := func() time.Time { return time.Date(2026, 11, 4, 12, 0, 0, 0, time.UTC) }
if _, err := askManifestVia(func(string, any) (json.RawMessage, error) { return nil, errors.New("no responders") }, f, at, "k"); err == nil || len(held) != 1 {
if _, err := askEntryVia(func(string, any) (json.RawMessage, error) { return nil, errors.New("no responders") }, f, at, "fp"); err == nil || len(held) != 1 {
t.Fatalf("err %v, findings %v", err, held)
}
raw, err := askManifestVia(func(string, any) (json.RawMessage, error) {
return json.RawMessage(`{"found":true,"value":{"x":1}}`), nil
}, f, at, "k")
if err != nil || string(raw) != `{"x":1}` || len(held) != 0 {
t.Errorf("raw %s, err %v, findings %v", raw, err, held)
e, err := askEntryVia(func(string, any) (json.RawMessage, error) {
return json.RawMessage(`{"entry":{"module":"demo","repository":"r","commit":"c","manifest":{"x":1},"manifest-digest":"sha256:ab"}}`), nil
}, f, at, "fp")
if err != nil || e.Module != "demo" || e.ManifestDigest != "sha256:ab" || len(held) != 0 {
t.Errorf("entry %+v, err %v, findings %v", e, err, held)
}
}
@@ -201,8 +242,8 @@ func (m mapFindings) Clear(_ context.Context, _, id, kind, _ string) error {
// manifests filled; the module's answer is the program's verdict whole.
func TestAComposeAskBuildsTheNodeEnginesComposerAtItsRoot(t *testing.T) {
m := composeModule(t)
read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`)
m.ReadManifest = read
read, digests := catalogHolds(t, `{"module":"demo","version":"1"}`)
m.ReadEntry = read
a := composeAskOf(t, digests[0])
a.Compose.Fetch.Repository, a.Compose.Dir, a.Compose.Package = "novox/mesh-host", catalogclient.RootPath, nodeEngineComposer
tree := ""
@@ -67,7 +67,7 @@ func main() {
m.GoSeatFile = m.Home + "/go-package-registry.json"
m.ArtifactStoreFile = m.Home + "/artifact-store.json"
m.Settings = m.Home + "/settings.json"
m.ReadManifest = askManifest
m.ReadEntry = askEntry
m.AskInput = stdio.Ask
m.KeptBuilds = busstate.ObjectsOf(KeptBuildsStore)
m.Outputs = busstate.ObjectsOf(steprun.OutputsStore)
@@ -10,6 +10,7 @@ import (
"sync"
"time"
meshcat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
buildrun "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildrun"
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/steprun"
@@ -114,9 +115,9 @@ type Module struct {
Capacity func() int
// SayStep says a pipeline step's end (steprun.AskSteps.SayStep); the seat's publish on this node when nil.
SayStep func(sdkstep.StepEnded) error
// ReadManifest asks the delivery indexer's read tool (ManifestsTool) for one manifest by its key, which a compose ask's
// request names its manifests by (compose.go); it reads none of the indexer's state (ADR 0596); GoModCache, when set, is the Go module cache in place of Cache's (a test).
ReadManifest func(key string) (json.RawMessage, error)
// ReadEntry asks the seat mesh-catalog's verb entry for the catalog graph entry of one fingerprint, which a compose ask's
// request names each manifest it gives by digest with (compose.go, design 47 G §615); GoModCache, when set, is the Go module cache in place of Cache's (a test).
ReadEntry func(fingerprint string) (meshcat.CatalogEntry, error)
// AskInput asks a seat verb the module invokes (module.json) as the runtime calls it (stdio.Ask): the verb a run ask's
// input names, answering the input by its digest (meshdeliverycheckerclient.ReadInput).
AskInput func(key string, body any) (json.RawMessage, error)
@@ -127,11 +127,11 @@ func TestAKeptProgramIsRunOverItsHeadsAndInput(t *testing.T) {
// and used to drop the seats.
func TestAComposeAskKeepsItsSeats(t *testing.T) {
m := composeModule(t)
read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`)
m.ReadManifest = read
read, digests := catalogHolds(t, `{"module":"demo","version":"1"}`)
m.ReadEntry = read
a := composeAskOf(t, digests[0])
req := composer.Request{V: composer.Version, Seats: []json.RawMessage{json.RawMessage(`{"seat":"node-x"}`)},
Cases: []composer.Case{{Name: "change", Manifests: map[string]composer.Manifest{"demo": {Digest: digests[0]}}}}}
Cases: []composer.Case{{Name: "change", Manifests: map[string]composer.Manifest{"demo": {Digest: digests[0], Fingerprint: fingerprintOf(digests[0])}}}}}
a.Compose.Request, _ = json.Marshal(req)
key := sdkstep.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, sdkstep.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()}, nil)
exe := m.composerPath(key)
+2 -2
View File
@@ -41,7 +41,6 @@
],
"invokes": [
"seat:mesh-hand-acts.hand-act",
"mesh-delivery-indexer.indexer_read_manifests",
"seat:oci-artifact-registry.publish",
"seat:oci-artifact-registry.publish-part",
"seat:go-package-registry.publish",
@@ -50,7 +49,8 @@
"seat:mesh-catalog.input",
"seat:mesh-wants.input",
"seat:mesh-module-assigner.assignments",
"seat:mesh-catalog.module"
"seat:mesh-catalog.module",
"seat:mesh-catalog.entry"
],
"config": {
"capacity": {