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:
@@ -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)
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user