From 1eb481ccc87ca080ef7e1f868c9c1efffa8e7c2f Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 01:18:40 +0100 Subject: [PATCH] Builder: the manifest names no other module, the floor reads the seat git and the catalog half MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The floor's tags are the seat git's verb tags and its requirements what the catalog half's entries name for the package (indexer_requirements and the forge bridge's bridge_tags go from code and manifest); a catalog revision the builder hears wakes a silent floor (the forge stream's consume and its wake go). The guard test says A-107 relaxes ADR 0437 section 8 for a seat's verbs and accepted kinds. The integration bed creates the state bounds the step bounds are kept in. The assignments view's seat form (A-119 (3)) is not built here. Builds: design 47 G §7, A-82, A-107, A-119 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../cmd/mesh-delivery-builder/catalog.go | 5 ++ .../mesh-delivery-builder/integration_test.go | 9 ++- .../cmd/mesh-delivery-builder/main.go | 32 ++------- .../cmd/mesh-delivery-builder/names.go | 5 +- .../cmd/mesh-delivery-builder/refused_test.go | 25 ++----- .../cmd/mesh-delivery-builder/versions.go | 69 ++++++------------- .../internal/versions/again.go | 4 +- .../internal/versions/entrynames.go | 17 +++++ .../internal/versions/entrynames_test.go | 19 +++++ .../internal/versions/floor.go | 18 +++-- .../internal/versions/versions.go | 8 +-- .../internal/versions/versions_test.go | 8 +-- .../mesh-delivery-builder/manifest_test.go | 15 ++-- modules/mesh-delivery-builder/module.json | 4 +- 14 files changed, 111 insertions(+), 127 deletions(-) diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/catalog.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/catalog.go index c1470990..79a4fc77 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/catalog.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/catalog.go @@ -75,6 +75,8 @@ type catalogHalf struct { halves map[string]*views.Follower once sync.Once ctx context.Context + // floor is told each entry change: a floor whose tags or requirements were silent is asked again (design 47 G A-119). + floor func(context.Context) // holders is told each entry change: a claim of a build seat may have moved (design 47 G A-82). holders func() } @@ -112,6 +114,9 @@ func (c *catalogHalf) Changed(ctx context.Context) func(branch, module string, e c.logf("%v", err) } c.b.Entry(ctx, origin, e) + if c.floor != nil { + defer c.floor(ctx) + } c.mu.Lock() was := c.named[module] now := namesOf(e) diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go index ef002e9b..431f1873 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go @@ -291,6 +291,10 @@ func startBedWith(t *testing.T, closed bool) *bed { viewstest.Serve(t, ctx, js, ctl, assignerModule, "assignments", assignerModule+".assigner_read_assignments", readers...) b.builds = mk(builds.BuilderBuildsBucket, builds.BuilderBuildsHistory) b.instances = mk(instancesBucket, 1) + // The state bounds, whose keys have each its own time to live: an ask's step bounds (design 47 G A-77, A-118) are kept in it. + if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: versions.BoundsBucket, History: 1, LimitMarkerTTL: time.Second}); err != nil { + t.Fatal(err) + } b.out = &saidLog{} // The builder's module log, as the runtime raises it (ADR 0297): its stream on the log's subjects. logStream := buscompose.LogStreamName(builds.BuilderModule, builds.BuilderLog) @@ -1043,7 +1047,7 @@ func TestARestartedBuilderPutsNoSecondAsk(t *testing.T) { // registry is the part of the bed design 51's builder cases need: the builder's states versions, findings, floor-waits and // reasks, EVENTS with the seat oci-artifact-registry's outcomes and the builder's durable consumer of it, the builder's // own events kept, and the floor's three sources served on the bus as their holders answer through the tool runner -// ({result}): the seat's versions, the forge bridge's bridge_tags and the indexer's indexer_requirements. +// ({result}): the seat's versions, the seat git's tags and the indexer's indexer_requirements. type registry struct { b *bed versions, findings, reasks jetstream.KeyValue @@ -1066,7 +1070,6 @@ func (b *bed) registry(t *testing.T, node string, seatVersions ...string) *regis // EVENTS with two seats' outcomes, and the forge bridge's stream (a branch's end): each with the builder's durable consumer. for stream, subjects := range map[string][]string{ eventsStream: {"mesh.mod.*.event.>", "mesh.seat.*.event.>"}, - forgeStream: {process.ModuleStreamSubject(forgeBridgeModule, ">")}, } { if _, err := b.js.CreateOrUpdateStream(b.ctx, jetstream.StreamConfig{Name: stream, Subjects: subjects}); err != nil { t.Fatal(err) @@ -1093,7 +1096,7 @@ func (b *bed) registry(t *testing.T, node string, seatVersions ...string) *regis } for subject, answer := range map[string]any{ "mesh.seat." + ociartifactregistry.Seat + ".call.versions.>": map[string]any{"versions": listed}, - "mesh.mod.mesh-delivery-forge-bridge.call.bridge_tags.>": map[string]any{"tags": []string{}}, + "mesh.seat.git.call.tags.>": map[string]any{"tags": []string{}}, "mesh.mod." + indexerModule + ".call.indexer_requirements.>": map[string]any{"requirements": []string{}}, } { reply, _ := json.Marshal(map[string]any{"result": answer, "node": "ctl"}) diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go index 40948fb0..390eb0bd 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go @@ -5,7 +5,6 @@ package main import ( - "cmp" "context" "errors" "fmt" @@ -168,7 +167,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro floorWaits.Keep(kvKeeper{ctx: ctx, kv: kv}, heldFloorWaits(ctx, kv, logger.Printf)) }) // A package is numbered under its name in its ecosystem, read from the catalog half's entries (design 32, design 47 G §348). - numberer := &versions.Numberer{Versions: versions.Versions{Store: awaitedVersions{b: versionsKV}}, Floor: floorVia(calls.Call, versions.HolderOf(branches)), Waits: floorWaits, + numberer := &versions.Numberer{Versions: versions.Versions{Store: awaitedVersions{b: versionsKV}}, Floor: floorVia(calls.Call, versions.RequirementsFrom(versions.EntriesOf(branches))), Waits: floorWaits, Names: versions.NamesFrom(versions.EntriesOf(branches))} // A publish that ends badly is a finding (design 51 *Health and conditions*): a holder's refused, a version no holder // answered within its bound (a key of the state bounds with its own time to live), a seat's outcome that does not read. @@ -367,22 +366,6 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // Each consumer that is not there yet is the finding family-not-heard naming its stream until it binds at a later // start (heard.Writer; ADR 0615 §5); one there that cannot be heard stops the builder, as before. notHeard := found.notHeard(ctx) - var stopped error - hear := func(stream, consequence string, each func(jetstream.Msg)) { - _ = notHeard.Bind(stream, consequence, func() error { - cons, err := cred.EventConsumer(ctx, js, stream) - if err != nil { - return fmt.Errorf("the consumer %s_%s on %s does not exist yet: %w", cred.Node, cred.Module, stream, err) - } - cc, err := process.HearEvents(cons, each, logger.Printf) - if err != nil { - stopped = cmp.Or(stopped, err) - return err - } - go func() { <-ctx.Done(); cc.Stop() }() - return nil - }) - } // The seat mesh-artifact-sweep says artifacts-gone when it found a key's blob gone (issue 1937, ADR 0571 §2): the // builder's durable consumer on EVENTS, made by the grants part from the manifest's consumes. Where it is not there yet the builder says // so; it still asks held at its start and at every module value. @@ -425,9 +408,6 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro } } hub.OnBound(reads.atBind) // what a node's engine said before the consumer was bound (ADR 0668 §4) - if stopped != nil { - return stopped - } reader.Start(ctx, js, cred, process.AwaitBucket(ctx, js, conn, firstViewsBucket, "builder_first_views reads nothing and builder_first_view_again is not heard", logger.Printf)) // Every part has registered: the hub pulls the module's durable. Where the consumer is not there yet the builder says so // and binds it at a later change (heard.Writer; ADR 0615 §5). @@ -503,13 +483,9 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro go func() { errs <- follow(ctx, qw, eachRead) }() } }) - // Each forge message wakes a floor whose bridge_tags was silent (design 51 *The floor*). A consumer not there yet is said. - ends := &forgeWakes{logf: logger.Printf, wake: func(ctx context.Context) { - (&floorWake{b: b, waits: floorWaits, logf: logger.Printf}).wake(ctx, versions.WakeForge, "", "") - }} - hear(forgeStream, "the forge's messages are not heard until the builder starts again", func(m jetstream.Msg) { ends.heard(ctx, m) }) - if stopped != nil { - return stopped + // A catalog revision the builder hears wakes a floor whose tags or requirements were silent (design 47 G A-119). + half.floor = func(ctx context.Context) { + (&floorWake{b: b, waits: floorWaits, logf: logger.Printf}).wake(ctx, versions.WakeRevision, "", "") } // The state engine-reads, new with ADR 0618 (issue 1790's kind): until it is on the bus each statement is kept in memory // only, and builder_engine_reads answers nothing. diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/names.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/names.go index 1c4b475a..79b2a481 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/names.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/names.go @@ -10,9 +10,8 @@ import ( // (novox/hq ADR 0625 §4, ADR 0634 §1), except the clients of the indexer's and the assigner's states, whose holder names // are used from there. const ( - gateModule = "mesh-delivery-gate" - checkerModule = "mesh-delivery-checker" - forgeBridgeModule = "mesh-delivery-forge-bridge" + gateModule = "mesh-delivery-gate" + checkerModule = "mesh-delivery-checker" // indexerModule is the holder of the states modules, manifests, candidates and packages the builder follows. indexerModule = "mesh-delivery-indexer" diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/refused_test.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/refused_test.go index 34d6449c..ab4875a0 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/refused_test.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/refused_test.go @@ -42,7 +42,6 @@ import ( func TestARefusedBucketIsSaidAndThePartGoesOn(t *testing.T) { t.Run("a floor's wake is heard on the events consumer (design 51 The floor)", aFloorsWakeIsHeard) t.Run("a failed trunk build is a finding until a later done one (DLV-B7)", aFailedTrunkBuildIsAFinding) - t.Run("a branch's end is acknowledged once its tagged versions are removed (design 51 Tagged versions)", aBranchsEndIsHeard) refused := refusingBus{err: errors.New("nats: permissions violation for publish to $JS.API.CONSUMER.CREATE.KV_mesh-delivery-indexer_modules")} if _, err := process.EssentialBucket(context.Background(), refusingBus{refuseOpen: true, err: refused.err}, "builder", "mesh-delivery-indexer_modules", "work"); err == nil || !strings.Contains(err.Error(), "grant of mesh-delivery-indexer_modules") { @@ -295,20 +294,6 @@ func (noVersions) Update(context.Context, string, []byte, uint64) (uint64, error } func (noVersions) KeysOf(context.Context, string) ([]string, error) { return nil, nil } -func aBranchsEndIsHeard(t *testing.T) { - // A forge message wakes a floor and is acknowledged; no tagged version is removed on a branch's end (design 47 G §373). - var logs bytes.Buffer - woken := 0 - h := &forgeWakes{wake: func(context.Context) { woken++ }, logf: log.New(&logs, "", 0).Printf} - open := &wakeMsg{subject: "mesh.mod.mesh-delivery-forge-bridge.event.forge.o.r.pull", data: []byte(`{"kind":"pull","repository":"o/r","branch":"feat/x","action":"opened"}`)} - h.heard(context.Background(), open) - merged := &wakeMsg{subject: "mesh.mod.mesh-delivery-forge-bridge.event.forge.o.r.merged", data: []byte(`{"kind":"merged","repository":"o/r","branch":"feat/x"}`)} - h.heard(context.Background(), merged) - if !open.acked || !merged.acked || woken != 2 || open.delayed != 0 || merged.delayed != 0 { - t.Errorf("an open pull acked %v, a merge acked %v, floors woken %d times", open.acked, merged.acked, woken) - } -} - // failingVersions is a state versions that cannot be read. type failingVersions struct{ noVersions } @@ -340,7 +325,7 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) { fw := &versions.FloorWaits{Said: f.floorSaid(context.Background())} fw.Wait("m/lib", versions.SeatGoPackages, "", versions.Floor{Silent: []string{versions.SourceTags}}, nil, time.Unix(0, 0)) floorKey := meshconditionsclient.Key(meshconditionsclient.ScopeModule, "m/lib", versions.FloorSilent) - if got, err := decodeFinding(st.vals[floorKey]); err != nil || !strings.Contains(got.Summary, "bridge_tags did not answer") { + if got, err := decodeFinding(st.vals[floorKey]); err != nil || !strings.Contains(got.Summary, "git.tags did not answer") { t.Fatalf("the floor finding = %+v, %v", got, err) } fw.Answered("m/lib") @@ -438,12 +423,12 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) { delete(st.vals, callKey) // ADR 0615 §5 (review round 1): a consumer not there yet is family-not-heard naming its stream, cleared when it binds. notHeard := f.notHeard(context.Background()) - unheardKey := heard.Key(builds.BuilderModule, "FORGE") - _ = notHeard.Bind("FORGE", "a branch's end is not heard", func() error { return errors.New("consumer not found") }) - if got, err := decodeFinding(st.vals[unheardKey]); err != nil || got.Kind != heard.Kind || !strings.Contains(got.Summary, "does not hear FORGE") { + unheardKey := heard.Key(builds.BuilderModule, "EVENTS") + _ = notHeard.Bind("EVENTS", "a branch's end is not heard", func() error { return errors.New("consumer not found") }) + if got, err := decodeFinding(st.vals[unheardKey]); err != nil || got.Kind != heard.Kind || !strings.Contains(got.Summary, "does not hear EVENTS") { t.Fatalf("a consumer not there: the finding = %+v, %v", got, err) } - _ = notHeard.Bind("FORGE", "a branch's end is not heard", func() error { return nil }) + _ = notHeard.Bind("EVENTS", "a branch's end is not heard", func() error { return nil }) if _, ok := st.vals[unheardKey]; ok { t.Error("a consumer bound leaves family-not-heard") } diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/versions.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/versions.go index f106a91f..e7d9d932 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/versions.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/versions.go @@ -12,6 +12,7 @@ import ( "github.com/nats-io/nats.go/jetstream" "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" + gitseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/git" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-sdk/go/process" @@ -19,12 +20,6 @@ import ( "mesh-delivery-builder/internal/versions" ) -// The floor's two module tools (novox/hq design 51 *The floor*); the seat's is `seat:.versions`. -const ( - bridgeTagsTool = "bridge_tags" - requirementsTool = "indexer_requirements" -) - // awaitedVersions is the state versions as versions.Store: bound once the bus holds it (the grants part makes it, issue // 1790); until then every use is an error, so a trunk build is built unnumbered and said (build.Builder.number). type awaitedVersions struct { @@ -103,17 +98,9 @@ func (s awaitedVersions) KeysOf(ctx context.Context, filter string) ([]string, e } // floorVia is the floor's three sources as calls of the builder's own account (design 51 *The floor*): the seat's verb -// versions, the forge bridge's bridge_tags and the indexer's indexer_requirements. A call that fails is that source +// versions, the seat git's tags and the requirements the catalog half's entries name (A-119). A call that fails is that source // silent. -func floorVia(call toolCaller, holder func(seat string) (string, bool)) versions.FloorSources { - sourceTool := func(source, tool string) (string, error) { - seat := versions.SourceSeats[source] - module, ok := holder(seat) - if !ok { - return "", fmt.Errorf("no module is known to hold the seat %s, the source of %s", seat, tool) - } - return module + "." + tool, nil - } +func floorVia(call toolCaller, requirements func(context.Context, string) ([]string, error)) versions.FloorSources { return versions.FloorSources{ Seat: func(_ context.Context, seat, name string) ([]string, error) { raw, err := call("seat:"+seat+".versions", map[string]any{"name": name}) @@ -137,39 +124,27 @@ func floorVia(call toolCaller, holder func(seat string) (string, bool)) versions return out, nil }, Tags: func(_ context.Context, repository, prefix string) ([]string, error) { - tool, err := sourceTool(versions.SourceTags, bridgeTagsTool) - if err != nil { - return nil, err + // The seat git's verb tags, whichever module holds the seat (design 47 G §7, A-82), page by page. + var out []string + for page := int64(1); ; page++ { + raw, err := call("seat:"+gitseat.Seat+"."+gitseat.VerbTags, gitseat.TagsInput{Repository: repository, Prefix: prefix, Page: page}) + if err != nil { + return nil, err + } + var a gitseat.TagsAnswer + if err := json.Unmarshal(raw, &a); err != nil { + return nil, fmt.Errorf("the seat git's tags answered what is no list of tags: %w", err) + } + if a.Error != "" { + return nil, fmt.Errorf("the seat git's tags: %s", a.Error) + } + out = append(out, a.Tags...) + if !a.More { + return out, nil + } } - raw, err := call(tool, map[string]any{"repository": repository, "prefix": prefix}) - if err != nil { - return nil, err - } - var a struct { - Tags []string `json:"tags"` - } - if err := json.Unmarshal(raw, &a); err != nil { - return nil, fmt.Errorf("bridge_tags answered what is no list of tags: %w", err) - } - return a.Tags, nil - }, - Requirements: func(_ context.Context, name string) ([]string, error) { - tool, err := sourceTool(versions.SourceRequirements, requirementsTool) - if err != nil { - return nil, err - } - raw, err := call(tool, map[string]any{"package": name}) - if err != nil { - return nil, err - } - var a struct { - Requirements []string `json:"requirements"` - } - if err := json.Unmarshal(raw, &a); err != nil { - return nil, fmt.Errorf("indexer_requirements answered what is no list of versions: %w", err) - } - return a.Requirements, nil }, + Requirements: requirements, } } diff --git a/modules/mesh-delivery-builder/internal/versions/again.go b/modules/mesh-delivery-builder/internal/versions/again.go index 709007be..4bc81b89 100644 --- a/modules/mesh-delivery-builder/internal/versions/again.go +++ b/modules/mesh-delivery-builder/internal/versions/again.go @@ -9,8 +9,8 @@ import ( // The tools' descriptions. const ( - FloorAgainDescription = "Ask the floor of one name again now, by hand (design 51 *The floor*): the seat's versions, the forge bridge's " + - "bridge_tags and the indexer's indexer_requirements, as the builder asks them by itself when a silent source says something. " + + FloorAgainDescription = "Ask the floor of one name again now, by hand (design 51 *The floor*): the seat's versions, the seat git's " + + "git.tags and the indexer's indexer_requirements, as the builder asks them by itself when a silent source says something. " + "Answers which sources were silent and whether the floor is now known. A name the builder's versions already hold needs no floor: " + "refused, saying so. A hand act." ) diff --git a/modules/mesh-delivery-builder/internal/versions/entrynames.go b/modules/mesh-delivery-builder/internal/versions/entrynames.go index a4c4ffa0..3551f6f4 100644 --- a/modules/mesh-delivery-builder/internal/versions/entrynames.go +++ b/modules/mesh-delivery-builder/internal/versions/entrynames.go @@ -135,3 +135,20 @@ func HolderOf(branches *meshcatalogclient.Branches) func(seat string) (string, b return holders[0], true } } + +// RequirementsFrom answers the versions of a package the entries of every followed half require, each once, in order: what a +// consumer's entry names for it in its packages (design 47 G A-119), the floor's third source. +func RequirementsFrom(all Entries) func(ctx context.Context, name string) ([]string, error) { + return func(_ context.Context, name string) ([]string, error) { + seen := map[string]bool{} + var out []string + for _, e := range all() { + if v := e.Packages[name]; v != "" && !seen[v] { + seen[v] = true + out = append(out, v) + } + } + sort.Strings(out) + return out, nil + } +} diff --git a/modules/mesh-delivery-builder/internal/versions/entrynames_test.go b/modules/mesh-delivery-builder/internal/versions/entrynames_test.go index 32097c78..06e8ecf7 100644 --- a/modules/mesh-delivery-builder/internal/versions/entrynames_test.go +++ b/modules/mesh-delivery-builder/internal/versions/entrynames_test.go @@ -53,3 +53,22 @@ func TestPackageNamesAreReadFromTheCatalogHalfsEntries(t *testing.T) { t.Errorf("CarriersFrom(twin) = %v, want %v", got, want) } } + +// Design 47 G A-119: the floor's requirements are what the catalog half's entries name for the package, each version once. +func TestRequirementsFromAreTheVersionsEntriesNameForAPackage(t *testing.T) { + entries := func() []meshcatalog.CatalogEntry { + return []meshcatalog.CatalogEntry{ + {Module: "a", Packages: map[string]string{"x/sdk": "v0.0.0-2026-aaa", "y": "v1.0.0"}}, + {Module: "b", Packages: map[string]string{"x/sdk": "v0.0.0-2026-bbb"}}, + {Module: "c", Packages: map[string]string{"x/sdk": "v0.0.0-2026-aaa"}}, + {Module: "d"}, + } + } + got, err := RequirementsFrom(entries)(context.Background(), "x/sdk") + if err != nil || !reflect.DeepEqual(got, []string{"v0.0.0-2026-aaa", "v0.0.0-2026-bbb"}) { + t.Errorf("requirements = %v, %v", got, err) + } + if got, _ := RequirementsFrom(entries)(context.Background(), "none"); len(got) != 0 { + t.Errorf("a package no entry names has requirements %v", got) + } +} diff --git a/modules/mesh-delivery-builder/internal/versions/floor.go b/modules/mesh-delivery-builder/internal/versions/floor.go index 516e7776..df838ce2 100644 --- a/modules/mesh-delivery-builder/internal/versions/floor.go +++ b/modules/mesh-delivery-builder/internal/versions/floor.go @@ -17,9 +17,9 @@ import ( type FloorSources struct { // Seat is `seat:.versions` of the registry seat the name is published to. Seat func(ctx context.Context, seat, name string) ([]string, error) - // Tags is the forge bridge's bridge_tags: the git tags of the repository the name is built from, under prefix. + // Tags is the seat git's tags: the git tags of the repository the name is built from, under prefix. Tags func(ctx context.Context, repository, prefix string) ([]string, error) - // Requirements is the indexer's indexer_requirements: the highest pseudo-versions consumers require. + // Requirements are the versions of a package the catalog half's entries require (design 47 G A-119). Requirements func(ctx context.Context, name string) ([]string, error) } @@ -64,9 +64,9 @@ func (s FloorSources) Ask(ctx context.Context, of FloorOf) (Floor, map[string]st const ( // WakePublished is the holder's `published` on the builder's durable consumer of `.events`. WakePublished = "published" - // WakeForge is the forge bridge's next message on its stream `forge`. - WakeForge = "forge" - // WakeAccepted is the indexer's next `manifest-accepted`. + // WakeRevision is a catalog revision the builder hears (the catalog half's entry changed): tags and requirements are asked again. + WakeRevision = "catalog-revision" + // WakeAccepted is the next `manifest-accepted` of the seat mesh-catalog. WakeAccepted = "manifest-accepted" // WakeCleared is the condition keeper's clear of a silent module's health condition on `mesh-conditions`. WakeCleared = "condition-cleared" @@ -75,7 +75,7 @@ const ( ) // sourceOfWake is the floor source each event says something of; a clear names the source by its module. -var sourceOfWake = map[string]string{WakePublished: SourceSeat, WakeForge: SourceTags, WakeAccepted: SourceRequirements} +var sourceOfWake = map[string]string{WakePublished: SourceSeat, WakeAccepted: SourceRequirements} // SourceSeats are the seats whose holders are the floor's tag and requirement sources (design 47 G A-82): the seat git (tags) // and the seat mesh-catalog (requirements), the holders read from the seat list and the catalog half. @@ -297,7 +297,11 @@ func (fw *FloorWaits) Wake(kind, seat, module, name string) []string { } default: src := sourceOfWake[kind] - if slices.Contains(w.Silent, src) && (kind != WakePublished || seat == "" || seat == w.Seat) { + if kind == WakeRevision { + if slices.Contains(w.Silent, SourceTags) || slices.Contains(w.Silent, SourceRequirements) { + out = append(out, n) + } + } else if slices.Contains(w.Silent, src) && (kind != WakePublished || seat == "" || seat == w.Seat) { out = append(out, n) } } diff --git a/modules/mesh-delivery-builder/internal/versions/versions.go b/modules/mesh-delivery-builder/internal/versions/versions.go index 8d6cf194..d84367d2 100644 --- a/modules/mesh-delivery-builder/internal/versions/versions.go +++ b/modules/mesh-delivery-builder/internal/versions/versions.go @@ -184,21 +184,21 @@ func Patch(series, version string) (int, bool) { } // Floor is what the three sources answered for a name (design 51 *The floor*, ADR 0606 §3): the seat's versions, the -// package's git tags (the forge bridge's bridge_tags) and the pseudo-versions consumers require (the indexer's +// package's git tags (the seat git's tags) and the pseudo-versions consumers require (the catalog half's // indexer_requirements). A source not answered is listed in Silent. type Floor struct { Seat []string `json:"seat,omitempty"` Tags []string `json:"tags,omitempty"` Requirements []string `json:"requirements,omitempty"` - // Silent names the sources that did not answer: "seat", "bridge_tags", "indexer_requirements". + // Silent names the sources that did not answer: "seat", "git.tags", "catalog.requirements". Silent []string `json:"silent,omitempty"` } // The floor's sources, as Floor.Silent names them. const ( SourceSeat = "seat" - SourceTags = "bridge_tags" - SourceRequirements = "indexer_requirements" + SourceTags = "git.tags" + SourceRequirements = "catalog.requirements" ) // Known is whether every source answered. diff --git a/modules/mesh-delivery-builder/internal/versions/versions_test.go b/modules/mesh-delivery-builder/internal/versions/versions_test.go index 0e3ca7f3..30b9f44c 100644 --- a/modules/mesh-delivery-builder/internal/versions/versions_test.go +++ b/modules/mesh-delivery-builder/internal/versions/versions_test.go @@ -256,12 +256,12 @@ func TestTheBuildersVersions(t *testing.T) { } f, why := src.Ask(ctx, FloorOf{Seat: "oci-artifact-registry", Name: "m/program"}) if f.Known() || !reflect.DeepEqual(f.Silent, []string{SourceTags}) || why[SourceTags] != "no answer" { - t.Fatalf("Ask = %+v, %v; want bridge_tags silent", f, why) + t.Fatalf("Ask = %+v, %v; want git.tags silent", f, why) } holders := map[string]string{SourceSeats[SourceTags]: "mesh-delivery-forge-bridge", SourceSeats[SourceRequirements]: "mesh-delivery-indexer"} fw := FloorWaits{Holder: func(seat string) (string, bool) { h, ok := holders[seat]; return h, ok }} w := fw.Wait("m/program", "oci-artifact-registry", "distribution", f, why, time.Unix(0, 0)) - if !strings.Contains(w.Finding(), "bridge_tags (no answer)") { + if !strings.Contains(w.Finding(), "git.tags (no answer)") { t.Errorf("finding = %q", w.Finding()) } for _, c := range []struct { @@ -270,7 +270,7 @@ func TestTheBuildersVersions(t *testing.T) { }{ {WakePublished, "oci-artifact-registry", "", "", nil}, // the seat answered already: a publish wakes nothing {WakeAccepted, "", "", "", nil}, - {WakeForge, "", "", "", []string{"m/program"}}, + {WakeRevision, "", "", "", []string{"m/program"}}, {WakeCleared, "", "mesh-delivery-forge-bridge", "", []string{"m/program"}}, // recovered with nothing to say {WakeCleared, "", "mesh-delivery-indexer", "", nil}, {WakeByHand, "", "", "m/program", []string{"m/program"}}, @@ -295,7 +295,7 @@ func TestTheBuildersVersions(t *testing.T) { kept := keeperMem{} restarted := &FloorWaits{} restarted.Keep(kept, []FloorWait{{Name: "m/held", Seat: SeatImages, Silent: []string{SourceTags}, Since: time.Unix(5, 0)}}) - if got := restarted.Wake(WakeForge, "", "", ""); !reflect.DeepEqual(got, []string{"m/held"}) { + if got := restarted.Wake(WakeRevision, "", "", ""); !reflect.DeepEqual(got, []string{"m/held"}) { t.Errorf("a wait held in floor-waits woke %v after a restart, want m/held", got) } restarted.Wait("m/new", SeatNPM, "", Floor{Silent: []string{SourceSeat}}, nil, time.Unix(9, 0)) diff --git a/modules/mesh-delivery-builder/manifest_test.go b/modules/mesh-delivery-builder/manifest_test.go index 53aabafd..8fbbe0b4 100644 --- a/modules/mesh-delivery-builder/manifest_test.go +++ b/modules/mesh-delivery-builder/manifest_test.go @@ -139,10 +139,10 @@ func TestManifestDeclarations(t *testing.T) { // 1607/AC1 t.Errorf("reads %s: the indexer's and the assigner's state is read by their tools and views families alone", r) } } - // Design 51 *The floor*: the floor's three sources, the registry seats' versions, bridge_tags and indexer_requirements. + // Design 51 *The floor*: the floor's three sources, the registry seats' versions, git.tags and indexer_requirements. // The watchdogs seat's presence says a node's machine is heard (issue 1966, ADR 0596 §2, ADR 0668 §3). - if want := []string{"mesh-delivery-assigner.assigner_read_assignments", "mesh-delivery-forge-bridge.bridge_tags", "mesh-delivery-indexer.indexer_requirements", "seat:mesh-catalog.entry", "seat:mesh-catalog.registrations", "seat:mesh-catalog.seat-definitions", "seat:mesh-catalog.revision", "seat:mesh-delivery.source", "seat:container-image-registry.publish", "seat:container-image-registry.remove", "seat:container-image-registry.versions", - "seat:git.tag", + if want := []string{"mesh-delivery-assigner.assigner_read_assignments", "seat:mesh-catalog.entry", "seat:mesh-catalog.registrations", "seat:mesh-catalog.seat-definitions", "seat:mesh-catalog.revision", "seat:mesh-delivery.source", "seat:container-image-registry.publish", "seat:container-image-registry.remove", "seat:container-image-registry.versions", + "seat:git.tag", "seat:git.tags", "seat:go-package-registry.publish", "seat:go-package-registry.remove", "seat:go-package-registry.versions", "seat:mesh-artifact-sweep.held", "seat:mesh-builds.reserved", "seat:mesh-catalog.versions", "seat:mesh-hand-acts.hand-act", "seat:mesh-module-assigner.assignments", "seat:mesh-watchdogs.presence", "seat:node-build-go.drop", "seat:node-build-image.drop", "seat:node-build-node.drop", "seat:node-engine.manifest-reads", "seat:npm-package-registry.publish", "seat:npm-package-registry.remove", "seat:npm-package-registry.versions", "seat:oci-artifact-registry.publish", "seat:oci-artifact-registry.remove", "seat:oci-artifact-registry.versions"}; !reflect.DeepEqual(sorted(m.Invokes), sorted(want)) { t.Errorf("invokes = %v, want %v", m.Invokes, want) @@ -156,7 +156,7 @@ func TestManifestDeclarations(t *testing.T) { // 1607/AC1 if want := []string{"container-image-registry.kept", "container-image-registry.published", "container-image-registry.refused", "container-image-registry.removed", "go-package-registry.kept", "go-package-registry.published", "go-package-registry.refused", "go-package-registry.removed", "mesh-artifact-sweep.artifacts-gone", "mesh-catalog.manifest-accepted", "mesh-catalog.seat-definition-changed", "mesh-catalog.seat-definition-removed", "mesh-catalog.version-released", "mesh-catalog.versions-filled", - "mesh-conditions.condition-cleared", "mesh-conditions.findings-asked", "mesh-delivery-assigner.views.assignments", "mesh-delivery-forge-bridge.forge.**", + "mesh-conditions.condition-cleared", "mesh-conditions.findings-asked", "mesh-delivery-assigner.views.assignments", "mesh-catalog.catalog-revision", "mesh-catalog.registration-changed", "mesh-delivery.source-moved", "node-build-go.toolchain-reported", "node-build-node.toolchain-reported", "node-engine.manifest-reads-changed", "node-engine.running", "npm-package-registry.kept", "npm-package-registry.published", "npm-package-registry.refused", "npm-package-registry.removed", "oci-artifact-registry.kept", "oci-artifact-registry.published", "oci-artifact-registry.refused", "oci-artifact-registry.removed", "operator-channel.decided"}; !reflect.DeepEqual(sorted(m.Consumes), sorted(want)) { @@ -262,6 +262,9 @@ func TestManifestProcess(t *testing.T) { // 1607/AC1 } } +// Design 47 G A-107 relaxes ADR 0437 §8 for a seat's verbs and its accepted kinds: the process answers them directly over the bus, +// the verbs through mesh-sdk's conn.AnswerSeat (a call the guard below does not forbid), the accepted kind `step` through the one +// named subscription. Any other request subject, and every ticker, timer and sleep, stays refused. func TestServesNoRequestSubjectAndHoldsNoTicker(t *testing.T) { // 1607/AC6 (ADR 0437 §8: no timer, a wait is a context's bound) forbidden := []string{"Subscribe(", "QueueSubscribe", "ChanSubscribe", "time.NewTicker", "time.Tick", "time.NewTimer", "time.AfterFunc", "time.After(", "time.Sleep"} var files int @@ -276,12 +279,12 @@ func TestServesNoRequestSubjectAndHoldsNoTicker(t *testing.T) { // 1607/AC6 (ADR return err } for _, f := range forbidden { - // The one subscription is the seat mesh-builds' accepted kind `step`, the runner's build ask (design 47 G A-97). + // The one subscription is the seat mesh-builds' accepted kind `step`, the runner's build ask (design 47 G A-97, A-107). if f == "Subscribe(" && filepath.ToSlash(path) == "cmd/mesh-delivery-builder/step.go" { continue } if strings.Contains(string(src), f) { - t.Errorf("%s holds %s: the builder serves no request subject and holds no timer", path, f) + t.Errorf("%s holds %s: the builder serves no request subject but its seat's verbs and accepted kinds (A-107) and holds no timer", path, f) } } return nil diff --git a/modules/mesh-delivery-builder/module.json b/modules/mesh-delivery-builder/module.json index bf2e4224..9702b31b 100644 --- a/modules/mesh-delivery-builder/module.json +++ b/modules/mesh-delivery-builder/module.json @@ -60,7 +60,6 @@ "mesh-catalog.version-released", "mesh-catalog.versions-filled", "mesh-conditions.condition-cleared", - "mesh-delivery-forge-bridge.forge.**", "node-build-go.toolchain-reported", "node-build-node.toolchain-reported", "node-engine.manifest-reads-changed", @@ -80,11 +79,10 @@ "mesh-delivery.source-moved" ], "invokes": [ - "mesh-delivery-forge-bridge.bridge_tags", - "mesh-delivery-indexer.indexer_requirements", "seat:container-image-registry.remove", "seat:container-image-registry.versions", "seat:git.tag", + "seat:git.tags", "seat:go-package-registry.remove", "seat:go-package-registry.versions", "seat:mesh-artifact-sweep.held",