Builder: the manifest names no other module, the floor reads the seat git and the catalog half

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
This commit is contained in:
2026-11-12 01:18:40 +01:00
parent fd8f019751
commit 1eb481ccc8
14 changed files with 111 additions and 127 deletions
@@ -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)
@@ -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"})
@@ -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.
@@ -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"
@@ -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")
}
@@ -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:<registry>.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,
}
}
@@ -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."
)
@@ -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
}
}
@@ -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)
}
}
@@ -17,9 +17,9 @@ import (
type FloorSources struct {
// Seat is `seat:<registry>.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 `<seat>.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)
}
}
@@ -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.
@@ -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))
@@ -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
+1 -3
View File
@@ -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",